Skip to content

justinstimatze/lucida

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

485 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

lucida

CI

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

Themes

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:

The four Expanse faction themes: mars-blue, mars-red, earth, drift

themes.mp4

mixed3d — the canyon flythrough

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.


Get running in 5 minutes

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 .env

Why 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 --generate

That'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.


What it produces

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.


Cost

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.

Where the money goes

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 offline

On 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.

Why the classifier stays on Sonnet

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.


Watcher options

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 N

Cells 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,B

HUD + URL params

The 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), Q dumps a contact sheet of all rendered tier-1 cells into refs/gibson/live-shots/.

Related projects

  • 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.

How it works (for the curious)

The pipeline behind each cell:

  1. watcher.py polls the transcript for new prose
  2. segmenter.py chops it into discrete snippets
  3. classifier.py assigns a substrate type; low-value snippets are suppressed
  4. specialists.py produces a snippet-grounded visual spec — a forcing-step audit checks that the specialist didn't invent data not in the source
  5. Cell lands in cells.json; the renderer polls and paints it live
  6. Every N mints, reflect.py synthesizes 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.


Adapters

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.txt

Files

lucida/
├── 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


Development

uv venv && uv pip install -e .[dev]
pre-commit install

Lint:

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.


Known limitations

  • Single-user, local-host by default. serve.py binds 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_lock and state_lock use fcntl.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_FLOOR higher 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=pack for a pure-2D mode.
  • No Anthropic-side context window cap. Long sessions accumulate context in recent_cells for the orchestrator. We don't truncate. A multi-day session could hit 200k context errors.
  • Cell substrate prompts are tuned for code/dev transcripts. Other transcript domains (writing, research) work but may classify differently than expected. Override via the --type CLI flag.

About

Real-time visualization dashboard for Claude Code — passively mints Vega charts, Mermaid diagrams, 3D scenes, and animated SVGs from your conversation as you work

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors