A KDE Plasma 6 panel widget that shows how much of your AI coding assistants' usage you have left — and what that usage would have cost at pay-per-use rates.
Every AI coding tool meters you differently. Claude has a rolling session window and weekly caps, plus separate ones for individual models; Codex has its own session/weekly pair; Antigravity splits its quota between Gemini and everything else; Grok bills by the week. Each hides that number behind a different CLI flag, web page, or nothing at all — and none of them tell you what the tokens you just burned would have cost on the API.
TallyBar puts all of it in the panel: one bar per limit, one number per bill.
Clicking the cost row opens a flyout with the spend graph — by hour, by day, or as a month heat grid. Clicking any bar or day expands it into a per-model breakdown.
| Provider | Quota / limits | Where that comes from |
|---|---|---|
| Codex | session window, weekly window, plan | the codex CLI's app-server over JSON-RPC; browser cookies as fallback |
| Claude | session window, weekly window, per-model windows | claude.ai, using your browser's existing session; plan read from disk |
| Gemini | daily usage, Google One AI credit pool | gemini.google.com, using your browser's existing session |
| Antigravity | Gemini window, Claude/GPT window, plan | the local Antigravity language server, plus Google's Cloud Code API |
| Grok | weekly window | xAI's billing service via the grok CLI's sign-in; else the CLI's log |
| Grok Bot | weekly window; on-demand spend when capped | the Grok Bot desktop app's sign-in, against Cursor's dashboard service |
Cost is computed separately, from local log files only — the transcripts
each CLI already writes to ~/.claude, ~/.codex, ~/.gemini, ~/.grok and
Antigravity's trajectory databases. Tokens are priced against the
LiteLLM model catalog (fetched once and
cached on disk), which is why the heading reads "Cost (if pay-per-use)" — it
is what your usage would have cost on the API, not what you were charged.
Each request is priced on its own, so a model that costs more for long prompts
(Claude Haiku 5.5 past 100K tokens, GPT-5.5 past 272K, Gemini 2.5 and 3.1 Pro past
200K, Grok 4.x from 200K) is billed at the higher rate only for the requests whose
prompt crosses that size.
Grok Bot has no local cost history yet: its tab shows the weekly bar only.
Clicking the cost row opens a separate chart window with a 7-day bar chart, a month grid, a 24-hour histogram, and a per-model breakdown you can expand by clicking a bar.
- KDE Plasma 6.0 or newer
- Python 3.11 or newer — the backend uses
asyncio.TaskGroupandasyncio.timeout, both new in 3.11 - Nothing else. The backend is standard-library only: no pip install, no
virtualenv, no daemon. (
.venvin this repo is for running the tests.)
Optional, per provider: KWallet for decrypting Chromium-family cookies, and whichever CLIs you actually use. Every provider is independent — a missing one shows a status line, never an error dialog.
Download io.github.dlansama.tallybar.plasmoid from the
latest release and
install it with the tool Plasma ships:
kpackagetool6 --type Plasma/Applet --install io.github.dlansama.tallybar.plasmoidOr install from source:
git clone https://github.com/DLANSAMA/TallyBarPlasmoid.git
cd TallyBarPlasmoid
make installThen right-click your panel → Add Widgets… → search for TallyBar.
make install wraps kpackagetool6; make build produces the same .plasmoid
package the release ships.
To update an existing install, make upgrade, then restart the shell so Plasma
drops its cached copy of the QML:
systemctl --user restart plasma-plasmashell.servicemake remove uninstalls it.
The widget refreshes every 5 minutes by default and paints the last known values immediately on cold start, so it is never blank while a fetch is in flight.
Each provider connects itself:
- Codex works as soon as its CLI has been used once — it is read from the local app-server.
- Grok works as soon as the
grokCLI has been signed in and used once. The bar is read live with the CLI's sign-in; when that has expired, or the network is off, it comes from the CLI's own log files instead. - Antigravity is read from its language server, which runs while Antigravity
(or
agy) is open. Plan and quota keep working from a cached token afterwards. - Claude and Gemini use the session cookies already in your browser (Chrome, Chromium, Brave, Edge or Firefox). If you are signed in to claude.ai or gemini.google.com in one of those, there is nothing to configure; if you are not, the widget shows a Sign in button that opens the right page.
- Grok Bot uses the sign-in the Grok Bot desktop app keeps: open the app and sign in once. That sign-in is encrypted with the Chromium Safe Storage key, so reading it needs KWallet, like the browser cookies.
On a machine with none of these installed, the widget still starts and shows one
status line per provider (No matching browser cookies, Grok Build data dir (~/.grok) not found, …) rather than failing.
The gear icon opens a settings flyout: refresh interval, which providers get a tab, what the panel itself displays (percentage, cost or bars), notification thresholds, per-provider mute, and an optional monthly budget.
Settings live in ~/.tallybar/config.json. The rest of that directory is the
widget's own state — cached snapshot, the Antigravity token ledger, the monthly
cost archive, and parse caches. All of it is regenerable: delete it and the next
refresh rebuilds it.
The widget is two halves separated by a process boundary:
QML frontend Python backend
io.github.dlansama.tallybar/contents/ui/ io.github.dlansama.tallybar/contents/code/
main.qml ── Plasma5Support ───► backend.py --once --background
(owns all state) DataSource (prints one JSON snapshot, exits)
▲ │
└────────── stdout JSON ────────────┘
There is no daemon and no socket. Every refresh runs backend.py once; it
gathers everything concurrently, prints a single JSON document, and exits. The
frontend JSON.parses stdout and re-renders. That shape is deliberate — a
widget that can't hang is worth more than one that saves a process spawn, and it
makes the backend trivially testable and debuggable by hand:
# Exactly what the widget runs, but readable
python3 io.github.dlansama.tallybar/contents/code/backend.py --once --pretty
# Skip every remote call (local logs and language servers only)
python3 io.github.dlansama.tallybar/contents/code/backend.py --once --no-network --pretty
# Just the cost/token numbers
python3 io.github.dlansama.tallybar/contents/code/backend.py --costbackend.py always prints parseable JSON, including on a fatal error — it falls
back to the last cached snapshot rather than exiting silently, because a crash
that prints nothing would blank the widget.
The contracts a change must not break — and the test that guards each one — are
in CLAUDE.md.
This widget reads credentials, so it is worth being explicit about what it does with them:
- Nothing is sent anywhere but the provider it belongs to. There is no
telemetry, no analytics, no update check. The only hosts contacted are the
ones in the table above plus
raw.githubusercontent.comfor the price catalog. - Browser cookies are read, never kept. The browser's own database is never
opened directly (that would fight the browser for the lock). It is copied into
a private
0700directory under$XDG_RUNTIME_DIR— tmpfs, cleared at logout — withO_NOFOLLOW, read once, and deleted. Copies stranded by a killed process are swept on the next run. - Chromium-family cookies are decrypted through KWallet, using the same key the browser itself uses. TallyBar never asks you for a password, and the background refresh never triggers a wallet-unlock prompt.
- Every diagnostic string is scrubbed for tokens, API keys, JWTs and session cookies before it can reach the snapshot, the UI, or a log.
- Everything written to disk is written atomically at
0600under a0700parent — temp file,fsync,os.replace— and the files that matter carry a.bakmirror with corruption quarantine on load. - Credentialed requests refuse redirects. CPython's redirect handler copies
the
Authorizationheader cross-origin; the Google and xAI endpoints that receive a bearer token never legitimately redirect, so a3xxfrom one is treated as an attack and raised instead of followed. - Refreshes stop while the screen is locked, and do not overwrite the cached snapshot when they do.
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
make test # 530 tests
make lint # pyflakes (what CI runs)
make check # flake8 + Qt6 qmllint (fails on QML errors) + mypy
make preview # load a UI component in a standalone window, no Plasma needed
make screenshots # re-render docs/screenshots/ (PNGs + demo GIF) offscreen from this machine's live usage
make render-baselines # re-render the mock-fixture PNGs the render tests compare against
make build # package io.github.dlansama.tallybar.plasmoidTallyBar can't support a provider until someone has seen what it actually returns, and no maintainer holds a subscription to every AI coding tool. So captures are cheap to produce and worth contributing:
python3 tools/capture_provider.py copilot # or: cursorIt reads the credential that tool already stored locally, makes one request,
scrubs the response through the same scrub_credentials the backend uses, and
writes a 0600 file. It uploads nothing. Free tiers are enough — GitHub Copilot
Free and Cursor's Hobby plan both yield a real response shape, which is the part
that can't be guessed.
Scrubbing is best-effort: read the file before attaching it to an issue.
The arithmetic lives in modules that don't reach the network — parsers.py is
pure (payload in, limit rows out) and accounting.py does token and cost maths
against local files only. That is what most of the test suite exercises. The UI
can be run without Plasma — see
tools/preview/README.md.
CI runs the lint and the tests on every pull request and every push to main;
pushing a v* tag builds the .plasmoid, stamps the version into
metadata.json, and attaches the package to a GitHub release.
- Linux/KDE only. It is a Plasma 6 applet; there is no port to anything else.
- The Claude, Gemini and Grok Bot readings depend on unofficial endpoints — the same ones their own apps call, with your own session. They are not public APIs and can change without notice. When one does, that provider degrades to a status message and the others keep working.
- Grok's live bar lasts as long as the CLI's sign-in. TallyBar uses the access
token the
grokCLI keeps in~/.grok/auth.jsonand never renews it; it lapses about six hours after the CLI last did. After that the bar shows xAI's last reading from the CLI's log, which only updates when the CLI starts, and marks it "(cached)" past an hour. Runninggrokrenews its sign-in. - Costs are estimates. They are list prices applied to locally logged token counts. Treat them as a sense of scale, not a bill. An Antigravity CLI session known only from its status-line total is billed at the base rate, since no single request's prompt size survives.
The idea comes from CodexBar by Peter Steinberger — the same job, done as a macOS menu-bar app. TallyBar is an independent implementation for Plasma with its own backend, and it borrows CodexBar's monochrome provider marks under that project's MIT license.
MIT — see LICENSE. Provider logos are the trademarks of their
respective owners and are bundled only to identify each provider; the bundled
MIT-licensed artwork carries its upstream copyright notice. Both are recorded in
io.github.dlansama.tallybar/NOTICE.





