turbo-debug-console 0.4.0

Turbo Vision monitor that renders a model-token stream over a socket
Documentation

turbo-debug-console

A Turbo Vision debug console for live token streams.

Point it at a port, stream bytes at it, and watch them render the way a terminal agent would render them — markdown, fenced code with syntax highlighting, thinking text dimmed, tool-call banners — inside a DOS-era text-mode UI with menus, windows and a status line.

It exists because a model's token stream is hard to watch while it is happening. Piping it to a file loses the formatting; piping it to a terminal loses it the moment anything else prints. This gives the stream its own window, keeps a scrollback, and survives the producer disconnecting and coming back.

screenshot

Install

brew install aovestdipaperino/tap/turbo-debug-console

Or from crates.io:

cargo install turbo-debug-console

Use

Run it:

turbo-debug-console

It listens on port 7878. Then stream something at it:

printf 'HELLO 1 tokens build\n' | nc 127.0.0.1 7878     # -> PORT 54312
your-program | nc 127.0.0.1 54312

Or skip the handshake entirely — anything that is not a handshake is treated as a raw token stream and gets its own window:

cat capture.txt | nc 127.0.0.1 7878

Sessions

The handshake exists so a stream can be named, reconnected to, and told apart by kind.

client -> control 7878 :  HELLO <version> <kind> <name>\n
server ->              :  PORT <n>\n              (or  ERR <reason>\n)
client -> data <n>     :  raw bytes, until the socket closes

<kind> is tokens (a model token stream, the default for the no-handshake fallback above) or trace (see Tracing streams below).

A session outlives its data socket. When the producer drops, its window stays, titled [disconnected], with the transcript intact — and the same port keeps listening. Sending HELLO again with the same name returns the same port, and the stream rejoins its original window below a -- reconnected -- rule.

That is the point: restart the program you are debugging as many times as you like, and its output keeps accumulating in one place.

Sessions that stay disconnected are reaped after 30 minutes.

Protocol version

The version is the first field of the handshake so that a client and a console of different vintages fail loudly instead of misparsing each other. The current version is 1. A console that does not speak the version a client asks for replies ERR unsupported protocol version <v> and closes, rather than guessing.

Tracing streams

Point a Rust program's tracing output straight at a window with the trace kind:

printf 'HELLO 1 trace myapp\n' | nc 127.0.0.1 7878     # -> PORT 54312

then, in myapp, write its tracing-subscriber JSON records to that port instead of stdout:

let socket = std::net::TcpStream::connect("127.0.0.1:54312")?;
tracing_subscriber::fmt().json().with_writer(socket).init();

Each JSON record becomes one line, colored by level:

12:04:01  WARN  myapp::db   retry  attempt=3

the time-of-day in gray, the level in its own color (ERROR red, WARN yellow, INFO white, DEBUG light gray, TRACE dark gray), the target in cyan, the message in the default color, and any other structured fields dimmed as key=value pairs after it. A line that isn't valid JSON — a stray panic message, a println! that snuck onto the same socket — is shown verbatim rather than dropped.

Keys

F10 menu
F6 next window
Alt-X quit
PgUp / PgDn / Home / End scroll the focused window
mouse wheel scroll the window under the pointer
scrollbar (right edge) arrows step a row, the track pages, drag the thumb
drag (left mouse) select text in the focused window
Esc clear the selection

Long lines wrap to the window's width rather than being cut off, so there is nothing to scroll horizontally to; scrolling is vertical only, by display row. Scrolling back releases autoscroll; End, or scrolling back down to the last row, re-arms it, so a window you are reading does not yank itself to the bottom when new output arrives. Each window has a vertical scrollbar along its right edge whose thumb shows where in the scrollback you are.

Drag with the left mouse button to select text; the selection follows the mouse and survives scrolling. A selection spanning wrapped lines copies back as the original logical lines — a soft wrap does not become a newline. A plain click, Esc, or new output on that window clears it.

Edit > Block mode switches dragging from a stream selection to a rectangular one: the column band between the two corners, taken from every row it spans, so a column of timestamps or numbers copies out without the text around it. Rows too short to reach the band contribute an empty line, which keeps the block's shape when it is pasted elsewhere. The mode is global, ticked in the menu and marked ▭ Block at the right end of the status line, and each selection keeps the shape it started with — toggling mid-drag does not reshape what is already highlighted. Select All is always a stream selection: it means the whole scrollback, never a column band.

File opens a saved capture into a new window, or writes the focused window's transcript out as plain text. Edit copies the current selection to the clipboard, selects the whole window, toggles block mode, and clears a window; thinking text and markdown rendering are always on, not toggles. Window cycles, tiles and cascades, and Cleanup closes every window whose client has gone away — the ones whose titles read [disconnected] — in one go. That is worth having because a new window covers the whole desktop, so older windows sit hidden behind the newest one; Cleanup sweeps the dead ones without hunting through the stack. Closing a window also tears its session down, releasing the port. Auto-cleanup is the same sweep as a standing rule: with it ticked, a window closes the moment its client goes away, rather than sitting there marked [disconnected]. It is off by default, because a dead window's scrollback is usually the thing you wanted to read.

Menu items grey out when they have nothing to act on: Copy until something is selected, Save As / Select All / Clear window until a window is focused, Cleanup until a window has actually lost its client, and Next / Tile / Cascade until there is more than one window. There is no Window > Close item — a window closes from the close box on its own frame.

How it renders

The rendering is not a reimplementation. It runs trace-stream — the same streaming renderer the plank agent uses for its own terminal output — over a Vec<u8>, and converts the ANSI it emits into Turbo Vision cells. Same state machine, so the two cannot drift.

Text styles come through, not just colour. Since Turbo Vision 2.2 a cell carries a style bitset alongside its colours, so ANSI bold, italic and underline survive: keyword highlighting renders bold, comments italic, and dimmed thinking text keeps its slant. Bold on an explicitly-chosen colour keeps that colour's hue rather than brightening it.

Build from source

git clone https://github.com/aovestdipaperino/turbo-debug-console
cd turbo-debug-console
cargo build --release

Built on turbo-vision, a Rust implementation of Borland's text-mode UI framework.

License

MIT — see LICENSE.