Skip to content

cc-deck/cc-deck

Repository files navigation

cc-deck   cc-deck

CI codecov Go Rust Zellij License Beta

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


Quickstart

Homebrew (macOS)

brew install cc-deck/tap/cc-deck
cc-deck config plugin install
zellij --layout cc-deck

Binary download

Download 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

Linux packages

# Fedora / RHEL
sudo dnf install ./cc-deck_*.rpm

# Debian / Ubuntu
sudo apt install ./cc-deck_*.deb

Download RPM and DEB packages from GitHub Releases. After installing, run cc-deck config plugin install to set up the Zellij plugin and hooks.

Try without installing

podman run -it --rm \
  -e ANTHROPIC_API_KEY=sk-ant-... \
  quay.io/cc-deck/cc-deck-demo:latest

Docker works too: replace podman with docker.

Build from source

git clone https://github.com/cc-deck/cc-deck.git
cd cc-deck
make install

Requires Zellij 0.44+, Go 1.22+, and Rust stable with wasm32-wasip1 target.


What does cc-deck do?

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.

Sidebar plugin

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.

Workspace management

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-project

Git 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.

Build command

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.capture discovers your local tools, shell config, plugins, MCP servers, and credentials
  • /cc-deck.build --target container|ssh|openshell generates 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.

Network filtering

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

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-project

Multi-platform

Run 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.


Usage

zellij --layout cc-deck

Or set as default in ~/.config/zellij/config.kdl:

default_layout "cc-deck"

Layout variants

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

Keyboard shortcuts

Global (from any tab)

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

Session list (navigation mode)

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

Mouse

Action Effect
Left-click session Switch to that session
Right-click session Rename session
Click [+] New tab

Customizing keybindings

Plugin shortcuts (Alt+s, Alt+a)

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 install overwrites 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.

Personal layout (recommended)

Create a personal layout that is not overwritten by make install:

cp ~/.config/zellij/layouts/cc-deck.kdl ~/.config/zellij/layouts/cc-deck-personal.kdl

Edit ~/.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"

Using Cmd keys (macOS + Ghostty)

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

Session states

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

Session lifecycle

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 Stop hook 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.

Fading indicators

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.

Smart attend (Alt+a)

Uses exclusive tiers. Only the highest non-empty tier is cycled:

  1. ⚠ Waiting (permission first, then notification, oldest first). When waiting sessions exist, Alt+a cycles only among those.
  2. ✓ Done (most recently finished first). Only used when no waiting sessions exist.
  3. ○ Idle/Init (tab order). Only used when nothing else needs attention.
  4. 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.

Working jump (Alt+w)

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.

Network filtering

Containerized sessions can restrict outbound network access to specific domains, preventing code or secret exfiltration from YOLO-mode agents.

Quick setup

Add a network section to your build.yaml:

network:
  allowed_domains:
    - github
    - python
    - golang

Then create a compose workspace with network filtering:

cc-deck ws new my-session --type compose --allowed-domains python,github

The session container runs on an internal network with all traffic routed through a tinyproxy sidecar that only allows the specified domains.

Domain groups

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.

Customizing domain groups

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

Create-time domain overrides

# 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 docker

Debugging blocked domains

cc-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 domains

Workspace management

The 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

Project-local configuration

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 project

The .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

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 attach

The project directory is bind-mounted at /workspace by default for bidirectional file sync.

SSH workspaces

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 ./src

Pre-flight checks during creation verify SSH connectivity and offer to install missing tools on the remote.

Variants

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-bugfix

Test coverage

Coverage measurement for the Rust plugin uses cargo-llvm-cov. Install prerequisites first:

cargo install cargo-llvm-cov
rustup component add llvm-tools-preview

Then 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.

Integration tests

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 only

Uninstall

cc-deck config plugin remove

Project structure

cc-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)

Known issues

Duplicate controller instances (Zellij bug)

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.

Contributing

Contributions are welcome. See CONTRIBUTING.md for the development process, including how Spec-Driven Development is used for larger changes.

Feature specifications

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

About

No description, website, or topics provided.

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors