Skip to content

tomplex/periscope

Repository files navigation

periscope

A live dashboard over your tmux sessions. Every window becomes a card; click into a card to open a full live terminal (xterm.js + tmux pipe-pane over WebSocket), send keystrokes, focus the window in tmux, or rename it. Parses Claude Code pane status — branch, PR, CI state, recap, spinner — and surfaces what's pending input.

Built for the "I have 30 tmux windows across 5 sessions with various Claude Code agents running, and I want a single pane of glass" workflow.

Requirements

  • tmux (any reasonably modern version)
  • uv for running the single-file script
  • A modern browser (uses WebSockets + vanilla JS)
  • Optional: ANTHROPIC_API_KEY for the ✨ auto-rename feature
  • Optional: Node 20+ if you want HMR while iterating on the frontend

Run

uv run server.py

Open http://127.0.0.1:8765/. Polls every 3s; the modal opens a live WebSocket bridge to the selected pane.

For the always-on / launchd-managed setup and the prod/dev port split, see CLAUDE.md → Development workflow and bin/periscope.

Frontend HMR (optional)

For hot-reload while editing static/app.js or static/styles.css:

npm install        # one-time
npm run dev        # then visit http://127.0.0.1:5174/

npm run dev runs the FastAPI server and Vite together via concurrently; ctrl+c stops both. Vite proxies /api/* and /ws/* to FastAPI, so only one URL matters in the browser. There's no build step — production still loads static/ as-is from FastAPI on :8765.

Native app (macOS)

Periscope can run as a native .app — its own Dock icon and Cmd-Tab entry instead of a browser tab. The app is a thin Tauri shell that loads the dashboard from http://127.0.0.1:8765, so the server still has to be running (uv run server.py, or the launchd service via bin/periscope install).

Download the latest .dmg from Releases, or build it from source:

cd src-tauri
cargo tauri build        # needs the Rust toolchain + `cargo tauri`
open target/release/bundle/macos/Periscope.app

Release .dmgs are unsigned, so Gatekeeper blocks them on first launch. After dragging Periscope.app into /Applications:

xattr -dr com.apple.quarantine /Applications/Periscope.app

Auto-rename (optional)

The ✨ button on each session header asks Haiku 4.5 to suggest fresh, descriptive names for every window in the session based on current pane content. Requires an Anthropic API key:

cp .env.example .env
# then edit .env and paste your key

Channels (Claude push/reply)

Periscope can push messages into the Claude Code sessions it spawns and surface Claude's replies in its UI. This uses Claude Code's channels feature, currently in research preview.

One-time setup

Claude Code keeps user-level MCP servers under the mcpServers key in ~/.claude.json. Merge the periscope entry in (don't overwrite the file — it holds lots of other Claude Code state):

jq '.mcpServers.periscope = {
  "command": "uv",
  "args": ["run", "--script", "/ABSOLUTE/PATH/TO/periscope/channel_shim.py"]
}' ~/.claude.json > ~/.claude.json.tmp && mv ~/.claude.json.tmp ~/.claude.json

Replace /ABSOLUTE/PATH/TO/periscope/ with your local checkout path. After this, restart any running claude sessions you want channels in (it's read at invocation time) or spawn fresh ones via periscope's + claude button.

How it works

When you click + claude in periscope, the spawned command is claude --dangerously-load-development-channels server:periscope. Claude launches channel_shim.py as a stdio child. The shim proxies MCP messages over a unix socket (/tmp/periscope-mcp.sock) to periscope's in-process MCP server, and reconnects transparently across periscope restarts.

The --dangerously-load-development-channels flag is required because channels are in research preview — bare --channels only resolves allowlisted entries.

Using channels

  • Push to Claude: open a pane's modal, type a message in the composer in the Messages section, and submit. Claude sees it on its next turn as a <channel source="periscope"> block.
  • Replies from Claude: Claude can call the reply tool with kind="need_human", kind="done", or kind="info" (the default). Messages show in the modal's Messages section; need_human triggers a pulsing red border on the pane card and fades the rest of the grid.

If a session was started outside periscope (no dev-channels flag), the push composer is disabled with a tooltip.

License

MIT — see LICENSE.

About

Live browser dashboard over tmux sessions — every window becomes a card, modal opens a live xterm.js terminal, parses Claude Code pane status (branch / PR / CI / spinner / recap).

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors