tmux-runtimelisted
Install: claude install-skill vmehera123/leashd
# tmux runtime
leashd's **default** runtime. Instead of leashd owning a subprocess, it runs a real interactive `claude` TUI inside a tmux pane on a private socket, and the safety pipeline runs over **Claude Code HTTP hooks** (the `--permission-prompt-tool` path does **not** fire in interactive mode). Requires `claude ≥ 2.1.141` and `tmux ≥ 3.3`. Default socket: `~/.leashd/tmux/tmux.sock` (`LEASHD_TMUX_SOCKET_DIR`).
## Files
| File | Role |
|---|---|
| `agents/runtimes/tmux.py` | `TmuxAgent` — the `BaseAgent` impl; spawns/follows panes, `cancel_chat()` to kill a live pane by chat |
| `agents/runtimes/tmux_session.py` | `TmuxSessionManager` — owns **all** tmux/libtmux interaction and the hook→gatekeeper bridge (~3k lines; the heavy core) |
| `web/tmux_hooks.py` | thin FastAPI router; Claude Code hooks POST here, it delegates to the session manager |
| `web/tmux_jsonl.py` | polls the session JSONL and feeds text/cost events back (streaming) |
| `web/tmux_server.py` | standalone loopback hook receiver for Telegram-only / CLI-only mode (WebUI/multi mode mounts the hook router on the WebUI app instead) |
`main.py:_maybe_tmux_session_manager` builds the **shared** `TmuxSessionManager` singleton so the hook receiver and the runtime drive the same manager.
## How a turn works
1. **Prompt in** — text is injected as **keystrokes** into the pane's composer. The TUI ignores programmatic answer payloads (`updatedInput.answers`), so questions/approvals must be answered by driving keys