No description
Find a file
Paweł J. Wal 5c67db47a2
Opus: Split main.py into deck.py (rendering/state), cli.py (commands), main.py (wiring)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 15:46:59 +02:00
.gitignore Opus: Stream Deck Mini controller with Ghostty tab switching 2026-04-02 12:57:13 +02:00
.python-version Opus: Stream Deck Mini controller with Ghostty tab switching 2026-04-02 12:57:13 +02:00
cli.py Opus: Split main.py into deck.py (rendering/state), cli.py (commands), main.py (wiring) 2026-04-06 15:46:59 +02:00
config.py Opus: Clean up comments, fix error swallowing, extract constants, validate config 2026-04-06 15:44:17 +02:00
deck.py Opus: Split main.py into deck.py (rendering/state), cli.py (commands), main.py (wiring) 2026-04-06 15:46:59 +02:00
demo.jpg Cleanup, demo 2026-04-06 15:23:01 +02:00
ghostty.py Opus: Clean up comments, fix error swallowing, extract constants, validate config 2026-04-06 15:44:17 +02:00
hooks.py Opus: Clean up comments, fix error swallowing, extract constants, validate config 2026-04-06 15:44:17 +02:00
LICENSE Cleanup, demo 2026-04-06 15:23:01 +02:00
main.py Opus: Split main.py into deck.py (rendering/state), cli.py (commands), main.py (wiring) 2026-04-06 15:46:59 +02:00
poller.py Opus: Clean up comments, fix error swallowing, extract constants, validate config 2026-04-06 15:44:17 +02:00
pyproject.toml Opus: macOS menu bar app and .app bundle for Raycast 2026-04-02 16:02:57 +02:00
README.md Cleanup, demo 2026-04-06 15:23:01 +02:00
ruff.toml Opus: Config-driven tabs, init commands, and splash screen 2026-04-02 13:42:57 +02:00
splash.png Opus: Config-driven tabs, init commands, and splash screen 2026-04-02 13:42:57 +02:00
uv.lock Opus: macOS menu bar app and .app bundle for Raycast 2026-04-02 16:02:57 +02:00

SpookyCat

Custom Elgato Stream Deck Mini controller for managing multiple Claude Code workspaces in Ghostty.

Each button maps to a Ghostty tab with a dedicated workspace. Buttons show the workspace icon, git branch info, and Claude's current state via color:

  • Black -- no Claude running
  • Green -- Claude idle/ready
  • Blue -- Claude working
  • Red/Yellow -- Claude needs attention (permission prompt, question)

Requirements

  • macOS
  • Elgato Stream Deck Mini (6 keys)
  • Ghostty terminal
  • uv for Python project management
  • hidapi (brew install hidapi)
  • Accessibility permissions for the terminal/app running SpookyCat

Setup

# Install dependencies
brew install hidapi
uv sync

# Generate config (edit with your workspaces)
uv run python main.py
# First run creates ~/.config/lol.pjw.spookycat/spookycat.toml and exits

# Install Claude Code hooks (for state monitoring)
uv run python main.py install-hooks

# Install CLI helper (adds `spookycat` to ~/.local/bin)
uv run python main.py install-cli

# Install macOS app (for Raycast/Spotlight)
uv run python main.py install-app

Usage

# Run from terminal
spookycat

# Run with debug logging
spookycat --log-level=debug

# Dynamically swap a workspace at runtime
spookycat set-workspace 5 ANO ~/telemetry-anomalies

Launch from Raycast by searching "SpookyCat". A menu bar icon (👻🐱) appears -- click it to quit.

Button behavior

  • Tap a button to switch to that Ghostty tab
  • Tap the active button while the SpookyCat window is focused to switch back to your previous window (Cmd+`)
  • Unconfigured keys stay dark

Config

Located at ~/.config/lol.pjw.spookycat/spookycat.toml:

[settings]
poll_interval = 5

[colors]
inactive = "#000000"
working = "#2980b9"
done = "#27ae60"
asking = "#c0392b"

[[tabs]]
key = 0
icon = "α"
workspace = "~/projects/alpha"
init = ["echo 'ready'"]

Tab fields

Field Description
key Stream Deck button (0-5), must be unique
icon Displayed on button -- unicode char, emoji, or short string
workspace Directory to cd into on tab creation
init Commands to run after cd (optional)

Commands

Command Description
install-hooks Install Claude Code hooks for state monitoring
uninstall-hooks Remove Claude Code hooks
install-app Create SpookyCat.app in ~/Applications
uninstall-app Remove SpookyCat.app
install-cli Add spookycat to ~/.local/bin
uninstall-cli Remove CLI helper
print-sample-config Print example config to stdout
set-workspace KEY ICON DIR Swap a workspace at runtime

How state monitoring works

SpookyCat monitors Claude Code via two mechanisms:

  1. Hooks (push): Claude Code hooks in ~/.claude/settings.json signal state changes through a Unix domain socket (/tmp/spookycat.sock). The hooks are no-ops when SpookyCat isn't running.

  2. Polling (pull): Every poll_interval seconds, SpookyCat checks for claude processes via pgrep/lsof and reads git branches via git rev-parse. This catches cases where hooks don't fire (crash, manual exit).

License

MIT, see LICENSE