Skip to content

About

No description, website, or topics provided.

Resources

Contributing

Stars

3 stars

Watchers

0 watching

Forks

Repository files navigation

codex-stats

codex-stats is a local analytics tool for coding agents.

It reads local session data from every coding assistant installed on the machine and surfaces:

  • a browser dashboard with an Overview tab for every tool combined, plus one tab per tool (Codex, OpenCode, Claude Code, Hermes, DeepSeek Harness)
  • day, week, month, and all-time windows inside every tab
  • model and project breakdowns
  • recent session history
  • estimated token-based cost (or each tool's own recorded cost when available)
  • per-tool cost overrides and a stacked per-tool token trend on the Overview
  • branch-level spend: a "Branches" panel that ranks work by cost per branch instead of per repository, and flags branches that went quiet — paid-for work that stopped and never finished
  • spend joined to the work it bought: an "Efficiency" panel reporting cost per 1k lines changed, cost per file, cost per editing session, the share of spend that went to sessions which changed nothing, a rework ratio, and the files that were rewritten most across every project at once
  • per-tool efficiency on the Overview, ranking each tool's spend against the lines it actually changed, so "which of my agents is worth it" has an answer
  • a provider breakdown showing which vendor each dollar went to, derived from the model name each session recorded
  • a reasoning-token share in Work Patterns, for models that bill thinking separately
  • anomaly-aware usage insights and recommendations
  • file-level impact tracking: a "Most Edited Files in This Project" table inside each project drilldown, showing per-file edit counts and add/delete line totals parsed from Codex, Claude Code, and DeepSeek Harness rollouts
  • regression detection vs last run, leaks (top offenders), runway/burn rate, and efficiency trends
  • tool behavior tracking: a "Tool Behavior" panel showing the tools each agent actually called — normalized into shared categories (Read, Edit, Execute, Search, Web, Subagent, Plan), with per-tool failure rates, repeated-call detection, read/write ratio, and abandoned-turn counts, across every tool with recorded calls
  • shareable JPG cards and browser PDF export from the dashboard

Data Sources

Every tool below always gets a tab, whether or not local data exists. Tools with no sessions render an empty state instead of being hidden.

Tool Location Notes
Codex ~/.codex state_5.sqlite + rollout JSONL files, including git branch
OpenCode ~/.local/share/opencode/opencode.db recorded cost + tokens per session
Claude Code ~/.claude/projects/**/*.jsonl per-project transcripts, including git branch
Hermes ~/.hermes/state.db recorded cost + tokens per session
DeepSeek Harness ~/.dsh/sessions JSONL event logs (zstd-compressed by default): tokens, tool calls, file edits

Use these environment variables to point at non-default locations (also used for test isolation):

  • CODEX_HOME (Codex), CODEX_STATS_OPENCODE_HOME (OpenCode), CODEX_STATS_CLAUDE_PROJECTS_DIR (Claude), CODEX_STATS_HERMES_HOME (Hermes), CODEX_STATS_DSH_HOME (DeepSeek Harness — the directory holding the per-project session folders, i.e. $DSH_HOME/sessions)

DeepSeek Harness

DeepSeek Harness (dsh) keeps one folder per session under $DSH_HOME/sessions/<project>/<session-id>/, holding a versioned JSONL log that is zstd-compressed by default. codex-stats reads the newest generation of each session (session.v<N>.jsonl[.zstd]), where a session is a folder named for its project and id — never the session.lock file, and never an older generation that a rewrite left behind.

  • Requests are counted from genuine user messages. dsh also logs tool results as message events; counting those would inflate every session with the agent's own replies.
  • Tokens come from each assistant turn's usage block plus any compaction summary, normalised into fresh input / cache reads / cache writes / output. DeepSeek charges nothing extra for cache writes, so that bucket stays zero.
  • File edits are reconstructed from edit, write, and str_replace_editor tool calls, which record the path and the old/new text — enough to count insertions and deletions per file, so dsh joins Codex and Claude Code in the efficiency and rewritten-files panels.
  • No git branch. dsh logs never name one, so its sessions are absent from the Branches panel rather than shown under a guessed branch.
  • Cost is estimated from DeepSeek's published off-peak rates. Peak hours (01:00–04:00 and 06:00–10:00 UTC on weekdays) are double that and are not modelled, so a peak-heavy session reads a little low.
  • Format changes are skipped, not guessed. A session written by a newer dsh format version than this build knows is left out with a note, and an interrupted write (a torn final zstd frame) keeps the complete frames rather than losing the whole session.

Reading a session means parsing its rollout or transcript line by line, and that cost is linear in total history on every launch, so each source reads only the 2,000 most recent sessions. This keeps startup predictable on long histories and is invisible on normal ones. When it does drop history, the dashboard says so above the metrics, naming each source it truncated, rather than reporting a partial history as a complete one.

  • CODEX_STATS_MAX_SESSIONS=N reads the N most recent sessions per source. Set it to 0 to read everything and get exact figures, which takes proportionally longer on a long history.

Install

pipx install codex-stats

Or with pip:

python3 -m pip install codex-stats

Command Reference

There is exactly one command, and it takes no options:

codex-stats

It reads local session data, writes a standalone dashboard HTML file to ~/.cache/codex-stats/dashboard.html, and opens it in your default browser. The path is fixed rather than a fresh temporary file, so the dashboard can be bookmarked and reloaded in place; each run overwrites it and the browser is given the file's timestamp so a reload always shows the run you just made, not a cached copy of the last one.

Override the location with XDG_CACHE_HOME.

Inside the dashboard, use the action bar to:

  • switch tools with the Overview / Codex / OpenCode / Claude Code / Hermes / DeepSeek Harness tab row
  • switch between Day, Week, Month, and All Time inside the active tool
  • print the active tool and window to PDF
  • download shareable JPG cards for summary, cost, focus, and project share

How It Works

codex-stats does not proxy or intercept API traffic. It reads local artifacts: Codex state_5.sqlite and rollout files, the OpenCode database, Claude Code project transcripts, the Hermes database, and DeepSeek Harness session event logs, then normalizes everything into one session model.

Leaderboard

The dashboard can submit your all-time totals to a shared leaderboard. The page you open never sends a number itself: it saves a username locally, and the submission is built and signed by codex-stats (not the browser) from the already-computed dashboard totals, then delivered straight from the tool to the server.

What is published, and what is not:

  • Sent: a username you type, plus totals for all-time tokens, requests, sessions, estimated cost, and per-CLI token share — the same figures already on your page.
  • Not sent: model names, prompts and tool calls, file names, project names, session details, timestamps, money in "real" units beyond the rounded cost share, or your IP beyond what any HTTPS request exposes to its server.
  • Device identity: each machine gets an opaque 32-char id derived from an HMAC of its MAC address. The MAC itself is read locally and never transmitted; the id cannot be reversed into the hardware address by the server, and it is what keeps one person from overwriting another's row.

The leaderboard is on by default after the first release that ships a key. To turn it off or move it, see the env vars:

Variable Effect
CODEX_STATS_DISABLE_LEADERBOARD=1 Silence the whole feature: no embed, no endpoint, no "Submit" button
CODEX_STATS_LEADERBOARD_URL Point submission at your own server (/api/submit)
CODEX_STATS_LEADERBOARD_KEY Override the shared submit key (lets admins rotate without a release)
CODEX_STATS_LEADERBOARD_TIMEOUT Seconds the submit endpoint waits before exiting (default 300)

Why a shared key ships, honestly: it is public the moment it ships, so it cannot keep the numbers genuine. What it protects is that stats come from the local database rather than forged or inflated by browser Javascript, and that one row cannot be overwritten without the owner's device id. Blocking spam is the server's job, not this client's. The feature is the same either way: codex-stats runs a short-lived local endpoint after writing the dashboard, hands the page the username dialog, and exits on its own once the window passes without a submission.

Notes

  • Branches are grouped per repository. A branch name is only unique inside its own checkout, so feature/login in two projects is tracked as two branches. Codex reads the branch from its session database and Claude Code records one per event; OpenCode and DeepSeek Harness record none at all, so their sessions never appear in the Branches panel. Hermes reads a branch from its own database, but leaves it null for sessions started outside a checkout, so its sessions appear only when one was recorded. A detached checkout is reported as having no branch rather than as a branch called HEAD. If a session switched branches mid-run, it is attributed to the branch most of its events landed on.

  • A branch "goes quiet" after 14 days without a session. Those are listed and totalled separately, because a branch that consumed tokens and then stopped is work that was paid for and never finished — invisible in a project-level view, where every branch of a repository collapses into one number.

  • Vendors are resolved from the model name, not the recorded provider. Codex and DeepSeek Harness record the real vendor (openai and deepseek), but OpenCode and Hermes record their own CLI name there instead, and those may route to any vendor, so grouping on that field would answer "which CLI" while claiming to answer "which vendor". Model names are matched with any vendor/ prefix stripped, so anthropic/claude-opus-4.6 and claude-opus-4.6 land in the same bucket.

  • Cost per line is only computed where file edits are recorded. Codex, Claude Code, and DeepSeek Harness name the files a session edited; OpenCode and Hermes record tokens and cost but nothing about files. Their spend is excluded from every ratio rather than counted as cost with no work to divide by, their sessions are not counted as read-only, and the per-tool table marks them "Not recorded" instead of dropping them or ranking them as expensive.

  • A view with no recorded edits reports counts, not zeroes. When tracked tools ran but changed nothing, there is no denominator, so the panel shows session counts and spend rather than a cost of $0.00 that would read as free work.

  • Rework is measured as deletions over insertions across the window. A "churn" file is one the window left smaller than it found it. The rewritten-files table ranks by how many separate sessions touched a file, and spans every project at once, since file impact inside a single project's drilldown cannot show a file that is hard in three repos.

  • Spend that cannot be attributed is shown, not dropped. Some tools record an internal codename instead of a model name, and no lookup can resolve that. Those sessions are grouped under "Unidentified" with their share of spend stated, rather than being assigned to a guessed vendor or silently omitted.

  • Reasoning tokens are additive. They are billed as output and counted on top of the plain output figure, so the reasoning share in Work Patterns is a true fraction of the total. Tools that do not report a separate reasoning figure show "Not reported" rather than a misleading 0%.

  • When a tool records its own cost (OpenCode, Hermes), that recorded value wins for the session and no estimate is used.

  • Otherwise cost is priced per token component, because cached reads and output are not the same price as fresh input. Every session is normalized to four buckets: fresh input, cache reads, cache writes, and output.

  • Providers disagree on how they report cache reads. OpenAI (Codex) counts them inside input_tokens; Anthropic (Claude) reports them separately. codex-stats normalizes both conventions at ingest, so the cache ratio is always a real fraction between 0 and 1 and the four components always sum to the provider's own total.

  • A default rate table ships for known models, taken from each provider's published pricing page and converted from USD per million tokens to USD per 1k. It is a dated snapshot: see RATE_SNAPSHOT_DATE and RATE_SNAPSHOT_SOURCES in src/codex_stats/config.py, and update it when published prices move.

    • OpenAI rates are standard processing, short context. OpenAI only bills explicit cache writes from GPT-5.6 onward; earlier models use implicit caching, so their cache-write rate is 0. Long-context (>272k) pricing is not modeled.
    • Anthropic rates are standard pricing with 5-minute cache writes (1.25x base input). 1-hour writes (2x), Batch (-50%), fast mode, and the 1.1x data-residency multiplier are not modeled.
    • Model names are matched exactly first, then with a provider prefix stripped, so anthropic/claude-opus-4.6 resolves to the same rates as claude-opus-4.6.
  • Any model that cannot be identified — most often a third-party alias such as a provider's internal codename — falls back to DEFAULT_FALLBACK_RATES, a mid-tier published rate rather than a flat rate. A flat fallback badly overestimates cache-heavy sessions, because cache reads are normally about 10x cheaper than fresh input. The dashboard labels these sessions ("N sessions on a fallback rate") rather than presenting a silently wrong number. Set default_usd_per_1k_tokens to use a single flat rate for them instead.

  • Override rates per model in ~/.config/codex-stats/config.toml:

    [pricing]
    # optional: replace the fallback for unidentifiable models with one flat rate
    # default_usd_per_1k_tokens = 0.003
    
    [pricing.model_usd_per_1k_tokens.gpt-5.4]
    input = 0.0025
    cached_read = 0.00025
    cache_write = 0.0
    output = 0.015
    
    # a single scalar still works and applies to all four components
    [pricing.model_usd_per_1k_tokens]
    some-alias-model = 0.004
    
    # a source rate overrides the model table
    [pricing.source_usd_per_1k_tokens]
    claude = 0.015
    
    [budget]
    # optional: warn once the current calendar month reaches this spend, and
    # state clearly when it passes. Omit the whole [budget] table for no banner.
    monthly_limit_usd = 120.0
    # optional: how near the limit is "approaching" (default 0.8 = 80%)
    warn_at_ratio = 0.9
  • Output depends on local file formats remaining compatible.

Roadmap

The current priority list lives in docs/roadmap.md.

Shareable Assets

The dashboard exports JPG cards with names like:

These sample assets were generated from the current renderer so the docs match what the dashboard actually downloads.

Development

For local development from the repo:

python3 -m venv .venv
source .venv/bin/activate
python -m pip install -U pip setuptools
python -m pip install -e .

Run without installing:

PYTHONPATH=src python3 -m codex_stats

About

No description, website, or topics provided.

Resources

Contributing

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages