Spend · Billing Guard CLI

Billing Guard CLI

A deterministic command-line audit of your Claude spend. It reads the Claude Code session telemetry already on your machine, pulls Anthropic's own Usage & Cost reports when you give it an Admin key, recomputes what each day should have cost from raw token counts, and flags the difference. Zero model tokens: detection is plain code, so running it costs nothing. See the Billing Guard overview for why this exists.

What it is

repoops-billing-audit is the portable shell around the Billing Guard reconciliation engine. One run:

  • Reads local Claude Code JSONL transcripts (default ~/.claude/projects, override with --claude-dir) and folds them into a day-by-model token ledger. Your ground truth; it never leaves the machine.
  • Pulls the Anthropic Admin Usage & Cost API for the same window when ANTHROPIC_ADMIN_KEY is set. The provider's claim.
  • Recomputes expected cost from token counts times a versioned rate card and compares inside a tolerance band (default 2%).
  • Prints a findings table (day, model, expected, billed, delta, signature) plus a coverage summary, and writes an evidence bundle JSON shaped for a finance dispute.

Exit codes make it automation-friendly: 0 everything reconciles, 1 findings, 2 configuration error.

Prerequisites

  • Node 20 or newer. Check with node --version.
  • Claude Code telemetry present. If you use Claude Code on this machine, transcripts already live under ~/.claude/projects. No setup needed.
  • Optional: an Anthropic Admin key. From console.anthropic.com: Settings, then API keys, then Create Admin Key. Set it as ANTHROPIC_ADMIN_KEYin the environment. Without it the CLI runs in telemetry-only mode: it prints the expected-cost ledger it can compute locally plus a clear "cannot verify without an Admin key" note. It never fakes a finding.

Install and run

Not on the public npm registry yet
The RepoOps repository is private, so a public npx repoops-billing-audit does not work yet: publishing the package is a pending operator decision. Until then, run it from a checkout, or via the GitHub form if you have access to the repository.
  1. From a checkout (works today)

    git clone https://github.com/sellingascode/repo-dashboard.git
    cd repo-dashboard
    npm install
    node bin/billing-guard.mjs --window 30
  2. Via npx from GitHub (authorized users)

    npx --package github:sellingascode/repo-dashboard repoops-billing-audit --window 30
    This resolves the package straight from the repository, so it needs GitHub access to it.
  3. With verification enabled

    # PowerShell
    $env:ANTHROPIC_ADMIN_KEY = "sk-ant-admin01-..."
    node bin/billing-guard.mjs --window 30 --out billing-guard-report.json
    
    # bash / zsh
    ANTHROPIC_ADMIN_KEY=sk-ant-admin01-... node bin/billing-guard.mjs --window 30

Flags:

  • --window <days> lookback window (default 30).
  • --tolerance <pct> tolerance band percent (default 2).
  • --claude-dir <path> Claude home directory (default ~/.claude).
  • --out <path> evidence bundle path (default ./billing-guard-report.json).
  • --json print the evidence bundle JSON instead of the table (for scripts and CI).

Scheduled background runs

The audit is cheap and read-only, so a daily background run on a managed laptop is the natural setup. Neither example below needs admin rights.

Windows: per-user Scheduled Task. Runs daily at 09:00 under your own account:

schtasks /Create /SC DAILY /ST 09:00 /TN "RepoOpsBillingAudit" ^
  /TR "node C:\path\to\repo-dashboard\bin\billing-guard.mjs --window 30 --out C:\Users\you\billing-guard-report.json"

Set ANTHROPIC_ADMIN_KEY as a user environment variable first (setx ANTHROPIC_ADMIN_KEY sk-ant-admin01-..., then a fresh session picks it up). Remove the task with schtasks /Delete /TN "RepoOpsBillingAudit".

macOS: LaunchAgent. Save as ~/Library/LaunchAgents/ai.repoops.billing-audit.plist, then launchctl load it:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
  <key>Label</key><string>ai.repoops.billing-audit</string>
  <key>ProgramArguments</key><array>
    <string>/usr/local/bin/node</string>
    <string>/path/to/repo-dashboard/bin/billing-guard.mjs</string>
    <string>--window</string><string>30</string>
    <string>--out</string><string>/Users/you/billing-guard-report.json</string>
  </array>
  <key>EnvironmentVariables</key><dict>
    <key>ANTHROPIC_ADMIN_KEY</key><string>sk-ant-admin01-...</string>
  </dict>
  <key>StartCalendarInterval</key><dict>
    <key>Hour</key><integer>9</integer><key>Minute</key><integer>0</integer>
  </dict>
</dict></plist>
launchctl load ~/Library/LaunchAgents/ai.repoops.billing-audit.plist
Wire the exit code to an alert
Exit code 1 means findings. In any wrapper script, treat a nonzero exit as "open the evidence bundle": the JSON at --outcarries both sides' numbers per flagged day.

Reading the findings table

Each finding is one row: day, model, expected, billed, delta, signature. The signatures:

  • variance-band: the billed day total exceeds the telemetry-recomputed expected cost beyond the tolerance band.
  • ghost-usage: the provider reports usage for a day and model on which local telemetry (which was capturing that day) logged zero tokens.
  • rate-check: the billed amount implies a per-token price matching no known rate-card row.
  • duplicate-burst: provider-reported usage is at least 2x the locally logged usage for the same day.
  • delta-alarm: day-over-day billed total grew 3x or more.
  • contradiction: the provider's own surfaces disagree; the usage report shows zero tokens while the cost report bills money.

Below the table, the coverage summary is as important as the findings. Days the local telemetry did not capture are listed as cannot-verify, never flagged: missing coverage is not evidence of overbilling. Structural blind spots (Priority Tier costs, code-execution charges) appear as cannot-cross-verify annotations.

The tolerance band

Anthropic explicitly documents client-side cost figures as approximations, so Billing Guard never compares dollars for cent-equality. Token counts are the stable primitive: expected cost is recomputed from your token counts times the published per-model rates, then compared inside the band (default 2%, --tolerance to adjust). A day inside the band reconciles; a day outside it becomes a finding with the raw numbers attached. If your baseline drift runs hotter than 2%, widen the band before trusting the alerts; a band tighter than the real-world approximation noise produces noise, not findings.

Honest limits

  • Telemetry is opt-in and local. The ground-truth ledger only covers machines and sessions where Claude Code transcripts exist. A day with no local capture is reported as cannot-verify, not as a finding.
  • Subscription plans without Admin API access cannot verify. The Usage & Cost reports require an organization Admin key. Claude subscription plans (Pro/Max) do not expose invoice-side data this way, so the CLI can only run telemetry-only mode there.
  • Known Admin API blind spots. Priority Tier costs are excluded from the cost report and code execution is excluded from the usage report; both are surfaced as annotations rather than silently mismatching.
  • The rate card is versioned, not omniscient. A model missing from the card makes its day cannot-verify rather than producing a made-up expected cost.

What's next

The dashboard surface for Billing Guard (a section on the spend tab family with the same findings, plus an evidence-bundle export button) is in development. Follow the Billing Guard overview or read the research post on why AI invoices deserve an audit trail.