Skip to content

Repository files navigation

ccdeck — a live dashboard for Claude Code and Codex

ccdeck is a free, open-source (AGPL-3.0) dashboard that runs on your machine and shows every Claude Code and OpenAI Codex CLI session, including ones started in a plain terminal: which Claude Code session needs input, each Claude Code subagent and every tool call as it runs, cost and quota left. Start it with npx ccdeck or the desktop app.

Step-by-step guides: ccdeck.dev/guides

Know which agent is waiting on you, and for how long.

ccdeck keeps them in one queue — every session stopped on a human, longest wait first, and the count in the topbar is one click to the oldest. That queue is Claude Code's, because the deck reads Codex from its rollout log and a rollout carries no such signal; the canvas under it is both, with every Claude Code subagent on a node of its own.

npm npm downloads License: AGPL v3 Node.js >=18 macOS · Linux · Windows

npx ccdeck

Or the desktop app, with the waiting count in your menu bar: macOS · Windows · Linux — every download

ccdeck showing Claude Code and Codex sessions, Claude Code subagents and tool calls on one canvas

A generated session, drawn by the deck itself — see assets/canvas-demo.mjs. Click through for full size.

which Claude Code session is blocked on you · tool calls · one canvas · cost · quota · local · sessions never leave

ccdeck.dev · Guides · What you get · Quick start · How it works · What it touches · Accounts · Local network · Options · FAQ


Why

Four agents running, and the machine has been quiet for twenty minutes. One of them stopped to ask something and you did not see it go by. From the outside every terminal tab looks the same — the one that is working and the one that has been holding a permission prompt since the coffee — so you find it by clicking through them, and the agent that was closest to finished is the one that has been waiting longest.

ccdeck answers that in one place: every session stopped on a human is at the top of the sidebar with the wait beside it, longest first, and the count in the topbar goes straight to the oldest one. The question stops being which tab and becomes this one.

That is the sharp end of a wider problem. An agent session is a tree, but a terminal shows it as a scroll: five subagents working in parallel arrive as one interleaved column of text, and the questions you actually have — what is running right now, what did that subagent do, which one is stuck, what is this costing — are the ones the scroll answers worst.

ccdeck draws the tree instead. It is local and needs no configuration: it registers a hook, listens, and paints.

What you get

One canvas. No tabs. No kanban.

The deck opens on these eight pictures the first time it runs — they are the whole tour, and Take the tour on an empty canvas brings them back.

Sessions waiting on you top the session list, longest wait first. Every agent and subagent is a node. Tool calls light up as they run.
Sessions waiting on you top the session list, longest wait first. Every agent and subagent is a node. Tool calls light up as they run.
Double-click any node: its prompt, every tool call, tokens and timing. What each session costs, and how much quota is left.
Double-click any node: its prompt, every tool call, tokens and timing. What each session costs, and how much quota is left.
Cores, memory and heat while the agents run, and what is hogging them. Several Claude accounts: switch, add one, share one to another machine.
Cores, memory and heat while the agents run, and what is hogging them. Several Claude accounts: switch, add one, share one to another machine.
Your machines repair each other's expired logins over the local network. Run claude or codex in any folder. It shows up here on its own.
Your machines repair each other's expired logins over the local network. Run claude or codex in any folder. It shows up here on its own.
Blocked on you A permission prompt, or a finished turn waiting for your next instruction, sorts that session to the top of the sidebar with how long it has been stuck — longest wait first, so the oldest block is the first row. A permission prompt also puts a count in the topbar that jumps straight to it. Claude Code only — the deck reads Codex from its rollout log, and a rollout carries no such signal.
Live DAG Nodes are agents and edges are spawns; each agent's latest tool calls sit beside its node and light up while they run. In-flight edges animate, settled ones dim.
Both providers, one canvas Claude Code through hooks, Codex through its rollout log. The model chip (Opus 5, GPT-5.5) tells them apart. Subagent cards are Claude Code only: a Codex session is one node with its tool calls.
Cost and quota, live Spend per model and per session, plus Claude and Codex quota windows as they refill.
Double-click to inspect Any node opens its prompt, tool calls, token usage and timing in the side panel. A single click selects it and frames its session.
Survives restarts Events are appended to this platform's log directory (see --history below) and replayed on open.
Accounts without a terminal Sign a new Claude account in, move one or your whole set to another machine, rename, reorder, remove — from the panel.
Logins that repair each other A Claude login that expires on one of your machines is copied back from another machine on the same network that still has it — see Local network.
Knows when it is stale Node caches modules at startup, so an upgraded-while-running deck keeps executing old code. This one says so, and can restart itself when nothing is running.
Workspace scoping --scope for the current directory, --workspace <path> for any subtree — for Claude Code and Codex alike.

Step by step: See which Claude Code session is waiting for input · See Claude Code subagents and tool calls live

Quick start

npx ccdeck

Opens http://127.0.0.1:4317, shows an eight-picture tour the first time, and registers the Claude Code hook on first run. If something else already holds 4317, the deck takes a port between 4318 and 4400 instead and prints the address it ended up on — that line in the terminal is the one to trust. Start any Claude Code or Codex session and the graph fills in live.

The deck keeps running after you close the terminal, and starts again when you log in. ccdeck --stop is the off switch; Ctrl+C only cancels a start that is still printing. ccdeck --status says what is running, and --foreground holds the terminal the way every version before 3.20 did.

No config file. No account. Nothing about your sessions is reported anywhere — the deck does send usage reports (its version, your system, your IP address and a device fingerprint, the errors it hits), on by default, and one switch under Appearance turns them off.

The deck cannot steer your agent. The hook it installs is a one-way forwarder: it POSTs the event, exits 0, and writes nothing to stdout. Those are the two channels Claude Code's hook protocol gives a hook for allowing, denying, deferring or rewriting the tool call it was told about, and this one uses neither — it has no way to answer at all. src/web/__tests__/hook-read-only.test.ts pins both halves, over the source and by running the real script.

What the deck does write, and the short list of what does leave the machine, is in What it touches.

Step by step: Install ccdeck with npx and see your first session. When a session does not show up: Troubleshoot ccdeck: status, logs, port and hooks

Desktop app

The same deck as an app: it starts the deck itself and puts an icon in the menu bar (the tray on Windows and Linux) that counts the Claude Code sessions stopped on a permission prompt or a question — the number sits beside the icon on macOS, and in the icon's tooltip and menu on Windows and Linux. With Notifications while closed ticked in that menu, which starts off, it also sends a notification while the window is closed wherever an open page would have played a sound, with the deck's own tone on macOS. It needs no Node.js. If a deck from npx ccdeck is already running, the app uses that one rather than starting a second, and replaces it only when it is older than the one the app carries.

System Download
macOS, Apple silicon ccdeck-mac-arm64.dmg
macOS, Intel ccdeck-mac-x64.dmg
Windows, x64 ccdeck-win-x64.exe
Linux, x86-64 AppImage · .deb
  • macOS — not notarised by Apple yet, so the first launch asks once: System Settings, Privacy & Security, Open Anyway. Installing from a terminal skips that step; the one-line command is under Download on the site.
  • Windows — installs for your user, with no admin prompt. It is not signed yet, so SmartScreen asks once: More info, then Run anyway.
  • Linux — on Debian and Ubuntu take the .deb: it installs with a double click and puts ccdeck in the applications menu. The AppImage is one file for every other distribution, and a browser saves it without the permission to run, so it does nothing at all until you give it one back — chmod +x ccdeck-linux-x86_64.AppImage, then open it. The icon also needs a tray to sit in: KDE and waybar have one, and GNOME needs the AppIndicator extension.

The app keeps itself current from this repository's releases, and installs an update only if it carries ccdeck's own signature. It installs on Quit, or by itself once the app has been left alone for a minute — its window closed or not in focus, and no deck starting — and restarts into the new version. That does not wait for your agents to go idle: the deck is gone for a second or two, which can leave a gap in the drawing of a turn, and the agents carry on. On the .deb, where installing asks for your password, it installs only when you choose Restart to update.

Step by step: Install the ccdeck app on Mac, Windows or Linux

Requirements

  • Node.js ≥ 18 — macOS, Linux and Windows. The floor is checked on every run: CI installs the packed release on Node 18 and boots it, so the badge is a measurement rather than a claim
  • Claude Code CLI or OpenAI Codex CLI (or both)
  • Optional: claude-swap for the Accounts panel; the deck can install it for you
  • Nothing else. On Apple Silicon the deck fetches macmon itself for the temperature rows; see below.

Temperature, per machine

The machine panel shows a Thermal section only where the machine actually answers, and it never invents a reading — no sensor means no row.

reads needs
Linux /sys/class/hwmon, then /sys/class/thermal/thermal_zone* nothing
Windows the Thermal Zone Information performance counter, then MSAcpi_ThermalZoneTemperature, then LibreHardwareMonitor's web server if it happens to be running nothing — where the machine has an ACPI thermal zone. Many do not; see below
macOS, Intel ioreg for the GPU, pmset -g therm for throttling nothing
macOS, Apple Silicon macmon, which the deck fetches for you nothing

Apple Silicon is the one that needs a tool, and it is not an oversight. No command that ships with macOS prints a temperature on an M-series Mac: powermetrics needs root, pmset -g therm records nothing there, and the sensors sit behind a private API that only native code can call. macmon reads them without sudo and covers M1 through M5.

You do not have to install it. The deck downloads the published binary into ~/.agents-deck/tools/macmon — the same place it already keeps uv — verifies it against the SHA-256 the GitHub release publishes, checks that it runs, and only then uses it. Not through Homebrew, because a machine without Homebrew would need Homebrew installed first, and that is a large thing to do to somebody who asked for a dashboard. It happens in the background, after the deck is already up, and never on the first run's critical path.

It is skipped entirely on a machine that already answers, on an Intel Mac — the release publishes an arm64 build and only that, so the architecture is checked before anything is fetched — and on one where you have macmon yourself, which is looked for on PATH and on either Homebrew prefix before the download is considered. AGENTS_DECK_NO_DOWNLOAD=1 turns it off on its own; AGENTS_DECK_NO_INSTALL=1 turns it off along with everything else.

Step by step: See CPU, memory and heat while your agents run

Windows, and why it is often blank

Windows is the platform where this most often shows nothing, and that is not a defect in the deck. Measured on a physical Windows 11 laptop — a Lenovo IdeaPad L340, Intel i5-9300H, English install, checked both as an ordinary user and as an administrator:

  • its firmware declares zero ACPI thermal zones, so the performance counter above is registered but has no instances
  • MSAcpi_ThermalZoneTemperature — which the deck does try, in the same PowerShell child, whenever the counter has no instances — answers Not supported even to an administrator
  • Win32_TemperatureProbe exists but every field reads 32768, which is WMI's value for "unknown"
  • the sensors are real and actively managed — Intel Dynamic Tuning is running — but it publishes them in root\WMI EsifDeviceInformation, which is Access denied without administrator

That is a class of machine, not a fault: modern Intel laptops moved thermal management into Intel DTT and stopped declaring the ACPI zones that Windows exposes to ordinary programs. There is no standard user-mode Windows API for CPU temperature — which is why HWiNFO, Core Temp and LibreHardwareMonitor all install a kernel driver, and why this deck does not.

Where the counter does have instances — many desktop boards, servers, and older laptops — it is read without any privileges at all. Its path is currently English-only; see #747.

One thing does work on the machines above, and it costs you nothing to have: if LibreHardwareMonitor happens to be running with its web server on, the deck reads its numbers over plain HTTP on localhost, which needs no privileges. That is a read, not a request — the deck does not install it, will not ask you to, and shows no section if it is not there. It is mentioned only so nobody is surprised to see degrees appear on a machine that had none.

How it works

Two capture paths feed one SSE stream, which feeds one canvas.

Claude Code — on first run, ccdeck adds a hook entry to ~/.claude/settings.json for every relevant event (or to $CLAUDE_CONFIG_DIR/settings.json, and every other path below moves with it, when you have that variable set):

SessionStart · UserPromptSubmit · PreToolUse · PostToolUse · PostToolUseFailure
SubagentStart · SubagentStop · Stop · SessionEnd · Notification

Each one fires the bundled hook.js, which POSTs the event JSON to the running server. The hook is fire-and-forget with a 1-second timeout: if the deck is not running, your session is not slowed down and nothing fails.

OpenAI Codex — Codex CLI hooks do not fire reliably on Windows, so nothing is installed at all. The server tails Codex's own rollout files at ~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl and reconstructs the equivalent stream — session start, prompts, tool calls, token usage, model. No hook install, no trust prompt. Set CODEX_HOME to override the path.

Quota is the one thing that is not just reading. It needs a live token, so when the one in ~/.codex/auth.json is within 90 seconds of expiring the deck refreshes it exactly as the CLI does and writes the rotated credential back — one refresh at a time, re-reading the file inside the lock, and atomically, because OpenAI's refresh tokens are single-use and a rotation that never reaches disk costs you a codex login. It happens only while the page is open, and nothing else in auth.json is touched.

What it touches

It never steers an agent or edits your code, but it is not read-only either — besides the hook entry and its own event log, it manages the two tools it leans on, and it refreshes the Codex token it reads quota with, rewriting ~/.codex/auth.json the way codex itself does. It also reads your browser's history, because Browser Watch is on unless you switch it off; that is its own section below, because it is the one thing here that is about you rather than about an agent.

What does go out is short and ordinary: a ~20-byte version check against the npm registry (plus one small request to confirm a version it has not seen before), installs and daily version checks for the two tools the deck manages (claude-swap from PyPI, ccusage from npm), on an Apple Silicon Mac whose sensors stay silent one release lookup and one binary download from GitHub for macmon, and, while the page is open, quota reads to Anthropic and OpenAI signed with your own credentials — that is where those numbers live. While the page is open, and while the desktop app has a session running or waiting, it also reads Anthropic's and OpenAI's public status pages (status.claude.com, status.openai.com), unsigned, about every three minutes, and once a minute at most while a page is not answering, so an outage on their side shows as a chip in the topbar instead of looking like a fault on yours. The desktop app also asks this repository's GitHub releases for its own updates. With Local network on, which it is unless you switch it off, the deck also announces itself to other decks on your local network over UDP (port 45317) and pairs with the ones that answer — see Local network. Unless you switch it off under Appearance → Help improve ccdeck, the deck also sends usage reports to api.ccdeck.dev: an install event, an update event, one "active" a day, and the errors it runs into with folders, addresses and keys scrubbed out. Each carries the version, your OS and CPU architecture, the channel (desktop, npm or a source checkout), the runtime, your IP address and a device fingerprint (a stable hashed machine id), and the machine's rough shape — the number of CPU cores, the amount of RAM, the locale, the shell, the terminal, and the Claude Code and Codex versions — with the daily "active" also carrying how many sessions, subagents and projects you have open. The fingerprint is a one-way hash of stable machine traits, never those traits in the clear; everything else is coarse counts and short tokens — no path, no project name, no free text. These reports are no longer anonymous: the install id is random, but the fingerprint follows this machine and the server records the IP the report arrives from. What still never leaves is your sessions, prompts, files, project names and paths. Switching it off deletes what was sent, and AGENTS_DECK_NO_REPORTS=1 keeps it off from the first start. While the deck runs it also sends a tiny heartbeat every few minutes, the install id and nothing else, so the count of decks running right now can be seen. Send feedback… under Appearance sends what you write to the same place, where the people who make ccdeck read it and may open a public issue from it, never with the contact you gave. AGENTS_DECK_NO_INSTALL=1 turns off everything but the quota reads; AGENTS_DECK_NO_DOWNLOAD=1 is the narrower version — no uv binary is fetched, the managed installs stay.

Browser Watch

The panel behind the eye icon answers one question: did something drive your browser while you were away? An agent with browser control opens pages through an API, and Chromium records that — visits.transition says FROM_API rather than LINK or TYPED. So a run of those, in a stretch when you were not touching the machine, is a finding worth showing.

To answer it the deck reads the browsers' own history. That means, on each poll: it finds every Chromium profile (Chrome, Brave, Edge, Vivaldi), copies each profile's History database to a temp file — a live database cannot be opened safely — queries the visits newer than the moment this deck started, and deletes the copy. Nothing leaves the machine, and the copy exists for as long as one query takes.

The switch decides whether any of that happens in the background, and it starts on. With Browser Watch off, the deck's five-minute poll reads no browser at all; with it on, the poll reads, keeps what it finds in ~/.claude/agent-dag/browser-watch/state.json, writes a line per finding to watch.log beside it, and performs whatever reaction you chose. Opening the panel always reads live, on either setting — that is you looking, and it is the only way the panel can tell you what is there.

Only visits after this deck started are ever considered. Your existing history is not swept, not archived and not shown.

Step by step: Detect browser automation while you were away

Accounts

The Accounts panel reads the store claude-swap keeps, and can drive it.

The deck installs that package itself, so it installs it bounded: claude-swap~=0.26, which is >= 0.26, == 0.*, and when PyPI can be reached the exact version it resolved rather than the range. What cswap --version reports afterwards has to be that version, or the deck says so and leaves the panel dark rather than driving a copy it cannot account for — this is the tool that holds your Claude logins. The daily upgrade is bounded by the same specifier, and what it left behind is written to ~/.agents-deck/cswap-upgrade.json.

+ → Sign in runs claude auth login, shows you the link, and hands the result to cswap add. The sign-in usually completes by itself once you approve it in the browser; only when the page shows a code — as it does on a deck opened from another machine — is there one to paste back into the dialog. The account you were using stays active — signing in replaces the live credentials, so the previous one is switched back the moment the new one is recorded. A pasted code goes straight into the CLI's stdin on this machine; it is never stored, logged, or sent anywhere else.

share on an account produces a ccdeck2:… blob to paste into another deck's + → Paste a share.

↗ in the panel header does the same for a set of them, which is what moving your accounts from home to work actually is. Tick the ones to send — all of them to start — and one blob carries the set. The dialog counts sign-in tokens rather than rows, and an account that cannot be exported is named rather than quietly dropped, so the number on the copy button is always the number in the blob.

On macOS, the login itself lives in the Keychain. If ccdeck is running from SSH, a LaunchAgent, or another session that cannot open that Keychain, the account may still be valid while this process cannot read or share it. ccdeck does not call that login expired: the account's row says Keychain unreadable, and Local network says cannot share here. It refuses clipboard and LAN exports from it, and tells you to start ccdeck from a Terminal window on the Mac itself. Once claude-swap can read the Keychain again, the account becomes shareable normally.

An import adds what is missing and leaves a working account exactly as it is. The one it does rewrite unasked is a slot claude-swap has itself quarantined as refresh-token-dead, which is what heals a machine whose login stopped working. The result names every account in the paste — imported, already here, dead token replaced, or not imported — and an account it skipped can be overwritten one at a time with update anyway.

Warning

A share carries the live login of every account in it, in the clear — claude-swap's export format has no encryption, and five ticked boxes is five passwords on your clipboard. It expires ten minutes after it is made and imports refuse it after that. While it lives, treat it as those passwords: anything that can read your clipboard can read the accounts.

Rename, Move to slot… and Remove are on the same row menu. Removal takes two clicks and cannot be undone.

Step by step: Switch between multiple Claude Code accounts · Copy your Claude Code login to another machine

Local network

A Claude login expires on the machine that has not used it for a while, and stays alive on the one that has. With two or three machines that is a claude login a week, each time on the machine you are not sitting at. Local network is the last section of the Claude accounts panel, and it is that chore done for you: decks on one network find each other, pair, and a login that has expired on one is copied back from another that still has it.

Four pictures, which are also the guide the section opens from See how it works:

A login can expire on one machine while it still works on another. Both decks are on. They find each other and ask to pair.
1. A login can expire on one machine while it still works on another. 2. Both decks are on. They find each other and ask to pair.
Tick which logins this machine may hand out. Nothing is shared until you do. An expired login is copied from a paired machine within a minute.
3. Tick which logins this machine may hand out. Nothing is shared until you do. 4. An expired login is copied from a paired machine within a minute.

On each machine, in order: open the Claude accounts panel (A), open Local network's settings (the sliders button), and tick the logins this machine may hand out. The section is on unless somebody switched it off. The two decks find each other over a UDP beacon on port 45317 and ask each other to pair; press accept where it is asked, and from then on every paired deck is asked once a minute for anything this deck's expired logins need.

When they do not find each other — a VPN, a guest network, two subnets — + at the top of the section reaches a deck by address, or with an invite the other deck minted. An invite is the route when the machine that cannot be seen is this one.

What is shared, and with whom. Nothing until you tick a login, and then only that login, and only with decks somebody at this machine accepted — the deck asks the machines it finds, and a machine that is asked waits for somebody there to press accept — unless its Say yes to every deck that asks switch, in the same dialog, is on. A paired deck can fill an expired slot of this deck's and nothing else: it cannot overwrite a login that still works here. What crosses the wire is the same live credential a share carries, sealed to the deck it is addressed to, so treat the pairing decision as the moment that matters. Unpairing stops future rounds; a login already copied stays where it went.

On a Mac, cannot share here is different from expired here. It means this ccdeck process cannot read the Keychain copy, so that machine is not used as a credential source and another deck does not keep trying to “repair” the same healthy slot every minute. A LAN import is checked after it lands too; if the receiving Mac still cannot read the Keychain, the round reports that problem instead of claiming the login was repaired.

Step by step: Repair expired Claude Code logins across machines

Options

ccdeck [options]

  -p, --port <number>      Preferred port  (default: 4317; fallback: random 4318–4400)
      --no-open            Don't open the browser automatically
      --foreground         Hold the terminal, the way every version before 3.20
                           did (Ctrl+C stops the deck again)
      --new                Replace the running deck with a fresh one
      --stop               Stop the running deck
                           (--port <n> stops only the one on that port)
      --status             What is running on this machine, and on which ports
      --logs               What a backgrounded deck wrote where a terminal would
                           have shown it
      --workspace <path>   Only capture sessions whose cwd is inside <path>
      --scope              Restrict to the current working directory
      --all                Capture every session on this machine  (default;
                           accepted and ignored — it is what a bare run does)
      --history <path>     Override the events log file
                           (default: this platform's log directory —
                           ~/Library/Logs/ccdeck on macOS, %LOCALAPPDATA%\ccdeck\Log
                           on Windows, $XDG_STATE_HOME/ccdeck on Linux)
      --no-persist         RAM-only mode — don't write or replay the log
      --install            Put the deck on your PATH and start it at login —
                           what an `npx` run needs to survive a reboot
      --install-service    Start the deck when you log in. Set up on first run;
                           this is only for putting it back
      --uninstall-service  Stop starting at login (`--uninstall` does this too)
      --at-login           What the login item starts the deck with: beside a
                           deck that is already running, it leaves that one be
      --codex              Force Codex capture even if ~/.codex/ is missing
      --no-codex           Skip Codex capture (Claude only)
      --claude             Force Claude capture even if Claude Code wasn't found
      --no-claude          Skip Claude entirely — no hooks, no claude-swap,
                           no Accounts panel (Codex only)
      --uninstall          Remove ccdeck's hooks from settings files, and name
                           the files that still hold this deck's private key
      --purge              With --uninstall: delete those files too
  -h, --help               Show this help
  -v, --version            Print the version and exit

Anything else on the command line is reported as an unknown option and then ignored — the deck still starts, so a typo costs you a warning rather than a dashboard.

--foreground is there for anything that was relying on the old behaviour — a wrapper script, a CI step, a supervisor of your own that starts the deck and waits on it. On the first run after upgrading, the deck from the older version is stopped and the new one takes its place; the boot report says so.

ccdeck runs in the background. The boot report prints in your terminal exactly as it always has — the hooks, the port, the URL — and then the prompt comes back and the deck stays up: closing the window does not take it with you any more, because it is in its own process group and the terminal's hangup never reaches it. ccdeck --stop ends it, ccdeck --status says what is running, and ccdeck --logs shows what it wrote after you stopped watching. Ctrl+C while the boot is still printing cancels the start, which is the one thing it still means.

One Windows exception, and it is ssh's rather than the deck's: OpenSSH puts a session's processes in a job object that it kills when the session ends, and nothing started from inside one survives it. A deck started from an ordinary Windows terminal, or by the login task below, keeps running — measured on a real machine, from a second SSH session, still serving.

It also starts when you log in, so a reboot does not cost you the morning's events. That is set up once, on the first run, and said out loud when it happens; ccdeck --uninstall-service undoes it and ccdeck --uninstall takes it with the hooks. A launchd agent on macOS, a systemd --user unit on Linux, a Task Scheduler logon task on Windows — none of them carrying a restart policy of its own, because the one that decides when a crashed deck stops coming back lives in ccdeck and two policies over one process is how a stop becomes a suggestion. An npx run never installs one: it would name a path inside npm's cache, which npm deletes without warning. On Linux, systemd --user is torn down at logout unless loginctl enable-linger is on for your account — the install says so rather than changing that for you.

If the deck falls over on its own, it comes back — five times in ten minutes, with the wait doubling each time, and then it stops and says so in the log rather than spinning on a machine nobody is watching. A clean --stop is never answered with a restart, and neither is a deck that failed to start in the first place: retrying a port the OS will not give you just prints the same refusal six times.

Typing plain ccdeck beside a deck that is already running opens that deck's tab rather than building a second one. It used to build the second one: port 4317 was busy, so the new deck took a random port out of 4318–4400 and stood there beside a perfectly healthy first one, and neither mentioned the other. The port fallback is still there — 4317 is also the standard OTLP collector port, and Windows reserves whole TCP ranges for Hyper-V, WSL2 and Docker — but it now runs only when the thing holding the port is not one of your decks. The deck on the port has to prove it is yours, with the same token handshake the hooks use, before its tab is opened.

There is only ever one. A start that asks for a different deck — a port, a workspace, a log, either --codex or --claude pair — or that is a newer version than the deck running, stops that deck and takes its place; --new does so unconditionally. Two starts at the same moment, such as the login item and a terminal opened at login, wait for each other instead of both starting. The rule holds per Claude config directory: pointing CLAUDE_CONFIG_DIR somewhere else is a second deck with its own identity, as it always was, and the login item is given the same directory as the shell that installed it.

ccdeck looks for each CLI before it does anything on that CLI's behalf. Claude Code counts as present when its binary is on PATH (or in one of the places its installers put it), or when its config dir carries traces of having been used; Codex counts as present when ~/.codex/ exists. On a machine with only one of them, the other one's hooks, installs and panels are skipped rather than shown empty — the boot banner says which way it went, and --claude / --codex override it if the guess is wrong.

--workspace is a filter this deck applies to itself, not a claim on the sessions it matches: every running deck whose workspace contains a session's directory draws that session. That is not a way to keep a machine-wide deck and one scoped to ~/proj side by side: in one Claude config directory the second start stops the first and takes its place, as above. The filter reads the same way on all three paths a session can reach the canvas by — Claude Code's hook, Codex's rollout files, and the boot replay of the events log — and the events log still gets exactly one copy of each event, whichever decks are up. A relative path is resolved against the directory you start the deck in, and once, so every path scopes to the same tree. The log is machine-wide by default and shared by every deck on the box, so a scoped deck replays only the part of it that is inside its own workspace: it comes up showing what it will go on to capture, and nothing else.

That one events log is also the reason Clear is not quite the per-deck button it looks like. The decks elect a single writer for each log file, and only that deck may empty it: Clear on any other deck wipes its own canvas and leaves the file to the deck that writes it. The confirmation says which of the two you are about to do, and how many decks share the log when it is yours to empty — so --history or --no-persist gives a deck a log of its own if you want Clear to answer to nobody else.

Step by step: Show one project's Claude Code and Codex sessions

Environment:

Variable Effect
AGENT_DAG_PORT Default port, same as -p
CODEX_HOME Override ~/.codex
AGENTS_DECK_NO_INSTALL=1 Never install or update claude-swap / ccusage, never ask npm about releases, never read the status pages, and never send reports or feedback
AGENTS_DECK_NO_DOWNLOAD=1 Never download the uv binary, but keep the managed installs
AGENTS_DECK_NO_UPDATE_CHECK=1 Don't ask npm about releases, but keep everything else
AGENTS_DECK_NO_STATUS=1 Don't read Anthropic's and OpenAI's status pages, so no incident chip appears
AGENTS_DECK_NO_FRESHEN=1 Never nudge claude-swap to collect usage early
AGENTS_DECK_NO_NOTIFY=1 Never raise a desktop notification: not when a session blocks and no page is open, and not when Browser Watch finds something
AGENTS_DECK_NO_LAN=1 Keep Local network off, whatever its switch in the panel says
AGENTS_DECK_NO_REPORTS=1 Never send usage reports, whatever the switch in Appearance says
AGENTS_DECK_CSWAP Full path to cswap, when it lives somewhere unusual
AGENTS_DECK_CLAUDE Full path to the claude CLI
AGENTS_DECK_CCUSAGE Full path to your own ccusage, used ahead of everything else
CLAUDE_SWAP_BACKUP Override the claude-swap store root the Accounts panel reads
CCDECK_HOME Put everything the deck writes — its state and its log — under this directory instead of the platform default. Wins over every rule below it
CLAUDE_CONFIG_DIR Override ~/.claude — the hook entry, the event log and everything else the deck writes move with it
AGENTS_DECK_LHM_PORT Port of a running LibreHardwareMonitor web server, when it is not 8085 (Windows temperatures)

Usage history is read with ccusage, and the deck takes the first of these that answers: AGENTS_DECK_CCUSAGE if you set it, then the copy it installed for itself under ~/.agents-deck/ccusage, then a ccusage on your PATH, then npx -y ccusage@latest. So installing ccusage yourself is enough — the deck will not fetch a second copy, and it works under AGENTS_DECK_NO_INSTALL=1, which is the combination that variable is for. When something fails, the modal names which of those four it was.

Step by step: See Claude Code and Codex cost and quota left

Being told to restart after an upgrade is local only — no network involved — and cannot be turned off, because a deck running superseded code is a bug you cannot see any other way.

Uninstall

npx ccdeck --uninstall

Removes every hook entry ccdeck injected from ~/.claude/settings.json, and ~/.codex/hooks.json if present — and the login item, if this machine had one — and then stops every deck still running, with a line for each. Those two exceptions to the narrowness below are deliberate: a login item left behind would keep starting a deck whose hooks had just been removed, and a deck left running would put them back itself on its next update. A deck that cannot be stopped makes the command exit 1.

It removes the hook entries and nothing else. The forwarder script (~/.claude/agent-dag/hook.js), the discovery directory around it, the events log, the deck's own state, and the tools ccdeck installed for you — claude-swap, ccusage, and a uv binary if it had to fetch one — are all left in place, and each has its own uninstaller.

Three directories hold ccdeck's own files, and the second one matters most: ~/.claude/agent-dag/ (the forwarder and the discovery directory), the platform's data directory, and the platform's log directory.

macOS Windows Linux
data — prefs.json ~/Library/Application Support/ccdeck %LOCALAPPDATA%\ccdeck\Data $XDG_DATA_HOME/ccdeck
log — events.jsonl ~/Library/Logs/ccdeck %LOCALAPPDATA%\ccdeck\Log $XDG_STATE_HOME/ccdeck

prefs.json is the one to delete deliberately: it holds this deck's Local network private key, the one every deck you paired with has pinned. Leaving it behind leaves that identity on the machine. (~/.agents-deck/ holds the managed tools rather than the deck's own state, and goes when you remove them.)

uv tool uninstall claude-swap (or pipx uninstall claude-swap) removes the account switcher.

Deleting the key

You do not have to work the table above out for yourself, and on an upgraded machine it is not the whole answer: prefs.json moved to the data directory by being copied, so a second copy is still in ~/.claude/agent-dag/ and deleting one of the two is not deleting the key.

--uninstall prints every path that still holds one, resolved for your machine. Adding --purge deletes them:

npx ccdeck --uninstall --purge

That takes your ccdeck settings — pairings, aliases, which accounts this deck offered — with the key, which is the point: after an uninstall there is nothing left for them to configure.

Step by step: Uninstall ccdeck and remove its Claude Code hook

Updating

The deck checks npm for a newer release at most once an hour, plus once when it starts — a ~20-byte GET to registry.npmjs.org, asking about the package this copy would actually install (a deck started with npx ccdeck asks about ccdeck). When that names a version it has not seen before, one more request confirms the version is really there: npm moves the dist-tag before the version itself has propagated, and a banner shown inside that window ends in ETARGET instead of an upgrade. So a check is one request, or two when there is something new to confirm — and a version that is tagged but not yet installable is looked at again in five minutes rather than in an hour. Click the version chip in the topbar to ask immediately.

What the banner offers depends on how this copy was installed:

Installed as Offer
global npm install Update now — runs npm install -g on the package you installed, then restarts once nothing is running
npx Update & restart — re-runs the spec through npx, which fetches a fresh copy and takes over the same port
git checkout the command, because your working copy leads npm: git pull && npm run build
directory not writable the command — a root-owned prefix is declined up front rather than failing inside npm
AGENTS_DECK_NO_INSTALL=1 the command only; you asked for no installs

Nothing is ever installed unless you click, the argument vector is fixed in the server rather than taken from the request, and the command is always on screen — button or no button. If npm fails, the banner shows npm's own last line.

Restarting

ccdeck runs as a two-process pair: a supervisor that owns nothing but the lifecycle, and the deck itself. When newer code is found, the deck exits with code 75 and the supervisor brings it back on the port it actually bound, which is not always the one it asked for. Both live in their own process group since 3.20, so stdout goes to deck.log rather than to the terminal you started from — ccdeck --logs reads it back. The supervisor also puts the deck back after a crash, five times in ten minutes with the wait doubling, and then stops and says why rather than spinning.

It restarts on its own only after 30 seconds with nothing running, because hook events are fire-and-forget and anything fired during the gap is lost. The toggle in the banner turns that off; the preference is per-browser. Under --no-persist a restart is refused outright — with no event log there is nothing to replay, and the canvas would be gone.

Step by step: Update ccdeck to the latest version

Design

  • Node = agent (root session or subagent)
  • Edge = parent → child (spawn); tool calls sit beside their agent's node
  • In-flight animates; settled dims
  • Double-click a node for the full story

Names

ccdeck is the name — of this repo, of the npm package and of the command.

The deck used to be published as agents-deck and agent-dag as well. Neither is published any more; both stay on npm at their last version, 3.22.8. An install under one of them keeps running, but it may never update itself again, and the only command it puts on your PATH is the old one. To move to ccdeck:

npm rm -g agents-deck agent-dag
npm i -g ccdeck
ccdeck --install-service   # only if the deck started when you logged in

The repository was previously named agents-deck; the old URL redirects here, so existing clones, links and bookmarks keep working.

Questions people ask

Does it work with Codex, or only Claude Code? Both, on one canvas. Claude Code arrives through a hook, Codex through its rollout log, and the model chip tells them apart. Subagent cards are Claude Code only: a Codex session is one node with its tool calls. So is the blocked on you queue, because a rollout log is a record of what happened and the queue needs to know what is happening now. Codex does report it — its app-server pushes a thread status carrying waitingOnApproval — and reading that is open work, not a wall. Step by step: Set up ccdeck for Codex CLI

Does anything leave my machine? Your sessions, never. The deck binds 127.0.0.1, so your sessions, prompts, files, project names and paths stay put. It does send usage reports — no longer anonymous: version, system, your IP address and a device fingerprint, and errors — unless you switch them off under Appearance. Beyond those, the outbound requests are a ~20-byte version check to the npm registry at most once an hour, and whatever the usage panels ask Anthropic and OpenAI for about your own quota. What it touches lists all of it.

Can it interfere with what my agent does? No. The hook it installs is a one-way forwarder: it POSTs the event, exits 0, and writes nothing to stdout — the two channels Claude Code's hook protocol gives a hook for allowing, denying or rewriting a tool call. It uses neither, so it has no way to answer at all, and a test pins both halves.

Does it need an account, an API key or a config file? None of the three. npx ccdeck and it runs.

macOS, Linux, Windows? All three, and each release is tested on all three.

What is the difference between ccdeck, agents-deck and agent-dag? One deck, three names on npm. ccdeck is the one to use; the other two are the names it shipped under before, kept working so nobody's command breaks. See Names.

Do I have to keep a terminal open? No. Since 3.20 the deck runs in the background, survives the terminal closing, and starts again when you log in. ccdeck --stop ends it.

Community

License

ccdeck is licensed under the GNU Affero General Public License v3.0 only (AGPL-3.0-only).

The current release, as distributed by the project, is offered under the AGPL in full: the complete current ccdeck-owned codebase, including the code implementing features that existed before the relicensing.

Copyright © 2026 Bargan Constantin.

See LICENSE for the full license text and LICENSING.md for licensing history and additional details.

ccdeck bundles third-party code under permissive licences (MIT, ISC, BSD-3-Clause). Those licences are theirs, not ccdeck's, and their notices are preserved in THIRD_PARTY_NOTICES.md.

About

Local dashboard for Claude Code and Codex CLI: which Claude Code session needs input, every subagent and tool call live, cost and quota, several Claude accounts. npx ccdeck or the desktop app. Free, open source.

Topics

Resources

Stars

10 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages