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.
- package/bin/cli-lsp-client +64 -0
- package/bin/cli-lsp-client.cmd +57 -0
- package/package.json +15 -40
- package/postinstall.mjs +145 -0
- package/preinstall.mjs +41 -0
- package/LICENSE +0 -21
- package/README.md +0 -554
- package/cli-lsp-client +0 -0
- package/src/cli.ts +0 -236
|
@@ -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.
|
|
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
|
-
"
|
|
41
|
-
"
|
|
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
|
-
"
|
|
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
|
+
}
|
package/postinstall.mjs
ADDED
|
@@ -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
|
-
}
|