clauddy 1.29.0 โ†’ 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,378 +1,79 @@
1
1
  # ๐ŸŸซ Clauddy
2
2
 
3
- A cute pixel-art desktop pet for macOS that tracks your Claude Code usage โ€” mirroring the official **Settings โ†’ Usage** panel (current session + weekly limits, in tokens & %), with animations. Use Codex or Cursor too? It can track those alongside (see [Codex](#codex) and [Cursor](#cursor)).
3
+ A pixel-art desktop pet that keeps an eye on your Claude Code usage: the real session and weekly % from your account, the tokens from your local logs, and a pet that works, sleeps and catches fire along with it. It can follow Codex and Cursor too, and you can dress it as a wizard, Santa or a gingerbread cookie.
4
4
 
5
5
  <p align="center">
6
-
7
- https://github.com/user-attachments/assets/be8d339c-ac69-4980-96a7-8e286f8b080f
8
-
9
- <em>A little terracotta creature that lives in the corner of your screen, eats your tokens, and naps when you're idle.</em>
6
+ <a href="#running">๐Ÿ“š Get started</a> ยท
7
+ <a href="docs/design/02-features.md">โœจ Features</a> ยท
8
+ <a href="docs/design/01-architecture.md">๐Ÿ—๏ธ Architecture</a> ยท
9
+ <a href="docs/design/03-setup.md">โš™๏ธ Setup</a> ยท
10
+ <a href="docs/decisions/">๐Ÿงญ Decisions</a>
10
11
  </p>
11
12
 
12
- ## What it shows
13
-
14
- - **Current session** โ€” real % used + **"resets in Xh Ym"** + session tokens, and a projection of where that pace is taking you (see [Burn rate](#burn-rate))
15
- - **Weekly ยท all models** โ€” real % used + tokens over the last 7 days
16
- - **Status line** under the pet: `โ— working ยท 1.6M tok/min` (or today's tokens when idle)
17
- - **By model ยท 7 days** โ€” Opus / Sonnet / Haiku / Fable, in tokens
18
- - **By project ยท 7 days** โ€” which repo actually ate the week, ranked, with the tail folded into `other`
19
- - **30-day map** โ€” colored squares by daily tokens (green = light โ†’ red = heavy), with the monthly total
20
-
21
- The **percentages are real**, pulled from your account (you log in once โ€” see below). The token counts, the by-model and by-project breakdowns, activity status, and 30-day map come from your local logs (`~/.claude/projects/**/*.jsonl`). Everything is token-based โ€” no dollars.
22
-
23
- ## Account & live usage
24
-
25
- The session/weekly **%** comes straight from your Anthropic account, so it matches the official panel exactly. You connect once via a browser login:
26
-
27
- 1. Open **โš™ Settings โ†’ Accounts โ†’ Claude โ†’ Connect** โ€” your browser opens an Anthropic auth page.
28
- 2. Log in, copy the **authentication code** shown, and paste it back into the app โ†’ **Connect**.
29
-
30
- The token is saved locally (see [Data & privacy](#data--privacy)) and refreshed automatically. **Until you connect**, the limits area shows a _"Connect your account"_ prompt instead of percentages.
31
-
32
- ### Several subscriptions
33
-
34
- Got more than one Claude account โ€” say a personal Pro and a Max from work? The **account chip** in the top-left corner is the switcher: click it for the list of accounts, with the active one marked, plus **"+ Add another account"** โ€” which opens the same browser login and drops the token it brings back into a new slot. The tray icon has the same list under its **Account** submenu. Give up halfway and the empty slot disappears on its own โ€” the list only ever holds accounts you actually logged into.
35
-
36
- The widget follows **one account at a time**: the one you pick is the one whose % is shown, whose logs are counted, and the only one that can notify you. Each account keeps its own token and its own armed alerts, so switching never replays a notification you already dismissed elsewhere. Everything else โ€” window position, display mode, zoom, thresholds โ€” is shared.
37
-
38
- Removing an account (the **ร—** on its row) deletes its token from disk. The one you're currently on can't be removed โ€” switch away first โ€” and neither can the last one left. Removing the first account clears its token without touching the settings that live in the same folder.
39
-
40
- > Prefer one widget per account instead? Setting `CLAUDE_CONFIG_DIR` still isolates a whole instance โ€” token, settings and logs โ€” so you can run two Clauddys side by side.
41
-
42
- ## Codex
43
-
44
- Clauddy can follow [Codex](https://openai.com/codex) (the CLI and the desktop app) next to Claude. Open **โš™ Settings โ†’ Accounts โ†’ Codex โ†’ Connect**: it looks for a Codex session on this computer and starts monitoring if it finds one. There's nothing to log into โ€” Codex writes its token counts and rate limits to `~/.codex/sessions` (or `$CODEX_HOME`), and Clauddy only reads those files.
45
-
46
- With both connected, **tabs under the pet** pick which service the whole panel shows โ€” account chip, pet mood, session and weekly %, the breakdowns, the 30-day map and the **โ†—** Usage link. The other tab keeps its % in view and gets a **!** when it crosses an alert threshold. Minimized, the ring becomes one reading per service; click one to switch.
47
-
48
- A few things differ from Claude:
49
-
50
- - **The % updates when Codex runs.** Its logs are only written during a session, so an idle Codex shows the last reading with its age (`read 1h 30m ago`). A window that has already reset since then shows **โ€”** rather than a made-up 0%.
51
- - **It can lag the account page.** Codex limits are shared with other ChatGPT products (Work, Agents, Excel) that don't write to these logs.
52
- - **No burn-rate projection and no activity scenes** โ€” the logs don't carry enough to draw them honestly.
53
-
54
- Disconnecting (the **ร—** on the Codex card) only stops monitoring in Clauddy; your Codex login and logs are untouched.
55
-
56
- ## Cursor
57
-
58
- Clauddy can follow [Cursor](https://cursor.com) too. Open **โš™ Settings โ†’ Accounts โ†’ Cursor โ†’ Connect**: it needs the Cursor app signed in on this computer, and starts monitoring once it finds that login. Clauddy reads the app's session token from its local storage and asks Cursor's own dashboard endpoints for your usage, the same numbers as the Usage page on cursor.com.
59
-
60
- With three services connected, the tabs under the pet show each logo and %, and the minimized dock gets a third line.
61
-
62
- Cursor works differently from the other two:
63
-
64
- - **Its budget is monthly.** The Cursor tab shows **this month ยท included** (the whole plan) and **API models** (the pool for named third-party models), both resetting on your billing date. There is no 5-hour session or weekly window.
65
- - **Activity scenes work.** Cursor's agent writes a transcript of every tool call under `~/.cursor/projects`, so the pet reads, edits, runs commands and researches along with it, as it does for Claude.
66
- - **No token counts.** Neither source reports tokens, so the Cursor tab has no breakdowns or 30-day map, and its status line says when the agent last ran instead.
67
- - **The endpoints are unofficial.** They're the ones the cursor.com dashboard uses, checked every 5 minutes. If Cursor changes them, the tab shows the last reading with its age until Clauddy is updated.
68
-
69
- Disconnecting (the **ร—** on the Cursor card) only stops monitoring in Clauddy; the Cursor app and its login are untouched.
70
-
71
- ## Burn rate
72
-
73
- Knowing you're at **82%** with **1h 12m** left on the window still leaves you doing arithmetic in your head. So Clauddy does it for you: it fits the slope of your recent usage and projects when you'd hit 100% โ€” showing one extra line under the session bar:
74
-
75
- - **`~35m left at this pace`** (in coral) โ€” you'd run out before the window resets. Ease off, or wrap up.
76
- - **`resets before you run out`** โ€” the reset gets there first. Carry on.
77
-
78
- The slope is fitted over your **session tokens** rather than the account %. The % is the number you care about, but it arrives as a whole number every few minutes โ€” over a short window the whole signal is a single `16 โ†’ 17` step, which throws the fitted pace off by multiples. Local-log tokens step too โ€” one jump per assistant turn โ€” but in increments some 10โ€“20ร— finer, so the slope is far steadier; the account % then anchors it, converting tokens into % and re-calibrating on every poll.
79
-
80
- It reads your **recent** pace, not the session average: go quiet for a few minutes and the projection eases off, which is the point.
81
-
82
- It only appears once there's enough to say honestly โ€” roughly 5 minutes into a session โ€” and stays hidden while you're idle, when the pace is flat, or right after a reset. A projection is a projection: change your pace and it changes with you.
83
-
84
- ## The pet's states
85
-
86
- <table>
87
- <tr>
88
- <td align="center"><img src="https://raw.githubusercontent.com/renatoaug/claude-usage-monitor/a8491cea34c4a8547965eadc3b24618ba50218e1/docs/media/idle.gif" width="280" alt="idle" /><br /><b>idle</b><br /><sub>breathes &amp; blinks</sub></td>
89
- <td align="center"><img src="https://raw.githubusercontent.com/renatoaug/claude-usage-monitor/a8491cea34c4a8547965eadc3b24618ba50218e1/docs/media/working.gif" width="280" alt="working" /><br /><b>working</b><br /><sub>hops &amp; eats token coins</sub></td>
90
- </tr>
91
- <tr>
92
- <td align="center"><img src="https://raw.githubusercontent.com/renatoaug/claude-usage-monitor/a8491cea34c4a8547965eadc3b24618ba50218e1/docs/media/on-fire.gif" width="280" alt="on fire" /><br /><b>on fire</b><br /><sub>session โ‰ฅ 90% โ†’ red, shivers, flames</sub></td>
93
- <td align="center"><img src="https://raw.githubusercontent.com/renatoaug/claude-usage-monitor/a8491cea34c4a8547965eadc3b24618ba50218e1/docs/media/tired.gif" width="280" alt="maxed out" /><br /><b>maxed out</b><br /><sub>session at 100% โ†’ drained, slumped, sweating</sub></td>
94
- </tr>
95
- <tr>
96
- <td align="center"><img src="https://raw.githubusercontent.com/renatoaug/claude-usage-monitor/a8491cea34c4a8547965eadc3b24618ba50218e1/docs/media/sleeping.gif" width="280" alt="sleeping" /><br /><b>sleeping</b><br /><sub>idle 5+ min โ†’ blue zzz &amp; moonlight</sub></td>
97
- <td align="center"><img src="https://raw.githubusercontent.com/renatoaug/claude-usage-monitor/a8491cea34c4a8547965eadc3b24618ba50218e1/docs/media/poke.gif" width="280" alt="poke" /><br /><b>poke</b><br /><sub>click the pet โ†’ squish &amp; hearts</sub></td>
98
- </tr>
99
- <tr>
100
- <td colspan="2" align="center"><img src="https://raw.githubusercontent.com/renatoaug/claude-usage-monitor/a8491cea34c4a8547965eadc3b24618ba50218e1/docs/media/celebrate.gif" width="280" alt="celebrate" /><br /><b>celebrate</b><br /><sub>session resets โ†’ jump &amp; confetti</sub></td>
101
- </tr>
102
- </table>
103
-
104
- Plus a welcome **wave** on launch. You can [poke the pet from the terminal](#play-with-the-pet) too.
105
-
106
- ### Pick your pet
107
-
108
- Clauddy is the default, but **โš™ Settings โ†’ Appearance โ†’ pet** has more: a Wizard, a Halloween pack (Pumpkin, Skull, Ghost, Bat, Black cat) and a Christmas pack (Snowman, Santa, Reindeer, Gingerbread). Click one to preview it and **Save** to keep it. Each pet stays still and blinks when nothing is running, and has its own way of moving while a provider works: the Pumpkin rocks, the Bat flaps, the Wizard levitates while an owl flies by.
109
-
110
- On holidays a pet from the season's pack takes over for the day, whichever pet you picked: one Halloween pet on October 31, one Christmas pet on December 24 and 25. The pick changes every year and holds all day. Your own pet comes back the next day.
111
-
112
- ### What Claude's up to
113
-
114
- While Claude Code is actively working, the pet sets up a little desk scene that
115
- mirrors **what it's doing right now** โ€” inferred from your local logs (the last
116
- tool it used). The status line names the activity, and three of them get their
117
- own animated scene:
118
-
119
- <table>
120
- <tr>
121
- <td align="center"><img src="https://raw.githubusercontent.com/renatoaug/claude-usage-monitor/a8491cea34c4a8547965eadc3b24618ba50218e1/docs/media/reading.gif" width="280" alt="reading" /><br /><b>reading</b><br /><sub>an open book under the reading lamp</sub></td>
122
- <td align="center"><img src="https://raw.githubusercontent.com/renatoaug/claude-usage-monitor/a8491cea34c4a8547965eadc3b24618ba50218e1/docs/media/editing.gif" width="280" alt="editing" /><br /><b>editing</b><br /><sub>types at the desk, coffee in reach</sub></td>
123
- </tr>
124
- <tr>
125
- <td colspan="2" align="center"><img src="https://raw.githubusercontent.com/renatoaug/claude-usage-monitor/a8491cea34c4a8547965eadc3b24618ba50218e1/docs/media/running.gif" width="280" alt="running" /><br /><b>running</b><br /><sub>watches a task log tick through its checks</sub></td>
126
- </tr>
127
- </table>
128
-
129
- Other activities โ€” **planning**, **researching**, **delegating**, **waiting** โ€”
130
- show up in the status line as they happen. When Claude goes quiet, the pet drops
131
- back to plain **working** / **idle**.
132
-
133
- ## The pet talks
134
-
135
- Now and then the pet says something in a speech bubble. It only speaks when something changes, never on a timer, and it keeps quiet when it has nothing specific to say. Remarks are at least 10 minutes apart; the big moments can cut that line.
136
-
137
- | Moment | What it says (expanded) | Minimized | Blip |
138
- | --- | --- | --- | --- |
139
- | First launch of the day | _Good morning! Yesterday was your heaviest day this week, 12M tokens._ | _Morning! Big day yesterday._ | โœ“ |
140
- | Session crosses your fire threshold | _Getting warm, 91% already. At this pace you'll run out in about 40m._ | _91%, getting warm!_ | โœ“ |
141
- | The session window resets | _Fresh window! That was 34M tokens over 4h 12m, mostly editing code._ | _Fresh window! 34M last time._ | โœ“ |
142
- | The session hits 100% | _That's the limit. I'll be back at 2:35 PM._ | _Maxed out till 2:35 PM._ | |
143
- | You're back after 2+ hours away | _Welcome back! You were gone 3h 12m. The session's at 12%._ | _Welcome back!_ | |
144
- | 90 minutes of unbroken work | _You've been at it for 1h 35m straight. Stretch break?_ | _Stretch break?_ | |
145
- | A new record day (30-day best) | _New record! 45M tokens today, your biggest day in a month._ | _New record: 45M!_ | |
146
- | Codex crosses fire, hits 100%, or resets | _Codex is at 91% now. It resets in 1h 12m._ | _Codex at 91%!_ | โœ“ (fire, reset) |
147
- | Cursor crosses fire, hits 100%, or starts a new month | _Cursor is at 91% now. It resets in 6d 4h._ | _Cursor at 91%!_ | โœ“ (fire, reset) |
148
-
149
- The greeting follows your clock (_Good morning_, _Good afternoon_, _Good evening_, or _Still up?_ before 5 AM) and appears once a day, and so does the record. The reset recap covers only what the pet actually saw, so if you launch it mid-session it says less. Click the bubble to dismiss it.
150
-
151
- ### The voice
152
-
153
- Optionally the pet also **blips** as it talks: short chiptune notes, one per letter, generated live with Web Audio (square waves, no sound files). It blips faster and higher when it's on fire, slower and lower when it's sleepy. Only the headline moments blip (the โœ“ rows above), because a sound on every remark wears out fast.
154
-
155
- Audio should never catch you off guard in a meeting:
156
-
157
- - **Off by default.** Turn it on under **โš™ Settings โ†’ Appearance โ†’ Chiptune voice** (you'll hear a sample).
158
- - **One-click mute.** While the voice is on, a ๐Ÿ”Š button sits in the title bar. Click it to mute for an hour, and again to unmute.
159
- - **Always silent** in menu-bar mode and while the pet is minimized.
13
+ https://github.com/user-attachments/assets/be8d339c-ac69-4980-96a7-8e286f8b080f
160
14
 
161
- **Speech bubbles** can be switched off entirely in the same place. Try any remark from the terminal with `bunx clauddy say <kind>` ([see below](#play-with-the-pet)).
15
+ ## Stack
162
16
 
163
- ## Install
17
+ A native app in Rust with egui, no browser engine: about 1โ€“2% CPU and a 20 MB app. The `clauddy` command on npm is a small launcher that downloads it.
164
18
 
165
- **macOS (Apple Silicon)** is the first-class build. **Windows (x64)** and **Linux (x64)** work too. The `bunx`/`npx` route below runs on all of them today.
19
+ More details, with every tool and version, in [`docs/design/01-architecture.md`](docs/design/01-architecture.md#stack).
166
20
 
167
- ### macOS
21
+ ## Requirements
168
22
 
169
- Two ways, depending on what you want:
23
+ - macOS on Apple Silicon (the first-class build), Windows x64 or Linux x64
24
+ - [Claude Code](https://claude.com/claude-code), whose logs in `~/.claude/projects` give the token counts; a Claude login, done once in Settings, gives the real %
25
+ - [Bun](https://bun.sh), or Node 18+ for `npx`, only to run it with `bunx clauddy`
26
+ - [Rust](https://rustup.rs), only to work on the code
27
+ - Optional: [Codex](https://openai.com/codex) or the [Cursor](https://cursor.com) app on the same machine, to follow them too
170
28
 
171
- #### 1. Install as an app โ€” opens at login (recommended)
29
+ ## Running
172
30
 
173
- One command โ€” it downloads the latest release and drops it in `/Applications`:
31
+ On macOS, as an app that opens at login:
174
32
 
175
33
  ```bash
176
34
  curl -fsSL https://raw.githubusercontent.com/renatoaug/claude-usage-monitor/main/install.sh | bash
177
35
  ```
178
36
 
179
- **Clauddy is free and open source** โ€” the command above just downloads the latest release from this repo and drops it in `/Applications`, nothing else (you can read [`install.sh`](install.sh) first if you'd like).
180
-
181
- Once installed, you can update in-app: **โš™ Settings โ†’ Check for updates โ†’ Update now** runs the same installer and relaunches the new build.
182
-
183
- Why not a normal download? macOS blocks **unsigned** apps downloaded through a browser with a scary *"damaged, move to Trash"* warning โ€” even when they're perfectly safe. It's a false alarm: the only way to silence it is to pay Apple **$99/year** to sign + notarize, which a free hobby app skips. Files fetched with `curl` aren't flagged, so this method simply **lets your Mac open the app** without the block. It then registers in **Login Items** and starts with your Mac โ€” set it and forget it.
184
-
185
- #### 2. Run it via `bunx` (no install)
186
-
187
- Needs [Bun](https://bun.sh) (or use `npx` with Node 24):
37
+ Anywhere, without installing:
188
38
 
189
39
  ```bash
190
40
  bunx clauddy
191
41
  ```
192
42
 
193
- The first run downloads Electron, so give it a moment. Handy for a quick run, but it stays up **only while that command is open** and won't start on its own. Quit it with the **ร—** button.
194
-
195
- ### Windows (x64)
196
-
197
- The quickest path works the same as macOS โ€” with [Bun](https://bun.sh) or Node 24 installed:
198
-
199
- ```powershell
200
- bunx clauddy # or: npx clauddy
201
- ```
202
-
203
- Prefer a standalone app with no Node/Bun? Grab the **portable zip** (`Clauddy-<version>-win-x64.zip`) from the [latest release](https://github.com/renatoaug/claude-usage-monitor/releases), unzip it anywhere, and run `Clauddy.exe`. Because the app is unsigned, Windows **SmartScreen** shows a "Windows protected your PC" prompt the first time โ€” click **More info โ†’ Run anyway**. From then on it starts with Windows.
204
-
205
- ### Linux (x64)
206
-
207
- The quickest path works the same as macOS โ€” with [Bun](https://bun.sh) or Node 24 installed:
208
-
209
- ```bash
210
- bunx clauddy # or: npx clauddy
211
- ```
212
-
213
- Prefer a standalone app? Grab the **AppImage** or **tar.gz** (`Clauddy-<version>-linux-x86_64.AppImage` / `Clauddy-<version>-linux-x64.tar.gz`) from the [latest release](https://github.com/renatoaug/claude-usage-monitor/releases), then:
214
-
215
- ```bash
216
- chmod +x Clauddy-*.AppImage
217
- ./Clauddy-*.AppImage
218
- ```
219
-
220
- Want the pet icon to show up in your app menu / taskbar too, instead of a generic icon? Running the AppImage directly doesn't register it anywhere โ€” GNOME (and most Wayland desktops) only pick up an app's icon from an installed `.desktop` entry. One-time setup, right next to the AppImage:
221
-
222
- ```bash
223
- ./Clauddy-*.AppImage --appimage-extract clauddy.desktop >/dev/null
224
- ./Clauddy-*.AppImage --appimage-extract usr/share/icons/hicolor/512x512/apps/clauddy.png >/dev/null
225
- mkdir -p ~/.local/share/applications ~/.local/share/icons/hicolor/512x512/apps
226
- cp squashfs-root/usr/share/icons/hicolor/512x512/apps/clauddy.png ~/.local/share/icons/hicolor/512x512/apps/clauddy.png
227
- sed "s|^Exec=.*|Exec=$(readlink -f Clauddy-*.AppImage) --no-sandbox %U|" squashfs-root/clauddy.desktop > ~/.local/share/applications/clauddy.desktop
228
- rm -rf squashfs-root
229
- update-desktop-database ~/.local/share/applications 2>/dev/null
230
- ```
231
-
232
- > The system tray icon needs an indicator extension on vanilla GNOME (e.g. "AppIndicator and KStatusNotifier Item Support") โ€” it works out of the box on Cinnamon, KDE, and XFCE. Autostart-at-login is wired up via an XDG `.desktop` entry in `~/.config/autostart/`.
233
-
234
- > The app keeps its data in `~/.claude-usage-monitor`, regardless of platform or how you run it.
235
-
236
- ## Controls
237
-
238
- - **Drag** the widget anywhere on screen
239
- - **โ€“** minimizes to just the pet, ringed by the live session % (the number sits inside the ring) โ€” or, with more than one service connected, one reading per service; the **โคข** button or a double-click on the pet expands it back
240
- - **Pet icon** in the compact header switches to **Just the pet** (floating mode). Drag the pet itself to move it. Hovering over the pet (or focusing it) pops up the usage glance as the pet's own speech bubble, one reading and a usage bar per service; the small expand button in it returns to compact. Keyboard users can focus the pet and press Enter. The chosen size is remembered. In compact and pet-only modes, the pet follows whoever is working, with provider logos identifying Claude, Codex, Cursor, or several at once. Usage selection stays manual. When neither is active, the hottest session still sets the mood: the pet catches fire (or maxes out) with that provider's logo beside it, and otherwise rests.
241
- - **โš™** opens settings (log in, toggle alerts, set thresholds, pick the display mode)
242
- - **โ†—** opens the official Usage page of the service on screen
243
- - **ร—** quits
244
-
245
- ### Floating or menu bar
246
-
247
- Under **โš™ Settings โ†’ Appearance** you can pick where Clauddy lives:
248
-
249
- - **Floating pet** โ€” the always-on widget in the corner (default).
250
- - **Menu bar** โ€” a small pet icon in the macOS menu bar showing your live session **%** (it turns ๐Ÿ”ฅ near your limit; with Codex or Cursor too it reads `C 64% ยท X 28% ยท Cu 5%`). Click it to pop open the full pet + usage panel; click away to dismiss. Right-click for a quick menu.
251
-
252
- Switching is instant โ€” no restart. (On Windows/Linux the icon lives in the system tray; the live % shows in its tooltip.)
253
-
254
- ### Provider reactions
255
-
256
- When a provider's activity changes, the pet briefly glances toward it, with no caption. Both can be working at once. A glance never changes your selected provider, and glances share a 30-second cooldown. Inactivity is treated as a pause, not a completed task.
257
-
258
- ## Alerts
259
-
260
- Optional **macOS notifications**, toggled (with their thresholds) in **โš™ Settings โ†’ Alerts**:
261
-
262
- | Notification | When |
263
- | --- | --- |
264
- | _Session at 82%_ โ€” `2h 39m left ยท resets 6:50 PM` | Your session crosses a threshold (default **80%** and **95%**) |
265
- | _Weekly usage at 84%_ โ€” `resets Fri 7:00 AM` | Same, for the weekly limit |
266
- | _Fable weekly at 84%_ โ€” `resets Fri 7:00 AM` | Same, for a per-model weekly limit |
267
- | _Codex session at 82%_ / _Codex weekly at 84%_ | Same, for Codex, when it's connected |
268
- | _Cursor usage at 82%_ / _Cursor API models at 84%_ โ€” `resets Oct 11` | Same, for Cursor's month, when it's connected |
269
- | _Session window reset_ โ€” `full budget again` | A session you had pushed past 80% rolls over |
270
- | _Clauddy lost access to your usage_ | The OAuth token expired or was revoked, so the % went back to being an estimate |
271
-
272
- The last threshold you set is the only one that makes a sound; the earlier ones arrive silently. Clicking any of them brings the widget to the front. Each fires once and re-arms when usage drops back below โ€” remembered across restarts, so relaunching at 85% doesn't repeat an alert you already dismissed.
273
-
274
- Percentages come from your account when you're logged in, and fall back to the local token estimate when you're not.
275
-
276
- The first alert asks macOS for permission; after that the app shows up in **System Settings > Notifications** like any other.
277
-
278
- ### Remind me at the session reset
279
-
280
- When the selected session crosses your first alert threshold, **Notify at reset** appears beneath the session details and in the compact monitor. Click to arm it; click **Reminder on** to cancel. This is a one-shot request, independent of the automatic threshold-alert toggle.
281
-
282
- Reminders survive restarts, belong to the Claude account, Codex or Cursor connection that created them, and are canceled when you disconnect that source. Clauddy must be running to notify; an overdue reminder is delivered on reopening or waking up. A fresh, lower reading confirms that session budget returned and the pet celebrates. If usage hasn't refreshed โ€” common with idle Codex logs โ€” the reminder only says the scheduled reset time arrived and asks you to check usage.
283
-
284
- ## Configure (`config.json`)
285
-
286
- Settings saved from the UI live in `~/.claude-usage-monitor/config.json`, so you can tweak them without rebuilding:
287
-
288
- ```jsonc
289
- {
290
- "mode": "floating", // "floating" pet in the corner, or "menubar" popover
291
- "alerts": true, // macOS notifications on/off
292
- "alertThresholds": [80, 95], // notify when session/week cross these % (two levels)
293
- "fireThreshold": 90, // session % at which the pet catches fire (maxed out stays 100)
294
- "pollIntervalMs": 4000, // how often local logs are re-read
295
- "activeThresholdMs": 20000, // fallback "active" window, for a log whose tail doesn't say whether a turn is open
296
- "sleepThresholdMs": 300000, // "sleeping" after this much idle time (5 min)
297
- "codex": { "enabled": false }, // set by Settings โ†’ Codex โ†’ Connect
298
- "cursor": { "enabled": false }, // set by Settings โ†’ Cursor โ†’ Connect
299
- "talk": true, // speech bubbles on transitions
300
- "sound": false, // chiptune blips, opt-in
301
- "soundMutedUntil": 0, // set by the ๐Ÿ”Š button: muted until this epoch ms
302
- }
303
- ```
304
-
305
- ## Play with the pet
43
+ [`docs/design/03-setup.md`](docs/design/03-setup.md) has Windows and Linux, updating, running from the code and every setting in `config.json`.
306
44
 
307
- With the widget running, poke it from the terminal โ€” just for fun:
45
+ ## Test
308
46
 
309
47
  ```bash
310
- bunx clauddy poke # ๐Ÿ’• squish + hearts
311
- bunx clauddy celebrate # ๐ŸŽ‰ jump + confetti
312
- bunx clauddy fire # ๐Ÿ”ฅ on fire
313
- bunx clauddy sleeping # ๐Ÿ˜ด blue zzz
314
- bunx clauddy working # ๐Ÿด eats token coins
315
- bunx clauddy tired # ๐Ÿฅต maxed out
316
- bunx clauddy idle # ๐Ÿ™‚ calm
317
- bunx clauddy auto # โ†ฉ๏ธ back to your real usage
318
- bunx clauddy say # ๐Ÿ’ฌ a remark in the speech bubble
319
- bunx clauddy say fire # or a specific one: greeting, fire, reset, maxed, welcome, streak, record, codex, cursor
48
+ cd native
49
+ cargo fmt --check # formatting
50
+ cargo test --release # unit tests and the snapshot gallery
51
+ scripts/bundle.sh # builds target/Clauddy.app (macOS)
52
+ CLAUDDY_STATE=fire cargo run --release # forces a mood: idle, working, fire, tired, sleeping
320
53
  ```
321
54
 
322
- Each state is written to the data dir the running widget watches, so it reacts
323
- live. (Installed globally? Drop the `bunx`: `clauddy poke`. Working on the repo?
324
- `./pet <state>` does the same.)
325
-
326
- ## How it works
327
-
328
- - **`main.js`** โ€” Electron main process: frameless, transparent, always-on-top window; polls usage; fires macOS notifications; watches `config.json` and `debug.json`.
329
- - **`usage.js`** โ€” reads `~/.claude/projects/**/*.jsonl`, sums tokens per model/project/day, detects the rolling 5-hour session window, the working/sleeping status, and which activity (reading/editing/running/โ€ฆ) Claude is on from its latest tool use.
330
- - **`auth.js`** โ€” OAuth login (PKCE, same public client as Claude Code) that fetches the authoritative usage %. Token stored locally, never committed.
331
- - **`renderer/`** โ€” the pet itself: an SVG pixel sprite, CSS animations, and the Web Animations API for particles. `voice.js` writes its remarks and synthesizes the blips.
332
- - **`make-icon.js`** โ€” generates the app icon from the pixel sprite (`build/icon.icns`).
333
-
334
- ## Data & privacy
335
-
336
- Everything lives on your machine, in `~/.claude-usage-monitor/`:
55
+ The snapshot gallery renders every screen state into `native/tests/snapshots/`; after an intended visual change, `UPDATE_SNAPSHOTS=1 cargo test --release` rewrites it. At the root, `bun run test` tests the npm launcher, and `bun install` turns on a pre-commit hook that formats it. CI runs all of it on every pull request.
337
56
 
338
- - `auth.json` โ€” your OAuth token (file mode `600`, never committed)
339
- - `config.json` โ€” your alert settings
340
- - `alerts.json` โ€” which notifications are already armed, so a restart doesn't repeat them
341
- - `accounts.json` โ€” your list of accounts and which one is active
57
+ ## Releases
342
58
 
343
- With Codex connected, Clauddy also reads (never writes) `~/.codex/sessions`. With Cursor connected, it reads the Cursor app's login from its local storage and the agent transcripts in `~/.cursor/projects`, also without writing.
344
- - `debug.json` โ€” scratch file for the `./pet` simulator
345
- - `accounts/<id>/` โ€” the same `auth.json` + `alerts.json`, for each extra account
59
+ Every merge to `main` can be a release: [semantic-release](https://semantic-release.gitbook.io) reads the Conventional Commit titles since the last tag (a `feat` bumps the minor, a `fix` or `perf` the patch, `docs` and `chore` nothing), builds the app on macOS, Windows and Linux runners, publishes the GitHub release and `clauddy` on npm. The whole pipeline is in [`docs/design/07-releases.md`](docs/design/07-releases.md).
346
60
 
347
- Nothing leaves your machine except the OAuth calls to Anthropic's own login and usage endpoints, and, with Cursor connected, the usage calls to Cursor's own API with the Cursor app's token.
61
+ ## Docs
348
62
 
349
- ## Contributing
63
+ Everything about how Clauddy works, runs and was decided is indexed in [`docs/`](docs/README.md). Code rules and the checks before calling a change done are in [`AGENTS.md`](AGENTS.md).
350
64
 
351
- Bug reports and ideas are welcome โ€” see **[CONTRIBUTING.md](CONTRIBUTING.md)**
352
- for setup and the few gotchas worth knowing before a first PR.
65
+ We follow [Conventional Commits](https://www.conventionalcommits.org).
353
66
 
354
- ## Dev tooling
67
+ ## Privacy
355
68
 
356
- - **Bun** for install/scripts, **Node 24** pinned in `.nvmrc`
357
- - **Tests**: `bun run test` (never bare `bun test` โ€” the groups under `test/`
358
- must each run in their own process). `bun run test:coverage` enforces the
359
- floor; every PR runs both.
360
- - **Biome** for format + lint (`bun run check`); a versioned **pre-commit hook** (`.githooks/pre-commit`) auto-formats staged files and blocks on errors. It's wired up automatically on `bun install` (via the `prepare` script).
69
+ Everything stays on your machine, in `~/.claude-usage-monitor/`. Nothing leaves it except the OAuth and usage calls to Anthropic and, with Cursor connected, the usage calls to Cursor with the Cursor app's own token. What is kept and what is read: [`docs/design/01-architecture.md`](docs/design/01-architecture.md#data-on-disk).
361
70
 
362
- ### Releasing
71
+ ## Community
363
72
 
364
- Releases are **fully automated**. Every push to `main` runs
365
- [semantic-release](https://semantic-release.gitbook.io) (`.github/workflows/release.yml`):
366
- it reads the **Conventional Commits** and, when there's something to ship,
367
- computes the version, builds the app for **macOS, Windows and Linux** on their
368
- own runners, publishes `clauddy` to npm, and cuts a GitHub Release with every
369
- artifact attached. Nothing to do by hand โ€” just merge your PRs.
73
+ - ๐Ÿ› [GitHub Issues](https://github.com/renatoaug/claude-usage-monitor/issues): report a bug or propose a feature
74
+ - ๐Ÿ”ง Pull requests are welcome: read [`CONTRIBUTING.md`](CONTRIBUTING.md) first for how to start and the checks
75
+ - ๐Ÿ™ [@renatoaug on GitHub](https://github.com/renatoaug): the maintainer
370
76
 
371
- The pipeline runs in three stages, because electron-builder can't cross-build
372
- Windows/Linux from macOS: `version` (a semantic-release dry-run that computes
373
- the next version) โ†’ `build` (a matrix that stamps that version into
374
- `package.json` so the filenames are right) โ†’ `publish` (downloads every
375
- artifact and runs semantic-release for real).
77
+ ## License
376
78
 
377
- - `feat:` โ†’ minor, `fix:` โ†’ patch, `feat!:`/`BREAKING CHANGE` โ†’ major.
378
- - `docs:`/`chore:`/`ci:` etc. don't trigger a release.
79
+ Clauddy is licensed under the [MIT License](LICENSE).
package/bin/clauddy.js CHANGED
@@ -1,87 +1,90 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // `clauddy` โ†’ launch the widget (spawns Electron on main.js)
4
- // `clauddy <state>` โ†’ poke the running widget for fun/demo (writes the state
5
- // to the data dir the app watches; the app reacts live)
6
- const path = require('node:path')
3
+ // `clauddy` launches the native app (native/, Rust). The first run of each
4
+ // version downloads that version's release build for this OS into
5
+ // ~/.claude-usage-monitor/bin/<version>/; later runs open it straight away.
6
+ // The app runs on its own: this launcher exits as soon as it has started it.
7
7
  const fs = require('node:fs')
8
8
  const os = require('node:os')
9
+ const path = require('node:path')
10
+ const { spawn, execFileSync } = require('node:child_process')
11
+
12
+ const REPO = 'renatoaug/claude-usage-monitor'
13
+
14
+ // the release asset for an OS, and the file inside it to start
15
+ function assetFor(platform, arch, version) {
16
+ if (platform === 'darwin' && arch === 'arm64')
17
+ return { file: `Clauddy-${version}-arm64-mac.zip`, app: 'Clauddy.app' }
18
+ if (platform === 'win32' && arch === 'x64')
19
+ return { file: `Clauddy-${version}-win-x64.zip`, app: 'Clauddy.exe' }
20
+ if (platform === 'linux' && arch === 'x64')
21
+ return { file: `Clauddy-${version}-linux-x64.tar.gz`, app: path.join('Clauddy', 'clauddy') }
22
+ return undefined
23
+ }
24
+
25
+ function dataDir() {
26
+ return process.env.CLAUDE_CONFIG_DIR
27
+ ? path.join(process.env.CLAUDE_CONFIG_DIR, 'usage-monitor')
28
+ : path.join(os.homedir(), '.claude-usage-monitor')
29
+ }
30
+
31
+ async function install(asset, version, dir) {
32
+ const url = `https://github.com/${REPO}/releases/download/v${version}/${asset.file}`
33
+ console.log(`clauddy: downloading ${asset.file}โ€ฆ`)
34
+ const res = await fetch(url)
35
+ if (!res.ok) throw new Error(`download failed (${res.status}): ${url}`)
36
+ const tmp = `${dir}.tmp`
37
+ fs.rmSync(tmp, { recursive: true, force: true })
38
+ fs.mkdirSync(tmp, { recursive: true })
39
+ const archive = path.join(tmp, asset.file)
40
+ fs.writeFileSync(archive, Buffer.from(await res.arrayBuffer()))
41
+ // ditto keeps the app bundle's signature; tar reads zips on Windows 10+
42
+ if (process.platform === 'darwin') execFileSync('ditto', ['-x', '-k', archive, tmp])
43
+ else execFileSync('tar', ['-xf', archive, '-C', tmp])
44
+ fs.rmSync(archive)
45
+ fs.rmSync(dir, { recursive: true, force: true })
46
+ fs.renameSync(tmp, dir)
47
+ }
9
48
 
10
- // same data dir the app uses (respects CLAUDE_CONFIG_DIR for multi-account)
11
- const dataDir = process.env.CLAUDE_CONFIG_DIR
12
- ? path.join(process.env.CLAUDE_CONFIG_DIR, 'usage-monitor')
13
- : path.join(os.homedir(), '.claude-usage-monitor')
49
+ // older versions' builds are dead weight once a newer one is in
50
+ function prune(root, keep) {
51
+ for (const name of fs.readdirSync(root)) {
52
+ if (name !== keep) fs.rmSync(path.join(root, name), { recursive: true, force: true })
53
+ }
54
+ }
14
55
 
15
- const STATES = [
16
- 'fire',
17
- 'sleeping',
18
- 'working',
19
- 'tired',
20
- 'idle',
21
- 'poke',
22
- 'celebrate',
23
- 'say',
24
- 'auto',
25
- 'clear',
26
- // activity states (what Claude Code is doing)
27
- 'editing',
28
- 'reading',
29
- 'planning',
30
- 'running',
31
- 'researching',
32
- 'delegating',
33
- 'waiting',
34
- ]
35
- // `clauddy say <kind>` previews one of the pet's remarks
36
- const REMARKS = [
37
- 'greeting',
38
- 'fire',
39
- 'reset',
40
- 'maxed',
41
- 'welcome',
42
- 'streak',
43
- 'record',
44
- 'codex',
45
- 'cursor',
46
- ]
47
- const arg = process.argv[2]
48
- const kind = arg === 'say' ? process.argv[3] || 'greeting' : undefined
56
+ async function main() {
57
+ const arg = process.argv[2]
58
+ if (arg === '--help' || arg === '-h') {
59
+ console.log(
60
+ 'clauddy โ€” a desktop pet that tracks your Claude Code usage\n\nUsage:\n clauddy launch the app',
61
+ )
62
+ return
63
+ }
64
+ const { version } = require('../package.json')
65
+ const asset = assetFor(process.platform, process.arch, version)
66
+ if (!asset) {
67
+ console.error(
68
+ `clauddy: no build for ${process.platform}-${process.arch} (macOS arm64, Windows x64, Linux x64)`,
69
+ )
70
+ process.exit(1)
71
+ }
72
+ const root = path.join(dataDir(), 'bin')
73
+ const dir = path.join(root, version)
74
+ const app = path.join(dir, asset.app)
75
+ if (!fs.existsSync(app)) {
76
+ await install(asset, version, dir)
77
+ prune(root, version)
78
+ }
79
+ const [cmd, args] = process.platform === 'darwin' ? ['open', [app]] : [app, []]
80
+ spawn(cmd, args, { detached: true, stdio: 'ignore' }).unref()
81
+ }
49
82
 
50
- if (!arg) {
51
- // no argument โ†’ launch the app
52
- const { spawn } = require('node:child_process')
53
- const electron = require('electron')
54
- const child = spawn(electron, [path.join(__dirname, '..', 'main.js')], { stdio: 'inherit' })
55
- child.on('close', (code) => process.exit(code ?? 0))
56
- } else if (arg === '--help' || arg === '-h') {
57
- console.log(
58
- [
59
- 'clauddy โ€” a cute desktop pet that tracks your Claude Code usage',
60
- '',
61
- 'Usage:',
62
- ' clauddy launch the widget',
63
- ' clauddy <state> poke the running widget (just for fun)',
64
- ' clauddy say [kind] make the pet say one of its remarks',
65
- '',
66
- 'States: fire, sleeping, working, tired, idle, poke, celebrate, auto',
67
- `Remarks: ${REMARKS.join(', ')}`,
68
- '(the widget must be running for a state to show)',
69
- ].join('\n'),
70
- )
71
- } else if (kind && !REMARKS.includes(kind)) {
72
- console.error(`clauddy: unknown remark "${kind}"`)
73
- console.error(`try: ${REMARKS.join(', ')}`)
74
- process.exit(1)
75
- } else if (STATES.includes(arg)) {
76
- // write the state for the running app to pick up
77
- fs.mkdirSync(dataDir, { recursive: true })
78
- fs.writeFileSync(
79
- path.join(dataDir, 'debug.json'),
80
- `${JSON.stringify({ state: arg, kind, t: Date.now() })}\n`,
81
- )
82
- console.log(`clauddy โ†’ ${arg}${kind ? ` ${kind}` : ''} (the running widget will react)`)
83
- } else {
84
- console.error(`clauddy: unknown command "${arg}"`)
85
- console.error('try: fire, sleeping, working, tired, idle, poke, celebrate, auto โ€” or --help')
86
- process.exit(1)
83
+ if (require.main === module) {
84
+ main().catch((e) => {
85
+ console.error(`clauddy: ${e.message}`)
86
+ process.exit(1)
87
+ })
87
88
  }
89
+
90
+ module.exports = { assetFor }