The TweetDeck for Claude Code. A Zellij sidebar plugin that monitors, attends to, and orchestrates multiple Claude Code sessions from a single terminal view. Zellij is a modern terminal multiplexer (like tmux, but with a plugin system and built-in layout management).
Warning
Beta software. APIs, configuration formats, and behavior may change between releases. The author uses it daily for real work and it generally does what it promises. Bug reports and feedback are welcome.
Website · Documentation · Quickstart · Contributing
brew install cc-deck/tap/cc-deck
cc-deck config plugin install
zellij --layout cc-deckDownload the latest release from GitHub Releases:
# macOS (Apple Silicon)
curl -fsSL https://github.com/cc-deck/cc-deck/releases/latest/download/cc-deck_$(curl -s https://api.github.com/repos/cc-deck/cc-deck/releases/latest | jq -r .tag_name | sed 's/^v//')_darwin_arm64.tar.gz | tar -xz
sudo mv cc-deck /usr/local/bin/
cc-deck config plugin install
zellij --layout cc-deck# Fedora / RHEL
sudo dnf install ./cc-deck_*.rpm
# Debian / Ubuntu
sudo apt install ./cc-deck_*.debDownload RPM and DEB packages from GitHub Releases. After installing, run cc-deck config plugin install to set up the Zellij plugin and hooks.
podman run -it --rm \
-e ANTHROPIC_API_KEY=sk-ant-... \
quay.io/cc-deck/cc-deck-demo:latestDocker works too: replace podman with docker.
git clone https://github.com/cc-deck/cc-deck.git
cd cc-deck
make installRequires Zellij 0.44+, Go 1.22+, and Rust stable with wasm32-wasip1 target.
Running multiple Claude Code sessions in separate terminals gets messy fast. You lose track of which session needs your input, which one just finished, and which one is stuck waiting for permission.
cc-deck puts a real-time sidebar next to your sessions that shows what each one is doing and directs your attention where it matters.
The sidebar tracks every Claude Code session across Zellij tabs. It shows activity status, handles permission requests, and provides keyboard-driven navigation. Alt+a (smart attend) cycles through sessions that need you, prioritizing permission requests over finished tasks over idle sessions. Alt+w jumps between working sessions. Click, type, or use vim-style j/k navigation.
Session indicators fade over time: a green checkmark dims to grey after five minutes, an idle circle darkens over an hour. You can tell at a glance how fresh each session is.
The cc-deck ws command manages Claude Code sessions across local, containerized, remote, and sandboxed backends. See the workspace management section for the full subcommand reference, project-local configuration, and workspace type details.
cc-deck ws new my-project --type container --image quay.io/cc-deck/cc-deck-demo
cc-deck ws attach my-projectGit repos are cloned into the workspace automatically when you pass --repo flags or run ws new from inside a git repository. Up to four repos clone in parallel.
cc-deck build replicates your local developer environment to container images, SSH machines, or OpenShell sandboxes from a single manifest. Two Claude Code slash commands handle the workflow:
/cc-deck.capturediscovers your local tools, shell config, plugins, MCP servers, and credentials/cc-deck.build --target container|ssh|openshellgenerates and builds the target
Capture once, build for any target. The manifest (build.yaml) stores tool requirements, settings paths, network domain groups, and credential declarations. After generating artifacts, cc-deck build run executes them without Claude Code. Use /cc-deck.capture --all to auto-accept all proposals without prompting.
Fixed Containerfile layers (the cc-deck/Zellij/Claude Code stack, shell finalization, footer) are rendered from Go templates during build init. Claude Code copies these snippets verbatim and generates only the variable parts between them, keeping structural layers deterministic across builds.
For OpenShell targets, the build generates a policy.yaml with network restrictions. Package registry endpoints (crates.io, proxy.golang.org, npmjs.org, pypi.org) are added automatically when the corresponding tools appear in the manifest. Credentials for OpenShell are declared in the manifest without storing secrets (credentials: [{type: claude-vertex}, {type: github}]). At ws new time, cc-deck resolves values from your host environment and creates OpenShell providers on the gateway. Supported types: claude, claude-vertex, github, gitlab, openai, nvidia, vertex, generic.
Containerized sessions can restrict outbound network access to specific domains, preventing code or secret exfiltration. Domain groups (python, rust, github, etc.) describe allowed domains by ecosystem. See the network filtering section for setup, domain groups, and customization.
Voice relay lets you dictate into any workspace session using local speech-to-text via whisper.cpp. Audio stays on your machine. A note indicator in the sidebar shows connection status. Toggle mute from the sidebar (Alt+v) or the voice TUI (m). Say "send" to submit a prompt.
brew install whisper-cpp
cc-deck ws voice --setup
cc-deck ws voice my-projectRun cc-deck locally with Zellij, in Podman containers, or on Kubernetes clusters with persistent StatefulSet-backed workspaces. OpenShift is detected automatically. The sidebar works the same everywhere.
zellij --layout cc-deckOr set as default in ~/.config/zellij/config.kdl:
default_layout "cc-deck"Three layout styles are installed:
| Layout | Command | Description |
|---|---|---|
standard |
zellij --layout cc-deck |
Sidebar + tab-bar + status-bar (default) |
minimal |
zellij --layout cc-deck-minimal |
Sidebar + compact-bar |
clean |
zellij --layout cc-deck-clean |
Sidebar only, no bars |
To change the default variant:
cc-deck config plugin install --layout minimal --force| Key | Action |
|---|---|
Alt+s |
Open session list / cycle through sessions |
Alt+a |
Jump to next session needing attention |
Alt+w |
Jump to next working session |
| Key | Action |
|---|---|
j / ↓ |
Move cursor down |
k / ↑ |
Move cursor up |
Enter |
Switch to selected session |
Esc |
Cancel (return to original session) |
r |
Rename session |
d |
Delete session (with confirmation) |
p |
Pause/unpause session |
n |
New tab |
/ |
Search/filter by name |
? |
Show keyboard help |
| Action | Effect |
|---|---|
| Left-click session | Switch to that session |
| Right-click session | Rename session |
| Click [+] | New tab |
Edit the plugin config in the layout file (~/.config/zellij/layouts/cc-deck.kdl):
plugin location="file:~/.config/zellij/plugins/cc_deck.wasm" {
mode "sidebar"
navigate_key "Super s" // default: "Alt s"
attend_key "Super n" // default: "Alt a"
working_key "Alt w" // default: "Alt w"
done_timeout "300" // seconds before Working to Done and Done to Idle (default: 300)
idle_fade_secs "3600" // idle indicator fade duration in seconds (default: 3600)
auto_pause_secs "3600" // auto-pause after idle for this many seconds (default: 3600, 0 to disable)
attend_cycle_ms "2000" // rapid-cycle window for attend/working in ms (default: 2000, 0 to disable)
}Key syntax follows Zellij key format: Alt, Ctrl, Super (Cmd on macOS), Shift as modifiers, followed by the key character.
After editing, restart Zellij to apply.
Note:
make installoverwrites the managed layout files (cc-deck.kdl,cc-deck-standard.kdl,cc-deck-clean.kdl). Use a personal layout file to preserve custom keybindings across reinstalls.
Create a personal layout that is not overwritten by make install:
cp ~/.config/zellij/layouts/cc-deck.kdl ~/.config/zellij/layouts/cc-deck-personal.kdlEdit ~/.config/zellij/layouts/cc-deck-personal.kdl with your custom keys:
plugin location="file:~/.config/zellij/plugins/cc_deck.wasm" {
mode "sidebar"
navigate_key "Super s"
attend_key "Super n"
}Then set it as default in ~/.config/zellij/config.kdl:
default_layout "cc-deck-personal"To use Cmd-based shortcuts, configure Ghostty to pass Cmd keys through to Zellij:
Ghostty (~/.config/ghostty/config):
keybind = cmd+s=unbind
keybind = cmd+n=unbind
| Icon | State | Description |
|---|---|---|
| ○ | Init | Session detected, not yet producing output |
| ● | Working | Actively generating output or calling tools |
| ⚠ | Waiting (Permission) | Needs user permission to proceed (highest attend priority) |
| ⚠ | Waiting (Notification) | Paused with informational notification |
| ✓ | Done | Task completed (green, fades to grey over 5 minutes) |
| ✓ | Agent Done | Sub-agent completed |
| ○ | Idle | Waiting for user input (fades to dark grey over 1 hour) |
| ⏸︎ | Paused | Excluded from attend cycling, name dimmed |
Sessions progress through states based on activity timeouts:
Working ──[5m idle]──> Done ──[5m]──> Idle ──[1h]──> Paused
- Working to Done: When no hook events arrive for 5 minutes, the session is considered complete. This is a fallback since Claude Code does not always fire the
Stophook on natural response completion. - Done to Idle: After 5 more minutes, the green checkmark fades to a grey circle.
- Idle to Paused: After 1 hour of inactivity, the session auto-pauses. Paused sessions are excluded from attend cycling and hook processing.
- Auto-unpause: Switching to a paused session (via click, navigate, or attend) unpauses it automatically.
Session indicators use time-aware color fading to show freshness:
- Done (✓): Fades from bright green to light grey over 5 minutes
- Idle (○): Fades from light grey to dark grey over 1 hour
The fade follows a square-root curve: changes are most visible in the first few minutes and taper off.
Uses exclusive tiers. Only the highest non-empty tier is cycled:
- ⚠ Waiting (permission first, then notification, oldest first). When waiting sessions exist, Alt+a cycles only among those.
- ✓ Done (most recently finished first). Only used when no waiting sessions exist.
- ○ Idle/Init (tab order). Only used when nothing else needs attention.
- Skips: Working and Paused sessions are never attended.
Subsequent presses round-robin within the selected tier. If the current session is already the attend target, it skips to the next candidate.
Cycles through sessions that are actively running, ordered by most recently active first. Only Working sessions (purple ●) are included. Waiting sessions are excluded because they need attention, which is Alt+a's job.
Rapid presses within 2 seconds cycle through all working sessions without revisiting. After a 2-second pause, the next press resets to the most recent working session.
Containerized sessions can restrict outbound network access to specific domains, preventing code or secret exfiltration from YOLO-mode agents.
Add a network section to your build.yaml:
network:
allowed_domains:
- github
- python
- golangThen create a compose workspace with network filtering:
cc-deck ws new my-session --type compose --allowed-domains python,githubThe session container runs on an internal network with all traffic routed through a tinyproxy sidecar that only allows the specified domains.
Built-in groups cover common ecosystems. Run cc-deck config domains list to see all available groups:
| Group | Covers |
|---|---|
python |
pypi.org, files.pythonhosted.org |
nodejs |
registry.npmjs.org, yarnpkg.com |
rust |
crates.io, static.crates.io |
golang |
proxy.golang.org, sum.golang.org |
github |
github.com, ghcr.io, githubusercontent.com |
gitlab |
gitlab.com, registry.gitlab.com |
docker |
registry-1.docker.io, auth.docker.io |
quay |
quay.io, cdn.quay.io |
Backend domains (Anthropic or Vertex AI) are included automatically.
Create ~/.config/cc-deck/domains.yaml to extend or override built-in groups:
cc-deck config domains init # Seed config with commented built-in definitions# Extend built-in python group with internal registry
python:
extends: builtin
domains:
- pypi.internal.corp
# Create a custom group
company:
domains:
- artifacts.internal.corp
- git.internal.corp# Specify domain groups when creating a compose workspace
cc-deck ws new my-session --type compose --allowed-domains rust,github
# Add or remove domains at runtime on a running session
cc-deck config domains add my-session rust
cc-deck config domains remove my-session dockercc-deck config domains blocked my-session # Show denied requests
cc-deck config domains add my-session pypi.org # Add domain at runtime
cc-deck config domains show python # Inspect a group's domainsThe cc-deck ws command group manages Claude Code sessions across all supported backends.
| Type | What it does |
|---|---|
local |
Zellij session on the host machine (default) |
container |
Single container managed by Podman |
compose |
Multi-container setup via podman-compose |
ssh |
Remote machine over SSH |
k8s-deploy |
Persistent Kubernetes workspace with StatefulSet |
openshell |
OpenShell sandbox with security policies |
| Subcommand | Description |
|---|---|
cc-deck ws new |
Create a new workspace |
cc-deck ws attach |
Attach to a workspace (auto-starts infrastructure if needed) |
cc-deck ws kill-session |
Kill the Zellij session without affecting infrastructure |
cc-deck ws start |
Start infrastructure for container/compose/k8s workspaces |
cc-deck ws stop |
Stop infrastructure (kills session first, then stops container/pod) |
cc-deck ws delete |
Delete a workspace and its resources |
cc-deck ws list |
List all workspaces with type-appropriate state display |
cc-deck ws status |
Show detailed status of a workspace |
cc-deck ws prune |
Remove stale project registry entries |
Workspace definitions can live inside the project repository in a .cc-deck/ directory at the git root. Team members can clone and create matching workspaces without manual flag passing.
# Set up a new project
cc-deck ws new --type compose --image quay.io/cc-deck/cc-deck-demo:latest
git add .cc-deck/ && git commit -m "Add cc-deck workspace config"
# Team member clones and gets the same environment
git clone git@github.com:org/my-api.git && cd my-api
cc-deck ws new # reads .cc-deck/environment.yaml
cc-deck ws attach # no name needed inside the projectThe .cc-deck/ directory separates committed artifacts from runtime state:
.cc-deck/
environment.yaml # Committed: declarative definition
.gitignore # Committed: ignores status.yaml and run/
image/ # Committed: build manifest, Containerfile
status.yaml # Gitignored: runtime state
run/ # Gitignored: generated compose files
When no workspace name is provided, cc-deck looks for .cc-deck/environment.yaml at the git root, then walks up the directory tree. All lifecycle commands (attach, status, start, stop, kill) support this implicit resolution.
Compose workspaces use podman-compose for multi-container orchestration. They generate runtime artifacts in .cc-deck/run/ within the project directory.
cc-deck ws new --type compose
cc-deck ws new --type compose --allowed-domains anthropic,github
cc-deck ws attachThe project directory is bind-mounted at /workspace by default for bidirectional file sync.
SSH workspaces run Zellij sessions on persistent remote machines. You connect over SSH, work inside the remote Zellij session, and detach when finished. The session continues running on the remote host.
cc-deck ws new remote-dev --type ssh --host user@dev.example.com
cc-deck ws attach remote-dev
cc-deck ws refresh-creds remote-dev
cc-deck ws push remote-dev ./srcPre-flight checks during creation verify SSH connectivity and offer to install missing tools on the remote.
When the same project needs multiple isolated container instances (for example, per-worktree containers), use the --variant flag:
cc-deck ws new --variant auth # container: cc-deck-my-api-auth
cc-deck ws new --variant bugfix # container: cc-deck-my-api-bugfixCoverage measurement for the Rust plugin uses cargo-llvm-cov. Install prerequisites first:
cargo install cargo-llvm-cov
rustup component add llvm-tools-previewThen use the Makefile targets:
| Target | Description |
|---|---|
make coverage |
Generate an HTML report and open it in the browser |
make coverage-summary |
Print a per-module coverage table to the terminal |
make coverage-json |
Write machine-readable JSON to cc-zellij-plugin/target/llvm-cov/coverage.json |
Coverage runs tests on the native target, not wasm32. Code behind #[cfg(target_family = "wasm")] guards is unreachable during measurement.
The plugin includes integration tests that exercise SidebarRendererPlugin and ControllerPlugin through their ZellijPlugin trait methods (load, update, pipe) with synthetic events. These tests verify the full event dispatch chain without a running Zellij instance.
Integration tests cover render payload pipeline, hook event processing, discovery protocol handshake, action dispatch, permission deferral, mode transitions, error handling, and protocol roundtrips.
make test # all tests
cargo test --lib integration_tests # integration tests onlycc-deck config plugin removecc-zellij-plugin/ Zellij sidebar plugin (Rust, WASM)
cc-deck/ CLI tool (Go)
docs/ Antora documentation source
demos/ Demo recording system
demo-image/ Demo container image build
base-image/ Base container image build
specs/ Feature specifications (SDD)
Zellij 0.43 and 0.44 occasionally create two WASM instances of a background plugin when load_plugins races with the AddClient event. This causes duplicate keybinding registrations and render broadcasts.
cc-deck mitigates this with a leader election protocol. On startup, each controller broadcasts a probe with its plugin ID. The instance with the lowest ID activates within two seconds; the other stays dormant. The leader sends a periodic heartbeat (every 30 seconds) so the dormant instance can detect failure and re-activate.
The only visible effect is a two-second delay before keybindings become active on a fresh Zellij start, which overlaps with Zellij's own initialization time.
Contributions are welcome. See CONTRIBUTING.md for the development process, including how Spec-Driven Development is used for larger changes.
cc-deck follows Spec-Driven Development. Each feature starts with a specification before implementation. Current specs:
| ID | Feature | Status |
|---|---|---|
| 002 | Kubernetes CLI | Planned |
| 012 | Sidebar Plugin | Implemented |
| 013 | Keyboard Navigation & Global Shortcuts | Implemented |
| 014 | Session Pause Mode & Keyboard Help | Implemented |
| 015 | Session Save and Restore | Planned |
| 016 | K8s Integration Tests | Planned |
| 017 | Base Container Image | Implemented |
| 018 | Build Pipeline | In Progress |
| 019 | Documentation & Landing Page | In Progress |
| 020 | Demo Recording System | In Progress |
| 021 | Release Process | Implemented |
| 022 | Network Security & Domain Filtering | In Progress |
| 023 | Environment Interface and CLI | Planned |
| 024 | Container Environment | Implemented |
| 025a | Sidebar State Refresh on Reattach | In Progress |
| 025b | Compose Environment | In Progress |
| 026 | Project-Local Config | Implemented |
| 027 | CLI Command Restructuring | In Progress |
| 028 | K8s Deploy Environment | Implemented |
| 030 | Single Instance Architecture | Implemented |
| 031 | Single Binary Merge | Implemented |
| 033 | SSH Remote Execution | In Progress |
| 034 | Build Command | Planned |
| 036 | Setup Run Command | Implemented |
| 037 | Environment Lifecycle Fixes | In Progress |
| 038 | Workspace Repos | In Progress |
| 039 | CLI Rename: Workspace & Build | In Progress |
| 041 | Workspace Channels | In Progress |
| 042 | Voice Relay | Implemented |
| 045 | Voice Sidebar Integration | In Progress |
| 056 | OpenShell Build Target | In Progress |
| 058 | OpenShell Credential Injection | In Progress |