Skip to content

tangshunpu/ccfleet

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ccfleet

Aggregate ccusage across every machine where you run Claude Code — one merged view of tokens, cost, and active billing blocks, over SSH.

┌────────────┬──────────────────────┬─────────┬─────────┬───────────┬─────────────┬─────────────┬─────────┬──────────────────────┐
│ Date       │ Models               │ Input   │ Output  │ Cache+    │ CacheR      │ Total       │ Cost    │ Devices              │
├────────────┼──────────────────────┼─────────┼─────────┼───────────┼─────────────┼─────────────┼─────────┼──────────────────────┤
│ 2026-04-10 │ haiku-4-5, opus-4-6  │ 11,825  │ 558,993 │ 2,791,514 │ 113,085,015 │ 116,447,347 │ $87.15  │ local, zju-pro6000   │
│ 2026-04-11 │ opus-4-6, sonnet-4-6 │ 2,074   │ 487,914 │ 2,324,279 │  55,606,452 │  58,420,719 │ $54.47  │ local, zju-pro6000   │
│ TOTAL      │ …                    │ 372,737 │ …       │ …         │ …           │ …           │ $308.61 │ local, zju-pro6000   │
└────────────┴──────────────────────┴─────────┴─────────┴───────────┴─────────────┴─────────────┴─────────┴──────────────────────┘

Think of it as "ccusage, but across all your dev boxes."

Why

If you run Claude Code on more than one machine — laptop, workstation, GPU boxes, remote dev hosts — ccusage only sees one at a time. You end up SSH-ing around, running ccusage daily on each, and adding the numbers in your head. ccfleet does that for you: one command, one merged table, no data ever leaves the hosts unencrypted.

Install

npm install -g ccfleet

Or run ad-hoc without installing:

npx ccfleet daily

30-second quickstart

No config file to write. ccfleet builds the config for you.

Step 1 — just your laptop

npm install -g ccfleet
ccfleet devices add local --local
ccfleet daily

Three commands. You'll see a merged table of every day you've used Claude Code on this machine.

Step 2 — add a remote machine

This step only applies if you already use ssh to connect to another computer (a server, a GPU box, a home desktop, etc). If you only run Claude Code on this one machine, Step 1 is all you need — skip to Step 3.

🍎 macOS users: install GNU rsync first.

brew install rsync

Apple ships openrsync 2.6.9, which crashes with renameat: No such file or directory whenever Claude Code is actively writing on the remote while ccfleet is mirroring it. GNU rsync 3.x (~10 seconds to install via Homebrew) fixes this and ccfleet picks it up automatically. Skip this only if you plan to use --cache-mode direct everywhere — but cache mode is much faster and usually what you want.

What's an "ssh alias"? When you type a command like ssh gpu-box and it connects somewhere, gpu-box is an alias defined in your ~/.ssh/config file. ccfleet reuses whatever aliases you already have — it does not ask for passwords and does not set up ssh for you.

Option A — you already ssh into it with a short name.

Suppose you normally connect with ssh gpu-box. Then:

# Use YOUR alias here, not "gpu-box" — that's just an example.
ccfleet devices add gpu --ssh-alias gpu-box --cache-mode cache
ccfleet devices test

gpu is the label ccfleet shows in tables; gpu-box is the actual ssh alias it connects with. You can use the same word for both if you want.

⚠️ If you see unknown host or Could not resolve hostname, the alias you passed does not exist in your ~/.ssh/config. Try ssh gpu-box yourself first — if that fails from your terminal, ccfleet can't connect either. Fix your ~/.ssh/config, or use Option B below.

Option B — you don't have an ssh alias, just a hostname + user.

ccfleet devices add gpu \
  --host 192.168.1.42 \
  --user your_username \
  --port 22 \
  --identity ~/.ssh/id_ed25519 \
  --cache-mode cache
ccfleet devices test

Replace 192.168.1.42, your_username, and the identity-file path with whatever you'd actually type into ssh. --port 22 and --identity … are optional — omit them if you don't need them.

Once devices test says ok:

ccfleet daily            # merged across laptop + gpu

Step 3 — explore

ccfleet daily --since 20260401             # just April
ccfleet blocks --active                    # current 5-hour billing block
ccfleet session --merge                    # grand total across everything
ccfleet daily --group-by device            # break down per machine
ccfleet daily --currency CNY               # show cost in ¥ instead of $
ccfleet devices list                       # what's configured

Fully automatic

ccfleet devices add writes ~/.config/ccfleet/config.toml for you with sensible defaults. You never need to edit TOML. If you want to, the schema is documented in Configuration reference and an annotated example lives in examples/config.toml.

Prerequisites

  • Node 20+ on the machine running ccfleet.
  • On each remote, one of:
    • Direct mode: ccusage reachable (default is npx -y ccusage@latest, override with ccusage_cmd).
    • Cache mode: rsync reachable, plus ccusage installed locally (once, on the ccfleet machine) — it runs against the mirrored JSONL.
  • macOS users, please install GNU rsync: brew install rsync. Apple's bundled openrsync 2.6.9 has known rename-race bugs when the remote ~/.claude/projects is being actively written by Claude Code, and you will hit them.

Commands

Command What it does
ccfleet daily Per-day token/cost table merged across devices.
ccfleet monthly Per-month rollup.
ccfleet session Per-session rollup.
ccfleet blocks ccusage's 5-hour billing-block view. --active shows the current block.
ccfleet sync Force an rsync for all cache-mode devices. --status shows last-sync-at + bytes.
ccfleet devices list|test|add|remove Device management.
ccfleet run <device> -- <args…> Escape hatch — run ccusage <args…> on a specific device, raw stdout.

All query commands support:

Flag
--since 20260401 --until 20260411 Date range (forwarded to ccusage).
--timezone America/Los_Angeles Time-zone for the day boundary.
--group-by device / --group-by project Slice the merged table.
--merge Single grand-total row.
--device <name> (repeatable) Limit to specific hosts.
--breakdown Per-model rows.
--json Machine-readable output.
--refresh Force an rsync before reading.
--direct Bypass the cache for this query.
--currency USD|CNY|SGD|EUR|… Show cost in any ISO 4217 currency. Live ECB rates, refreshed once per day and cached locally. --json output always stays in USD.

How it works

Every device is queried in one of two modes:

  • Direct modessh <host> -- npx -y ccusage@latest daily --json, parse the response. Zero local state. Needs ccusage reachable on the remote.
  • Cache modersync the remote ~/.claude/projects to ~/.cache/ccfleet/<device>/projects, then run ccusage locally against the mirror with CLAUDE_CONFIG_DIR pointed at the cache dir. Warm queries are effectively free because ccusage is reading local JSONL, and the same mirror answers daily, monthly, session, and blocks without re-syncing.

Cache mode auto-refreshes when the mirror is older than cache_ttl_minutes (default 15). Use --refresh to force a sync mid-session, --direct to skip the cache entirely. local devices always run ccusage locally, uncached.

All device queries run in parallel, bounded by defaults.parallel. A failure on one device never blocks the others — it surfaces as a per-device error row with an actionable hint ("unknown host", "host key verification failed", "remote missing ccusage", etc).

Configuration reference

[defaults]
parallel          = 4              # max concurrent device queries
timeout_seconds   = 30             # per-device timeout
cache_mode        = "direct"       # "direct" (default) | "cache"
cache_ttl_minutes = 15             # auto-refresh threshold for cache mode
claude_dir        = "~/.claude"    # remote Claude data dir (override per-device)
ssh_multiplex     = true           # ControlMaster/ControlPersist for speed
currency          = "USD"          # 3-letter ISO (USD|CNY|SGD|EUR|GBP|JPY|…); `--currency` overrides

[[devices]]
name        = "work"               # required, unique
# Pick one of three ways to address the host:
ssh_alias   = "work"               # (a) use ~/.ssh/config — recommended
# host      = "work.example.com"   # (b) raw hostname + optional:
# user      = "alice"
# port      = 22
# identity  = "~/.ssh/id_ed25519"
# local     = true                 # (c) mark as local, no ssh, no cache
cache_mode  = "cache"              # override defaults per-device
claude_dir  = "/home/alice/.claude"
ccusage_cmd = "/home/alice/.bun/bin/ccusage"  # replace the default npx invocation

Or manage devices from the CLI:

ccfleet devices add work --ssh-alias work --cache-mode cache
ccfleet devices add gpu  --host gpu.example.com --user alice --identity ~/.ssh/id_ed25519
ccfleet devices test
ccfleet devices remove gpu

Troubleshooting

unknown host (check ~/.ssh/config) / Could not resolve hostname … — the --ssh-alias you passed isn't defined in your ~/.ssh/config, or it points at a hostname DNS can't resolve. Quick check: run ssh <that-alias> yourself in a new terminal. If that fails, ccfleet can't connect either — it's a plain ssh problem, not a ccfleet problem. Fix your ssh config, or re-add the device with --host <ip-or-hostname> / --user … flags instead of --ssh-alias.

command not found: ccusage — the remote doesn't have ccusage. Install it (npm i -g ccusage), or set ccusage_cmd to a pinned path. Direct mode defaults to npx -y ccusage@latest, which requires node + npm on the remote.

rsync failed (exit 10/23/24): renameat: No such file or directory — you're on macOS's openrsync 2.6.9. brew install rsync to get GNU rsync 3.x, which is first on PATH after install.

permission denied (publickey) — run ssh <alias> manually once to confirm your key is loaded and the remote accepts it. ccfleet uses BatchMode=yes, so it can't prompt for passwords interactively.

host key verification failedssh <alias> once to accept the host key, then retry.

Numbers don't match what ccusage shows locallyccfleet run <device> -- daily --since YYYYMMDD runs ccusage on that device raw, so you can diff against the merged view and find the drift.

Want to see what ccfleet is actually running?DEBUG=1 ccfleet daily prints stack traces on errors. For the literal argv, ccfleet run <device> -- --help is the passthrough.

Development

git clone https://github.com/tangshunpu/ccfleet
cd ccfleet
npm install
npm run build        # produces dist/cli.js
npm link             # puts `ccfleet` on your PATH, pointing at dist/cli.js

Architecture notes and future work live in PLAN.md.

License

MIT

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors