ccdock
v0.3.4
Published
A TUI sidebar to orchestrate VS Code windows and track Claude Code agents.
Maintainers
Readme
ccdock
Why?
Running multiple Claude Code agents across git worktrees is powerful — but managing the VS Code windows that go with them is a nightmare. You end up Alt-Tabbing through a dozen windows, losing track of which agent is doing what, and manually arranging editors every time you switch context.
Existing "hub" tools either force you into a CLI-only workflow or require a proprietary editor. But you already have VS Code. You just need something to keep it organized.
ccdock sits in a narrow terminal sidebar and takes care of the rest: auto-positioning VS Code windows, tracking every Claude Code agent in real time, and letting you switch between sessions with a single click.
Features
- VS Code orchestration — Auto-open, position, and switch VS Code (or Cursor) windows next to the sidebar. Click a session, and the right editor snaps into focus.
- Real-time agent monitoring — See exactly what each Claude Code agent is doing: which tool it's calling, what file it's reading, what command it's running.
- Git worktree management — Create, switch, and delete worktrees via git-wt integration. Each worktree gets its own session.
- Activity log — Live feed of tool invocations with session numbers (#N) across all active agents.
- Mouse + keyboard — Click to select sessions, scroll wheel to navigate, or use vim-style
j/kkeys. - Auto-layout — VS Code windows automatically resize and reposition when the terminal resizes.
Requirements
- macOS (uses AppleScript for window management)
- Bun runtime (v1.0+)
- VS Code or Cursor
- git-wt for worktree creation (
go install github.com/k1LoW/git-wt@latest) - Ghostty terminal (used for sidebar window detection)
- A terminal font with Nerd Font support (for icons)
Install
# ccdock itself
bun install -g ccdock
# git-wt (required for worktree creation)
go install github.com/k1LoW/git-wt@latestSetup
1. Configure workspace directories
Edit ~/.config/ccdock/config.json (auto-created on first run):
{
"workspace_dirs": ["~/workspace"],
"editor": "code",
"sound": {
"enabled": true,
"permission_request": "/System/Library/Sounds/Funk.aiff",
"notification": "/System/Library/Sounds/Glass.aiff"
},
"notifications": {
"enabled": true,
"events": ["PermissionRequest", "Notification"]
}
}| Key | Description |
| -------------------------- | ------------------------------------------------------------------------------------ |
| workspace_dirs | Directories to scan for git repositories |
| editor | Editor command: "code" for VS Code, "cursor" for Cursor |
| sound.enabled | Play a sound when an agent surfaces a PermissionRequest / Notification |
| sound.permission_request | Sound file (afplay-compatible) for permission prompts |
| sound.notification | Sound file for general notifications |
| notifications.enabled | Pop a macOS Notification Center alert in addition to the sound |
| notifications.events | Hook events that trigger an alert (PermissionRequest, Notification, Stop, ...) |
When notifications.enabled is on, the sound is delivered by macOS as part of the alert — the standalone afplay path is suppressed for that event to avoid a double-beep.
Click-through behavior depends on which backend ccdock can find:
- If
terminal-notifieris installed (brew install terminal-notifier), clicking Show activates Ghostty (the sidebar's terminal) so ccdock comes back to the foreground. - Otherwise ccdock falls back to
osascript display notification. macOS attributes those alerts to Script Editor, so the Show button opens Script Editor — installingterminal-notifieris recommended for the better UX.
Override the activated app with CCDOCK_NOTIFY_BUNDLE_ID=<bundle.id> (e.g. com.googlecode.iterm2 for iTerm2). Set CCDOCK_TERMINAL_NOTIFIER=/path/to/terminal-notifier if it lives outside the standard Homebrew prefixes.
Set CCDOCK_SILENT=1 in the environment to mute every sound and notification regardless of config (handy for tests / quiet sessions). Other macOS system sounds live in /System/Library/Sounds/ (Glass, Funk, Submarine, Ping, Sosumi, …).
2. Set up Claude Code hooks
Add to ~/.claude/settings.json to enable agent status monitoring:
{
"hooks": {
"PreToolUse": [{ "matcher": "", "hooks": [{ "type": "command", "command": "ccdock hook claude-code PreToolUse" }] }],
"PostToolUse": [{ "matcher": "", "hooks": [{ "type": "command", "command": "ccdock hook claude-code PostToolUse" }] }],
"PermissionRequest": [{ "matcher": "", "hooks": [{ "type": "command", "command": "ccdock hook claude-code PermissionRequest" }] }],
"Stop": [{ "matcher": "", "hooks": [{ "type": "command", "command": "ccdock hook claude-code Stop" }] }],
"Notification": [{ "matcher": "", "hooks": [{ "type": "command", "command": "ccdock hook claude-code Notification" }] }],
"SessionEnd": [{ "matcher": "", "hooks": [{ "type": "command", "command": "ccdock hook claude-code SessionEnd" }] }]
}
}3. Set up Codex hooks (optional)
If you use OpenAI Codex CLI, you can forward its lifecycle hooks to ccdock too. Tested with codex-cli 0.123.0.
Enable the under-development
codex_hooksfeature in~/.codex/config.toml:[features] codex_hooks = trueCreate
~/.codex/hooks.json(Codex 0.123.0 only reads hooks from this file — inline TOML hooks inconfig.tomlare not wired up yet):{ "hooks": { "PreToolUse": [ { "matcher": "", "hooks": [{ "type": "command", "command": "ccdock hook codex PreToolUse", "timeout": 30 }] } ], "PostToolUse": [ { "matcher": "", "hooks": [{ "type": "command", "command": "ccdock hook codex PostToolUse", "timeout": 30 }] } ], "PermissionRequest": [ { "matcher": "", "hooks": [{ "type": "command", "command": "ccdock hook codex PermissionRequest", "timeout": 30 }] } ], "Stop": [ { "matcher": "", "hooks": [{ "type": "command", "command": "ccdock hook codex Stop", "timeout": 30 }] } ] } }Restart Codex so it picks up the new configuration.
Known limitations (upstream Codex)
- Only
Bash-style shell tools (local_shell/shell/exec_command) fire hooks reliably today.apply_patch, file writes, and MCP tool invocations currently do not emitPreToolUse/PostToolUse(openai/codex#16732, #17794). Stopdoes not fire undercodex exec(openai/codex#18607); interactive sessions are fine.- Hooks are disabled on Windows.
- Codex does not emit
SessionEnd. Agents in thestoppedstate (after theStophook) are preserved indefinitely so completed sessions stay visible; entries are removed when the matching session is deleted or when the agent'scwdno longer maps to a known session.
Usage
ccdock # start the sidebar TUI
ccdock help # show helpKeybindings
| Key | Action |
| ------------ | --------------------------------------- |
| j / k | Navigate between sessions |
| Enter | Focus editor window for selected session |
| Tab | Focus editor window (same as Enter) |
| n | Create new session (interactive wizard) |
| d | Delete session |
| r | Realign all VS Code windows |
| c | Toggle compact mode |
| l | Toggle activity log |
| q / Ctrl+C | Quit (with option to close editors) |
| Mouse click | Select session |
| Scroll wheel | Navigate between sessions |
Session card states
| Card appearance | Meaning |
| --------------- | ------- |
| White border + green ● | Editor is focused |
| Normal border + green ● | Editor is open but not focused |
| Spinning ⠋ indicator | Editor is launching |
| Dim border, no dot | Editor is closed |
Agent status
| Icon | Status | Description |
| ---- | ------ | ----------- |
| ● green | running | Agent is executing tools |
| ●/○ yellow pulse | waiting | Awaiting user permission |
| ○ gray | idle | Agent started but hasn't done anything yet |
| ● teal | stopped | Agent finished its turn (Stop event received) |
How it works
Architecture
Claude Code hooks --> ccdock hook --> writes agent JSON files
|
ccdock sidebar (polls every 2s) <----------+
|
+--> reads session + agent state files
+--> queries VS Code windows via AppleScript
+--> renders TUI with merged state- State —
~/.local/state/ccdock/stores session and agent state as JSON files - Hooks —
ccdock hookwrites agent state files when Claude Code fires events - Window management — AppleScript via
osascriptto position VS Code next to the sidebar - Wizard —
nkey scans workspace dirs, offers create/existing/root worktree options viagit wt
File structure
src/
main.ts — CLI entry point
sidebar.ts — Main event loop, input handling
types.ts — Type definitions
config/config.ts — Config (~/.config/ccdock/)
workspace/state.ts — Session/agent state persistence
workspace/editor.ts — VS Code open/focus
workspace/window.ts — AppleScript window management
worktree/manager.ts — Git worktree operations
worktree/scanner.ts — Repository discovery
tui/render.ts — Sidebar rendering
tui/wizard.ts — Session wizard rendering
tui/input.ts — Keyboard input parsing
tui/ansi.ts — ANSI escape codes
agent/hook.ts — Hook handlerDevelopment
# Clone
git clone https://github.com/shibutani/ccdock.git
cd ccdock
bun install
# Run directly
bun run dev
# Type check
bun run typecheck
# Format
bun run format
# Build standalone binary (optional)
bun run buildProject structure
The project uses Bun as runtime and Biome for formatting.
src/main.ts— CLI entry point, routesstart/hook/helpcommandssrc/sidebar.ts— Main event loop: keyboard input, timers, state refreshsrc/tui/— Terminal UI rendering (cards, wizard, input parsing, ANSI codes)src/workspace/— File-based state, AppleScript window management, editor controlsrc/worktree/— Git worktree operations and repository scanningsrc/agent/— Claude Code hook handler
Publishing
npm publishLicense
MIT
