by sverrirsig
macOS desktop dashboard for monitoring and managing multiple Claude Code sessions
# Add to your Claude Code skills
git clone https://github.com/sverrirsig/claude-controlWhen you're running several Claude Code instances across different repos and worktrees, it's hard to keep track of what each one is doing. Claude Control auto-discovers all running sessions and gives you a single dashboard with live status, git changes, conversation previews, and quick actions — without leaving the app.

claude CLI processes via the process table, uses hook events for authoritative PID-to-JSONL mapping with mtime-based fallbackghNo comments yet. Be the first to share your thoughts!
send-keys/create-pr to idle sessions and see PR links once created.node-version)gh) for PR detection (optional)A .node-version file is included for version managers like fnm and nvm. With auto-switching enabled, both will pick up the correct version when you cd into the project. Otherwise, run fnm use or nvm use.
Download the latest .dmg from the Releases page, open it, and drag the app to Applications. Both Apple Silicon and Intel builds are available.
# Clone the repo
git clone https://github.com/sverrirsig/claude-control.git
cd claude-control
# Install dependencies
npm install
# Run in development mode (hot-reload)
npm run electron:dev
# Or build a distributable DMG
npm run electron:build
The development server runs on port 3200. The Electron shell loads it automatically.
| Command | Description |
|---|---|
| npm run electron:dev | Dev mode with hot-reload (Next.js + Electron) |
| npm run electron:build | Production build → DMG + ZIP in dist/ |
| npm run electron:pack | Production build → unpacked app in dist/ |
| npm run dev | Next.js dev server only (no Electron shell) |
| npm run build | Next.js production build only |
| npm run test | Run unit tests (Vitest) |
| npm run test:watch | Run tests in watch mode |
| npm run lint | Run ESLint |
| npm run typecheck | Run TypeScript type checking |
claude via pslsof~/.claude-control/events/<pid>.json) for authoritative PID→JSONL mapping and session statusHook events are installed automatically into ~/.claude/settings.json on first launch. Each Claude process writes its status, session ID, and transcript path to a <pid>.json file on every lifecycle event.
Primary (hook events):
| Status | Hook Event |
|---|---|
| Working | UserPromptSubmit, SubagentStart, PostToolUseFailure |
| Waiting | PermissionRequest (overridden to Working if CPU > 15%) |
| Idle | SessionStart, Stop |
| Finished | SessionEnd |
Fallback (heuristic, when hooks unavailable):
| Status | Condition | |---|---| | Working | JSONL modified recently AND CPU > 5%, or CPU > 15% | | Waiting | Pending tool use (after 3s settle) or asking for user input | | Idle | Process alive, low activity | | Errored | Last message contains error indicators | | Finished | Process no longer running |
Electron shell (macOS native window)
↓
Browser (SWR polls /api/sessions every 1s)
↓
Next.js API Routes (standalone server)
↓
┌──────────────────────────────────────────┐
│ discovery.ts → process-utils.ts │ ps, lsof
│ → hooks-reader.ts │ <pid>.json → status + transcript
│ → paths.ts │ ~/.claude/projects mapping
│ → session-reader.ts │ JSONL parsing
│ → git-info.ts │ git status, diff, PR detection
│ → status-classifier.ts │ Heuristic fallback
└──────────────────────────────────────────┘
No database — all state is derived from the process table, hook event files, and JSONL transcripts on every request.
Claude-control auto-detects which terminal each Claude session is running in by walking the process tree. Capabilities vary by terminal:
These terminals support tab-level focus, text input, and keystroke sending — clicking "focus" in the dashboard switches to the exact tab running that session.
| Terminal | Focus method | How it works |
|---|---|---|
| Terminal.app | AppleScript (TTY matching) | Matches tabs by TTY, uses System Events for keystrokes. Works out of the box. |
| iTerm2 | AppleScript (TTY matching) | Iterates windows/tabs/sessions, matches by TTY path. Native write text for keystrokes. Works out of the box. |
| kitty | Remote control (Unix socket) | Uses kitten @ IPC to resolve window by PID, then focus by window ID. Supports tmux-in-kitty matching. Requires configuration (see below). |
| WezTerm | CLI (wezterm cli) | Uses wezterm cli to list panes, focus by pane ID, and send text directly. Works out of the box. |
| cmux | AppleScript (panel ID) | Reads cmux's native session JSON to resolve TTY → panel ID, then focuses via AppleScript. Works out of the box. |
kitty requires remote control to be enabled. Add the following to ~/.config/kitty/kitty.conf:
allow_remote_control socket-only
listen_on unix:/tmp/kitty-{kitty_pid}
You must restart kitty after making these changes (listen_on is not reloaded on config refresh).
allow_remote_control socket-only — Allows external programs to control kitty via the Unix socket, while preventing programs running inside kitty (e.g. scripts you run) from doing so. This is the recommended security setting.listen_on unix:/tmp/kitty-{kitty_pid} — Creates a socket at /tmp/kitty-<pid> that claude-control uses to send commands. The {kitty_pid} placeholder ensures each kitty instance gets its own socket.To verify it's working, run this inside kitty:
kitten @ ls
If it outputs JSON with your windows and tabs, remote control is active. Without these settings, claude-control falls back to basic app activation (no tab selection).
These terminals are detected and can be activated, but focus goes to the app — not a specific tab. Text and keystrokes are sent via macOS System Events.
| Terminal | Notes | |---|---| | Ghostty | Has AppleScript support since v1.3 but lacks PID/TTY properties for tab matching (#10756). Full support expected when 1.4 ships. | | Warp | No tab-level IPC available. | |