cli-lsp-client 1.14.1 → 1.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,64 @@
1
+ #!/bin/sh
2
+ set -e
3
+
4
+ if [ -n "$CLI_LSP_CLIENT_BIN_PATH" ]; then
5
+ resolved="$CLI_LSP_CLIENT_BIN_PATH"
6
+ else
7
+ # Get the real path of this script, resolving symlinks
8
+ script_path="$0"
9
+ while [ -L "$script_path" ]; do
10
+ link_target="$(readlink "$script_path")"
11
+ case "$link_target" in
12
+ /*) script_path="$link_target" ;;
13
+ *) script_path="$(dirname "$script_path")/$link_target" ;;
14
+ esac
15
+ done
16
+ script_dir="$(dirname "$script_path")"
17
+ script_dir="$(cd "$script_dir" && pwd)"
18
+
19
+ # Detect platform and architecture
20
+ case "$(uname -s)" in
21
+ Darwin) platform="darwin" ;;
22
+ Linux) platform="linux" ;;
23
+ MINGW*|CYGWIN*|MSYS*) platform="win32" ;;
24
+ *) platform="$(uname -s | tr '[:upper:]' '[:lower:]')" ;;
25
+ esac
26
+
27
+ case "$(uname -m)" in
28
+ x86_64|amd64) arch="x64" ;;
29
+ aarch64) arch="arm64" ;;
30
+ armv7l) arch="arm" ;;
31
+ *) arch="$(uname -m)" ;;
32
+ esac
33
+
34
+ name="cli-lsp-client-${platform}-${arch}"
35
+ binary="cli-lsp-client"
36
+ [ "$platform" = "win32" ] && binary="cli-lsp-client.exe"
37
+
38
+ # Search for binary starting from script location
39
+ resolved=""
40
+ current_dir="$script_dir"
41
+ while [ "$current_dir" != "/" ]; do
42
+ # Check dist/ first (for local development)
43
+ candidate="$current_dir/dist/$name/bin/$binary"
44
+ if [ -f "$candidate" ]; then
45
+ resolved="$candidate"
46
+ break
47
+ fi
48
+ # Then check node_modules/ (for production installs)
49
+ candidate="$current_dir/node_modules/$name/bin/$binary"
50
+ if [ -f "$candidate" ]; then
51
+ resolved="$candidate"
52
+ break
53
+ fi
54
+ current_dir="$(dirname "$current_dir")"
55
+ done
56
+
57
+ if [ -z "$resolved" ]; then
58
+ printf "Failed to find %s binary for your platform\n" "$name" >&2
59
+ exit 1
60
+ fi
61
+ fi
62
+
63
+ # Execute the binary
64
+ exec "$resolved" "$@"
@@ -0,0 +1,57 @@
1
+ @echo off
2
+ setlocal enabledelayedexpansion
3
+
4
+ if defined CLI_LSP_CLIENT_BIN_PATH (
5
+ set "resolved=%CLI_LSP_CLIENT_BIN_PATH%"
6
+ goto :execute
7
+ )
8
+
9
+ rem Get the directory of this script
10
+ set "script_dir=%~dp0"
11
+ set "script_dir=%script_dir:~0,-1%"
12
+
13
+ rem Detect architecture
14
+ if "%PROCESSOR_ARCHITECTURE%"=="AMD64" (
15
+ set "arch=x64"
16
+ ) else if "%PROCESSOR_ARCHITECTURE%"=="ARM64" (
17
+ set "arch=arm64"
18
+ ) else (
19
+ set "arch=x64"
20
+ )
21
+
22
+ set "name=cli-lsp-client-windows-!arch!"
23
+ set "binary=cli-lsp-client.exe"
24
+
25
+ rem Search for the binary
26
+ set "resolved="
27
+ set "current_dir=%script_dir%"
28
+
29
+ :search_loop
30
+ rem Check dist/ first (for local development)
31
+ set "candidate=%current_dir%\dist\%name%\bin\%binary%"
32
+ if exist "%candidate%" (
33
+ set "resolved=%candidate%"
34
+ goto :execute
35
+ )
36
+
37
+ rem Then check node_modules/ (for production installs)
38
+ set "candidate=%current_dir%\node_modules\%name%\bin\%binary%"
39
+ if exist "%candidate%" (
40
+ set "resolved=%candidate%"
41
+ goto :execute
42
+ )
43
+
44
+ for %%i in ("%current_dir%") do set "parent_dir=%%~dpi"
45
+ set "parent_dir=%parent_dir:~0,-1%"
46
+
47
+ if "%current_dir%"=="%parent_dir%" goto :not_found
48
+ set "current_dir=%parent_dir%"
49
+ goto :search_loop
50
+
51
+ :not_found
52
+ echo Failed to find %name% binary for your platform >&2
53
+ exit /b 1
54
+
55
+ :execute
56
+ start /b /wait "" "%resolved%" %*
57
+ exit /b %ERRORLEVEL%
package/package.json CHANGED
@@ -1,53 +1,28 @@
1
1
  {
2
2
  "name": "cli-lsp-client",
3
- "version": "1.14.1",
3
+ "version": "1.16.0",
4
+ "description": "CLI tool supporting claude code hooks for fast LSP diagnostics with background daemon and multi-project support",
4
5
  "repository": {
5
6
  "type": "git",
6
7
  "url": "git+https://github.com/eli0shin/cli-lsp-client.git"
7
8
  },
8
- "main": "src/cli.ts",
9
- "devDependencies": {
10
- "@eslint/js": "^9.33.0",
11
- "@modelcontextprotocol/sdk": "^1.17.3",
12
- "@types/bun": "latest",
13
- "@types/node": "latest",
14
- "eslint": "^9.33.0",
15
- "eslint-config-prettier": "10.1.8",
16
- "eslint-import-resolver-typescript": "^4.4.4",
17
- "eslint-plugin-import-x": "^4.16.1",
18
- "eslint-plugin-unused-imports": "^4.1.4",
19
- "globals": "^16.3.0",
20
- "prettier": "3.6.2",
21
- "typescript": "latest",
22
- "typescript-eslint": "^8.39.1",
23
- "vscode-jsonrpc": "^8.2.1",
24
- "vscode-languageserver-types": "^3.17.5",
25
- "zod": "3.25"
26
- },
27
- "bin": {
28
- "cli-lsp-client": "cli-lsp-client"
29
- },
30
9
  "bugs": {
31
10
  "url": "https://github.com/eli0shin/cli-lsp-client/issues"
32
11
  },
33
- "description": "CLI tool supporting claude code hooks for fast LSP diagnostics with background daemon and multi-project support",
34
- "files": [
35
- "cli-lsp-client"
36
- ],
37
12
  "homepage": "https://github.com/eli0shin/cli-lsp-client#readme",
38
13
  "license": "MIT",
14
+ "bin": {
15
+ "cli-lsp-client": "./bin/cli-lsp-client"
16
+ },
39
17
  "scripts": {
40
- "build": "bun build src/cli.ts --compile --outfile cli-lsp-client",
41
- "dev": "bun run src/cli.ts",
42
- "typecheck": "bun tsc --noEmit",
43
- "lint": "eslint .",
44
- "lint:fix": "eslint . --fix",
45
- "format": "prettier --check .",
46
- "format:fix": "prettier --write .",
47
- "test": "bun test",
48
- "test:watch": "bun test tests/ --watch",
49
- "prepublishOnly": "bun test && bun run build",
50
- "postinstall": "node scripts/postinstall.js"
18
+ "preinstall": "node ./preinstall.mjs",
19
+ "postinstall": "node ./postinstall.mjs"
51
20
  },
52
- "type": "module"
53
- }
21
+ "optionalDependencies": {
22
+ "cli-lsp-client-windows-x64": "1.15.0",
23
+ "cli-lsp-client-linux-arm64": "1.15.0",
24
+ "cli-lsp-client-linux-x64": "1.15.0",
25
+ "cli-lsp-client-darwin-x64": "1.15.0",
26
+ "cli-lsp-client-darwin-arm64": "1.15.0"
27
+ }
28
+ }
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "fs"
4
+ import path from "path"
5
+ import os from "os"
6
+ import { fileURLToPath } from "url"
7
+ import { createRequire } from "module"
8
+
9
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
10
+ const require = createRequire(import.meta.url)
11
+
12
+ // Skip postinstall when running from source (dev/CI)
13
+ function shouldSkipPostinstall() {
14
+ const packageRoot = path.join(__dirname, "..")
15
+ const srcDir = path.join(packageRoot, "src")
16
+ if (fs.existsSync(srcDir)) {
17
+ console.log("Skipping postinstall (running from source)")
18
+ return true
19
+ }
20
+ return false
21
+ }
22
+
23
+ function detectPlatformAndArch() {
24
+ let platform
25
+ switch (os.platform()) {
26
+ case "darwin":
27
+ platform = "darwin"
28
+ break
29
+ case "linux":
30
+ platform = "linux"
31
+ break
32
+ case "win32":
33
+ platform = "windows"
34
+ break
35
+ default:
36
+ platform = os.platform()
37
+ }
38
+
39
+ let arch
40
+ switch (os.arch()) {
41
+ case "x64":
42
+ arch = "x64"
43
+ break
44
+ case "arm64":
45
+ arch = "arm64"
46
+ break
47
+ default:
48
+ arch = os.arch()
49
+ }
50
+
51
+ return { platform, arch }
52
+ }
53
+
54
+ function findBinary() {
55
+ const { platform, arch } = detectPlatformAndArch()
56
+ const packageName = `cli-lsp-client-${platform}-${arch}`
57
+ const binary = platform === "windows" ? "cli-lsp-client.exe" : "cli-lsp-client"
58
+
59
+ try {
60
+ const packageJsonPath = require.resolve(`${packageName}/package.json`)
61
+ const packageDir = path.dirname(packageJsonPath)
62
+ const binaryPath = path.join(packageDir, "bin", binary)
63
+
64
+ if (!fs.existsSync(binaryPath)) {
65
+ throw new Error(`Binary not found at ${binaryPath}`)
66
+ }
67
+
68
+ return binaryPath
69
+ } catch (error) {
70
+ throw new Error(`Could not find package ${packageName}: ${error.message}`)
71
+ }
72
+ }
73
+
74
+ async function regenerateWindowsCmdWrappers() {
75
+ console.log("Windows + npm: Rebuilding bin links")
76
+
77
+ try {
78
+ const { execSync } = require("child_process")
79
+ const pkgPath = path.join(__dirname, "..")
80
+
81
+ const isGlobal = process.env.npm_config_global === "true" || pkgPath.includes(path.join("npm", "node_modules"))
82
+
83
+ const cmd = `npm rebuild cli-lsp-client --ignore-scripts${isGlobal ? " -g" : ""}`
84
+ const opts = {
85
+ stdio: "inherit",
86
+ shell: true,
87
+ ...(isGlobal ? {} : { cwd: path.join(pkgPath, "..", "..") }),
88
+ }
89
+
90
+ execSync(cmd, opts)
91
+ console.log("Successfully rebuilt npm bin links")
92
+ } catch (error) {
93
+ console.error("Error rebuilding npm links:", error.message)
94
+ }
95
+ }
96
+
97
+ async function stopExistingDaemons() {
98
+ try {
99
+ const { spawn } = require("child_process")
100
+ const child = spawn("cli-lsp-client", ["stop-all"], { stdio: "inherit" })
101
+ child.on("error", () => {})
102
+ } catch (error) {
103
+ // Ignore errors - daemon might not be running
104
+ }
105
+ }
106
+
107
+ async function main() {
108
+ if (shouldSkipPostinstall()) {
109
+ return
110
+ }
111
+
112
+ try {
113
+ if (os.platform() === "win32") {
114
+ if (process.env.npm_config_user_agent?.startsWith("npm")) {
115
+ await regenerateWindowsCmdWrappers()
116
+ }
117
+ await stopExistingDaemons()
118
+ return
119
+ }
120
+
121
+ const binaryPath = findBinary()
122
+ const binScript = path.join(__dirname, "..", "bin", "cli-lsp-client")
123
+
124
+ // Create symlink to actual binary
125
+ if (fs.existsSync(binScript)) {
126
+ fs.unlinkSync(binScript)
127
+ }
128
+
129
+ fs.symlinkSync(binaryPath, binScript)
130
+ console.log(`Binary symlinked: ${binScript} -> ${binaryPath}`)
131
+
132
+ // Stop existing daemons
133
+ await stopExistingDaemons()
134
+ } catch (error) {
135
+ console.error("Failed to create binary symlink:", error.message)
136
+ process.exit(1)
137
+ }
138
+ }
139
+
140
+ try {
141
+ main()
142
+ } catch (error) {
143
+ console.error("Postinstall error:", error.message)
144
+ process.exit(0)
145
+ }
package/preinstall.mjs ADDED
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "fs"
4
+ import path from "path"
5
+ import os from "os"
6
+ import { fileURLToPath } from "url"
7
+
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
9
+
10
+ function main() {
11
+ if (os.platform() !== "win32") {
12
+ console.log("Non-Windows platform, skipping preinstall")
13
+ return
14
+ }
15
+
16
+ console.log("Windows: Modifying package.json bin entry")
17
+
18
+ const packageJsonPath = path.join(__dirname, "..", "package.json")
19
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"))
20
+
21
+ // Point to .cmd file on Windows
22
+ packageJson.bin = {
23
+ "cli-lsp-client": "./bin/cli-lsp-client.cmd",
24
+ }
25
+
26
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2))
27
+ console.log("Updated package.json bin to use cli-lsp-client.cmd")
28
+
29
+ // Remove Unix script
30
+ const unixScript = path.join(__dirname, "..", "bin", "cli-lsp-client")
31
+ if (fs.existsSync(unixScript)) {
32
+ fs.unlinkSync(unixScript)
33
+ }
34
+ }
35
+
36
+ try {
37
+ main()
38
+ } catch (error) {
39
+ console.error("Preinstall error:", error.message)
40
+ process.exit(0)
41
+ }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 cli-lsp-client
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
package/README.md DELETED
@@ -1,554 +0,0 @@
1
- # cli-lsp-client
2
-
3
- CLI tool for getting LSP diagnostics. Uses a background daemon to keep LSP servers running.
4
-
5
- ## Features
6
-
7
- - Get diagnostics from LSP servers
8
- - Get hover information for symbols (functions, variables, types)
9
- - Background daemon for fast repeated requests
10
- - Built in Claude Code hook to provide feedback on file edit tool calls
11
- - Comprehensive daemon management (`list`, `stop-all` commands)
12
- - Multi-project support with isolated daemon instances per directory
13
- - [Custom language server support via config files](#custom-language-servers-config-file)
14
-
15
- ## Supported Languages
16
-
17
- | Language | LSP Server | Auto-installed | Notes |
18
- | --------------------- | ------------------------------ | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- |
19
- | TypeScript/JavaScript | `typescript-language-server` | ✓ (via bunx) | `.ts`, `.tsx`, `.js`, `.jsx`, `.mjs`, `.cjs`, `.mts`, `.cts` |
20
- | Python | `pyright-langserver` | ✓ (via bunx) | `.py`, `.pyi` |
21
- | JSON | `vscode-json-language-server` | ✓ (via vscode-langservers-extracted) | `.json`, `.jsonc` - includes schema validation |
22
- | CSS | `vscode-css-language-server` | ✓ (via vscode-langservers-extracted) | `.css`, `.scss`, `.sass`, `.less` |
23
- | YAML | `yaml-language-server` | ✓ (via bunx) | `.yaml`, `.yml` - includes schema validation |
24
- | Bash/Shell | `bash-language-server` | ✓ (via bunx) | `.sh`, `.bash`, `.zsh` - **requires shellcheck** (`brew install shellcheck`) |
25
- | GraphQL | `graphql-language-service-cli` | ✓ (via bunx) | `.graphql`, `.gql` |
26
- | **R** | **R languageserver** | **✗** | **`.r`, `.R`, `.rmd`, `.Rmd` - see [R Installation](#r-installation-guide) below** |
27
- | **C#** | **OmniSharp-Roslyn** | **✗** | **`.cs` - see [C# Installation](#c-installation-guide) below** |
28
- | **Swift** | **SourceKit-LSP** | **✗** | **`.swift` - see [Swift Configuration](#swift-configuration) below** |
29
- | Go | `gopls` | ✗ | Requires manual install: `go install golang.org/x/tools/gopls@latest` |
30
- | Java | `jdtls` (Eclipse JDT) | ✗ | `.java` - see [Java Installation](#java-installation-guide) below |
31
- | Lua | `lua-language-server` | ✗ | `.lua` - requires manual install via package manager (brew, scoop) or from [releases](https://github.com/LuaLS/lua-language-server/releases) |
32
-
33
- ## How It Works
34
-
35
- - Daemon starts automatically when needed
36
- - LSP servers spawn based on file type
37
- - Finds project roots using config files (tsconfig.json, etc.)
38
- - Servers stay running for subsequent requests
39
-
40
- ## Claude Code Integration
41
-
42
- ### MCP Server
43
-
44
- Add as an MCP server to enable Claude to access symbol definitions and hover information:
45
-
46
- ```bash
47
- claude mcp add lsp --scope user -- bunx cli-lsp-client mcp-server
48
- ```
49
-
50
- ### Real-time Diagnostics Hook
51
-
52
- Get instant diagnostic feedback for TypeScript, Python, JSON, CSS, YAML, Bash, GraphQL, R, C#, Swift, Go, Java, and Lua files as you edit in Claude Code.
53
-
54
- #### Setup
55
-
56
- Configure Claude Code to use the built-in hook command:
57
-
58
- ```json
59
- {
60
- "$schema": "https://json.schemastore.org/claude-code-settings.json",
61
- "hooks": {
62
- "SessionStart": [
63
- {
64
- "matcher": "startup|resume",
65
- "hooks": [
66
- {
67
- "type": "command",
68
- "command": "npx -y cli-lsp-client start"
69
- }
70
- ]
71
- }
72
- ],
73
- "PostToolUse": [
74
- {
75
- "matcher": "Edit|MultiEdit|Write",
76
- "hooks": [
77
- {
78
- "type": "command",
79
- "command": "npx -y cli-lsp-client claude-code-hook"
80
- }
81
- ]
82
- }
83
- ]
84
- }
85
- }
86
- ```
87
-
88
- #### How It Works
89
-
90
- - **SessionStart**: Automatically starts LSP servers when Claude Code starts for faster initial diagnostics
91
- - **PostToolUse**: Runs diagnostics after each file edit (Edit, MultiEdit, Write tools)
92
- - Built-in file filtering for all supported languages (16 file types)
93
- - Shows errors, warnings, and hints inline
94
- - Graceful error handling - never breaks your editing experience
95
- - Uses the same fast daemon as the regular diagnostics command
96
-
97
- #### Example Output
98
-
99
- When you save a file with errors, you'll see immediate feedback:
100
-
101
- ```
102
- Edit operation feedback:
103
- - [npx -y cli-lsp-client claude-code-hook]:
104
- ERROR at line 3, column 9:
105
- Type 'number' is not assignable to type 'string'.
106
- Source: typescript
107
- Code: 2322
108
- ```
109
-
110
- ## Custom Language Servers (Config File)
111
-
112
- You can extend the built-in language servers by creating a custom configuration file. This allows you to add support for any LSP server not included by default.
113
-
114
- ### Configuration File
115
-
116
- Create a config file at `~/.config/cli-lsp-client/settings.json` (default location) or use `--config-file` to specify a custom path:
117
-
118
- ```json
119
- {
120
- "servers": [
121
- {
122
- "id": "svelte",
123
- "extensions": [".svelte"],
124
- "rootPatterns": ["svelte.config.js", "package.json"],
125
- "command": ["bunx", "svelte-language-server", "--stdio"],
126
- "env": {
127
- "NODE_ENV": "development"
128
- },
129
- "initialization": {
130
- "settings": {
131
- "svelte": {
132
- "compilerWarnings": true
133
- }
134
- }
135
- }
136
- }
137
- ],
138
- "languageExtensions": {
139
- ".svelte": "svelte"
140
- }
141
- }
142
- ```
143
-
144
- ### Using Custom Config
145
-
146
- **Default config file location:**
147
-
148
- ```bash
149
- # Uses ~/.config/cli-lsp-client/settings.json automatically
150
- npx cli-lsp-client diagnostics Component.svelte
151
- ```
152
-
153
- **Custom config file location:**
154
-
155
- ```bash
156
- # Specify custom config file path
157
- npx cli-lsp-client --config-file ./my-config.json diagnostics Component.svelte
158
- npx cli-lsp-client --config-file ./my-config.json hover Component.svelte myFunction
159
- npx cli-lsp-client --config-file ./my-config.json status
160
- ```
161
-
162
- **Important:** When using `--config-file`, you must include it on every command. The CLI automatically restarts the daemon when switching between different config files to ensure the correct language servers are loaded.
163
-
164
- ### Config File Schema
165
-
166
- - `servers`: Array of custom language server definitions
167
- - `id`: Unique identifier for the server
168
- - `extensions`: File extensions this server handles (e.g. `[".svelte"]`)
169
- - `rootPatterns`: Files/patterns used to detect project root (e.g. `["package.json"]`)
170
- - `command`: Command array to start the LSP server (e.g. `["bunx", "svelte-language-server", "--stdio"]`)
171
- - `env`: Optional environment variables for the server process
172
- - `initialization`: Optional LSP initialization parameters
173
-
174
- - `languageExtensions`: Maps file extensions to LSP language identifiers
175
-
176
- ## Usage
177
-
178
- ### Get Diagnostics
179
-
180
- ```bash
181
- # Check a TypeScript file
182
- npx cli-lsp-client diagnostics src/example.ts
183
-
184
- # Check any supported file type
185
- npx cli-lsp-client diagnostics app.py
186
- npx cli-lsp-client diagnostics main.go
187
- npx cli-lsp-client diagnostics analysis.R
188
- npx cli-lsp-client diagnostics Program.cs
189
-
190
- # Check Swift files (requires config file)
191
- npx cli-lsp-client diagnostics Sources/App/main.swift
192
- ```
193
-
194
- Exit codes: 0 for no issues, 2 for issues found.
195
-
196
- ```bash
197
- $ npx cli-lsp-client diagnostics error.ts
198
- ERROR at line 5, column 20:
199
- Argument of type 'string' is not assignable to parameter of type 'number'.
200
- Source: typescript
201
- Code: 2345
202
- ```
203
-
204
- ### Get Hover Information
205
-
206
- ```bash
207
- # Get hover info for a function
208
- npx cli-lsp-client hover src/main.ts myFunction
209
-
210
- # Get hover info for a variable or type
211
- npx cli-lsp-client hover app.py MyClass
212
- npx cli-lsp-client hover analysis.R mean
213
- npx cli-lsp-client hover Program.cs Console
214
-
215
- # Get hover info for Swift symbols (requires config file)
216
- npx cli-lsp-client hover Sources/App/main.swift greetUser
217
- ```
218
-
219
- ````bash
220
- $ npx cli-lsp-client hover src/client.ts runCommand
221
- Location: src/client.ts:370:17
222
- ```typescript
223
- export function runCommand(command: string, commandArgs: string[]): Promise<void>
224
- ````
225
-
226
- ````
227
-
228
- ### Daemon Management
229
-
230
- ```bash
231
- # Check daemon status with uptime and running language servers
232
- npx cli-lsp-client status
233
-
234
- # List all running daemons across directories
235
- npx cli-lsp-client list
236
-
237
- # Stop current directory's daemon
238
- npx cli-lsp-client stop
239
-
240
- # Stop all daemons across all directories (useful after package updates)
241
- npx cli-lsp-client stop-all
242
-
243
- # Show version
244
- npx cli-lsp-client --version
245
-
246
- # Show help
247
- npx cli-lsp-client help
248
- ````
249
-
250
- The `status` command shows the current daemon's uptime and running language servers:
251
-
252
- ```bash
253
- $ npx cli-lsp-client status
254
- LSP Daemon Status
255
- ================
256
- PID: 33502
257
- Uptime: 1m 38s
258
-
259
- Language Servers:
260
- - typescript (.) - running 1m 33s
261
- - pyright (.) - running 1m 10s
262
-
263
- Total: 2 language servers running
264
- ```
265
-
266
- The `list` command shows all running daemon instances with their working directories, PIDs, and status:
267
-
268
- ```bash
269
- $ npx cli-lsp-client list
270
-
271
- Running Daemons:
272
- ================
273
- Hash | PID | Status | Working Directory
274
- ----------------------------------------------------------
275
- h0gx9u | 12345 | ● Running | /Users/user/project-a
276
- 94yi9w | 12346 | ● Running | /Users/user/project-b
277
-
278
- 2/2 daemon(s) running
279
- ```
280
-
281
- Use `stop-all` when updating the CLI package to ensure all old daemon processes are terminated and fresh ones spawn with the updated code.
282
-
283
- ## Java Installation Guide
284
-
285
- Eclipse JDT Language Server requires Java 17+ and manual setup:
286
-
287
- ### Installation Steps
288
-
289
- 1. **Download**: Get the latest server from [Eclipse JDT.LS downloads](http://download.eclipse.org/jdtls/snapshots/)
290
- 2. **Extract**: Unpack to your preferred location (e.g., `/opt/jdtls/`)
291
- 3. **Create wrapper script** named `jdtls` in your PATH:
292
-
293
- ```bash
294
- #!/bin/bash
295
- java -Declipse.application=org.eclipse.jdt.ls.core.id1 \
296
- -Dosgi.bundles.defaultStartLevel=4 \
297
- -Declipse.product=org.eclipse.jdt.ls.core.product \
298
- -Xms1g -Xmx2G \
299
- -jar /opt/jdtls/plugins/org.eclipse.equinox.launcher_*.jar \
300
- -configuration /opt/jdtls/config_linux \
301
- -data "${1:-$HOME/workspace}" \
302
- --add-modules=ALL-SYSTEM \
303
- --add-opens java.base/java.util=ALL-UNNAMED \
304
- --add-opens java.base/java.lang=ALL-UNNAMED "$@"
305
- ```
306
-
307
- 4. **Make executable**: `chmod +x /usr/local/bin/jdtls`
308
-
309
- ### Alternative Installation Methods
310
-
311
- **Homebrew (macOS/Linux)**:
312
-
313
- ```bash
314
- brew install jdtls
315
- ```
316
-
317
- **Arch Linux**:
318
-
319
- ```bash
320
- pacman -S jdtls
321
- ```
322
-
323
- ### Configuration Notes
324
-
325
- - Replace `config_linux` with `config_mac` on macOS or `config_win` on Windows
326
- - Adjust the `-data` workspace path as needed
327
- - Requires Java 17 or higher to run
328
-
329
- For detailed setup instructions, see the [official Eclipse JDT.LS documentation](https://github.com/eclipse-jdtls/eclipse.jdt.ls).
330
-
331
- ## R Installation Guide
332
-
333
- The R language server requires R runtime and the `languageserver` package:
334
-
335
- ### Installation Steps
336
-
337
- 1. **Install R**: Download and install R from [CRAN](https://cran.r-project.org/) or use a package manager:
338
-
339
- **macOS (Homebrew)**:
340
-
341
- ```bash
342
- brew install r
343
- ```
344
-
345
- **Ubuntu/Debian**:
346
-
347
- ```bash
348
- sudo apt-get update
349
- sudo apt-get install r-base
350
- ```
351
-
352
- **Windows**: Download installer from [CRAN Windows](https://cran.r-project.org/bin/windows/base/)
353
-
354
- 2. **Install R languageserver package**: Open R and run:
355
-
356
- ```r
357
- install.packages("languageserver")
358
- ```
359
-
360
- Or from command line:
361
-
362
- ```bash
363
- R --slave -e 'install.packages("languageserver", repos="https://cran.rstudio.com/")'
364
- ```
365
-
366
- ### Verification
367
-
368
- Test that the language server works:
369
-
370
- ```bash
371
- R --slave -e 'languageserver::run()'
372
- ```
373
-
374
- ### Project Detection
375
-
376
- The R LSP automatically detects R projects based on these files:
377
-
378
- - `DESCRIPTION` (R packages)
379
- - `NAMESPACE` (R packages)
380
- - `.Rproj` (RStudio projects)
381
- - `renv.lock` (renv dependency management)
382
- - Any `.r`, `.R`, `.rmd`, `.Rmd` files
383
-
384
- For more information, see the [R languageserver documentation](https://github.com/REditorSupport/languageserver).
385
-
386
- ## C# Installation Guide
387
-
388
- The C# language server requires .NET SDK and OmniSharp-Roslyn:
389
-
390
- ### Installation Steps
391
-
392
- 1. **Install .NET SDK**: Download .NET 6.0+ from [Microsoft .NET](https://dotnet.microsoft.com/download) or use a package manager:
393
-
394
- **macOS (Homebrew)**:
395
-
396
- ```bash
397
- brew install dotnet
398
- ```
399
-
400
- **Ubuntu/Debian**:
401
-
402
- ```bash
403
- # Add Microsoft package repository
404
- wget https://packages.microsoft.com/config/ubuntu/$(lsb_release -rs)/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
405
- sudo dpkg -i packages-microsoft-prod.deb
406
- sudo apt-get update
407
- sudo apt-get install -y dotnet-sdk-8.0
408
- ```
409
-
410
- **Windows**: Download installer from [.NET Downloads](https://dotnet.microsoft.com/download)
411
-
412
- 2. **Install OmniSharp-Roslyn**: Download the latest release from [OmniSharp releases](https://github.com/OmniSharp/omnisharp-roslyn/releases):
413
-
414
- **Automatic script** (recommended):
415
-
416
- ```bash
417
- # Download and extract OmniSharp to ~/.omnisharp/
418
- mkdir -p ~/.omnisharp
419
- curl -L https://github.com/OmniSharp/omnisharp-roslyn/releases/latest/download/omnisharp-osx-arm64-net6.0.tar.gz | tar -xz -C ~/.omnisharp/
420
-
421
- # Create symlink to make omnisharp available in PATH
422
- sudo ln -sf ~/.omnisharp/OmniSharp /usr/local/bin/omnisharp
423
- ```
424
-
425
- **Manual installation**:
426
- - Download the appropriate release for your platform (Windows: `omnisharp-win-x64-net6.0.zip`, Linux: `omnisharp-linux-x64-net6.0.tar.gz`)
427
- - Extract to a directory (e.g., `~/.omnisharp/`)
428
- - Add the executable to your PATH or create a symlink
429
-
430
- 3. **Set environment variables**:
431
-
432
- **Fish shell**:
433
-
434
- ```bash
435
- set -Ux DOTNET_ROOT ~/.dotnet
436
- ```
437
-
438
- **Bash/Zsh**:
439
-
440
- ```bash
441
- echo 'export DOTNET_ROOT=~/.dotnet' >> ~/.bashrc # or ~/.zshrc
442
- source ~/.bashrc # or ~/.zshrc
443
- ```
444
-
445
- **Note**: `DOTNET_ROOT` must be set in your shell environment for the C# language server to work. The CLI will only load OmniSharp if this environment variable is defined. Restart your terminal after setting the environment variable to ensure it's available.
446
-
447
- ### Verification
448
-
449
- Test that OmniSharp works:
450
-
451
- ```bash
452
- # Verify DOTNET_ROOT is set
453
- echo $DOTNET_ROOT
454
-
455
- # Test OmniSharp command
456
- omnisharp --help
457
- ```
458
-
459
- ### Project Detection
460
-
461
- The C# LSP automatically detects C# projects based on these files:
462
-
463
- - `*.sln` (Solution files)
464
- - `*.csproj` (Project files)
465
- - `project.json` (Legacy project files)
466
- - `global.json` (.NET global configuration)
467
- - Any `.cs` files
468
-
469
- For more information, see the [OmniSharp documentation](https://github.com/OmniSharp/omnisharp-roslyn).
470
-
471
- ## Swift Configuration
472
-
473
- Swift language support is available through SourceKit-LSP, which is included with Xcode Command Line Tools. Support for swift and other LSPs can be added via a config file.
474
-
475
- ### Prerequisites
476
-
477
- **macOS (with Xcode Command Line Tools)**:
478
-
479
- ```bash
480
- # Check if SourceKit-LSP is available
481
- xcrun --find sourcekit-lsp
482
- ```
483
-
484
- **Alternative toolchains**: If using Swift toolchains from swift.org, SourceKit-LSP is included and can be run with:
485
-
486
- ```bash
487
- xcrun --toolchain swift sourcekit-lsp
488
- ```
489
-
490
- ### Configuration
491
-
492
- Create a config file at `~/.config/cli-lsp-client/settings.json`:
493
-
494
- ```json
495
- {
496
- "servers": [
497
- {
498
- "id": "sourcekit_lsp",
499
- "extensions": [".swift"],
500
- "rootPatterns": ["Package.swift", ".xcodeproj", ".xcworkspace"],
501
- "command": ["xcrun", "sourcekit-lsp"],
502
- "env": {}
503
- }
504
- ],
505
- "languageExtensions": {
506
- ".swift": "swift"
507
- }
508
- }
509
- ```
510
-
511
- For more information about SourceKit-LSP, see the [official documentation](https://github.com/swiftlang/sourcekit-lsp).
512
-
513
- ### Additional Commands
514
-
515
- ```bash
516
- # Start LSP servers for current directory (faster subsequent requests)
517
- npx cli-lsp-client start
518
-
519
- # Start servers for specific directory
520
- npx cli-lsp-client start /path/to/project
521
-
522
- # View daemon log file path
523
- npx cli-lsp-client logs
524
- ```
525
-
526
- ## Examples
527
-
528
- ```bash
529
- # Check a specific file
530
- npx cli-lsp-client diagnostics src/main.ts
531
-
532
- # Get hover info for a symbol
533
- npx cli-lsp-client hover src/main.ts myFunction
534
-
535
- # List all daemon instances
536
- npx cli-lsp-client list
537
-
538
- # Stop all daemons after package update
539
- npx cli-lsp-client stop-all
540
- ```
541
-
542
- ## Development
543
-
544
- ### Installation
545
-
546
- ```bash
547
- # Install dependencies and build
548
- bun install
549
- bun run build # Build executable
550
- bun run typecheck
551
- bun test
552
- ```
553
-
554
- Add new LSP servers in `src/lsp/servers.ts`.
package/cli-lsp-client DELETED
Binary file
package/src/cli.ts DELETED
@@ -1,236 +0,0 @@
1
- #!/usr/bin/env bun
2
-
3
- import path from 'path';
4
- import { z } from 'zod';
5
- import { startDaemon } from './daemon.js';
6
- import { runCommand, sendToExistingDaemon } from './client.js';
7
- import { formatDiagnosticsPlain } from './lsp/formatter.js';
8
- import type { Diagnostic } from './lsp/types.js';
9
- import { HELP_MESSAGE } from './constants.js';
10
- import { ensureDaemonRunning } from './utils.js';
11
- import { startMcpServer } from './mcp/server.js';
12
- import packageJson from '../package.json' with { type: 'json' };
13
-
14
- // Schema for Claude Code PostToolUse hook payload
15
- const HookDataSchema = z.object({
16
- session_id: z.string().optional(),
17
- transcript_path: z.string().optional(),
18
- cwd: z.string().optional(),
19
- hook_event_name: z.string().optional(),
20
- tool_name: z.string().optional(),
21
- tool_input: z
22
- .object({
23
- file_path: z.string().optional(),
24
- content: z.string().optional(),
25
- })
26
- .optional(),
27
- tool_response: z.any().optional(),
28
- });
29
-
30
- export async function handleClaudeCodeHook(
31
- filePath: string
32
- ): Promise<{ hasIssues: boolean; output: string; daemonFailed?: boolean }> {
33
- // Check if file exists
34
- if (!(await Bun.file(filePath).exists())) {
35
- return { hasIssues: false, output: '' };
36
- }
37
-
38
- // Filter supported file types
39
- const supportedExts = [
40
- '.ts',
41
- '.tsx',
42
- '.js',
43
- '.jsx',
44
- '.mjs',
45
- '.cjs',
46
- '.mts',
47
- '.cts',
48
- '.py',
49
- '.pyi',
50
- '.go',
51
- '.json',
52
- '.jsonc',
53
- '.css',
54
- '.scss',
55
- '.sass',
56
- '.less',
57
- '.yaml',
58
- '.yml',
59
- '.sh',
60
- '.bash',
61
- '.zsh',
62
- '.java',
63
- '.lua',
64
- '.graphql',
65
- '.gql',
66
- '.r',
67
- '.R',
68
- '.rmd',
69
- '.Rmd',
70
- '.cs',
71
- ];
72
- const ext = path.extname(filePath);
73
- if (!supportedExts.includes(ext)) {
74
- return { hasIssues: false, output: '' };
75
- }
76
-
77
- // Get diagnostics (suppress errors to stdout)
78
- try {
79
- // Ensure daemon is running
80
- const daemonStarted = await ensureDaemonRunning();
81
-
82
- if (!daemonStarted) {
83
- // Failed to start daemon - return with flag so caller can handle
84
- return {
85
- hasIssues: false,
86
- output:
87
- 'Failed to start LSP daemon. Please try running "cli-lsp-client stop" and retry.',
88
- daemonFailed: true,
89
- };
90
- }
91
-
92
- const result = await sendToExistingDaemon('diagnostics', [filePath]);
93
-
94
- // The diagnostics command returns an array of diagnostics
95
- if (!Array.isArray(result) || result.length === 0) {
96
- return { hasIssues: false, output: '' };
97
- }
98
-
99
- const diagnostics = result as Diagnostic[];
100
-
101
- // Format output for Claude Code hook (plain text, no ANSI codes)
102
- const formatted = formatDiagnosticsPlain(filePath, diagnostics);
103
- return { hasIssues: true, output: formatted || '' };
104
- } catch (_error) {
105
- // Silently fail - don't break Claude Code experience
106
- return { hasIssues: false, output: '' };
107
- }
108
- }
109
-
110
- function showHelp(): void {
111
- process.stdout.write(HELP_MESSAGE + '\n');
112
- }
113
-
114
- type ParsedArgs = {
115
- command: string;
116
- commandArgs: string[];
117
- configFile?: string;
118
- };
119
-
120
- function parseArgs(args: string[]): ParsedArgs {
121
- let configFile: string | undefined;
122
- const filteredArgs: string[] = [];
123
-
124
- for (let i = 0; i < args.length; i++) {
125
- const arg = args[i];
126
-
127
- if (arg === '--config-file') {
128
- if (i + 1 >= args.length) {
129
- process.stderr.write('Error: --config-file requires a path argument\n');
130
- process.exit(1);
131
- }
132
- configFile = args[i + 1];
133
- i++; // Skip the next argument since it's the config file path
134
- } else if (arg.startsWith('--config-file=')) {
135
- configFile = arg.split('=', 2)[1];
136
- if (!configFile) {
137
- process.stderr.write('Error: --config-file= requires a path after the equals sign\n');
138
- process.exit(1);
139
- }
140
- } else {
141
- filteredArgs.push(arg);
142
- }
143
- }
144
-
145
- const command = filteredArgs[0] || 'status';
146
- const commandArgs = filteredArgs.slice(1);
147
-
148
- return { command, commandArgs, configFile };
149
- }
150
-
151
- async function run(): Promise<void> {
152
- const rawArgs = process.argv.slice(2);
153
- const { command, commandArgs, configFile } = parseArgs(rawArgs);
154
-
155
- // Check if we're being invoked to run as daemon
156
- if (process.env.LSPCLI_DAEMON_MODE === '1') {
157
- await startDaemon();
158
- return;
159
- }
160
-
161
- // Handle help command directly (no daemon needed)
162
- if (command === 'help' || command === '--help' || command === '-h') {
163
- showHelp();
164
- return;
165
- }
166
-
167
- // Handle version command directly (no daemon needed)
168
- if (command === 'version' || command === '--version' || command === '-v') {
169
- process.stdout.write(packageJson.version + '\n');
170
- return;
171
- }
172
-
173
- // Handle mcp-server command directly (starts MCP server)
174
- if (command === 'mcp-server') {
175
- await startMcpServer();
176
- return;
177
- }
178
-
179
- // Handle claude-code-hook command directly (reads JSON from stdin)
180
- if (command === 'claude-code-hook') {
181
- try {
182
- // Read JSON from stdin
183
- const stdinData = await new Promise<string>((resolve, reject) => {
184
- let data = '';
185
- process.stdin.on('data', (chunk) => {
186
- data += chunk.toString();
187
- });
188
- process.stdin.on('end', () => {
189
- resolve(data);
190
- });
191
- process.stdin.on('error', reject);
192
- });
193
-
194
- if (!stdinData.trim()) {
195
- process.exit(0); // No input, silently exit
196
- }
197
-
198
- // Parse the JSON to get the file path
199
- const parseResult = HookDataSchema.safeParse(JSON.parse(stdinData));
200
- if (!parseResult.success) {
201
- process.exit(0); // Invalid JSON format, silently exit
202
- }
203
- const hookData = parseResult.data;
204
- // Extract file_path from PostToolUse tool_input
205
- const filePath = hookData.tool_input?.file_path;
206
-
207
- if (!filePath) {
208
- process.exit(0); // No file path, silently exit
209
- }
210
-
211
- const result = await handleClaudeCodeHook(filePath);
212
- if (result.daemonFailed) {
213
- // Daemon failed to start - exit with status 1 to show error to user
214
- process.stderr.write(result.output + '\n');
215
- process.exit(1);
216
- }
217
- if (result.hasIssues) {
218
- process.stderr.write(result.output + '\n');
219
- process.exit(2);
220
- }
221
- process.exit(0);
222
- } catch (_error) {
223
- // Silently fail for hook commands to not break Claude Code
224
- process.exit(0);
225
- }
226
- }
227
-
228
- // All other user commands are handled by the client (which auto-starts daemon if needed)
229
- await runCommand(command, commandArgs, configFile);
230
- }
231
-
232
- export { run };
233
-
234
- if (import.meta.main) {
235
- run();
236
- }