Track Claude Code & Codex Spend
@marginfront/code-cost-clarity is a small command-line tool. One command wires your coding-agent usage telemetry (from Claude Code, Codex, or both) through a local collector and into MarginFront, priced per developer, per model, with accurate prompt-cache token splitting.
This is internal cost visibility, not billing and not a spending cap. Your company watches its own AI coding spend (per developer, per model). It does not charge developers and it does not cut anyone off.
This tool never reads, needs, or transmits your Anthropic or OpenAI API key. The only credential it uses is your MarginFront key, and only to send usage to MarginFront.
Beta (pilot software). Code Cost Clarity is in active development. Pin a version, expect rough edges, and report issues.
Usenpx, notnpm install. This is a CLI tool, not a library. Installing it into a project pulls in its dependencies and can surface unrelated audit warnings. Run it withnpx @marginfront/code-cost-clarity@latest init(the@latestskips a stale npx cache). Thenpm i ...box on npmjs.com is npm’s auto-generated default for every package and is not the intended usage here.
What this does (one sentence)
Every time a developer runs Claude Code or Codex, this tool captures the token usage and sends it to MarginFront as a usage event, so you can see who used what, on which model, and how much it cost.The mental model (read this first)
Think of it like a cash-register receipt system:- Claude Code (and Codex) is the register. As it works, it broadcasts receipts: how many tokens, which model, which developer.
- The collector (
otelcol-contrib, open source) is the catcher. It runs in the background, catches the receipts, and writes them to a file. - The forwarder (the glue inside this tool) reads those receipts and sends each one to MarginFront.
- MarginFront records the event, prices it, and shows it under the developer’s email.
OTEL_METRIC_EXPORT_INTERVAL) plus a final flush when the session ends. It ships at 5 minutes by default (batched, so you get roughly one event per turn). Lower it (60 seconds or 5 seconds) for a more live drip, or raise it (10 minutes) to batch harder. The collector keeps repeated reports from double-counting no matter the interval.
Quick start
Get your MarginFront key first. Log in at app.marginfront.com, then go to Build -> API keys -> Create key pair and copy the secret key (mf_sk_...). init will ask you to paste it.
source, no second terminal. Your spend shows up in MarginFront under your developer email, automatically, and the background meter restarts itself at every login.
Already running Claude or Codex? Quit and reopen them after init (and start a fresh terminal session). The desktop apps and each terminal session read the telemetry config when they launch, so anything that was already open won’t emit until it’s reopened.
There is noCheck the meter anytime:code-cost-claritycommand on its own. Installing it does not add a new command to your computer, so typingcode-cost-clarity statusby itself says “command not found” and nothing is wrong. Every command runs throughnpx, like the ones on this page.
init, run npx @marginfront/code-cost-clarity run to stream your spend in the terminal (Ctrl-C stops it, and shuts its collector down too). You’ll see a line per turn like:
What gets captured, and what doesn’t
This meters the coding agents: it reads theclaude_code.* and codex.* telemetry that Claude Code and Codex emit to the local collector. init wires telemetry into both places these agents look, so it captures them in the terminal and the desktop apps:
- Terminal / CLI (
claude,codex) and local IDE coding sessions read the config from~/.claude/settings.jsonand~/.codex/config.toml. - The Claude Code and Codex desktop apps don’t read that config block, and a Dock-launched app never sees your shell, so
initalso writes the same vars into the macOS launchd GUI-session environment, which Dock-launched apps inherit. Quit and reopen a desktop app afterinitso it reads them at launch.
- The Claude consumer chat app (the chat window). That’s the general assistant, not Claude Code, so it never emits the coding-agent telemetry this reads.
- Anything in a cloud sandbox or not connected to your machine: a cloud session, or a remote / devcontainer / SSH setup where the agent can’t reach the local collector at
127.0.0.1:4318. If a surface doesn’t run on your machine and reach the local collector, this can’t see it.
The commands
| Command | What it does |
|---|---|
init | Creates ~/.marginfront-ccc/, writes your settings and the collector config, downloads the collector binary, and installs a background meter (a launchd job) that starts at login. Wires telemetry into Claude Code and Codex, including the desktop apps (via the launchd GUI-session env). On a normal terminal it also asks you to paste your MarginFront secret key (mf_sk_...) and saves it for you (nothing to hand-edit). If your team shares one Claude/Codex login, it also asks which developer this machine’s cost belongs to (see Sharing one login across the team). Safe to re-run: it never re-asks once a key is saved, and never overwrites it. Add --no-prompt on a server or in a script. |
status | Shows whether the background meter is running, plus recent activity. |
start | Starts (or reinstalls) the background meter. init does this for you; use it to bring the meter back after stop. |
preview <capture.json> | Prints the exact record it would send for one captured snapshot (Claude Code or Codex). Needs no MarginFront API key, so it’s great for a dry run. |
run | Optional live terminal view. Starts the collector in the foreground and streams your spend to the terminal until Ctrl-C (Claude Code, Codex, or both, no extra flag). The background meter from init already does this without a terminal; use run when you want to watch it live. Add --fold-cache only for an unpriced model (see below). |
stop | Pauses the background meter (and any foreground run). start brings it back. |
uninstall | Stops everything (the background meter and the collector), reverts the telemetry config it wrote (Claude settings.json, the Codex [otel] block, and the desktop GUI-session env), and deletes the collector binary and runtime files (reclaims the ~360 MB). Keeps your settings. Add --purge to also delete your settings and MarginFront API key. |
help, version | The usual. |
Per-developer attribution is automatic
What makes “who spent what” work is the developer’s email, and Claude Code puts it in the telemetry on its own. It’s the developer’s logged-in Claude account email. There is no manual email setup. Each developer just loads the telemetry settings and runs Claude Code normally. Works whether the developer signs in with an org-managed seat or an interactive login. On org-managed Claude seats the email is stamped for free. If a developer’s sign-in doesn’t surface an email, the tool doesn’t drop the usage. It attributes it to a clearly labeled placeholder customer (claude-code-no-identity) and prints how to fix it (sign in with an org-managed seat, or attach a customer mapping). You’ll see the placeholder in preview or run output if it ever kicks in.
Codex attributes the same way, with one wrinkle: whether your email shows up depends on how you sign in to Codex (an org-managed seat or an OpenAI API-key sign-in stamps it; some interactive ChatGPT logins may not). That sign-in is between you and OpenAI; this tool never sees your OpenAI key. See Also capture Codex.
Your MarginFront API key is separate from the developer’s identity: it’s the tool’s own credential for posting to MarginFront.runneeds it;previewdoes not.
Sharing one login across the team
Some teams share one Claude or Codex login across the whole team. When that happens, every developer’s telemetry carries the same email, so all the AI cost piles onto one person and per-developer attribution breaks. This tool fixes that without any change to how you sign in, because it runs locally on each laptop, so each laptop can carry its own developer identity. Duringinit it asks one question: which developer this machine’s AI cost belongs to. The question is pre-filled with your global git email (git config --global user.email), which is usually the right person sitting at this laptop. You have three choices:
- Press Enter to accept the pre-filled email.
- Type a different email to attribute this machine to someone else.
- Clear it and leave it blank to auto-detect from whatever email the coding-agent login reports.
CCC_DEVELOPER_EMAIL in ~/.marginfront-ccc/.env (mode 600, right next to your key). To change it later, edit that one line and then stop and start (or re-run init).
Which email wins, highest first:
CCC_DEVELOPER_EMAIL(this machine’s developer). When set, it wins for every record this machine sends: Claude and Codex, token turns and tool calls.- Otherwise the email the coding-agent login reports.
- Otherwise the no-identity placeholder (
claude-code-no-identityorcodex-no-identity).
This is internal cost visibility only.CCC_DEVELOPER_EMAILre-labels which developer the cost shows up under in your MarginFront. It does not change who pays or any customer’s bill. The--no-promptinstall (server or script) skips this question and leavesCCC_DEVELOPER_EMAILunset.
Cache pricing
Claude Code reports four kinds of tokens (fresh input, output, cache-read, and cache-creation), and Anthropic prices them differently. This tool splits them into their correct typed fields (cacheReadTokens / cacheWriteTokens) so MarginFront can price each at its own cache rate instead of lumping everything into one input number. That token split is accurate by default.
- Default (recommended). Cache tokens go in the
cacheReadTokensandcacheWriteTokensfields; MarginFront prices each at the model’s catalog rate. --fold-cache(emergency round-up only): for a model MarginFront can’t price yet, this rolls the cache tokens into billed input at the fresh-input rate, pushing the number up toward reality so it’s never silently low (a conservative ceiling). Use it only when a model has no cache price; otherwise the default is more accurate.
NEEDS_COST_BACKFILL (a visible gap, never a silent $0), and one click in MarginFront prices it both retroactively and going forward. The raw cache numbers are also kept in metadata, and Claude Code’s own cache-accurate cost is in metadata.claudeCodeCostUsd to reconcile against.
A long-context model id likeclaude-opus-4-8[1m]is normalized toclaude-opus-4.8to match MarginFront’s pricing table. The raw id is kept inmetadata.rawModel.
Also capture Codex
The same tool captures Codex too, and there’s nothing extra to install: Codex reports to the same local collector. You get three setups for free: Claude Code only, Codex only, or both (the same developer’s email shows up across all of it). Turn it on. Add an[otel] block to your ~/.codex/config.toml, pointing at the same collector:
exporter is a table, with the endpoint nested inside it and ending in /v1/logs. (A flat exporter = "otlp-http" with a bare top-level endpoint silently exports nothing. Verified.)
Then run Codex normally. run already watches both sources from the one collector file, so there’s no extra flag. init prints this same block at the end, so you have it handy.
Why Codex’s numbers are counted differently
Anthropic and OpenAI count tokens differently, and getting this wrong silently mis-charges you. For Codex, two of the token counts are already inside the others:- The cached tokens are already part of the input count.
- The reasoning tokens are already part of the output count.
| MarginFront field | From Codex | Why |
|---|---|---|
inputTokens | input count - cached count | the fresh, non-cached input only |
cacheReadTokens | cached count | priced at the cheaper cache-read rate |
outputTokens | output count | reasoning is already inside this, billed at the output rate as OpenAI bills it |
| (no cacheWrite) | (none) | OpenAI has no cache-creation tokens |
metadata for your own audit. They’re just never billed twice. (If you ever hit a Codex model MarginFront can’t price yet, --fold-cache works the same conservative way: it bills the whole input at the input rate and drops the cache-read field, so the number reads high, never low.)
Whether your email shows up depends on how you sign in
Per-developer attribution rides on the developer’s email from Codex’s telemetry, the same idea as Claude Code. Whether that email surfaces depends on how you sign in to Codex: an org-managed seat or an OpenAI API-key sign-in stamps it; some interactive ChatGPT logins may not. (That sign-in is between you and OpenAI; this tool never sees your OpenAI key.) If it doesn’t surface, the usage is not dropped. It lands under thecodex-no-identity placeholder with a fix hint, exactly like the Claude Code path.
Good to know
- A
gpt-5-codexpricing row must exist first. A full-rate row (including its cache rate) has to be in MarginFront’s pricing catalog. Until it is, a Codex event lands asNEEDS_COST_BACKFILL(a visible gap, never a silent$0). Adding that row is a one-time catalog step. codex-auto-reviewis skipped. It’s an internal pseudo-model, not a real billable model, so the tool drops it rather than mis-price it.codexandcodex execboth export the usage logs this tool reads (verified against a live session). Some Codex versions don’t emit OTel metrics, andcodex mcp-servertelemetry has had bugs, but neither is the logs stream this tool uses, so neither affects capture here.- The
[otel]block above is the verified shape (exporter as a table, endpoint nested with/v1/logs). If a future Codex version changes it, keep the exporter pointed at the local collector on/v1/logs.
One thing to confirm: check the exact Codex dollar figure against one real captured session, both that Codex reports per-turn (not running-total) token counts, and that your developer email shows up under your sign-in mode. The mapping above is the verified-safe default; the confirmation is a quick one-session check, not a reason to wait before installing.
Confirm it landed (independent read-back)
Pull the most recent events for one developer straight from the API:Maintain / shut off
- Collector version is pinned for safety. The install downloads one known-good collector release and verifies its fingerprint before it ever runs. Moving to a newer collector ships in a package update (so the integrity check always has a fingerprint to verify against). There is no useful per-run version override.
- Stop temporarily:
stoppauses the background meter;start(or re-runninginit) brings it back.statusshows whether it’s running. - Remove it:
uninstallfrees the ~360 MB, stops everything, and reverts the telemetry config it wrote (Claudesettings.json, the Codex[otel]block, and the desktop GUI-session env). It keeps your key;uninstall --purgedeletes your settings too.
Troubleshooting
- “No MARGINFRONT_API_KEY found”: paste your MarginFront key into
~/.marginfront-ccc/.env, orexport MARGINFRONT_API_KEY=...in therunshell.previewworks without one. - HTTP 401 / 403: wrong or expired key, or you pasted a publishable key (
mf_pk_*) where a secret key (mf_sk_*) is required. The forwarder sends your usage, and publishable keys are rejected on writes. Pull a fresh secret key from your MarginFront dashboard under Build → API keys. - HTTP 422 / validation error: body-shape mismatch. Run
preview <capture.json>and compare the record. - Collector file stays empty: make sure
initfinished and the background meter is running (status), and that you opened a fresh Claude/Codex session afterinit(an already-open app or shell won’t emit until reopened). If it’s still empty, re-runinitto rewrite the collector config: it defaults to http/protobuf/4318, the transport that works with Claude Code, and gRPC/4317 silently exports nothing. usageCostis null: the normalized model id didn’t match the pricing table. Tokens are still recorded; the cache-accurate cost is inmetadata.claudeCodeCostUsd.- Numbers ballooning: the collector’s delta conversion isn’t running. Re-run
initto rewrite the collector config. - Seeing
claude-code-no-identity(orcodex-no-identity)? Your sign-in didn’t surface an email. Use an org-managed Claude seat, or attach a customer mapping. (Codex uses thecodex-no-identityplaceholder when a ChatGPT login doesn’t surface an email.) - Codex spend not showing up? Confirm the
[otel]block is in your user-level~/.codex/config.toml(not a project config), the endpoint ishttp://127.0.0.1:4318, andrunis going. Some Codex versions/subcommands have telemetry bugs, so check your version. - “Collector integrity check FAILED” or “Refusing to run an unverified collector”: the collector that downloaded didn’t match the fingerprint the tool trusts, so it was stopped before it ran. The most common cause is a corporate network that inspects and rewrites downloads (a TLS-inspecting proxy), or a corrupted transfer. Re-run
initon a network without that kind of proxy. Don’t try to bypass the check: a download that keeps failing the fingerprint on a clean network is exactly the case the check exists to stop. The “unverified” wording instead means you pointedCCC_OTELCOL_VERSIONat a release the tool has no fingerprint for. UnsetCCC_OTELCOL_VERSIONto use the built-in pinned version.
Security
- This tool never reads, needs, or transmits your Anthropic or OpenAI API key. The only credential it touches is your MarginFront key, used only to POST usage to MarginFront.
- Your MarginFront API key never lives in the package. It’s saved only in
~/.marginfront-ccc/.env(mode 600) on your machine. The forwarder reads it from the environment only, never hardcoded, never logged. - The published package contains only the built code and its README. Captured telemetry, the collector binary, and your
.envare all kept off the machine that runs this tool and out of the package. - The collector this tool downloads is checked against a built-in SHA-256 fingerprint (pinned from OpenTelemetry’s signed checksums) before it is ever made runnable. If the downloaded file doesn’t match, it is deleted and the install stops, so a download that was tampered with or rewritten in transit never runs on your machine.
For developers (technical appendix)
Input shape: OTLP/JSON,resourceMetrics[].scopeMetrics[].metrics[]. Two metrics matter: claude_code.token.usage (one datapoint per type in input/output/cacheRead/cacheCreation) and claude_code.cost.usage (USD).
Grouping key: (user.email, model, session.id) → one MarginFront record per group.
Token mapping: input→inputTokens, output→outputTokens, cacheRead→cacheReadTokens, cacheCreation→cacheWriteTokens (Anthropic’s cache_creation_input_tokens). With --fold-cache, cache tokens are added into inputTokens instead and the typed fields are omitted (no double count).
Temporality: Claude Code emits cumulative counters. The collector converts them to deltas; the forwarder trusts each line is already an increment.
Ingest: POST https://api.marginfront.com/v1/sdk/usage/record, headers Content-Type: application/json and x-api-key: <key> (not Bearer). Body envelope { records: [...] }. The endpoint auto-creates the customer (by customerExternalId) and agent (by agentCode) on first POST, and resolves your org from the MarginFront API key (the body can’t override it).
Codex source (the second input shape)
Input shape: OTLP/JSON logs,resourceLogs[].scopeLogs[].logRecords[] (a different tree than Claude Code’s resourceMetrics). The usage event is named codex.sse_event; it’s detected by that name (on the event.name attribute or the record body) or, failing that, by the presence of any token-count attribute.
Value encoding: log attribute values arrive as intValue (a JSON string, per the protobuf int64 rule) or doubleValue (a JSON number) for token counts, and stringValue for identity fields. All three are handled.
Token fields: input_token_count, output_token_count, cached_token_count, reasoning_token_count, tool_token_count, plus model, user.email, and a session id (conversation.id / session.id). The accurate nested mapping is in the Codex section above. Cached is subtracted from input, reasoning is never added to output.
Granularity: one MarginFront record per codex.sse_event (one per turn). There’s no cumulative-to-delta step on the logs pipeline (that’s a metrics-only processor); each Codex log record is already one turn’s usage.
Identity: agentCode: "codex", signalName: "codex-turn", modelProvider: "openai", environment: "development". Records carry metadata.source: "codex".
Routing: both sources write one JSON document per line to the same collector file. The forwarder routes each line by which tree it has (resourceMetrics → Claude Code, resourceLogs → Codex), so one watcher handles either or both.
