ai-usage

skill
Security Audit
Fail
Health Warn
  • License — License: MIT
  • Description — Repository has a description
  • Active repo — Last push 0 days ago
  • Low visibility — Only 9 GitHub stars
Code Fail
  • rm -rf — Recursive force deletion command in .github/scripts/install-smoke.sh
  • rm -rf — Recursive force deletion command in .github/scripts/tap.sh
  • rm -rf — Recursive force deletion command in install.sh
  • network request — Outbound network request in install.sh
Permissions Pass
  • Permissions — No dangerous permissions requested

No AI report is available for this listing yet.

SUMMARY

Local usage collector for Codex, Claude, Grok, and Hermes.

README.md

ai-usage

How much of your Claude Code, Codex, and Grok quota is left, for you and every machine on your team,
and whether it lasts until the reset.

Latest release
CI
MIT license
Telegram: @usualguy

Website · Quick start · What it shows · Install · Teams · Privacy · JSON for agents · Commands

The top of the ai-usage page for a made-up team: ATTENTION lists a full Claude window, a Codex login that runs out on Friday, and a server folder where nobody is logged in to Claude Code, and counts the rest, from a silent bot to a subscription nobody uses; below it, every subscription with how full it will be at its reset

A made-up team: two laptops, a Linux server, and nine bots on one Codex login. The whole page · more pictures

ai-usage is one small binary for macOS, Linux, and Windows. The system scheduler runs it every 15 minutes. Each run reads what Claude Code, Codex, Grok, and Hermes already record on disk, asks the installed tools for your account and quota, and exits. Machines that share a team key see each other through a small relay, which sees the numbers but not the names.

Highlights

  • Every subscription on one page. Claude Code and the Claude app's agent-mode sessions, Codex from its CLI, the ChatGPT app, or an IDE extension, Grok, and Hermes. Each account shows its 5-hour, weekly, and per-model windows, and its tokens today and over 7, 30, and 90 days, weighed by what the API charges for each kind.
  • A forecast, not just a percentage. Each window says how full it will be at its reset at your pace, the average since it began or the last day's when that is faster: over, tight, ok, or under. An over window says when it runs out.
  • How full each window got before it reset, over the last 90 days, for every account in the team: a subscription that runs out week after week, or one mostly left unused.
  • ATTENTION, only when something is wrong. A full window, one that will run out before its reset, a machine whose tool fails or that stopped reporting, an old release, a subscription nobody uses.
  • The whole team. Laptops, servers, and bots in containers, every machine against every subscription, with each one's share. A colleague joins with one command. See Teams.
  • Private by design. The relay sees tools, plans, and numbers. Names, emails, and project paths are encrypted with the team key, and every snapshot is signed. See Privacy.
  • Nothing to run or look after. No daemon, no account, no config file. It installs with one line or with Homebrew, and schedules itself. A script install updates itself; a Homebrew one updates with brew upgrade or ai-usage update.
  • Spends none of your quota. It asks the tools you already have instead of calling Anthropic, OpenAI, or xAI, and never reads your credentials. Asking Claude Code for its usage spends no tokens. See Other data folders.
  • A menu bar app on macOS. A ring in the menu bar shows what is left; a click says what is out or runs out, and when, and a notification says it once as it happens. See Menu bar app.
  • JSON for agents, with a versioned schema, so an agent can pick the account with room. See JSON for agents.

Quick start

curl -fsSL https://raw.githubusercontent.com/neoromantic/ai-usage/main/install.sh | sh

That line installs ai-usage, registers it with launchd or cron, and prints your report. On macOS 14 and later it also installs the menu bar app.

Or, from v0.8.0, with Homebrew on macOS or 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 app

From then on:

ai-usage             # collect now and open the page
ai-usage team key    # print the key a colleague installs with to join your team

A colleague joins your team with that key:

curl -fsSL https://raw.githubusercontent.com/neoromantic/ai-usage/main/install.sh |
  AI_USAGE_TEAM_KEY='aiu-team-1:…' sh

Questions, ideas, a tool you want supported: write me on Telegram, @usualguy, or open an issue.

Contents

What it shows

The whole ai-usage page for a made-up team: two laptops, a server, and nine bots that share one Codex login

The team in the picture is made up. docs/demo has its report and more pictures, and ai-usage report --from docs/demo/team.json shows it in your terminal.

The same page as text, for another made-up team
ai-usage · annbook · team qmvrtzpa                                  ● collected 7m ago  ● relay 7m ago  ● up to date

ATTENTION
 OUT      codex [email protected]   back Fri 17:09, in 1d 23h
 OUT      claude ann · Fable   back Sun 01:00, in 3d 7h
 OVER     claude [email protected]  runs out ~Thu 01:17 at this week's pace, 2d 23h before reset · reading 1d old
 OVER     codex [email protected]  runs out ~Sat 03:01 at the last day's pace, 2d 17h before reset
 OUTSIDE  codex [email protected]  +25 points in 20h, no team device spent
 ERROR    Mac.localdomain      codex: app-server exited without answering
 +3 more: 1 no reading, 1 duplicate, 1 old

SUBSCRIPTIONS  7 · 2 out · 1 over · 3 no reading

  CLAUDE         PLAN        THIS WEEK                 LEFT  RESETS              AT RESET  DEVICES              LAST
● [email protected]   max         ━━━━━━━━━╋━━━━━━────────  ~33%  3d 7h  Sun 01:00  ~174% over   2  annbook            7m
    Fable                    ━━━━━━━━━━━━╋━━━━━━━━━━━    0%  3d 7h  Sun 01:00         out
  [email protected]  max         update Claude Code or p…     ?                             —   1  Mac.localdomain    1h

  CODEX
● [email protected]   pro         ━━━━━━━━━━━━━━━━━╋━━━━━━    0%  1d 23h Fri 17:09         out   2  annbook            1h
  [email protected]  prolite     ━━━━━━╋━━━──────────────   58%  5d 2h  Mon 20:12   165% over   1  MacBook-Pro-Kim    3h
● [email protected]  pro         ━━━┃────────────────────   88%  5d 22h Tue 16:30      81% ok  11  srv1              34m
  unknown                    ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈     ?                             —   1  Mac.localdomain   16m

  GROK
● a4c2e917       SuperGrok…  ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈     ?                             —   1  annbook           11h
UTILIZATION  fullest % before each reset
  only this device's cycles: ci-runner-eu-west-build-pool-node-0042ab runs v0.3.4: it should update within 15 minutes

  CLAUDE         CYCLES  OUT  AVG  NEWEST FIRST
● [email protected]       13    5  86%   100  100   92  ~78  100   64   88  100   71  ~45   96  100   83
    Fable             3    1  64%   100   62  ~30
    5h               12    2  59%    38  100   71  ~52   90   12   64  100   41   77    8   55

  CODEX
● [email protected]        6    2  86%   100   87   93  100   58   76
  [email protected]       4    1  94%    97   91  100  ~88
● [email protected]       9    ·  28%    22  ~35   18   40   27   31   12   44   19

DEVICES × SUBSCRIPTIONS  15 · 7d UTC · M weighted tokens                  ‹usage›  status   ‹tokens›  share
                        CODEX ─────────────────────────────  CLAUDE ──────────  GROK ───  NO QUOTA
                             lee      ann  unknown      sam       ann      kim  a4c2e917    hermes     TOTAL
  srv1                      1080       53        ·        ·        25        ·         ·        49      1207
● annbook                      ·      810        ·        ·       230        ·        17         ·      1057
× Mac.localdomain              ·        ·      427        ·         ·      125         ·         ·       551
↓ MacBook-Pro-Kim              ·        ·        ·      269         ·        ·         ·         ·       269
  bot-a                      135        ·        ·        ·         ·        ·         ·         ·       135
  bot-b                      113        ·        ·        ·         ·        ·         ·         7       119
  bot-c                       72        ·        ·        ·         ·        ·         ·         4        76
  bot-d                       54        ·        ·        ·         ·        ·         ·         ·        54
  bot-e                       41        ·        ·        ·         ·        ·         ·         ·        41
  bot-f                       32        ·        ·        ·         ·        ·         ·         ·        32
  bot-g                       18        ·        ·        ·         ·        ·         ·         ·        18
↓ ci-runner-eu-west-b…        18        ·        ·        ·         ·        ·         ·         ·       ~18
  bot-h                        9        ·        ·        ·         ·        ·         ·         ·         9
  bot-i                        2        ·        ·        ·         ·        ·         ·         ·         2
~ Mac.localdomain              ·        ·        ·        ·         ·        ·         ·         ·         ·
  TOTAL                     1572      863      427      269       256      125        17        59     ~3586

PROJECTS  annbook · by 7d · M weighted tokens · claude since Sep 2
  PROJECT                                            7D   90D  SESS  VIA            LAST
  ~/src/acme/app                                    975  3465    14  codex, claude    1h
  ~/Vault                                           194   585    18  codex            3h
  ~/src/ai-usage                                     98    98     1  claude, grok     7m
  ~/src/site                                         82   279     2  codex            2d
  ~/src/acme/os                                      36   105     1  claude, grok    11h
  ~/dotfiles                                         17    41     1  codex, claude    4d
  ~/scratch                                          17    36     1  claude           5d
  ~/src/acme/relay                                   14    41     1  codex            1d
  ~/Library/Mobile Documents/…/Notes/Projects/2026    7    24     1  claude           6d
  ~/src/acme/landing                                  4    32     1  codex            6d
  + 4 more

━ used  ─ left  ┃╋ even use  ┈ no reading  ? unknown  — no forecast  ● here  × error  ↓ old  · none  ‹› chosen
~ stale/estimated: ci-runner-eu-west-build-pool-node-0042ab runs v0.3.4: it should update within 15 minutes

The header names this machine and the team, and says whether collection, the relay, and self-update are healthy; when one of them fails, its error is in ATTENTION. The relay's age is the older of the last push and the last read of the team, and its dot turns yellow once the team was last read over an hour ago. ATTENTION appears only when something is wrong: a subscription that is out, or will run out by its reset at its pace; one logged in on this machine that will end its window nearly full; one whose window fills while no team machine spends on it; a machine whose collector or one of its tools fails, or that has not reported for a day; a subscription in use with no reading, with why and what to do, which for tokens no login was known for names the machine, and the folder, where the tool says nobody is logged in; a reinstalled machine's old record; machines on an older release; a subscription past half its window that will be left mostly unused; and, dim, a paid subscription no team machine has used for a week, which is IDLE in place of that. An OVER line whose time has passed with nobody on the team spending on the subscription since says it "would run out" then, with the reading's age, as nothing says it did.

SUBSCRIPTIONS has one row per Claude, Codex, and Grok account, grouped by tool, the worst first. The bar is the weekly window: the heavy line is what has been used, and the tick how much would be used by now if the window were spent evenly. LEFT is what is left, RESETS when it resets, and AT RESET how full it will be then at its average pace since the window began, or since it was last at 0%, or at the last day's pace when that is faster, what it gained since the newest reading a day old. Each is measured over a day at least, or a tenth of a window shorter than a day, so a busy first morning does not read as the pace of the week, and before then the forecast shows only when it is over already: over at 100% or more, which means it runs out by its reset, tight from 85%, ok from 50%, and under below that. A window that limits the account more than the weekly one, such as a model's window or a full 5-hour window, gets an indented row of its own. ~ marks a reading over 6 hours old, unless the window is full, since a full window stays full until it resets. ? marks an account with no reading, a window that has reset since it was read, or a Claude 5-hour or weekly window that a refused request did not read. DEVICES counts the machines that used the account in this window and names the busiest. Hermes is a tool, not a subscription: what it spends through a Codex or Grok login counts as that login's use.

UTILIZATION is how full each subscription's windows got before each reset over the last 90 days: the fullest reading of each period, so a window that ran out stays at 100% even when the provider reset it early or took usage back. 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, indented 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. ~ marks a period last read more than 6 hours before it ended, or a quarter of a shorter window, as when the laptop slept, so it may have got fuller. A period read only at 0% is left out, and so is one that ended over 90 days ago and, of a window shorter than a day, any but the newest 12. Narrower, the section shows fewer periods, then drops CYCLES. The team takes each period's fullest reading from every machine that read it; see Teams.

UTILIZATION for one developer over 90 days: Claude's week ran out 5 times in 13, its 5-hour window 4 times in 11, and Codex and SuperGrok were never more than 71% full

One developer's subscriptions: at this week's pace Claude runs out on Saturday, 12 hours before its reset, and its model window tonight; Codex has 78% left, and SuperGrok will leave most of its week unused

DEVICES × SUBSCRIPTIONS is every machine against every subscription, in millions of weighted tokens over the last 7 UTC days. A weighted token weighs each kind of token by what the provider's API charges for it against input: Claude's output 5, a cache read 0.1, and a cache write 1.25, or 2 when it is kept an hour; Codex's output 8 and a cache read 0.1; Grok's output 5 and a cache read 0.25. So a session that rereads a large cache shows more than its input and output, and less than every token counted alike. It is a proxy for how much quota a session uses, not the provider's formula. NO QUOTA holds the tokens no subscription covers, such as Hermes on an API key. Columns that do not fit fold into REST, so each row's cells add up to its TOTAL. A machine on a release before v0.4.0 sends no weighted tokens, so its are estimated from its input and output: ~ marks its TOTAL and the team's, and the legend says why and what to do. The section has a second view, status: a row per machine with its user, its release, how long ago it reported, the tools it reads, its tokens today and over 7, 30, and 90 days, and a note of what is wrong with it, such as an error, an update to install, or why it does not update itself. --devices prints that view. With only this machine, the section is USAGE instead: a row per subscription, with today, 7 days, 30 days, and 90 days. PROJECTS lists this machine's top projects over every account; --projects lists all of them. A project is a git repository, with its subfolders and worktrees, those Claude Code and Codex make included; a folder outside any repository counts under the nearest folder above it that the tools worked in, and is a project of its own when there is none, never reaching up to the home folder, Downloads and folders like it, or a temporary folder. USAGE and PROJECTS count this machine's local days, and 7, 30, and 90 days as rolling hours, while the team's tables count UTC days, as machines send them, so this machine's own row there can differ. Today is the exception once every machine runs v0.7.0 or later: each sends its tokens by hour for the last two days, so the team's today counts from your own midnight, wherever each teammate is, and the title says today without UTC. Where the ledger holds less than 90 days of a tool, as when Claude Code deleted older transcripts, the PROJECTS title says since when. A dim legend at the bottom explains only the marks on screen.

The status view of DEVICES: each machine's user, release, when it last reported, the tools it reads, its tokens, and what is wrong with it

The matrix shows accounts by short names: the part of an email before the @, or the first 8 characters of an id. ai-usage alias gives an account a name for the whole team; see Teams.

When standard input and output are both terminals, ai-usage opens the same page as an interactive view. It scrolls, with the head of DEVICES kept at the top while its rows go by, and the matrix scrolls sideways; s switches DEVICES between the matrix and the status view, p picks the period (today, 7, 30, or 90 days), % shows each machine's percent of each subscription's tokens in the period, and in TOTAL of all the team's, r collects now, ? lists every key, and q quits. Piped output, --json, --plain, and TERM=dumb print the page instead, and so does the installer's first run. The page is at its best at 120 to 160 columns; narrower, it drops columns in a fixed order, down to 80.

The interactive view, key by key: the page, s for status, % for each machine's share, p for 30 days, and ? for help

Menu bar app

On macOS 14 and later, the installer also puts AI Usage, a menu bar app, in ~/Applications and opens it. With Homebrew, or after an install without it, ai-usage app install adds it; it then follows ai-usage. It starts at login and updates with ai-usage.

The AI Usage menu bar app with the made-up team's subscriptions: Claude mira's 5-hour window is out and back in 1h 25m

The menu bar shows a ring of what is left of the fullest window among the subscriptions logged in on this Mac, or among the team's when Settings' Track says so, with its percent left. The ring turns orange when the window will run out before its reset, and red with the time until it is back when it is out. Settings can show the time until reset instead, or the ring alone.

The menu bar item in each state: on track, tight, used up, stale, over, out, time until reset, no reading, and the ring alone

The popover opens with one line that answers the first question: what is out and when it is back, what runs out and when, or that all is on track and where the most room is. When the report has more to say, the line ends with a link, "11 alerts ›", to every entry of its ATTENTION, in its order and its words, in place of the tab: one line each with the mark of its kind, a click opening the subscription in Limits or the devices in Usage, and last the subscription with the most room. Chips under it name what to fix on the devices, such as errors or a device that stopped reporting; a click shows those devices. At the line's end, a dot is the collector's health with the report's age, ↻ collects now, and ⋯ opens the menu. Then three tabs, and for a team, the Team button, which opens the Team window:

Limits: a row per subscription account with a bar of what is left, the percent left, and the time until reset Usage: the tokens of each device over 7 days Projects: this Mac's repositories, grouped by folder
  • Limits: a row per account: a bar of what is left, with the forecast's part faint, the percent left, and the time until the reset. A row that runs out counts down to when it does instead, "↘17h 19m" in orange, or says "↘ ran out" when an old reading has and the team spent on it since, and names the window and its reset under it. When nobody has, the row counts down to its reset, and the line under it says the window "would run out ~Tue 05:18". When another window stops the account, such as a 5-hour window that is out, the row says so under it.
  • Usage: the tokens of each device or each subscription, today or over 7, 30, or 90 days, in whole millions.
  • Projects: this Mac's repositories, their worktrees counted in, grouped by the folder they share.

A click on a row opens its details: each window with its forecast, and the forecast of the one that runs out with the pace it takes, or where it was headed when nobody spent on it since; who spent the window since it began, "build-01 84M, scout 35M, +8 more · since 21 Sep 02:40", the busiest first and the rest as a count; when the subscription was last used; how old its reading is and which device took it, when the reading is old or another device's; and why the report calls it idle or could not read it, which a click on the line of the subscriptions not read also gives whole; a device's release, tools, and errors, in the words of the report's NOTE; a project's sessions. Color means a problem only: red is out, orange runs out before the reset, yellow is tight. ⌘1 to ⌘3 switch the tabs and ⌘R collects now.

The alerts: every entry of the report's ATTENTION, one line each with the mark of its kind, and the subscription with the most room under them The details of a subscription that runs out: its countdown, each window, the forecast at this week's pace, the bot that took the reading, the devices that spent the window, and when it was last used

The menu, under ⋯ or a click on the dot, has the collector's status, a colored dot for each part, and this Mac; then Collect Now, an update when there is one, and Team Window…; the report, the device status view, and every project in Terminal; and Settings and Quit. Each part is under a header, each action has its symbol, and the shortcuts, ⌘R, ⌘D, ⌘,, and ⌘Q, also work while the popover is open. A chip about the collector opens it with the chip's messages first, under Attention.

The menu: Status with the collection, the relay, the update, and this Mac; Actions with Collect Now and Team Window; Open in Terminal with Report, Device Status, and All Projects; and AI Usage with Settings and Quit

The Team window has the whole team on one screen, as the terminal's DEVICES does. The Team button beside the popover's tabs opens it, and so do ⌘D and Team Window… in the menu; with only this Mac there is no team, so none of them shows. Usage puts each device against each subscription, grouped by tool, then the tokens no subscription covers and TOTAL. A cell is shaded by its tokens, a step for every half of a tenfold below the largest, as in the terminal, and its tooltip says "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. Status has each device's user, release (↓ when it is behind the latest), last report, the tools it reads (× for a failing one), its tokens in each period, and the report's NOTE. A click on a heading sorts the rows; until then they are in the report's order. As in the terminal, 1, 7, 3, and 9 pick the period, % switches Usage between tokens and each device's share of its column, and s switches between Usage and Status. The window opens where it was last, at its size.

The Team window on Usage: twelve devices against seven subscriptions under Codex, Claude, and Grok, over 7 days, with heat shading and a TOTAL row The Team window on Status: each device's user, collector version, when it last reported, its tools, its tokens today and over 7, 30, and 90 days, and the report's note

Notifications say when a window is out, "Claude mira · 5h is out", with when it is back, and when one will run out before its reset, "Claude mira · 7d runs out ~Sat 11:12", with how long before its reset. A window whose run-out time passed with nobody on the team spending on it since gets none. They are the windows the report's ATTENTION marks out and over, and Settings picks which: When a Window Is Out (on by default); When a Window Will Run Out, Within a Day (the default), Any Time, or Never, since a forecast early in a long window can say it runs out days ahead and change by the next reading; and Subscriptions, This Mac's (the default), those logged in here, or The Team's, every subscription with a reading, bots' included, where a notification also names the device that uses it most, "used on build-01 +9". Each window gets one notification per period and state, not one at every run: a forecast that dips under 100% and rises again says nothing new, and after its reset a window starts over. The app remembers what it said in its settings, so a restart says nothing twice. A click on a notification opens the popover on that subscription in Limits, when the menu bar has room to show the app's item. macOS asks for permission when there is first something to say, or when you turn a notification on; when you decline, the app asks no more, and Settings says where to turn notifications on.

Settings does what the terminal commands do: this Mac's name in the team (ai-usage name), the names of accounts (ai-usage alias), the relay (ai-usage relay), the team key, open at login, what the menu bar shows and whose subscriptions it tracks, notifications, and updates. About names the app and its version once, beside Check for Updates, which runs ai-usage update and so updates the app too, and says where the command-line tool is, "Command-Line Tool (Homebrew)" when the report says Homebrew installed it. It names the tool's release only when it is another one, "Command-line tool v0.5.1: updates with Check for Updates", or "Command-line tool v0.5.1 (Homebrew): updates with Check for Updates or brew upgrade", or says the tool is not found. Under Homebrew, Check for Updates runs ai-usage update, which runs brew upgrade neoromantic/tap/ai-usage and brings the app to the release brew installed; it waits up to 15 minutes for it. When it fails, the app shows ai-usage's own error, not what brew printed before it.

Settings, General: what the menu bar shows and whose subscriptions it tracks, notifications when a window is out or will run out and for whose subscriptions, this Mac's name, open at login, and About with the app's version and where the command-line tool is

The app computes nothing itself. It runs ai-usage report --json every minute and when the popover opens, and shows what the report says: forecasts, states, what needs attention, and the collector's health. Its notifications come from the same report. The tooltip of a release that is out takes its words from the report too: "ai-usage v0.5.0 installs v0.5.1 on its next check" for a script install, and "v0.5.1 is out; Update runs brew upgrade neoromantic/tap/ai-usage" for a Homebrew one. It finds the binary through the launch agent, or shows the install command when there is none, with the Homebrew one under it. To go without it, install with AI_USAGE_NO_APP=1, or remove it as Uninstall shows; neither self-update nor brew upgrade brings it back, and ai-usage app install does.

Install

macOS and Linux:

curl -fsSL https://raw.githubusercontent.com/neoromantic/ai-usage/main/install.sh | sh

Windows, in PowerShell:

irm https://raw.githubusercontent.com/neoromantic/ai-usage/main/install.ps1 | iex

Or with Homebrew on macOS and Linux, from v0.8.0.

Releases are built for amd64 and arm64 on each OS, and the menu bar app is one build for Apple silicon and Intel Macs. The installer:

  1. downloads the release file for your OS and CPU, and checksums.txt from the same release, and on macOS 14 and later the menu bar app from that release too
  2. checks the SHA-256 checksums
  3. installs the binary into a folder on your PATH (see below), or to %LOCALAPPDATA%\Programs\ai-usage\ai-usage.exe on Windows, where it also adds that folder to your user PATH
  4. saves the relay and joins the team, if you gave them
  5. runs ai-usage once, which registers it with the scheduler and prints the report with a short guide under it
  6. on macOS 14 and later, puts the menu bar app in ~/Applications/AI Usage.app in place of an earlier copy, quits the copy that runs, and opens the new one; with no one logged in at the screen, as over SSH, it says to open the app there once instead

On macOS and Linux, an upgrade replaces the binary where it is: ~/.local/bin/ai-usage, or the ai-usage found on PATH when you can write to its folder. A new install goes into the first of ~/.local/bin and ~/bin that is on your PATH and that you can write to, and never into a folder that belongs to another tool, such as ~/.cargo/bin or Homebrew's /opt/homebrew/bin and /usr/local/bin.

When none of them is, the binary goes into ~/.local/bin, and the installer adds that folder to PATH in the profile of your login shell, the one $SHELL names:

Shell Profile
zsh ~/.zshrc, or $ZDOTDIR/.zshrc
bash on macOS the first of ~/.bash_profile, ~/.bash_login, and ~/.profile that exists, else a new ~/.bash_profile; a terminal on macOS opens a login shell, which reads only that one
bash on Linux ~/.bashrc
fish ~/.config/fish/conf.d/ai-usage.fish, or the same under $XDG_CONFIG_HOME
sh, dash, ash, ksh, or mksh ~/.profile
another shell, such as tcsh or nushell none; the installer prints the line to add

It adds a block that starts with # Added by the ai-usage installer, once: running the installer again adds nothing. A new terminal finds ai-usage by name; in the terminal you installed from, use the full path the installer prints. With AI_USAGE_NO_MODIFY_PATH=1, and as root under sudo, where HOME can still be yours, it leaves profiles alone and prints the line to add.

The installer replaces only its own binary. Another program named ai-usage, or a link such as a version manager's shim, stays where it is, and the installer picks a folder as it would for a new install. When the only folder left holds one, it stops; set AI_USAGE_BIN_DIR then. When another ai-usage still comes first on your PATH, such as an older copy in a folder you cannot write to, the installer says so, because typing ai-usage runs that one.

When Homebrew installed ai-usage for you, which the installer tells by an opt/ai-usage you own under $HOMEBREW_PREFIX, /opt/homebrew, /usr/local, or /home/linuxbrew/.linuxbrew, it stops before it downloads anything. Another user's Homebrew copy, in a prefix you share, does not stop it:

ai-usage install: ai-usage is installed with Homebrew (/opt/homebrew/bin/ai-usage); update it with
`brew upgrade neoromantic/tap/ai-usage`. To use this installer instead,
run `brew uninstall neoromantic/tap/ai-usage` first; the state folder and team key stay.

With AI_USAGE_BIN_DIR set it installs a second copy anyway, which leaves the schedule to Homebrew's; see Two copies.

The installer asks no questions. Run it again to upgrade in place. It reads these variables:

Variable Meaning
AI_USAGE_BIN_DIR where the binary goes, instead of the folder the installer picks; it edits no profile then
AI_USAGE_NO_MODIFY_PATH leave shell profiles alone
AI_USAGE_NAME this machine's name in the team, instead of its host name
AI_USAGE_RELAY relay URL to save before the first run
AI_USAGE_TEAM_KEY team key to join before the first run
AI_USAGE_DOWNLOAD_URL where to download release files from, instead of the latest GitHub release
AI_USAGE_ALLOW_ROOT install for root even though the installer runs under sudo
AI_USAGE_NO_APP leave out the menu bar app on macOS

To join a team while installing, which is how a teammate's machine joins yours (ai-usage team key prints the key and ai-usage relay show the relay):

curl -fsSL https://raw.githubusercontent.com/neoromantic/ai-usage/main/install.sh |
  AI_USAGE_RELAY=https://relay.example.com AI_USAGE_TEAM_KEY='aiu-team-1:…' sh
$env:AI_USAGE_RELAY = 'https://relay.example.com'
$env:AI_USAGE_TEAM_KEY = 'aiu-team-1:…'
irm https://raw.githubusercontent.com/neoromantic/ai-usage/main/install.ps1 | iex

A key typed on the command line stays in your shell history. To avoid that, install first, then run ai-usage team join and paste the key.

Run the installer as the person whose usage you want to collect. Under sudo it stops, because the collector would register root's schedule and read root's tools, and could leave root-owned files in your home. On a server whose bots run as root, install it as root itself, with AI_USAGE_ALLOW_ROOT=1 if you got there through sudo. For a container, see In a container.

On Linux the schedule needs crontab, which containers and some minimal systems lack; ai-usage schedule run takes its place there. The installer needs curl or wget, and sha256sum, shasum, or openssl.

The menu bar app is for the person at the screen, so root never gets it. When it cannot be installed, for example on macOS 13, the installer says why and the rest of the install stands.

Homebrew

From v0.8.0, on macOS and Linux:

brew install neoromantic/tap/ai-usage
ai-usage team join          # optional: paste a teammate's key, to join their team
ai-usage relay set URL      # optional: your own relay
ai-usage name set NAME      # optional: this machine's name in the team
ai-usage                    # collect, register with launchd or cron, and print the report
ai-usage app install        # on macOS 14 and later: the menu bar app

Homebrew installs the binary and nothing else: it runs no command of ai-usage's, so the first ai-usage registers the schedule, as the installer's first run does, and ai-usage app install adds the menu bar app. If you ran ai-usage schedule remove before, run ai-usage schedule install instead of the bare ai-usage, since runs leave a removed schedule removed. Homebrew is per-user: run these as the person whose usage you want to collect, not with sudo.

Type the full name, neoromantic/tap/ai-usage, in every brew command. Homebrew trusts a formula from a tap outside its own by that name, and refreshes the tap before an upgrade only when it is named. If you added the tap first with brew tap neoromantic/tap and Homebrew asks you to trust it, brew trust neoromantic/tap does.

The schedule names Homebrew's stable path, such as /opt/homebrew/opt/ai-usage/bin/ai-usage, which brew moves to each new release, so upgrades never touch the launch agent or the crontab line. A copy Homebrew installed never replaces itself; see Updates.

ai-usage app install puts the menu bar app of the binary's release in ~/Applications/AI Usage.app, as the installer does, whatever installed ai-usage. It needs macOS 14 or later, refuses root and builds from source, quits a copy that runs, and opens the new one; with no one logged in at the screen, it says to open the app there once. Runs keep the app at the binary's release from then on. It says run ai-usage first, so the app finds a report when nothing has been collected yet, and installs the app anyway.

Two copies

Homebrew's copy and one the installer put in ~/.local/bin or ~/bin would take turns registering with the scheduler, so the scheduler runs only Homebrew's. The installer's copy, from v0.8.0, registers Homebrew's copy in its place, as does ai-usage schedule install run from it, and leaves updates to Homebrew's. ai-usage status then says Homebrew's ai-usage at /opt/homebrew/bin/ai-usage runs the schedule; remove this copy: rm /Users/ann/.local/bin/ai-usage. Homebrew's copy says another ai-usage at /Users/ann/.local/bin/ai-usage also runs; remove it: rm /Users/ann/.local/bin/ai-usage. An installer copy before v0.8.0 updates itself to the latest release within 15 minutes, and hands over from then on. Both copies share the state folder, the team key, and the history.

An installer before v0.8.0 could put its copy in /opt/homebrew/bin or /usr/local/bin, where brew then cannot link its own, and brew install says so. Both copies then say to run brew link --overwrite neoromantic/tap/ai-usage, which puts Homebrew's link in place of the installer's copy.

Only your own Homebrew copy counts. Another user's, in a prefix you share, as root sees an admin's /home/linuxbrew/.linuxbrew, runs only that user's schedule, so your copy registers and updates itself as before.

Switch between the installer and Homebrew

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 in your shell profile that starts with # Added by the ai-usage installer; 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.

From Homebrew to the installer: brew uninstall neoromantic/tap/ai-usage, then run the installer again. Its first run registers the installer's copy in place of Homebrew's.

First run

The first run:

  • creates the state folder
  • generates a new team key, unless the installer joined a team, so an install starts as a team of one
  • registers with the scheduler: a line in your crontab on Linux, a launch agent on macOS, a task named ai-usage in Task Scheduler on Windows
  • collects and prints the report
  • prints a short guide under the report: which scheduler now collects every 15 minutes, or what to do when none could be registered; what the team and the relay see; what a weighted token weighs; when it applies, that the team's tables count UTC days, and today from your local midnight, that the ledger holds less than 90 days of Claude, or why the team's totals are estimated; and the commands worth knowing, with how to invite a colleague, ai-usage app install on a Mac with macOS 14 or later that does not have the menu bar app, how to pause the system scheduler's runs, and how to uninstall

The guide is printed once. When the scheduler collected first, or the first run you started used --json, the guide comes with the next ai-usage that prints text; ai-usage report never prints it.

The installer's first run: the report, and under it the guide, which says ai-usage now collects every 15 minutes through launchd, what the team and the relay see, and the commands worth knowing

The crontab line looks like this. It keeps the PATH of the shell that installed it, so that scheduled runs find the tools, and it names the state folder, so that scheduled runs use the same device, team key, and history as your own runs:

*/15 * * * * PATH='/home/ann/.local/bin:/usr/bin:/bin' '/home/ann/.local/bin/ai-usage' collect --quiet --home '/home/ann/.config/ai-usage' >/dev/null 2>&1 # ai-usage

On macOS the same command and PATH go into ~/Library/LaunchAgents/io.github.neoromantic.ai-usage.plist, which launchd runs at every quarter hour in your login session, where the collector can read the keychain. Registering it needs no prompt, though macOS may show a notification that ai-usage added a background item. If the Mac missed runs while asleep, launchd runs it once on wake. With no one logged in at the screen, as over SSH before the first login, there is no session to register in, and ai-usage status says so.

The Windows task runs ai-usage.exe collect --quiet --home <state folder> every 15 minutes. Unlike Task Scheduler's defaults, it also runs on battery, runs once after the computer wakes if it missed a run, never runs twice at once, and is stopped after 10 minutes.

A scheduled run, collect --quiet, first waits up to a minute, the same for a machine every time, so that a team's machines do not all reach the relay at the quarter hour. It sends this machine's snapshot every time and reads the team when its last read is 20 minutes old, every other run.

To pause the collector, comment out the crontab line, disable the task in Task Scheduler, or on macOS run:

launchctl disable gui/$(id -u)/io.github.neoromantic.ai-usage
launchctl bootout gui/$(id -u)/io.github.neoromantic.ai-usage

Runs leave it that way, and ai-usage status says it was disabled by hand. ai-usage schedule install turns it back on.

The state folder is ~/Library/Application Support/ai-usage on macOS, $XDG_CONFIG_HOME/ai-usage or ~/.config/ai-usage on Linux, and %LOCALAPPDATA%\ai-usage on Windows (not the roaming profile, so the device id stays on this machine). AI_USAGE_HOME moves it. The scheduler entry follows the folder of the last run that registered it, so after moving the folder, run ai-usage once with the new AI_USAGE_HOME. It holds:

File Contents
config.json this device's id, the relay, remembered and added homes, the CLAUDE_CONFIG_DIR value seen for them, which login each added Hermes home bills through, whether the schedule is off
team.key the team's private key; this is the secret
team.key.previous the key from before the last team join to another team; also a secret
state.json the last good readings, sessions, and health, and whether the guide is still to be printed; one a later release saved is left as it is, and the run says to update
state.json.last a copy of the state.json this release last saved; a run whose state.json does not parse starts again from it
state.json.bad a state.json that did not parse, kept when a run started again from state.json.last, or from an empty state when that did not parse either
state.json.pre-v0.4 the state.json a release before v0.4.0 saved, kept once for going back to it
projects.json the project each folder belonged to, so a worktree that is gone still counts with its repository
cache/ what the last run parsed of each Claude Code transcript, so the next one reads only what was added
team-cache.json the team's snapshots from the last read
samples/ one file per day of samples, kept for 90 days; UTILIZATION finds each window's periods in them
run.lock, config.lock, schedule.lock keep two runs from overlapping, two commands from changing config.json at once, and tell other runs that schedule run schedules this folder; held with the system's file lock, which ends with its process

Teams

A team is one key pair. Every machine in the team stores the same private key. The team's name is the fingerprint of its public key, like 472ghuwcctyu…, which is not secret.

To add a machine to your team:

  1. On a machine in the team, run ai-usage team key. It prints the private key, aiu-team-1:…. Anyone who has it can read the team's snapshots and publish into the team, so share it the way you would share a password.
  2. On the new machine, install with AI_USAGE_TEAM_KEY as shown above, or run ai-usage team join and paste the key, as after a Homebrew install.
  3. Make sure both machines use the same relay: ai-usage relay show.

Joining saves the previous key as team.key.previous, drops the cached team, and asks the relay, if one is set, to remove this machine from the old team. ai-usage team lists the team's machines. ai-usage team forget-device ID removes a retired machine's snapshot from the relay and from this machine's team view; ID can be the start of the id, at least the 8 characters that ATTENTION shows for a reinstalled machine's old record, as 3f9a1c2e. A snapshot that is not updated expires on its own: after 90 days, or sooner for a machine that has reported for less time than that, but never sooner than 7 days after its last update.

A key cannot be revoked. To shut someone out, start a new team and join the remaining machines to it: on one machine, move team.key out of the state folder, run ai-usage to generate a new key, and give that key to the others.

In the team view, token counts add up across machines. Quota percentages do not: an account's quota is the newest reading any machine has for it.

While any machine in the team runs a release before v0.4.0, every machine sends the snapshot such a release reads, without weighted tokens, so that none drops out of its view. The team's weighted tokens are then estimated from input and output, marked ~, and the legend names the machines holding them back and what to do: releases from v0.2.5 on should update within 15 minutes, and one that has not, as when its update fails or Homebrew installed it, takes ai-usage update on it; older ones need install.sh again. A retired machine on an old release, or one silent for over a week, holds them back until ai-usage team forget-device forgets it.

Likewise, while any machine runs a release before v0.5.0, no machine sends the periods UTILIZATION shows, since such a release cannot read them: each machine shows its own, and a line under the section's title names the machines holding them back and what to do. A relay that does not accept them yet, because it runs an older release, has each machine send its snapshot without them and try again a day later.

DEVICES × SUBSCRIPTIONS for a made-up team: twelve machines, most of them bots, against six subscriptions and the tokens no login was known for, in millions of weighted tokens over 7 days

A machine goes by its host name; a Mac goes by the local host name in Sharing settings, which does not change with the network. ai-usage name set NAME gives it another, and the team sees it after the machine's next run.

An account goes by a short name in the report: the part of an email before the @, or the first 8 characters of an id. To give one a name of your choosing for the whole team:

ai-usage alias                                  # the names the team gave accounts
ai-usage alias [email protected] ann              # name an account
ai-usage alias codex:[email protected] ann-cx     # name it on one tool only
ai-usage alias ann --clear                      # go back to the default name

The account is its label, its current name, or PROVIDER:LABEL; an email matches in any case. The same label on several tools is one person, so a name without a provider goes to each of them. A name is at most 12 columns wide, with no spaces. It travels sealed in this machine's snapshot, so the team sees it after this machine's next run, and the newest name any machine set for an account wins.

Relay

The relay is a small HTTP API that keeps one snapshot per machine. It is needed only for the team view; without one, ai-usage reports on this machine alone.

How a team's numbers travel: each machine sends a signed snapshot with names, emails, and paths sealed; the relay sees tools, plans, percentages, and token counts; every machine reads the whole team back and opens it with the team key

ai-usage relay show                          # the relay in use, or "no relay configured"
ai-usage relay set https://relay.example.com
ai-usage relay clear

A release build may carry a default relay, chosen when the release was built. relay set overrides it and relay clear goes back to it. AI_USAGE_RELAY overrides both for one run.

The relay accepts only a small, fixed-shape usage snapshot signed by the team key. It checks the signature and the shape and stores nothing else. It limits requests per IP address, writes per team, and machines per team (50). Each IP address can also add only 5 new teams and 100 new machines a day.

To run your own relay on Vercel with Upstash for Redis (formerly Vercel KV), use the button, which copies this repository into your Git account and deploys it with a new database:

Deploy with Vercel

Or, from a clone with the Vercel CLI logged in, run sh scripts/deploy-relay.sh. On any other machine, ai-usage relay serve runs the same relay, and so does the official Go image in Docker. Then point every machine in the team at it with ai-usage relay set URL. docs/relay.md covers each way, the limits, and updates; docs/relay-protocol.md specifies the protocol for other clients and relays.

Commands

Command What it does
ai-usage [--json] [--offline] [VIEW] [DISPLAY] collect now and print the report; --offline skips the relay
ai-usage collect [--quiet] [--json] [--offline] [--home DIR] [VIEW] [DISPLAY] the same; the scheduler runs collect --quiet --home DIR, and --home overrides AI_USAGE_HOME
ai-usage report [--json] [VIEW] [DISPLAY] print the last collected report without collecting
ai-usage report --from FILE [VIEW] [DISPLAY] show a report saved with --json, such as one in docs/demo, instead of this machine's; it reads no state and collects nothing
ai-usage status [--json] [DISPLAY] version, last success, last error, relay, schedule, update, and each tool's state, with full error texts
ai-usage team the team fingerprint and its machines
ai-usage team key print the team's private key
ai-usage team join [KEY] join a team; the key is read from standard input when omitted
ai-usage team forget-device ID remove a retired machine's snapshot from the relay and from this machine's team view; ID may be the start of the id, at least 8 characters
ai-usage home list every data folder this machine reads, per tool
ai-usage home add PROVIDER DIR... [--quota-from PROVIDER:DIR] read more data folders; see Other data folders
ai-usage home remove PROVIDER DIR... [--forget] stop reading folders added before; --forget also drops the sessions counted from them, for folders another collector reads now
ai-usage relay show print the relay in use
ai-usage relay set URL save a relay URL, http:// or https://
ai-usage relay clear forget the saved relay
ai-usage relay serve [--addr :8080] [--client-ip-header NAME] run a relay; behind a reverse proxy, name the header it sets to the client's address
ai-usage name show print this machine's name in the team
ai-usage name set NAME name this machine in the team, instead of its host name; at most 64 characters
ai-usage name clear go by the host name again
ai-usage alias list the names the team gave accounts
ai-usage alias ACCOUNT NAME name an account for the whole team, in at most 12 columns; ACCOUNT is a label, a name, or PROVIDER:LABEL
ai-usage alias ACCOUNT --clear go back to the account's default name
ai-usage schedule install register with the scheduler, and let later runs keep it registered
ai-usage schedule remove unregister, and stop later runs from registering again
ai-usage schedule status whether the scheduler runs this binary with this state folder, or was disabled by hand
ai-usage schedule run be the scheduler where there is none, as in a container: collect now and every 15 minutes until stopped
ai-usage update check for a release now; where Homebrew installed ai-usage, run brew upgrade neoromantic/tap/ai-usage
ai-usage app install add the menu bar app of this release, on macOS 14 and later, whatever installed ai-usage
ai-usage version print the version
ai-usage help print usage

VIEW flags choose what the page shows. --projects lists every project on this machine in PROJECTS, not only the top ones. --devices shows DEVICES as each machine's status, and the interactive view opens on it; s switches back. status takes neither.

DISPLAY flags change how the console looks. --json ignores them.

Flag Meaning
--color=auto|always|never auto, the default, colors a terminal unless NO_COLOR is set or TERM is dumb
--ascii draw with ASCII only; the default when the locale (LC_ALL, LC_CTYPE, LANG) is not UTF-8, except in Windows Terminal
--width N lay out for N columns, 80 to 160; the default is the terminal's width, else COLUMNS, else 80
--plain print the report; when standard input and output are both a terminal, the default is the interactive view, which scrolls, reloads, and lists its keys under ?
Variable Meaning
AI_USAGE_HOME the state folder
AI_USAGE_RELAY the relay URL, overriding the saved one
AI_USAGE_NAME this machine's name in the team, overriding the saved one in the runs that see it; launchd and cron do not pass on a shell's variables, so name a computer with ai-usage name set
AI_USAGE_NO_SCHEDULE when set, this run does not register with the scheduler
CLAUDE_CONFIG_DIR, CODEX_HOME, GROK_HOME, HERMES_HOME another data folder for that tool

Other data folders

Each tool's default folder (~/.claude, ~/.codex, ~/.grok, ~/.hermes) is always read, and so are the profiles inside a Hermes folder, the per-account Codex folders Orca keeps, and the Claude Code folder the Claude desktop app keeps for each agent-mode (Cowork) session. A folder named by CLAUDE_CONFIG_DIR, CODEX_HOME, GROK_HOME, or HERMES_HOME in a run you start is remembered for scheduled runs. Folders nothing names, such as bots running under other users, are added by hand:

ai-usage home add hermes /srv/bots/alpha/.hermes /srv/bots/beta/.hermes --quota-from codex:/srv/bots/.codex

Hermes keeps its own login for a Codex or Grok subscription, so the collector cannot read which account it uses. By default a Hermes folder is taken to use the account logged in to ~/.codex or ~/.grok. --quota-from names the folder whose login it uses instead, one per tool, which is then read too; it covers the profiles inside each Hermes folder. ai-usage home shows every folder a run reads and what each bills through, and marks one that is gone as missing. ai-usage home remove will not remove a folder Hermes folders take their quota from until they are removed or named another. Name a folder whose login is kept fresh, by Codex itself or by whatever refreshes it for the bots. A copy that only seeded another login store goes stale: it still says whose it is, but its quota reads fail with HTTP 401.

Each Hermes session's tokens go to the login of the folder it was read from. When Hermes folders bill one route through different logins, as bots on a shared login beside your own Hermes, the Hermes account shows the quota of the login most of its tokens in the last 90 days went through.

Accounts and quota come from the tools themselves. Codex is asked through the codex on PATH; when that one is missing or too old to answer, through the copy the ChatGPT app on macOS or OpenAI's extension for VS Code, Cursor, or Windsurf bundles. Claude Code's quota comes from its usage cache, which it updates only when it reads the usage, as its /usage dialog does. So for a Claude subscription whose cache is missing or at least 10 minutes old, when its folder's sessions show you used Claude Code there in the last hour and since, each run first has Claude Code read it with claude -p /usage, through the newest Claude Code installed: the one on PATH, a native install's versions, Homebrew's, npm's, the desktop app's, or the one an editor extension bundles. The reading is then at most one run older than your last use of that folder, when a run comes within the hour after it. A folder you have not used in the last hour, or not since its reading, is not asked: nothing was spent there since, and your login is not kept alive for it. Nor are two Claude folders that share their session logs through a symlink, since the logs cannot say which login ran those sessions. Claude Code answers /usage itself: no model is asked and no token is spent, and the run names a model that does not exist, as a safety net. It leaves no session and runs none of your hooks, though hooks an organization manages still run. Like any Claude Code session, it renews your Claude login when that has expired, which on a folder in use your own sessions do anyway; the collector itself never reads or writes the login. A login with an API key, a Console login, or a cloud provider has no quota and is not asked. Only Claude Code 2.1.208 and later cache the usage, and from 2.1.0 on, Claude Code exits without an error whether or not it read it. So a Claude Code whose install says it is older is not run, nor is one whose settings set CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC in their env, since it then cannot read the usage. When the read fails, is not made for one of those reasons, or leaves no new reading, as offline, the report shows the last reading with its age, and the tool's status is partial until the folder has been idle for an hour. The reason says why in a few words, then Claude Code's version and what cache there is, such as claude /usage: Claude Code 2.1.150 does not cache the usage; update it to 2.1.208 or later (no cache) or claude /usage: no new reading: could not read the usage (Claude Code 2.1.281, cache 3h old), which it says when Claude Code was offline, its login lacks the profile scope, its settings turn nonessential traffic off where the collector does not look, or a Claude Code whose version is not known is older than 2.1.208, and your team sees it in the device's NOTE. Nothing else Claude Code printed is kept: not your usage, when it resets, or the skills, plugins, and MCP servers it lists. When Claude has refused a request because a window was full, and the cache is older than the refusal, the account the session belongs to shows the windows refused alone, each at 100% until it resets. That reading does not go old while the window is full. A refusal from before the run that found another account logged in to the folder is not used for that account, since it may be the earlier one's. Usage counted in a folder while its tool never answered goes to the first account it names there. Usage counted while it said nobody is logged in stays unknown. A Codex session whose log several folders hold, as Orca links each into every account's folder, goes to the account that Codex's own thread database, state_N.sqlite, says created it, where it says so, and otherwise to the account whose weekly window resets when the session's last rate-limit reading says, within 20 seconds and clearly nearer than any other; else it stays with its first folder. From that database the collector reads only thread ids, account ids, and times, never a title, message, or path.

The Claude desktop app's agent-mode sessions are the exception. The app keeps a folder for each under local-agent-mode-sessions in its data folder (~/Library/Application Support/Claude on macOS, %APPDATA%\Claude on Windows), and every run finds them there again, so they are never remembered. Nobody logs in to them, and nothing is asked about them: each session's tokens go to the account the app recorded for it, or to unknown when that record is missing or names none. From that record the collector takes only the account and the folders, never the session's title, first message, or system prompt. The project is the first folder you gave the session, or Claude app when you gave none, since its working directory is inside the app's virtual machine. These sessions have no quota of their own; when Claude Code reports a quota for the same account, that reading applies.

On a server, run the collector as a user that can read those folders. On macOS and Linux, a Claude folder another user owns is only read: Claude Code is not asked to read its usage, since it would save that user's config, and a renewed login, as the collector's user, out of that user's reach. Its reading is as fresh as that user's own Claude Code left it. Hermes databases are read in place, read-only; a database Hermes has open is read the way any SQLite reader reads it, and one nobody has open is read without taking a lock.

Session logs are read a line at a time. A line over 32 MiB, such as a prompt with a huge paste or a tool's huge output, is read with its lists and its long texts left out, in no more memory than that, so the usage on it still counts and it is no error. A tool's status is partial with N malformed lines when lines that hold usage do not parse, and with N lines too long to read when a line is still over 32 MiB after that, as it could have held usage.

In a container

The collector runs in a container as on any Linux machine: install it inside, as the user whose tools it should read, and each container is a machine in the team. Keep that user's home on a volume, name the machine with AI_USAGE_NAME, and run ai-usage schedule run beside the main process, since containers rarely have cron. docs/containers.md has the install command, an entrypoint, a Dockerfile, and an s6-overlay service.

Nine bot containers and a server on one Codex login, each machine's share of it in the interactive view

JSON for agents

ai-usage --json and ai-usage report --json print the report as JSON with "schema_version": 4. A field changes meaning only with a new schema version. usage and days count input plus output tokens; burn and burn_days beside them count weighted tokens, which the page shows.

ai-usage --json through jq: each subscription's tool, short name, and state

schema_version, generated_at
collector   version, device, team, last run, last success, last error,
            relay, schedule, and update state, and health[] as the header shows it
attention[] out, over, tight, outside, error, no_reading, silent, duplicate, old, under, idle: the account or
            devices, when, and why, and subject and text, the line as the console words it
providers[] claude, codex, grok, hermes: status (ok, partial, error, skipped), error, homes,
            covered_since, accounts[]: label, name, home, plan, state (out, over, tight, ok,
                        under, unknown), quota with from and windows[] (percent, resets_at, main,
                        limits, stale, reset, state, forecast with basis and pace), link, sessions,
                        tokens, usage (today, 7d, 30d, 90d), burn, fresh, days[], burn_days[],
                        linked_usage[], last_active_at, projects[]
projects[]  this machine's projects over every account, with usage, burn, active_sessions, and providers
team        pulled_at, latest_version, estimated, held_back_by, estimated_reason,
            devices[] (error, silent, old, usage, burn, gap, replaced_by, note, limitations), providers[]
            with accounts summed across devices (name, alias, state, quota, usage, burn, users,
            busiest, no_reading, outside, per_device[], linked_usage[]), and matrix (columns[],
            rows[] with share and burn_share, of cells with share and burn_share)

ai-usage status --json prints schema_version, collector, and sources[]. Every field is described in docs/json-schema.md.

Privacy

A snapshot is what leaves the machine. It is at most 64 KB, and the relay rejects any field it does not know.

Sealed with the team key, so only the team can read them:

  • the machine's name (its host name, unless ai-usage name set gave it another) and OS user name
  • account labels, such as an email address
  • project folder paths
  • error messages
  • the names ai-usage alias gave accounts

In plain text, so the relay can check the shape:

  • the team fingerprint, the device id (random), and the collector version
  • provider names, plan names, and whether an account is logged in
  • quota window names, percentages, and reset times
  • session and token counts, tokens and weighted tokens per day and since each quota window began, and timestamps
  • each tool's status: ok, partial, error, or skipped

So the relay's operator can see how many machines a team has, which tools and plans they use, how much, and when, but not who, on which machine, or in which project. The operator also sees the IP addresses that connect, which the relay keeps in rate-limit counters for up to a day.

What the relay keeps for one machine, trimmed: the machine, user, account, and project paths sealed, the provider, plan, quota windows, and session count in plain text

Every snapshot is signed with the team key, and each machine checks the signatures of what it reads. The relay cannot forge or change a snapshot without being noticed. It can withhold one, delete one, or keep serving an older one.

The state folder on your machine keeps account labels and project paths in plain text.

Updates

A release build checks GitHub for a new release at every scheduled run, so every machine the installer set up installs a release within about 15 minutes of its publication; Homebrew's copies update with brew. A run you start less than 10 minutes after a check, such as r in the interactive view, does not check again, even when that check failed; a failed check is tried again by the next scheduled run. The check reads which release github.com/neoromantic/ai-usage/releases/latest redirects to, not the GitHub API, which allows 60 requests an hour per IP address without a token: a host with many collectors behind one address would come close to that. When there is a newer vX.Y.Z release, it downloads the file for this OS and CPU from that release, checks it against checksums.txt from the same release, runs it once with version, and replaces its own binary only if the new one starts and reports that release. That run finishes on the old binary; the next run is the update, which launchd on macOS 27 can refuse once, as Troubleshooting says. ai-usage update checks right away.

The check also runs when a collection fails, for example on a state file this version cannot read, or on a bug that stops the run. A release that breaks collection can then still be replaced by the one that fixes it. Finding the latest release gives up after 30 seconds, so a slow GitHub holds up a run only that long. A download may take several minutes on a slow link; it is dropped only when no data arrives for a minute.

Self-update cannot be turned off for a script install. It skips prereleases, such as v1.3.0-rc.1. A binary in a folder you cannot write to cannot update itself, and ai-usage status shows that error. A release that downloads but does not install, for example one that does not start on this machine, is not downloaded again for 6 hours, and its error stays in ai-usage status; runs still look for a newer release, and ai-usage update tries again at once. Rerunning the installer also upgrades.

The team sees why a machine does not update itself. While its last check failed, its snapshot carries the error, from the next run on, and the status view's NOTE shows it; on a machine that runs an older release, it is an error in ATTENTION too. Once your machine's reads of the team have found another reporting on the same older release for 7 hours, longer than any release takes to update itself, NOTE says how long it has not updated, and so does OLD in ATTENTION. The hours count from its first run found after a newer release was out, so a laptop that was closed meanwhile is not counted, and they start again after it has not reported for a day. A GitHub refusal names its reason, such as a rate limit, without your IP address.

On macOS, the check also keeps the menu bar app at the binary's release, once the binary is at it. When ~/Applications/AI Usage.app is an earlier release, it downloads ai-usage_darwin_app.zip from the binary's release, checks it against checksums.txt, unpacks it beside the app, requires the unpacked app to report that release, and swaps it in with two renames. The running app sees the new version on disk and restarts into it. An app built from source, which reports no release, is left alone, and a missing one is not installed. A failure is an update error in ai-usage status that starts with menu bar app:. An app that downloads but does not install is, like a release, not downloaded again for 6 hours; ai-usage update tries again at once, and says what became of the binary first.

The checksum guards against a broken download. It does not protect against a compromised release, since both files come from the same place.

With Homebrew

A copy Homebrew installed never replaces itself: Homebrew owns its files. Each run still checks GitHub, so the report says when a release is out, v0.8.1 is out; run brew upgrade neoromantic/tap/ai-usage or ai-usage update in ai-usage status, and the JSON's collector.update has "method": "homebrew" and that command. On macOS the check keeps the menu bar app at the binary's release, never ahead of it.

ai-usage update runs brew upgrade neoromantic/tap/ai-usage, with what brew prints, and so does the menu bar app's Update. When brew still has the older release, as when it read the tap a few minutes before, or HOMEBREW_NO_AUTO_UPDATE is set, it runs brew update --quiet and upgrades once more. Then the new binary brings the menu bar app to its release, has launchd start the agent so that macOS 27 refuses no scheduled run, and says installed v0.8.1 with Homebrew; the next run uses it, and installed the menu bar app of v0.8.1 when it did. Otherwise it says up to date (v0.8.1). brew gets 8 minutes, and the new binary 4 more to finish. When brew fails, ai-usage update says brew upgrade failed:, or brew update failed: for the second try, and the last line brew printed; when brew installed a release before it failed, that release still finishes first. As root, it refuses: Homebrew installs are per-user.

A release reaches Homebrew a few minutes after GitHub, once the release workflow has updated the tap. Until then ai-usage update says Homebrew's formula is at v0.8.0; GitHub has v0.8.1, which reaches Homebrew soon, and exits 1, after the release brew did install, if newer than this one, has finished the update. A plain brew upgrade works too, and the next run notices it; see Troubleshooting.

A Homebrew machine updates when someone runs brew upgrade or ai-usage update on it, not within 15 minutes. Until then it can hold back what the team turns on only once every machine runs a release that knows it, as the Teams section says. The report first says it should update within 15 minutes, as for any machine, and once it has reported on the older release for 7 hours, mira-mbp runs v0.8.0 and has not updated; run ai-usage update on it, and its NOTE and OLD say how long it has not updated. When a collection fails while a newer release is out, its error ends with — `brew upgrade neoromantic/tap/ai-usage` may fix it.

A binary built from source reports version devel (dev before v0.4.0). It never updates itself and never registers with the scheduler on its own; run ai-usage schedule install for that.

Troubleshooting

A Mac missed a run after an update

On macOS 27, launchd pins a launch agent it loads at login to its binary's code signature. ai-usage is signed ad hoc, so that is the binary's hash, which every release changes. Once an update has replaced the binary, launchd refuses the agent's next start, and at the start after it repairs the pin, records the new binary for the next login, and runs it. So an update can cost one scheduled run, never more. While launchd refuses it, launchctl print shows:

$ launchctl print gui/$(id -u)/io.github.neoromantic.ai-usage | grep -E '^.(state|last exit|job state|properties) '
	state = not running
	last exit reason = OS_REASON_CODESIGNING
	job state = spawn failed
	properties = inferred program | needs LWCR update | managed LWCR | has LWCR

From v0.5.1, ai-usage saves that run where it can:

  • A run you start that installs a release, such as ai-usage update, the menu bar app's Update button, ai-usage, or r in the view, has launchd start the agent at once when launchd pinned it (has LWCR). launchd refuses that start and repairs the pin at a second one, so no scheduled run is refused. The agent then collects, as at a scheduled run.
  • A scheduled run that installs a release cannot: launchd does not start the agent while it runs. So launchd refuses its next run. Any run you start meanwhile, such as ai-usage, ai-usage status, ai-usage report, or the menu bar app reading the report, has launchd start the agent then, which repairs the pin and collects.
  • ai-usage schedule status says when launchd refused the agent's last run, and ai-usage status repairs it.
  • With Homebrew, ai-usage update and the menu bar app's Update start the agent twice, as a self-update a run you start does. A brew upgrade you run yourself is like a scheduled update: the menu bar app's next read of the report, or any ai-usage you run, notices that the release changed since the last collection and has launchd start the agent twice, which repairs the pin.

An agent launchd pinned to nothing, as on macOS 26 and earlier, is left as it is. When launchd still refuses the agent, ai-usage status says so, and ai-usage schedule install loads it again from the command line, which launchd pins to nothing until the next login. launchd still records the binary it pinned before, though, so the first run after that login can be refused once more, and launchd repairs it at the run after.

Uninstall

Run these while the binary is still installed. forget-device is optional: it removes this machine's snapshot from the relay now instead of letting it expire. ai-usage status shows this machine's device id.

macOS:

pkill -x AIUsageBar
rm -r ~/Applications/AI\ Usage.app
rm -f ~/Library/LaunchAgents/io.github.neoromantic.ai-usage.bar.plist
defaults delete io.github.neoromantic.ai-usage.bar
ai-usage schedule remove
ai-usage team forget-device d-…
rm "$(command -v ai-usage)"
rm -r ~/Library/Application\ Support/ai-usage

The first four quit the menu bar app and remove it and its settings. It opens at login as a login item; when macOS does not let it register as one, it writes that launch agent instead. Its settings remember that it set this up, so without the fourth line a later install would not open at login.

Linux:

ai-usage schedule remove
ai-usage team forget-device d-…
rm "$(command -v ai-usage)"
rm -r "${XDG_CONFIG_HOME:-$HOME/.config}/ai-usage"

Windows, in PowerShell:

ai-usage schedule remove
ai-usage team forget-device d-…
Remove-Item -Recurse "$env:LOCALAPPDATA\Programs\ai-usage", "$env:LOCALAPPDATA\ai-usage"

Then open Edit environment variables for your account from the Start menu and remove the …\AppData\Local\Programs\ai-usage entry from Path.

With Homebrew, on macOS, the first four lines above remove the menu bar app; then, on macOS and Linux:

ai-usage schedule remove
ai-usage team forget-device d-…
brew uninstall neoromantic/tap/ai-usage
brew untap neoromantic/tap                       # optional, when you use nothing else from it
rm -r ~/Library/Application\ Support/ai-usage    # on Linux: rm -r "${XDG_CONFIG_HOME:-$HOME/.config}/ai-usage"

Run ai-usage schedule remove before brew uninstall: without the binary, the launch agent or crontab line stays and fails every 15 minutes, silently. If the binary is gone already, remove the schedule by hand, on macOS with

launchctl bootout gui/$(id -u)/io.github.neoromantic.ai-usage
rm ~/Library/LaunchAgents/io.github.neoromantic.ai-usage.plist

and on Linux by deleting the line that ends in # ai-usage with crontab -e. After ai-usage schedule remove, a later install registers again only with ai-usage schedule install, unless you also removed the state folder.

On macOS and Linux, if the installer added ~/.local/bin to your shell profile, remove the block that starts with # Added by the ai-usage installer, or conf.d/ai-usage.fish for fish.

If you set AI_USAGE_BIN_DIR or AI_USAGE_HOME, remove those folders instead.

Build from source

Go 1.26 or newer. 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:

go build -o ai-usage ./cmd/ai-usage
go test ./...

The menu bar app builds on macOS with Xcode or the Command Line Tools, and runs on macOS 14 and later. macos/build.sh builds it for both CPUs, signs it ad hoc, and zips it; a version that is not plain vX.Y.Z keeps self-update away from it:

(cd macos && xcrun swift run AIUsageChecks)
sh macos/build.sh v9.9.9-dev /tmp/ai-usage-app.zip
ditto -x -k /tmp/ai-usage-app.zip ~/Applications

How releases are built and published: docs/releasing.md.

License

MIT. See LICENSE.

Reviews (0)

No results found