I use Claude Code a lot. Enough that I kept finding myself typing /status every twenty minutes to see whether I was about to bump into the session limit or burn through the weekly bucket. After the tenth time in one afternoon I figured I should just put it on the bar.

So I did. It is called claude-pill and it lives in the top bar of my Hyprland setup, between the weather and the clock.

Claude Pill preview

Repo: github.com/luisg0nc/claude-pill

What it shows

The pill collapses down to two numbers: session percent and week percent. So you might see something like ✨ 12% · 47%, which is the same data /status prints, just always there in the corner of your eye. The sparkle icon and the text color shift through neutral, warning, and error as you climb toward the cap.

Hover and you get a small popup with the rest of the picture: subscription tier (Pro or Max), how long the OAuth token has before it auto-refreshes, the session limit with its reset time, the weekly limit, and the Opus weekly bucket if you have one.

If the token ever expires, the sparkle becomes a warning sign and the text changes to “auth”. That is the only state where the pill is asking you to do something.

How it talks to Anthropic

This was the interesting part. There is no public endpoint for /status. So how does the CLI know your numbers?

I ran strings on the claude binary, grepped for oauth, and found a function called fetchUtilization calling /api/oauth/usage. One quick curl later, with the Bearer token from ~/.claude/.credentials.json, confirmed the response shape:

{
  "five_hour":        { "utilization": 5,  "resets_at": "..." },
  "seven_day":        { "utilization": 47, "resets_at": "..." },
  "seven_day_opus":   { "utilization": 30, "resets_at": "..." },
  "seven_day_sonnet": { "utilization": 17, "resets_at": "..." }
}

That is exactly what the pill needs. The widget reuses the same OAuth token the CLI already has, so there is no separate auth flow to set up. If you can run claude, the pill works.

Being polite to the API

The first version polled every couple of minutes and I immediately thought “I am going to get rate limited by my own widget”. So the service layer has two gates:

  • A soft floor of 10 minutes, which is the default poll interval. Configurable.
  • A hard floor of 60 seconds, which even a manual refresh cannot bypass. This one is in the QML constant, not in the user config, because the bucket on Anthropic’s side does not refill any faster and there is no point hammering it.

On top of that, the last successful response is cached on disk at ~/.local/state/quickshell/user/claude-usage-cache.json with a fetched_at timestamp. When I reload Quickshell (Ctrl+Super+R), the pill reads the cache first and only fires the network call if the cache is older than the poll interval. So a hundred shell reloads in an hour cause exactly zero extra API calls.

The race I almost shipped: FileView reads are async in QML, so the credentials file and the cache file load on different ticks. The initial fetch needs both before it can decide whether to skip the network call. Two boolean flags (_credsLoaded, _cacheChecked) gate a _maybeInitialFetch() function that only fires when both have arrived. Without that gate the first poll would always run with an empty token, fail silently, and the pill would just sit there showing ? until the next timer tick.

Where it lives in the bar

The pill is wired up next to the weather widget on the right side of the bar. I tried putting it in the dense fixed-width group in the middle and immediately squished the clock, which was its own kind of comedy. It lives in the right-side flexible RowLayout now and the clock is happy again.

Loader {
    Layout.leftMargin: 4
    active: Config.options.bar.claudeStatus.enable
    visible: active
    sourceComponent: BarGroup {
        ClaudeStatusIndicator {}
    }
}

Sharing it

It is on GitHub: luisg0nc/claude-pill. MIT license, three QML files, a README with install steps and debugging one-liners.

Worth being upfront about the requirements: this is not a fully standalone Quickshell widget. It imports a handful of singletons and styled widgets from the end-4 / illogical-impulse dotfile base that I run on top of, things like StyledText, StyledPopup, BarGroup, Appearance. If you already run that base, three cp commands and a small config block and you are done. If you run a different Quickshell config, the service file (the one that polls the API and manages the cache) is dependency free, so you can lift that out and rebuild the pill UI in whatever widget toolkit your config uses. The interesting bits, the endpoint reverse, the caching policy, the credential reuse, all live in the service.

What I learned

Two small things worth writing down.

First, when you reverse engineer an undocumented endpoint, you do not need a full mitmproxy setup. strings on the binary plus one live curl got me from “no idea” to a working integration in about ten minutes. I almost over-engineered the discovery step.

Second, on-disk caching with a hard floor is the cheapest possible kindness you can do for an API. The Anthropic bucket does not care about the difference between a poll every 60 seconds and a poll every 10 minutes, but the latter is far less likely to ever surprise me with a 429. Soft floor for the common case, hard floor for the “I clicked refresh ten times” case, and a single timestamp file to glue them together.

The pill has been sitting on my bar for a few days now and I have not opened /status once since.