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 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
+ }