Skip to content

fielding/thinktax

Repository files navigation

thinktax

A multi-provider LLM cost tracker for developers using Claude Code, Cursor, Codex CLI, and more.

Track your AI coding assistant spending across all your tools in one place, with timezone-aware reporting, per-project attribution, and macOS menu bar integration via Sketchybar.

output

Features

  • Multi-provider support - Claude Code, Cursor, Codex CLI, Glean, Review Crew, OpenClaw, Apprentice
  • Actual-spend-first philosophy - Uses reported costs when available, falls back to estimates
  • Timezone-aware reporting - Today/MTD/YTD windows respect your local timezone
  • Multiple breakdowns - By provider, project, model, or source
  • Sketchybar integration - Live spending in your macOS menu bar with animated breakdowns
  • Offline-first - All data stored locally, works without network

Installation

# Clone and build
git clone https://github.com/fielding/thinktax.git
cd thinktax
npm install
npm run build

# Link globally (optional)
npm link

Quick Start

# 1. Create config file
cp config.sample.toml ~/.config/thinktax/config.toml  # Linux
cp config.sample.toml ~/Library/Application\ Support/thinktax/config.toml  # macOS

# 2. Edit config with your settings (see Configuration below)

# 3. Collect usage data
thinktax refresh

# 4. View your spending
thinktax status

CLI Reference

thinktax refresh

Collect latest usage from all configured providers and write normalized events.

thinktax refresh
# Collected 1234 events (56 new). Claude 800, Codex 400, Cursor 34, OpenClaw 0, Apprentice 0, Glean 0, ReviewCrew 0.

thinktax status

Show usage totals with optional breakdowns.

# Default: today and month-to-date
thinktax status

# JSON output
thinktax status --json

# Breakdown by provider, project, model, or source
thinktax status --breakdown provider
thinktax status --breakdown project

# Specific time windows
thinktax status --today
thinktax status --mtd
thinktax status --ytd
thinktax status --all

thinktax sketchybar

Output payload for Sketchybar integration.

thinktax sketchybar              # Plain text: "today $12.34"
thinktax sketchybar --format json  # JSON with full breakdown

thinktax popup

Detailed breakdown for popup displays.

thinktax popup
thinktax popup --format json
thinktax popup --today --mtd

thinktax graph

Display cost over time as an ASCII chart in the terminal.

# Default: last 30 days, all providers
thinktax graph

# Last 7 days
thinktax graph --days 7

# Filter by provider
thinktax graph --provider anthropic
thinktax graph --provider cursor
thinktax graph --provider openai

# Adjust chart height
thinktax graph --height 20

Example output:

Total cost - last 30 days
──────────────────────────────────────────────────
 $273.09 ┼╮
 $185.22 ┤│││  ╭╮ ╭╮        ╭╮        ╭
  $97.35 ┤  ╰─╯ │╭╯│  ││  ╭╮││ │││╰╮  │
   $9.48 ┤                           ╰╯
  01-04                  02-02
──────────────────────────────────────────────────
Total: $2818.26  |  Avg: $93.94/day  |  Max: $273.09

thinktax doctor

Diagnostics for troubleshooting.

thinktax doctor
# Config: ~/.config/thinktax/config.toml (found)
# Data dir: ~/.local/share/thinktax
# Last claude refresh: 2026-02-02T10:30:00Z
# Last codex refresh: 2026-02-02T10:30:00Z

Global Options

--config <path>    # Override config file path
--timezone <tz>    # Override timezone (e.g., "America/New_York")

Configuration

Create a config file at:

  • Linux: ~/.config/thinktax/config.toml
  • macOS: ~/Library/Application Support/thinktax/config.toml
[ui]
timezone = "America/Los_Angeles"
includeUnknown = false  # Include unknown models in totals

[claude]
projectsDir = "~/.claude/projects"

[codex]
home = "~/.codex"

# Cursor - typically no config needed! Auth is auto-extracted from Cursor's state.vscdb
# Uncomment only if you need Team Admin API fallback or custom paths

# [cursor.team]
# # Team Admin API (optional, for team admins only)
# apiKey = "${CURSOR_API_KEY}"
# etagTtlMinutes = 15

# [cursor.local]
# # Custom state.vscdb path (only if non-standard Cursor installation)
# stateVscdbPath = "~/Library/Application Support/Cursor/User/globalStorage/state.vscdb"

# Project mappings (optional)
[[projects.mappings]]
match.instanceId = "claude-instance-folder"
id = "my-project"
name = "My Project"
root = "/path/to/project"

[[projects.mappings]]
match.pathPrefix = "/path/to/another-project"
name = "Another Project"

Environment Variable Interpolation

Config values support environment variables:

[cursor.team]
apiKey = "${CURSOR_API_KEY}"
token = "$CURSOR_TOKEN"

Data Storage

All data is stored locally:

Location Linux macOS
Config ~/.config/thinktax/ ~/Library/Application Support/thinktax/
Data ~/.local/share/thinktax/ ~/Library/Application Support/thinktax/data/

Data directory structure:

data/
├── events/
│   └── YYYY-MM-DD.jsonl    # Daily normalized events
├── snapshots/
│   └── YYYY-MM-DD.summary.json  # Pre-aggregated summaries
└── state/
    ├── sync.json           # Last refresh timestamps
    └── etag.json           # API response cache

Sketchybar Integration (macOS)

thinktax includes a Sketchybar module for live spending in your menu bar.

Install

cd thinktax
./sketchybar/install.sh

This will:

  1. Copy plugin files to ~/.config/sketchybar/
  2. Install a launchd job for automatic data refresh every 5 minutes
  3. Configure the CLI path

Configure Sketchybar

Add to your ~/.config/sketchybar/sketchybarrc:

source "$ITEM_DIR/thinktax.sh"

Then reload:

sketchybar --reload

Features

  • Click to expand provider breakdown (Cursor, Claude, Codex)
  • Auto-collapse after 5 seconds
  • Color indicators:
    • Red label = stale data (>24h since refresh)
    • Normal = fresh data
  • Shows today's total and MTD in parentheses

Uninstall

./sketchybar/uninstall.sh

Collectors

Claude Code

Discovers JSONL files under ~/.claude/projects/ and extracts usage from assistant responses.

Supported data:

  • Token counts (input, output, cache read/write)
  • Model names
  • Timestamps
  • Project attribution via instance folder
  • Subscription vs API billing detection (via SessionStart hook)

Codex CLI

Parses session logs from ~/.codex/ with cumulative-to-delta token conversion.

Supported data:

  • Token counts per session
  • Model names
  • Project attribution via git root detection

Cursor

Primary: Dashboard API (automatic, zero config!)

thinktax automatically extracts your session token from Cursor's internal SQLite database (state.vscdb) and uses it to fetch your actual billing data from Cursor's dashboard API. This means:

  • No API keys to configure - just install and run
  • Actual spend in cents - reported costs, not estimates
  • Per-request granularity - every API call with token counts and costs
  • 30-day lookback - fetches full usage history with pagination
  • ETag caching - respects rate limits, 15-minute cache TTL

The auth token is automatically refreshed from your Cursor installation each time you run thinktax refresh.

Fallback: Team Admin API (optional, for team admins)

If you have team admin credentials, you can configure the Team API as a fallback:

[cursor.team]
apiKey = "${CURSOR_API_KEY}"
# or: email + token for Basic auth

Supported data:

  • Token counts (input, output, cache read/write)
  • Model breakdown (claude-3-5-sonnet, gpt-4o, agent_review, etc.)

OpenClaw

Parses session JSONL files synced from OpenClaw (Kimi-based coding assistant). Sessions are discovered in the configured sessionsDir or the default data directory.

Supported data:

  • Token counts (input, output, cache read/write)
  • Model names and provider attribution
  • Subscription billing via [openclaw.billing] config
[openclaw]
# sessionsDir = "~/Library/Application Support/thinktax/data/openclaw-sessions"

[openclaw.billing]
defaultMode = "subscription"
plan = "kimi"

Apprentice

Parses usage logs from ~/.apprentice/usage/. All Apprentice sessions are billed as API usage by default.

Supported data:

  • Token counts per request
  • Model and provider attribution
  • Request latency

Glean

Tracks API costs from Glean, an automated AI session logger that summarizes coding sessions into Obsidian daily notes. Glean makes LLM calls (typically Claude Haiku) to generate summaries, and logs per-call usage to ~/.local/state/glean/usage/.

Supported data:

  • Token counts per summarization call
  • Model and provider attribution
  • Summarization reason (idle, manual, lock, sleep)
[glean]
# usageDir = "~/.local/state/glean/usage"  # default

Review Crew

Tracks summarizer costs from Review Crew, a multi-agent PR review tool that runs Claude, GPT, and Gemini reviewers in parallel. Only the orchestrator's summarizer call is collected here — reviewer agent costs are already captured by the Claude Code and Cursor collectors respectively.

Supported data:

  • Exact token counts and cost for the summarizer call
  • PR metadata (repo, PR number, title, verdict)
[reviewCrew]
# historyDir = "~/.review-crew/history"  # default

Subscription Billing (Claude Max Plan)

If you use the Claude.ai Max plan ($200/month) alongside API key billing, thinktax can distinguish between the two so subscription-covered usage shows as $0 instead of inflated API-rate estimates.

How it works

Claude Code's JSONL logs don't record which auth method was used. thinktax solves this with a lightweight Claude Code SessionStart hook that checks ANTHROPIC_API_KEY at process start:

  • Empty/unset = OAuth authentication = subscription (Max plan)
  • Set = API key = pay-per-token

This pairs naturally with aliases like:

alias claude='ANTHROPIC_API_KEY= command claude --dangerously-skip-permissions'   # Max plan
alias claude-api='command claude --dangerously-skip-permissions'                    # API key

Setup

1. Install the hook (included in hooks/):

mkdir -p ~/.claude/hooks
cp hooks/thinktax-billing-tag.sh ~/.claude/hooks/
chmod +x ~/.claude/hooks/thinktax-billing-tag.sh

2. Register in Claude Code settings (~/.claude/settings.json):

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "startup",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/thinktax-billing-tag.sh",
            "timeout": 5
          }
        ]
      }
    ]
  }
}

3. Add billing config to your thinktax config:

[claude.billing]
defaultMode = "subscription"  # Max plan users: subscription is the correct default
monthlyCost = 200.00
plan = "max"

4. Reprocess existing events to apply billing tags retroactively:

thinktax reprocess

What you see

Today: $0.00 (in 38915, out 21628) [plan saved $22.09]
MTD:   $45.23 (in 500000, out 30000) [plan saved $142.49]
  • final_usd = $0 for subscription sessions (covered by flat fee)
  • estimated_usd still tracks "what it would have cost" at API rates
  • [plan saved $X] shows your subscription savings
  • Use --breakdown billing to see subscription vs API split

Billing registry

The hook writes session tags to ~/.config/thinktax/billing-sessions.jsonl:

{"session_id":"abc123","billing":"subscription","ts":"2026-02-05T23:20:00Z"}
{"session_id":"def456","billing":"api","ts":"2026-02-06T01:15:00Z"}

Sessions without a registry entry fall back to claude.billing.defaultMode.

Pricing

Pricing data for 60+ models is included in pricing/models.json. Models are matched by substring, so variants like claude-3-5-sonnet-20241022 match claude-3-5-sonnet.

To update pricing:

  1. Edit pricing/models.json
  2. Rebuild: npm run build

Unknown models are excluded from totals by default. Set includeUnknown = true in config to include them with zero cost.

Troubleshooting

"No events collected"

  1. Check collector paths in config match your actual installations
  2. Run thinktax doctor to verify paths exist
  3. Ensure you have usage data (run some prompts first)

"Stale data" indicator

Data older than 24 hours shows as stale. Run thinktax refresh or check if the launchd job is running:

launchctl list | grep thinktax

Cursor API errors

  1. Ensure Cursor is installed and you've logged in at least once
  2. Run thinktax doctor to check if auth extraction is working
  3. If using a non-standard Cursor path, set cursor.local.stateVscdbPath in config
  4. Team Admin API is optional and only needed for team-level data

Sketchybar not updating

  1. Check the refresh log: tail -f ~/.local/state/thinktax/refresh.log
  2. Manually trigger: sketchybar --trigger thinktax
  3. Verify launchd job: launchctl list | grep thinktax

Development

# Install dependencies
npm install

# Build
npm run build

# Watch mode
npm run dev

# Run tests
npm test

License

MIT - see LICENSE

Contributing

See CONTRIBUTING.md for development setup and guidelines.

About

A token cost tracker for Claude Code, Cursor, and Codex CLI

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors