cli-lsp-client 1.4.0 → 1.6.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 CHANGED
@@ -13,23 +13,25 @@ CLI tool for getting LSP diagnostics. Uses a background daemon to keep LSP serve
13
13
 
14
14
  ## Supported Languages
15
15
 
16
- | Language | LSP Server | Auto-installed | Notes |
17
- |----------|------------|----------------|-------|
18
- | TypeScript/JavaScript | `typescript-language-server` | ✓ (via bunx) | `.ts`, `.tsx`, `.js`, `.jsx`, `.mjs`, `.cjs`, `.mts`, `.cts` |
19
- | Python | `pyright-langserver` | ✓ (via bunx) | `.py`, `.pyi` |
20
- | JSON | `vscode-json-language-server` | ✓ (via vscode-langservers-extracted) | `.json`, `.jsonc` - includes schema validation |
21
- | CSS | `vscode-css-language-server` | ✓ (via vscode-langservers-extracted) | `.css`, `.scss`, `.sass`, `.less` |
22
- | YAML | `yaml-language-server` | ✓ (via bunx) | `.yaml`, `.yml` - includes schema validation |
23
- | Bash/Shell | `bash-language-server` | ✓ (via bunx) | `.sh`, `.bash`, `.zsh` - **requires shellcheck** (`brew install shellcheck`) |
24
- | Go | `gopls` | | Requires manual install: `go install golang.org/x/tools/gopls@latest` |
25
- | Java | `jdtls` (Eclipse JDT) | | `.java` - see [Java Installation](#java-installation-guide) below |
26
- | Lua | `lua-language-server` | | `.lua` - requires manual install via package manager (brew, scoop) or from [releases](https://github.com/LuaLS/lua-language-server/releases) |
27
-
16
+ | Language | LSP Server | Auto-installed | Notes |
17
+ | --------------------- | ------------------------------ | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- |
18
+ | TypeScript/JavaScript | `typescript-language-server` | ✓ (via bunx) | `.ts`, `.tsx`, `.js`, `.jsx`, `.mjs`, `.cjs`, `.mts`, `.cts` |
19
+ | Python | `pyright-langserver` | ✓ (via bunx) | `.py`, `.pyi` |
20
+ | JSON | `vscode-json-language-server` | ✓ (via vscode-langservers-extracted) | `.json`, `.jsonc` - includes schema validation |
21
+ | CSS | `vscode-css-language-server` | ✓ (via vscode-langservers-extracted) | `.css`, `.scss`, `.sass`, `.less` |
22
+ | YAML | `yaml-language-server` | ✓ (via bunx) | `.yaml`, `.yml` - includes schema validation |
23
+ | Bash/Shell | `bash-language-server` | ✓ (via bunx) | `.sh`, `.bash`, `.zsh` - **requires shellcheck** (`brew install shellcheck`) |
24
+ | GraphQL | `graphql-language-service-cli` | (via bunx) | `.graphql`, `.gql` |
25
+ | **R** | **R languageserver** | **✗** | **`.r`, `.R`, `.rmd`, `.Rmd` - see [R Installation](#r-installation-guide) below** |
26
+ | **C#** | **OmniSharp-Roslyn** | **✗** | **`.cs` - see [C# Installation](#c-installation-guide) below** |
27
+ | Go | `gopls` | ✗ | Requires manual install: `go install golang.org/x/tools/gopls@latest` |
28
+ | Java | `jdtls` (Eclipse JDT) | ✗ | `.java` - see [Java Installation](#java-installation-guide) below |
29
+ | 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
30
 
29
31
  ## How It Works
30
32
 
31
33
  - Daemon starts automatically when needed
32
- - LSP servers spawn based on file type
34
+ - LSP servers spawn based on file type
33
35
  - Finds project roots using config files (tsconfig.json, etc.)
34
36
  - Servers stay running for subsequent requests
35
37
 
@@ -37,7 +39,7 @@ CLI tool for getting LSP diagnostics. Uses a background daemon to keep LSP serve
37
39
 
38
40
  ### Real-time Diagnostics Hook
39
41
 
40
- Get instant diagnostic feedback for TypeScript, Python, Go, JSON, CSS, YAML, Bash, Java, and Lua files as you edit in Claude Code.
42
+ Get instant diagnostic feedback for TypeScript, Python, JSON, CSS, YAML, Bash, GraphQL, R, C#, Go, Java, and Lua files as you edit in Claude Code.
41
43
 
42
44
  #### Setup
43
45
 
@@ -53,7 +55,7 @@ Configure Claude Code to use the built-in hook command:
53
55
  "hooks": [
54
56
  {
55
57
  "type": "command",
56
- "command": "npx -y cli-lsp-client warmup"
58
+ "command": "npx -y cli-lsp-client start"
57
59
  }
58
60
  ]
59
61
  }
@@ -75,9 +77,9 @@ Configure Claude Code to use the built-in hook command:
75
77
 
76
78
  #### How It Works
77
79
 
78
- - **SessionStart**: Automatically warms up LSP servers when Claude Code starts for faster initial diagnostics
80
+ - **SessionStart**: Automatically starts LSP servers when Claude Code starts for faster initial diagnostics
79
81
  - **PostToolUse**: Runs diagnostics after each file edit (Edit, MultiEdit, Write tools)
80
- - Built-in file filtering for all supported languages (11 file types)
82
+ - Built-in file filtering for all supported languages (16 file types)
81
83
  - Shows errors, warnings, and hints inline
82
84
  - Graceful error handling - never breaks your editing experience
83
85
  - Uses the same fast daemon as the regular diagnostics command
@@ -88,7 +90,7 @@ When you save a file with errors, you'll see immediate feedback:
88
90
 
89
91
  ```
90
92
  Edit operation feedback:
91
- - [npx -y cli-lsp-client claude-code-hook]:
93
+ - [npx -y cli-lsp-client claude-code-hook]:
92
94
  ERROR at line 3, column 9:
93
95
  Type 'number' is not assignable to type 'string'.
94
96
  Source: typescript
@@ -106,6 +108,8 @@ npx cli-lsp-client diagnostics src/example.ts
106
108
  # Check any supported file type
107
109
  npx cli-lsp-client diagnostics app.py
108
110
  npx cli-lsp-client diagnostics main.go
111
+ npx cli-lsp-client diagnostics analysis.R
112
+ npx cli-lsp-client diagnostics Program.cs
109
113
  ```
110
114
 
111
115
  Exit codes: 0 for no issues, 2 for issues found.
@@ -126,20 +130,23 @@ npx cli-lsp-client hover src/main.ts myFunction
126
130
 
127
131
  # Get hover info for a variable or type
128
132
  npx cli-lsp-client hover app.py MyClass
133
+ npx cli-lsp-client hover analysis.R mean
134
+ npx cli-lsp-client hover Program.cs Console
129
135
  ```
130
136
 
131
- ```bash
137
+ ````bash
132
138
  $ npx cli-lsp-client hover src/client.ts runCommand
133
139
  Location: src/client.ts:370:17
134
140
  ```typescript
135
141
  export function runCommand(command: string, commandArgs: string[]): Promise<void>
136
- ```
137
- ```
142
+ ````
143
+
144
+ ````
138
145
 
139
146
  ### Daemon Management
140
147
 
141
148
  ```bash
142
- # Check daemon status and memory usage
149
+ # Check daemon status with uptime and running language servers
143
150
  npx cli-lsp-client status
144
151
 
145
152
  # List all running daemons across directories
@@ -156,6 +163,22 @@ npx cli-lsp-client --version
156
163
 
157
164
  # Show help
158
165
  npx cli-lsp-client help
166
+ ````
167
+
168
+ The `status` command shows the current daemon's uptime and running language servers:
169
+
170
+ ```bash
171
+ $ npx cli-lsp-client status
172
+ LSP Daemon Status
173
+ ================
174
+ PID: 33502
175
+ Uptime: 1m 38s
176
+
177
+ Language Servers:
178
+ - typescript (.) - running 1m 33s
179
+ - pyright (.) - running 1m 10s
180
+
181
+ Total: 2 language servers running
159
182
  ```
160
183
 
161
184
  The `list` command shows all running daemon instances with their working directories, PIDs, and status:
@@ -165,7 +188,7 @@ $ npx cli-lsp-client list
165
188
 
166
189
  Running Daemons:
167
190
  ================
168
- Hash | PID | Status | Working Directory
191
+ Hash | PID | Status | Working Directory
169
192
  ----------------------------------------------------------
170
193
  h0gx9u | 12345 | ● Running | /Users/user/project-a
171
194
  94yi9w | 12346 | ● Running | /Users/user/project-b
@@ -204,11 +227,13 @@ java -Declipse.application=org.eclipse.jdt.ls.core.id1 \
204
227
  ### Alternative Installation Methods
205
228
 
206
229
  **Homebrew (macOS/Linux)**:
230
+
207
231
  ```bash
208
232
  brew install jdtls
209
233
  ```
210
234
 
211
235
  **Arch Linux**:
236
+
212
237
  ```bash
213
238
  pacman -S jdtls
214
239
  ```
@@ -221,14 +246,141 @@ pacman -S jdtls
221
246
 
222
247
  For detailed setup instructions, see the [official Eclipse JDT.LS documentation](https://github.com/eclipse-jdtls/eclipse.jdt.ls).
223
248
 
249
+ ## R Installation Guide
250
+
251
+ The R language server requires R runtime and the `languageserver` package:
252
+
253
+ ### Installation Steps
254
+
255
+ 1. **Install R**: Download and install R from [CRAN](https://cran.r-project.org/) or use a package manager:
256
+
257
+ **macOS (Homebrew)**:
258
+ ```bash
259
+ brew install r
260
+ ```
261
+
262
+ **Ubuntu/Debian**:
263
+ ```bash
264
+ sudo apt-get update
265
+ sudo apt-get install r-base
266
+ ```
267
+
268
+ **Windows**: Download installer from [CRAN Windows](https://cran.r-project.org/bin/windows/base/)
269
+
270
+ 2. **Install R languageserver package**: Open R and run:
271
+ ```r
272
+ install.packages("languageserver")
273
+ ```
274
+
275
+ Or from command line:
276
+ ```bash
277
+ R --slave -e 'install.packages("languageserver", repos="https://cran.rstudio.com/")'
278
+ ```
279
+
280
+ ### Verification
281
+
282
+ Test that the language server works:
283
+ ```bash
284
+ R --slave -e 'languageserver::run()'
285
+ ```
286
+
287
+ ### Project Detection
288
+
289
+ The R LSP automatically detects R projects based on these files:
290
+ - `DESCRIPTION` (R packages)
291
+ - `NAMESPACE` (R packages)
292
+ - `.Rproj` (RStudio projects)
293
+ - `renv.lock` (renv dependency management)
294
+ - Any `.r`, `.R`, `.rmd`, `.Rmd` files
295
+
296
+ For more information, see the [R languageserver documentation](https://github.com/REditorSupport/languageserver).
297
+
298
+ ## C# Installation Guide
299
+
300
+ The C# language server requires .NET SDK and OmniSharp-Roslyn:
301
+
302
+ ### Installation Steps
303
+
304
+ 1. **Install .NET SDK**: Download .NET 6.0+ from [Microsoft .NET](https://dotnet.microsoft.com/download) or use a package manager:
305
+
306
+ **macOS (Homebrew)**:
307
+ ```bash
308
+ brew install dotnet
309
+ ```
310
+
311
+ **Ubuntu/Debian**:
312
+ ```bash
313
+ # Add Microsoft package repository
314
+ wget https://packages.microsoft.com/config/ubuntu/$(lsb_release -rs)/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
315
+ sudo dpkg -i packages-microsoft-prod.deb
316
+ sudo apt-get update
317
+ sudo apt-get install -y dotnet-sdk-8.0
318
+ ```
319
+
320
+ **Windows**: Download installer from [.NET Downloads](https://dotnet.microsoft.com/download)
321
+
322
+ 2. **Install OmniSharp-Roslyn**: Download the latest release from [OmniSharp releases](https://github.com/OmniSharp/omnisharp-roslyn/releases):
323
+
324
+ **Automatic script** (recommended):
325
+ ```bash
326
+ # Download and extract OmniSharp to ~/.omnisharp/
327
+ mkdir -p ~/.omnisharp
328
+ curl -L https://github.com/OmniSharp/omnisharp-roslyn/releases/latest/download/omnisharp-osx-arm64-net6.0.tar.gz | tar -xz -C ~/.omnisharp/
329
+
330
+ # Create symlink to make omnisharp available in PATH
331
+ sudo ln -sf ~/.omnisharp/OmniSharp /usr/local/bin/omnisharp
332
+ ```
333
+
334
+ **Manual installation**:
335
+ - Download the appropriate release for your platform (Windows: `omnisharp-win-x64-net6.0.zip`, Linux: `omnisharp-linux-x64-net6.0.tar.gz`)
336
+ - Extract to a directory (e.g., `~/.omnisharp/`)
337
+ - Add the executable to your PATH or create a symlink
338
+
339
+ 3. **Set environment variables**:
340
+
341
+ **Fish shell**:
342
+ ```bash
343
+ set -Ux DOTNET_ROOT ~/.dotnet
344
+ ```
345
+
346
+ **Bash/Zsh**:
347
+ ```bash
348
+ echo 'export DOTNET_ROOT=~/.dotnet' >> ~/.bashrc # or ~/.zshrc
349
+ source ~/.bashrc # or ~/.zshrc
350
+ ```
351
+
352
+ **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.
353
+
354
+ ### Verification
355
+
356
+ Test that OmniSharp works:
357
+ ```bash
358
+ # Verify DOTNET_ROOT is set
359
+ echo $DOTNET_ROOT
360
+
361
+ # Test OmniSharp command
362
+ omnisharp --help
363
+ ```
364
+
365
+ ### Project Detection
366
+
367
+ The C# LSP automatically detects C# projects based on these files:
368
+ - `*.sln` (Solution files)
369
+ - `*.csproj` (Project files)
370
+ - `project.json` (Legacy project files)
371
+ - `global.json` (.NET global configuration)
372
+ - Any `.cs` files
373
+
374
+ For more information, see the [OmniSharp documentation](https://github.com/OmniSharp/omnisharp-roslyn).
375
+
224
376
  ### Additional Commands
225
377
 
226
378
  ```bash
227
- # Warm up LSP servers for current directory (faster subsequent requests)
228
- npx cli-lsp-client warmup
379
+ # Start LSP servers for current directory (faster subsequent requests)
380
+ npx cli-lsp-client start
229
381
 
230
- # Warm up for specific directory
231
- npx cli-lsp-client warmup /path/to/project
382
+ # Start servers for specific directory
383
+ npx cli-lsp-client start /path/to/project
232
384
 
233
385
  # View daemon logs
234
386
  npx cli-lsp-client logs
package/cli-lsp-client CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cli-lsp-client",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "description": "CLI tool for fast LSP diagnostics with background daemon and multi-project support",
5
5
  "type": "module",
6
6
  "main": "src/cli.ts",
@@ -23,14 +23,27 @@
23
23
  "build": "bun build src/cli.ts --compile --outfile cli-lsp-client",
24
24
  "dev": "bun run src/cli.ts",
25
25
  "typecheck": "bun --bun tsc --noEmit",
26
+ "lint": "eslint .",
27
+ "lint:fix": "eslint . --fix",
28
+ "format": "prettier --check .",
29
+ "format:fix": "prettier --write .",
26
30
  "test": "bun test",
27
31
  "test:watch": "bun test tests/ --watch",
28
32
  "prepublish": "bun test && bun run build"
29
33
  },
30
34
  "devDependencies": {
35
+ "@eslint/js": "^9.33.0",
31
36
  "@types/bun": "latest",
32
37
  "@types/node": "latest",
33
- "typescript": "latest"
38
+ "eslint": "^9.33.0",
39
+ "eslint-config-prettier": "10.1.8",
40
+ "eslint-import-resolver-typescript": "^4.4.4",
41
+ "eslint-plugin-import-x": "^4.16.1",
42
+ "eslint-plugin-unused-imports": "^4.1.4",
43
+ "globals": "^16.3.0",
44
+ "prettier": "3.6.2",
45
+ "typescript": "latest",
46
+ "typescript-eslint": "^8.39.1"
34
47
  },
35
48
  "peerDependencies": {
36
49
  "typescript": "^5.0.0"
@@ -38,6 +51,7 @@
38
51
  "dependencies": {
39
52
  "pyright": "^1.1.403",
40
53
  "vscode-jsonrpc": "^8.2.1",
41
- "vscode-languageserver-types": "^3.17.5"
54
+ "vscode-languageserver-types": "^3.17.5",
55
+ "zod": "^4.0.17"
42
56
  }
43
57
  }
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,64 +10,104 @@ 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
- export async function handleClaudeCodeHook(filePath: string): Promise<{ hasIssues: boolean; output: string; daemonFailed?: boolean }> {
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', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.mts', '.cts',
21
- '.py', '.pyi',
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', '.jsonc',
24
- '.css', '.scss', '.sass', '.less',
25
- '.yaml', '.yml',
26
- '.sh', '.bash', '.zsh',
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', '.gql'
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: 'Failed to start LSP daemon. Please try running "cli-lsp-client stop" and retry.',
46
- daemonFailed: true
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 (error) {
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
- console.log(HELP_MESSAGE);
110
+ process.stdout.write(HELP_MESSAGE + '\n');
70
111
  }
71
112
 
72
113
  async function run(): Promise<void> {
@@ -88,7 +129,7 @@ async function run(): Promise<void> {
88
129
 
89
130
  // Handle version command directly (no daemon needed)
90
131
  if (command === 'version' || command === '--version' || command === '-v') {
91
- console.log(packageJson.version);
132
+ process.stdout.write(packageJson.version + '\n');
92
133
  return;
93
134
  }
94
135
 
@@ -112,10 +153,14 @@ async function run(): Promise<void> {
112
153
  }
113
154
 
114
155
  // Parse the JSON to get the file path
115
- const hookData = JSON.parse(stdinData);
116
- // Handle both PostToolUse format (tool_input.file_path) and simple format (file_path)
117
- const filePath = hookData.tool_input?.file_path || hookData.file_path || hookData.filePath;
118
-
156
+ const parseResult = HookDataSchema.safeParse(JSON.parse(stdinData));
157
+ if (!parseResult.success) {
158
+ process.exit(0); // Invalid JSON format, silently exit
159
+ }
160
+ const hookData = parseResult.data;
161
+ // Extract file_path from PostToolUse tool_input
162
+ const filePath = hookData.tool_input?.file_path;
163
+
119
164
  if (!filePath) {
120
165
  process.exit(0); // No file path, silently exit
121
166
  }
@@ -123,15 +168,15 @@ async function run(): Promise<void> {
123
168
  const result = await handleClaudeCodeHook(filePath);
124
169
  if (result.daemonFailed) {
125
170
  // Daemon failed to start - exit with status 1 to show error to user
126
- console.error(result.output);
171
+ process.stderr.write(result.output + '\n');
127
172
  process.exit(1);
128
173
  }
129
174
  if (result.hasIssues) {
130
- console.error(result.output);
175
+ process.stderr.write(result.output + '\n');
131
176
  process.exit(2);
132
177
  }
133
178
  process.exit(0);
134
- } catch (error) {
179
+ } catch (_error) {
135
180
  // Silently fail for hook commands to not break Claude Code
136
181
  process.exit(0);
137
182
  }