A beautiful web UI for the Claude Code & Codex CLIs
Quickstart
bun install -g kanna-code
If Bun isn't installed, install it first:
curl -fsSL https://bun.sh/install | bashThen run from any project directory:
That's it. Kanna opens in your browser at localhost:3210.
Features
- Multi-provider support — switch between Claude and Codex (OpenAI) from the chat input, with per-provider model selection, reasoning effort controls, and Codex fast mode
- Project-first sidebar — chats grouped under projects, with live status indicators (idle, running, waiting, failed)
- Drag-and-drop project ordering — reorder project groups in the sidebar with persistent ordering
- Local project discovery — auto-discovers projects from both Claude and Codex local history
- Rich transcript rendering — hydrated tool calls, collapsible tool groups, plan mode dialogs, and interactive prompts with full result display
- Quick responses — lightweight structured queries (e.g. title generation) via Haiku with automatic Codex fallback
- Plan mode — review and approve agent plans before execution
- Persistent local history — refresh-safe routes backed by JSONL event logs and compacted snapshots
- Auto-generated titles — chat titles generated in the background via Claude Haiku
- Session resumption — resume agent sessions with full context preservation
- WebSocket-driven — real-time subscription model with reactive state broadcasting
Architecture
Browser (React + Zustand)
↕ WebSocket
Bun Server (HTTP + WS)
├── WSRouter ─── subscription & command routing
├── AgentCoordinator ─── multi-provider turn management
├── ProviderCatalog ─── provider/model/effort normalization
├── QuickResponseAdapter ─── structured queries with provider fallback
├── EventStore ─── JSONL persistence + snapshot compaction
└── ReadModels ─── derived views (sidebar, chat, projects)
↕ stdio
Claude Agent SDK / Codex App Server (local processes)
↕
Local File System (~/.kanna/data/, project dirs)
Key patterns: Event sourcing for all state mutations. CQRS with separate write (event log) and read (derived snapshots) paths. Reactive broadcasting — subscribers get pushed fresh snapshots on every state change. Multi-provider agent coordination with tool gating for user-approval flows. Provider-agnostic transcript hydration for unified rendering.
Requirements
- Bun v1.3.5+
- A working Claude Code environment
- (Optional) Codex CLI for Codex provider support
Embedded terminal support uses Bun's native PTY APIs and currently works on macOS/Linux.
Install
Install Kanna globally:
bun install -g kanna-code
If Bun isn't installed, install it first:
curl -fsSL https://bun.sh/install | bashOr clone and build from source:
git clone https://github.com/jakemor/kanna.git
cd kanna
bun install
bun run buildUsage
kanna # start with defaults (localhost only) kanna --port 4000 # custom port kanna --no-open # don't open browser kanna --share # create a public share URL + terminal QR
Default URL: http://localhost:3210
Network access (Tailscale / LAN)
By default Kanna binds to 127.0.0.1 (localhost only). Use --host to bind a specific interface, or --remote as a shorthand for 0.0.0.0:
kanna --remote # bind all interfaces — browser opens localhost:3210 kanna --host dev-box # bind to a specific hostname — browser opens http://dev-box:3210 kanna --host 192.168.1.x # bind to a specific LAN IP kanna --host 100.64.x.x # bind to a specific Tailscale IP
When --host <hostname> is given, the browser opens http://<hostname>:3210 automatically. Other machines on your network can connect to the same URL:
Public share link
Use --share to create a temporary public trycloudflare.com URL and print a terminal QR code:
kanna --share kanna --share --port 4000
--share is incompatible with --host and --remote. It does not open a browser automatically; instead it prints:
QR Code:
...
Public URL:
https://<random>.trycloudflare.com
Local URL:
http://localhost:3210
Development
The same --remote and --host flags can be used with bun run dev for remote development.
--share is also supported in dev mode and exposes the Vite client URL publicly:
bun run dev --share bun run dev --port 3333 --share
In dev, --port sets the Vite client port and the backend runs on port + 1, so bun run dev --port 3333 --share publishes http://localhost:3333.
--share remains incompatible with --host and --remote.
Use bun run dev --port 4000 to run the Vite client on 4000 and the backend on 4001.
Or run client and server separately:
bun run dev:client # http://localhost:5174 bun run dev:server # http://localhost:5175
Scripts
| Command | Description |
|---|---|
bun run build |
Build for production |
bun run check |
Typecheck + build |
bun run dev |
Run client + server together |
bun run dev:client |
Vite dev server only |
bun run dev:server |
Bun backend only |
bun run start |
Start production server |
Project Structure
src/
├── client/ React UI layer
│ ├── app/ App router, pages, central state hook, socket client
│ ├── components/ Messages, chat chrome, dialogs, buttons, inputs
│ ├── hooks/ Theme, standalone mode detection
│ ├── stores/ Zustand stores (chat input, preferences, project order)
│ └── lib/ Formatters, path utils, transcript parsing
├── server/ Bun backend
│ ├── cli.ts CLI entry point & browser launcher
│ ├── server.ts HTTP/WS server setup & static serving
│ ├── agent.ts AgentCoordinator (multi-provider turn management)
│ ├── codex-app-server.ts Codex App Server JSON-RPC client
│ ├── provider-catalog.ts Provider/model/effort normalization
│ ├── quick-response.ts Structured queries with provider fallback
│ ├── ws-router.ts WebSocket message routing & subscriptions
│ ├── event-store.ts JSONL persistence, replay & compaction
│ ├── discovery.ts Auto-discover projects from Claude and Codex local state
│ ├── read-models.ts Derive view models from event state
│ └── events.ts Event type definitions
└── shared/ Shared between client & server
├── types.ts Core data types, provider catalog, transcript entries
├── tools.ts Tool call normalization and hydration
├── protocol.ts WebSocket message protocol
├── ports.ts Port configuration
└── branding.ts App name, data directory paths
Data Storage
All state is stored locally at ~/.kanna/data/:
| File | Purpose |
|---|---|
projects.jsonl |
Project open/remove events |
chats.jsonl |
Chat create/rename/delete events |
messages.jsonl |
Transcript message entries |
turns.jsonl |
Agent turn start/finish/cancel events |
snapshot.json |
Compacted state snapshot for fast startup |
Event logs are append-only JSONL. On startup, Kanna replays the log tail after the last snapshot, then compacts if the logs exceed 2 MB.