Restarting to pick up a code change costs the three things this app exists to hold: the proxy port, the embedded claude pane, and the live feed. So ctrl-r does not restart — it execs the binary now on disk into this same process. execve keeps the pid, the open fds and the child processes, so the listener socket, the pty and its claude child all simply carry on; the feed travels in a JSON snapshot. Build nothing, watch nothing. A build is the user's business, and a running instance must not decide on its own when to become different code. Rebuild outside, then press ctrl-r in each instance. Drain before the exec. It destroys the tokio tasks relaying in-flight responses, so the proxy stops accepting and finishes what it has first. The socket stays open throughout (App::listener_fd is a dup), so requests made during the swap queue in the kernel backlog and are served by the new image — verified end to end: nothing refused, nothing cut. Treat the snapshot as advisory. It is written by the old binary and read by the new one, whose types usually just changed — that is the normal case, not the edge case. The fd numbers stay in plain fields and the feed is decoded per session behind a sanitiser, so a schema change costs the feed and never the port or the pane.
claude-cloak
A TUI that displays Claude Code's streams token-by-token — thinking, text, and tool calls — by sitting as a pass-through proxy between Claude Code and the Anthropic API.
No extra usage: it never issues requests of its own; it observes the SSE stream of requests Claude Code was already making, forwarding bytes verbatim and unbuffered.
claude ──ANTHROPIC_BASE_URL──▶ claude-cloak (127.0.0.1:8484) ──▶ api.anthropic.com
│
▼
TUI: live token feed
Usage
cargo build --release
./target/release/claude-cloak
Then point Claude Code at the proxy, either per-shell:
export ANTHROPIC_BASE_URL=http://127.0.0.1:8484
claude
or globally in ~/.claude/settings.json:
{ "env": { "ANTHROPIC_BASE_URL": "http://127.0.0.1:8484" } }
Note: with the global setting, Claude Code can't reach the API while the proxy isn't running.
Keys
| key | action |
|---|---|
q / Esc |
quit |
Tab / Shift-Tab |
switch session |
j/k, arrows, PgUp/PgDn |
scroll (disables follow) |
f / G / End |
follow live tail |
g / Home |
jump to top |
Display
- ✻ thinking — dim italic, streamed token-by-token
- text — rendered as markdown (
tui-markdown) - ⚙ tool calls — input JSON pretty-printed on completion, raw fragments while streaming
- meta — message boundaries with model, context size, stop reason, token counts
- errors — API/stream errors in red
Sessions are keyed by the Claude Code session ID found in request metadata; concurrent requests (subagents) tap independently.
--headless runs the proxy without the TUI. CT_PORT overrides the port (default 8484).
Hot reload
Swap a running instance onto a newly built binary with ctrl-r — without
losing the proxy port, the embedded claude pane, or the live feed.
cargo build # in any terminal, whenever you like
# then press ctrl-r in each running instance
claude-cloak never builds anything itself and watches no files. You rebuild the way you always would; ctrl-r says "run that one now".
ctrl-r execs the same path the instance was started from, so a debug instance reloads onto a rebuilt debug binary and a release instance onto a rebuilt release one. It does not cross profiles.
How it survives
The app execves the new binary into its own process, so the pid, the open
file descriptors and the child processes all stay. Before the exec the proxy
drains: it stops accepting and lets in-flight responses finish, while the
listening socket stays open so requests made during the swap wait in the kernel
backlog and are served by the new code. Nothing is refused and nothing is
truncated.
The footer shows ⟳ reloading… while it drains, then · reload #1 once the new
code is running. A failed exec — ctrl-r pressed while the linker still had the
file open — reports ⚠ reload failed: … and changes nothing; press it again.
A state snapshot the new types no longer fit costs the feed only — never the port and never the pane.