158 lines
5.6 KiB
Markdown
158 lines
5.6 KiB
Markdown
# claude-usage
|
|
|
|
A Linux system tray indicator that shows your Claude.ai usage — current session (5-hour) and weekly — directly in the panel.
|
|
|
|
Renders a small ring icon with the percentage, updates every 10 minutes, and has an optional "live" mode that keeps a Chromium instance open and reads the DOM every 15 seconds.
|
|
|
|
## How it works
|
|
|
|
Claude.ai does not expose a public usage API, so this scrapes `https://claude.ai/settings/usage` using a logged-in Playwright session:
|
|
|
|
- **`scraper.ts`** — Playwright + Chromium. On Linux, it uses Xvfb (a virtual display) to run the browser "headed" (required to pass Cloudflare) without showing any windows. The session is persisted to `session/state.json` so you only need to log in once.
|
|
- **`tray.py`** — GTK3 + AyatanaAppIndicator3. Draws the icon with Cairo, calls the scraper via `bun`, and renders the result as a circular progress ring (green / yellow / red by percent).
|
|
|
|
## Platform support
|
|
|
|
| Component | Linux | macOS | Windows |
|
|
|-----------|-------|-------|---------|
|
|
| `scraper.ts` (CLI) | Full | Works without Xvfb (browser visible) | Works without Xvfb (browser visible) |
|
|
| `tray.py` (system tray) | Full | Not supported | Not supported |
|
|
|
|
The tray indicator depends on GTK3 + AyatanaAppIndicator3, which is Linux-only. Porting it to Windows or macOS would require swapping in something like [`pystray`](https://github.com/moses-palmer/pystray) (cross-platform tray) or [`rumps`](https://github.com/jaredks/rumps) (macOS).
|
|
|
|
The scraper itself is portable — it's just Bun + Playwright. The only Linux-specific part is the Xvfb wrapper used to hide the browser window during automated polling.
|
|
|
|
---
|
|
|
|
## Linux
|
|
|
|
### Requirements
|
|
|
|
- **bun** — installed at `~/.bun/bin/bun` (the path is hard-coded in `tray.py`; edit if yours differs)
|
|
- **Python 3** with `PyGObject`, `AyatanaAppIndicator3`, `cairo`
|
|
- **Xvfb** — virtual display server
|
|
- **Playwright Chromium**
|
|
|
|
On Debian/Ubuntu/Mint:
|
|
|
|
```bash
|
|
sudo apt install python3-gi gir1.2-ayatanaappindicator3-0.1 python3-cairo xvfb
|
|
```
|
|
|
|
### Install
|
|
|
|
The tray script expects the repo at `~/.local/share/claude-usage/`:
|
|
|
|
```bash
|
|
git clone https://gt.zyon.no/Stian/claude-usage.git ~/.local/share/claude-usage
|
|
cd ~/.local/share/claude-usage
|
|
bun install playwright
|
|
bunx playwright install chromium
|
|
```
|
|
|
|
First login (opens a real Chromium window so you can log in manually):
|
|
|
|
```bash
|
|
bun scraper.ts --login
|
|
```
|
|
|
|
After that, the session is stored in `session/state.json`.
|
|
|
|
### Run the tray
|
|
|
|
```bash
|
|
python3 ~/.local/share/claude-usage/tray.py &
|
|
```
|
|
|
|
Add it as a startup application in Cinnamon/GNOME so it launches at login.
|
|
|
|
Tray menu:
|
|
- **Session / Weekly** — current percent + reset time
|
|
- **Update Now** — force an immediate refresh
|
|
- **Live Mode** — switch to 15-second polling (keeps Chromium open in the background)
|
|
- **Login** — re-run the login flow if your session expires
|
|
- **Quit**
|
|
|
|
### CLI usage
|
|
|
|
```bash
|
|
bun scraper.ts # pretty output with colors and progress bars
|
|
bun scraper.ts --json # single JSON line — useful for scripts
|
|
bun scraper.ts --live # JSON Lines, one per 15 seconds (Ctrl+C to stop)
|
|
bun scraper.ts --login # open browser for a fresh login
|
|
```
|
|
|
|
---
|
|
|
|
## Windows
|
|
|
|
The tray (`tray.py`) won't run on Windows. The scraper does, with two changes.
|
|
|
|
### Requirements
|
|
|
|
- [Bun for Windows](https://bun.sh/docs/installation) (or Node.js — `bun` is just used as a TypeScript runner)
|
|
- Playwright Chromium: `bunx playwright install chromium`
|
|
|
|
### Patch the scraper
|
|
|
|
Two pieces in `scraper.ts` reference Linux-only Xvfb. On Windows, replace each `check()` and `live()` Xvfb spawn block with a plain Chromium launch (drop the `Xvfb` spawn and the `env: { ..., DISPLAY: display }` part). The browser window will be visible while it's running — that's the trade-off without Xvfb. If you want it hidden, run Chromium with `headless: true` and accept that Cloudflare may block you intermittently.
|
|
|
|
A simpler alternative: only ever invoke `--login` and `--json` (one-shot calls), and accept that a browser window flashes up briefly each time.
|
|
|
|
### Run
|
|
|
|
```powershell
|
|
bun scraper.ts --login # log in once
|
|
bun scraper.ts --json # poll usage
|
|
```
|
|
|
|
To poll on a schedule, set up a Task Scheduler job that runs the `--json` command and pipes the output somewhere useful (a log file, a notification, a Streamdeck button — your choice).
|
|
|
|
A proper Windows tray port would mean rewriting `tray.py` against `pystray` + `Pillow`, which isn't done here.
|
|
|
|
---
|
|
|
|
## macOS
|
|
|
|
Same story as Windows: the scraper works (no Xvfb needed — just remove the Xvfb spawn), the tray does not. Use `rumps` if you want a macOS menubar version.
|
|
|
|
---
|
|
|
|
## Output format
|
|
|
|
```json
|
|
{
|
|
"sessionUsed": 42,
|
|
"sessionResets": "Mon 14:00",
|
|
"weeklyUsed": 18,
|
|
"weeklyResets": "Tue 09:00",
|
|
"sonnetUsed": 12,
|
|
"extraSpent": "0.00",
|
|
"loggedIn": true
|
|
}
|
|
```
|
|
|
|
If the session has expired: `{"loggedIn": false}` and exit code 1.
|
|
|
|
## Files
|
|
|
|
```
|
|
~/.local/share/claude-usage/
|
|
├── scraper.ts # Playwright scraper
|
|
├── tray.py # GTK tray indicator (Linux)
|
|
└── session/
|
|
└── state.json # Playwright storage state (cookies) — gitignored
|
|
```
|
|
|
|
The tray writes logs to `/tmp/claude-usage-tray.log` and generated icons to `/tmp/claude-usage-icons/`.
|
|
|
|
## Security
|
|
|
|
`session/state.json` contains your Claude.ai cookies and is equivalent to your login — do not share it. It is excluded via `.gitignore`.
|
|
|
|
The tray enforces a single instance via a PID file at `/tmp/claude-usage-tray.pid`; restarting it kills any older instances automatically.
|
|
|
|
## License
|
|
|
|
No license specified — treat as personal/internal use unless the repo owner adds one.
|