@marginfront/code-cost-clarity
v0.8.0
Published
See your Claude Code and Codex spend in MarginFront. One command wires your coding-agent usage telemetry through a local collector into MarginFront, priced per developer, per model, with accurate prompt-cache token splitting and automatic billable tool-ca
Maintainers
Readme
@marginfront/code-cost-clarity
See your Claude Code and Codex spend in MarginFront. One command wires your coding-agent usage telemetry through a local collector and into MarginFront, priced per developer, per model, with accurate prompt-cache token splitting. One package, three modes: Claude Code only, Codex only, or both.
⚠️ Beta (pilot software). Under active development - pin a version, expect rough edges, and report issues.
Use
npx, 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(@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.
📖 Full documentation: https://docs.marginfront.com/tools/code-cost-clarity
This is internal cost visibility, not billing and not a spend cap. The installing company watches its own AI coding spend (per developer, per model, R&D-vs-COGS). It does not charge developers and it does not cut anyone off.
Your LLM keys stay yours. This tool never reads, needs, or transmits your
Anthropic or OpenAI API key. The only credential it uses is your MarginFront key
(the secret mf_sk_*), and only to send usage to MarginFront.
What this does (one sentence)
Every time a developer runs Claude Code, this connector 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 is the register. As it works, it broadcasts receipts (OpenTelemetry exports): 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 (our glue, inside this package) reads those receipts and sends each one to MarginFront.
- MarginFront records the event, prices it, and shows it under the developer's email.
Claude Code (your task)
| broadcasts usage on a timer (you choose the interval) + once at session end
v
Collector (otelcol-contrib, local) --- converts cumulative->delta so we don't double-count
| writes OTLP/JSON lines to live-otlp.jsonl
v
Forwarder (this package, `run`)
| POST /v1/sdk/usage/record
v
MarginFront (cost dashboard)How often does it report? It's timer-based, not per-message. Claude Code
flushes usage on the export interval (OTEL_METRIC_EXPORT_INTERVAL) plus a final
flush when the session ends. The package ships this at 5 minutes, batched, so
you get roughly one event per turn without a long wait. Lower it (e.g. 60s or 5s)
for a more live drip, or raise it (e.g. 10 min) to batch harder. The collector's
delta conversion keeps repeated reports from double-counting.
Quick start
Get your MarginFront key first. Sign in at app.marginfront.com, go to
Build -> API keys -> "Create key pair", and copy the secret key
(mf_sk_*). Direct link: https://app.marginfront.com/build?tab=keys
Setup is one step now. init does everything:
npx @marginfront/code-cost-clarity initinit walks you through it:
- It downloads the collector (~360 MB, one time) and creates
~/.marginfront-ccc/. - It prompts you to paste your MarginFront SECRET key (
mf_sk_*) and saves it to~/.marginfront-ccc/.envfor you (mode 600). A publishable key (mf_pk_*) is rejected on send, so use the secret one. - It shows you exactly what it will change in your global config and asks for a
y/N(defaults to No). See the Privacy section for the full list. On yes it:- adds seven telemetry settings to
~/.claude/settings.json, - adds a small
[otel]block to~/.codex/config.toml, and - installs a tiny background helper (a macOS LaunchAgent) that runs the meter.
- adds seven telemetry settings to
Then just code:
claude # or: codex (or open the Claude/Codex desktop apps)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.
That's it. No source, no second terminal. Your spend shows up in MarginFront under
your developer email, automatically, and the background meter restarts itself at
every login.
There is no
code-cost-claritycommand.initinstalls no binary on your PATH — typingcode-cost-clarity statuswill say "command not found" and nothing is wrong. Every command runs through npx, always.
Check on it anytime:
npx @marginfront/code-cost-clarity statusPrefer a live terminal view instead of the background meter? After init, run
npx @marginfront/code-cost-clarity run to stream spend in your terminal (Ctrl-C
stops it). You'll see a line per turn like:
[14:22:07] recorded [email protected] · in=3210 out=287 · server=$0.0232 · cc=$0.0236 event=9f0c2a71-...CI / scripted installs: add --no-prompt to init to skip BOTH questions (the
key paste and the consent prompt) and proceed. It will wire your global config
as listed above. Paste your key into ~/.marginfront-ccc/.env separately.
The background meter (always on, no terminal)
init (and start) install the forwarder as a macOS LaunchAgent, a small
background helper macOS manages for you. This is what makes "just code" work:
- It starts at login. No terminal to keep open, nothing to remember.
- It restarts itself if it ever stops (and survives a reboot).
- It needs no key in your shell. It reads your saved key from
~/.marginfront-ccc/.env, so a fresh login Just Works.
Manage it with three commands:
| Command | What it does |
| -------- | ----------------------------------------------------------------------------------------------------------- |
| start | (Re)installs + starts the background meter. init already does this; use it to bring it back after stop. |
| status | Tells you whether the meter is running, and shows the last lines of its output + error logs. |
| stop | Pauses the meter (the background daemon and any foreground run). start brings it back. |
What gets captured, and what doesn't. CCC meters the coding agents: it reads the
claude_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 env from~/.claude/settings.json/~/.codex/config.toml. - The Claude Code and Codex desktop apps don't read that env block and never see your
shell, so
initalso writes the same vars into the macOS launchd GUI-session env (launchctl setenv), which Dock-launched apps inherit. Quit and reopen a desktop app afterinitso it reads them at launch. (All four surfaces were confirmed capturing live.)
It does not capture:
- The Claude consumer chat app (the chat window) - the general assistant, not Claude Code, so it never emits the coding-agent telemetry CCC reads (it also runs in a sandbox separate from your machine).
- Anything in a cloud sandbox or not connected to this device - 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 here and emit to the local collector, CCC can't see it.
The commands
| Command | What it does |
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| init | The one setup command. Downloads the collector, prompts you to paste your MarginFront secret key (mf_sk_*), then, after a y/N consent prompt, wires telemetry into ~/.claude/settings.json + ~/.codex/config.toml and installs the background meter. Safe to re-run (never re-asks for a saved key, never clobbers your settings). --no-prompt skips both questions and proceeds (CI / scripts). |
| start | (Re)installs + starts the background meter (the LaunchAgent). init already runs this; use it to bring the meter back after stop. |
| status | Is the background meter running? Shows recent activity + the last lines of its error log. |
| preview <capture.json> | Prints the exact record it would send for one captured snapshot. Needs no MarginFront key, great for a dry run. |
| run | Streams your live spend in this terminal instead of the background daemon (Ctrl-C stops it + the collector). Add --fold-cache only for an unpriced model (see below). |
| stop | Pauses the meter: the background daemon and any foreground run. |
| uninstall | Stops everything, undoes every config change (the telemetry keys in settings.json, the Codex [otel] block, the background helper), and deletes the collector binary + runtime files (reclaims the ~360 MB). Keeps your settings. Add --purge to also delete your settings + MarginFront key. |
| help, version | The usual. |
Per-developer attribution is automatic
What makes "who spent what" work is user.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 config. Each developer 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 an
developer's sign-in doesn't surface an email, the connector 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/run
output if it ever kicks in.
The MarginFront API key is separate from the developer's identity: it's the connector's own credential for posting to MarginFront.
runneeds it;previewdoes not.
Shared one login across the team? Set a per-machine developer (CCC_DEVELOPER_EMAIL)
Some teams share one Claude/Codex login across the whole team. When that
happens, every developer's telemetry carries the same user.email, so all the
AI cost collapses onto one person and per-developer attribution breaks. CCC fixes
this without any change to how you log in, because CCC runs locally on each
laptop - so each laptop can carry its own developer identity.
During init we ask one question: which developer this machine's AI cost should
be attributed to. The prompt 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 keep the old behavior - auto-detect from whatever email the coding-agent login reports.
Whatever you choose is saved as 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 + start (or just re-run init).
Precedence at send time (highest wins):
CCC_DEVELOPER_EMAIL(this machine's developer) - if 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 (
user.email). - Otherwise the no-identity placeholder (
claude-code-no-identity/codex-no-identity).
If you don't set it (leave it blank), nothing changes - attribution is
byte-for-byte what it was before this feature: the login's own user.email, then
the placeholder.
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-prompt(CI / scripted) install skips this question and leavesCCC_DEVELOPER_EMAILunset.
Privacy (read this)
This tool is internal cost visibility, and being honest about what it turns on matters, so here is the full picture in plain English.
Your developer email travels in the telemetry, in plaintext, on purpose. That's
the whole point: per-developer cost attribution needs to know who ran the turn.
Claude Code stamps user.email (your logged-in account email) onto the usage
telemetry, the local collector writes it to a file on your machine, and the
forwarder POSTs it to MarginFront so the spend lands under your name. It is not
hashed or anonymized. If you don't want the email attached, sign in with a login
that doesn't surface one, and the usage falls back to the claude-code-no-identity
placeholder instead.
Telemetry becomes always-on, globally, for every Claude session. When you say
"yes" to the consent prompt, init writes seven telemetry settings into
~/.claude/settings.json. Both the claude command line and the Claude Desktop
app read that file, so from then on every Claude session on this computer
broadcasts usage to the local collector, not just the terminal you set it up in.
That's deliberate (it's what makes "just code" work and what captures Desktop), but
it's a real, persistent, machine-wide change, which is exactly why init asks first
and defaults to No.
The exact things init changes (and nothing else):
~/.claude/settings.json: adds these seven keys to the"env"block:CLAUDE_CODE_ENABLE_TELEMETRY,OTEL_METRICS_EXPORTER,OTEL_LOGS_EXPORTER,OTEL_EXPORTER_OTLP_PROTOCOL,OTEL_EXPORTER_OTLP_ENDPOINT,OTEL_METRIC_EXPORT_INTERVAL,OTEL_LOGS_EXPORT_INTERVAL. It backs the file up first and never overwrites a value you'd already set.~/.codex/config.toml: adds one fenced[otel]block (or, if you already have your own[otel], leaves it alone and prints the block to paste).~/Library/LaunchAgents/ai.marginfront.ccc.plist+ a copy of the meter program (the background helper).
Your MarginFront secret key is the one thing that never leaves
~/.marginfront-ccc/.env (mode 600). It is never written into
settings.json, config.toml, or any telemetry payload.
uninstall reverses every one of those changes:
- removes the LaunchAgent and the copied meter program,
- removes only the seven telemetry keys it added to
settings.json, and only if the value still matches what it wrote, so anything you changed survives, - removes only its own fenced
[otel]block fromconfig.toml, - deletes the collector binary + runtime files.
It keeps your saved key unless you add --purge. The one thing it will not touch
is your ~/.zshrc: if an older (0.4.x) setup left a source ~/.marginfront-ccc/.env
line there, uninstall reminds you about it but never edits your shell file silently.
Cache pricing
Claude Code reports four kinds of tokens (fresh input, output, cache-read, and
cache-creation), and Anthropic prices them differently. This connector 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 typed
cacheReadTokens/cacheWriteTokensfields; 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.
The dollar figure uses your MarginFront catalog's rate for each model. If a model
isn't priced yet, its usage lands 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.
Pricing note (catalog/ops): coding agents use Anthropic's 1-hour prompt cache, so set your catalog's cache-write rate to the 1-hour rate (2× base input), not the 5-minute rate (1.25× input). Reconciling a live session against
metadata.claudeCodeCostUsdconfirmed the 1-hour rate matches to the cent; the 5-minute rate reads ~30-40% low on a cache-heavy session.
A long-context model id like
claude-opus-4-8[1m]is normalized toclaude-opus-4.8to match MarginFront's pricing table. The raw id is kept inmetadata.rawModel.
Billable tool calls (automatic)
Tokens are the bulk of coding-agent spend, but the connector also forwards
billable tool calls (a paid web search, a metered MCP or API tool) as their
own line items, tagged by tool name. There's no client config: MarginFront's
pricing catalog decides what's billable. A tool with a price row gets priced; any
other tool lands NEEDS_COST_BACKFILL (a visible gap you can price later, never a
silent charge). Free built-ins (file reads, shell, grep) are forwarded too and
priced $0 by the catalog, so your usage view shows the full picture of tool
activity without inventing cost. Older CCC builds that still drop built-ins
client-side keep working unchanged - they simply don't send those $0 lines.
Also capture Codex (second source)
The same package captures Codex too. Nothing extra to install. Codex reports to the same local collector. You get three modes for free: Claude Code only, Codex only, or both (same developer's name on all of it).
init turns it on for you when you consent, it adds this [otel] block to
your ~/.codex/config.toml automatically:
[otel]
exporter = { otlp-http = { endpoint = "http://127.0.0.1:4318/v1/logs", protocol = "binary" } }The exporter is a table, the endpoint lives inside it and ends in
/v1/logs, and protocol sits in that same table. (A flat exporter = "otlp-http"
with a bare top-level endpoint silently exports nothing. Verified.)
The only case init won't write it for you is if you already have your own
[otel] settings. It won't rewrite your config; it just prints the block above so
you can merge it by hand. Either way, then run Codex normally. The background meter
(and run) already watches both sources from the one collector file; there's no
extra flag. uninstall removes only the block it added.
Why Codex's numbers are computed differently
Anthropic and OpenAI count tokens differently, and getting this wrong silently mis-charges you. For Codex, two of the token counts are already nested inside the others:
cached_token_countis already part ofinput_token_count.reasoning_token_countis already part ofoutput_token_count.
So we do not add reasoning on top of output, and we do not bill input and cached separately as if they were distinct. The accurate split we send is:
| MarginFront field | From Codex | Why |
| ----------------- | ---------------------------------------- | ---------------------------------------------------------------------------------------- |
| inputTokens | input_token_count - cached_token_count | the fresh, non-cached input only |
| cacheReadTokens | cached_token_count | priced at the cheaper cache-read rate |
| outputTokens | output_token_count | reasoning is already inside this, billed at the output rate, same as OpenAI bills it |
| (no cacheWrite) | n/a | OpenAI has no cache-creation tokens |
The raw reasoning, tool, and cached counts are recorded in metadata for
audit; they are never billed twice. (If you ever hit an unpriced Codex model,
--fold-cache still 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.)
Identity by sign-in mode (org seat vs ChatGPT login)
Per-developer attribution rides on user.email from Codex's telemetry, same idea
as Claude Code. Whether that email surfaces depends on how the developer signs in
to Codex (an org-managed or API-key sign-in stamps it; some interactive ChatGPT
logins may not). We never read that OpenAI key; the only thing that matters here is
whether the telemetry carries an email. If it doesn't surface, the usage isn't
dropped. It lands under the codex-no-identity placeholder with a fix hint,
exactly like the Claude path.
Edge cases to know
gpt-5-codexpricing row is a precondition. A full-rate row (including its cache rate) must exist in MarginFront's pricing catalog. Until it does, a Codex event landsNEEDS_COST_BACKFILL(a visible gap, safe), never a silent$0. This is an ops/catalog step, not something this package can seed.codex-auto-reviewis excluded. It's an internal pseudo-model, not a real billable model, so we drop it rather than mis-price it.codexandcodex execboth export the usage logs this package reads, verified against a live session. (Separately, some Codex versions don't emit OTel metrics, andcodex mcp-servertelemetry has had bugs, but neither is the logs stream we use, so neither affects capture here.)- The
[otel]block above is the verified shape. If a future Codex version changes it, keep the exporter a table pointed at the local collector on/v1/logs.
Confirm the dollar figure once. The exact Codex dollar figure should be confirmed against one real captured session: that Codex reports per-turn (not session-cumulative) token counts on the log stream, and that your developer email populates under your sign-in mode. The mapping above is the verified-safe default; one real session is enough to confirm it for your setup.
Confirm it landed (independent read-back)
KEY="<your MarginFront API key>"
curl -s -H "x-api-key: $KEY" \
"https://api.marginfront.com/v1/[email protected]&limit=5&sortBy=usageDate&sortOrder=desc"Maintain / shut off
- Collector version is pinned for safety. The installer downloads one known-good, checksum-verified collector release. Moving to a newer collector ships in a package update (so the integrity check always has a fingerprint to verify against); there is no per-run version override.
- Check on it:
statussays whether the background meter is running and shows the last lines of its logs. - Pause temporarily:
stoppauses the background meter (and any foregroundrun).startbrings it back; it also comes back on the next login. - Remove it:
uninstallstops everything, undoes every changeinitmade (the telemetry keys it added to~/.claude/settings.json, its[otel]block in~/.codex/config.toml, and the background helper) and frees the ~360 MB. It keeps your MarginFront key;uninstall --purgedeletes your settings too. The one thing it won't touch is~/.zshrc: if an older (0.4.x) setup left asource ~/.marginfront-ccc/.envline there,uninstallreminds you but never edits your shell file silently. Remove that line by hand if you like.
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/expired key, or you pasted a publishable key
(
mf_pk_*) where a secret key (mf_sk_*) is required. The forwarder POSTs usage, and publishable keys are rejected on writes. Pull a fresh secret key atapp.marginfront.com→ Build → API keys → "Create key pair" (https://app.marginfront.com/build?tab=keys). - HTTP 422 / validation error: body-shape mismatch. Run
preview <capture.json>and compare the record. - Collector file stays empty: telemetry isn't reaching the collector. Most
often that's because you skipped the consent prompt (so
~/.claude/settings.jsonwas never wired), so re-runinitand answery, then start a freshclaudesession. (Older manual setups could also be on gRPC/4317; the settings default to http/protobuf/4318, the transport that actually works with Claude Code.) 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
cumulativetodeltaprocessor isn't running. Re-runinitto rewrite the collector config. - Seeing
claude-code-no-identity(orcodex-no-identity)? Your login didn't surface an email. Use an org-managed Claude seat, or attach a customer mapping.
Security
- 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.
- Your MarginFront key never lives in this 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. It is never written into~/.claude/settings.jsonor~/.codex/config.toml. Only the six non-secret telemetry settings go there (see Privacy). - The published package contains only the built code and this README. Captured
telemetry, the collector binary, and your
.envare all kept out (and the package.gitignoreguards the source tree too).
For developers (technical appendix)
Input shape: OTLP/JSON, tree 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).
Value encoding (handled both ways): the console exporter emits the value as
asInt (a JSON string); the collector's file exporter emits it as asDouble (a
JSON number).
Grouping key: (user.email, model, session.id) → one MarginFront record per
group.
Token mapping: input→inputTokens, output→outputTokens,
cacheRead→cacheReadTokens, cacheCreation→cacheWriteTokens (Anthropic
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 and ignores the delta
preference env var. The collector's cumulativetodelta processor converts 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 + 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 key (the body can't override it).
Codex source (the second input shape)
Input shape: OTLP/JSON logs, tree resourceLogs[].scopeLogs[].logRecords[] (a
different tree than Claude Code's resourceMetrics). The usage event is named
codex.sse_event; we detect it 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). No
cumulativetodelta 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" (R&D). 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.
