For the monitor where you used to read code.
Your Claude Code session, rendered live as a mission-control display. Every decision, comparison, flow, and structure becomes a live visual cell — charts, diagrams, 3D scenes, animated SVGs. The display builds up as you work.
It probably doesn't make you more productive. Das Blinkenlights for AI sessions.
demo.mp4
The dashboard ships a wardrobe of themes — switch via ?theme= in the URL
or the THEME chip in the HUD.
| Theme | Feel |
|---|---|
lab |
Default dark, cyan accent |
mars-blue |
The Expanse, Rocinante — cobalt tactical radar, bezel gauges |
mars-red |
The Expanse, classic MCRN — red war-table, Donnager data stack |
earth |
The Expanse, UN Navy — royal-blue situations plot, institutional |
drift |
The Expanse, OPA/Belter — amber orbital plot, salvage eclectic |
vigil |
MCU/Jarvis — cold electric cyan, arc reactor gold |
ops |
Star Trek LCARS — full L-frame chrome |
circuit |
Tron Legacy — hard grid, neon data strip |
noir |
Blade Runner 2049 — amber holograms, blue-black |
terminus |
Alien/Nostromo — phosphor green, CRT vignette |
renegade |
Mass Effect N7 — omnitool orange, diagonal geometry |
mainframe |
ReBoot (1994) — Energy Sea teal |
conclave |
Eva/NERV — amber scan lines, monospace |
minimal |
Vercel/Linear — clean flat light |
gastown |
Steampunk brass + serif |
hackers |
Hackers (1995) Gibson canyon — cyan-dominant, rare magenta |
hailmary |
Project Hail Mary — cyan-white wireframe, monochrome |
Each theme ships per-theme entrance animations and window-edge chrome authentic to its source material. Themes also declare a preferred layout that activates on switch.
The four faction themes from The Expanse — each with its own live tactical furniture, not just a palette swap:
themes.mp4
The hackers theme pairs with a mixed3d layout that arranges your
cells onto the faces of a city of glass towers. A swoopy camera tours
the canyon, climbing between corridors and pausing on tier-1 cells as
it passes. The visual lineage is Hackers (1995) — Ellingson Mineral
Company's Gibson — with a bit of Tron Legacy and Ghost in the
Shell in the mix.
lucida-demo-v4.mp4
Try it:
http://localhost:8766/?theme=hackers&layout=mixed3d
Cells render in three LOD tiers as the camera approaches: ambient
decorative bed (tier 2, shared per-substrate textures) → text/title
snap (tier 1 mid) → full graph render (tier 1 close). Mermaid,
animated-SVG, treemap, gauge, force-graph, timeline-ribbon, and
trajectory substrates all render in-place on tower faces. Click any
cell to park the camera in front of it; press R to resume the tour.
Requirements: Python 3.11+, Node.js 18+, an Anthropic API key, a running Claude Code session.
git clone https://github.com/justinstimatze/lucida && cd lucida
uv venv && uv pip install -e .
npm install # pulls mermaid + jsdom + puppeteer (bundles its own Chromium, ~170MB)
cp .env.example .env
# fill in your ANTHROPIC_API_KEY in .envWhy npm install: lucida lints mermaid specs at mint time and
pre-renders mermaid diagrams server-side so the browser never blocks
on mermaid.render(). Both rely on Node deps. Puppeteer's
postinstall hook downloads a bundled Chromium — no system Chrome
required, and the download only happens once per machine.
Start the renderer — open this on your second monitor and leave it there:
python3 serve.py
# http://localhost:8766/serve.py bundles the static server and the snap receiver (which
persists Mermaid SVG renders into cells/ so heavy substrates don't
re-render every session). python3 -m http.server 8766 works too but
skips the cache.
For the Tron/Hackers (1995) Gibson canyon look on first run, try:
http://localhost:8766/?theme=hackers&layout=mixed3d
Start watching your Claude Code session:
python watcher.py \
--transcript ~/.claude/projects/.../transcript.jsonl \
--watch 30 --write --generateThat's it. New cells appear as the conversation progresses.
The display starts blank — cells mint as new content appears in the transcript. Start a conversation in Claude Code and within a few exchanges you'll see the first cells land. You'll immediately feel cooler.
Prefer one command? ./scripts/start.sh launches serve.py and watcher.py
side-by-side with labeled output. Edit the script to point at your own
transcript path.
Lucida reads each passage in your conversation and picks a reasonable visual for it automatically:
- Graphs and diagrams — architecture, flows, entity relationships, state machines
- Charts — comparisons, cost breakdowns, quantitative series
- Tables — structured decisions, callouts, tradeoff matrices
- Treemaps — proportional categorical breakdowns
- 3D wireframes — topology, spatial structure (Three.js, FUI-style)
- Animated SVGs — cycles, decay, state transitions
- Sparklines — single-variable trajectories
- Timeline ribbons — chronological events with horizontal flow
- Gauges — single scalars within a stated range (memory, latency, score)
Ambient FUI flair — transient cells, mint-time scrubbers, per-theme ambient motion — appears automatically. No prompts required, no payload, no static chrome. Implies "computer go beep boop."
Visuals arrive pre-themed to the active theme. No configuration needed — the classifier chooses the substrate, the specialist generates the spec, and the renderer paints it.
About $0.02–0.03 per cell using Sonnet 4.6. A busy hour-long session mints 30–80 cells — roughly $0.60–$2.00. Classifier calls are cached.
Turn off --generate to run the classifier only (free) and mint manually
when you want a visual.
Measured with tools/spend_audit.py, which reconstructs per-stage spend
from the cache counters recorded in your own cells.json — run it on your
data rather than trusting anyone's averages:
python tools/spend_audit.py # calibrated (a few free count_tokens calls)
python tools/spend_audit.py --no-calibrate # fully offlineOn a long real session the classifier was ~44% of recorded spend (it runs once per segment with an ~11.5K-token cached prefix), with the rest spread across the specialists — none above ~11%. Within the classifier, cost splits roughly half cached-prefix reads, half its own output tokens.
The obvious cheap move — flipping the classifier to Haiku 4.5 for the ~3x
rate cut — was tried and rejected on quality: on a 72-snippet
stratified replay (both models, same prompt), Haiku agreed with Sonnet on
cell_type only 36% of the time and collapsed half the sample to
low-confidence text, which the confidence gate then suppresses. The
saving would have arrived as a half-empty dashboard. The replay harness is
tools/classifier_agreement_check.py — re-run it before trying another
classifier model (every stage's model is overridable via LUCIDA_*_MODEL
env vars; see .env.example).
What does cut cost without touching judgment: the classifier prompt asks for telegraphic one-sentence reasoning (output tokens are the expensive half), and every stage sets a prompt-cache breakpoint, so keeping the watcher polling inside the 5-minute cache TTL keeps prefix reads at ~10% of list price.
python watcher.py \
--transcript <path> # Claude Code .jsonl transcript
--watch 30 # poll interval in seconds (omit for one-pass)
--write # persist to cells.json
--generate # call specialists (costs API tokens)
--session-id <name> # tag cells with a session name
--max-cells all # default — keep every cell; pass N to cap at last NCells accumulate by default. cells.json grows with each minted cell.
Pass --max-cells N (or LUCIDA_MAX_CELLS=N) if you want a rolling cap —
e.g. --max-cells 500 keeps only the last 500.
Multiple sessions:
# Terminal 1
python watcher.py --transcript session-a.jsonl --session-id A --watch 30 --write --generate
# Terminal 2
python watcher.py --transcript session-b.jsonl --session-id B --watch 30 --write --generate
# View both side-by-side
# http://localhost:8766/?session=A,BThe HUD at the top of the page is a live status bar. Click it to expand.
The SESSION chip opens a dropdown listing every session in the corpus.
Full URL param reference:
?theme=<name> theme (lab / vigil / ops / circuit / noir / terminus /
renegade / mainframe / conclave / minimal / gastown /
hackers / hailmary)
?layout=<name> layout (pack / grid / treemap / scatter / tactical /
terminal / mixed3d)
?session=<id> scope to one session
?session=<a>,<b>,<c> N-column mission-control view
?nocache=1 bypass the persistent SVG cache (force fresh mermaid
renders this load)
?perf=1 dev: enable per-frame perf logging
?debug=1 dev: enable mixed3d debug logging
In ?layout=mixed3d, dev keys: D toggles the debug overlay (camera path
- tower bounds),
Qdumps a contact sheet of all rendered tier-1 cells intorefs/gibson/live-shots/.
- agentic-city by Mark Ferree — kindred local-only FUI dashboard for AI sessions, but framed from the opposite angle: it renders the codebase as an isometric SimCity with active Claude/Codex/Gemini agents flying overhead as UFOs. Where lucida centers the transcript content as visual cells, agentic-city centers the codebase as terrain. The companion library agentwatch is a Go transcript- watcher that normalizes Claude/Codex/Gemini session state into one feed — worth a look if you want multi-vendor session ingest.
The pipeline behind each cell:
watcher.pypolls the transcript for new prosesegmenter.pychops it into discrete snippetsclassifier.pyassigns a substrate type; low-value snippets are suppressedspecialists.pyproduces a snippet-grounded visual spec — a forcing-step audit checks that the specialist didn't invent data not in the source- Cell lands in
cells.json; the renderer polls and paints it live - Every N mints,
reflect.pysynthesizes the stream into a summary cell
The UserPromptSubmit hook injects recent mints into your next Claude Code
prompt, so the conversation knows what just landed on the display.
To wire it up, add this to your ~/.claude/settings.json:
{
"hooks": {
"UserPromptSubmit": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "/path/to/lucida/hooks/recent_mints.sh"
}
]
}
]
}
}The hook is silent when nothing has minted recently — it only speaks when
there are new cells. Set LUCIDA_MINT_WINDOW_MIN=30 (default: 60) to
tune the lookback window.
Flatten any AI session log into the format the watcher expects:
python -m adapters.cli --source claude-code <transcript.jsonl> --out /tmp/transcript.txt
python -m adapters.cli --source aider <chat.md> --out /tmp/transcript.txtlucida/
├── index.html renderer
├── notebook.css all theme chrome
├── themes/ per-theme token JSON
├── serve.py static server + snap receiver
├── orchestrator.py one-shot entry point
├── watcher.py continuous listener
├── specialists.py visual spec generators
├── classifier.py substrate classifier
├── reflect.py synthesis cells
├── adapters/ transcript adapters
├── scripts/start.sh launches serve + watcher together
└── hooks/recent_mints.sh Claude Code prompt injection hook
Not committed: cells.json, mint_log.jsonl
uv venv && uv pip install -e .[dev]
pre-commit installLint:
uv run ruff check .
uv run ruff format .Tests:
uv run pytest tests/
# integration tests (needs ANTHROPIC_API_KEY):
uv run pytest tests/integration/CI runs lint + tests + a bandit security scan on every push.
- Single-user, local-host by default.
serve.pybinds 127.0.0.1. No auth layer — if you expose the port externally, anything that can reach it can read your cells and mint log. - State files are POSIX-only.
cells_lockandstate_lockusefcntl.flock. Windows users will run without locking — fine for single-process use, race-prone for parallel watchers. - Cells are LLM output rendered with DOMPurify sanitization. HTML
and SVG cells are sanitized before insertion (strips
<script>, event handlers, dangerous URLs). A bug in DOMPurify or a future sanitizer-bypass would still be exposure for a session run with an attacker-controlled transcript. - API cost is on the user. Every transcript turn that lands a mint
triggers a classifier + specialist call (plus an occasional
reflection). Default models are tuned for cost, but a runaway
transcript ingestor will burn through API credit. Set
LUCIDA_RETRIGGER_SCORE_FLOORhigher to make the mint gate stricter. - Mixed3d is GPU-heavy. ~250-500MB GPU memory at saturation
(~1500 cells in scene, 300-entry snap cache). Integrated GPUs may
drop frames; use
?layout=packfor a pure-2D mode. - No Anthropic-side context window cap. Long sessions accumulate
context in
recent_cellsfor the orchestrator. We don't truncate. A multi-day session could hit200k contexterrors. - Cell substrate prompts are tuned for code/dev transcripts. Other
transcript domains (writing, research) work but may classify
differently than expected. Override via the
--typeCLI flag.
