Skip to main content

agent-cost

See Claude Code and Codex CLI token usage and estimated costs from the logs already on your machine.

Keep pricing gaps visible: unknown models are marked unpriced, and cache-write estimates with missing TTL information are marked lower_bound. JSON reports include the rate catalog version and SHA-256 used for the calculation; the catalog records its pricing sources.

These are estimates based on the selected rate catalog, not your actual bill. The CLI makes no runtime network calls and has zero runtime dependencies. On macOS and Linux with IANA timezone data, no account, service, or project configuration is required. See the platform note for minimal Windows Python environments.

Need the result in another program? agent-cost measure returns versioned measure/v1 JSON for session IDs you supply. Task and PR attribution remain the caller's responsibility.

日本語サマリ · Synthetic output example

Try it on your machine

With uvx, no persistent install is needed:

uvx --from coding-agent-cost agent-cost doctor
uvx --from coding-agent-cost agent-cost report

The package runner may need network access to download the tool. The CLI itself does not upload your logs.

The first command checks the expected local paths, the Codex database, and the bundled rate catalog. It does not open every Claude JSONL file. The second command performs the real scan, prints the result, and exposes unreadable inputs in data_quality.skipped_files. This path was exercised on macOS from a clean temporary directory against the published 0.1.0 package on 2026-08-23.

Prefer a persistent command? Install the PyPI distribution, then run the same two commands:

pip install coding-agent-cost
agent-cost doctor
agent-cost report

The PyPI distribution is named coding-agent-cost, while the command remains agent-cost and the import remains agent_cost. The shorter PyPI name is unavailable because of PyPI's similarity rule; this project is not affiliated with the unrelated agentcost distribution.

Synthetic output example

This is synthetic Claude data, not observed usage, a bill, or evidence of savings. The CLI output below was reproduced with PyPI package coding-agent-cost==0.1.0 and source commit d170ea301ed0c46351749214bd299e75ae8a7786 (only trailing space padding is omitted). The two-event fixture contains known-model input, cache writes without a TTL breakdown, and unknown-model input.

Month    Agent   Model                 Token Kind           Tokens   Priced   Unpriced  Est. Cost (USD)  Credits  Status
-------  ------  --------------------  -------------------  -------  -------  --------  ---------------  -------  -----------
2026-06  claude  claude-opus-4-8       cache_write_unknown  1000000  1000000  0         6.2500           -        lower_bound
2026-06  claude  claude-opus-4-8       input_nocache        1000000  1000000  0         5.0000           -        priced
2026-06  claude  model-not-in-catalog  input_nocache        500      0        500       0.0000           -        unpriced

Total tokens: 2,000,500   Total estimated cost: $11.2500
Rates catalog: 2026-07-29  (sha256=5d86e11b3c95...)
Data quality: malformed_events=0  skipped_files=0  negative_deltas=0  unpriced_tokens=500
  • priced: $5.0000 is a catalog-based estimate for 1,000,000 input tokens, not a confirmed charge.
  • lower_bound: $6.2500 uses the 5-minute cache-write rate because the TTL is missing. This is a lower bound under the catalog and observed token data, not a lower bound on your actual bill.
  • unpriced: 0.0000 does not mean free. Read pricing_status (the Status column) together with unpriced_tokens (Unpriced): 500 tokens have no price and are excluded from the dollar total. The $11.2500 total also includes the cache-write lower-bound estimate.
Reproduce with synthetic data only (Python 3.9+ and uvx)

Run from this repository's root. Both log locations and the config file are explicitly set inside a temporary directory. Inherited log-location overrides are removed for the child process, so it cannot fall back to your real Claude or Codex logs. The temporary data is removed when the command exits. The package runner may download the pinned package.

python3 - <<'PY'
import json
import os
from pathlib import Path
import shutil
import subprocess
import tempfile

fixture = Path("examples/synthetic-claude.jsonl").resolve()
with tempfile.TemporaryDirectory(prefix="agent-cost-example-") as directory:
    root = Path(directory)
    project = root / "claude" / "projects" / "synthetic"
    project.mkdir(parents=True)
    (root / "codex").mkdir()
    shutil.copyfile(fixture, project / "session.jsonl")
    config = root / "config.json"
    config.write_text(json.dumps({
        "claude_home": str(root / "claude"),
        "codex_home": str(root / "codex"),
    }))
    env = dict(os.environ)
    for key in ("CLAUDE_HOME", "CODEX_HOME", "PYTHONPATH"):
        env.pop(key, None)
    env["AGENT_COST_CONFIG"] = str(config)
    subprocess.run([
        "uvx", "--from", "coding-agent-cost==0.1.0", "agent-cost",
        "report", "--agent", "claude", "--since", "2026-06-01",
        "--until", "2026-06-02", "--format", "table",
    ], cwd=root, env=env, check=True)
PY

Use --format json in the same command to see the full catalog SHA-256 and field names.

Why this exists

Many usage trackers are designed as rich, convenient dashboards. That can be the right choice when broad agent coverage and interactive exploration matter most. agent-cost is for a different trust model:

  • Local-first and zero-network: source logs stay on disk; the package has zero runtime dependencies.
  • Auditable: token facts, the versioned rate catalog, its digest, and data-quality counters are available for inspection and machine consumption.
  • Fail-closed pricing: unknown models remain unpriced; ambiguous cache-write TTLs are labeled lower_bound instead of silently receiving a best-guess price.
  • Explicit attribution boundary: measure accepts named session ids, but agent-cost does not infer a task from a branch or PR. A workflow such as spec-lane can own the session-to-task binding and consume the versioned JSON result.

This is not a claim that local CLI accounting is universally better than a dashboard. It is a smaller primitive for environments where data egress, dependency surface, custom metrics, or task attribution need to remain under the operator's control.

See Choosing by use case and trust model for a dated, source-linked comparison with broader CLI trackers, local dashboards, and observability stacks.

Report and export

agent-cost report turns each local usage event into a canonical fact (one model, one token kind, one timestamp, one count), prices each fact against the bundled rate catalog, and prints an aggregated table:

agent-cost report --since 2026-06-01 --until 2026-07-01 --format table
agent-cost report --group-by month,agent --format csv
agent-cost report --format json > usage.json
agent-cost export --agent claude --out facts.jsonl   # raw canonical facts

Useful flags: --since/--until (half-open window; date-only values are interpreted in --timezone, default UTC), --agent claude,codex, --group-by month,agent,model,token-kind, --rates PATH (use a different catalog entirely, see below), --exclude-archived (Codex threads).

Platform note: report, export, and measure use Python's IANA timezone database even when the timezone is UTC. macOS and typical Linux installations provide it through the operating system. A minimal Windows Python environment may require pip install tzdata first; tzdata is not currently a declared package dependency.

If another program wants to parse agent-cost's output for one or more specific session ids, see agent-cost measure below rather than scraping report.

What this measures, and what it doesn't

agent-cost only reads data that is already on disk. It never talks to the network, never calls gh, and never resolves branches or PRs.

  • Claude Code: every logical assistant message is one billing event, attributed to the exact model on that event (a session that switches models mid-conversation is not folded into one "primary model"). Claude Code's transcript writes one JSONL line per content block of the same message, and those lines are deduplicated first -- but only when a row carries a full message.id + requestId pair; a row missing either half is emitted on its own (never merged) and flagged source_quality: "identity_missing" rather than assumed billing-accurate. identity_missing facts are still priced and included in rows/totals; the flag is a warning, not an exclusion or an unpriced status. A message's lines don't necessarily repeat an identical usage block: model and the input-side fields (input tokens, cache read, cache-write TTL breakdown) stay the same across a message's lines, but output_tokens typically grows line by line as the response streams in, and intermediate lines usually lack usage.speed entirely (reported as mode "unknown"), with only the final line carrying a concrete mode. Within a deduplicated group, model or an input-side field that actually differs across the group's rows, two rows disagreeing on a concrete mode ("normal" vs "fast"), or an output_tokens value that decreases or is non-monotonic across them, is counted in data_quality.conflicting_duplicate_groups as a real billing disagreement -- output_tokens growing row by row, and mode "unknown" mixed with a single concrete mode elsewhere in the group, are both the ordinary streaming pattern just described and are not flagged, since the reader's own cross-check of real transcripts found exactly that pattern in every observed duplicated group. See CHANGELOG.md's 0.2.0 entry. When Anthropic's prompt-cache TTL breakdown (5-minute vs 1-hour writes) is present in the log, it's used; otherwise the cache-write tokens are priced at the 5-minute rate as an explicit lower bound and flagged lower_bound rather than guessed at the (more expensive) 1-hour rate.
  • Codex CLI: rollout files record a cumulative token count after each turn; agent-cost turns that into per-turn deltas. Under the Codex token-rate tariff used here, cache writes are not added as a separate charge. The current reader does not emit independent cache-write facts; newer logs can include cache_write_input_tokens, but this field is not used to add a cache-write charge. This differs from older logs where that signal was unavailable. Codex's output fact is output_tokens alone: cross-checking real rollout files confirms total_tokens == input_tokens + output_tokens in every sample, which means reasoning_output_tokens is a breakdown of output tokens already counted, not an additional charge -- adding it in would double it.
  • Anthropic prices are standard (non-batch) API list prices. The Batch API is roughly 50% cheaper, but agent-cost's logs carry no signal for whether a request went through Batch, so all Claude usage is priced at standard rates; this overstates cost for anyone using Batch.
  • Cost is always an estimate. The output field is estimated_cost_usd, never cost_usd: it is a list-price calculation from token counts, not a bill. For Codex, whose provider bills in credits, the row also carries a credits figure; credits × usd_per_credit is an illustrative USD conversion, not what you were actually charged (enterprise allowances, overage rules, and fast-mode multipliers with unknown values are exactly why the raw credits number is kept alongside the USD estimate rather than only the USD).
  • An unrecognized model, or a token kind a model's rate period doesn't define, is reported as unpriced with estimated_cost_usd: null-like zero and the tokens broken out in unpriced_tokens -- agent-cost never invents a price for something it doesn't have a rate for.
  • Corrupted log lines, files that vanish mid-read, and Codex cumulative counters that go backwards (e.g. after a session reset) are all counted in the report's data_quality block instead of being silently dropped or clamped to zero.
  • Known catalog gaps, tracked in agent_cost/rates.json's notes: gpt-5.6 (Sol/Terra/Luna) credits could not be confirmed from the primary source (help.openai.com's Codex rate card returns HTTP 403 to automated fetches); the values in the catalog come from several independent secondary sources that agree with each other and are internally consistent with usd_per_credit, but are not primary-source-verified -- re-check them once the rate card is reachable. Update either via a custom --rates file if you have a confirmed number.

Updating the rate catalog

GPT-6 Astra: Codex Standard/Fast estimates use exact ID gpt-6-astra and owned settings matched to turn/model context in the public Codex 0.153.4 JSONL format. Explicit default uses Standard; priority uses 2.5x. Missing/ambiguous tier or model attribution stays unpriced, with tokens preserved. These are request-setting estimates, not confirmed processing tiers or actual bills. The catalog observation cutoff is not an official launch time; earlier usage is unpriced. See evidence, synthetic tests and model-switch limits. API pricing and legacy message billing are outside this entry.

Prices live in agent_cost/rates.json, not in code. It's a historical catalog: each model can have several time-bounded rate periods, so a price change is recorded as a new period rather than overwriting the old one (see claude-sonnet-5's launch-promo period for a worked example). Every catalog carries a catalog_version, a list of sources (the pricing page a rate came from), and is validated on load (no duplicate model keys or aliases, no negative rates, no overlapping periods for the same model).

To use your own catalog instead of the one bundled with the package, pass --rates path/to/rates.json to report or export -- this fully replaces the bundled catalog, it does not merge with it. Inspect any catalog with:

agent-cost rates show                       # list every model_key
agent-cost rates show --model gpt-5.5       # one model's rate history
agent-cost rates validate path/to/rates.json

agent-cost report's JSON output always echoes the catalog's catalog_version and the sha256 of the exact rates file used, so a report can be traced back to the prices that produced it.

Machine consumption: agent-cost measure

report is for a person reading a table. measure is for another program calling agent-cost as a subprocess and parsing its stdout -- e.g. a build orchestrator attributing cost to a specific unit of work it already knows the session id(s) for.

agent-cost measure --session-id <id> [--session-id <id> ...] \
  [--since --until --timezone] [--agent claude,codex] [--rates PATH] --format json
  • One or more --session-id is required (repeat the flag for more than one); measure never scans "everything," only the sessions you name.
  • --since/--until accept a date-only value (interpreted in --timezone), an offset-qualified ISO 8601 datetime (+00:00, +09:00, ...), or the same datetime with a trailing Z instead of an offset (2026-07-31T00:00:00Z, exactly what Date.toISOString() in JS emits) -- all three are accepted by report/export/measure alike.
  • Exit code is 0 on success -- including when none of the given session ids matched any usage at all, which is a valid, representable answer (empty totals, "matched": false per session), not a failure. Exit code 2 means bad input (no --session-id, an unparseable --since/ --until/--timezone, or an invalid --rates catalog) -- nothing was measured, don't trust any partial output.
  • Output is one JSON object on stdout with a protocol_version field (currently "measure/v1") a caller should check before trusting the shape below. Within a major version, only additive changes (new fields) are made; a field being removed or changing meaning bumps the version.
{
  "protocol_version": "measure/v1",
  "producer_version": "0.2.1",
  "accounting_basis": "agent-cost-raw-total/v2",
  "generated_at": "...",
  "window": { "since": "...", "until": null },
  "timezone": "UTC",
  "agent": ["claude", "codex"],
  "rates": { "catalog_version": "2026-07-29", "sha256": "..." },
  "session_ids": ["sess-1", "sess-2"],
  "sessions": {
    "sess-1": { "matched": true, "rows": [ /* same row shape as report --format json */ ], "totals": { "tokens": 12345, "priced_tokens": 12345, "unpriced_tokens": 0, "estimated_cost_usd": 0.42, "credits": 0.0 } },
    "sess-2": { "matched": false, "rows": [], "totals": { "tokens": 0, "priced_tokens": 0, "unpriced_tokens": 0, "estimated_cost_usd": 0.0, "credits": 0.0 } }
  },
  "total": { "rows": [ /* union across every requested session_id */ ], "totals": { "...": "..." } },
  "data_quality": {
    "malformed_events": 0,
    "skipped_files": 0,
    "negative_deltas": 0,
    "unpriced_tokens": 0,
    "duplicate_rows_skipped": 0,
    "conflicting_duplicate_groups": 0,
    "missing_dedup_identity_rows": 0,
    "source_quality": { "ok": 41, "first_event_delta": 2, "identity_missing": 0 }
  }
}

Rows are grouped by agent/model/token-kind only -- measure never buckets by month, since a query is already scoped to specific sessions. total is the union of every requested session_id (not a global report), so it's the number to attribute to whatever unit of work those sessions represent. producer_version is the agent-cost package version that produced this payload; accounting_basis identifies the token-accounting semantics behind the numbers (separate from protocol_version, which only tracks the JSON shape) -- a consumer that persists historical measurements should key comparability on accounting_basis, not producer_version alone, since a future release can bump the latter while keeping the former. data_quality.unpriced_tokens and .source_quality are scoped to the requested sessions; so are the three dedup counters (duplicate_rows_skipped, conflicting_duplicate_groups, missing_dedup_identity_rows), which are Claude-only and computed over the intersection of the requested session ids and the --since/--until window, never over an unrequested session's rows. .malformed_events/.skipped_files/.negative_deltas describe the health of the underlying log read within --since/--until and are not attributable to one session.

Privacy

agent-cost makes zero network calls. agent-cost export's JSONL never includes absolute file paths, rollout paths, prompt/message content, or git branch names -- only the fields needed to reproduce a cost estimate: occurred_at_utc, agent, session_id, model_raw, model_key, token_kind, tokens, mode, and source_quality (a fixed-vocabulary caveat about how that one fact was derived, e.g. "ok" or Codex's "first_event_delta" -- never null).

License

MIT. See LICENSE.


日本語サマリ

agent-cost は Claude Code / Codex CLI がローカルに残すログ(~/.claude/projects/**/*.jsonl と ~/.codex/state_5.sqlite + rollout ファイル)だけを読み、トークン使用量とおおよそのコストを 見積もる CLI です。ネットワークアクセスは一切行いません。

  • 集計の最小単位は「1 イベント = 1 モデル × 1 token 種別」の fact であり、session 単位でモデルを 丸めません。Claude の prompt cache は TTL 内訳(5分/1時間)が取れればそれを使い、取れない場合は 5分単価で 下限推計(lower_bound)として明示します。対象の Codex トークン料金体系では cache write を別料金として加算せず、現行 reader は独立した cache-write fact を出力しません。 新しいログに cache_write_input_tokens が含まれていても追加料金の計算には使用しません。 これは、旧ログでその情報を観測できないこととは区別します。
  • 出力フィールドは estimated_cost_usd(推計であることを明示)。Codex は credits も併記します。 未知のモデル・単価表にない token 種別は unpriced として扱い、憶測の価格を出しません。
  • 単価表 (agent_cost/rates.json) は履歴型カタログで、値上げは新しい期間として追加します。 --rates PATH で別カタログに完全差し替えできます。
  • agent-cost measure --session-id ID [--session-id ID ...] --format json は他プログラムから subprocess で叩くための機械可読な契約です(protocol_version: "measure/v1")。指定した session_id が1件も見つからなくても終了コードは0(空集計として表現)、--session-id 未指定など の入力エラーのみ終了コード2です。
  • 破損したログ行、読めなくなったファイル、Codex の累積カウンタが逆行するケースなどは、すべて data_quality に件数として記録し、黙って丸めたり捨てたりしません。
  • Codex の output は output_tokens のみです。実 rollout データを突合した結果 total_tokens == input_tokens + output_tokens が常に成立することを確認しており、 reasoning_output_tokens は output の内訳(二重計上してはいけない)と判断しています。
  • Anthropic の単価は標準(非 Batch)API 価格です。Batch API は約50%安いですが、ログからは Batch 利用かどうか判別できないため、常に標準単価で推計します(Batch 利用者には過大推計)。
  • claude-opus-5 のローンチ日は根拠を確認できず effective_from はプレースホルダです。 gpt-5.6(sol/terra/luna)は一次情報(help.openai.com の rate card)が 403 で取得できなかったため、 相互に整合する複数の二次情報源の値を採用しています(一次情報での裏取りは未完了、詳細は rates.json の notes)。

Metadata

Release files for coding-agent-cost 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for coding-agent-cost 0.2.1
File Size Uploaded
coding_agent_cost-0.2.1.tar.gz 95.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for coding-agent-cost 0.2.1
File Interpreter ABI Platform
coding_agent_cost-0.2.1-py3-none-any.whl Python 3 none any Details

Total release size: 145.2 kB

Release files / coding_agent_cost-0.2.1.tar.gz

Download URL coding_agent_cost-0.2.1.tar.gz
Size 95.8 kB
Tags Source
SHA-256 checksum
How to use checksums
f1aac6ccaa94b574b0a145a85fc1b50f2c2a23a4d31d6e6852e133c2e8bc8482
BLAKE2b-256 checksum
How to use checksums
56ba874830cfb170d02201b42e6c84a2336c343d755a246f3d29c1d0d2db8b94
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.9.6

Release files / coding_agent_cost-0.2.1-py3-none-any.whl

Download URL coding_agent_cost-0.2.1-py3-none-any.whl
Size 49.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
78641a1a23a762b7c61ff47e5dc0e6a98af365dc6f81d749c353852268409dbf
BLAKE2b-256 checksum
How to use checksums
2137ebe63b721843c864e81e1983cbacbf745ce10955e670e071547dd322144a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.9.6

Release history Release notifications | RSS feed

0.5.2

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.2

2 release files

This release

0.2.1 This release

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page