# claude-thinking TUI that displays Claude Code's API streams token-by-token (thinking, text, tool calls) by acting as a pass-through proxy: Claude Code points `ANTHROPIC_BASE_URL` at `127.0.0.1:8484`, we forward everything verbatim to `api.anthropic.com` and tee SSE responses into the UI. **Never issue API requests of our own** — zero extra usage is the core constraint of this project. ## Architecture ``` src/main.rs entry; tokio runtime for proxy task, TUI on main thread; --headless mode src/proxy.rs axum fallback handler: buffers request body (for session metadata), forwards via reqwest, streams response back unbuffered, tees SSE src/sse.rs incremental SSE parser; tolerant of chunk splits mid-event/mid-UTF-8 src/app.rs Arc> shared state; Tap = one in-flight tapped request, translates SSE events → session Entries (Drop closes it out) src/ui.rs ratatui rendering @ ~30fps; session list + scrollable feed ``` Data flow: proxy task parses SSE chunks → `Tap::handle()` mutates shared state → UI thread redraws on its own tick (no channel; just the mutex). ## Key invariants - **Latency-neutral pass-through**: response bytes are forwarded as-is, never buffered or rewritten. Auth headers pass through untouched. If the tap code panics or misparses, the proxy must still relay bytes (tee is best-effort). - **`accept-encoding` is stripped** from forwarded requests so the upstream sends identity encoding we can parse in transit. Don't "fix" that. - Hop-by-hop headers (`content-length`, `transfer-encoding`, etc.) are stripped both directions; hyper re-frames. - Sessions are keyed by the UUID after `session_` in request `metadata.user_id`. Concurrent requests (subagents) share a session but each `Tap` tracks its own current entry index — entries/sessions are append-only, so indices stay stable. - Tool input streams as raw JSON fragments; pretty-printed only on `content_block_stop`. Text re-renders markdown every frame, so partial markdown self-heals. ## Gotchas - `tui-markdown` is pinned `=0.3.5`: later versions use `ratatui-core` (0.30 alpha types), incompatible with ratatui 0.29. - ratatui needs feature `unstable-rendered-line-info` for `Paragraph::line_count` (used for follow/auto-scroll). - reqwest is `default-features = false` + `rustls-tls,stream` — don't enable compression features (would re-add accept-encoding). - Bind failures (port in use) only surface in the TUI status bar; check for a stale `claude-thinking` process holding 8484. - Testing: SSE parser has unit tests (`cargo test`). For a live pass-through check: `--headless`, then POST to `127.0.0.1:8484/v1/messages` without auth — a relayed 401 from Anthropic proves the round-trip. The TUI can't run in a non-tty. ## Not yet handled (known MVP limits) - Non-streaming requests pass through untapped (e.g. `count_tokens`). - Sessions are never pruned; long sessions re-render fully each frame. - Tool results only appear once the *next* request fires; if the session ends right after a tool call, that result is never seen. Output is what Claude Code sends the model (i.e. post-truncation).