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.
npx ccdeckOr the desktop app, with the waiting count in your menu bar: macOS · Windows · Linux — every download
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
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.
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.
| 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
npx ccdeckOpens 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
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
- 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
macmonitself for the temperature rows; see below.
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 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 — answersNot supportedeven to an administratorWin32_TemperatureProbeexists but every field reads32768, 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.
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.
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.
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
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
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:
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
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.
npx ccdeck --uninstallRemoves 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.
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 --purgeThat 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
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.
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
- 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
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 inThe repository was previously named agents-deck; the old URL redirects here,
so existing clones, links and bookmarks keep working.
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.
- 💬 Questions & ideas — GitHub Discussions
- 🐛 Bugs & feature requests — GitHub Issues
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.
