cli-lsp-client 1.5.0 → 1.7.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/README.md +287 -22
- package/cli-lsp-client +0 -0
- package/package.json +35 -26
- package/src/cli.ts +115 -34
package/README.md
CHANGED
|
@@ -10,26 +10,30 @@ CLI tool for getting LSP diagnostics. Uses a background daemon to keep LSP serve
|
|
|
10
10
|
- Built in Claude Code hook to provide feedback on file edit tool calls
|
|
11
11
|
- Comprehensive daemon management (`list`, `stop-all` commands)
|
|
12
12
|
- Multi-project support with isolated daemon instances per directory
|
|
13
|
+
- [Custom language server support via config files](#custom-language-servers-config-file)
|
|
13
14
|
|
|
14
15
|
## Supported Languages
|
|
15
16
|
|
|
16
|
-
| Language
|
|
17
|
-
|
|
18
|
-
| TypeScript/JavaScript | `typescript-language-server`
|
|
19
|
-
| Python
|
|
20
|
-
| JSON
|
|
21
|
-
| CSS
|
|
22
|
-
| YAML
|
|
23
|
-
| Bash/Shell
|
|
24
|
-
|
|
|
25
|
-
|
|
|
26
|
-
|
|
|
27
|
-
|
|
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) |
|
|
28
32
|
|
|
29
33
|
## How It Works
|
|
30
34
|
|
|
31
35
|
- Daemon starts automatically when needed
|
|
32
|
-
- LSP servers spawn based on file type
|
|
36
|
+
- LSP servers spawn based on file type
|
|
33
37
|
- Finds project roots using config files (tsconfig.json, etc.)
|
|
34
38
|
- Servers stay running for subsequent requests
|
|
35
39
|
|
|
@@ -37,7 +41,7 @@ CLI tool for getting LSP diagnostics. Uses a background daemon to keep LSP serve
|
|
|
37
41
|
|
|
38
42
|
### Real-time Diagnostics Hook
|
|
39
43
|
|
|
40
|
-
Get instant diagnostic feedback for TypeScript, Python,
|
|
44
|
+
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.
|
|
41
45
|
|
|
42
46
|
#### Setup
|
|
43
47
|
|
|
@@ -77,7 +81,7 @@ Configure Claude Code to use the built-in hook command:
|
|
|
77
81
|
|
|
78
82
|
- **SessionStart**: Automatically starts LSP servers when Claude Code starts for faster initial diagnostics
|
|
79
83
|
- **PostToolUse**: Runs diagnostics after each file edit (Edit, MultiEdit, Write tools)
|
|
80
|
-
- Built-in file filtering for all supported languages (
|
|
84
|
+
- Built-in file filtering for all supported languages (16 file types)
|
|
81
85
|
- Shows errors, warnings, and hints inline
|
|
82
86
|
- Graceful error handling - never breaks your editing experience
|
|
83
87
|
- Uses the same fast daemon as the regular diagnostics command
|
|
@@ -88,13 +92,79 @@ When you save a file with errors, you'll see immediate feedback:
|
|
|
88
92
|
|
|
89
93
|
```
|
|
90
94
|
Edit operation feedback:
|
|
91
|
-
- [npx -y cli-lsp-client claude-code-hook]:
|
|
95
|
+
- [npx -y cli-lsp-client claude-code-hook]:
|
|
92
96
|
ERROR at line 3, column 9:
|
|
93
97
|
Type 'number' is not assignable to type 'string'.
|
|
94
98
|
Source: typescript
|
|
95
99
|
Code: 2322
|
|
96
100
|
```
|
|
97
101
|
|
|
102
|
+
## Custom Language Servers (Config File)
|
|
103
|
+
|
|
104
|
+
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.
|
|
105
|
+
|
|
106
|
+
### Configuration File
|
|
107
|
+
|
|
108
|
+
Create a config file at `~/.config/cli-lsp-client/settings.json` (default location) or use `--config-file` to specify a custom path:
|
|
109
|
+
|
|
110
|
+
```json
|
|
111
|
+
{
|
|
112
|
+
"servers": [
|
|
113
|
+
{
|
|
114
|
+
"id": "svelte",
|
|
115
|
+
"extensions": [".svelte"],
|
|
116
|
+
"rootPatterns": ["svelte.config.js", "package.json"],
|
|
117
|
+
"command": ["bunx", "svelte-language-server", "--stdio"],
|
|
118
|
+
"env": {
|
|
119
|
+
"NODE_ENV": "development"
|
|
120
|
+
},
|
|
121
|
+
"initialization": {
|
|
122
|
+
"settings": {
|
|
123
|
+
"svelte": {
|
|
124
|
+
"compilerWarnings": true
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
],
|
|
130
|
+
"languageExtensions": {
|
|
131
|
+
".svelte": "svelte"
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Using Custom Config
|
|
137
|
+
|
|
138
|
+
**Default config file location:**
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
# Uses ~/.config/cli-lsp-client/settings.json automatically
|
|
142
|
+
npx cli-lsp-client diagnostics Component.svelte
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Custom config file location:**
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
# Specify custom config file path
|
|
149
|
+
npx cli-lsp-client --config-file ./my-config.json diagnostics Component.svelte
|
|
150
|
+
npx cli-lsp-client --config-file ./my-config.json hover Component.svelte myFunction
|
|
151
|
+
npx cli-lsp-client --config-file ./my-config.json status
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**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.
|
|
155
|
+
|
|
156
|
+
### Config File Schema
|
|
157
|
+
|
|
158
|
+
- `servers`: Array of custom language server definitions
|
|
159
|
+
- `id`: Unique identifier for the server
|
|
160
|
+
- `extensions`: File extensions this server handles (e.g. `[".svelte"]`)
|
|
161
|
+
- `rootPatterns`: Files/patterns used to detect project root (e.g. `["package.json"]`)
|
|
162
|
+
- `command`: Command array to start the LSP server (e.g. `["bunx", "svelte-language-server", "--stdio"]`)
|
|
163
|
+
- `env`: Optional environment variables for the server process
|
|
164
|
+
- `initialization`: Optional LSP initialization parameters
|
|
165
|
+
|
|
166
|
+
- `languageExtensions`: Maps file extensions to LSP language identifiers
|
|
167
|
+
|
|
98
168
|
## Usage
|
|
99
169
|
|
|
100
170
|
### Get Diagnostics
|
|
@@ -106,6 +176,11 @@ npx cli-lsp-client diagnostics src/example.ts
|
|
|
106
176
|
# Check any supported file type
|
|
107
177
|
npx cli-lsp-client diagnostics app.py
|
|
108
178
|
npx cli-lsp-client diagnostics main.go
|
|
179
|
+
npx cli-lsp-client diagnostics analysis.R
|
|
180
|
+
npx cli-lsp-client diagnostics Program.cs
|
|
181
|
+
|
|
182
|
+
# Check Swift files (requires config file)
|
|
183
|
+
npx cli-lsp-client diagnostics Sources/App/main.swift
|
|
109
184
|
```
|
|
110
185
|
|
|
111
186
|
Exit codes: 0 for no issues, 2 for issues found.
|
|
@@ -126,15 +201,21 @@ npx cli-lsp-client hover src/main.ts myFunction
|
|
|
126
201
|
|
|
127
202
|
# Get hover info for a variable or type
|
|
128
203
|
npx cli-lsp-client hover app.py MyClass
|
|
204
|
+
npx cli-lsp-client hover analysis.R mean
|
|
205
|
+
npx cli-lsp-client hover Program.cs Console
|
|
206
|
+
|
|
207
|
+
# Get hover info for Swift symbols (requires config file)
|
|
208
|
+
npx cli-lsp-client hover Sources/App/main.swift greetUser
|
|
129
209
|
```
|
|
130
210
|
|
|
131
|
-
|
|
211
|
+
````bash
|
|
132
212
|
$ npx cli-lsp-client hover src/client.ts runCommand
|
|
133
213
|
Location: src/client.ts:370:17
|
|
134
214
|
```typescript
|
|
135
215
|
export function runCommand(command: string, commandArgs: string[]): Promise<void>
|
|
136
|
-
|
|
137
|
-
|
|
216
|
+
````
|
|
217
|
+
|
|
218
|
+
````
|
|
138
219
|
|
|
139
220
|
### Daemon Management
|
|
140
221
|
|
|
@@ -156,7 +237,7 @@ npx cli-lsp-client --version
|
|
|
156
237
|
|
|
157
238
|
# Show help
|
|
158
239
|
npx cli-lsp-client help
|
|
159
|
-
|
|
240
|
+
````
|
|
160
241
|
|
|
161
242
|
The `status` command shows the current daemon's uptime and running language servers:
|
|
162
243
|
|
|
@@ -181,7 +262,7 @@ $ npx cli-lsp-client list
|
|
|
181
262
|
|
|
182
263
|
Running Daemons:
|
|
183
264
|
================
|
|
184
|
-
Hash | PID | Status | Working Directory
|
|
265
|
+
Hash | PID | Status | Working Directory
|
|
185
266
|
----------------------------------------------------------
|
|
186
267
|
h0gx9u | 12345 | ● Running | /Users/user/project-a
|
|
187
268
|
94yi9w | 12346 | ● Running | /Users/user/project-b
|
|
@@ -220,11 +301,13 @@ java -Declipse.application=org.eclipse.jdt.ls.core.id1 \
|
|
|
220
301
|
### Alternative Installation Methods
|
|
221
302
|
|
|
222
303
|
**Homebrew (macOS/Linux)**:
|
|
304
|
+
|
|
223
305
|
```bash
|
|
224
306
|
brew install jdtls
|
|
225
307
|
```
|
|
226
308
|
|
|
227
309
|
**Arch Linux**:
|
|
310
|
+
|
|
228
311
|
```bash
|
|
229
312
|
pacman -S jdtls
|
|
230
313
|
```
|
|
@@ -237,6 +320,188 @@ pacman -S jdtls
|
|
|
237
320
|
|
|
238
321
|
For detailed setup instructions, see the [official Eclipse JDT.LS documentation](https://github.com/eclipse-jdtls/eclipse.jdt.ls).
|
|
239
322
|
|
|
323
|
+
## R Installation Guide
|
|
324
|
+
|
|
325
|
+
The R language server requires R runtime and the `languageserver` package:
|
|
326
|
+
|
|
327
|
+
### Installation Steps
|
|
328
|
+
|
|
329
|
+
1. **Install R**: Download and install R from [CRAN](https://cran.r-project.org/) or use a package manager:
|
|
330
|
+
|
|
331
|
+
**macOS (Homebrew)**:
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
brew install r
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
**Ubuntu/Debian**:
|
|
338
|
+
|
|
339
|
+
```bash
|
|
340
|
+
sudo apt-get update
|
|
341
|
+
sudo apt-get install r-base
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
**Windows**: Download installer from [CRAN Windows](https://cran.r-project.org/bin/windows/base/)
|
|
345
|
+
|
|
346
|
+
2. **Install R languageserver package**: Open R and run:
|
|
347
|
+
|
|
348
|
+
```r
|
|
349
|
+
install.packages("languageserver")
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
Or from command line:
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
R --slave -e 'install.packages("languageserver", repos="https://cran.rstudio.com/")'
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Verification
|
|
359
|
+
|
|
360
|
+
Test that the language server works:
|
|
361
|
+
|
|
362
|
+
```bash
|
|
363
|
+
R --slave -e 'languageserver::run()'
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Project Detection
|
|
367
|
+
|
|
368
|
+
The R LSP automatically detects R projects based on these files:
|
|
369
|
+
|
|
370
|
+
- `DESCRIPTION` (R packages)
|
|
371
|
+
- `NAMESPACE` (R packages)
|
|
372
|
+
- `.Rproj` (RStudio projects)
|
|
373
|
+
- `renv.lock` (renv dependency management)
|
|
374
|
+
- Any `.r`, `.R`, `.rmd`, `.Rmd` files
|
|
375
|
+
|
|
376
|
+
For more information, see the [R languageserver documentation](https://github.com/REditorSupport/languageserver).
|
|
377
|
+
|
|
378
|
+
## C# Installation Guide
|
|
379
|
+
|
|
380
|
+
The C# language server requires .NET SDK and OmniSharp-Roslyn:
|
|
381
|
+
|
|
382
|
+
### Installation Steps
|
|
383
|
+
|
|
384
|
+
1. **Install .NET SDK**: Download .NET 6.0+ from [Microsoft .NET](https://dotnet.microsoft.com/download) or use a package manager:
|
|
385
|
+
|
|
386
|
+
**macOS (Homebrew)**:
|
|
387
|
+
|
|
388
|
+
```bash
|
|
389
|
+
brew install dotnet
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
**Ubuntu/Debian**:
|
|
393
|
+
|
|
394
|
+
```bash
|
|
395
|
+
# Add Microsoft package repository
|
|
396
|
+
wget https://packages.microsoft.com/config/ubuntu/$(lsb_release -rs)/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
|
|
397
|
+
sudo dpkg -i packages-microsoft-prod.deb
|
|
398
|
+
sudo apt-get update
|
|
399
|
+
sudo apt-get install -y dotnet-sdk-8.0
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
**Windows**: Download installer from [.NET Downloads](https://dotnet.microsoft.com/download)
|
|
403
|
+
|
|
404
|
+
2. **Install OmniSharp-Roslyn**: Download the latest release from [OmniSharp releases](https://github.com/OmniSharp/omnisharp-roslyn/releases):
|
|
405
|
+
|
|
406
|
+
**Automatic script** (recommended):
|
|
407
|
+
|
|
408
|
+
```bash
|
|
409
|
+
# Download and extract OmniSharp to ~/.omnisharp/
|
|
410
|
+
mkdir -p ~/.omnisharp
|
|
411
|
+
curl -L https://github.com/OmniSharp/omnisharp-roslyn/releases/latest/download/omnisharp-osx-arm64-net6.0.tar.gz | tar -xz -C ~/.omnisharp/
|
|
412
|
+
|
|
413
|
+
# Create symlink to make omnisharp available in PATH
|
|
414
|
+
sudo ln -sf ~/.omnisharp/OmniSharp /usr/local/bin/omnisharp
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
**Manual installation**:
|
|
418
|
+
- Download the appropriate release for your platform (Windows: `omnisharp-win-x64-net6.0.zip`, Linux: `omnisharp-linux-x64-net6.0.tar.gz`)
|
|
419
|
+
- Extract to a directory (e.g., `~/.omnisharp/`)
|
|
420
|
+
- Add the executable to your PATH or create a symlink
|
|
421
|
+
|
|
422
|
+
3. **Set environment variables**:
|
|
423
|
+
|
|
424
|
+
**Fish shell**:
|
|
425
|
+
|
|
426
|
+
```bash
|
|
427
|
+
set -Ux DOTNET_ROOT ~/.dotnet
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
**Bash/Zsh**:
|
|
431
|
+
|
|
432
|
+
```bash
|
|
433
|
+
echo 'export DOTNET_ROOT=~/.dotnet' >> ~/.bashrc # or ~/.zshrc
|
|
434
|
+
source ~/.bashrc # or ~/.zshrc
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
**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.
|
|
438
|
+
|
|
439
|
+
### Verification
|
|
440
|
+
|
|
441
|
+
Test that OmniSharp works:
|
|
442
|
+
|
|
443
|
+
```bash
|
|
444
|
+
# Verify DOTNET_ROOT is set
|
|
445
|
+
echo $DOTNET_ROOT
|
|
446
|
+
|
|
447
|
+
# Test OmniSharp command
|
|
448
|
+
omnisharp --help
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### Project Detection
|
|
452
|
+
|
|
453
|
+
The C# LSP automatically detects C# projects based on these files:
|
|
454
|
+
|
|
455
|
+
- `*.sln` (Solution files)
|
|
456
|
+
- `*.csproj` (Project files)
|
|
457
|
+
- `project.json` (Legacy project files)
|
|
458
|
+
- `global.json` (.NET global configuration)
|
|
459
|
+
- Any `.cs` files
|
|
460
|
+
|
|
461
|
+
For more information, see the [OmniSharp documentation](https://github.com/OmniSharp/omnisharp-roslyn).
|
|
462
|
+
|
|
463
|
+
## Swift Configuration
|
|
464
|
+
|
|
465
|
+
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.
|
|
466
|
+
|
|
467
|
+
### Prerequisites
|
|
468
|
+
|
|
469
|
+
**macOS (with Xcode Command Line Tools)**:
|
|
470
|
+
|
|
471
|
+
```bash
|
|
472
|
+
# Check if SourceKit-LSP is available
|
|
473
|
+
xcrun --find sourcekit-lsp
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
**Alternative toolchains**: If using Swift toolchains from swift.org, SourceKit-LSP is included and can be run with:
|
|
477
|
+
|
|
478
|
+
```bash
|
|
479
|
+
xcrun --toolchain swift sourcekit-lsp
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
### Configuration
|
|
483
|
+
|
|
484
|
+
Create a config file at `~/.config/cli-lsp-client/settings.json`:
|
|
485
|
+
|
|
486
|
+
```json
|
|
487
|
+
{
|
|
488
|
+
"servers": [
|
|
489
|
+
{
|
|
490
|
+
"id": "sourcekit_lsp",
|
|
491
|
+
"extensions": [".swift"],
|
|
492
|
+
"rootPatterns": ["Package.swift", ".xcodeproj", ".xcworkspace"],
|
|
493
|
+
"command": ["xcrun", "sourcekit-lsp"],
|
|
494
|
+
"env": {}
|
|
495
|
+
}
|
|
496
|
+
],
|
|
497
|
+
"languageExtensions": {
|
|
498
|
+
".swift": "swift"
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
For more information about SourceKit-LSP, see the [official documentation](https://github.com/swiftlang/sourcekit-lsp).
|
|
504
|
+
|
|
240
505
|
### Additional Commands
|
|
241
506
|
|
|
242
507
|
```bash
|
|
@@ -246,7 +511,7 @@ npx cli-lsp-client start
|
|
|
246
511
|
# Start servers for specific directory
|
|
247
512
|
npx cli-lsp-client start /path/to/project
|
|
248
513
|
|
|
249
|
-
# View daemon
|
|
514
|
+
# View daemon log file path
|
|
250
515
|
npx cli-lsp-client logs
|
|
251
516
|
```
|
|
252
517
|
|
package/cli-lsp-client
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,43 +1,52 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cli-lsp-client",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"
|
|
5
|
-
|
|
3
|
+
"version": "1.7.0",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "git+https://github.com/eli0shin/cli-lsp-client.git"
|
|
7
|
+
},
|
|
6
8
|
"main": "src/cli.ts",
|
|
7
|
-
"
|
|
9
|
+
"devDependencies": {
|
|
10
|
+
"@eslint/js": "^9.33.0",
|
|
11
|
+
"@types/bun": "latest",
|
|
12
|
+
"@types/node": "latest",
|
|
13
|
+
"eslint": "^9.33.0",
|
|
14
|
+
"eslint-config-prettier": "10.1.8",
|
|
15
|
+
"eslint-import-resolver-typescript": "^4.4.4",
|
|
16
|
+
"eslint-plugin-import-x": "^4.16.1",
|
|
17
|
+
"eslint-plugin-unused-imports": "^4.1.4",
|
|
18
|
+
"globals": "^16.3.0",
|
|
19
|
+
"prettier": "3.6.2",
|
|
20
|
+
"typescript": "latest",
|
|
21
|
+
"typescript-eslint": "^8.39.1",
|
|
22
|
+
"vscode-jsonrpc": "^8.2.1",
|
|
23
|
+
"vscode-languageserver-types": "^3.17.5",
|
|
24
|
+
"zod": "^4.0.17"
|
|
25
|
+
},
|
|
8
26
|
"bin": {
|
|
9
27
|
"cli-lsp-client": "cli-lsp-client"
|
|
10
28
|
},
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/eli0shin/cli-lsp-client/issues"
|
|
31
|
+
},
|
|
32
|
+
"description": "CLI tool supporting claude code hooks for fast LSP diagnostics with background daemon and multi-project support",
|
|
11
33
|
"files": [
|
|
12
34
|
"cli-lsp-client"
|
|
13
35
|
],
|
|
14
|
-
"repository": {
|
|
15
|
-
"type": "git",
|
|
16
|
-
"url": "git+https://github.com/eli0shin/cli-lsp-client.git"
|
|
17
|
-
},
|
|
18
36
|
"homepage": "https://github.com/eli0shin/cli-lsp-client#readme",
|
|
19
|
-
"
|
|
20
|
-
"url": "https://github.com/eli0shin/cli-lsp-client/issues"
|
|
21
|
-
},
|
|
37
|
+
"license": "MIT",
|
|
22
38
|
"scripts": {
|
|
23
39
|
"build": "bun build src/cli.ts --compile --outfile cli-lsp-client",
|
|
24
40
|
"dev": "bun run src/cli.ts",
|
|
25
|
-
"typecheck": "bun
|
|
41
|
+
"typecheck": "bun tsc --noEmit",
|
|
42
|
+
"lint": "eslint .",
|
|
43
|
+
"lint:fix": "eslint . --fix",
|
|
44
|
+
"format": "prettier --check .",
|
|
45
|
+
"format:fix": "prettier --write .",
|
|
26
46
|
"test": "bun test",
|
|
27
47
|
"test:watch": "bun test tests/ --watch",
|
|
28
|
-
"
|
|
48
|
+
"prepublishOnly": "bun test && bun run build",
|
|
49
|
+
"postinstall": "node scripts/postinstall.js"
|
|
29
50
|
},
|
|
30
|
-
"
|
|
31
|
-
"@types/bun": "latest",
|
|
32
|
-
"@types/node": "latest",
|
|
33
|
-
"typescript": "latest"
|
|
34
|
-
},
|
|
35
|
-
"peerDependencies": {
|
|
36
|
-
"typescript": "^5.0.0"
|
|
37
|
-
},
|
|
38
|
-
"dependencies": {
|
|
39
|
-
"pyright": "^1.1.403",
|
|
40
|
-
"vscode-jsonrpc": "^8.2.1",
|
|
41
|
-
"vscode-languageserver-types": "^3.17.5"
|
|
42
|
-
}
|
|
51
|
+
"type": "module"
|
|
43
52
|
}
|
package/src/cli.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
3
|
import path from 'path';
|
|
4
|
+
import { z } from 'zod';
|
|
4
5
|
import { startDaemon } from './daemon.js';
|
|
5
6
|
import { runCommand, sendToExistingDaemon } from './client.js';
|
|
6
7
|
import { formatDiagnosticsPlain } from './lsp/formatter.js';
|
|
@@ -9,70 +10,146 @@ import { HELP_MESSAGE } from './constants.js';
|
|
|
9
10
|
import { ensureDaemonRunning } from './utils.js';
|
|
10
11
|
import packageJson from '../package.json' with { type: 'json' };
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
// Schema for Claude Code PostToolUse hook payload
|
|
14
|
+
const HookDataSchema = z.object({
|
|
15
|
+
session_id: z.string().optional(),
|
|
16
|
+
transcript_path: z.string().optional(),
|
|
17
|
+
cwd: z.string().optional(),
|
|
18
|
+
hook_event_name: z.string().optional(),
|
|
19
|
+
tool_name: z.string().optional(),
|
|
20
|
+
tool_input: z
|
|
21
|
+
.object({
|
|
22
|
+
file_path: z.string().optional(),
|
|
23
|
+
content: z.string().optional(),
|
|
24
|
+
})
|
|
25
|
+
.optional(),
|
|
26
|
+
tool_response: z.any().optional(),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export async function handleClaudeCodeHook(
|
|
30
|
+
filePath: string
|
|
31
|
+
): Promise<{ hasIssues: boolean; output: string; daemonFailed?: boolean }> {
|
|
13
32
|
// Check if file exists
|
|
14
|
-
if (!await Bun.file(filePath).exists()) {
|
|
33
|
+
if (!(await Bun.file(filePath).exists())) {
|
|
15
34
|
return { hasIssues: false, output: '' };
|
|
16
35
|
}
|
|
17
|
-
|
|
36
|
+
|
|
18
37
|
// Filter supported file types
|
|
19
38
|
const supportedExts = [
|
|
20
|
-
'.ts',
|
|
21
|
-
'.
|
|
39
|
+
'.ts',
|
|
40
|
+
'.tsx',
|
|
41
|
+
'.js',
|
|
42
|
+
'.jsx',
|
|
43
|
+
'.mjs',
|
|
44
|
+
'.cjs',
|
|
45
|
+
'.mts',
|
|
46
|
+
'.cts',
|
|
47
|
+
'.py',
|
|
48
|
+
'.pyi',
|
|
22
49
|
'.go',
|
|
23
|
-
'.json',
|
|
24
|
-
'.
|
|
25
|
-
'.
|
|
26
|
-
'.
|
|
50
|
+
'.json',
|
|
51
|
+
'.jsonc',
|
|
52
|
+
'.css',
|
|
53
|
+
'.scss',
|
|
54
|
+
'.sass',
|
|
55
|
+
'.less',
|
|
56
|
+
'.yaml',
|
|
57
|
+
'.yml',
|
|
58
|
+
'.sh',
|
|
59
|
+
'.bash',
|
|
60
|
+
'.zsh',
|
|
27
61
|
'.java',
|
|
28
62
|
'.lua',
|
|
29
|
-
'.graphql',
|
|
63
|
+
'.graphql',
|
|
64
|
+
'.gql',
|
|
65
|
+
'.r',
|
|
66
|
+
'.R',
|
|
67
|
+
'.rmd',
|
|
68
|
+
'.Rmd',
|
|
69
|
+
'.cs',
|
|
30
70
|
];
|
|
31
71
|
const ext = path.extname(filePath);
|
|
32
72
|
if (!supportedExts.includes(ext)) {
|
|
33
73
|
return { hasIssues: false, output: '' };
|
|
34
74
|
}
|
|
35
|
-
|
|
75
|
+
|
|
36
76
|
// Get diagnostics (suppress errors to stdout)
|
|
37
77
|
try {
|
|
38
78
|
// Ensure daemon is running
|
|
39
79
|
const daemonStarted = await ensureDaemonRunning();
|
|
40
|
-
|
|
80
|
+
|
|
41
81
|
if (!daemonStarted) {
|
|
42
82
|
// Failed to start daemon - return with flag so caller can handle
|
|
43
|
-
return {
|
|
44
|
-
hasIssues: false,
|
|
45
|
-
output:
|
|
46
|
-
|
|
83
|
+
return {
|
|
84
|
+
hasIssues: false,
|
|
85
|
+
output:
|
|
86
|
+
'Failed to start LSP daemon. Please try running "cli-lsp-client stop" and retry.',
|
|
87
|
+
daemonFailed: true,
|
|
47
88
|
};
|
|
48
89
|
}
|
|
49
|
-
|
|
90
|
+
|
|
50
91
|
const result = await sendToExistingDaemon('diagnostics', [filePath]);
|
|
51
|
-
|
|
92
|
+
|
|
52
93
|
// The diagnostics command returns an array of diagnostics
|
|
53
94
|
if (!Array.isArray(result) || result.length === 0) {
|
|
54
95
|
return { hasIssues: false, output: '' };
|
|
55
96
|
}
|
|
56
|
-
|
|
97
|
+
|
|
57
98
|
const diagnostics = result as Diagnostic[];
|
|
58
|
-
|
|
99
|
+
|
|
59
100
|
// Format output for Claude Code hook (plain text, no ANSI codes)
|
|
60
101
|
const formatted = formatDiagnosticsPlain(filePath, diagnostics);
|
|
61
102
|
return { hasIssues: true, output: formatted || '' };
|
|
62
|
-
} catch (
|
|
103
|
+
} catch (_error) {
|
|
63
104
|
// Silently fail - don't break Claude Code experience
|
|
64
105
|
return { hasIssues: false, output: '' };
|
|
65
106
|
}
|
|
66
107
|
}
|
|
67
108
|
|
|
68
109
|
function showHelp(): void {
|
|
69
|
-
|
|
110
|
+
process.stdout.write(HELP_MESSAGE + '\n');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
type ParsedArgs = {
|
|
114
|
+
command: string;
|
|
115
|
+
commandArgs: string[];
|
|
116
|
+
configFile?: string;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
function parseArgs(args: string[]): ParsedArgs {
|
|
120
|
+
let configFile: string | undefined;
|
|
121
|
+
const filteredArgs: string[] = [];
|
|
122
|
+
|
|
123
|
+
for (let i = 0; i < args.length; i++) {
|
|
124
|
+
const arg = args[i];
|
|
125
|
+
|
|
126
|
+
if (arg === '--config-file') {
|
|
127
|
+
if (i + 1 >= args.length) {
|
|
128
|
+
process.stderr.write('Error: --config-file requires a path argument\n');
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
configFile = args[i + 1];
|
|
132
|
+
i++; // Skip the next argument since it's the config file path
|
|
133
|
+
} else if (arg.startsWith('--config-file=')) {
|
|
134
|
+
configFile = arg.split('=', 2)[1];
|
|
135
|
+
if (!configFile) {
|
|
136
|
+
process.stderr.write('Error: --config-file= requires a path after the equals sign\n');
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
filteredArgs.push(arg);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const command = filteredArgs[0] || 'status';
|
|
145
|
+
const commandArgs = filteredArgs.slice(1);
|
|
146
|
+
|
|
147
|
+
return { command, commandArgs, configFile };
|
|
70
148
|
}
|
|
71
149
|
|
|
72
150
|
async function run(): Promise<void> {
|
|
73
|
-
const
|
|
74
|
-
const command
|
|
75
|
-
const commandArgs = args.slice(1);
|
|
151
|
+
const rawArgs = process.argv.slice(2);
|
|
152
|
+
const { command, commandArgs, configFile } = parseArgs(rawArgs);
|
|
76
153
|
|
|
77
154
|
// Check if we're being invoked to run as daemon
|
|
78
155
|
if (process.env.LSPCLI_DAEMON_MODE === '1') {
|
|
@@ -88,7 +165,7 @@ async function run(): Promise<void> {
|
|
|
88
165
|
|
|
89
166
|
// Handle version command directly (no daemon needed)
|
|
90
167
|
if (command === 'version' || command === '--version' || command === '-v') {
|
|
91
|
-
|
|
168
|
+
process.stdout.write(packageJson.version + '\n');
|
|
92
169
|
return;
|
|
93
170
|
}
|
|
94
171
|
|
|
@@ -112,10 +189,14 @@ async function run(): Promise<void> {
|
|
|
112
189
|
}
|
|
113
190
|
|
|
114
191
|
// Parse the JSON to get the file path
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
192
|
+
const parseResult = HookDataSchema.safeParse(JSON.parse(stdinData));
|
|
193
|
+
if (!parseResult.success) {
|
|
194
|
+
process.exit(0); // Invalid JSON format, silently exit
|
|
195
|
+
}
|
|
196
|
+
const hookData = parseResult.data;
|
|
197
|
+
// Extract file_path from PostToolUse tool_input
|
|
198
|
+
const filePath = hookData.tool_input?.file_path;
|
|
199
|
+
|
|
119
200
|
if (!filePath) {
|
|
120
201
|
process.exit(0); // No file path, silently exit
|
|
121
202
|
}
|
|
@@ -123,22 +204,22 @@ async function run(): Promise<void> {
|
|
|
123
204
|
const result = await handleClaudeCodeHook(filePath);
|
|
124
205
|
if (result.daemonFailed) {
|
|
125
206
|
// Daemon failed to start - exit with status 1 to show error to user
|
|
126
|
-
|
|
207
|
+
process.stderr.write(result.output + '\n');
|
|
127
208
|
process.exit(1);
|
|
128
209
|
}
|
|
129
210
|
if (result.hasIssues) {
|
|
130
|
-
|
|
211
|
+
process.stderr.write(result.output + '\n');
|
|
131
212
|
process.exit(2);
|
|
132
213
|
}
|
|
133
214
|
process.exit(0);
|
|
134
|
-
} catch (
|
|
215
|
+
} catch (_error) {
|
|
135
216
|
// Silently fail for hook commands to not break Claude Code
|
|
136
217
|
process.exit(0);
|
|
137
218
|
}
|
|
138
219
|
}
|
|
139
220
|
|
|
140
221
|
// All other user commands are handled by the client (which auto-starts daemon if needed)
|
|
141
|
-
await runCommand(command, commandArgs);
|
|
222
|
+
await runCommand(command, commandArgs, configFile);
|
|
142
223
|
}
|
|
143
224
|
|
|
144
225
|
export { run };
|