Skip to main content
Continuum’s analytics layer is a Swift re-implementation of the ideas behind ccusage by @ryoppippi. It parses the same kinds of local agent logs, applies the same deduplication logic for Claude, and prices tokens with the same LiteLLM snapshot. Parsing and pricing run on your Mac, against files the agent CLIs already wrote there. No usage data leaves the device unless you explicitly turn on Mac → iPhone sync or link the Mac to an organization. The one column that arrives from elsewhere is Continuum-hosted inference, which has no local log to read. ccusage daily is the ground truth. If Continuum’s numbers diverge from ccusage, ccusage is correct.

What gets parsed

The loader walks each provider’s own on-disk logs. A provider whose store isn’t there — because you don’t use it, or its CLI isn’t installed — is skipped cleanly rather than treated as an error, and providers you’ve turned off in Settings → Providers get no column at all.
The parsed-source list grows as Continuum adds providers. Every column except Continuum-hosted comes from a log the agent CLI wrote on your own machine — Continuum reads those files, it does not instrument the CLIs.

Deduplication

Claude’s JSONL frequently repeats the same assistant turn across files when a session resumes. Continuum collapses those on the message’s id — with the top-level requestId as an optional qualifier when present. (Recent Claude Code builds dropped requestId, so requiring both would double-count resumed history; keying on message.id matches ccusage’s outcome.) Cross-file duplicates are caught at merge time, not just within a file. Codex carries a cumulative total_token_usage snapshot rather than per-turn counts, so Continuum computes per-turn deltas by subtracting the previous cumulative. Zero-delta heartbeats are dropped, and a non-monotonic drop (a session reset mid-file) is treated as a fresh baseline rather than a negative delta. Codex records carry no dedup key — the delta math is the dedup.

Multi-account aggregation

You can add a second account on every provider (see multi-account), but only Claude and Codex contribute extra history to this loader. Those two write their logs under a per-instance config directory ($CLAUDE_CONFIG_DIR/projects, $CODEX_HOME/sessions), so the loader merges each account’s root into the Claude and Codex passes alongside the default account and the history rolls into the same aggregate totals. The other providers isolate accounts by other means — an API key at spawn, an isolated HOME, an XDG_DATA_HOME — which routes and bills the session correctly but produces no second on-disk log root to walk. Their spend still lands in analytics; it just isn’t split by account. The extra roots are re-read on every refresh, so an account you add in Settings joins the totals on the next refresh without rebuilding the store or relaunching the app.
Totals are aggregated across accounts in this version — there is no per-account spend breakdown yet. See Providers for the multi-account setup flow.

Time windows

Windows are calendar-day aligned in your local timezone, matching ccusage’s daily default. UTC bucketing was evaluated and rejected — it diverges from ccusage near midnight. The tokens-by-model breakdown on the Usage tab is windowed slightly differently (it also exposes a trailing-90d view), but the dollar totals and per-repo split use the four windows above.

Repo identity

Continuum walks up the directory tree from each record’s working directory to find the canonical git repository, so every branch and worktree of one repo rolls up to a single row. It handles:
  1. Regular git directory — the directory containing .git is the repo root.
  2. Worktree pointer file — reads the gitdir: line (resolving a relative pointer against the .git file’s own parent) and buckets under the main worktree.
  3. Conductor workspace (<…>/conductor/workspaces/<repo>/<branch>) — introspects a live sibling branch’s .git pointer to discover the underlying main repo, falling back to a stable per-repo bucket.
  4. Claude Code worktree (<repo>/.claude/worktrees/<branch>) — collapses every worktree of a repo into one bucket.
  5. Sole-git-child descent — if a directory isn’t itself a repo but contains exactly one child that is, it collapses to that child (covers launching an agent from a parent folder).
A cwd that resolves to none of these collapses to a shared “Other” row, and a record with no cwd at all collapses to “(unknown)”. This keeps random UUID folders, home directories, and abandoned workspace IDs out of the repo list.

Pricing

Token costs are calculated from a bundled LiteLLM pricing snapshot, so analytics work offline and totals are reproducible. Tiered pricing — rates that change above a prompt-size threshold — is applied per model. Models that aren’t in the snapshot are tracked under “unpriced model tokens” so their token counts still appear even when no dollar figure can be attached. A second bundled file of manual overrides is merged on top of the snapshot and wins per key. It exists for rates LiteLLM hasn’t shipped yet — newly released frontier models, xAI and Z.ai GLM ids, and the models Continuum hosts itself — and doubles as an audit trail: each manual entry carries a note recording the source of the rate and the date it was verified. Contributors can refresh the snapshot with tools/refresh-pricing.sh, which fetches the current LiteLLM table, filters it to the model families Continuum tracks (claude-*, gpt-*, o[0-9]*, chatgpt-*, gemini-*, gemma-*, grok-*, xai/*), then re-applies the manual overrides on top.

Cache

Parsed results cache to the app’s container, falling back to:
The cache is schema-versioned. When you update Continuum and the schema changes, old caches are treated as empty and re-parse on first load — nothing stale survives an upgrade. Entries are keyed by file path and hold that file’s per-day, per-repo, and per-model token totals along with its dedup keys. A file is reused from cache only when its mtime and size match. The newest-mtime file per provider always bypasses the cache — the active session may still be appending mid-walk. Cold reparses checkpoint after each provider’s corpus, so an interrupted re-parse (quit, sleep, crash) resumes instead of restarting from zero.

Mac → iPhone sync

The Mac writes a usage snapshot to iCloud key-value storage under the key cloud.analytics.v1. iPhone picks it up when iCloud reports the change and renders the same totals, chart, and per-repo split. Nothing leaves the device except through this key.
Personal-team Apple Developer accounts cannot sign the iCloud entitlement. On those builds, the iPhone analytics surface falls back to “waiting for Mac sync.” Upgrading to a paid Apple Developer Program membership resolves it.
If this Mac is linked to an organization, detailed per-repo analytics are never mirrored to personal iCloud — the snapshot is suppressed and any snapshot written before the link is cleared, so an employee’s repo names don’t end up in personal iCloud while their usage reports to the org. See Privacy and Organization.

Loader performance

The loader walks every provider directory in parallel, parsing files at background priority so a multi-gigabyte cold reparse doesn’t starve the rest of the app. Overlapping refresh requests coalesce — a second caller awaits the first’s result instead of starting a parallel walk — and a lightweight mtime probe lets the store skip a refresh entirely when nothing on disk changed since the last load.
  • Usage — the live quota gauges and the analytics panel below them.
  • Providers — connecting subscriptions and adding multiple accounts.
  • Hosted inference — where the Continuum column’s numbers come from.
  • Mobile — iCloud sync to iPhone.
  • Privacy — what stays local and what (with opt-in) leaves the device.