"""Client library for LSP tool scripts. Connects to the bridge daemon, starting it if needed.""" import json import os import socket import subprocess import sys import time SOCK_PATH = os.path.join(os.environ.get("XDG_RUNTIME_DIR", "/tmp"), "ollie-lsp.sock") PID_PATH = SOCK_PATH + ".pid" def _bridge_running() -> bool: if not os.path.exists(PID_PATH): return False try: pid = int(open(PID_PATH).read().strip()) os.kill(pid, 0) return True except (OSError, ValueError): return False def _start_bridge(): """Start the bridge daemon as a background process.""" lib_dir = os.path.dirname(os.path.abspath(__file__)) bridge_script = os.path.join(lib_dir, "bridge.py") subprocess.Popen( [sys.executable, bridge_script], stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, start_new_session=True, ) # Wait for socket to appear for _ in range(50): if os.path.exists(SOCK_PATH): return time.sleep(0.1) print("error: bridge daemon failed to start", file=sys.stderr) sys.exit(1) def request(method: str, **kwargs) -> dict: """Send a request to the bridge and return the response.""" if not _bridge_running(): _start_bridge() payload = {"method": method, **kwargs} data = json.dumps(payload).encode() sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) try: sock.connect(SOCK_PATH) sock.sendall(data) sock.shutdown(socket.SHUT_WR) chunks = [] while True: chunk = sock.recv(4096) if not chunk: break chunks.append(chunk) response = json.loads(b"".join(chunks)) except ConnectionRefusedError: # Stale socket, restart if os.path.exists(SOCK_PATH): os.unlink(SOCK_PATH) _start_bridge() return request(method, **kwargs) finally: sock.close() if "error" in response: print(f"error: {response['error']}", file=sys.stderr) sys.exit(1) return response.get("result")