https://github.com/kylefoxaustin/claude-connect
A simple way to view claude code sessions in a browser window and pipe data between them
https://github.com/kylefoxaustin/claude-connect
claude claude-code dashboard developer-tools python session-management
Last synced: 13 days ago
JSON representation
A simple way to view claude code sessions in a browser window and pipe data between them
- Host: GitHub
- URL: https://github.com/kylefoxaustin/claude-connect
- Owner: kylefoxaustin
- Created: 2026-05-18T23:02:01.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2026-06-05T17:52:51.000Z (19 days ago)
- Last Synced: 2026-06-05T18:39:40.772Z (19 days ago)
- Topics: claude, claude-code, dashboard, developer-tools, python, session-management
- Language: Python
- Size: 398 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Claude Connect
**A local dashboard for watching all your Claude Code sessions at once — in your browser or as a standalone desktop app — plus an optional message bus that lets them talk to each other.**
[](https://github.com/kylefoxaustin/claude-connect/releases)
[](#requirements)
[](#how-it-works)
```
┌─ ● api-server ────────────────┐ ┌─ ● web-app ───────────────────┐
│ ~/code/api 📬3 │ │ ~/code/web │
│ ┌──────────────────────────┐ │ │ ┌──────────────────────────┐ │
│ │ ...running test suite │ │ │ │ ...waiting on user input │ │
│ └──────────────────────────┘ │ │ └──────────────────────────┘ │
│ msgs: 47 ⏱ 2s ago │ │ msgs: 12 ⏱ 1m ago │
└────────────────────────────────┘ └────────────────────────────────┘
```
> 💡 **Heads up on names:** the repo is `claude-connect`; the dashboard binary is `conductor`. Same project — you'll see both names throughout.

The classic 2D board: live tiles, status dots, the orange **backend** group, the central **Bus** tile, and connection wires (solid = active, dashed = passive). *Sample data shown.*
### …or flip to 3D
New in **2.2**: a **🧊 3D** button swaps the board into a WebGL scene where your sessions float around a glowing bus core, grouped sessions cluster together, and you orbit/zoom the whole thing. Three layouts — pick by feel:
| Carousel *(default)* | Orbital | Gallery |
| :---: | :---: | :---: |
|  |  |  |
| Spin through sessions; the front card is focused and fully readable. | Sessions orbit the bus core on a sphere; groups share an arc. | Your saved 2D positions, lifted into depth; groups clump together. |
Cards always face the camera, so text stays readable at any angle. 2D stays the default — 3D is one click away. *Sample data shown.*
---
## Why?
If you're like us, you're running 3, 4, or 8 Claude Code sessions at once across different projects. You forget which terminal is doing what. You miss when one finishes and goes quiet. You wish they could *coordinate* — "hey, the API change is in, you can start on the frontend."
**Claude Connect solves both problems:**
- **See everything at a glance.** One tile per live Claude session — status dot, live preview of what it's saying, time since last activity. Click a tile to jump to that terminal.
- **Let your Claudes talk.** Wire up the optional message bus and your sessions can `/msg-send` each other across projects. The dashboard shows the traffic with animated connection lines.
It's **read-only and local**. It watches Claude's `~/.claude/projects/*.jsonl` logs and process state — it never modifies Claude itself, and it only binds to `127.0.0.1`.
---
## Features
- 🪟 **Live tiles** for every running Claude session — auto-discovered, no config
- 🎯 **Click-to-focus** — clicking a tile raises the actual terminal window
- 📬 **Cross-session messaging** with an animated bus tile showing live traffic
- ✉️ **Compose from the dashboard** — send your own bus message to all sessions or a chosen few, with an optional "ping" that makes them read it now
- 🟢 **Status indicators** — `active` / `warm` / `idle` / `dormant` / `waiting` / `ended`
- 💾 **Persistent layout** — drag tiles to rearrange and **resize** them (corner grip); both stick
- 🗕 **Minimize to dock** — tuck rarely-touched sessions into a bottom dock (still live), restore with a click
- ▦ **Groups** — color-code sessions into named groups; minimize a whole group to one dock chip with a rollup badge
- 🧊 **3D view** *(new in 2.2)* — flip the whole board into a WebGL scene (Carousel / Orbital / Gallery); grouped sessions cluster in space, cards stay readable, 2D remains the default
- 🔀 **Active/Passive bus control** — click a tile's tag chip to toggle whether it's auto-notified of bus traffic
- 🎨 **Themeable** — dark/light, animations, connection-line styling
- 🔒 **Local only** — `127.0.0.1`, in-memory, restart-clean
---
## Requirements
- **Linux with X11** (Wayland is best-effort)
- **Python 3.10+**
- **`wmctrl` and `xdotool`** for terminal focus and `/msg-check` injection
- Both optional — without them, the focus and 📬 buttons just no-op
- **Tilix users** also get exact-tile focus via `gdbus` (ships with GLib, already present on any GTK desktop) — see [Reliable Terminal Focus](#reliable-terminal-focus)
- **Native App Edition only:** system WebKitGTK (`python3-gi`, `gir1.2-webkit2-4.0`, `libwebkit2gtk-4.0-37`) — see [Native App Edition](#native-app-edition-ubuntu). The Web Browser Edition needs none of this.
---
## Quick Start
```bash
# 1. Clone and install
git clone https://github.com/kylefoxaustin/claude-connect
cd claude-connect
pip install -e ".[dev]"
# 2. Install the OS helpers (recommended)
sudo apt install wmctrl xdotool
# 3. Copy the example settings
cp settings.example.toml settings.toml
# 4. Run it
make dev # http://127.0.0.1:8765, auto-reload
```
Open the URL in a browser. Start a Claude Code session in another terminal — a tile appears within seconds. **No per-session registration. No config to get started.**
Other targets:
```bash
make run # production-style, no reload
make test # run the pytest suite
```
---
## Editions
Conductor ships in two editions from the same codebase:
| Edition | What it is | Run |
|---|---|---|
| **Web Browser** | The dashboard served at `http://127.0.0.1:8765`, opened in any browser. | `make dev` / `make run` |
| **Native App** | The *same* dashboard wrapped in a native desktop window (pywebview → WebKitGTK), with its own app-menu launcher and dock icon. | `make native` |
Both editions ship in a **single release per version** (`vX.Y.Z`) — it's one
codebase at one commit, the edition is just how you run it. New features land
once and both editions get them.
### Native App Edition (Ubuntu)
The native edition runs the identical FastAPI + JS app inside a WebKitGTK window.
The uvicorn server runs in a background thread; **closing the window stops the
server** (nothing is left running). If a Conductor is already serving the port
(e.g. a `make dev` instance, or a second launch), the app *attaches* to it
instead of starting a duplicate.
```bash
# 1. System WebKitGTK (not pip-installable — provides PyGObject + the WebKit2 typelib)
sudo apt install python3-gi gir1.2-webkit2-4.0 libwebkit2gtk-4.0-37
# 2. Build the native venv (--system-site-packages, so it can see system gi/WebKit) + pywebview
make install-native
# 3. Launch the native window
make native
# 4. (Optional) Add a launcher to your app menu / dock, with this checkout's paths baked in
make install-desktop
```
`make install-native` creates a **separate** `.venv-native` so the Web edition's
`.venv` stays untouched. `make install-desktop` writes
`~/.local/share/applications/conductor.desktop` (icon: `assets/conductor.svg`,
`StartupWMClass=conductor` for dock grouping) — re-run it if you move the repo.
> Tested against WebKit2 **4.0** (Ubuntu 22.04). The SVG connection-line overlays
> and tile drag render correctly under WebKitGTK.
---
## Using the Dashboard
### Tiles
Each tile is one live Claude session. It shows:
- **Status dot** — `active` (writing now), `warm` (recent), `idle`, `dormant`, `waiting` (on user input), `ended`
- **Squashed live preview** of the latest message
- **Message count + time since last activity**
- **📬 bubble** — appears when this session has unread bus messages (only if the [bus](#cross-session-bus) is wired up)
- **Tag chip** (e.g. `[backend]`) — its bus identity, and a click-toggle for [Active/Passive](#active--passive) membership
**Drag** a tile to move it; **drag the bottom-right corner** to resize it; the **`–`** button minimizes it to the bottom dock. Hover a truncated title to see it in full. Position, size, and minimized state persist across restarts.
### Minimize / dock
Click a tile's **`–`** to tuck it into a thin **dock** along the bottom — a tiny chip with its status dot, name, and 📬 badge. It's still monitored (the dot keeps updating; a new message still lights the badge), just out of the way. Hover for the full name; **click the chip to restore** it to its previous position and size. Minimized tiles' bus wires are hidden to keep the board clean.
### Groups
For a crowded board, organize sessions into **named, color-coded groups**. Each tile has a **▦** button that opens a small menu:
- **▦ → New group from this** (prompts for a name) — or **Add to ▸ {group}** to join an existing one. A tile belongs to one group; members get a colored top accent.
- On a grouped tile: **Rename group**, **Move to ▸ {other}**, **Remove from group**, or **Minimize group** — which folds the *whole* group into a single dock chip (swatch + name + member count + active dot + total 📬). Click the chip to expand it back.
- The **▦ Groups** panel (top bar) lists every group to rename, recolor, minimize/restore, or ungroup.
Groups are *logical* (members keep their own positions — they aren't auto-arranged) and persist across restarts.
### 🧊 3D view
Click **🧊 3D** in the top bar to lift the board into a 3D scene; click **🗔 2D** to come back. **2D is the default** and stays untouched — 3D is an optional view, and your choice (plus the last layout) is remembered.
- **Navigate** — *drag* to orbit the camera, *scroll* to zoom. Click a card's **▶** to focus its terminal, or its tag chip to toggle Active/Passive, just like 2D.
- **Cards always face you.** No matter how you orbit, every card is billboarded toward the camera so the text stays readable — depth and motion live in the *space between* sessions, never in the angle of the content.
- **Groups become places.** Cards carry their group color (border + glow), and group members physically **cluster together** in the scene. The glowing **bus core** sits at the center with wires fanning out (solid = active, dashed = passive), pulsing on each message.
- **Three layouts** (switcher floats at the bottom):
- **Carousel** *(default)* — sessions on a ring you spin through; the front card is enlarged and in focus.
- **Orbital** — sessions orbit the bus core on a sphere; each group shares an arc.
- **Gallery** — your saved 2D positions lifted into depth (active sessions float forward, idle recede), with groups pulled into tight clumps.
> The 3D view loads Three.js from a CDN the first time you open it (no build step). Offline or CDN blocked? It cleanly falls back to 2D with a notice — the 2D board never depends on it. Assign/rename/recolor groups from the 2D **▦** menu; 3D visualizes whatever's set.
### Click a tile
Raises that session's terminal window. See [Reliable terminal focus](#reliable-terminal-focus) for the trick that makes this work cleanly when you have many sessions in one terminal app.
### Click the 📬 bubble
Runs `/msg-check` *inside that live Claude* — it raises the window and types the command for you. Behavior is configurable so you don't clobber a Claude mid-task (see below).
### Bus tile
A central tile shows recent cross-session traffic. SVG lines fan out to every session that's on the bus and animate on each message. The line style tells you *how* a session is on the bus (see the legend, bottom-left):
- **Active** (solid) — wired to the bus hooks, so it's **auto-notified** of new messages and receives broadcasts.
- **Passive** (dashed, dim) — has used the bus manually (`/msg-send` / `/msg-check`) but isn't auto-notified, so it **won't see broadcasts** unless you Ping it. (These are sessions outside `bus.sh`'s hook whitelist — a deliberate scoping so the bus doesn't pester unrelated sessions.)
### Active / Passive
Click a tile's **tag chip** to toggle that session between **Active** (auto-notified) and **Passive** (manual only) right from the dashboard. Conductor writes the membership to `~/.claude/bus-state/active-tags`, and `bus.sh` reads it — so the change takes effect on that session's **next prompt** (promote a throwaway session into the team, or stop pestering one). The list seeds from your existing active set on first toggle, so nothing changes until you click.
> Requires the data-file-aware `bus.sh` (the shipped [`bus/bus.sh`](bus/bus.sh) reads `active-tags`, falling back to its built-in `BUS_WHITELIST` when the file is absent). If you wired up the bus before this, re-copy `bus/bus.sh`.
### ✉ Compose
Click **Compose** in the top bar to send your own message on the bus — like email for your sessions. You're a first-class sender (tagged `[operator]` by default; set `bus.sender_tag` in `settings.toml` to your name).
- **All sessions** (default) — a broadcast everyone sees.
- **Specific sessions** — uncheck "All" and pick recipients. The message is soft-addressed with a leading `@to [tag] …` line, so receivers (and the dashboard's connection-line animation) know who it's for. It's still on the shared log; addressing is advisory, not private.
- **Ping recipients** — also injects `/msg-check` into the chosen sessions so they read it immediately (specific recipients only — broadcast-ping would steal focus across every window). `Ctrl/⌘+Enter` sends.
> Sending makes Claude Connect a *writer* on the bus (it still never touches Claude's own state). Requires the markdown bus adapter.
### ⚙ Settings
Theme, connection lines (incl. **Lines behind tiles** to drop the wires behind the tiles for a cleaner board — each wire still stays anchored to its own tile via a plug + stub drawn on top), animations, rescan cadence, and the **bus-bubble click policy**:
| Policy | Behavior |
| ------------------------------------------- | --------------------------------------------------------------------------------------------- |
| **Confirm if busy, inject if idle** *(default)* | Inject silently when idle; confirm first if the session looks busy (writing in the last 30s). |
| **Always inject** | Type `/msg-check` immediately, regardless of state. |
| **Block while busy** | Badge is dimmed/non-clickable while busy; injects only when idle. |
| **Always confirm** | Ask before every injection. |
> ⚠️ "Busy" is inferred from `jsonl` activity + CPU. A Claude blocked on a long, quiet tool call can still look idle, so this guard *shrinks* the risk of clobbering rather than eliminating it. Use *Always confirm* if you want a prompt every time.
---
## Cross-Session Bus
> *Optional, but the killer feature.*
The 📬 features ride on a **shared markdown message log**. Claude sessions append to it with `/msg-send` and read it with `/msg-check`; hooks make each session aware of new messages. Claude Connect tails the same log to drive the bus tile.
### Setup
```bash
install -Dm755 bus/bus.sh ~/.claude/bin/bus.sh
cp bus/commands/*.md ~/.claude/commands/
# Then merge bus/settings.hooks.example.json into ~/.claude/settings.json
```
Point Claude Connect at the log in the `[bus]` section of `settings.toml`. Full setup docs and format spec:
- 📖 [`bus/README.md`](bus/README.md) — install + slash commands
- 📖 [`docs/claude-bus.md`](docs/claude-bus.md) — message format spec
### What if a session isn't on the bus?
Claude Connect works fully without it. Session discovery comes from OS process state, not from the bus, so:
- ✅ An un-wired Claude still appears as a normal tile — status, preview, message count, click-to-focus all work
- ❌ It just won't show a 📬 badge or a connection line to the Bus tile
This is actually useful — if you wire up some sessions and leave others out, the dashboard shows at a glance which ones are on the tunnel and which are silent.
---
## Reliable Terminal Focus
A single terminal server (Tilix, `gnome-terminal-server`) owns all its windows, so they share one PID — and a tiled window shows only the *active* tile's title at a time. So matching purely on **window title** is ambiguous: a backgrounded tile has no title of its own on the window, and a stray same-named terminal (e.g. a shell `cd`'d into the project dir) can win the match.
**Tilix gets an exact path.** Each tilix tile stamps its shell with a `TILIX_ID` env var, so Claude Connect reads that UUID from the Claude process and tells tilix (over its `com.gexperts.Tilix` D-Bus interface) to focus that precise tile — **raising the window *and* switching to the exact tile**, even inside a combined/tiled window where several Claudes share one window. No wrapper, no setup; it just works if you run Claude inside tilix.
> Scope: this exact-tile path is **tilix-only** and only tested on tilix. Other terminals fall through to the title-matching path below — no regression, just less precise.
For non-tilix terminals, focus falls back to **window-title matching**, and the optional `claude-tracked` wrapper in [`scripts/`](scripts/) gives each Claude its own window with a unique X11 title so focus and 📬 injection target precisely:
```bash
sudo install -m755 scripts/claude-tracked /usr/local/bin/
claude-tracked api-server --resume
```
Without either the tilix path or the wrapper, focus is best-effort: Claude Connect raises the terminal window owning the Claude PID, but can't switch between tabs packed into one window.
---
## Configuration
Edit `settings.toml` (copied from `settings.example.toml`). Key knobs:
| Setting | What it does | Default |
| ----------------------------- | ------------------------------------------------------------- | ------- |
| `scanner.interval_seconds` | Full rescan cadence | `3` |
| `bus.adapter` | `markdown` (the reference bus), `jsonl` (generic), or `fake` | — |
| `bus.markdown_path` | Path to the bus log | — |
| `bus.state_dir` | Where unread state lives | — |
| `bus.script_path` | Path to `bus.sh` | — |
| `ui.end_fadeout_seconds` | How long ended-session tiles linger after exit | `30` |
---
## What's stored, and where
Conductor keeps **no central database** — state lives in two clearly separated places:
**Your browser (localStorage, per-origin `127.0.0.1:8765`)** — all the visual/layout customization. Written to disk by the browser, so it survives closing the tab, restarting the browser, restarting the server, and rebooting:
| Key | Holds |
| --- | --- |
| `conductor.prefs.v1` | theme, connection-line visibility, lines-behind, flow animation, bus-bubble click policy |
| `conductor.positions.v2` | tile positions **and sizes** |
| `conductor.minimized.v2` | which tiles are minimized to the dock |
| `conductor.groups.v2` | your groups (names, colors, members, collapsed state) |
Layout/minimize/group state is keyed by **project directory**, so a session re-attaches to its saved spot, size, and group whenever it runs in the same directory — across reboots and even fresh (non-resumed) sessions. Conductor doesn't prune offline tiles' layout, so it waits for them to return. Clear it with **Reset layout**, **Ungroup**, or your browser's site-data tools.
**On your machine (the bus), not the browser:** the cross-session bus log (`~/Documents/claude-bus/messages.md`), unread state (`~/.claude/bus-state/`), and the active/passive whitelist (`~/.claude/bus-state/active-tags`). The Conductor server itself is **in-memory and restart-clean** — it holds no persistent state of its own.
---
## How It Works
For the curious:
1. **SessionScanner** enumerates Claude Code processes via `psutil`, resolves `/proc//cwd`, and walks `~/.claude/projects//` to find each session's `*.jsonl`.
2. **ActivityWatcher** uses `watchdog` (inotify on Linux) to react to jsonl writes without polling.
3. A **WebSocket hub** at `/ws` fans updates out to the browser: a full session + bus snapshot every `scanner.interval_seconds` (3 s by default), plus an immediate per-session push whenever a jsonl write fires (inotify), and a bus event as each message arrives.
4. The **frontend** renders one tile per session — plain JS, no build step.
5. **BusAdapter** tails the message-bus log; the Bus tile shows recent traffic and SVG lines fan out to sessions on the bus.
6. **WindowMapper** raises the right terminal window on click and types `/msg-check` into a session when you click its 📬 — focusing the exact tilix tile via `gdbus` + `TILIX_ID` when available, else falling back to `wmctrl`/`xdotool` title matching.
Full design doc: [`CONDUCTOR_SPEC.md`](CONDUCTOR_SPEC.md)
---
## Design Notes
- **Single-host only.** Binds to `127.0.0.1`.
- **No persistence.** State is in-memory and restart-clean.
- **No build step on the frontend.** Edit `frontend/*.js` and reload.
---
## Troubleshooting
**Tiles aren't appearing.**
Check that `claude` is actually running and that `~/.claude/projects/` has recent jsonl files. Drop `scanner.interval_seconds` to `1` temporarily to confirm discovery is happening.
**Clicking a tile doesn't focus the terminal.**
On **Tilix**, focus is exact out of the box (via `gdbus` + `TILIX_ID`) — make sure `gdbus` is on PATH (it ships with GLib). On other terminals, make sure `wmctrl` is installed; for tabbed/tiled non-tilix terminals, install the `claude-tracked` wrapper so each session gets a unique window title. See [Reliable Terminal Focus](#reliable-terminal-focus).
**📬 bubbles never appear.**
The bus isn't wired up. See [Cross-Session Bus](#cross-session-bus). Sessions work fine without it — they just won't have bubbles or connection lines.
**`/msg-check` doesn't inject when I click 📬.**
Check that `xdotool` is installed and the session's terminal window is on the current desktop/workspace.
---
## Maintainer
Built and maintained by **Kyle Fox** ([@kylefoxaustin](https://github.com/kylefoxaustin)).
Got an idea, found a bug, or want to share how you're using it? Open an [issue](https://github.com/kylefoxaustin/claude-connect/issues) or ping me on GitHub.
## Contributing
Issues and PRs welcome. See [`CLAUDE.md`](CLAUDE.md) for the agent-friendly contributor guide.