Your AI Hub spend, in the corner of your eye — never in your face.
A macOS menu bar app that shows your personal AI Hub (LLM gateway) usage at a glance. An instrument, not a scoreboard.
One pill. Three instruments.
- The pulse — a live sparkline of your last hour of burn, age-faded so the newest samples read brightest
- The amount — today's spend in dollars
- The border IS the budget — the pill's own outline traces your daily budget as it fills, and it's the one element allowed to raise its voice. It escalates in one direction only, as the day goes:
The number only dims once you've actually spent 100%. The border warns early; the app never claims your budget is gone while a tenth of it remains.
Everything dims when the gateway is unreachable — your data is never silently stale.
Pill size ladder. Full (88pt, above) → Compact (52pt, drops the sparkline and rounds the amount) → Minimal (26pt, the border gauge alone). Pick one from the right-click menu under Pill size, or leave it on Automatic and it falls to Minimal when a notch clips the status item. Right-click also offers Copy today's spend, Open history folder, and Quit.
Click the pill. One panel, hairlines and whitespace only. It answers four questions in the order you'd ask them — how much today, at what pace, on which models, and how does this week compare — and each answer holds a fixed slot, so the card never resizes under your pointer.
How much today? $54.51 of $400 today, in large monospaced digits.
At what pace? One computed sentence — "At this pace you'll reach budget around 9:40 pm." When there's nothing urgent to say, it compares you against yourself instead: "Typical day by now: $34 — you're at $12." On the Month tab it adds a runway line, "On track for ~$X this month."
Today's curve. Cumulative spend hour by hour against a dotted budget ceiling, with the median of your recent days drawn faintly behind it — no legend, no label; the shape is the sentence. Hover it for a crosshair and a dot that snaps to the nearest hour you actually have a reading for, plus a floating readout in your local time. A gap hour never answers: the pointer past your last reading snaps back to it rather than inventing a value.
Which models? Ranked by cost, with a Today / Month switcher. Each row shows
the model, its share of the period's spend (gpt-5 · 62%), the cost, and the
unit price in $/M tokens — because two models can burn identical tokens at
wildly different prices, so raw token counts are a vanity metric.
How does this week compare? Seven cells above the footer, a true
Monday-to-Sunday calendar week — the letters are always M T W T F S S, so the
row is a frame you learn rather than one that re-labels itself each morning. Each
cell is shaded by that day's share of the week's biggest, the contribution-graph
grammar, so the week's shape reads at a glance. Today wears a ring and advances
through the fixed row as the week goes; the days still ahead sit empty, and a day
that hasn't happened never joins the week's max. Hover any cell for that
day's exact cost. A day with no reading stays silent rather than claiming
$0.00 — that would be a different claim. The strip appears once at least 4
days of the current week carry data.
Two 20pt slots in the corner, outside the layout flow. Both are chrome, not data: neither dims when a reading goes stale.
The update bell. A permanent status light, so "no news" is something you can actually read off the popover rather than infer from an absence. Grey and perfectly still when you're up to date — clicking it shows a quiet one-line card naming the version you're running, and offers nothing else, because there's nothing to do. Yellow and gently rocking when a newer GitHub release exists; clicking then gives you the one-line Homebrew update command with a Copy button, a link to the release notes, and Skip this version, which is remembered per version (skipping 1.0.1 won't hide 1.0.2). It checks at most once every six hours, stays quiet when offline, and never downloads or installs anything by itself.
The version dot. A 6pt dot in the far corner. Hover it for the changelog
card: the running version's own note first, then the recent history beneath it.
The list is generated from CHANGELOG.md at build time, so it can't drift from
the binary you're running.
The footer. Dashboard ↗ · API key · Start at login · a health dot
that goes amber and dims everything when the gateway is unreachable.
Opening cold. If today's local history holds a reading, the popover shows it immediately — dimmed, captioned "Last reading · 4m ago" — and the live poll brightens it in place. A brightness change, not a numbers jump. A true first run shows an honest spinner.
Homebrew (recommended):
brew tap NSXBet/tap
brew install --cask vela-ishtar
xattr -cr "/Applications/Vela Ishtar.app"
open -a "Vela Ishtar"The tap is private to the NSXBet org — you need GitHub access to it. The
xattr -cr clears the quarantine flag (the app is ad-hoc signed; we don't
have an Apple Developer account yet).
Update to a new version:
brew update && brew upgrade --cask vela-ishtar
xattr -cr "/Applications/Vela Ishtar.app"That's the same one-liner the update bell hands you.
Direct download:
curl -LO https://github.com/NSXBet/vela-ishtar/releases/download/v1.0.5/VelaIshtar-1.0.5.zip
unzip VelaIshtar-1.0.5.zip -d /Applications/
xattr -cr "/Applications/Vela Ishtar.app"
open -a "Vela Ishtar"After launching: there is no window — Vela Ishtar is a menu bar app. Look top-right, next to the clock: a small pill showing today's spend. Click it for the full popover. If you don't see the pill, your menu bar may be full (common on notched MacBooks) — quit a few other menu bar apps and relaunch.
From source (no Xcode needed, Command Line Tools only):
git clone https://github.com/NSXBet/vela-ishtar.git
cd vela-ishtar
./build.sh
open "build/Vela Ishtar.app"On first launch the popover asks for your AI Hub token (the same gt_…
token you use for the gateway). It's stored in your macOS Keychain and
never leaves your Mac except to the AI Hub gateway. macOS asks once to
authorize the Keychain item — that's normal.
Optional: toggle Start at login in the popover footer.
- Polls
GET https://ai-llm-gateway.fbr.land/v1/me/usageevery 60s with your token. The response is scoped to you only. - Your daily budget resets at midnight UTC — that's how the gateway buckets spend, so that's the boundary the pace sentence uses.
- Your personal limit comes from the API, so the UI auto-scales whether your budget is $100, $400, or anything else.
- Hourly spend history is cached locally in
~/Library/Application Support/VelaIshtar/history.json— it powers the curve, the week strip, and the median comparisons, and gets richer the longer you run the app.
Days are the gateway's, not your clock's. History is keyed by the API's
spend_date label, because the two disagree around midnight — keying by local
UTC used to file yesterday's total under today and make the curve visibly
decrease within a day.
Models period switcher. Month ranks models by current-month cost, straight
from the API's top_models. Today gets its per-model split the same way — the
gateway's today_models reports the spend day's cost, tokens, and requests per
model, which the app reconciles against your authoritative daily total (any
residue from other tokens or credentials lands in an explicit Other row). If
the gateway can't provide a split — an older gateway build, or the two daily
aggregates disagree beyond the restatement tolerance — you get your real daily
total plus a line saying why there's no split, never a confident wrong
breakdown. A Week segment was removed: /v1/me/usage has no weekly per-model
endpoint.
Comparison surfaces wait for enough data. The ghost curve and the median-day sentence need five past days holding a reading at the hour being compared; the week strip needs at least 4 days of the current week to carry data. Below that they stay hidden, and all three also stay hidden while a reading is stale — pinning "a typical day" against hours-old data would be a confident lie. Silence beats a number computed from thin data; that's the same rule everywhere in the app.
Update checks. The app asks GitHub for the latest release tag at most once every six hours, and only to compare version numbers. It fails closed: offline, a rate limit, a draft, a prerelease, or a payload it can't parse all mean "no bell" rather than a false alarm. Nothing is downloaded or installed automatically — the bell hands you a Homebrew command and gets out of the way. "Skip this version" is remembered, per version.
Rebuilding from source: each ./build.sh changes the ad-hoc signature,
so macOS may ask once for Keychain access on the first run of a new build.
One prompt per rebuild; one-time for a build you keep. Apple Silicon only
(arm64) for now.
Calm assurance. Facts, plainly stated, then silence. No leaderboards, no notifications, no "AI insights", no celebration mechanics. Monochrome everywhere except the budget border, which is the one thing allowed to raise its voice — and only in one direction, as spend climbs.
Two rules earn most of the behaviour above:
- Never show a number the data doesn't support. Gaps stay gaps, derived splits that don't reconcile aren't shown, stale readings are dimmed and bannered, and every comparison surface waits for enough history.
- The fewer pixels that move, the more each one means. The card is a fixed height in both periods, so a tab tap moves nothing but the indicator. Motion is reserved for four things — the curve's draw-on, the sonar ring that announces the scrubber, the period indicator's slide, and the update bell's rock — and every one of them is off under Reduce Motion.
Pure AppKit + Swift 6, zero dependencies, ~6,500 LOC (comments included — this
codebase explains itself). Builds with bare swiftc into an ad-hoc-signed
.app — no Xcode. SwiftPM runs the unit tests.
make test # 275 unit tests
./build.sh # compile + bundle + ad-hoc sign into build/Vela Ishtar.app
make release # sync README, rebuild, zip for the Homebrew caskThe split is deliberate. Sources/VelaCore is pure Foundation and holds every
rule worth pinning — pace verdicts and the median-day benchmark, the week
window, cell-intensity buckets, the budget border's dash math and colour
thresholds, hover-readout text, the Today-by-model reconciliation of the
gateway's today_models rows against the authoritative daily total, footer
arithmetic, semver comparison — so all of it is unit-tested. Sources/App is
AppKit rendering and event handling, verified on screen rather than in tests:
geometry that only means something once it's drawn.
Every bugfix that has a VelaCore seam gets a regression test that fails without the fix. Ones that don't — pixel alignment, tracking areas, layer transforms — are verified by rendering offscreen and probing the result.
Single source of truth for the version is Info.plist. Everything else
(README badge, install URL, the in-app what's-new list) derives from it or from
CHANGELOG.md at build time — edit those two files, never the derived bits by
hand.
- Bump both
CFBundleShortVersionStringandCFBundleVersioninInfo.plist. - Add a
## [x.y.z]section to the top ofCHANGELOG.md; the first line after each header is the one-liner that ships in the version dot's what's-new list. make test— all green.make release— syncs the README (readme-version), rebuilds, and zipsbuild/VelaIshtar-x.y.z.zipwith its SHA256.- Commit, tag
vx.y.z, push branch + tag. gh release create vx.y.z build/VelaIshtar-x.y.z.zip …, then bump the cask inNSXBet/homebrew-tap(version + sha256).- Verify the 3-way match: tag commit == release asset digest == cask sha256 == live download.
Reads only your own usage. Stores only your token (Keychain) and your spend history (a local file). No analytics, no telemetry, no tracking.
It talks to exactly two hosts, and only these two: the AI Hub gateway (your
usage, with your token) and api.github.com (an unauthenticated read of the
latest release tag, at most once every six hours, to decide whether to show the
update bell). The GitHub call sends no token and no usage data.





