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.
Files changed (4) hide show
  1. package/README.md +287 -22
  2. package/cli-lsp-client +0 -0
  3. package/package.json +35 -26
  4. 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 | 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
-
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, Go, JSON, CSS, YAML, Bash, Java, and Lua files as you edit in Claude Code.
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 (11 file types)
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
- ```bash
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 logs
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.5.0",
4
- "description": "CLI tool for fast LSP diagnostics with background daemon and multi-project support",
5
- "type": "module",
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
- "license": "MIT",
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
- "bugs": {
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 --bun tsc --noEmit",
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
- "prepublish": "bun test && bun run build"
48
+ "prepublishOnly": "bun test && bun run build",
49
+ "postinstall": "node scripts/postinstall.js"
29
50
  },
30
- "devDependencies": {
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
- 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');
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 args = process.argv.slice(2);
74
- const command = args[0] || 'status';
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
- console.log(packageJson.version);
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 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
-
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
- console.error(result.output);
207
+ process.stderr.write(result.output + '\n');
127
208
  process.exit(1);
128
209
  }
129
210
  if (result.hasIssues) {
130
- console.error(result.output);
211
+ process.stderr.write(result.output + '\n');
131
212
  process.exit(2);
132
213
  }
133
214
  process.exit(0);
134
- } catch (error) {
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 };