Always-on-top Windows widget visualizing local Claude Code usage (5h block, weekly, per-model)
Find a file
2026-05-09 00:41:10 +01:00
scripts Add seed-fake-jsonl.ps1 verification helper and README 2026-05-09 00:08:07 +01:00
src Add Svelte 5 frontend (App, TitleBar, BlockRing, ModelStack, WeeklyBar, Settings) 2026-05-09 00:07:02 +01:00
src-tauri Fix watch.rs: import notify::Watcher trait, use tauri JoinHandle, drop unused imports 2026-05-09 00:41:10 +01:00
.gitignore Initial scaffold 2026-05-09 00:00:26 +01:00
CLAUDE.md Initial scaffold 2026-05-09 00:00:26 +01:00
index.html Add Svelte 5 frontend (App, TitleBar, BlockRing, ModelStack, WeeklyBar, Settings) 2026-05-09 00:07:02 +01:00
memory.md Update memory.md: record successful push to Forgejo 2026-05-09 00:29:21 +01:00
package.json Allow esbuild postinstall via pnpm.onlyBuiltDependencies (pnpm 11 default-deny) 2026-05-09 00:37:10 +01:00
README.md Add seed-fake-jsonl.ps1 verification helper and README 2026-05-09 00:08:07 +01:00
svelte.config.js Add Svelte 5 frontend (App, TitleBar, BlockRing, ModelStack, WeeklyBar, Settings) 2026-05-09 00:07:02 +01:00
tsconfig.json Add Svelte 5 frontend (App, TitleBar, BlockRing, ModelStack, WeeklyBar, Settings) 2026-05-09 00:07:02 +01:00
tsconfig.node.json Add Svelte 5 frontend (App, TitleBar, BlockRing, ModelStack, WeeklyBar, Settings) 2026-05-09 00:07:02 +01:00
vite.config.ts Add Svelte 5 frontend (App, TitleBar, BlockRing, ModelStack, WeeklyBar, Settings) 2026-05-09 00:07:02 +01:00

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.

┌────────────── 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         │
└───────────────────────────────────────┘

What this is not

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.

Architecture (one paragraph)

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.

Build & run

You need a Windows host with the Tauri 2 toolchain — see Tauri prerequisites. Quick version:

winget install Rustlang.Rustup OpenJS.NodeJS.LTS
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).

Then, from this directory:

pnpm install
pnpm tauri dev          # iterate
pnpm tauri build        # 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):

{
  "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:
    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 boundaryscripts\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 feedscripts\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 4080 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.