orquesta-cli 0.2.108 → 0.2.112

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 (34) hide show
  1. package/dist/cli.js +23 -1
  2. package/dist/core/commands/help.js +5 -0
  3. package/dist/core/commands/index.js +7 -0
  4. package/dist/core/commands/lsp.d.ts +3 -0
  5. package/dist/core/commands/lsp.js +37 -0
  6. package/dist/core/commands/mcp.d.ts +3 -0
  7. package/dist/core/commands/mcp.js +46 -0
  8. package/dist/core/commands/undo.d.ts +4 -0
  9. package/dist/core/commands/undo.js +45 -0
  10. package/dist/core/config/config-manager.d.ts +7 -1
  11. package/dist/core/config/config-manager.js +36 -0
  12. package/dist/core/file-snapshot-store.d.ts +25 -0
  13. package/dist/core/file-snapshot-store.js +104 -0
  14. package/dist/core/lsp/index.d.ts +6 -0
  15. package/dist/core/lsp/index.js +75 -0
  16. package/dist/core/lsp/jsonrpc.d.ts +18 -0
  17. package/dist/core/lsp/jsonrpc.js +38 -0
  18. package/dist/core/lsp/lsp-client.d.ts +40 -0
  19. package/dist/core/lsp/lsp-client.js +201 -0
  20. package/dist/core/lsp/server-registry.d.ts +14 -0
  21. package/dist/core/lsp/server-registry.js +85 -0
  22. package/dist/eval/eval-runner.js +14 -0
  23. package/dist/orchestration/plan-executor.js +2 -0
  24. package/dist/tools/llm/simple/file-tools.js +8 -2
  25. package/dist/tools/mcp/index.d.ts +3 -0
  26. package/dist/tools/mcp/index.js +3 -0
  27. package/dist/tools/mcp/mcp-client.d.ts +16 -0
  28. package/dist/tools/mcp/mcp-client.js +180 -0
  29. package/dist/tools/mcp/mcp-config.d.ts +4 -0
  30. package/dist/tools/mcp/mcp-config.js +87 -0
  31. package/dist/types/index.d.ts +21 -0
  32. package/dist/ui/components/PlanExecuteApp.js +25 -2
  33. package/dist/ui/hooks/slashCommandProcessor.js +17 -0
  34. package/package.json +2 -1
package/dist/cli.js CHANGED
@@ -349,6 +349,16 @@ program
349
349
  const toolsPromise = initializeOptionalTools().then(() => {
350
350
  logger.flow('Optional tools initialized');
351
351
  });
352
+ const mcpPromise = import('./tools/mcp/index.js')
353
+ .then(({ initializeMcpServers }) => initializeMcpServers())
354
+ .then((r) => {
355
+ if (r.servers > 0 || r.errors.length > 0) {
356
+ logger.flow('MCP initialized', { servers: r.servers, tools: r.tools, errors: r.errors.length });
357
+ }
358
+ })
359
+ .catch((error) => {
360
+ logger.warn('MCP init failed', { error: error instanceof Error ? error.message : String(error) });
361
+ });
352
362
  const syncPromise = (configManager.hasOrquestaConnection() && configManager.shouldAutoSync())
353
363
  ? syncOrquestaConfigs().then(syncResult => {
354
364
  if (syncResult.success && (syncResult.added > 0 || syncResult.updated > 0)) {
@@ -375,7 +385,7 @@ program
375
385
  logger.flow('No LLM endpoints configured');
376
386
  }
377
387
  await Promise.race([
378
- Promise.all([toolsPromise, syncPromise]),
388
+ Promise.all([toolsPromise, syncPromise, mcpPromise]),
379
389
  new Promise(resolve => setTimeout(resolve, Number(process.env['ORQUESTA_INIT_TIMEOUT_MS']) || 12000).unref?.()),
380
390
  ]);
381
391
  spinner.stop();
@@ -414,6 +424,18 @@ program
414
424
  sessionId: sessionManager.getCurrentSessionId(),
415
425
  exitReason: 'normal',
416
426
  });
427
+ try {
428
+ const { disconnectMcpServers } = await import('./tools/mcp/index.js');
429
+ await disconnectMcpServers();
430
+ }
431
+ catch {
432
+ }
433
+ try {
434
+ const { shutdownAllLspClients } = await import('./core/lsp/index.js');
435
+ await shutdownAllLspClients();
436
+ }
437
+ catch {
438
+ }
417
439
  if (cleanup) {
418
440
  await cleanup();
419
441
  }
@@ -6,12 +6,16 @@ export const helpCommand = {
6
6
  Available commands:
7
7
  /exit, /quit - Exit the application
8
8
  /clear - Clear conversation and TODOs
9
+ /undo - Revert the file changes from the last turn
10
+ /redo - Re-apply the changes undone by /undo
9
11
  /compact - Compact conversation to free up context
10
12
  /memory - Persistent memory: /memory add <note> | list | remove <n> | clear
11
13
  /settings - Open settings menu
12
14
  /model - Switch between LLM models
13
15
  /project - Switch between Orquesta projects
14
16
  /tool - Enable/disable optional tools (Browser, Background)
17
+ /mcp - List connected MCP servers and their tools
18
+ /lsp - LSP diagnostics after edits: status | on | off
15
19
  /load - Load a saved session
16
20
  /usage - Show token usage statistics
17
21
  /cost - Estimated USD spend this process (by model)
@@ -26,6 +30,7 @@ Available commands:
26
30
 
27
31
  Keyboard shortcuts:
28
32
  Ctrl+C - Exit
33
+ Ctrl+S - Send now / steer the running task (no wait for next turn)
29
34
  Ctrl+T - Toggle TODO details
30
35
  ESC - Interrupt current execution
31
36
  @ - File browser
@@ -4,10 +4,17 @@ import { compactCommand } from './compact.js';
4
4
  import { memoryCommand } from './memory.js';
5
5
  import { recallCommand } from './recall.js';
6
6
  import { helpCommand } from './help.js';
7
+ import { mcpCommand } from './mcp.js';
8
+ import { undoCommand, redoCommand } from './undo.js';
9
+ import { lspCommand } from './lsp.js';
7
10
  commandRegistry.register(clearCommand);
8
11
  commandRegistry.register(compactCommand);
9
12
  commandRegistry.register(memoryCommand);
10
13
  commandRegistry.register(recallCommand);
11
14
  commandRegistry.register(helpCommand);
15
+ commandRegistry.register(mcpCommand);
16
+ commandRegistry.register(undoCommand);
17
+ commandRegistry.register(redoCommand);
18
+ commandRegistry.register(lspCommand);
12
19
  export { commandRegistry } from './registry.js';
13
20
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,3 @@
1
+ import { SlashCommand } from './types.js';
2
+ export declare const lspCommand: SlashCommand;
3
+ //# sourceMappingURL=lsp.d.ts.map
@@ -0,0 +1,37 @@
1
+ import { configManager } from '../config/config-manager.js';
2
+ import { getServerDefs, resolveServerBinary, getActiveLspClientCount } from '../lsp/index.js';
3
+ export const lspCommand = {
4
+ name: '/lsp',
5
+ description: 'LSP diagnostics: /lsp | on | off',
6
+ async execute(context, rawInput) {
7
+ const reply = (content) => {
8
+ const updatedMessages = [...context.messages, { role: 'assistant', content }];
9
+ context.setMessages(updatedMessages);
10
+ return { handled: true, shouldContinue: false, updatedContext: { messages: updatedMessages } };
11
+ };
12
+ const arg = rawInput.slice('/lsp'.length).trim().toLowerCase();
13
+ if (arg === 'on' || arg === 'enable') {
14
+ await configManager.setLspEnabled(true);
15
+ return reply('✓ LSP diagnostics enabled.');
16
+ }
17
+ if (arg === 'off' || arg === 'disable') {
18
+ await configManager.setLspEnabled(false);
19
+ return reply('✓ LSP diagnostics disabled.');
20
+ }
21
+ const enabled = configManager.isLspEnabled();
22
+ const lines = [
23
+ `🩺 LSP diagnostics: ${enabled ? 'ON' : 'OFF'} (${getActiveLspClientCount()} server(s) running)`,
24
+ '',
25
+ 'Language servers (✓ = found on PATH):',
26
+ ];
27
+ for (const def of getServerDefs()) {
28
+ const bin = resolveServerBinary(def.command);
29
+ const mark = bin ? '✓' : '·';
30
+ const where = bin ? `→ ${bin}` : 'not installed';
31
+ lines.push(` ${mark} ${def.id} (${def.extensions.join(', ')}) ${def.command} ${where}`);
32
+ }
33
+ lines.push('', 'Toggle with /lsp on | /lsp off');
34
+ return reply(lines.join('\n'));
35
+ },
36
+ };
37
+ //# sourceMappingURL=lsp.js.map
@@ -0,0 +1,3 @@
1
+ import { SlashCommand } from './types.js';
2
+ export declare const mcpCommand: SlashCommand;
3
+ //# sourceMappingURL=mcp.d.ts.map
@@ -0,0 +1,46 @@
1
+ import { getConnectedMcpServers } from '../../tools/mcp/index.js';
2
+ import { loadMcpServerConfigs } from '../../tools/mcp/mcp-config.js';
3
+ export const mcpCommand = {
4
+ name: '/mcp',
5
+ description: 'List connected MCP servers and their tools',
6
+ async execute(context) {
7
+ const reply = (content) => {
8
+ const updatedMessages = [...context.messages, { role: 'assistant', content }];
9
+ context.setMessages(updatedMessages);
10
+ return { handled: true, shouldContinue: false, updatedContext: { messages: updatedMessages } };
11
+ };
12
+ const live = getConnectedMcpServers();
13
+ const configured = loadMcpServerConfigs();
14
+ if (configured.length === 0 && live.length === 0) {
15
+ return reply('No MCP servers configured.\n\n' +
16
+ 'Add one to ~/.orquesta-cli/config.json:\n' +
17
+ ' "mcpServers": [\n' +
18
+ ' { "name": "filesystem", "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "."] }\n' +
19
+ ' ]\n\n' +
20
+ 'Or drop a Claude-compatible .mcp.json in the project root.');
21
+ }
22
+ const liveByName = new Map(live.map((s) => [s.name, s]));
23
+ const lines = [`🔌 MCP servers (${live.length} connected / ${configured.length} configured):`];
24
+ for (const cfg of configured) {
25
+ const conn = liveByName.get(cfg.name);
26
+ const transport = cfg.url ? `http ${cfg.url}` : `stdio ${cfg.command ?? ''}`.trim();
27
+ if (conn) {
28
+ lines.push(`\n ✓ ${cfg.name} (${transport}) — ${conn.tools.length} tool(s)`);
29
+ for (const t of conn.tools)
30
+ lines.push(` • ${t}`);
31
+ }
32
+ else {
33
+ lines.push(`\n ✗ ${cfg.name} (${transport}) — not connected`);
34
+ }
35
+ }
36
+ for (const s of live) {
37
+ if (!configured.some((c) => c.name === s.name)) {
38
+ lines.push(`\n ✓ ${s.name} (runtime) — ${s.tools.length} tool(s)`);
39
+ for (const t of s.tools)
40
+ lines.push(` • ${t}`);
41
+ }
42
+ }
43
+ return reply(lines.join('\n'));
44
+ },
45
+ };
46
+ //# sourceMappingURL=mcp.js.map
@@ -0,0 +1,4 @@
1
+ import { SlashCommand } from './types.js';
2
+ export declare const undoCommand: SlashCommand;
3
+ export declare const redoCommand: SlashCommand;
4
+ //# sourceMappingURL=undo.d.ts.map
@@ -0,0 +1,45 @@
1
+ import { fileSnapshotStore } from '../file-snapshot-store.js';
2
+ import * as path from 'path';
3
+ function rel(p) {
4
+ const r = path.relative(process.cwd(), p);
5
+ return r && !r.startsWith('..') ? r : p;
6
+ }
7
+ function formatResult(verb, result) {
8
+ const lines = [`↩️ ${verb} "${result.label}" — ${result.restored.length} file(s):`];
9
+ for (const p of result.restored)
10
+ lines.push(` • ${rel(p)}`);
11
+ if (result.failed.length > 0) {
12
+ lines.push(` ⚠️ ${result.failed.length} could not be restored:`);
13
+ for (const p of result.failed)
14
+ lines.push(` ✗ ${rel(p)}`);
15
+ }
16
+ return lines.join('\n');
17
+ }
18
+ function reply(context, content) {
19
+ const updatedMessages = [...context.messages, { role: 'assistant', content }];
20
+ context.setMessages(updatedMessages);
21
+ return { handled: true, shouldContinue: false, updatedContext: { messages: updatedMessages } };
22
+ }
23
+ export const undoCommand = {
24
+ name: '/undo',
25
+ description: 'Revert the file changes from the last turn',
26
+ async execute(context) {
27
+ const result = await fileSnapshotStore.undo();
28
+ if (!result) {
29
+ return reply(context, 'Nothing to undo. (Only agent create_file/edit_file changes are tracked — not bash edits.)');
30
+ }
31
+ return reply(context, formatResult('Reverted', result));
32
+ },
33
+ };
34
+ export const redoCommand = {
35
+ name: '/redo',
36
+ description: 'Re-apply the changes undone by /undo',
37
+ async execute(context) {
38
+ const result = await fileSnapshotStore.redo();
39
+ if (!result) {
40
+ return reply(context, 'Nothing to redo.');
41
+ }
42
+ return reply(context, formatResult('Re-applied', result));
43
+ },
44
+ };
45
+ //# sourceMappingURL=undo.js.map
@@ -1,4 +1,4 @@
1
- import { OpenConfig, EndpointConfig, ModelInfo, OrquestaConfig, OrchestrationConfig, OrchestrationRole } from '../../types/index.js';
1
+ import { OpenConfig, EndpointConfig, ModelInfo, OrquestaConfig, OrchestrationConfig, OrchestrationRole, McpServerConfig, LspServerConfig } from '../../types/index.js';
2
2
  export declare class ConfigManager {
3
3
  private config;
4
4
  private initialized;
@@ -62,6 +62,12 @@ export declare class ConfigManager {
62
62
  setRefinerEnabled(enabled: boolean): Promise<void>;
63
63
  isSingleAgentMode(): boolean;
64
64
  setSingleAgentMode(enabled: boolean): Promise<void>;
65
+ getMcpServers(): McpServerConfig[];
66
+ addMcpServer(server: McpServerConfig): Promise<void>;
67
+ removeMcpServer(name: string): Promise<boolean>;
68
+ getLspServers(): LspServerConfig[];
69
+ isLspEnabled(): boolean;
70
+ setLspEnabled(enabled: boolean): Promise<void>;
65
71
  isWorktreeIsolationEnabled(): boolean;
66
72
  setWorktreeIsolationEnabled(enabled: boolean): Promise<void>;
67
73
  }
@@ -425,6 +425,42 @@ export class ConfigManager {
425
425
  config.orchestration.singleAgentMode = enabled;
426
426
  await this.saveConfig();
427
427
  }
428
+ getMcpServers() {
429
+ return this.config?.mcpServers ?? [];
430
+ }
431
+ async addMcpServer(server) {
432
+ const config = this.getConfig();
433
+ const list = config.mcpServers ?? [];
434
+ const idx = list.findIndex((s) => s.name === server.name);
435
+ if (idx >= 0)
436
+ list[idx] = server;
437
+ else
438
+ list.push(server);
439
+ config.mcpServers = list;
440
+ await this.saveConfig();
441
+ }
442
+ async removeMcpServer(name) {
443
+ const config = this.getConfig();
444
+ const list = config.mcpServers ?? [];
445
+ const next = list.filter((s) => s.name !== name);
446
+ const removed = next.length !== list.length;
447
+ if (removed) {
448
+ config.mcpServers = next;
449
+ await this.saveConfig();
450
+ }
451
+ return removed;
452
+ }
453
+ getLspServers() {
454
+ return this.config?.lspServers ?? [];
455
+ }
456
+ isLspEnabled() {
457
+ return this.config?.lsp?.enabled ?? true;
458
+ }
459
+ async setLspEnabled(enabled) {
460
+ const config = this.getConfig();
461
+ config.lsp = { ...config.lsp, enabled };
462
+ await this.saveConfig();
463
+ }
428
464
  isWorktreeIsolationEnabled() {
429
465
  return this.config?.orchestration?.worktreeIsolation ?? false;
430
466
  }
@@ -0,0 +1,25 @@
1
+ export interface UndoRedoResult {
2
+ label: string;
3
+ restored: string[];
4
+ failed: string[];
5
+ }
6
+ declare class FileSnapshotStore {
7
+ private current;
8
+ private undoStack;
9
+ private redoStack;
10
+ beginTurn(label: string): void;
11
+ seal(): void;
12
+ record(absPath: string, priorContent: string | null): void;
13
+ private apply;
14
+ undo(): Promise<UndoRedoResult | null>;
15
+ redo(): Promise<UndoRedoResult | null>;
16
+ getStatus(): {
17
+ undo: number;
18
+ redo: number;
19
+ pendingEdits: number;
20
+ };
21
+ reset(): void;
22
+ }
23
+ export declare const fileSnapshotStore: FileSnapshotStore;
24
+ export {};
25
+ //# sourceMappingURL=file-snapshot-store.d.ts.map
@@ -0,0 +1,104 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import { invalidateCache } from './file-cache.js';
4
+ import { logger } from '../utils/logger.js';
5
+ const MAX_SNAPSHOT_BYTES = 5 * 1024 * 1024;
6
+ const MAX_STACK = 50;
7
+ class FileSnapshotStore {
8
+ current = null;
9
+ undoStack = [];
10
+ redoStack = [];
11
+ beginTurn(label) {
12
+ this.seal();
13
+ this.redoStack = [];
14
+ this.current = { label: label.slice(0, 120), ts: Date.now(), entries: [] };
15
+ }
16
+ seal() {
17
+ if (this.current && this.current.entries.length > 0) {
18
+ this.undoStack.push(this.current);
19
+ if (this.undoStack.length > MAX_STACK)
20
+ this.undoStack.shift();
21
+ }
22
+ this.current = null;
23
+ }
24
+ record(absPath, priorContent) {
25
+ if (!this.current) {
26
+ this.current = { label: '(edits)', ts: Date.now(), entries: [] };
27
+ }
28
+ if (this.current.entries.some((e) => e.path === absPath))
29
+ return;
30
+ if (priorContent !== null && Buffer.byteLength(priorContent, 'utf-8') > MAX_SNAPSHOT_BYTES) {
31
+ logger.warn('Skipping snapshot: file too large for undo', { path: absPath });
32
+ return;
33
+ }
34
+ this.current.entries.push({ path: absPath, content: priorContent });
35
+ }
36
+ async apply(cp) {
37
+ const restored = [];
38
+ const failed = [];
39
+ for (const entry of cp.entries) {
40
+ let captured;
41
+ try {
42
+ captured = await fs.readFile(entry.path, 'utf-8');
43
+ }
44
+ catch {
45
+ captured = null;
46
+ }
47
+ try {
48
+ if (entry.content === null) {
49
+ await fs.rm(entry.path, { force: true });
50
+ }
51
+ else {
52
+ await fs.mkdir(path.dirname(entry.path), { recursive: true });
53
+ await fs.writeFile(entry.path, entry.content, 'utf-8');
54
+ }
55
+ invalidateCache(entry.path);
56
+ entry.content = captured;
57
+ restored.push(entry.path);
58
+ }
59
+ catch (err) {
60
+ logger.warn('Undo/redo failed for path', {
61
+ path: entry.path,
62
+ error: err instanceof Error ? err.message : String(err),
63
+ });
64
+ failed.push(entry.path);
65
+ }
66
+ }
67
+ return { label: cp.label, restored, failed };
68
+ }
69
+ async undo() {
70
+ this.seal();
71
+ const cp = this.undoStack.pop();
72
+ if (!cp)
73
+ return null;
74
+ const result = await this.apply(cp);
75
+ this.redoStack.push(cp);
76
+ if (this.redoStack.length > MAX_STACK)
77
+ this.redoStack.shift();
78
+ return result;
79
+ }
80
+ async redo() {
81
+ const cp = this.redoStack.pop();
82
+ if (!cp)
83
+ return null;
84
+ const result = await this.apply(cp);
85
+ this.undoStack.push(cp);
86
+ if (this.undoStack.length > MAX_STACK)
87
+ this.undoStack.shift();
88
+ return result;
89
+ }
90
+ getStatus() {
91
+ return {
92
+ undo: this.undoStack.length + (this.current && this.current.entries.length > 0 ? 1 : 0),
93
+ redo: this.redoStack.length,
94
+ pendingEdits: this.current?.entries.length ?? 0,
95
+ };
96
+ }
97
+ reset() {
98
+ this.current = null;
99
+ this.undoStack = [];
100
+ this.redoStack = [];
101
+ }
102
+ }
103
+ export const fileSnapshotStore = new FileSnapshotStore();
104
+ //# sourceMappingURL=file-snapshot-store.js.map
@@ -0,0 +1,6 @@
1
+ import { shutdownAllLspClients, getActiveLspClientCount, Diagnostic } from './lsp-client.js';
2
+ export declare function formatDiagnostics(relPath: string, diags: Diagnostic[]): string;
3
+ export declare function getDiagnosticsForFile(absPath: string): Promise<string>;
4
+ export { shutdownAllLspClients, getActiveLspClientCount };
5
+ export { findServerForFile, getServerDefs, resolveServerBinary } from './server-registry.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,75 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { findServerForFile } from './server-registry.js';
4
+ import { getOrCreateClient, shutdownAllLspClients, getActiveLspClientCount } from './lsp-client.js';
5
+ import { configManager } from '../config/config-manager.js';
6
+ import { logger } from '../../utils/logger.js';
7
+ const ROOT_MARKERS = ['tsconfig.json', 'package.json', 'pyproject.toml', 'go.mod', 'Cargo.toml', '.git'];
8
+ function findProjectRoot(absPath) {
9
+ let dir = path.dirname(absPath);
10
+ for (;;) {
11
+ for (const marker of ROOT_MARKERS) {
12
+ if (fs.existsSync(path.join(dir, marker)))
13
+ return dir;
14
+ }
15
+ const parent = path.dirname(dir);
16
+ if (parent === dir)
17
+ return path.dirname(absPath);
18
+ dir = parent;
19
+ }
20
+ }
21
+ function isEnabled() {
22
+ try {
23
+ return configManager.isLspEnabled();
24
+ }
25
+ catch {
26
+ return true;
27
+ }
28
+ }
29
+ const SEVERITY_LABEL = { 1: 'error', 2: 'warning', 3: 'info', 4: 'hint' };
30
+ export function formatDiagnostics(relPath, diags) {
31
+ const relevant = diags.filter((d) => d.severity <= 2);
32
+ if (relevant.length === 0)
33
+ return '';
34
+ relevant.sort((a, b) => a.severity - b.severity || a.line - b.line);
35
+ const MAX = 20;
36
+ const shown = relevant.slice(0, MAX);
37
+ const lines = shown.map((d) => {
38
+ const label = SEVERITY_LABEL[d.severity] ?? 'error';
39
+ const src = d.source ? ` (${d.source})` : '';
40
+ return ` ${label} [${d.line + 1}:${d.character + 1}]${src}: ${d.message.replace(/\s+/g, ' ').trim()}`;
41
+ });
42
+ const errorCount = relevant.filter((d) => d.severity === 1).length;
43
+ const warnCount = relevant.length - errorCount;
44
+ const header = `Diagnostics for ${relPath} — ${errorCount} error(s), ${warnCount} warning(s):`;
45
+ const more = relevant.length > MAX ? `\n …and ${relevant.length - MAX} more` : '';
46
+ return `\n\n${header}\n${lines.join('\n')}${more}`;
47
+ }
48
+ export async function getDiagnosticsForFile(absPath) {
49
+ if (!isEnabled())
50
+ return '';
51
+ try {
52
+ const server = findServerForFile(absPath);
53
+ if (!server)
54
+ return '';
55
+ let text;
56
+ try {
57
+ text = fs.readFileSync(absPath, 'utf-8');
58
+ }
59
+ catch {
60
+ return '';
61
+ }
62
+ const root = findProjectRoot(absPath);
63
+ const client = getOrCreateClient(server.def, root);
64
+ const diags = await client.getDiagnostics(absPath, text);
65
+ const rel = path.relative(process.cwd(), absPath) || path.basename(absPath);
66
+ return formatDiagnostics(rel, diags);
67
+ }
68
+ catch (err) {
69
+ logger.warn('LSP diagnostics failed', { path: absPath, error: err instanceof Error ? err.message : String(err) });
70
+ return '';
71
+ }
72
+ }
73
+ export { shutdownAllLspClients, getActiveLspClientCount };
74
+ export { findServerForFile, getServerDefs, resolveServerBinary } from './server-registry.js';
75
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,18 @@
1
+ export interface JsonRpcMessage {
2
+ jsonrpc: '2.0';
3
+ id?: number | string;
4
+ method?: string;
5
+ params?: unknown;
6
+ result?: unknown;
7
+ error?: {
8
+ code: number;
9
+ message: string;
10
+ data?: unknown;
11
+ };
12
+ }
13
+ export declare function encodeMessage(msg: JsonRpcMessage): Buffer;
14
+ export declare class MessageDecoder {
15
+ private buffer;
16
+ push(chunk: Buffer): JsonRpcMessage[];
17
+ }
18
+ //# sourceMappingURL=jsonrpc.d.ts.map
@@ -0,0 +1,38 @@
1
+ export function encodeMessage(msg) {
2
+ const json = JSON.stringify(msg);
3
+ const body = Buffer.from(json, 'utf-8');
4
+ const header = Buffer.from(`Content-Length: ${body.length}\r\n\r\n`, 'ascii');
5
+ return Buffer.concat([header, body]);
6
+ }
7
+ export class MessageDecoder {
8
+ buffer = Buffer.alloc(0);
9
+ push(chunk) {
10
+ this.buffer = this.buffer.length === 0 ? chunk : Buffer.concat([this.buffer, chunk]);
11
+ const out = [];
12
+ for (;;) {
13
+ const headerEnd = this.buffer.indexOf('\r\n\r\n');
14
+ if (headerEnd === -1)
15
+ break;
16
+ const header = this.buffer.subarray(0, headerEnd).toString('ascii');
17
+ const match = /Content-Length:\s*(\d+)/i.exec(header);
18
+ if (!match) {
19
+ this.buffer = this.buffer.subarray(headerEnd + 4);
20
+ continue;
21
+ }
22
+ const contentLength = parseInt(match[1], 10);
23
+ const bodyStart = headerEnd + 4;
24
+ const bodyEnd = bodyStart + contentLength;
25
+ if (this.buffer.length < bodyEnd)
26
+ break;
27
+ const body = this.buffer.subarray(bodyStart, bodyEnd).toString('utf-8');
28
+ this.buffer = this.buffer.subarray(bodyEnd);
29
+ try {
30
+ out.push(JSON.parse(body));
31
+ }
32
+ catch {
33
+ }
34
+ }
35
+ return out;
36
+ }
37
+ }
38
+ //# sourceMappingURL=jsonrpc.js.map
@@ -0,0 +1,40 @@
1
+ import { fileURLToPath } from 'url';
2
+ import { LspServerDef } from './server-registry.js';
3
+ export interface Diagnostic {
4
+ severity: number;
5
+ line: number;
6
+ character: number;
7
+ message: string;
8
+ source?: string;
9
+ }
10
+ declare class LspClient {
11
+ private def;
12
+ private rootDir;
13
+ private proc;
14
+ private decoder;
15
+ private nextId;
16
+ private pendingRequests;
17
+ private diagnostics;
18
+ private diagWaiters;
19
+ private openDocs;
20
+ private initialized;
21
+ private version;
22
+ constructor(def: LspServerDef, rootDir: string);
23
+ private send;
24
+ private request;
25
+ private notify;
26
+ private handleMessage;
27
+ start(): Promise<void>;
28
+ getDiagnostics(absPath: string, text: string): Promise<Diagnostic[]>;
29
+ private onceDiagnostics;
30
+ private delay;
31
+ private waitFor;
32
+ private teardown;
33
+ stop(): Promise<void>;
34
+ }
35
+ export declare function getOrCreateClient(def: LspServerDef, rootDir: string): LspClient;
36
+ export declare function shutdownAllLspClients(): Promise<void>;
37
+ export declare function getActiveLspClientCount(): number;
38
+ export { fileURLToPath };
39
+ export type { LspClient };
40
+ //# sourceMappingURL=lsp-client.d.ts.map