Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1,035 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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. Identifies Claude Code and Codex panes alongside ordinary shells, combines their agent state with branch/PR/CI metadata, and surfaces work that is running or ready for attention.

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

Requirements

  • tmux (any reasonably modern version)
  • uv for running the single-file script
  • Node 20+ + npm — the frontend is a Vite-built bundle (static/dist/)
  • A modern browser (uses WebSockets)
  • Codex CLI 0.146.0 for the initial, macOS-tested Codex integration
  • Optional: ANTHROPIC_API_KEY for the ✨ auto-rename feature

Run

npm install && npm run build   # one-time / whenever static/src/ changes
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.

Agent lifecycle hooks

bin/periscope install-hook merges Periscope's Claude and Codex lifecycle hooks into their existing user configuration; it does not replace unrelated hooks. Provider-specific commands are also available:

bin/periscope install-claude-hook
bin/periscope install-codex-hook
bin/periscope uninstall-claude-hook
bin/periscope uninstall-codex-hook

The experimental Codex definition is written atomically to $CODEX_HOME/hooks.json (default ~/.codex/hooks.json). After installing, open /hooks in Codex and review/trust the exact Periscope command. Periscope does not bypass hook trust. /api/healthz reports definitions and observations, but cannot infer trust. Codex hook attribution is currently marked verification: "unresolved" until live root/subagent hook evidence is captured; recorded bindings are therefore not authoritative state evidence.

If /api/healthz reports the Codex hook as installed but unobserved, first confirm /hooks trusts that exact command, then start a fresh Codex session and submit a prompt. Also check that the server and Codex use the same $CODEX_HOME; an explicit empty value has not yet been verified and should be avoided. Do not use --dangerously-bypass-hook-trust.

Codex launch and state

Choose Codex in either launcher to open it in the selected worktree. The new-tab route also accepts a Codex resume UUID; Periscope validates it against the local rollout catalog, rejects a UUID already bound to a live Codex pane, pre-binds the new pane, and runs codex resume <uuid> -C <cwd>.

Codex v1 focuses on working, idle, and the existing derived done attention state. Missing or contradictory evidence is unknown and must not produce a completion alert. Codex panes default to the live terminal and do not offer transcript mode, Claude channels, Claude plan usage, memory warnings, narrator status, context reset, or AI auto-rename.

Lifecycle hooks are intended to provide pane/session identity once the manual evidence gate is satisfied. Until then, verified rollout records and the macOS process/TUI detector are defensive, conservative fallbacks. Interruption, cancellation, approval round trips, Stop continuation, Linux process layouts, and root-vs-subagent hook behavior remain manual verification items; ambiguous cases stay unknown.

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

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages