cli-lsp-client 1.0.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/LICENSE +21 -0
- package/README.md +181 -0
- package/cli-lsp-client +0 -0
- package/package.json +42 -0
- package/src/cli.ts +113 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 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
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
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
|
+
- Background daemon for fast repeated requests
|
|
9
|
+
- Built in Claude Code hook to provide feedback on file edit tool calls
|
|
10
|
+
|
|
11
|
+
## Supported Languages
|
|
12
|
+
|
|
13
|
+
| Language | LSP Server | Auto-installed | Notes |
|
|
14
|
+
|----------|------------|----------------|-------|
|
|
15
|
+
| TypeScript/JavaScript | `typescript-language-server` | ✓ (via bunx) | `.ts`, `.tsx`, `.js`, `.jsx`, `.mjs`, `.cjs`, `.mts`, `.cts` |
|
|
16
|
+
| Python | `pyright-langserver` | ✓ (via bunx) | `.py`, `.pyi` |
|
|
17
|
+
| JSON | `vscode-json-language-server` | ✓ (via vscode-langservers-extracted) | `.json`, `.jsonc` - includes schema validation |
|
|
18
|
+
| CSS | `vscode-css-language-server` | ✓ (via vscode-langservers-extracted) | `.css`, `.scss`, `.sass`, `.less` |
|
|
19
|
+
| YAML | `yaml-language-server` | ✓ (via bunx) | `.yaml`, `.yml` - includes schema validation |
|
|
20
|
+
| Bash/Shell | `bash-language-server` | ✓ (via bunx) | `.sh`, `.bash`, `.zsh` - **requires shellcheck** (`brew install shellcheck`) |
|
|
21
|
+
| Go | `gopls` | ✗ | Requires manual install: `go install golang.org/x/tools/gopls@latest` |
|
|
22
|
+
| Java | `jdtls` (Eclipse JDT) | ✗ | `.java` - see [Java Installation](#java-installation-guide) below |
|
|
23
|
+
| Lua | `lua-language-server` | ✗ | `.lua` - requires manual install via package manager (brew, scoop) or from [releases](https://github.com/LuaLS/lua-language-server/releases) |
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
## How It Works
|
|
27
|
+
|
|
28
|
+
- Daemon starts automatically when needed
|
|
29
|
+
- LSP servers spawn based on file type
|
|
30
|
+
- Finds project roots using config files (tsconfig.json, etc.)
|
|
31
|
+
- Servers stay running for subsequent requests
|
|
32
|
+
|
|
33
|
+
## Claude Code Integration
|
|
34
|
+
|
|
35
|
+
### Real-time Diagnostics Hook
|
|
36
|
+
|
|
37
|
+
Get instant diagnostic feedback for TypeScript, Python, Go, JSON, CSS, YAML, Bash, Java, and Lua files as you edit in Claude Code.
|
|
38
|
+
|
|
39
|
+
#### Setup
|
|
40
|
+
|
|
41
|
+
Configure Claude Code to use the built-in hook command:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
{
|
|
45
|
+
"$schema": "https://json.schemastore.org/claude-code-settings.json",
|
|
46
|
+
"hooks": {
|
|
47
|
+
"PostToolUse": [
|
|
48
|
+
{
|
|
49
|
+
"matcher": "Edit|MultiEdit|Write",
|
|
50
|
+
"hooks": [
|
|
51
|
+
{
|
|
52
|
+
"type": "command",
|
|
53
|
+
"command": "npx -y cli-lsp-client claude-code-hook"
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
#### How It Works
|
|
63
|
+
|
|
64
|
+
- Automatically runs diagnostics after each file edit
|
|
65
|
+
- Built-in file filtering for all supported languages (11 file types)
|
|
66
|
+
- Shows errors, warnings, and hints inline
|
|
67
|
+
- Graceful error handling - never breaks your editing experience
|
|
68
|
+
- Uses the same fast daemon as the regular diagnostics command
|
|
69
|
+
|
|
70
|
+
#### Example Output
|
|
71
|
+
|
|
72
|
+
When you save a file with errors, you'll see immediate feedback:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
Edit operation feedback:
|
|
76
|
+
- [npx -y cli-lsp-client claude-code-hook]:
|
|
77
|
+
ERROR at line 3, column 9:
|
|
78
|
+
Type 'number' is not assignable to type 'string'.
|
|
79
|
+
Source: typescript
|
|
80
|
+
Code: 2322
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Usage
|
|
84
|
+
|
|
85
|
+
### Get Diagnostics
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# Check a TypeScript file
|
|
89
|
+
npx cli-lsp-client diagnostics src/example.ts
|
|
90
|
+
|
|
91
|
+
# Check any supported file type
|
|
92
|
+
npx cli-lsp-client diagnostics app.py
|
|
93
|
+
npx cli-lsp-client diagnostics main.go
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Exit codes: 0 for no issues, 2 for issues found.
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
$ npx cli-lsp-client diagnostics error.ts
|
|
100
|
+
ERROR at line 5, column 20:
|
|
101
|
+
Argument of type 'string' is not assignable to parameter of type 'number'.
|
|
102
|
+
Source: typescript
|
|
103
|
+
Code: 2345
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Other Commands
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# Check daemon status
|
|
110
|
+
npx cli-lsp-client status
|
|
111
|
+
|
|
112
|
+
# Stop daemon (it will auto-restart when needed)
|
|
113
|
+
npx cli-lsp-client stop
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Java Installation Guide
|
|
117
|
+
|
|
118
|
+
Eclipse JDT Language Server requires Java 17+ and manual setup:
|
|
119
|
+
|
|
120
|
+
### Installation Steps
|
|
121
|
+
|
|
122
|
+
1. **Download**: Get the latest server from [Eclipse JDT.LS downloads](http://download.eclipse.org/jdtls/snapshots/)
|
|
123
|
+
2. **Extract**: Unpack to your preferred location (e.g., `/opt/jdtls/`)
|
|
124
|
+
3. **Create wrapper script** named `jdtls` in your PATH:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
#!/bin/bash
|
|
128
|
+
java -Declipse.application=org.eclipse.jdt.ls.core.id1 \
|
|
129
|
+
-Dosgi.bundles.defaultStartLevel=4 \
|
|
130
|
+
-Declipse.product=org.eclipse.jdt.ls.core.product \
|
|
131
|
+
-Xms1g -Xmx2G \
|
|
132
|
+
-jar /opt/jdtls/plugins/org.eclipse.equinox.launcher_*.jar \
|
|
133
|
+
-configuration /opt/jdtls/config_linux \
|
|
134
|
+
-data "${1:-$HOME/workspace}" \
|
|
135
|
+
--add-modules=ALL-SYSTEM \
|
|
136
|
+
--add-opens java.base/java.util=ALL-UNNAMED \
|
|
137
|
+
--add-opens java.base/java.lang=ALL-UNNAMED "$@"
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
4. **Make executable**: `chmod +x /usr/local/bin/jdtls`
|
|
141
|
+
|
|
142
|
+
### Alternative Installation Methods
|
|
143
|
+
|
|
144
|
+
**Homebrew (macOS/Linux)**:
|
|
145
|
+
```bash
|
|
146
|
+
brew install jdtls
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**Arch Linux**:
|
|
150
|
+
```bash
|
|
151
|
+
pacman -S jdtls
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Configuration Notes
|
|
155
|
+
|
|
156
|
+
- Replace `config_linux` with `config_mac` on macOS or `config_win` on Windows
|
|
157
|
+
- Adjust the `-data` workspace path as needed
|
|
158
|
+
- Requires Java 17 or higher to run
|
|
159
|
+
|
|
160
|
+
For detailed setup instructions, see the [official Eclipse JDT.LS documentation](https://github.com/eclipse-jdtls/eclipse.jdt.ls).
|
|
161
|
+
|
|
162
|
+
## Examples
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
# Check a specific file
|
|
166
|
+
npx cli-lsp-client diagnostics src/main.ts
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Development
|
|
170
|
+
|
|
171
|
+
### Installation
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
# Install dependencies and build
|
|
175
|
+
bun install
|
|
176
|
+
bun run build # Build executable
|
|
177
|
+
bun run typecheck
|
|
178
|
+
bun test
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Add new LSP servers in `src/lsp/servers.ts`.
|
package/cli-lsp-client
ADDED
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cli-lsp-client",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A Bun CLI daemon application",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/cli.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"cli-lsp-client": "cli-lsp-client"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"cli-lsp-client"
|
|
12
|
+
],
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/eli0shin/cli-lsp-client.git"
|
|
16
|
+
},
|
|
17
|
+
"homepage": "https://github.com/eli0shin/cli-lsp-client#readme",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/eli0shin/cli-lsp-client/issues"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "bun build src/cli.ts --compile --outfile cli-lsp-client",
|
|
23
|
+
"dev": "bun run src/cli.ts",
|
|
24
|
+
"typecheck": "bun --bun tsc --noEmit",
|
|
25
|
+
"test": "bun test",
|
|
26
|
+
"test:watch": "bun test tests/ --watch",
|
|
27
|
+
"prepublish": "bun test && bun run build"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/bun": "latest",
|
|
31
|
+
"@types/node": "latest",
|
|
32
|
+
"typescript": "latest"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"typescript": "^5.0.0"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"pyright": "^1.1.403",
|
|
39
|
+
"vscode-jsonrpc": "^8.2.1",
|
|
40
|
+
"vscode-languageserver-types": "^3.17.5"
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { startDaemon } from './daemon.js';
|
|
5
|
+
import { runCommand } from './client.js';
|
|
6
|
+
import { formatDiagnosticsPlain } from './lsp/formatter.js';
|
|
7
|
+
import type { Diagnostic } from './lsp/types.js';
|
|
8
|
+
|
|
9
|
+
export async function handleClaudeCodeHook(filePath: string): Promise<{ hasIssues: boolean; output: string }> {
|
|
10
|
+
// Check if file exists
|
|
11
|
+
if (!await Bun.file(filePath).exists()) {
|
|
12
|
+
return { hasIssues: false, output: '' };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Filter supported file types
|
|
16
|
+
const supportedExts = [
|
|
17
|
+
'.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.mts', '.cts',
|
|
18
|
+
'.py', '.pyi',
|
|
19
|
+
'.go',
|
|
20
|
+
'.json', '.jsonc',
|
|
21
|
+
'.css', '.scss', '.sass', '.less',
|
|
22
|
+
'.yaml', '.yml',
|
|
23
|
+
'.sh', '.bash', '.zsh',
|
|
24
|
+
'.java',
|
|
25
|
+
'.lua',
|
|
26
|
+
'.graphql', '.gql'
|
|
27
|
+
];
|
|
28
|
+
const ext = path.extname(filePath);
|
|
29
|
+
if (!supportedExts.includes(ext)) {
|
|
30
|
+
return { hasIssues: false, output: '' };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Get diagnostics (suppress errors to stdout)
|
|
34
|
+
try {
|
|
35
|
+
const { sendToExistingDaemon } = await import('./client.js');
|
|
36
|
+
const result = await sendToExistingDaemon('diagnostics', [filePath]);
|
|
37
|
+
|
|
38
|
+
// The diagnostics command returns an array of diagnostics
|
|
39
|
+
if (!Array.isArray(result) || result.length === 0) {
|
|
40
|
+
return { hasIssues: false, output: '' };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const diagnostics = result as Diagnostic[];
|
|
44
|
+
|
|
45
|
+
// Format output for Claude Code hook (plain text, no ANSI codes)
|
|
46
|
+
const formatted = formatDiagnosticsPlain(filePath, diagnostics);
|
|
47
|
+
return { hasIssues: true, output: formatted || '' };
|
|
48
|
+
} catch (error) {
|
|
49
|
+
// Silently fail - don't break Claude Code experience
|
|
50
|
+
return { hasIssues: false, output: '' };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function run(): Promise<void> {
|
|
55
|
+
const args = process.argv.slice(2);
|
|
56
|
+
const command = args[0] || 'status';
|
|
57
|
+
const commandArgs = args.slice(1);
|
|
58
|
+
|
|
59
|
+
// Check if we're being invoked to run as daemon
|
|
60
|
+
if (process.env.LSPCLI_DAEMON_MODE === '1') {
|
|
61
|
+
await startDaemon();
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Handle claude-code-hook command directly (reads JSON from stdin)
|
|
66
|
+
if (command === 'claude-code-hook') {
|
|
67
|
+
try {
|
|
68
|
+
// Read JSON from stdin
|
|
69
|
+
const stdinData = await new Promise<string>((resolve, reject) => {
|
|
70
|
+
let data = '';
|
|
71
|
+
process.stdin.on('data', (chunk) => {
|
|
72
|
+
data += chunk.toString();
|
|
73
|
+
});
|
|
74
|
+
process.stdin.on('end', () => {
|
|
75
|
+
resolve(data);
|
|
76
|
+
});
|
|
77
|
+
process.stdin.on('error', reject);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (!stdinData.trim()) {
|
|
81
|
+
process.exit(0); // No input, silently exit
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Parse the JSON to get the file path
|
|
85
|
+
const hookData = JSON.parse(stdinData);
|
|
86
|
+
// Handle both PostToolUse format (tool_input.file_path) and simple format (file_path)
|
|
87
|
+
const filePath = hookData.tool_input?.file_path || hookData.file_path || hookData.filePath;
|
|
88
|
+
|
|
89
|
+
if (!filePath) {
|
|
90
|
+
process.exit(0); // No file path, silently exit
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const result = await handleClaudeCodeHook(filePath);
|
|
94
|
+
if (result.hasIssues) {
|
|
95
|
+
console.error(result.output);
|
|
96
|
+
process.exit(2);
|
|
97
|
+
}
|
|
98
|
+
process.exit(0);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
// Silently fail for hook commands to not break Claude Code
|
|
101
|
+
process.exit(0);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// All other user commands are handled by the client (which auto-starts daemon if needed)
|
|
106
|
+
await runCommand(command, commandArgs);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export { run };
|
|
110
|
+
|
|
111
|
+
if (import.meta.main) {
|
|
112
|
+
run();
|
|
113
|
+
}
|