Mood stabilizer for your AI bill.
Weighed, measured, kept in balance. One number, every provider.
Status: v0.3.x. Anthropic + OpenAI + OpenRouter + ElevenLabs adapters wired. SwiftBar menubar plugin + Claude Code / cship status-line wrapper shipped. Daemon split (lithiumd) parked until polling cadence demands it.

CLI. The original surface. Same numbers, different paint.
You use Anthropic. You also use OpenAI. And OpenRouter. Maybe a local model. At the end of the month you have no idea what you spent. Each provider has its own dashboard, none of them talk to each other, and you've been doing the math in a spreadsheet, badly.
lithium is a tiny local daemon that polls every provider you use, normalizes the numbers into one SQLite database, and answers exactly one question:
How much am I actually spending on LLMs this month, across everything, fixed and variable?
That's the whole product. No web dashboard, no SaaS, no telemetry, no analytics. The data lives on your machine. The CLI prints the answer.
Three things go wrong when you run agents across multiple providers:
- You don't notice runaway cost until the bill arrives. A misconfigured Whetstone wave or a forgotten cron can burn $200 in a day before you check.
- Fixed costs (Max plans, monthly subscriptions) and variable costs (per-token API) live in different mental buckets. Most operators only track one. Both add up.
- Cross-provider visibility is nobody's job. Anthropic shows you Anthropic. OpenAI shows you OpenAI. The aggregate is your problem.
lithium makes it the daemon's problem.
| Feature | Description |
|---|---|
lithium today |
Today's spend, by source, with totals |
lithium month |
Month-to-date + projected end-of-month |
lithium status |
One-line spend output for statusline integrations |
lithium adapters |
List configured providers + last-poll status |
lithium config |
Edit ~/.config/lithium/config.toml in $EDITOR |
lithium doctor |
Verify config + connectivity + DB health |
| Anthropic | Cost Report admin API + Claude Code local-state reader |
| OpenAI | /v1/organization/costs admin API per-day USD by line item |
| OpenRouter | /api/v1/key (regular API key works) for daily/weekly/monthly |
| ElevenLabs | /v1/user/subscription (regular API key): forecasted monthly USD + character usage |
| Fixed costs | Declare flat-rate subscriptions (Max, ChatGPT Pro) for true total |
| SQLite storage | All data local at ~/.local/share/lithium/usage.db |
| No telemetry | Nothing leaves your machine. Period. |
# 1. Install
cargo install --git https://github.com/shawnpetros/lithium
# 2. Initialize config + storage
lithium config # opens ~/.config/lithium/config.toml in $EDITOR
lithium init # creates the SQLite database
# 3. Add at least one provider's key to the config (uncomment + paste):
# [providers.anthropic] admin_api_key = "sk-ant-admin01-..."
# [providers.openai] admin_api_key = "sk-admin-..."
# [providers.openrouter] api_key = "sk-or-..."
# [providers.elevenlabs] api_key = "sk_..."
# 4. Pull data
lithium poll
# 5. Look at it
lithium today
lithium monthEach provider is independent. Wire only the ones you use. See the per-provider notes below for where to generate each key.
The Cost Report API requires an admin key (sk-ant-admin01-...), distinct from a regular API key (sk-ant-api03-...). On personal accounts, admin keys are gated behind organization existence:
- Go to https://platform.claude.com/settings
- If you don't have an org: walk through "Create an organization" first. Personal accounts become 1-person orgs (you're the owner).
- https://platform.claude.com/settings/admin-keys → Create Admin Key, name it
lithium-local - Paste under
[providers.anthropic] admin_api_key
lithium doctor prints the key prefix so you can verify the type at a glance.
/v1/organization/costs also requires an admin key (sk-admin-...):
- https://platform.openai.com/settings/organization/admin-keys
- Create admin key → paste under
[providers.openai] admin_api_key
/api/v1/key accepts any OpenRouter API key. No admin / management key dance:
- https://openrouter.ai/keys → create or copy an existing key
- Paste under
[providers.openrouter] api_key
Bonus: OpenRouter pre-aggregates usage_daily / usage_weekly / usage_monthly, so polling once gives you all three at once.
/v1/user/subscription returns next_invoice.amount_due_cents (forecasted monthly charge) plus character usage vs limit:
- https://elevenlabs.io/app/settings/api-keys → create or copy a key
- Paste under
[providers.elevenlabs] api_key
Single row per poll covers the current calendar month; subsequent polls within the same month UPSERT in place. Tier name lands in the model label, character usage in raw_payload for future surface use.
Output looks like:
lithium - 2026-04-27
Anthropic
API direct $4.21 (claude-sonnet-4-6: $3.80, claude-haiku-4-5: $0.41)
Claude Code session 47% used (resets in 1h 12m)
Claude Code weekly 23% used (resets in 4d 2h)
Total today: $4.21
lithium status emits one short line, no trailing newline, ready to compose into any statusline tool that runs a shell command and embeds stdout.
$ lithium status
⚖ $15.17
$ lithium status --no-icon
$15.17
$ lithium status --prefix Li
Li $15.17
$ lithium status --silent-zero # empty when month-to-date is $0
$ lithium status --threshold-color # paints by budget (cream / brass / oxblood)
$ lithium status --json # machine-readable
Full flag list: --no-icon, --icon <STR>, --prefix <STR>, --silent-zero, --threshold-color, --json, --decimals <N>. Default icon is U+2696 (⚖); Nerd Font users can swap to U+F24E ( ) via --icon.
Starship / cship. cship hands $custom.X tokens through to starship, so the same block works for both. Put this in ~/.config/starship.toml:
[custom.lithium]
command = 'lithium status'
when = 'command -v lithium >/dev/null 2>&1'
format = '[$output]($style) '
style = 'fg:#F08222'
shell = ['bash', '--noprofile', '--norc']For cship users, also reference the module from ~/.config/cship.toml:
[cship]
lines = ["$directory$git_branch$cship.model $custom.lithium"]Then point Claude Code's statusLine.command at cship and you're done. Bare starship users add $custom.lithium to your normal format string.
oh-my-posh. Add a command segment to your theme JSON:
{
"type": "command",
"style": "plain",
"foreground": "#F08222",
"properties": { "command": "lithium status --silent-zero" }
}tmux. Edit ~/.tmux.conf:
set -g status-right '#[fg=#F08222]#(lithium status)#[default] %H:%M'
Powerlevel10k. Add a custom segment in ~/.p10k.zsh:
function prompt_lithium() {
local out
out=$(lithium status --silent-zero) || return
[[ -n $out ]] && p10k segment -f 208 -t "$out"
}
typeset -g POWERLEVEL10K_RIGHT_PROMPT_ELEMENTS+=(lithium)Bare bash / zsh PS1. Direct command substitution:
PS1='$(lithium status --silent-zero) \w \$ 'cship exports CSHIP_* env vars (CSHIP_MODEL, CSHIP_CONTEXT_PCT, CSHIP_COST_USD, etc) to every starship module <name> subprocess it spawns. That means a [custom.X] block can render the same Claude Code data cship's native modules see, with full control over format and styling. Useful when you want to:
- drop the powerline branch glyph that cship's
git_branchemits but won't let you customize via its own config - drop the auto-appended
(1M context)annotation thatcship.modeladds for 1M-variant opus models - add a dim
(N%)context-window indicator thatcship.context_bardoesn't expose
Add to ~/.config/starship.toml:
# Strip the powerline branch glyph. cship's $git_branch passes through
# starship, so [git_branch] config applies even though [cship.git_branch]
# config wouldn't.
[git_branch]
symbol = ""
# Plain model name without Claude Code's auto-appended " (1M context)"
# suffix. CSHIP_MODEL holds display_name verbatim including the annotation.
[custom.model]
command = 'echo "${CSHIP_MODEL% (1M context)}"'
when = '[ -n "$CSHIP_MODEL" ]'
format = '$output'
shell = ['bash', '--noprofile', '--norc']
# Dim (N%) context indicator.
[custom.context]
command = 'echo "($CSHIP_CONTEXT_PCT%)"'
when = '[ -n "$CSHIP_CONTEXT_PCT" ]'
format = '[$output]($style)'
style = 'dimmed'
shell = ['bash', '--noprofile', '--norc']
# Nerd Font apothecary balance glyph (U+F24E) as the lithium label.
# Trailing space in the format gives breathing room before the amount.
# Replace the printf command with `printf "Li"` if you don't have a Nerd Font.
[custom.balance_icon]
command = 'printf "\xef\x89\x8e"'
when = 'command -v lithium >/dev/null 2>&1'
format = '$output '
shell = ['bash', '--noprofile', '--norc']
# Override the canonical [custom.lithium] block from above with a threshold-
# painted variant: lithium emits the bare amount, --threshold-color paints by
# % of [budget] monthly_variable_usd:
# <50% cream neutral
# 50-75% ember early warning (orange)
# 75-100% brass approaching limit
# >100% oxblood over budget
[custom.lithium]
command = 'lithium status --no-icon --threshold-color'
when = 'command -v lithium >/dev/null 2>&1'
format = '$output '
shell = ['bash', '--noprofile', '--norc']And reference them from ~/.config/cship.toml:
[cship]
lines = ["$directory$git_branch$custom.model $custom.context $custom.balance_icon $custom.lithium"]Renders: lithium on main Opus 4.7 (12%) $15.17 where the amount auto-paints by budget. Set [budget] monthly_variable_usd in your lithium config to enable threshold paint; without it, the amount stays cream regardless of spend. No wrapper script anywhere; pure declarative config.
The first user-visible UI surface lands as a SwiftBar plugin: glance at month-to-date variable spend in your menubar, drop the menu down for the per-provider breakdown and the projected end-of-month total. Cream + brass thresholds match the alchemy/leather aesthetic.
# Prerequisites: SwiftBar (brew install --cask swiftbar) + jq (brew install jq)
# Then symlink the plugin into the SwiftBar plugins folder.
mkdir -p "$HOME/Library/Application Support/SwiftBar/Plugins"
ln -sf "$HOME/projects/lithium/plugins/swiftbar/lithium-spend.5m.sh" \
"$HOME/Library/Application Support/SwiftBar/Plugins/lithium-spend.5m.sh"
# Refresh: SwiftBar -> Refresh All (cmd+R from any plugin) or quit + relaunch.The filename suffix .5m.sh controls refresh cadence (every 5 minutes). Edit to .15m.sh for slower polls or .1h.sh for hourly.
- Menubar:
⚖ $XX.XXwhere the dollar amount is month-to-date variable spend across all configured providers. Color shifts based on[budget] monthly_variable_usdif set: cream by default, brass at 75-100%, oxblood over budget. - Submenu: day-of-month context, projected EOM total, per-provider subtotals (sorted by spend), declared fixed monthly subscriptions, budget readout, plus action items (refresh, run poll, edit config, open repo).
In ~/.config/lithium/config.toml:
[budget]
monthly_variable_usd = 100.0The plugin then warns at 75% (brass) and alarms over 100% (oxblood). Without this, the menubar stays cream regardless of spend.
┌─ Provider adapters (Rust) ─────────────────────────┐
│ anthropic.rs - Admin API + Claude Code session │
│ openai.rs - Admin API [phase 2] │
│ openrouter.rs - /api/v1/key [phase 2] │
│ elevenlabs.rs - /v1/user/subscr. [phase 2] │
└────────────────┬───────────────────────────────────┘
│
▼
SQLite at ~/.local/share/lithium/usage.db
│
▼
┌─────────────┼─────────────┬──────────┬─────────┐
▼ ▼ ▼ ▼ ▼
CLI cship SwiftBar OpenClaw Web
today/ status menubar MCP tool dashboard
month line + hooks [phase 4]
[✓ v0.3] [✓ v0.3] [phase 4]
Phase 1 ships only the CLI. Each subsequent phase adds one surface, polished to the same standard before the next one starts.
| Phase | Scope | Status |
|---|---|---|
| P1 | Anthropic adapter + CLI surface | ✅ v0.1.0 |
| P2 | OpenAI + OpenRouter + ElevenLabs adapters | ✅ v0.2.x |
| P3 | SwiftBar menubar plugin | ✅ v0.3.0 |
| P3.x | cship status-line segment |
✅ v0.3.4 |
| P3.y | lithiumd daemon (sub-5min polling) |
Not started |
| P4 | OpenClaw MCP hooks (cost gates) + optional web dashboard | Not started |
The discipline: each phase ships at finished quality before the next one starts. No half-built surface in main. See docs/ADAPTER-CONTRACT.md if you want to contribute another provider.
lithium runs entirely on your machine. No analytics, no telemetry, no phoning home. The only network calls go directly to provider APIs (Anthropic, OpenAI, OpenRouter) using the admin keys you provide. Source is auditable; if you find a single egress that isn't to a provider you configured, open an issue and call it out.
- Rust for the daemon and CLI
- SQLite for storage (via
rusqlite) - Reqwest for provider API calls
- Tracing for structured logs
- Clap for the CLI
- Tokio runtime
lithium is built in the open as a santifer-discipline project: each phase ships at finished public-portfolio quality before the next one is started. Issues, PRs, and adapter contributions for additional providers welcome. Adapter contract is documented in docs/ADAPTER-CONTRACT.md (added in Phase 2).
MIT. See LICENSE.
Built by Shawn Petros (petrosindustries.com).
Named after the periodic-table element and the mood stabilizer. Both stop runaway.
An apothecary's balance, kept in a workshop drawer.



