Polish for shipping: robust auto-detect, empty state, real icons, end-user README
- cli_usage::default_command now enumerates WSL distros and probes each for claude before falling back; no more hardcoded -d Ubuntu. - New autodetect_claude_command Tauri command + IPC binding so the UI knows whether claude is reachable. - App.svelte: clear 'Claude Code not found' empty state with install link. - Real icons: scripts/make-icon.py generates a 1024x1024 source.png; runtime produces 32/128/256 PNGs and a multi-resolution .ico. README in icons/ explains how to regen. - README rewritten for friends: install / requirements / troubleshooting on top; build-from-source moved to bottom.
This commit is contained in:
parent
0a960dae2d
commit
9be856d37c
14 changed files with 321 additions and 128 deletions
178
README.md
178
README.md
|
|
@ -1,113 +1,105 @@
|
|||
# claude-usage-widget
|
||||
# Claude Usage Widget
|
||||
|
||||
A small always-on-top Windows desktop widget that visualizes your local Claude Code usage:
|
||||
|
||||
- **Current 5-hour session block** — progress ring + countdown to block end.
|
||||
- **Past 7 days** — daily bars + total.
|
||||
- **Per-model breakdown** — Opus / Sonnet / Haiku stacked across the current block.
|
||||
|
||||
Reads `~/.claude/projects/**/*.jsonl` directly. No Anthropic API. No auth.
|
||||
A small always-on-top Windows desktop widget that shows your live Claude
|
||||
subscription usage — the same percentages Claude Code's `/usage` command
|
||||
displays, refreshed every 5 minutes.
|
||||
|
||||
```
|
||||
┌────────────── Claude Usage ───────╳ ─┐
|
||||
│ │
|
||||
│ ╭─────╮ │
|
||||
│ ╱ ╲ │
|
||||
│ │ 47k │ ← 5-hour block │
|
||||
│ │ 24% │ │
|
||||
│ ╲ 3h 12m╱ │
|
||||
│ ╰─────╯ │
|
||||
│ │
|
||||
│ Models (current block) │
|
||||
│ ▰▰▰▰▰▰▰▰▱▱▱▱▱▱▱▱ │
|
||||
│ ● Opus 32k ● Sonnet 11k │
|
||||
│ │
|
||||
│ 7-day total 842k · 42% │
|
||||
│ ▁▃▆█▂▅▇ │
|
||||
│ Sat Sun Mon Tue Wed Thu Fri │
|
||||
└───────────────────────────────────────┘
|
||||
┌─────── Claude Usage ────────────╳ ─┐
|
||||
│ │
|
||||
│ ╭───────╮ │
|
||||
│ ╱ 72% ╲ │
|
||||
│ │ session │ │
|
||||
│ ╲ resets ╱ │
|
||||
│ ╰ 2:50am ╯ │
|
||||
│ │
|
||||
│ Models (current block) │
|
||||
│ ▰▰▰▰▰▰▰▰▰▱▱▱▱▱▱▱ │
|
||||
│ ● Opus 42M ● Haiku 3M │
|
||||
│ │
|
||||
│ Weekly limits resets May 9 │
|
||||
│ All models ▆░░░░░ 8% │
|
||||
│ Sonnet ▃░░░░░ 5% │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## What this is *not*
|
||||
## Install
|
||||
|
||||
It does not call the Anthropic API and cannot show server-side ground-truth caps. The "5-hour" and "weekly" numbers are derived from your local JSONL transcripts — the same data `ccusage` operates on. The cap values shown in the percentage and the warning thresholds are user-configurable in Settings.
|
||||
1. **Download** the latest `Claude.Usage.Widget_<version>_x64-setup.exe`
|
||||
from the Forgejo releases page.
|
||||
2. **Run the installer.** Windows SmartScreen will warn "unrecognized publisher"
|
||||
(it's not code-signed). Click **More info → Run anyway**.
|
||||
3. The widget pops up in the upper-left corner. Drag it where you want.
|
||||
|
||||
## Architecture (one paragraph)
|
||||
## Requirements
|
||||
|
||||
A Tauri 2 app with a Rust backend and a Svelte 5 + Vite + TS frontend. The Rust side enumerates `~/.claude/projects/**/*.jsonl`, tail-parses each file (resuming from a cached byte offset so we never re-parse already-seen lines), dedupes events by `requestId || uuid` (subagent transcripts overlap parents), aggregates into 5-hour blocks (ccusage-equivalent algorithm: `block_start = floor_to_hour(first_ts)`, `block_end = block_start + 5h`, new block on ≥5h gap or end-of-block), computes a rolling 7-day window in the user's local timezone, and emits a `usage-updated` event whenever anything changes. A `notify-debouncer-full` watcher fires on file changes; a 60s `tokio::time::interval` poll backstops it because `ReadDirectoryChangesW` on the WSL `\\wsl$\` 9P mount can miss events. The widget window is frameless, transparent, `alwaysOnTop`, `skipTaskbar`, and 280×360 px; drag via the custom title bar.
|
||||
- **Windows 10/11** with [WebView2 Runtime](https://developer.microsoft.com/en-us/microsoft-edge/webview2/)
|
||||
(preinstalled on Windows 11; downloadable on Windows 10).
|
||||
- **[Claude Code](https://docs.claude.com/en/docs/claude-code)** installed and signed in.
|
||||
- Native Windows install (`claude.exe` on PATH) → works automatically.
|
||||
- Or installed inside WSL → also works automatically; the widget probes each
|
||||
distro to find one with `claude`.
|
||||
|
||||
## Build & run
|
||||
## Using it
|
||||
|
||||
You need a **Windows** host with the Tauri 2 toolchain — see [Tauri prerequisites](https://v2.tauri.app/start/prerequisites/). Quick version:
|
||||
- The big ring shows your **current 5-hour session** percentage with a reset countdown.
|
||||
- The bars below show the **rolling weekly** limits (all models + Sonnet only).
|
||||
- Per-model breakdown (Opus / Sonnet / Haiku) shows how much of *your local
|
||||
current 5-hour block* came from each — derived from your local Claude Code
|
||||
transcripts. (Anthropic's `/usage` doesn't break this out, so we compute it
|
||||
ourselves.)
|
||||
- **↻** button (title bar) — force-refresh `/usage` right now.
|
||||
- **⚙** — Settings (custom claude command, refresh interval, autostart, distro override).
|
||||
- **×** — quit.
|
||||
- The window is **resizable** — drag any edge.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**"Claude Code not found"** — the widget couldn't find a `claude` to run. Make sure
|
||||
`claude --version` works in either PowerShell or a WSL shell. If it works in
|
||||
a non-default WSL distro, open Settings and set **claude command** to e.g.
|
||||
`wsl.exe -d Debian bash -lc claude`.
|
||||
|
||||
**Ring shows "no /usage data"** — the spawn worked but Anthropic's output didn't
|
||||
match the parser. Open Settings → Test /usage now → expand the *raw output*
|
||||
disclosure and file an issue with what's there.
|
||||
|
||||
**SmartScreen blocks the installer** — expected; the binary isn't code-signed.
|
||||
"More info → Run anyway".
|
||||
|
||||
**Autostart toggle doesn't survive reboot** — that's the dev build limitation.
|
||||
The released installer registers a stable path so autostart works correctly there.
|
||||
|
||||
## Privacy
|
||||
|
||||
Everything stays on your machine. The widget:
|
||||
|
||||
- Reads your local Claude Code config (`~/.claude/projects/`) for the per-model breakdown.
|
||||
- Spawns `claude /usage` to read live percentages — that command Anthropic
|
||||
serves from their backend over your existing Claude Code session, exactly
|
||||
the same as when you type `/usage` interactively. The widget never sees
|
||||
your OAuth token.
|
||||
- Stores its own settings at `%APPDATA%\claude-widget\config.json`.
|
||||
- Makes no other network calls.
|
||||
|
||||
## Building from source
|
||||
|
||||
You need a **Windows host** with:
|
||||
|
||||
```powershell
|
||||
winget install Rustlang.Rustup OpenJS.NodeJS.LTS
|
||||
winget install Rustlang.Rustup OpenJS.NodeJS.LTS Microsoft.VisualStudio.2022.BuildTools
|
||||
rustup default stable-x86_64-pc-windows-msvc
|
||||
npm i -g pnpm
|
||||
# Also: MSVC C++ Build Tools + Windows SDK (Visual Studio Installer),
|
||||
# and Microsoft Edge WebView2 Runtime (preinstalled on Windows 11).
|
||||
corepack enable
|
||||
corepack prepare pnpm@latest --activate
|
||||
```
|
||||
|
||||
Then, from this directory:
|
||||
Then in this repo:
|
||||
|
||||
```powershell
|
||||
pnpm install
|
||||
pnpm tauri dev # iterate
|
||||
pnpm tauri build # NSIS installer in src-tauri\target\release\bundle\nsis\
|
||||
pnpm tauri dev # iterate
|
||||
pnpm tauri build # produces NSIS installer in src-tauri\target\release\bundle\nsis\
|
||||
```
|
||||
|
||||
If you're developing in WSL but building on Windows, the WSL filesystem is mounted at `\\wsl$\<distro>\` from Windows; clone or copy this folder onto the Windows side (or work directly via the UNC path) before running `pnpm tauri build` — Tauri itself needs the MSVC linker.
|
||||
|
||||
> **Icons.** Before `pnpm tauri build` succeeds, drop a 1024×1024 PNG into `src-tauri/icons/source.png` and run `pnpm tauri icon src-tauri/icons/source.png` to generate every required size.
|
||||
|
||||
## Configuration
|
||||
|
||||
`%APPDATA%\claude-widget\config.json` (auto-created on first run):
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"caps": {
|
||||
"block_tokens": 200000, // 5h block cap — placeholder default
|
||||
"weekly_tokens": 2000000 // 7d weekly cap — placeholder default
|
||||
},
|
||||
"wsl_distro_override": null, // null = autodetect via `wsl.exe -l -q`
|
||||
"include_native": true, // also scan %USERPROFILE%\.claude\projects
|
||||
"window_pos": null,
|
||||
"autostart": false
|
||||
}
|
||||
```
|
||||
|
||||
Everything except `window_pos` is editable in the in-app Settings panel (gear icon).
|
||||
|
||||
## Verification checklist
|
||||
|
||||
1. **Cold parse correctness** — compare `BlockRing` total to:
|
||||
```bash
|
||||
jq -s '[.[]|select(.type=="assistant")|.message.usage|(.input_tokens+.output_tokens+.cache_creation_input_tokens+.cache_read_input_tokens)]|add' \
|
||||
~/.claude/projects/<path>/<sessionId>.jsonl
|
||||
```
|
||||
2. **Block boundary** — `scripts\seed-fake-jsonl.ps1 -OffsetHours -6` appends a back-dated synthetic line; confirm it produces a *new* prior block instead of folding into the active one.
|
||||
3. **Dedupe** — duplicate one assistant line into the matching `subagents/<id>.jsonl`; total must not double.
|
||||
4. **Live tail** — start a real `claude` session in WSL; the ring should tick up within a couple of seconds (debouncer ~250 ms).
|
||||
5. **Watcher fallback** — set `WIDGET_NO_WATCH=1` in the env (TODO: wire this up if needed) and append a line; the next 60s poll picks it up.
|
||||
6. **Fake feed** — `scripts\seed-fake-jsonl.ps1` writes a synthetic assistant line; UI updates without restart.
|
||||
7. **WSL detection** — switch `wsl_distro_override` in Settings to a distro with no `.claude/`; snapshot goes empty.
|
||||
8. **Autostart** — toggle on, reboot, confirm widget appears (Task Manager → Startup tab); toggle off, reboot, confirm it doesn't.
|
||||
9. **Transparency / drag** — no chrome; title-bar drag moves the window; position survives restart.
|
||||
10. **Memory ceiling** — 24h soak, expect 40–80 MB RSS.
|
||||
|
||||
## Files of interest
|
||||
|
||||
| File | Purpose |
|
||||
|---|---|
|
||||
| `src-tauri/src/jsonl.rs` | Streaming parse + model normalization + dedupe key |
|
||||
| `src-tauri/src/usage.rs` | 5-hour blocks, weekly window, snapshot builder (incl. unit tests) |
|
||||
| `src-tauri/src/watch.rs` | `notify` debouncer + 60s poll fallback + emit |
|
||||
| `src-tauri/src/paths.rs` | WSL detection, `\\wsl$\…` UNC path resolution |
|
||||
| `src-tauri/src/commands.rs`| Tauri `#[command]` IPC surface |
|
||||
| `src-tauri/tauri.conf.json`| Frameless / transparent / always-on-top window config |
|
||||
| `src/components/*.svelte` | UI |
|
||||
|
||||
## License
|
||||
|
||||
Private project. Not published.
|
||||
Project layout, architecture decisions, and known follow-ups live in
|
||||
[`memory.md`](./memory.md).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue