Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

codexbar-systray

A Linux system tray for CodexBar, which only ships a UI on macOS. It polls codexbar usage --json for the providers you pick and shows the tightest usage percentage in the panel, favouring short rate-limit windows over weekly quotas.

The tray menu open below a GNOME panel icon. Three provider rows show Codex at 4% of its weekly window with 6d 23h left, Claude at 55% of a 5h window with 1h 30m left, and OpenCode Go at 60% flagged "burning too fast" with 2h 34m left, above Refresh now, Providers, Auto refresh, two toggles, and Edit config file.

Text version of the menu
┌──────────────────────────────────────────────────────────────────────┐
│ Codex        ·  4% (weekly)  ·  6d 23h left                        ▸ │
│ Claude       ·  55% (5h)     ·  1h 30m left                        ▸ │
│ OpenCode Go  ·  60% (5h)     ·  ⚠ burning too fast  ·  2h 34m left ▸ │
├──────────────────────────────────────────────────────────────────────┤
│ Refresh now                                                        ⟳ │
│ Providers                                                          ▸ │
│ Auto refresh: Every 5 minutes                                      ▸ │
│ ✓ Skip credit lookups (faster)                                       │
│ ✓ Pace alerts (colour by burn rate)                                  │
├──────────────────────────────────────────────────────────────────────┤
│ Updated just now                                                     │
│ Edit config file…                                                    │
│ Quit                                                                 │
└──────────────────────────────────────────────────────────────────────┘

Each provider row opens a submenu with its rate-limit windows, credits, cost, pace hints, and an Auth source radio group mapping to codexbar's --source auto|web|cli|oauth|api.

Single binary, no system dependencies

The tray speaks the freedesktop StatusNotifierItem protocol directly over D-Bus via ksni/zbus, and renders its icon with tiny-skia. There is no GTK, Qt, Cairo, libdbus or libappindicator in the dependency tree, at build time or at run time.

$ make build
$ ldd target/x86_64-unknown-linux-musl/release/codexbar-systray
	statically linked
$ ls -lh target/x86_64-unknown-linux-musl/release/codexbar-systray
-rwxrwxr-x 1.9M codexbar-systray

Copy that one file anywhere. The only runtime requirements are a session D-Bus and the codexbar CLI on PATH.

Install

make install   # build + install into ~/.local
make enable    # start now and at every login

make install drops the binary in ~/.local/bin, a .desktop entry in ~/.local/share/applications, and a systemd user unit in ~/.config/systemd/user. Nothing is written outside $HOME and no sudo is needed. Re-running make install upgrades in place and restarts the service if it was running. Override the location with make install PREFIX=/usr/local.

Managing the service (thin wrappers around systemctl --user):

Command Effect
make enable Start now and autostart at login
make disable Stop now and cancel autostart
make start / make stop / make restart One-off control
make status Is it running?
make logs Follow journalctl --user output

Removal:

make uninstall   # binary, .desktop and unit; keeps ~/.config/codexbar-systray
make purge       # the above plus the config file

A plain cargo build --release also works if you do not need the static musl build; it then links only libc, libm and libgcc_s. Without systemd, run the binary from your session autostart instead — the tray has no service dependencies of its own.

GNOME

GNOME Shell has no built-in tray. You need the AppIndicator extension, which Ubuntu ships and enables by default:

gnome-extensions enable ubuntu-appindicators@ubuntu.com

KDE Plasma, Xfce, Cinnamon, MATE, Budgie and most Wayland panels (Waybar, ironbar, …) support StatusNotifierItem natively.

Usage

codexbar-systray [options]

  -c, --config <path>   Config file (default: $XDG_CONFIG_HOME/codexbar-systray/config.json)
  -b, --binary <path>   codexbar executable to call (overrides the config)
  -h, --help            Show this help
  -V, --version         Show version
  • Left click — open the menu.
  • Middle click — refresh everything.

The provider list and the set of valid --source values are scraped from codexbar --help at startup, so a codexbar upgrade that adds providers is picked up automatically. Providers in your config that a newer codexbar no longer knows about are dropped silently. A configured source that is not in the scraped list is kept — it is still passed to codexbar, so the menu shows it (marked unknown) rather than claiming the codexbar default is in use.

codexbar reports per-provider failures inside its JSON payload rather than on stderr, which is how every web-only provider answers on Linux. Those messages are surfaced as errors: the row shows ⚠ <message>, the submenu shows the full text, and the icon dims like any other failure.

Configuration

Everything is editable from the menu; the file is only there for the extras.

~/.config/codexbar-systray/config.json:

{
  "codexbar_binary": "codexbar",
  "refresh_interval_secs": 300,
  "timeout_secs": 90,
  "no_credits": false,
  "pace_alerts": true,
  "providers": [
    { "provider": "codex", "source": "oauth", "in_summary": true },
    { "provider": "claude", "source": "oauth", "in_summary": true },
    { "provider": "opencodego", "in_summary": true }
  ]
}
Key Meaning
codexbar_binary Executable name or absolute path.
refresh_interval_secs Auto refresh period; 0 disables it. Capped at 86400. A value the menu does not offer is honoured and shown as its own entry.
timeout_secs Per-provider subprocess timeout. Web-scraping sources are slow; keep this generous. Capped at 3600.
no_credits Adds --no-credits, skipping the (slow) credit balance lookups.
pace_alerts Escalate the icon colour when a window is burning faster than an even rate. Never changes the number or the bar, but a window escalated to orange can become the one that gets quoted.
providers[].source Omit the key to let codexbar choose (no --source flag).
providers[].account Optional --account <label>.
providers[].account_index Optional --account-index <n>.
providers[].in_summary Whether this provider feeds the tray icon's roll-up number.

Tray icon

A seven-segment percentage over a usage bar, coloured by severity. The ramp is deliberately conventional — at 22px the hue is the only thing most people actually read, so it has to mean the obvious thing:

Usage Colour Constant
< 50% green #2EA043 palette::OK
50–79% amber #D49A00 palette::WARN
80–91% orange #E0620D palette::HIGH
>= 92% red #D1242F palette::CRITICAL

Both the digits and the bar fill show the same number: the actual usage of the quoted window (see below), taken from whichever provider flagged in_summary is closest to its limit. They cannot disagree, and neither can the icon and the provider row — both go through ProviderReport::display_window.

Which window gets quoted

A provider usually reports several windows: Claude a 5-hour lane and a weekly one, plus per-project extras. Only one of them fits in the icon and the provider row, and it is the shortest one, not the busiest.

Longer windows out-accumulate shorter ones almost by construction — a weekly quota at 31% against a 5h quota at 21% is the ordinary state of affairs — so ranking by percentage alone means the weekly lane is all you ever see, while the 5h lane is the one that decides whether the next request goes through.

A longer window takes over once it is genuinely actionable, i.e. once it reaches orange (>= 80%) or is pushed there by burn rate. Amber is deliberately not enough: a weekly quota spends most of an active week sitting in amber, and letting that win would undo the preference on exactly the days it matters. Providers that report no short window at all — Codex sends primary: null whenever its 5h lane is untouched — still quote the window they do have. When no window carries a length, codexbar's slot order (primarysecondarytertiary) stands in for it.

Every window is always listed in full in the provider's submenu.

Colour and burn rate

Burn rate never changes the number. With pace_alerts enabled, a window running more than 6 points ahead of an even burn rate bumps the severity one rung (green → amber), and more than 12 points ahead bumps it two. So 26% used in a 5-hour window can read amber or orange, but the bar still shows 26% and the digits still say 26. Windows behind pace are never escalated, and pace is ignored until 3% of a window has elapsed, where a single busy minute would project nonsense.

Because the escalated band is also what ranks windows, a short window burning fast keeps the row it would otherwise lose to a busier long one — which is the right outcome: it is about to lock you out first.

Providers with nothing left to spend are skipped when picking the icon's number, so one blown weekly quota does not pin the icon at red 100 and hide everything else. If every provider is spent, the icon reads 100 again.

means no providers are configured and ! means every provider failed. The icon dims when the data behind it is not trustworthy — a provider errored, or the numbers have outlived three refresh intervals (age is only a signal while auto refresh is on; with it off, old data is expected). An in-flight refresh does not dim it; the data on screen is still the best available. Pixmaps are emitted at 22/32/48/64 px so the host can pick a crisp size on HiDPI.

Differences from the macOS app

The upstream menu bar icon is a monochrome template image with two capsule bars (session on top, weekly hairline below) and no number; percentages are drawn as adjacent menu bar text. StatusNotifierItem has no template-image concept and no adjacent text, so this tray uses colour and in-icon digits instead. The 50% and 80% boundaries match the macOS quota-warning defaults (which are expressed as 50% and 20% remaining); the 92% band and the pace escalation are additions. The bar fills with usage rather than remaining headroom, so it agrees with the digits beside it — upstream defaults to the opposite. The pace ladder itself (2/6/12 points of delta) is upstream's own.

Every colour the tray uses lives in src/icon.rs::palette, so re-skinning is a five-line change.

Preview the renderings without a desktop:

make icons

Development

make check   # fmt + clippy -D warnings + tests
make help    # every target

make check is the whole gate and also what CI runs on pushes and pull requests, alongside a static musl build that asserts the binary stays statically linked.

Layout:

File Responsibility
src/main.rs CLI args, wiring, tray/worker lifetime
src/config.rs JSON config, atomic save
src/codexbar.rs --help capability scraping, subprocess with timeout
src/model.rs Defensive parse of codexbar's per-provider JSON shapes
src/worker.rs Concurrent refresh engine off the D-Bus thread
src/tray.rs Menu tree, labels, formatting
src/icon.rs ARGB32 icon rendering
src/names.rs Provider id → display name
src/timefmt.rs RFC 3339 parsing, relative durations

Notes on codexbar's JSON

codexbar usage --provider X --json prints an array, one entry per account. Shapes vary a lot across the ~60 providers, so model.rs walks serde_json::Value rather than deriving a fixed schema. Handled today: usage.primary|secondary|tertiary, usage.extraRateWindows[], usage.providerCost, usage.identity, credits.remaining and pace.*.summary. Pace objects (pace.primary|secondary|tertiary) are read structurally so the tray can colour by burn rate, not just current usage. Providers that don't supply pace data get the same treatment from elapsed window time: expectedUsedPercent is just how far through the window we are, so both paths feed one multiplicative projection (used * 100 / expected) and cannot disagree.

License

MIT — see LICENSE. Same license as upstream CodexBar, which this is an unofficial companion to; it only shells out to the codexbar CLI and ships none of its code.

About

A Linux-native system tray for CodexBar.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages