Repository navigation
Releases: neoromantic/ai-usage
Release list
v0.8.0
Install with Homebrew
ai-usage now installs with Homebrew, on macOS and Linux:
brew install neoromantic/tap/ai-usage
ai-usage # collect, register with launchd or cron, and print the report
ai-usage app install # on macOS 14 and later, the menu bar appBefore ai-usage, run ai-usage team join and paste a teammate's key to join their team, and ai-usage relay set URL for your own relay. Homebrew installs the binary and nothing else, so the first ai-usage registers the 15-minute run, as the installer's first run does. Type the full name, neoromantic/tap/ai-usage, in every brew command: Homebrew trusts the tap by it, and refreshes the tap before an upgrade only when it is named. The one-line installer works as before. See Homebrew in the README.
Updates with Homebrew
A copy Homebrew installed never replaces itself: Homebrew owns its files. It still checks for releases at every run, so you know when one is out:
update v0.8.1 is out; run brew upgrade neoromantic/tap/ai-usage or ai-usage update
ai-usage updaterunsbrew upgrade neoromantic/tap/ai-usagefor you, and then brings the menu bar app to the release brew installed:installed v0.8.1 with Homebrew; the next run uses it. The menu bar app's Update and Check for Updates run it too.- A plain
brew upgradeworks as well. When you runai-usage, or the menu bar app reads the report, before the next scheduled run, it notices the upgrade, and on macOS 27 launchd refuses no scheduled run. Otherwise launchd refuses one, as after a self-update, and the next run repairs it. - Homebrew is a few minutes behind GitHub. The release workflow puts each release in the tap once it is published. Until then,
ai-usage updatesaysHomebrew's formula is at v0.8.0; GitHub has v0.8.1, which reaches Homebrew soon. - The team waits for it. A Homebrew machine updates when someone runs brew on it, not within 15 minutes, so it can hold back what the team turns on only once every machine has a release. After 7 hours on the older release, the report names it:
mira-mbp runs v0.8.0 and has not updated; run ai-usage update on it.
The menu bar app with ai-usage app install
ai-usage app install adds the menu bar app of the binary's release on macOS 14 and later, whatever installed ai-usage: Homebrew, or the installer with AI_USAGE_NO_APP=1. It quits a copy that runs and opens the new one, and runs keep the app at the binary's release from then on. There is no Homebrew cask: Homebrew quarantines what a cask downloads, and macOS would refuse the app, which is not notarized.
In the app, About says "Command-Line Tool (Homebrew)" for a Homebrew install, a release that is out says "v0.8.1 is out; Update runs brew upgrade neoromantic/tap/ai-usage", and the screen shown without ai-usage adds the Homebrew command under the installer's. When an update fails, the app shows ai-usage's own error, not what brew printed before it.
The team's words for a machine that has not updated
A machine on an older release no longer "updates itself within 15 minutes": it "should update within 15 minutes", since a Homebrew machine waits for brew. One that has not, as when its update fails or Homebrew installed it, is asked for by name: leo-air runs v0.3.4 and has not updated; run ai-usage update on it, or run ai-usage update on leo-air, srv1, which have not updated. The first-run guide's invite now says a colleague runs ai-usage team join and pastes the key, or installs with it as AI_USAGE_TEAM_KEY.
JSON: how this copy updates
collector.update has method, "self" or "homebrew", and command, "brew upgrade neoromantic/tap/ai-usage" for a Homebrew copy and null otherwise, including as root. A report from an earlier release has neither; read them as "self" and null. collector.schedule.error can now be set while the schedule is registered, to say that another copy of ai-usage should go. The schema version stays 4. See docs/json-schema.md.
Switch to Homebrew, or back
From the installer to Homebrew. Remove the installer's copy, rm ~/.local/bin/ai-usage (or the path which -a ai-usage shows), and the block that starts with # Added by the ai-usage installer in your shell profile. Then brew install neoromantic/tap/ai-usage and run ai-usage. The state folder and the team key stay, so the machine stays the same device in the team.
If you keep both, the scheduler runs only Homebrew's copy: the installer's registers Homebrew's in its place and stops updating itself. ai-usage status says which copy to remove. A copy an earlier installer put in /opt/homebrew/bin or /usr/local/bin keeps brew from linking its own there; brew link --overwrite neoromantic/tap/ai-usage replaces it. Another user's Homebrew copy, in a prefix you share, leaves yours alone.
From Homebrew to the installer. brew uninstall neoromantic/tap/ai-usage, then run the installer again. Run ai-usage schedule remove before uninstalling for good; Uninstall has the Homebrew lines.
Compatibility and upgrade
What to do. Nothing, for a script install. Releases from v0.2.5 on install v0.8.0 within 15 minutes, and on a Mac the menu bar app updates with the binary.
The installer. install.sh stops when Homebrew installed ai-usage for the user who runs it, and says to use brew upgrade instead, unless AI_USAGE_BIN_DIR is set. A new install no longer goes into /opt/homebrew/bin or /usr/local/bin, which are Homebrew's: it goes into ~/.local/bin or ~/bin. A copy already in one of them is still upgraded where it is.
Homebrew starts at v0.8.0. The tap has no older release: a binary before v0.8.0 does not know Homebrew installed it, and would replace itself inside Homebrew's folders.
State. The state folder keeps ran_version, the release of the last run. The snapshot and the relay do not change, and the menu bar app keeps its settings.
For the website
This release changes claims of the README, its install commands, and what it says of the menu bar app:
- a second install command,
brew install neoromantic/tap/ai-usage, beside the one-line installer, withai-usage app installfor the menu bar app; - updates: self-update cannot be turned off for a script install, and a Homebrew install updates with
brew upgradeorai-usage update; the Highlights line now says so; - the menu bar app comes with the installer or with
ai-usage app install, not only with the installer; - the team's words for a machine on an older release, and the first-run guide's invite, which show in the pictures
team.png,team-light.png,team-80.png,team-devices.png,team-bots.png,team-status.png,install.png, and the tour indocs/demo.
Known limitations
- Homebrew machines update only when someone runs
brew upgradeorai-usage updateon them, and the snapshot does not say which machines Homebrew installed. The team sees such a machine as one that has not updated. - Homebrew installs are per-user. As root,
ai-usage updaterefuses, and the report has no command. brew uninstallwithoutai-usage schedule removefirst leaves the launch agent or crontab line behind, which fails silently every 15 minutes. The README says how to remove it by hand.- On macOS 27, launchd running Homebrew's linked path after
brew upgradehas been reasoned through, and is repaired as after a self-update, but not yet watched on a real upgrade. - Windows has no Homebrew;
install.ps1is unchanged.
Full Changelog: v0.7.0...v0.8.0
v0.7.0
The team's today starts at your midnight
The team's tables counted UTC days, as devices sent them. For a team across time zones, that made today nobody's today: the UTC day begins at 17:00 in Los Angeles, at 02:00 in Berlin, and at 09:00 in Tokyo. At six in the evening in Los Angeles, the team's today had just begun and showed almost nothing, and a teammate's working day there fell on two UTC days.
Now each device sends its tokens by hour for the last two days, and the team's today counts from your own midnight, wherever each teammate works:
DEVICES × SUBSCRIPTIONS 15 · today · M weighted tokens
- Today in DEVICES × SUBSCRIPTIONS, the status view's TODAY column, and every team object in the JSON is the hours since local midnight on the machine that shows the report. Your own row there now says what USAGE says.
- 7d, 30d, and 90d still count UTC days, and their titles still say
UTC. Over a week, the day's difference matters much less. - The menu bar app's Team window says "Today" in its header where it said "Today UTC", and "7 Days UTC" as before.
- While a teammate runs an older release, today is the UTC day, as before, and the title says
today UTC. See below.
Claude Code's reset times on any machine
Claude Code says when a window resets in a time zone by name, such as resets 9am (Europe/Berlin). Reading that needs a time zone database, which Windows and slim containers do not have, so there the window had no reset time and no forecast. The binary now carries its own, which adds about 400 KB.
JSON: today_local
The team has today_local: true when every team object's today is the hours since local midnight, in the time zone of the machine that made the report, and false when it is the report's UTC day. A report from an earlier release has none; read it as false. The schema version stays 4. See docs/json-schema.md.
Compatibility and upgrade
What to do. Nothing. Releases from v0.2.5 on install v0.7.0 within 15 minutes, and on a Mac the menu bar app updates with the binary and restarts into it.
How the hours arrive. A release before v0.7.0 rejects a snapshot with fields it does not know. So a device sends its hours, hours and burn_hours, only once its last team read, at most 3 hours old, shows every teammate on v0.7.0 or later, as with the cycles of v0.5.0. Until then, today is the UTC day for everyone. Most teams update within about half an hour of the release.
- A retired device, or one silent for over a week, keeps today on UTC days until
ai-usage team forget-device IDforgets it, as it does for the cycles. - A device that has not sent its hours yet, as in its first run on v0.7.0, counts its UTC day for that run.
- A relay that does not accept the hours yet gets the snapshot without them in the same run, and is offered them again a day later. The public relay accepts them already; run your own relay on v0.7.0 before your collectors update.
State. The state folder keeps no_hours_until for a relay that refused the hours. Nothing else changes, and the menu bar app keeps its settings.
Known limitations
- Today counts each device's hours up to its last report, as every period does. A device that went to sleep before your midnight adds nothing to today.
- In a time zone half an hour off UTC, such as India's, an hour counts on the day most of it falls in, as USAGE already counts it.
- 7d, 30d, and 90d are UTC days, so they still start at a different local time than today does.
ai-usage report --fromshows an older report's today as the UTC day it counted.
What's Changed
- Add AGENTS.md, and keep the website's private repo out of the release steps by @neoromantic in #22
Full Changelog: v0.6.2...v0.7.0
v0.6.2
The menu bar app: a subscription's details say what they know
A click on a subscription in Limits opens its details. They already said who spent the window, where it is headed, and how old the reading is, but the app worked out the window's start on its own, and every subscription had a line about its reading, even a fresh one this Mac took. Now each line comes from the report, and the reading's line shows only when it tells you something.
- This window. Who spent the main window since it began, by the team's weighted tokens: the two busiest devices, then the rest as a count, "build-01 84M, scout 35M, +8 more · since 21 Sep 02:40", where it said "+8" after the second device. When the line has no room, it drops the start first, then the second device, "build-01 84M, +9 more", and its tooltip still names every device. Alone, it says the tokens, "102M · since 20 Sep 01:00". The start is now the report's, the time it counts those tokens from, where the app worked it out on its own.
- Forecast. The window that runs out, where it is headed by its reset and the pace that takes it there, "164% at reset · this week's pace", as before. For a forecast that stopped, as v0.6.1 has it, where nobody on the team has spent on the subscription since it would have run out, it now says where the window was headed, "would reach 108% at reset · this week's pace", and no longer reads as a window still on its way there.
- Last used. When anyone on the team last used the subscription, "20m ago", as before.
- Reading. How old the reading is and which device took it, "4m old, from scout (hermes)", now only when that says something: the reading is stale, another device took it, it came through another login, or a stopped forecast stands on it. A fresh reading this Mac took has no line. A stale reading's age is still in yellow.
See the details of the demo team's Codex bots, whose reading another device took. The README's Menu bar app has the picture in dark and light.
JSON: the window's start and the reading's device
The report adds two fields, so that the app shows the report's answers instead of working them out again:
window_starton each column ofteam.matrix: when the main window began, the time itswindow_tokensandwindow_burncount from. It isnullwhen no main window is known: for a column of tokens no subscription covers, a subscription with no reading of its main window, or one whose window has reset since the reading.device_idon a team account'squota: the id of the device whose reading is newest, as a team device'sdevicehas it, besidedevice, its name. A reader tells this device's reading from another's by it.
No field changed meaning, so the schema version stays 4. A report from an earlier release has neither field; read them as null. ai-usage report --from does not fill them in. See docs/json-schema.md.
Compatibility and upgrade
What to do. Nothing. Releases from v0.2.5 on install v0.6.2 within 15 minutes, and on a Mac the menu bar app updates with the binary and restarts into it.
What changed. The menu bar app's details of a subscription, and the two fields of the JSON. The console's report, ATTENTION, the forecasts, the snapshot, and the relay are as in v0.6.1. This Mac's ai-usage works out both fields from what its teammates already send, so they need not update for them, whatever release they run.
macOS 27. As in v0.6.1, an update that a run you start installs, such as ai-usage update or the app's Check for Updates, costs no scheduled run. One that a scheduled run installs costs the next scheduled run, which any ai-usage you start, or the app's next read of the report, makes up.
State. Nothing changes in the state folder or the snapshot, and the app keeps its settings.
Known limitations
- A menu bar app from v0.6.1 shows its details as before, a line about every reading and a stopped forecast's "108% at reset", until it updates: for the moment between the binary's update and the app's, or, when the app's update fails to install, until it is downloaded again 6 hours later.
- The app shows what the command-line tool's report has. When the tool is another release before v0.6.2, as Settings' About says, the details have no start, and a team's reading names its device even when it is this Mac's and fresh.
- This window counts what the team's devices sent, as of each one's last report. Spend from outside the team, such as on the web or on a machine without ai-usage, is not in it.
- The macOS 27 limitations of v0.5.1 stand: after a scheduled run installs a release, launchd still refuses the next scheduled run, and a run you start, or the app's next read of the report, makes it up.
What's Changed
- Menu bar app: who spent the window, last used, and where the reading is from by @neoromantic in #21
Full Changelog: v0.6.1...v0.6.2
v0.6.1
The menu bar app: every way in shows
Two of the popover's ways in did not look like ways in: the menu opened only from the status dot, and the Team window from a ⤢ at the top of Usage. Now each has a button of its own.
- Team. A button named Team, with its symbol, sits beside the popover's tabs, in place of the ⤢ in Usage, and opens the Team window.
⌘Dand Team Window… in the menu still open it too. With only this Mac there is no team, and no button. See the popover's header. - The menu's button. The header ends in the status dot with the report's age, ↻, which collects now, and ⋯ with a chevron, which opens the menu as a pull-down in a Mac toolbar does. A click on the dot still opens it too.
- The menu, in sections. Its items are under headers, each action with its symbol: Status, a colored dot for the collection, the relay, and the update, and this Mac with the team's size; Actions, Collect Now and Team Window…; Open in Terminal, the report, the device status view, and every project; and AI Usage, Settings and Quit. The shortcuts,
⌘R,⌘D,⌘,, and⌘Q, also work while the popover is open. A chip about the collector opens the menu with the chip's messages first, under Attention, at most 8 lines of them. See the menu. - About, said once. Settings' About names the app and its version once, beside Check for Updates, and says where the command-line tool is. It names the tool's release only when it is another one, "Command-line tool v0.5.1: updates with Check for Updates", or says the tool is not found. See Settings.
The README's Menu bar app has these pictures, in dark and light.
Forecasts: a day's pace, and no run-out nobody saw
AT RESET, the OVER lines of ATTENTION, and the menu bar app's countdowns all come from each window's forecast: its reading plus its pace times the hours to its reset. Three things in it read wrong.
- A day at least. A window a day long or longer, such as the weekly one, now measures its average pace over a day at least, where it was a tenth of the window, 16.8 hours in a week. A burst in the first hours of a week, as when a provider resets the week early for everyone, no longer reads as the pace of the week. Until its first day has passed, a weekly window shows a forecast only when it is over already. A 5-hour window still measures over a tenth of it, half an hour.
- The last day's pace. The recent pace is now what the readings gained since the newest one a day old, over the hours between them, in place of the least-squares slope of the last day's readings. A burst and then a quiet afternoon reads as what it added over the day, not as the pace of the burst. Steady readings give the same forecast as before.
- Would run out. A run-out time comes from the newest reading, which can be a day old when nobody uses the subscription. Once that time passed, ATTENTION said the window "ran out", though nobody had spent on it since and nothing said it did. Now, when the run-out time has passed with nobody on the team spending on the subscription since, the forecast is stopped: ATTENTION says the window would run out then, at that pace, with the reading's age, and lists it after the windows that still run out. A window at 100% is out either way.
OVER codex sam@mail.test runs out ~Sat 03:01 at the last day's pace, 2d 17h before reset
OVER claude ann@acme.dev would run out ~Mon 22:30 at this week's pace, 3d 13h before reset · reading 1d old
- In the menu bar app. The first line says the same, "Claude mira · Fable would run out ~Thu 05:18", with "reading 1d 12h old" under it. The subscription's row counts down to its reset, where it said "↘ ran out", and the line under it says the window "would run out ~Thu 05:18"; its details add the reading's age. When another window of the account still runs out, the row shows that one. The app sends no notification for a stopped window. See the alerts, with the demo team's forecasts at the new pace.
- JSON. A window's
forecasthasstopped,truewhen itsruns_out_athas passed with nobody on the team spending on the account since, by the account'slast_active_at, and absent whenfalse. Itsoverentry inattentionsays "would run out" in itstext, and hasreading_age_secondshowever old the reading is.recentinbasisis still the last day's pace, now measured as the readings' gain.ai-usage report --fromfills instoppedfor a report saved without it. The schema version stays 4. See docs/json-schema.md.
Compatibility and upgrade
What to do. Nothing. Releases from v0.2.5 on install v0.6.1 within 15 minutes, and on a Mac the menu bar app updates with the binary and restarts into it.
What changed. The report's forecasts, its ATTENTION, the JSON's stopped, and the menu bar app. The snapshot and the relay are as in v0.6.0. Each device works out the forecasts from the readings the team already shares, so teammates need not update for this, whatever release they run. Until a teammate updates, its own report forecasts as v0.6.0 did, so its AT RESET for a weekly window early in its week, or after a burst, can differ from yours.
macOS 27. As in v0.6.0, an update that a run you start installs, such as ai-usage update or the app's Check for Updates, costs no scheduled run. One that a scheduled run installs costs the next scheduled run, which any ai-usage you start, or the app's next read of the report, makes up.
State. Nothing changes in the state folder or the snapshot, and the app keeps its settings.
Known limitations
- A menu bar app from v0.6.0 that reads a v0.6.1 report still says a stopped window "ran out", in its first line and its row, and can send a notification that it ran out, until it updates: for the moment between the binary's update and the app's, or, when the app's update fails to install, until it is downloaded again 6 hours later. Its alerts already show the report's words, "would run out".
- Stopped goes by when the team last spent on the account. Spend from outside the team, such as on the web or on a machine without ai-usage, does not count, so a window filled from outside can read as one that would run out until a new reading says it is out. Its line keeps the reading's age.
- In its first day, a weekly window shows no forecast unless it is over already, where it showed one after 16.8 hours.
- The macOS 27 limitations of v0.5.1 stand: after a scheduled run installs a release, launchd still refuses the next scheduled run, and a run you start, or the app's next read of the report, makes it up.
What's Changed
- App: a visible Team button and menu, and one version in About by @neoromantic in #17
- Forecast: a day's floor, endpoint pace, and no 'ran out' on a stale reading by @neoromantic in #19
Full Changelog: v0.6.0...v0.6.1
v0.6.0
The menu bar app: a Team window
The popover is a glance at what this Mac's person can act on. The person who looks after the team had to open the terminal for the rest: ai-usage report --devices, or DEVICES in the report. Now the menu bar app has a window for it, with the whole team on one screen, in the report's numbers.
- Opening it. Team Window… in the menu under the dot beside the popover's first line opens it, and so do
⌘Dwhile the popover is open and the ⤢ at the top of Usage. With only this Mac there is no team, and Usage already shows it whole, so none of them shows. - Usage. Each device against each subscription, grouped by tool, then the tokens no subscription covers, under No Quota, and TOTAL, as the terminal's DEVICES lays them out. A cell is shaded by its tokens, a step for every half of a tenfold below the largest, as in the terminal, so a bot's 2M still reads beside a laptop's 160M. Its tooltip says the cell in full: "build-01 · Codex bots: 131M in 7 Days · 46% of column · 84M this window". A subscription's name is in the color of its state. See Usage.
- Status. Each device's user, release, with ↓ when it is behind the latest, last report, the tools it reads, with × for a failing one, its tokens today and over 7, 30, and 90 days, and the report's NOTE, whole in its tooltip. The line at the top counts the devices, the errors, and the devices not reporting, as the terminal's status view does. See Status.
- Periods, shares, and sorting. As in the terminal,
1,7,3, and9pick the period,%switches Usage between tokens and each device's share of its column, andsswitches between Usage and Status; the buttons at the top do the same. A click on a heading sorts the rows, and TOTAL stays last; until then they are in the report's order, the most tokens over 7 days first. - A window like any Mac's. Its columns resize, it follows light and dark, and VoiceOver reads each cell as its tooltip does. It opens where it was last, at its size, and the app shows in the Dock while it or Settings is open.
The README's Menu bar app has both pictures, in dark and light.
Track: the ring for the team
The menu bar's ring shows what is left of the fullest window among the subscriptions logged in on this Mac. Settings, General, Menu Bar, has a new choice, Track:
- This Mac's Subscriptions, the default, is what the ring showed until now.
- The Team's has the ring follow every subscription of the team with a reading, bots' included, for the person who looks after them all. It turns orange and red, and shows the time until a window is back, as it does for this Mac's.
The Preview under it shows the ring as each choice draws it. Track changes the ring only: which windows send a notification is still the Subscriptions choice under Notifications.
Compatibility and upgrade
What to do. Nothing. Releases from v0.2.5 on install v0.6.0 within 15 minutes, and on a Mac the menu bar app updates with the binary and restarts into it. Track starts at This Mac's Subscriptions, so the ring shows what it did until you change it.
What changed. Only the menu bar app. The binary, the report and its JSON, and the snapshot are as in v0.5.1. The Team window lays out the report's team.matrix and team.devices, which this Mac's ai-usage works out from what its teammates already send, so they need not update for it, whatever release they run.
macOS 27. As the v0.5.1 notes say, an update that a run you start installs, such as ai-usage update or the app's Update button, costs no scheduled run. One that a scheduled run installs costs the next scheduled run, which any ai-usage you start, or the app's next read of the report, makes up.
State. Nothing changes in the state folder or the snapshot. Track is kept in the app's settings, beside what the menu bar shows.
Known limitations
- The Team window shows what the report has. Its cells and rows open nothing: for a device's or a subscription's details, use the popover or the terminal's report.
- Its numbers are the report the app reads every minute, so they are as fresh as that read, and a device's tokens as fresh as its last report.
- With The Team's, the ring can show a subscription that nobody on this Mac can do anything about, such as a bot's. Its notifications follow their own setting.
- The macOS 27 limitations of v0.5.1 stand: after a scheduled run installs a release, launchd still refuses the next scheduled run, and a run you start, or the app's next read of the report, makes it up.
What's Changed
- Team window and the ring's scope in the menu bar app by @neoromantic in #15
Full Changelog: v0.5.1...v0.6.0
v0.5.1
The menu bar app: every alert, in the report's words
The popover's first line still says what matters most: what is out and when it is back, or what runs out and when. The rest of the report's ATTENTION was a count, "+3 more", and the app worded what it showed on its own. Now it shows everything the report says, in the report's words.
- Alerts. The first line ends with a link, "11 alerts ›", which shows every entry of ATTENTION in place of the tab, in the report's order: one line each, with the mark of its kind, what it is about, and what to know, as the console says it. A line the popover cuts is whole in its tooltip. A click on a line opens its subscription in Limits, or its devices in Usage, and Back goes back to the tab. The last line, Most room, names the subscription with the most left. See the alerts.
- Idle. A paid subscription the team has not used for a week is the last alert, a dim line, "idle 9d, paid and unused", as the report's new IDLE says below.
- The countdown. A Limits row that runs out before its reset counts down to when it does, "↘17h 19m" in orange, in place of the time until the reset, and names the window under the row: "7d runs out ~Fri 08:59 · resets Mon 02:40". When an old reading says it has run out by now, the row says "↘ ran out".
- Details. A click on a subscription now also gives the forecast of the window that runs out and the pace it takes, "164% at reset · this week's pace"; how old its reading is and which device took it; what fills it from outside the team; who spent the window since it began, in weighted tokens, "build-01 84M, scout 35M +8"; when it was last used; and why the report calls it idle or could not read it. See the details of a subscription that runs out.
- Not read. The line under a tool's subscriptions that names those with no reading, such as "Not read: unknown · not logged in on build-01 …", is cut at the popover's width. A click on it opens the report's reason for each whole, with what to do, and so does the alert of a subscription not read.
- Devices. Usage says a device's problem under its row in the words of the status view's NOTE: the error it reports, since when it has been silent, or the release it is behind and why it does not update.
- An old reading stays in sight. When the reading behind the first line is old, the line keeps its age, such as "reading 1d old", at every width, ahead of the count of the others.
- Terminal. The menu under the dot beside the first line has Open Report in Terminal, Device Status in Terminal, and All Projects in Terminal. Each opens a Terminal window that runs
ai-usage report, with--devicesor--projects, with the PATH and state folder the collector uses. Device Status is left out when this Mac is the team's only device.
The README's Menu bar app has both pictures, in dark and light.
IDLE, and where tokens with no login come from
ATTENTION has a new kind, IDLE, and NO READING now says where the tokens no login was known for come from:
NO READING claude unknown not logged in on build-01 (~/triage/.claude): run claude and log in
IDLE grok 7f3b9c21 idle 9d, paid and unused
- IDLE is a paid subscription with a reading that no team device has used for 7 days, by when it was last used, and that nothing says someone outside the team uses: its weekly window is under 1%, or began before that last use. It is dim and comes last, since nothing about it is due soon: use it again, or cancel it. It takes the place of UNDER for that subscription, and is never said of
unknown, Hermes, or a free plan. - NO READING for
unknownnames the device, and the folder when it has several, whose tool answered that nobody is logged in, with how to log in, and the row ofunknownin SUBSCRIPTIONS says what to do. Without such an answer, it says nothing rather than guess. - JSON. Each
attentionentry hassubjectandtext, its line as the console says it at its longest, and each team device hasnote, its NOTE in the status view, so an agent can show what the console says without wording it again. Clocks in them are in the time zone of the machine that made the report, andai-usage report --fromfills them in for a report saved without them.attentioncan hold the new kindidle, which a reader that does not know it should skip. The schema version stays 4. See docs/json-schema.md.
The snapshot does not change: a device on v0.5.1 works all of this out from what its teammates already send, whatever release they run.
macOS: an update no longer costs a scheduled run
On macOS 27, launchd pins the launch agent it loads at login to its binary's code signature. ai-usage is signed ad hoc, so the signature is the binary's hash, and every release has another. Once an update had replaced the binary, launchd refused the agent's next run, with OS_REASON_CODESIGNING, and at the run after it repaired the pin and ran the new binary. So each update cost a Mac one scheduled run, and its reading was half an hour old before the next one.
- A run you start that installs a release, such as
ai-usage update, the menu bar app's Update button,ai-usage, orrin the view, now has launchd start the agent at once when launchd pinned it. launchd refuses that start and repairs the pin at a second one right after, so no scheduled run is refused, and it records the new binary for the next login too. The agent then collects, as at a scheduled run. - A scheduled run that installs a release cannot do the same, since launchd does not start the agent while it runs, so launchd still refuses the next run. Any run you start after that refusal has launchd start the agent, which repairs the pin and collects:
ai-usage,ai-usage status,ai-usage report, or the menu bar app, which reads the report every minute. With the app open, the refused run is made up within about a minute. ai-usage schedule statussays when launchd refused the agent's last run. When launchd still refuses it after the repair,ai-usage statusshows that as a schedule problem, withai-usage schedule installas the fix.
The README's Troubleshooting says how to see a refusal in launchctl print.
Compatibility and upgrade
What to do. Nothing. Releases from v0.2.5 on install v0.5.1 within 15 minutes, and on a Mac the menu bar app updates with the binary. On macOS 27, a release before v0.5.1 makes that update, so launchd can refuse the run after it once, as before. The run after that brings the agent back, and so, as soon as launchd has refused it, does any ai-usage you start, or the menu bar app's next read of the report, since both run v0.5.1 by then.
Other systems. An agent launchd pinned to nothing, as on macOS 26 and earlier, is left as it is, and so are cron, schedule run, and Task Scheduler.
State. Nothing changes in the state folder or the snapshot. The JSON gains subject, text, note, and the idle kind, and no field changes its meaning.
Known limitations
- After a scheduled run installs a release, launchd still refuses the next scheduled run. A run you start after that, or the menu bar app's next read of the report, makes it up.
ai-usage schedule installloads the agent from the command line, which launchd pins to nothing until the next login. launchd still records the binary it pinned before, so the first run after that login can be refused once, and launchd repairs it at the run after.- A run you start while the agent runs leaves the pin to launchd, which does not start the agent twice.
What's Changed
- Report the app's words: ATTENTION subject and text, device notes, IDLE, and why tokens have no login by @neoromantic in #11
- Menu bar app: every alert, run-out countdowns, caveats, and the report in Terminal (#7) by @neoromantic in #12
- Keep the macOS schedule running after self-update by @neoromantic in #14
Full Changelog: v0.5.0...v0.5.1
v0.5.0
UTILIZATION: how full each window got before it reset
A new section under SUBSCRIPTIONS says how full each subscription's windows got before each reset, over the last 90 days. SUBSCRIPTIONS says how this week will end; UTILIZATION says how the last ones did: a subscription that runs out week after week, or one mostly left unused.
UTILIZATION fullest % before each reset
CLAUDE CYCLES OUT AVG NEWEST FIRST
● mira@studio.dev 13 5 91% 100 94 ~71 100 88 100 82 97 100 76 91 100
5h 11 4 77% 100 64 38 100 92 ~55 71 100 47 83 100
CODEX
● mira@studio.dev 13 · 47% 52 41 ~29 63 48 37 55 44 71 39 46 58
- An account's row is its main window, the weekly one, with its other windows, such as a model's or the 5-hour one, under it. CYCLES counts the periods that ended, OUT those that ran out, and AVG is their average. Then comes each period's percent, newest first, colored as AT RESET is: out at 100%, tight from 85%, ok from 50%, and under below that.
- A period is one reset of a window, and its percent is the fullest reading any device took of it. A week that ran out stays at 100% when the provider resets it early or takes usage back.
~marks a period last read well before it ended, as when the laptop slept, so it may have got fuller.- A period read only at 0% is left out, and so is Hermes, whose quota is another login's. Of a window shorter than a day, such as the 5-hour one, only the newest 12 periods are kept.
- The history is there from the first run after the update: each device finds the periods in the samples of the last 90 days that earlier releases kept too.
- The JSON has
utilizationon each team account, andcycles_held_back_byandcycles_reasonon the team. The schema version stays 4. See docs/json-schema.md.
How the team's cycles arrive
Each device sends its periods in its snapshot, as cycles, and the team takes the fullest reading of each period across devices: a week one laptop read at 71% and a server read at 100% shows 100%.
A release before v0.5.0 rejects a snapshot with fields it does not know. So a device sends cycles only once its last team read, at most 3 hours old, shows every teammate on v0.5.0 or later. The gate v0.4.0 added for weighted tokens must be open too. Until then no device sends them, each device's UTILIZATION shows its own periods, and a line under the section's title names the devices holding them back and what to do:
UTILIZATION fullest % before each reset
only this device's cycles: leo-air runs v0.4.0: it updates itself within 15 minutes
- Releases from v0.2.5 on update themselves within 15 minutes, so on most teams the line is gone within about half an hour of the release. Older ones need
install.shagain; the line says so. - A retired device, or one silent for over a week, holds the cycles back until
ai-usage team forget-device IDforgets it. - A relay that does not accept
cyclesyet gets the snapshot without them in the same run, and is offered them again a day later. Meanwhile the line saysthe relay did not accept cycles, with when the device tries again.
Forecasts: no false OVER after an early reset
A window's pace is now measured over a tenth of the window at least: 16.8 hours in a week, and 30 minutes in a 5-hour window.
Until now the forecast took a week's first busy hours as the pace of the week. When Codex reset every weekly window early, each account that worked a little that morning showed OVER with most of its quota left: 6% used 3 hours into the week read as 311% at the reset. A window that sat at 0% and then read 1% a few minutes later did the same in mid-week.
- The average pace runs from the window's start, or from its last reading at 0%, but over a tenth of the window at least: what was used in less time counts as used over that tenth. In its first tenth, a window is
overonly once about a tenth of it is used already. - The recent pace, the slope of the last day's readings, needs readings that span a tenth of the window: at least an hour, as before, so 5-hour windows forecast as they did, and at most 20 hours.
- Past a window's first tenth, with a reading every 15 minutes, nothing changes. The JSON's shape is the same.
Notifications in the menu bar app
The menu bar app now posts a notification when a window runs out, or will run out before its reset: the windows ATTENTION marks out and over.
- Out: "Claude mira · 5h is out", with when it is back.
- Will run out: "Claude mira · 7d runs out ~Sat 10:28", with how long that is before its reset.
Settings, General, Notifications picks which:
| Setting | Choices | Default |
|---|---|---|
| When a Window Is Out | on, off | on |
| When a Window Will Run Out | Within a Day, Any Time, Never | Within a Day |
| Subscriptions | This Mac's, The Team's | This Mac's |
- Within a Day says nothing until the window runs out within the next 24 hours, since a forecast early in a long window can change by the next reading.
- This Mac's are the subscriptions logged in on this Mac. The Team's are all the subscriptions with a reading, bots' included, and the notification also names the device that uses one most, as in "used on build-01 +9".
- Each window is said once per period and state: once when it will run out, once more when it is out, and again only after it resets. A forecast that dips under 100% and rises again says nothing new, and a restart repeats nothing.
- A click on a notification opens the popover on that subscription in Limits.
- macOS asks for permission when there is first something to say, or when you turn a notification on. If you decline, the app asks no more, and Settings says that notifications are off, with a button to System Settings.
The notifications come from the report the popover shows; the app computes nothing of its own.
Long log lines
A session log line over 32 MiB, such as a prompt with a huge paste or a tool's huge output, was skipped and counted as malformed. Claude Code, Codex, or Grok then showed partial with 1 malformed lines at every run, and the usage on such a line was lost.
- Such a line is now read with its lists and long texts left out, in no more memory than 32 MiB, so its usage counts and it is no error.
- A line still over 32 MiB after that is skipped and named apart, as
N lines too long to readrather thanN malformed lines. The tool stays partial for it, since that line could have held usage. - The first v0.5.0 run reads the Claude Code transcripts once more in full, since what it keeps of them moves to
cache/claude-v2.gob, and removes the oldclaude-v1.gob. The ledger counts only what a session grew, so nothing is counted twice.
Compatibility and upgrade
What to do. Usually nothing. Releases from v0.2.5 on install v0.5.0 within 15 minutes, and on a Mac the menu bar app updates with the binary. Reinstall a teammate on a release before v0.2.5 with install.sh, and forget a retired device with ai-usage team forget-device, so that the team's cycles arrive.
Relay. If you run your own relay, deploy it from this release before your devices update: it accepts snapshots with cycles and without. An older relay answers a snapshot with cycles with 422. The device then sends it without them in the same run and offers them again 24 hours later, so nothing breaks, but each device shows only its own UTILIZATION until the relay is updated. See docs/relay.md.
Mixed-version teams. A device on an earlier release keeps reading the team as before, since no device sends cycles while it is there. One case is left, as with v0.4.0: a device the team has never seen, freshly installed with a release before v0.5.0 that someone pinned, can read snapshots with cycles on its first run and skip them until it updates itself at its next run, or until the others close the gate at their next team read. The installers always install the newest release.
State. The ledger keeps its format; state.json gains one field, for when the relay last refused cycles.
Building from source. ai-usage now needs Go 1.26 or newer, up from 1.25, for the updated modules, among them Bubble Tea v2.1.0 and SQLite v1.60.1. go.mod names Go 1.26.9 as its toolchain, so an older Go 1.26 downloads 1.26.9 to build, unless GOTOOLCHAIN=local. A relay run in Docker needs the golang:1.26 image. A build from source reports devel and counts as new, so it never holds the cycles back. Give a hand build with a suffix a release of v0.5.0 or later before it, such as v9.9.9-local: one before v0.5.0 counts as that release and holds the cycles back from the whole team.
Known limitations
- UTILIZATION knows only the periods some device of the team read. A period nobody read near its end is marked
~, and one from before ai-usage was installed, or over 90 days ago, is not there. - A v0.4.x built from source reports
devel, so it does not hold the cycles back, and it skips the snapshots with them until it is built again from v0.5.0. - Notifications come from the menu bar app, only while it runs, and only on macOS. The collector itself notifies no one.
What's Changed
- Measure a window's pace over a tenth of the window at least by @neoromantic in #4
- Read a valid log line over 32 MiB instead of counting it as malformed by @neoromantic in #5
- Quota utilization per account: how full each window got before each reset by @neoromantic in #9
- Notify when a window runs out or will run out by @neoromantic in #10
New Contributors
- @neoromantic made their first contribution in #4
Full Changelog: https://github.com/neoromantic/ai-usage/compar...
v0.4.0
Weighted tokens replace tokens in+out
Every token figure on the page is now in weighted tokens: each kind of token a tool records, weighed by what the provider's API charges for it against an input token.
Until v0.3.4 the page counted input plus output and left cache out. That hid most of Claude Code's work. Claude Code sends almost everything new in a session as a cache write, and re-sends the conversation as cache reads, so its input is close to nothing and its in+out is mostly output. Codex counts the same new context as input. Next to Codex, a Claude-heavy project, device, or teammate looked several times smaller than it was, and PROJECTS ranked the busiest Claude work below lighter work.
| Tool | Input | Output | Cache write | Cache read |
|---|---|---|---|---|
| Claude | 1 | 5 | 1.25, or 2 for a write kept an hour | 0.1 |
| Codex | 1 | 8 | 1 | 0.1 |
| Grok | 1 | 5 | 1 | 0.25 |
| Hermes | as the tool its login bills to; Claude's for an unknown route |
Weighted tokens follow what quota spends better than in+out did, but they are not any provider's quota formula. The first-run guide and the ? help say what one weighs.
The JSON keeps usage, days, share, and window_tokens as input plus output, with the meaning they had, and adds burn, burn_days, burn_share, and window_burn beside them, along with fresh (input, cache writes, and output: the new context, whatever the tool). The schema version stays 4. See docs/json-schema.md.
This device
- PROJECTS and USAGE count weighted tokens over your local days: today since local midnight, and 7, 30, and 90 days as the last 168, 720, and 2160 hours. SESS counts the sessions active in the period, not those started in it.
- Projects group better. A folder outside any repository counts with the project folder above it, worktrees that were deleted stay with their repository, and Codex chats make one project. Two long paths that would print alike show what tells them apart.
- Coverage. Where the ledger holds less than 85 days of a tool, the PROJECTS title says since when, as
· claude since Sep 2, and those projects' 90D is dim. Claude Code deletes transcripts after 30 days by default; the guide says how to keep 90. - Claude Code sessions are dated by their last message, cache writes kept for an hour weigh as such, and output Claude Code tracked beyond its transcript counts in the hour it was tracked. Replies whose transcript ends without a final count are counted in
transcript_gaps. - Codex sub-agents count with their family, the history a forked session replays is no longer counted again, and a session whose log several account folders hold goes to the account Codex's own thread database names, else to the one whose weekly window resets when the session's own rate-limit reading says, else stays where it was. A session whose log now holds fewer tokens than were counted is taken back to what the log shows.
- Hermes history is spread over the hours a session worked, so a bot's first run no longer puts months of tokens in one hour, and auxiliary calls on an unknown or
autoroute bill to the session's own login. A chain of sessions whose first one is over 90 days old is counted once. - Claude Code's quota is read with the newest Claude Code installed, from
PATH, the native installer, Homebrew, npm, the desktop app, or an editor extension, so an old copy first on the scheduler'sPATHno longer leaves you without a reading. - The menu bar app shows weighted tokens, the new ATTENTION lines, estimated totals, and reinstalled devices. Next to an ai-usage before v0.4.0 it shows what that one reports, labeled tokens in+out.
Team
- DEVICES × SUBSCRIPTIONS and the devices' status table count weighted tokens over UTC days, as devices send them, and say so:
7d UTC. This device's own row can differ from PROJECTS for that reason. - Columns that do not fit fold into a REST column, so each row's cells add up to its TOTAL at any width.
- A total estimated from a device on an earlier release is marked
~, and the legend says which devices and what to do. - The SUBSCRIPTIONS column of how many devices use a subscription is headed DEVICES, and a subscription with no reading says why in place of its bar, such as which device needs a newer Claude Code.
- A teammate's last error says when it happened, in ATTENTION, the devices' NOTE, and the menu bar app.
- A gap in a teammate's reports counts only time this device's own team reads saw, so a laptop that slept no longer shows gaps for every server.
ai-usage team forget-devicetakes the start of an id, at least the 8 characters ATTENTION prints.
Forecasts and ATTENTION
- A window is forecast at the faster of its average pace since it began and its recent pace, the slope of the last day's readings, from this device's samples and the team's reads. A line forecast at the recent pace says so. A week that speeds up is caught days earlier.
- New ATTENTION lines:
TIGHT: a window of an account on this device will end its reset 85% to 99% full.OUTSIDE: a window rose 5 points or more in the last day while no device of the team spent on it, so someone outside the team uses the subscription.NO READING: a subscription used in the last 7 days has no quota reading, with the reason and what to do.DUPLICATE: a reinstalled device's old record is still on the relay, with theforget-devicecommand that removes it.
OLDnames what an old release cannot do, such as read the Claude quota, and says to reinstall it.+N moresays how many of each kind it hides.- The header dates the relay by the older of the last push and the last team read, and warns once the team was last read over an hour ago.
Reliability
state.jsonis copied tostate.json.lastafter every save. Astate.jsonthat does not parse is kept asstate.json.bad, and the run goes on from the last copy instead of from nothing.- A run without the network is called offline, as one problem, and the tools it read stay ok.
- A clean run clears the last error. A Claude Code that cannot read the quota is no longer an error that marks the device failed: it is a missing reading that names the install to update, such as
update Claude Code to 2.1.208 or newer. - Errors from a tool's command end with what it last printed, up to 100 characters.
- Codex's quota read has a budget of its own and one retry, so a slow answer no longer costs the reading.
- A Claude folder is asked for its quota when the desktop app used the same account more recently than the folder's own sessions.
Performance
- Claude Code transcripts are parsed once: later runs reuse what was parsed, read only the lines added since, and skip prompts and tool results without decoding them. On a long Claude history, a run no longer spends most of its time parsing.
- Scheduled runs start at a point of their own in the minute after the quarter hour, so a team's devices no longer reach the relay all at once.
Compatibility and upgrade
Mixed-version teams. v0.4.0 adds three fields to the snapshot: weighted days, weighted totals per quota window, and when the last error happened. A release before v0.4.0 rejects a snapshot with fields it does not know, so a device sends them only once its last team read, at most 3 hours old, shows every teammate on v0.4.0 or later. Until then every device sends the snapshot older releases read, no device loses sight of the team, and the team's weighted tokens are estimated from input and output and marked ~.
- Releases from v0.2.5 on update themselves within 15 minutes. Older ones need
install.shagain; the legend and ATTENTION name them. - A device that is retired, or silent for over a week, still holds the fields back until
ai-usage team forget-device IDforgets it. A device that expired from the relay is remembered for 90 days, so a laptop on an old release that wakes up later never meets a snapshot it cannot read. - A build from source now reports
devel. One built from source before v0.4.0 reportsdevand holds the fields back until it is built again. A hand build with a suffix counts as the release before the suffix, so build with one from v0.4.0 on, such asv9.9.9-local, not thev0.0.0-localearlier docs suggested. - One case is left: a device the team has never seen, freshly installed with a release before v0.4.0 that someone pinned, can read the new snapshots on its first run and skip them until it updates itself at its next run, or until the others close the gate at their next team read, within about 20 minutes. The installers always install the newest release.
Relay. If you run your own relay, deploy it from this release before your devices update: it accepts the new fields and the older snapshot both. A relay that answers the new fields with 422, such as an older self-hosted one, gets the older snapshot in the same run and is not offered them again for 24 hours. The team read now says when the relay first stored each device, so a forgotten device is told apart from one that expired. Scheduled runs read the team when the last read is 20 minutes old, every other run, instead of hourly: about 32,000 store commands a month per device, so Upstash's free plan fits about 15 devices. See docs/relay.md.
State. The first v0.4.0 run migrates the ledger to count every kind of token per hour, and keeps the file it started from as state.json.pre-v0.4. Each session's totals by kind of token are spread over the hours its logs show, where they are still there, and else over the hours the earlier release recorded. Sessions v0.4.0 reads differently, such as Codex sub-agents and Hermes auxiliary calls an earlier release counted apart, move with their counts, so the upgrade counts nothing twice. To go back to an earlier release, stop the scheduler, put state.json.pre-v0.4 back as state.json, and ...