bodek is a pure front-end: a Bubble Tea TUI that streams from an
odek serve engine over WebSocket. It never re-implements agent behaviour
(tools, approvals, sandbox, skills, memory) — all of that lives in odek.
Keep it that way.
| Path | Responsibility |
|---|---|
cmd/bodek |
CLI entry point: flags, lifecycle, version / upgrade subcommands |
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 |
internal/update |
Self-upgrade: fetch and swap in the latest GitHub release binary |
make build # compile → bin/bodek
make test # go test -race ./... (always use the race detector)
make vet # go vet ./...
make lint # golangci-lint (config: .golangci.yml)
make fmt # go fmt ./...
make cover # coverage report for internal packagesRun all of these and make them pass before committing:
make fmtmake vetmake lint— must report0 issuesmake test— the full race-enabled suite, all packages green
Never commit with failing tests or lint findings. If a change breaks an existing test, fix the code or deliberately update the test — never delete or weaken a test just to get green.
Use Conventional Commits semantics:
<type>(<scope>): <short imperative summary>
<optional body: what and why, not how>
- Types:
feat(user-visible behaviour),fix(bug),refactor,perf,test,docs,chore,ci,build. - Scope: usually the package, e.g.
tui,client,server,tokens. - Summary: lowercase, imperative, no trailing period, ≤ 72 chars.
- Breaking changes:
!after the scope and aBREAKING CHANGE:footer. - One logical change per commit; don't mix refactors with features.
Examples from this repo's history:
fix(tui): interleave reasoning blocks with tool calls chronologically
feat(tui): compact tool steps with Ctrl+E details toggle
- Internal-package coverage is ~99% — keep it there. Any new behaviour needs a test; any bug fix needs a regression test.
- TUI tests drive the model directly: construct a
Model(seenewTestModelininternal/tui), feedclient.Events throughhandleEvent/Update, and assert onm.msgs, rendered output (renderMessage,plain()), or key handling (key("ctrl+e")). - Don't assert on exact timings or spinner frames — keep tests non-flaky.
- The client and server packages are tested against an in-process
odek servestand-in; reuse those fixtures.
- Standard Go style,
go fmtclean, golangci-lint clean (.golangci.yml). - Match the surrounding file's comment density and naming; exported API is rare here — most identifiers stay unexported.
- Minimal diffs: change only what the task requires. No drive-by refactors, renames, or reformatting.
- Security: anything rendered from the wire (tokens, tool output, file
contents) must go through
sanitize()— seeinternal/tui/model.go. Never render raw remote content. Tool results arrive as JSON envelopes wrapped in untrusted-content markers: decode the envelope and fold the wrappers away before display — don't render either verbatim. - The transcript model in
internal/tui: each assistantmessagekeeps a chronologicalitems []turnItemtimeline (reasoning blocks and step references interleaved). Preserve arrival order; don't regress to a single per-turn reasoning blob, and don't reintroduce a separate in-transcript thinking placeholder alongside it. - Events arrive from
internal/clientalready in chronological order — keep ingestion order-dependent and idempotent. internal/tuiis split by responsibility:model.goholds the core model,events.goevent handling,input.gokey/text input,approval.gothe approval flow,reconnect.gosocket recovery. Put new code in the matching file instead of growingmodel.go.- The TUI reconnects with backoff and resumes the session after a socket
drop (
reconnect.go) — don't break that by assuming a single connection per run. - Streaming renders are coalesced into one flush per 80ms for performance; batching new redraw paths the same way keeps the TUI responsive.
- The slash-completion popup holds key capture while open; typed keys must keep flowing to the input. Route keys through the popup first, then fall through to normal input handling.
- Never modify the odek project from a bodek session. bodek is a pure
front-end; if a fix requires changes in odek (protocol,
odek serve, engine behaviour), do not edit that repo — instead summarize the required change and suggest it to the user as a task for an odek session. - Don't run
git commit/git pushunless the user explicitly asks. - Don't add dependencies without checking
go.modfirst and flagging it to the user. - Keep
README.md(key bindings, commands, feature list) and this file in sync when you change user-visible behaviour. - CI (
.github/workflows) runs build, vet, lint, and race tests on every push — a red pipeline means the commit checklist above was skipped.