Jonas H 15b20a8af1 Put every app key behind one prefix
The keybindings had no red thread because there were three focus states
and every key had to ask "is this mine or the child's" — hence ctrl-q,
because plain q was forwarded. Replace the whole model: unprefixed keys
belong to the embedded claude, always, and everything cloak owns sits
behind ctrl-space (CT_PREFIX to change), tmux-style, in one table that
the which-key popup, the footer hint and the dispatch all read.

Esc is the only key with a rule of its own, and it is about reachability
rather than modes: it closes the topmost overlay, and with nothing open
it goes to the child, so interrupt and Esc-Esc rewind keep working. View
state — filters, the lane the feed shows — is a setting, not a mode, and
is deliberately not escapable.

That removes the focus model entirely, which lets the two feeds collapse
into one: the feed renders whichever lane feed_lane names, at full
width, and the stream picker chooses it. Subagents used to live in a
modal popup holding a second draw_feed with its own cache and scroll
model, rendering the same thing twice. The sessions panel loses its half
of the screen the same way. Both pickers become bottom strips with the
feed readable above them, so walking the list previews each row — which
is the view-without-resuming that /resume cannot do, and frees enter to
attach the pane.

Adds a find bar with in-place highlighting, scrolling to the matching
line rather than the containing entry, and drops the two keys that
served the old layout.
2026-09-10 14:16:34 +02:00
2026-09-10 14:16:34 +02:00
2026-08-17 07:47:35 +02:00
2026-09-10 14:16:34 +02:00
2026-09-10 14:16:34 +02:00

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

Everything you type goes to the embedded claude. There is no focus model and no key you have to think about — q, j and Esc all reach Claude Code, because the app's own keys live behind a prefix, tmux-style.

Press ctrl-space and a which-key popup shows what is available. The keys work immediately; you never have to wait for the popup.

^space + action
s sessions — j/k previews each one in the feed, enter opens it in claude, Esc keeps reading it. Turn tree with space, branch with b
a streams — j/k previews the main chain, each subagent, each hosted tool call. Sized to the list, capped at a third
n new session (model picker)
c continue the most recent session
f filter which entry kinds show
/ find bar — matches highlight in place, enter/ next, prev, with a 3/12 counter
] [ next / previous user prompt
. back to the live main chain, tailing it — undoes a picked session, a picked lane, and a parked scroll
z Z zoom the pane / zoom the feed
r hot reload
q quit

Repeatable keys (] [) keep the popup open, so ^space ]]] walks. ^space ^space sends a literal prefix to the child.

Two keys are the app's without a prefix, for the same reason a terminal keeps them for itself: the wheel and shift+PgUp/PgDn scroll the feed (or the pane's own scrollback while the pane is zoomed). They keep working while a list is open, so you can read what you highlighted before committing to it.

Esc closes the topmost overlay. With nothing open it goes to Claude Code, so interrupt and Esc-Esc rewind keep working. Filters and the stream you picked are settings, not modes — Esc never resets them.

CT_PREFIX changes the prefix (CT_PREFIX=ctrl-b, ctrl-], f1, …), for the case where your Claude Code wants ctrl-space for itself. CT_DEBUG_KEYS=1 shows what your terminal actually delivers. ctrl-q quits from anywhere, as a way out if the prefix never arrives.

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 ^space r — without losing the proxy port, the embedded claude pane, or the live feed.

cargo build            # in any terminal, whenever you like
                       # then press ^space r in each running instance

claude-cloak never builds anything itself and watches no files. You rebuild the way you always would; the key says "run that one now".

It 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 — 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.

Description
No description provided
Readme 2.3 MiB
Languages
Rust 97.3%
Python 2.7%