A beautiful Bubble Tea terminal interface for the odek agent.
ββββββ ββββββ ββββββ βββββββ ββ ββ
ββ ββ ββ ββ ββ ββ ββ ββ ββ
ββββββ ββ ββ ββ ββ βββββ βββββ
ββ ββ ββ ββ ββ ββ ββ ββ ββ
ββββββ ββββββ ββββββ βββββββ ββ ββ
bodek is a pure front-end. It launches (or attaches to) an odek serve
instance and renders the agent's live stream β reasoning, tokens, tool calls,
approvals, skills, and memory β as a polished TUI. Every bit of agent
behaviour (tools, danger gating, sandbox, skills, memory, sessions) comes from
odek itself; bodek never re-implements any of it.
odek already ships a streaming WebSocket protocol (the one its Web UI speaks). bodek reuses that exact protocol from the terminal, which means:
- Zero duplicated logic β tools, the
dangerapproval engine, the Docker sandbox, skills, and memory all run inside odek, unchanged. - Full fidelity β token streaming, per-tool activity, and security prompts appear in the TUI exactly as the engine emits them.
- One source of truth β upgrade odek and bodek gets the new behaviour for free.
- Minimal core footprint β odek stays deliberately dependency-light, while the rich terminal UI lives here with its Bubble Tea, glamour, and other front-end dependencies.
ββββββββββββββββ WebSocket (RFC 6455, JSON) ββββββββββββββββββββ
β bodek β ββββββββββββββββββββββββββββββΊ β odek serve β
β (Bubble Tea) β tokens Β· tools Β· approvals β (ReAct engine, β
β TUI client β β tools, sandbox) β
ββββββββββββββββ ββββββββββββββββββββ
Prerequisite: bodek is only the front-end β you also need the odek
engine. See odek's install instructions
(any OpenAI-compatible provider key via ODEK_API_KEY).
Download the latest compiled binary from the
releases page β archives
are published for Linux, macOS, and Windows (amd64 & arm64), with
checksums.txt for verification.
One-liner for Linux / macOS (resolves the latest asset for your platform and
installs into ~/.local/bin):
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
ARCH=$(uname -m); [ "$ARCH" = "x86_64" ] && ARCH=amd64
URL=$(curl -fsSL https://api.github.com/repos/BackendStack21/bodek/releases/latest \
| grep browser_download_url | grep "${OS}_${ARCH}" | cut -d '"' -f 4)
curl -fsSL "$URL" | tar -xz bodek && install -m 755 bodek ~/.local/bin/On Windows, download the windows_amd64 (or arm64) .zip from the
releases page and put bodek.exe on your PATH.
# Install odek (the engine) and bodek (the TUI)
go install github.com/BackendStack21/odek/cmd/odek@latest
go install github.com/BackendStack21/bodek/cmd/bodek@latest
# Provide an LLM key (any OpenAI-compatible provider)
export ODEK_API_KEY=sk-...
bodekbodek looks for odek on your PATH. To point at a specific binary use
--odek-bin, or skip spawning entirely with --url.
bodek # launch odek serve and start chatting
bodek --sandbox # run tool calls inside odek's Docker sandbox
bodek --url 'http://127.0.0.1:8080/?token=β¦' # attach with the token URL odek serve printed
bodek --url http://127.0.0.1:8080 --token d3adb33f # attach with an explicit token
bodek --odek-bin ./odek # use a specific odek binary
bodek --mouse # enable mouse wheel scrolling (blocks text selection)
bodek -- --prompt-caching # pass extra flags through to `odek serve`
bodek version # print the bodek version
bodek upgrade # download and install the latest releaseodek serve protects its WebSocket and REST APIs with a per-instance token it
prints to stderr at startup (odek serve β‘ http://β¦/?token=β¦). When bodek
spawns the server it picks the token up automatically; when you attach to one
that's already running, paste the token URL into --url or pass the token
itself via --token. Older odek versions without token enforcement are
detected and connected to as before.
Configuration (model, base URL, API key, MCP servers, memory, skills) is read
by odek serve from its usual chain β ~/.odek/config.json β ./odek.json β
ODEK_* env vars β so bodek inherits whatever you've already set up.
| Key | Action |
|---|---|
β |
Send the prompt (queues it while a turn is running) |
^K |
The palette β everything: commands, sessions, models, drawer tabs |
/ |
Open the command palette (see below) |
@ |
Attach a file (see below) |
alt+β / alt+β |
Jump to the previous / next turn |
^F |
Fold/unfold the most recent turn card (click any turn head with --mouse) |
tab |
Open/close the latest reasoning block (live turns auto-expand) |
^R |
Browse & resume saved sessions |
^O |
Switch the model |
^T |
Toggle extended thinking for the next turn |
^J |
Insert a newline in the input |
^L |
Clear the conversation |
Esc |
Cancel the running turn (queued prompts return to the input) |
β / β / PgUp / PgDn / ^U / ^D |
Scroll the transcript (arrows at the input's edge lines) |
^P / ^N |
Recall previous prompts (prompt history) |
^G / End (empty input) |
Jump to the latest output |
F1 |
Show the help card |
wheel (with --mouse) |
Scroll the transcript Β· click tool rows, turn heads, and the cockpit |
β (disconnected, empty input) |
Retry the connection |
^C |
Quit |
Prompts sent while a turn is running are queued and sent automatically
when the turn ends β the footer shows how many are waiting. While the
transcript is scrolled up mid-run, the footer flags β new output; press
^G to jump to the latest. If the connection drops, bodek retries with
backoff and, after giving up, keeps your draft and offers a manual retry
on β with an empty input.
Every printable character always types. No bare letter, digit, or
punctuation key is ever bound in the composer β actions live on chords and
non-character keys (^K palette, alt+ββ turn jumps, F1 help), so a
prompt can start with ?, [, or any other character.
Type / at the start of the input for a command palette. β/β to choose,
β₯ to complete, β to run, esc to dismiss. You can also just type the full
command and press β.
| Command | Action |
|---|---|
/help |
Show available commands and key bindings |
/clear |
Clear the conversation |
/stats |
Session metrics card (cost, cache, context gauge) |
/server |
Cockpit β server, link, budget & session in one card (or click the header) |
/sessions |
Browse, search, pin, rename, export & resume sessions |
/runs |
Headless REST runs β live status, remote approvals, cancel |
/run <prompt> |
Start a headless run (fresh session) and watch it in the runs tab |
/events |
The odek.event/v1 runtime feed |
/memory |
Facts by target, pending-episode promote, consolidate |
/skills |
Skill provenance badges & promote |
/tools |
Tool registry with enabled state & MCP servers |
/config |
Sanitized config, lifetime usage, connections (kick) |
/model [name] |
Switch model (opens a picker with no argument) |
/thinking [on|off] |
Toggle extended thinking for the next turn |
/cancel |
Cancel the running turn |
/attach <path> |
Stage a file to send with the next prompt (5 MB each, 10 MB total) |
/unattach [name] |
Drop staged files (all when no name given) |
/quit |
Exit bodek |
/sessions, /runs, /events, /memory, /skills, /tools, and
/config all open tabs of one drawer with a shared grammar:
]/[cycle tabs Β·1β7jump Β·rrefresh Β·esccloses.- Every management row opens a detail view on
ββ the full text behind the gate: a skill's description and provenance, a fact or pending episode's body, an MCP server's command/args/limits, raw JSON for nested config values.β/βscroll it,esc/qfolds back (selection kept), andppromotes straight from the detail β no more promoting blind. - Sessions β
/search (server-side),ppin,rrename,e/Eexport md/json,ddelete (yconfirms β deletes are always two-step),βresume. - Runs β live 3s poll,
A/D/Tremote approvals,ccancel,prefresh pending approvals,edrill into the run's event trail. - Events β the
odek.event/v1ring:ffilter to this session,xclear filters (a runs-tab drill-in scopes it to one run). - Memory β
a/Aadd user/env facts,ddelete fact (yconfirms),ppromote a pending episode,c/Econsolidate. - Skills β provenance badges plus a dim description line;
ppromote (also from the detail view),Pforce-promote tainted. - Tools/Config β registry + MCP servers; config values flatten one
level (
sandbox.enabled), nested values show raw JSON in the detail; lifetime usage,dkick a connection,Styped shutdown death-gate.
Type @ to attach a file. bodek searches the working tree and shows a
completion popup; β/β to choose, β or β₯ to insert, esc to dismiss.
> summarize @internal/client/client.go and explain the protocol
odek resolves and inlines the file content server-side (wrapped in its
untrusted-content boundary), so attachments go through the same security model
as any other external input β bodek doesn't special-case them. (Saved sessions
are resumed via /sessions or ^R, not @.)
When the agent requests approval for a dangerous operation, pick an outcome from the panel and confirm β typing never answers by accident:
| Key | Action |
|---|---|
β / β (or β / β) |
Move the highlight (Approve / Deny / Trust class when offered) |
β |
Confirm the highlighted option |
Esc |
Deny (abort) |
Tab |
Expand/collapse the full command & description text |
PgUp / PgDn / ^U / ^D |
Scroll the transcript while the panel is open |
After three same-class approvals inside a minute the server engages friction
mode: the panel shows the recent-approval count, the trust shortcut is
withdrawn, and approving requires typing the literal word approve and
pressing β (a mistyped word resets β retyping is the point). Denying stays
one Esc.
- EMBER Terminal β the WebUI's design language (electric amber on
blue-charcoal) as terminal tokens;
BODEK_THEME=ember-light|high-contrast|classicandNO_MOTION=1for a fully static UI. - The palette (
^K) β every surface one fuzzy search away, every row teaching its chord. - Turn cards β telemetry rides the turn head,
^Ffolds noisy turns,[/]jump turn-to-turn, reasoning accordions auto-expand live and collapse on the next turn. - Typed tool renderers β diffs tint with diffstats, file reads get line numbers, JSON indents, test runs show pass/fail verdicts.
- Streaming answers rendered as Markdown (glamour).
- Tool activity β every
tool_call/tool_resultshown live with a glyph per tool, a spinner, an argument preview, and a result excerpt rendered as a tree (βΏ) β multi-line, blank-stripped, capped with a+N more linesfooter, and tinted with aβwhen the call fails. - Sub-agents β delegations are labelled and their
subagent_logactivity nests beneath the delegating call, so a sub-agent's progress reads as its own branch of the step tree. - Security approvals β odek's
dangerengine prompts surface as an inline panel; your answer is sent straight back over the socket. - Live reasoning β the model's pre-tool thinking streams in dimmed text, with a running elapsed timer and cycling status while it works. Long turns keep every thinkβreply pair intact: each reasoning block is followed by its own answer card, in arrival order.
- Command palette (
/) and file attachments (@) β live, navigable popups. - Context-aware progress β while the agent works, a status line just
above the input (right below your last message) shows what it's actually
doing (
π§ͺ running tests,π reading client.go,π pushing) with a live elapsed timer. - Session browser (
^R) β resume, replay, delete, pin (p), rename (r), export a transcript (emarkdown,EJSON), and search server-side (/);nloads the next page. Resuming sends asession_switchso the server-side memory buffer is restored before you type. - Auto-reconnect β if the socket drops, bodek redials with backoff
(500ms β 8s, 5 attempts) and re-adopts the session over the fresh socket;
only a server that stays down leaves the
disconnectedbadge. - Model switcher (
^O) β change the model for the next turn. The picker merges the server's configured model with its built-in profile catalog (/api/profiles), each annotated with its context window. - Skill suggestions β when odek's learn loop proposes a skill, a passive
card above the composer answers on
alt+s(save) /alt+x(skip); it never blocks sending, and auto-save governs real persistence. - Server shutdown β the config tab's
Srequires typing the literal wordshutdown(the approval-friction pattern); the socket drop that follows is expected state, withβstarting a fresh instance in spawn mode. - Live server snapshot β a 25s heartbeat measures WebSocket round-trip
latency and refreshes server uptime, connection count, and streaming state
(the β‘ badge beside the model);
/statssurfaces the full link row. - Cancellation (
Esc) β abort a running turn via odek's cancel API. - Sandbox aware β the header shows
π‘ sandboxedorβ host access; pass--sandboxto run tool calls inside odek's Docker isolation. - Telemetry β a pressure-tinted context-window gauge in the header
(
ctx βββββ 38% 380/1k, eighth-block fill, greenβamberβred), per-turn token/latency footers, and the full session roll-up in/stats. - Cost tracking β when odek has token prices configured (limits), the
header shows the running session spend, each turn footer its estimated
cost, and
/statsrolls up the session (with themax_cost_usdcap when set); hidden entirely otherwise. - Fluent by default β gradient wordmark and hairline, smooth braille spinner, smart autoscroll that never yanks you while you read history, and a scroll-position indicator.
- Engine notices β skill loads, memory merges, and agent signals appear as quiet status lines.
- Version display β the header shows bodek's own version next to the logo and the spawned odek's version next to the model name.
- Update hint β at startup, a quiet note appears when a newer bodek release
is available (
bodek upgradeinstalls it; dev builds are never nagged).
make build # β bin/bodek
make run # build and launch
make test # go test -race ./...
make cover # coverage report for internal packages
make lint # golangci-lint (if installed)
make vet
make tidyContinuous integration runs build, go vet, golangci-lint, and the race-
enabled test suite on every push (see .github/workflows).
Tagged releases (vX.Y.Z) are built and published automatically by
GoReleaser.
Project layout:
| Path | Responsibility |
|---|---|
cmd/bodek |
CLI entry point: flags, lifecycle, wiring |
internal/server |
Launch / attach to odek serve, resolve the auth token |
internal/client |
odek serve WebSocket protocol (transport + REST + decoding) |
internal/tokens |
Local persistence of per-session auth tokens |
internal/tui |
The Bubble Tea model, update loop, panels, and view |
bodek is a pure client, so it is highly testable: the WebSocket protocol, REST
endpoints, token store, and the full Bubble Tea update/view loop are exercised
by unit and integration tests against an in-process odek serve stand-in.
Internal-package statement coverage is ~99% (client 100%, tui 99%, tokens
98%, server 95% β the remainder is unreachable OS-error handling).
MIT β see LICENSE.