Skip to content

Commit 02c9418

Browse files
authored
Merge pull request #7 from shadowsaber16/feat/remote-ssh-support
feat: support remote Claude Code sessions via BUDDI_HOST env var
2 parents 4329586 + 0f8c91c commit 02c9418

1 file changed

Lines changed: 45 additions & 5 deletions

File tree

buddi/Buddi/Resources/buddi-hook.py

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,59 @@
11
#!/usr/bin/env python3
22
"""
33
Buddi Hook
4-
- Sends session state to Buddi.app via Unix socket
4+
- Sends session state to Buddi.app via Unix socket (local) or TCP (remote)
55
- For PermissionRequest: waits for user decision from the app
6+
7+
Environment variables:
8+
BUDDI_HOST If set (e.g. "localhost:9999"), connect over TCP instead of
9+
a Unix socket. Intended for Claude Code running on a remote
10+
VM with an SSH reverse port-forward back to the Mac running
11+
Buddi. Always tunnel over SSH — events may include prompts,
12+
tool calls, and file paths.
13+
BUDDI_SOCKET Override the default Unix socket path (/tmp/buddi.sock).
14+
Ignored when BUDDI_HOST is set.
615
"""
716
import json
817
import os
918
import socket
1019
import sys
1120

12-
SOCKET_PATH = "/tmp/buddi.sock"
21+
BUDDI_SOCKET = os.environ.get("BUDDI_SOCKET", "/tmp/buddi.sock")
22+
BUDDI_HOST = os.environ.get("BUDDI_HOST")
1323
TIMEOUT_SECONDS = 300 # 5 minutes for permission decisions
1424

25+
if BUDDI_HOST:
26+
_host = BUDDI_HOST.rpartition(":")[0].strip("[]")
27+
if _host not in ("localhost", "127.0.0.1", "::1"):
28+
print(
29+
f"buddi-hook: warning: BUDDI_HOST={BUDDI_HOST!r} is not a loopback "
30+
"address; events contain prompts and tool inputs — only use over "
31+
"an SSH tunnel.",
32+
file=sys.stderr,
33+
)
34+
35+
36+
def _connect_to_buddi():
37+
"""Open a connection to Buddi (TCP if BUDDI_HOST is set, else Unix socket)."""
38+
if BUDDI_HOST:
39+
host, sep, port = BUDDI_HOST.rpartition(":")
40+
if not sep or not host:
41+
raise OSError(f"BUDDI_HOST must be host:port, got {BUDDI_HOST!r}")
42+
try:
43+
port_num = int(port)
44+
except ValueError:
45+
raise OSError(f"BUDDI_HOST port must be an integer, got {port!r}")
46+
if not 0 < port_num <= 65535:
47+
raise OSError(f"BUDDI_HOST port {port_num} out of range (1-65535)")
48+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
49+
sock.settimeout(TIMEOUT_SECONDS)
50+
sock.connect((host, port_num))
51+
else:
52+
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
53+
sock.settimeout(TIMEOUT_SECONDS)
54+
sock.connect(BUDDI_SOCKET)
55+
return sock
56+
1557

1658
def get_tty():
1759
"""Get the TTY of the Claude process (parent)"""
@@ -83,9 +125,7 @@ def get_cmux_surface():
83125
def send_event(state):
84126
"""Send event to app, return response if any"""
85127
try:
86-
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
87-
sock.settimeout(TIMEOUT_SECONDS)
88-
sock.connect(SOCKET_PATH)
128+
sock = _connect_to_buddi()
89129
sock.sendall(json.dumps(state).encode())
90130

91131
# For permission requests, wait for response

0 commit comments

Comments
 (0)