Accurate token usage & cost tracker for Claude Code
Claude Code's built-in tracking undercounts output tokens by ~2x
claudeoo captures the real numbers.
Why? • Features • Installation • Usage • How It Works • Query Commands
Claude Code stores session history in ~/.claude/projects/<project>/<session-id>.jsonl, but these logs undercount output tokens by ~2x.
Why? Claude streams responses in chunks and logs usage mid-stream. The final message_delta event, the one Anthropic actually bills you for, never gets written to disk.
Real impact:
A 128-turn Opus session (49 min) shows:
- JSONL logs: 23,725 output tokens
- Actual usage: 45,050 output tokens
- Gap: ~$0.47/session adds up fast for power users
No stop_reason, incomplete cache tokens, no way to reconcile costs against your Anthropic bill.
The fix: claudeoo intercepts the live SSE stream and captures the final usage event, the same numbers Anthropic bills you for.
| Feature | claudeoo | ccusage | cccost |
|---|---|---|---|
| Accurate output tokens | ✅ Final SSE event | ❌ Mid-stream* | ✅ Final SSE event |
| Real-time tracking | ✅ | ❌ Post-hoc only | ✅ |
| Per-turn breakdowns | ✅ | ❌ | ❌ |
| Query CLI | ✅ stats / sessions / export | ✅ daily / monthly / session | ❌ |
| SQLite storage | ✅ | ❌ | ❌ |
| Full API logs | ✅ | ❌ | ❌ |
| Session reports | ✅ | ❌ | ❌ |
| Auto-updated pricing | ✅ | ❌ Hardcoded | ❌ Hardcoded |
| Content type tracking | ✅ thinking / text / tool_use | ❌ | ❌ |
| CSV / JSON export | ✅ | ✅ JSON only | ❌ |
| Zero dependencies | ✅ | ❌ | ✅ |
| Drop-in replacement | ✅ | ✅ | ✅ |
*ccusage reads Claude's built-in JSONL logs which capture usage snapshots mid-stream, before the final
message_deltaarrives, resulting in undercounted output tokens.
npm install -g claudeooRequirements:
| Requirement | Version |
|---|---|
| Node.js | >= 22.5.0 (for built-in node:sqlite) |
| Claude Code | npm-installed: npm install -g @anthropic-ai/claude-code |
git clone https://github.com/stoic/claudeoo.git
cd claudeoo
npm install
npm run build
npm linkUse claudeoo exactly like you use claude all arguments are passed through:
# Interactive session
claudeoo
# Single prompt
claudeoo -p "explain this code"
# With Claude flags
claudeoo --dangerously-skip-permissions --model sonnet
# With verbose tracking
claudeoo --coo-verbose -p "hello"Your terminal tab title updates in real-time during streaming:
[claudeoo] 💰 $0.47 | ↑125K ↓3.2K | turn 5 | ⏳
After each session ends, you get a clean summary with token counts, cost, and file paths:
# Today's usage stats
claudeoo stats --today
# This week / all time
claudeoo stats --week
claudeoo stats --all
# List recent sessions
claudeoo sessions --limit 10
# Per-turn breakdown for a session
claudeoo session <session-id>
# Export all data
claudeoo export --format csv --output usage.csv
claudeoo export --format json
# Show current pricing
claudeoo pricing --showclaudeoo finds your npm-installed Claude Code (cli.js) and launches it with a node --require preload that wraps globalThis.fetch(). It observes the Anthropic API's SSE stream without modifying it, Claude works exactly as normal.
claudeoo [args...]
→ node --require interceptor-loader.js <claude-cli.js> ...args
→ interceptor wraps globalThis.fetch()
→ filters /v1/messages calls
→ observes SSE stream via Symbol.asyncIterator monkey-patch
→ captures message_start (input tokens) + message_delta (output tokens)
→ writes to ~/.claudeoo/usage.db + JSONL + session reports
→ updates terminal title with live cost
Each API call records:
| Field | Description |
|---|---|
| Input tokens | Prompt tokens sent to the API |
| Output tokens | Completion tokens (accurate, from final event) |
| Cache creation | Tokens written to prompt cache |
| Cache read | Tokens read from prompt cache |
| Thinking chars | Characters in thinking/reasoning blocks |
| Text chars | Characters in text response blocks |
| Tool use chars | Characters in tool_use blocks |
| Model | Model ID used for the call |
| Cost | Calculated cost in USD |
| Duration | Wall-clock time per API call |
| Stop reason | Why the model stopped (end_turn, tool_use, etc.) |
| Flag | Description |
|---|---|
--coo-verbose |
Real-time per-call logging to stderr |
--coo-no-db |
Skip SQLite, write JSONL only |
These flags are consumed by claudeoo and not passed to Claude.
All data is stored in ~/.claudeoo/:
~/.claudeoo/
├── usage.db # SQLite database (queryable)
├── pricing.json # Auto-updated model pricing
├── sessions/
│ └── <session-id>.jsonl # Per-call JSONL records
├── reports/
│ └── <session-id>.json # Detailed session report (JSON)
└── logs/
└── <session-id>.jsonl # Full API request/response log
Pricing is auto-fetched from Anthropic's pricing page on every startup (5s timeout, falls back to cached). Supports all current Claude models including Opus, Sonnet, and Haiku variants with cache pricing.
claudeoo pricing --show| Model | Input | Output | Cache Write | Cache Read |
|---|---|---|---|---|
| Claude Opus 4.6 | $5.00 | $25.00 | $6.25 | $0.50 |
| Claude Sonnet 4.6 | $3.00 | $15.00 | $3.75 | $0.30 |
| Claude Haiku 4.5 | $1.00 | $5.00 | $1.25 | $0.10 |
Per million tokens. Full pricing in pricing.json.
claudeoo/
├── package.json # npm package config
├── tsconfig.json # TypeScript config
└── src/
├── cli.ts # Entry point, argument parsing, command routing
├── run.ts # Find Claude cli.js, spawn with interception
├── interceptor-loader.js # CommonJS preload (must stay .js)
├── interceptor.ts # fetch() wrapper, SSE parser, content block tracker
├── recorder.ts # Write records to JSONL + SQLite + full logs
├── db.ts # SQLite schema, queries for stats/sessions/export
├── pricing.ts # Model pricing resolver (fuzzy match model IDs)
├── pricing.json # Shipped default pricing data
├── update-pricing.ts # Auto-fetch latest pricing from Anthropic docs
├── types.ts # All TypeScript interfaces
├── format.ts # Terminal colors, table formatting, session summary
├── stats.ts # claudeoo stats command
├── sessions.ts # claudeoo sessions / session <id> commands
└── export.ts # claudeoo export command (CSV/JSON)
Inspired by cccost by Mario Zechner
MIT

