Skip to content

Releases: btsouth/ceiling

Ceiling 1.5.36

Choose a tag to compare

@github-actions github-actions released this 23 Aug 23:41
5bae231

Ceiling 1.5.36 is a maintenance release focused on safer local state and usage numbers you can trust.

Highlights

  • codexbar usage --all-accounts now fetches every configured Codex and Claude account with isolated errors and stable ordering.
  • Running codexbar without a subcommand now opens the default usage view as documented.
  • Desktop and server refresh intervals are honored, including response caching for serve.
  • Detached Settings windows reopen where you left them, and concurrent or corrupt geometry writes no longer wipe other saved positions.
  • Account isolation, Codex latest-session selection, quota ranking, and today's spend reporting are more accurate.
  • Update status and progress are announced to assistive technology, with automated accessibility regression coverage now in CI.

Security

Windows now rotates a serve.token that was exposed through unsafe file permissions instead of reusing the leaked secret.

MCP compatibility

session_cost_usd is now period_cost_usd. The new cost_period field identifies the billing period represented by that amount.

Thanks @harshvardhan60792 for fixing clipboard controls that could report success after a failed copy in #381.

Full changelog

Ceiling 1.5.35

Choose a tag to compare

@github-actions github-actions released this 22 Aug 22:58
804f971

[Ceiling] 1.5.35 - 2026-08-22

Charts stops showing numbers it cannot stand behind. A month whose models have no published prices painted as an empty one, the busiest day on the activity heatmap could share a shade with the quietest, and a Codex session carried whatever calendar day it was first parsed on, so a DST change or a trip moved usage onto the wrong day. Two cards scanning at once could overwrite each other's index, and a price refresh landing mid-scan stamped old dollars as current for every card already in the file.

The rest is state a surface reported but could not actually reach. A settings or credentials write could run unserialized on a filesystem that cannot flock, Install and Restart could check a staged installer against a different release's digest, and dismissing an update mid-download un-dismissed itself on the next progress chunk. A machine reporting no home directory no longer reads Gemini credentials from — or writes refreshed Google tokens into — whatever directory it happened to start in.

Ceiling also asks for a GitHub star now. At most twice, ever, and only after a provider has actually reported a reading.

Added

  • Ceiling asks for a GitHub star, at most twice ever. A card in the bottom-right of the dashboard, and the only thing Ceiling has ever asked of anyone using it. The first ask waits until a provider has actually reported a reading and that reading has been on screen for twenty seconds, so it lands after the app has done something useful rather than during setup, when it would be asking to be paid before the work. The second, if the first went unanswered, waits for a later version to have shipped and for a week to have passed since the first, because a version bump the day after is still two asks in one week. Clicking through to GitHub ends it permanently; so does reaching the second ask. Later and the close button mean the same thing and never count as interest. It is not a Windows notification, it takes no focus and traps none, Escape closes it, and the only network call involved is opening the repository in your browser when you ask for it.

Security

  • Gemini treats a missing home directory as not logged in. With no HOME or USERPROFILE, Ceiling used the working directory as ~/.gemini, so a checkout fixture could be read and then written back after a refresh. Both the credential read and the credential write now refuse that fallback, matching client_config.json.

Fixed

  • Grok project names read from a Windows path no longer come back as the whole path. A session's cwd is written by whichever machine ran Grok, so a Windows path reaches Ceiling on Linux and inside WSL. Path::file_name only treats \ as a separator on Windows, so everywhere else a cwd of C:\projects\personal\ceiling was reported as a project literally named C:\projects\personal\ceiling, and the URL-encoded session folder split the same way. Both now split on either separator on every host. A Codex sessions directory joined onto a Windows root also keeps \ instead of picking up the host's separator. Windows behaviour is unchanged.
  • A corrupted Claude de-duplication key no longer silently inflates Charts and Estimated API value. The usage index treated invalid UTF-8 in a present dedup_key as "this record has no key", so the same transcript event could be counted twice if its counterpart still had one. Invalid UTF-8 now rejects the whole index the way a required string already does, and the next scan rebuilds it from the transcripts. A key that was never written is still encoded as the length sentinel, not as a failed decode.
  • A filesystem that cannot flock no longer writes settings unserialized. The state-write lock treated every flock errno other than contention as "no lock needed" and continued, so an NFS/FUSE/SMB home without lockd let the tray and the CLI replace api_keys.json over each other. Only ENOTSUP — the filesystem saying it does not implement locking at all — now falls back, read from the errno rather than from how a given Rust version happens to decode it, and the fallback is an exclusive-create sibling that a second writer cannot take. ENOLCK is no longer treated as a broken filesystem: it also means the kernel lock table is full or lockd failed for that one call, while another process still holds a real flock, so falling back to the sibling would have let both writers through. It fails the write instead. An unknown errno already did. Because the sibling has no kernel-backed release, a leftover from a killed process still has to age out, but the age it has to reach is now two minutes rather than the ten seconds an acquirer waits — a holder doing slow work can no longer be outlasted and have its lock taken, and a lock file stamped in the future by a skewed server clock reads as held rather than as expired. A holder unlinks the sibling on release only while it is still the same file it created, so a takeover cannot cascade into deleting a third writer's lock. A state-write.lock the current user cannot open — left by a sudo run, or by another account — now fails the write and names the path to remove. It used to be unlinked and recreated, which put the running holder and the new one on two different inodes and let both writes proceed. A directory in the lock path still fails the write. Releasing that sibling also hands the lock cleanly to the next writer on Windows, which reports a name whose deletion has begun as access-denied rather than as already-existing; a waiter that arrived in that window used to get a hard failure instead of the lock a moment later.
  • The 1.5.33 notes no longer claim the incident badge is Ceiling's only non-provider request. That sentence was false: models.dev (prices) and GitHub (update checks) already run without that opt-in. The badge is still off by default. The replacement makes no exclusive claim at all: the badge adds each enabled provider's public status page to the hosts Ceiling talks to, on top of the usage requests those providers already receive, while models.dev and GitHub are fixed hosts that run either way. A copy test bans the exclusive phrasings so neither version can come back.
  • The activity heatmap no longer paints the busiest day the same shade as the quietest. Quartile cuts that collapsed to one value still held every active cell at mid-scale, including a day orders of magnitude above the rest, and with four or fewer active cells the 75th-percentile cut sat on the maximum so the legend's top swatch never appeared. An outlier now reaches the darkest step, a genuinely flat month stays mid-scale, and the busiest cell of a small set reaches the top of the ramp. The top swatch is decided by the busiest cell rather than by the 75th-percentile cut, which was wrong in both directions: when the upper half tied, the busiest cell fell into the mid band, and with four distinct cells the cut sat low enough that the second-busiest matched the busiest.
  • A single light day no longer becomes the spend-anomaly baseline. After a week away, one $1.50 session was "the recent daily median", so the first ordinary $12 day back toasted as an 8x spike and marked the day sent. A real runaway later that evening then said nothing. The detector now needs three floor-clearing days before it will compare; fewer is the same as a zero baseline, which already stays quiet. Closes SBS-965.
  • Install and Restart no longer checks the downloaded installer against a different release. Clicking Download and Check for updates at the same moment could let the check finish second and store a newer release digest next to the installer that had just completed. Install then failed closed and discarded a valid download. Download now takes the updater lock for the whole start, a check that lost the slot does not overwrite an in-flight download, and apply verifies the digest captured when that download began. Dismissing an update while its download is still running now sticks: a later progress chunk used to write the Downloading state back, which let the finishing download store Ready and bring Install & Restart back after the banner had been dismissed. That discarded download's installer is deleted rather than left in the staging directory.
  • Two Charts scans no longer wipe each other's usage index. The API-value card and the heatmap can finish at the same time, and each commit encoded its snapshot then released the lock before writing the file. The slower writer could put the older snapshot back on disk, so files the faster scan had just indexed were gone after a restart and had to be parsed again. The lock now covers the write, so the file on disk is always the latest encode. In-memory totals were already correct.
  • A light user whose every day is under $1 can now receive a spend-anomaly alert. Days under a dollar still do not build the recent-median baseline — a five-cent leftover next to a real day would otherwise manufacture a false spike. When that leaves no median at all, the detector used to go silent, so a $0.60/day user never heard about a $180 loop. It now falls back to a $20 absolute trigger (20x the $1 floor, the largest spike factor the setting accepts) so a runaway still toasts and an ordinary first day does not. The budget alert is a separate opt-in and is no longer this feature's only answer for that user. The trigger is compared at cent precision, so a day worth exactly $20.00 cannot be missed because a sum of floating-point estimates landed a fraction under it while the toast still printed "$20.00".
  • The activity heatmap follows the UI language. The rest of Charts already translated; this card had been written after locale landed and never joined. Titles, empty states, axis labels, screen-reader text, and dollar/token formatting now go through the same bundles as the rest of the app, so a Chinese language setting no longer leaves this card in English. A language without it...
Read more

Ceiling 1.5.34

Choose a tag to compare

@github-actions github-actions released this 18 Aug 22:50
419d69a

[Ceiling] 1.5.34 - 2026-08-18

Charts opens in about two seconds instead of about thirty. Each local transcript is now parsed once into a small index beside your settings rather than re-read from the top by every card, every time, and the cards keep their last result so a restart is not a cold start. The numbers are unchanged: an indexed scan is checked against a full re-parse, and the index is discarded outright whenever model prices move.

The rest of this release is about surfaces reporting a state they could not actually reach. Signing out of StepFun could leave a live token behind if a refresh was in flight, Gemini treated a token endpoint it could not reach as a signed-out account, a countdown could read "0m" while a window still had a minute in it, and About labelled a failed download as a failed update check and said it in half-translated English.

Fixed

  • Charts opens in a couple of seconds instead of half a minute. On a machine holding gigabytes of Codex and Claude transcripts, opening Charts started three separate walks of the same logs at once, and each one read every file from the top: Estimated API value scanned ninety days when the furthest period it shows reaches back sixty, the activity heatmap scanned thirty, and the provider charts scanned again on top. Nothing was kept, so switching tabs paid for all of it again, and clicking Yesterday or 30 days re-ran a full scan for numbers the card already had in hand. Each transcript is now parsed once and its records are kept in a small index beside your settings; a file that grew since is resumed from where the last read stopped rather than re-read from the start, and a file that was replaced rather than appended to is read again in full. Providers are scanned at the same time instead of one after another, both cards keep their last result on disk so a restart is not a cold start, and the work runs in the background shortly after launch for anyone who opens these cards, so the wait lands where nobody is watching it. The numbers are unchanged: an indexed scan is checked against a full re-parse, and the index is discarded outright if model prices move, since it stores the dollars they produced.
  • Signing out of StepFun no longer leaves a live Oasis token in the keyring. A successful refresh wrote the new token to the OS keyring (codexbar-stepfun / api_key), and Revoke stored credentials only cleared Preferences, cookies, and token-accounts. The next fetch then read the leftover and stayed signed in. Revoke now asks the provider to delete that copy. A keyring error fails the revoke instead of reporting success while the token remains.
  • Gemini no longer treats a still-refreshable seat as signed out. The quota poll used the access token until the exact expiry second and turned a 401 into AuthRequired, so a valid refresh token still produced one failed 5-minute cycle each hour. Gemini now refreshes five minutes early, like Claude and Vertex, and a 401 retries once after refresh. A revoked refresh token is still AuthRequired.
  • Reset countdowns agree at a remaining day and in the last minute. The CLI statusline used hours > 24, so 24h 10m stayed "24h 10m" while the tray and the TypeScript hooks already said "1d 0h". The native tooltip and taskbar strip floored a still-future 30s remainder to "0m", the stuck-timer leftover SBS-621 fixed in the hooks. Every reset surface now floors one total of minutes, clamps a sub-minute remainder to 1, and cuts a day at 1440 minutes. A user looking at the tray, the CLI, and the tooltip at 24h 1s sees "1d 0h" (or "Resets in 1d" in the locale sentence); at 30s they see "1m", never "0m".
  • A failed update check no longer tells you that you are current. Checking for updates treated a GitHub outage, a rate-limit, or an unreadable release payload the same as "no newer release", so About said you were up to date. Only a successful "latest is not newer" is Idle now. Failures, including the existing 15s timeout, are Error, and About shows that the check could not run. A second check after a download is ready no longer clears Install & Restart.
  • codexbar cost and serve /cost now include Grok. CostScanner and Charts already read ~/.grok/sessions (costUsdTicks). The CLI and the local HTTP endpoint still treated Grok as unsupported and said only Codex and Claude have local logs. After the usage command started enumerating enabled providers (default claude, codex, cursor, grok), every bare codexbar cost listed Grok as unavailable. The same machine's Charts tab already showed those sessions. codexbar mcp get_spend used the same Codex/Claude-only list. Cursor, Gemini, and Copilot stay unsupported.
  • WSL.md no longer documents automatic browser-cookie import or gcloud auth login for Gemini. Cookie extraction is disabled; Gemini reads the Gemini CLI file ~/.gemini/oauth_creds.json. The site no longer calls cookies "browser imports".

What's Changed

  • Parse each transcript once when Charts opens by @tsouth89 in #329
  • Map update-check failures to Error, not Idle (SBS-931) by @tsouth89 in #327
  • Align reset countdown formatters at 24h and the last minute (SBS-927) by @tsouth89 in #324
  • Clear the leftover StepFun Oasis token on revoke (SBS-920) by @tsouth89 in #322
  • Refresh Gemini tokens before expiry and retry a 401 (SBS-928) by @tsouth89 in #323
  • Stop documenting automatic cookie import and gcloud Gemini login (SBS-933) by @tsouth89 in #326
  • Include Grok in CLI and serve local cost scans (SBS-934) by @tsouth89 in #325
  • Retake the strip shot and put the heatmap on the site by @tsouth89 in #321
  • Release 1.5.34 by @tsouth89 in #330

Full Changelog: v1.5.33...v1.5.34

Ceiling 1.5.33

Choose a tag to compare

@github-actions github-actions released this 16 Aug 21:41
81c096a

[Ceiling] 1.5.33 - 2026-08-16

Adds an activity heatmap to Charts and an opt-in spend warning that needs no budget set, and lets the floating bar follow whichever app you are working in. Mostly, though, this release stops surfaces reporting a state they could not actually read: a provider outage is now told apart from an empty quota, a missing Cursor plan reads as unavailable rather than 0% used, SuperGrok's weekly figure is decoded rather than guessed at, the taskbar strip stops cutting the last character off a reset, and prices, chart caches, and CLI logs stop losing or hoarding data on disk. Release builds also drop the loopback exception their content policy was carrying from the dev server.

Supersedes 1.5.32. That version was tagged, signed, and drafted on 2026-08-16, and its installers were uploaded to the versioned download path, but the GitHub release was never published and no one received it. The v1.5.32 tag stays as a marker. Everything from it is included here, plus the taskbar reset fix that was found in its build.

Added

  • Providers having a public outage get a badge on their card. A "0 tokens left" reading and a provider outage looked identical, so the second was read as the first. The provider card now carries the status page's own wording, plus a control that opens that page. Off by default and opt-in under Notifications, because it is the only outbound request Ceiling makes that is not to a provider you already signed in to. Nothing about you is sent. Only enabled providers whose status page can actually be read are polled, at most once every fifteen minutes, and only while a surface is asking. An operational provider gets no badge, and neither does a page that could not be read.
  • The floating bar can follow the app you are in. Pinned providers stay the default. Active shows the provider for the focused supported app or terminal agent. Active plus critical also keeps providers at or above the warning threshold. An unrelated window keeps the last active provider. Detection is local, cached, and does not call provider APIs. You can turn watching off.
  • Charts has an activity heatmap. It answers when you actually work from timestamps already on disk: a 30-day strip and a weekday-by-hour grid, machine-wide across Codex, Claude, and Grok. Nothing new is collected and nothing leaves the machine. The hourly series is built in the same pass as the daily one and is a strict refinement of it, so the calendar view and the peak-hours view cannot disagree about a total. Intensity bands follow the quartiles of the cells that have activity, so a single heavy day does not flatten the rest of the month into one shade.
  • A warning when today's spend runs far above your recent norm. A budget alert only helps someone who set a budget. This one needs no cap: it asks whether today looks unlike the days around it, which is the shape of a runaway agent loop well before the bill arrives. Off by default and opt-in under Notifications, alongside the other spend alerts, and it reads your local Codex and Claude logs, so spend on other providers is in neither today's total nor the baseline. It compares against the median rather than the mean, because one heavy day last week would otherwise raise the bar enough to hide a real spike today, and it leaves today out of its own baseline so a bigger runaway cannot make itself harder to detect. Under a dollar it stays quiet, and a baseline of zero reports nothing at all, since a fresh install and a genuinely idle week look the same from the dollars alone.

Security

  • Release builds no longer allow the webview to reach loopback ports. The shipped connect-src carried four localhost / 127.0.0.1 entries on any port. They exist for the Vite dev server and its hot-reload socket and were never needed at runtime: the webview makes no network calls of its own, and every provider request runs in Rust, including the Wayfinder gateway. Any script that ran inside the webview could previously reach every loopback service on the machine. The permissive rules now live in a dev-only overlay that tauri:dev merges in.

Fixed

  • The taskbar strip stops cutting the last character off a reset. The second line of a tile was trimmed to fifteen characters, which is a guess at a pixel width rather than a measurement of one. "Monthly · 10d 1h" is sixteen, so every monthly window with a two-digit day lost its trailing "h" and read as a countdown that was an hour out rather than as a line that had been cut, while "Weekly · 3d 23h" fit at exactly fifteen and hid the problem. The line is now measured in the font it is drawn in, against the width of the tile it belongs to. Where the whole thing will not fit it gets shorter in steps you can still trust: the countdown loses its finer unit first, so "Monthly · 23h 59m" becomes "Monthly · 23h" rather than nothing at all. That step matters most in the last day before a reset, when the countdown switches to hours and minutes and becomes the widest thing this line ever carries, wide enough to miss even the roomiest tile. Only when a tile cannot hold a coarse countdown does it fall back to the window name alone, which still says which ceiling it is; the figure is a hover away in the flyout.
  • SuperGrok's weekly percentage and reset were silently wrong. The decoder scanned the response payload looking for anything that resembled a float or a timestamp, so it could pick up the wrong field and report a confident number that did not match Grok's own meter. Both the used percentage and the period end are now read from their named fields by number.
  • Grok picks the same account every time on a multi-seat login. Account selection took whichever entry a hash map happened to yield first, so the SuperGrok meter could point at a different seat after a restart with nothing changed. It now prefers an explicit active marker, then the newest expiry, then a stable sort.
  • codexbar usage with no provider flag now reads the providers you enabled. It was documented as printing your enabled providers and always queried Claude regardless. It resolves through the same enabled set the desktop app uses. -p all and -p <provider> are unchanged.
  • Spinner arrows, the caret, and selection highlights follow the app theme. Those three parts of a form field are painted by the engine rather than by our CSS, and they were left to inherit color-scheme from the root. The app theme and the Windows theme are independent settings, so that inheritance was one refactor away from a light spinner on a dark field. Each control now sets it directly.
  • The last glow in the app is gone. One rule still drew an 8px halo on the promo boost chip, against the rule the UI doc sets out. It is now a zero-blur accent ring, which keeps the emphasis. A prebuild check fails the build on any new shadow with no offset and a non-zero blur, and it self-checks so a detector that stops working fails loudly instead of passing quietly.
  • Linux and WSL look for sessions where they are actually kept. Cursor's session database is read from ~/.config/Cursor before the old guess, Claude Desktop's from ~/.config/Claude, and the Windows profile behind a WSL install is no longer inferred from directory listing order, which could silently pick the wrong user. CEILING_WINDOWS_USERNAME names it explicitly when the guess is not wanted. Windows paths and the existing overrides are unchanged.
  • A crash on Linux or WSL no longer leaves a lock nothing can clear. The state-write lock was a file created exclusively, so a second writer failed on the spot instead of waiting, and a process killed outright left the file behind for good. It now takes an exclusive flock, which waits its turn and is released by the kernel when the process dies however it dies. Windows locking is unchanged.
  • Opening Settings from the floating bar no longer lands on General. The bar asked for a menuBar tab the Settings window does not have, so the window fell back to General instead of Display. It now opens Display, the same tab the dashboard already uses for that action. The lists that name a Settings tab on each side of the bridge are compared in CI so a renamed tab cannot silently send you to General again.
  • The OpenCode mark now matches the real brand, and stays visible on a light taskbar. OpenCode's square-ring logo is monochrome, black on white or white on black, but nothing showed it that way. The taskbar strip painted it in an invented blue, and the dashboard drew it in the near-black taken straight from the brand asset, which left it almost invisible against a dark card. The strip now paints whichever half of the brand the taskbar behind it can actually show, which also fixes the Grok monogram beside it: both were a fixed near-white that disappeared on a light Windows theme. The dashboard cannot switch per theme the same way, so it takes one mid gray that clears both, the same one its chart bars use; the bundled SVGs now take their fill from that value instead of carrying their own. OpenCode Go charts pick up that gray too, having previously fallen through to the generic cost blue.
  • Cursor no longer treats missing usage as zero. An empty individualUsage object used to paint a 0% monthly bar and hide a real team pool sitting next to it. Monthly is now marked unavailable when Cursor reports no reading, and an empty individual object falls through to team usage. Glance surfaces no longer paint a 0% Plan bar when monthly is unavailable — Overview, flyout, detail, floating bar, and the native taskbar tile show the named state instead. On-demand stays billed spend; plan and included dollars are labeled Included so they are not read as an invoice. A missing Composer tracking database is shown as unavailable, not as no activity.
  • Leftover English on glance surfaces now goes through locale keys. Floatbar settings, freshness ch...
Read more

Ceiling 1.5.31

Choose a tag to compare

@github-actions github-actions released this 14 Aug 12:35
ecd098e

[Ceiling] 1.5.31 - 2026-08-14

Hardens updates, credentials, and the local serve API, and ships the unpublished 1.5.30 work: a second tray click hides the dashboard, Grok banked resets show, and the taskbar strip finds a second lane when the usual gap is gone.

Supersedes 1.5.30. The GitHub release was tagged and drafted on 2026-08-13 but never published; the v1.5.30 tag stays as a marker. A Microsoft Store submission was created from that tag. Everything from it is included here.

Security

  • Ceiling checks who signed an update before it runs it. An automatic update was trusted on the strength of the SHA256 that GitHub's release metadata reported, so that metadata was the only thing standing between Ceiling and launching an attacker's installer with the user's privileges. Every downloaded installer is now independently checked against Windows Authenticode and pinned to Ceiling's publisher identity, once when the download completes and again immediately before launch. An installer that fails either check is deleted instead of being left on disk to be retried. The publisher identity is pinned rather than the signing key, because Azure Trusted Signing issues a fresh short-lived leaf certificate for every release and a key pin would reject the next legitimate one.
  • codexbar serve now requires a per-user bearer token. The local HTTP API bound to loopback with only a Host check, so any process on the machine could read usage, email, organization, and raw provider errors. /usage and /cost now need Authorization: Bearer unless you pass --allow-unauthenticated. The token is created at <config>/Ceiling/serve.token with current-user ACL on Windows and mode 0600 elsewhere, and is printed only the first time. Identity and raw provider errors are omitted by default; --include-identity opts back in. /health stays open.
  • Windows-owned PowerShell and where.exe no longer launch from PATH. Notifications, Antigravity, the updater, and a few where lookups used a bare name, so a hostile current directory or PATH entry could substitute the binary. They now resolve under %SystemRoot%\System32 and refuse to start if that file is missing. Claude, Codex, and gh still use PATH.
  • Cookie and token paste fields are masked, and destructive credential actions confirm first. Manual cookies and token accounts used raw textareas, and Remove/Revoke fired on a single click. Secrets are hidden by default with an explicit reveal, and cookie, API-key, token-account, and provider-wide revoke go through an in-app dialog that names the provider and credential type. A successful removal refreshes provider state so leftover quota or identity is not left on screen.

Added

  • A second click on the tray icon hides the dashboard. Left-click always reopened the stats window, so a glance meant hunting for the close button. First click shows it, second click hides it. A Windows double-click is ignored so the window does not flash open and disappear. Right-click and the keyboard shortcut are unchanged. Closes #280.
  • Grok banked resets show up the way Codex already did. Grok's Settings → Usage now lists redeemable reset tokens. Ceiling already counted those for Codex and left Grok's chip blank. The same count now appears on Overview, provider detail, and the taskbar flyout. Redeeming still happens on grok.com. Ceiling only displays how many are left.
  • Settings says why the taskbar widget is hidden. The native strip can vanish because there is no gap, because Start cannot be found, or because no providers are enabled, and none of that was visible anywhere, so the feature looked broken. The Taskbar Usage group now reports "Shown on N taskbar(s)", no free space, waiting for landmarks, or no enabled providers. The row stays off when the widget is disabled. Thanks @diogochaves!
  • OpenCode and OpenCode Go show their mark on the taskbar strip. Those providers had no bitmap in the native overlay, so the tile was a blank disc. They now use the square-ring glyph and the same blue as the rest of the app.

Fixed

  • diagnose no longer copies provider error text into an export meant to be shared. The command already withheld cookies, tokens, account emails, and response bodies, but a failed fetch put the provider's own error string straight into the payload, and that string can carry a signed URL, an account identifier, or an echoed request header. Every failure now reports a local category and a fixed local message instead of anything the provider said.
  • Heavy Codex sessions no longer vanish from Charts and cost totals. Codex usage was parsed into 32-bit integers, so a cumulative total past roughly 2.1 billion tokens wrapped to a negative number, which the next step clamped to zero. The effect was not a wrong number but a missing one: the session's tokens and its dollars simply stopped being counted. Token counts are now 64-bit from the log line through to the summary, cumulative deltas saturate instead of wrapping, a genuinely corrupt total is skipped without poisoning the running baseline, and a decreasing cumulative total no longer lowers that baseline so the next valid line over-counts.
  • Adding, removing, or switching an account no longer races another change. Every account edit read the store, changed it in memory, and wrote it back, with nothing holding those three steps together. Two edits that overlapped could each write a copy of the file that predated the other, so one of them disappeared with no error and no sign anything had gone wrong. A Copilot device login finishing while the user edited accounts in Settings could do the same. Account changes now run as a single locked transaction against the latest state on disk.
  • A token refresh no longer rewrites the whole Gemini or Grok credential file. Both providers refreshed by reading the file, rebuilding it from the fields Ceiling models, and writing it back with no lock and no atomic replace. Three things could go wrong: two refreshes at once could interleave and lose one, a crash partway through the write could truncate the file and sign the user out, and any field Ceiling did not model was dropped on the floor, including a newer refresh token the provider's own CLI had just written. A refresh now holds a lock for the whole read-modify-write, merges only the fields that actually changed, and replaces the file atomically while preserving its original permissions. Grok writes refresh_token only when the token endpoint issued a new one. If the credential file is missing, persist recreates it instead of failing the refresh.
  • Charts no longer mix one account's usage into another. A removed or unresolved account could inherit the machine-wide transcript cache, so Charts showed someone else's tokens under the wrong tab. Each configured account now has its own chart tab and its own cache identity. Compare stays an explicit aggregate. Polling stops for identities that no longer resolve.
  • Provider detail no longer applies a stale refresh. Switching providers or accounts quickly could let an older request finish last and paint the previous seat's numbers onto the new one. Detail commits now require a matching request epoch plus provider and account identity, account selection resets at provider boundaries, and bursty provider updates coalesce before a refresh.
  • Credential status is scoped to the provider you have selected. Shared credential-file protection was treated as "this provider has a credential", so opening provider B could show a protected store and a revoke action that belonged to provider A. Presence is now per selected provider, and per selected token account. An unreadable store stays unknown instead of reporting a false absence.
  • The taskbar widget takes a second lane when the usual one does not fit. On a left-aligned taskbar, Start sits at the left edge and the Widgets-to-Start gap's right edge goes negative, so the strip hid with no explanation. Windhawk's "Start button always on left" did the same. The Widgets-to-Start lane is still preferred. If it has no verified gap, the widget tries the stretch between Start and the tray and lands in the largest empty gap, never covering a pinned icon. Left alignment with Windows Widgets enabled used to freeze the strip on top of the app icons instead. That path now places into the fallback lane and treats the Widgets entry as an obstacle. Closes #261. Thanks @diogochaves!

Internal

  • The privileged PR-review runner pins actions/checkout to a reviewed commit instead of a moving tag.

What's Changed

  • Credit the contributor in the 1.5.29 changelog entry by @tsouth89 in #265
  • Pin checkout on the persistent PR review runner by @tsouth89 in #276
  • Surface the taskbar widget's hidden state in Settings by @diogochaves in #264
  • Scope credential status to the selected provider by @tsouth89 in #279
  • Show Grok banked usage resets by @tsouth89 in #282
  • Isolate chart data by account by @tsouth89 in #277
  • Fall back to a second taskbar lane when the Widgets→Start lane can't fit the widget by @diogochaves in #281
  • Discard stale provider detail responses by @tsouth89 in #278
  • Hide the dashboard on a second tray icon click by @tsouth89 in #283
  • Prepare Ceiling 1.5.30 by @tsouth89 in #284
  • Keep provider error text out of the diagnose export by @tsouth89 in #286
  • Count Codex tokens beyond the 32-bit ceiling by @tsouth89 in #288
  • Make account store edits transactional by @tsouth89 ...
Read more

Ceiling 1.5.29

Choose a tag to compare

@github-actions github-actions released this 12 Aug 01:18
8de324e

[Ceiling] 1.5.29 - 2026-08-12

Finishes what 1.5.27 started: Cursor's on-demand spend now reads as money on every surface that shows it, not only the Overview card. Also lets a monthly quota raise a pace warning, and gives every settings control a name a screen reader can reach.

Supersedes 1.5.28, which was tagged but whose build was cancelled before it signed or published anything; everything from it is included here. The v1.5.28 tag stays as a marker.

Fixed

  • Cursor's on-demand spend reads as money on the taskbar, not just on the Overview. 1.5.27 taught the Overview card to show dollars, but the surfaces you actually glance at were left behind. The taskbar tile picked the on-demand lane correctly and then drew "62%" � the fraction of a spend cap, which says nothing about the $1,112.92 behind it, and which inverts to a cheerful "38%" if you display remaining rather than used. The flyout was worse: it discarded any lane whose name contained "on-demand" before building its rows, so the tile named a lane that the panel beneath it refused to show, and because the same filter ran before the "+N more limits" count, nothing hinted that a row had been dropped. The free-floating bar carried the identical percentage-only defect. Activity had the same habit from the other direction: it listed the On-demand row faithfully and then headlined it "56% used", describing the shape of the bar instead of the bill. A lane billed in currency now leads with the amount on every surface that shows it � taskbar tile, hover flyout, floating bar, and the Activity schedule � the flyout lists On-demand with its spend beside the bar and reserves it a slot so a depleted plan cannot crowd it out, and on-demand running uncapped reports spend-to-date rather than going blank for want of a denominator. Reported in #191.
  • A monthly quota can raise a pace warning. 1.5.27 fixed the pace verdict shown on screen but not the toast behind it. Predictive pace warnings looked only at a provider's primary and secondary windows, and a monthly quota arrives in the tertiary slot � so a month on course to run dry before it reset could never say so, while the weekly window beside it warned freely. Every core slot is now a candidate, and two slots reporting the same cadence collapse to a single warning rather than clobbering each other's state.
  • Settings controls have names a screen reader can reach. A settings row rendered its visible label as plain text with nothing tying it to the control beside it, so a screen reader arrived at an unlabelled toggle, dropdown, or number box; text inputs could not be given a name at all. Each row now supplies its label to the control it wraps, without overriding one a control already carries. Closes #215. Thanks @Vermitrude!

Full Changelog: v1.5.27...v1.5.29

Ceiling 1.5.27

Choose a tag to compare

@github-actions github-actions released this 11 Aug 02:33
f435b2f

[Ceiling] 1.5.27 - 2026-08-11

Makes Cursor's on-demand spend readable at a glance, corrects two numbers Ceiling was reporting wrong, and replaces browser cookie import with a manual setup you can see.

Supersedes 1.5.26, which was built but never published; everything from it is included here.

Changed

  • Browser cookie import is gone, replaced by a manual copy-and-paste setup. Ceiling could read cookies out of the browser's own database to authenticate a provider, and provider fallbacks could reach for that database without being asked. Handing an app your browser's cookie store is a large amount of trust for a usage meter, and it happened where you could not watch it. Providers that need a cookie now ask for one, with a guide for copying it out of the browser's developer tools, and the value goes to the same secure store as every other credential. If a provider was authenticated by browser import, it will ask you to set it up again. Everything else is untouched: CLI credentials, IDE credentials, OAuth sign-in, and Claude Desktop sessions are all still detected automatically, and providers on those paths need no attention.

Added

  • Tab strips can be driven from the keyboard. Settings, the Charts provider selector, the account switcher, and the chart-type tabs all announced themselves as tab strips to assistive technology while ignoring arrow keys entirely, and each one spent a Tab stop per tab. Left and Right move between tabs, Home and End jump to the ends, focus wraps, and a strip is now a single Tab stop. Each panel names the tab that opened it, and panels are focusable, so tabbing out of a strip lands in the content it just opened and a scrolling panel can be scrolled from the keyboard.

Fixed

  • Cursor's on-demand usage is readable without opening a provider. On-demand is the only Cursor lane that bills real money, and once the included plan is at 100% it is the only number that matters. It now shows its percentage and its dollars together on the Overview card — "56%" beside "$1,002.16 of $1,800.00" — and takes the taskbar strip's slot once spending starts or the included allowance is exhausted. An unused $0 on-demand lane stays out of the way on a healthy card. Reported in #191.
  • Codex no longer overcharges gpt-5.1-codex-mini. Pricing lookup collapsed *-codex model names onto their base model, which is right for the plain aliases but folded the distinct mini and max variants into gpt-5.1. Mini input was billed at the gpt-5.1 rate of $1.25 per million tokens instead of its own $0.25, so every cost total containing mini usage read high. Dated and openai/-prefixed spellings of those variants now resolve to the same correct entry.
  • The pace verdict describes the window that is actually running hot. Pace was chosen by cadence — the weekly window, always — so a provider reporting both a weekly and a monthly quota could show a reassuring verdict about the window nobody was spending. An OpenCode Go account read "Weekly pace · Far below budget · -15.0%" directly beneath a monthly bar whose own marker showed it fifteen points over. Monthly quotas were doubly unreachable: pace looked only at the primary and secondary slots, and monthly quotas are reported in a third slot, and the cadence test would have rejected anything longer than fourteen days anyway. Every window a provider reports is now a candidate, and the one running hottest against its own clock is the one you are told about. The twelve-hour floor stays, so a five-hour session still gets no verdict rather than a meaningless one.
  • Concurrent writes can no longer lose stored settings or credentials. Settings, API keys, manual cookies, and token accounts were each read, changed, and written back without a lock, so two writers overlapping — the desktop app and the CLI, or two changes landing together — could drop whichever change was read first. All four now serialize through one cross-process lock, and revoking a provider's credentials happens inside a single validated critical section rather than as several separate writes.
  • Configured Claude accounts no longer collapse onto one globally active session. When multiple accounts use separate CLAUDE_CONFIG_DIR directories, each card now fetches through OAuth credentials scoped to its own directory. Ceiling no longer tries the process-wide Claude Desktop, browser, ambient CLI, or API-token session first, which could make two distinct accounts show the same email and usage. Single-account automatic fallback behavior is unchanged.

Internal

  • Cost pricing tables and cost math have regression coverage for the first time, including the mini-rate case above, along with new tests for the usage-level threshold classifier and the critical usage boundary.
  • Owner pull requests are reviewed by a pinned Grok action.

Ceiling 1.5.25

Choose a tag to compare

@github-actions github-actions released this 09 Aug 06:49
ca7ca7b

Highlights

Claude's taskbar tile now shows the capacity that governs your account

Claude's model-specific limits, such as "Fable only," could take over the single taskbar lane when they reached 100%. That hid Session and Weekly capacity even though switching models still let you work. The native taskbar tile and floating bar now reserve that lane for the real account pools; model limits remain visible everywhere that lists all windows.

Credential and settings updates no longer risk unrelated data

  • An unreadable API-key or manual-cookie store is left untouched instead of being treated as empty and overwritten.
  • Unknown provider settings and token accounts survive saves and downgrades instead of resetting preferences or disappearing.
  • Removing one token account preserves the account you actually selected.

Custom Codex endpoints are checked by their real host

Plaintext custom Codex URLs are restricted to genuine loopback hosts. URL shapes that merely contain localhost while pointing at a remote host are rejected, preventing the Codex access token from being sent there. Legitimate local proxies continue to work.

Also fixed

  • Countdown formatting no longer loses nearly an hour around hour boundaries.
  • Relative reset timers hold at one minute instead of displaying "Resets in 0m."

Installers

  • Ceiling-1.5.25-Setup.exe - standard installer
  • Ceiling-1.5.25-portable.exe - portable
  • Ceiling-1.5.25-Store-Setup.exe - Microsoft Store package (WebView2 bundled)

Portable builds show alerts as banners but do not keep them in the notification center. That requires the Start Menu shortcut installed by the standard or Store build.


Full Changelog: v1.5.24...v1.5.25

Ceiling 1.5.24

Choose a tag to compare

@github-actions github-actions released this 05 Aug 05:52
d74f430

Highlights

This release supersedes 1.5.23, which was never published — everything from it is included here.

Your usage bars now show where you should be

Ceiling could already work out whether you were on course to run out before a window reset. It just wasn't on the thing you actually look at.

Every weekly and monthly bar — in the Overview and in a provider's detail view — now carries a marker for where usage should be at this point in the window. One rule, everywhere: the marker is where the bar's edge should be right now.

  • Edge sitting at the marker → you're on pace.
  • Edge past it → you're ahead of budget, and the overspend fills in as a striped band, so you can see how far, not just which side.
  • Bars set to show remaining capacity mirror the marker, so it means the same thing either way.

It's worked out from elapsed time against the window's own length, which means it needs nothing from the provider and shows up on every long window at once, instead of only the single window a pace prediction was calculated for.

Five-hour session bars are deliberately left plain. Nobody spends a session evenly, so a marker there would drift across the bar all afternoon and tell you nothing.

Predictive pace warnings are back, if you want them

Under Settings → Notifications, "Predictive Pace Warnings" alerts you when a window is on course to be exhausted before it resets. It's off by default.

This existed but was unreachable: switched off on every launch with no way to enable it, and limited to Claude and Codex even then. Any provider that reports a reset can raise one now, and warnings name the window by its real cadence instead of calling a monthly quota a "session".

Cursor on-demand spend is visible, in dollars

If you hit 100% of your Cursor plan, on-demand is where the real money goes, and it was easy to miss entirely. It could vanish on some account shapes, and anyone running on-demand without a spend cap saw nothing at all, because a meter needs a cap to draw a percentage against.

The On-demand row now appears in a provider's detail view with its spend beside it, uncapped spend is reported as a plain figure rather than dropped, and it takes the cost slot ahead of plan usage. On-demand is the only Cursor lane that bills real money, so it should be the number you see.

A Cursor meter that could only ever say 0% is gone

The Cursor card carried a "Promotional" bar pinned at 0% by its own arithmetic, and a badge claiming the bonus expired at the end of the billing cycle when Cursor credits expire on their own schedule. Both numbers were wrong and neither can be derived from what the Cursor API reports, so the lane has been removed rather than guessed at.

Also fixed

  • Claude's Opus model pool can now raise usage alerts alongside the session and weekly windows. It could previously sit at 99% in silence.
  • A weekly window promoted into the primary slot is no longer reported as a session.
  • Devin's usage percentages are resolved from every reported window at once, so a response mixing 0.4 and 32 reads the 0.4 as 0.4% rather than rescaling it to 40%.
  • Release binaries are built with link-time optimization and symbol stripping for the first time. The build profile had been sitting where cargo ignored it, so shipped builds were larger and slower than intended.

Installers

  • Ceiling-1.5.24-Setup.exe - standard installer
  • Ceiling-1.5.24-portable.exe - portable
  • Ceiling-1.5.24-Store-Setup.exe - Microsoft Store package (WebView2 bundled)

Portable builds show alerts as banners but do not keep them in the notification center. That requires the Start Menu shortcut installed by the standard or Store build.


Full Changelog: v1.5.22...v1.5.24

Ceiling 1.5.22

Choose a tag to compare

@github-actions github-actions released this 03 Aug 05:13
a640074

Highlights

The first 1% of usage no longer reads as 100%

A lightly used OpenCode Go account could report its rolling window as 100% used while the dashboard showed 1%. The usage page reports each window as either whole percentages or fractions of the limit, and a lone 1 means 1% in one and 100% in the other. The per-window scaling rule turned the first 1% of use into a maxed-out rolling window.

The scale is now resolved once per response from real evidence in the payload, and only read as fractions when a window actually holds a fractional value. The same bug was present in the OpenCode, Qoder, Chutes, and Sakana providers, and it is fixed for all of them.

OpenCode Go's monthly window has a name

The OpenCode Go card now labels its monthly bar "Monthly" instead of the generic "Extra", so the third usage window on that card is no longer anonymous.

The Store build submits again

The 1.5.21 Microsoft Store submission was rejected because Partner Center caps installer parameters at 40 characters and the inherited value was longer. The parameters are now normalized to that limit, with startup-prompt suppression and the restart-required exit code preserved, and a deterministic Store-package preparation test guards it in CI.

Installers

  • Ceiling-1.5.22-Setup.exe - standard installer
  • Ceiling-1.5.22-portable.exe - portable
  • Ceiling-1.5.22-Store-Setup.exe - Microsoft Store package (WebView2 bundled)

Portable builds show alerts as banners but do not keep them in the notification center. That requires the Start Menu shortcut installed by the standard or Store build.


Full Changelog: v1.5.21...v1.5.22