Claude Code and Codex both record enough local data to reconstruct a useful week of AI-assisted work. They do not record it in the same shape.
A report built by adding every number in every JSONL row will double-count some Codex sessions, miss Claude cache activity, and assign usage to the wrong model after a mid-session switch. The engineering problem is normalization: turn two changing log formats into one small event model, then make every later calculation depend on that model.
This article describes the macOS implementation released in Agent Island v1.6.1. The method can be used in another local tool; the important parts are the accounting rules and their limits.

Start with the records that mean usage
Claude Code stores project sessions as JSONL files under its local project directories. The parser accepts assistant messages only when they contain both a usage object and a model name. It reads input, output, cache-creation, and cache-read tokens separately.
Claude logs can contain repeated usage records. A row is deduplicated when both its message ID and request ID are available. A row missing either identifier is retained because dropping it would turn an incomplete log into a silent undercount. Synthetic model placeholders and zero-token rows are ignored.
Codex rollout files require a different parser. A turn_context row supplies the active model, while a later event_msg with token_count supplies usage. The parser carries the current model forward because a session can switch models between turns.
The count source is last_token_usage, not the accumulating total_token_usage. Using the cumulative value is risky with forked sessions because earlier usage can be counted again. Codex also reports cached input inside its input total, so the parser subtracts cached input before pricing the remaining input at the normal rate.
Both readers emit the same internal record:
provider
timestamp
model
input tokens
output tokens
cache creation tokens
cache read tokens
After this boundary, report code no longer needs to know which JSONL format produced an event.
Keep two token totals
A single token total is ambiguous. The implementation keeps two. The wire total includes input, output, cache creation, and cache reads. It answers, "How much token traffic appeared in the local records?" That number drives the report headline and daily activity bars.
The narrower total includes input and output only. It is useful for views that exclude cache tokens. Keeping both avoids rescanning logs when the display changes.
Cost is a third measure. Each event is priced with separate input, output, cache-creation, and cache-read rates. Agent Island labels the result API value because it is a counterfactual estimate based on published API rates. It is not a subscription invoice or a claim about money saved.
Named models missing from the price table are not assigned a guessed rate. Their estimated value is zero and the UI can surface an unpriced-model warning. An early Codex token row that arrives before model context uses the parser's model fallback; that is a missing-context rule, not pricing for an unknown named model.
Align the week to calendar days
The report covers today plus the previous six local calendar days, starting at midnight. It is not a rolling 168-hour window.
Daily bars are calendar buckets. If per-model rows used a rolling window, they could include most of an extra day and exceed the headline total. The aggregator uses one calendar-aligned boundary for daily totals and per-model rows.
The same pass over normalized events builds daily wire-token buckets, provider totals, per-model input/output totals, per-model wire totals, and per-model API value. Model rows are ranked by API value rather than token count. A cheaper model with many cache reads can move more tokens while contributing less estimated value than a smaller amount of expensive output.
Make sharing an explicit user action
The report is rendered from the local aggregate as a fixed-size image. Opening the report does not upload it. The user can copy the image to the clipboard or open the system share picker. Both actions start from a cached local render. The application does not post the image, choose a destination, or send the underlying transcript.
local JSONL files
-> normalized token events
-> calendar aggregates
-> local image render
-> clipboard or system share picker, after a click
A network analytics pipeline would make attribution easier, but it would change the product's trust model. The current design measures what the app can know locally and leaves publication to the user.
Cache parsing work, not product data on a server
Large session histories make a full JSONL scan expensive. The readers memoize per-file parse results using the file path, modification time, and size. On a later refresh, unchanged files can reuse parsed events.
The Codex reader also rejects lines that cannot contain the two relevant record types before paying the JSON parsing cost. Large response-item rows, including embedded media or tool output, are not usage records and do not need to enter memory. This optimization avoids repeating work without changing the accounting model.
What the ledger cannot prove
The report reflects records still present on the machine. Deleted or moved sessions cannot be reconstructed. A provider can change its local format. A newly released model can appear before the embedded pricing snapshot knows its rates. The current calculation also omits Anthropic's special long-context tier, so a qualifying request can differ from the estimate.
The ledger cannot tell whether a token produced valuable code. Token volume is activity, not outcome quality. Preserve each provider's raw accounting distinctions, normalize only what is shared, and state what the resulting number means. That produces a ledger someone can check instead of a dashboard that merely looks precise.
Agent Island is an open-source status companion for Claude Code and Codex. Read the source or star the repository on GitHub.
Agent Island is an open-source status companion for Claude Code and Codex.
← All posts