ai-usagebar 0.7.1

Waybar widget + TUI for AI plan usage across Anthropic, OpenAI, Z.AI, OpenRouter, and DeepSeek
Documentation
# CLAUDE.md

Notes for Claude Code (and humans) about working in this repo. Keep tight:
these are invariants we keep almost-forgetting, not a project tour.

## Release checklist — must do all of these

When cutting a new version (patch, minor, or major):

1. **Bump `Cargo.toml` `version`** — e.g. `0.3.2``0.3.3`.
2. **Update `CHANGELOG.md`**:
   - Add a new `## [X.Y.Z] — YYYY-MM-DD` section above the previous one.
   - Categorize entries by **Added / Changed / Fixed / Security** (Keep-A-Changelog).
   - Update the `[Unreleased]` compare link and add a new release link at the bottom.
3. **Bump `packaging/aur/PKGBUILD`**`pkgver=X.Y.Z`, `pkgrel=1`, reset `sha256sums` to `'SKIP'`.
4. **Bump `packaging/aur/PKGBUILD-bin`** — same `pkgver`, `pkgrel=1`, reset both
   `sha256sums_x86_64` and `sha256sums_aarch64` to `'SKIP'`.
5. **Run gate before tagging**:
   ```
   cargo test                                  # 200+ tests must pass
   cargo clippy --all-targets -- -D warnings   # clean
   cargo machete                               # no unused deps
   ```
6. **Commit, tag, push**:
   ```
   git commit -m "vX.Y.Z — …"
   git tag -a vX.Y.Z -m "vX.Y.Z — …"
   git push origin main && git push origin vX.Y.Z
   ```
7. **Wait for CI** (3–5 min): the tag push auto-triggers
   `.github/workflows/release.yml` which builds both x86_64 and
   aarch64 tarballs and publishes a GitHub Release.
8. **Pin the real sha256s** in both PKGBUILDs:
   ```
   cd packaging/aur
   # Source:
   curl -sLO https://github.com/akitaonrails/ai-usagebar/archive/refs/tags/vX.Y.Z.tar.gz
   sha256sum vX.Y.Z.tar.gz   # paste into PKGBUILD
   # Bin x86_64:
   curl -sL https://github.com/akitaonrails/ai-usagebar/releases/download/vX.Y.Z/ai-usagebar-linux-x86_64.tar.gz.sha256
   # Bin aarch64:
   curl -sL https://github.com/akitaonrails/ai-usagebar/releases/download/vX.Y.Z/ai-usagebar-linux-aarch64.tar.gz.sha256
   ```
9. **Regenerate `.SRCINFO`s**:
   ```
   cd packaging/aur && makepkg --printsrcinfo > .SRCINFO
   # And from a scratch dir with the bin PKGBUILD: makepkg --printsrcinfo > .SRCINFO-bin
   ```
10. **AUR push is automated via CI** when `AUR_SSH_KEY` is set (since
    v0.4.4). The `publish-aur` job in `.github/workflows/release.yml`
    runs after `build` + `release` succeed, pins the real sha256s into
    both PKGBUILDs, and pushes via `KSXGitHub/github-actions-deploy-aur`.
    Step 10 below is the manual fallback when the secret isn't
    configured or when CI is unavailable.

    **Manual fallback** — separate AUR git repos:
    - `~/Projects/aur-ai-usagebar``ssh://aur@aur.archlinux.org/ai-usagebar.git`
    - `~/Projects/aur-ai-usagebar-bin``ssh://aur@aur.archlinux.org/ai-usagebar-bin.git`

    **Always `git fetch origin && git reset --hard origin/master` in each
    AUR clone first.** A previous session may have pushed an intermediate
    release that your local clone never saw — in which case naively
    committing on top diverges and produces a non-trivial rebase
    conflict. The clones are throwaway: reset, then overlay the canonical
    `packaging/aur/PKGBUILD*` + regen'd `.SRCINFO*` from the main repo,
    commit, push.

**Anything skipping any of 1–10 is an incomplete release.** Tags are
immutable; do **not** force-move a tag once it's pushed. Cut a new
patch version instead.

## Hard invariants — never break these

- **Widget always exits 0.** Waybar hides modules that don't. Wrap
  every error in a fallback `` JSON. See `widget::run::fallback`.
- **Cache writes are atomic** (tempfile + persist). Multi-monitor
  Waybar instances coexist via per-vendor `flock`.
- **Tag immutability.** Never `git push --force origin vX.Y.Z` once a
  release is public. The one-time exception in v0.3.0 was a mistake.
- **No secrets in tracked files.** Inline API keys in config.toml are
  the user's choice (and `chmod 600`ed by the Settings overlay), but
  **never commit** a real key. The `.gitignore` covers `.env`,
  `*.credentials.json`, and `.claude/`.
- **Tests are hermetic.** A `#[test]`/`#[tokio::test]` must never read or
  write a real `$HOME`/`$XDG` path (config, cache, creds, Omarchy theme)
  or branch on an ambient env var — the AUR `check()` runs `cargo test`
  during `makepkg`, so any test that reads a user's *customized* files
  fails the install on their machine. Always inject the path/dependency
  via the test seam, never the real-path resolver:
  `Cache::at` not `for_vendor`, `active::{read_from,write_to,cycle_at}`
  not `{read,write,cycle}`, `creds::read_from` not `default_path`,
  `Theme::merged_with_omarchy_file` not `merged_with_omarchy`,
  `Cli::resolve_vendor_with` not `resolved_vendor`, `App::with_theme`
  not `new`. Live API tests stay behind `#[ignore]` (see `tests/live.rs`).
  *Carve-out:* a test that asserts the path *resolver itself* honors the
  OS convention may read the env var it's testing (e.g. the Windows-gated
  `default_path_uses_userprofile_on_windows` reads `%USERPROFILE%`) —
  USERPROFILE is the production input being verified, not ambient state
  the test is incidentally coupled to.

## Secret-discipline rules (learned the hard way)

Two separate leaks in early sessions where real keys appeared in the
Claude conversation transcript (never on disk/GitHub/AUR, but still
worth rotating):

- **Never `cat` a config file** that could contain `api_key` / `token`
  / OAuth credentials. Use `jq 'keys'` for structure, or
  `grep -v 'api_key\|token\|secret\|password'` to show non-secret lines.
- **Never `env | grep …`** without a tight filter. Even
  `env | grep -E "^(RUST|CARGO|LD)"` matched `AWS_*` and
  `WHATSAPP_*` once because of shared substring patterns. Prefer
  `printenv VAR | sed 's|.*|<value-set>|'` per variable.
- **For OAuth credential files** (`~/.claude/.credentials.json`,
  `~/.codex/auth.json`): `jq 'keys'` only.

## Live API smoke discipline

`make smoke` exercises real undocumented endpoints (Anthropic OAuth,
OpenAI Codex OAuth, Z.AI monitor). If the smoke test fails after a
vendor's response shape drifts:

1. Capture the actual response (`curl -sH "Authorization: …" …`).
2. Update the matching `types.rs` in `src/{anthropic,openai,zai,openrouter,deepseek}/`.
3. Re-run `make smoke` until green.
4. **Bump pkgrel (not pkgver) in both PKGBUILDs** — the user-visible
   functionality is unchanged; it's a packaging update tracking a
   silent upstream change.

## What lives where

- `src/active.rs` — scroll-cycle active vendor state file
- `src/anthropic/`, `src/openai/`, `src/openrouter/`, `src/zai/`,
  `src/deepseek/` — per-vendor types + fetch + render
- `src/anthropic/keychain.rs` — macOS-only `security(1)` fallback when
  `~/.claude/.credentials.json` is absent (Claude Code on macOS stores
  the OAuth blob in the login Keychain). Module-gated with
  `#[cfg(target_os = "macos")]`; Linux build never compiles it.
- `src/cache.rs` — atomic per-vendor cache writes + flock, plus the shared
  cross-platform path resolvers (`xdg_cache_dir`, `home_dir`). `home_dir`
  resolves `$HOME` / `%USERPROFILE%` via `directories::BaseDirs` and is reused
  by both OAuth-credential vendors (`anthropic`, `openai`) so the OS convention
  lives in one place.
- `src/tui/settings.rs` — Settings overlay (toml_edit-backed,
  auto-signals waybar after save)
- `src/tui/panels.rs` — native ratatui per-vendor panels
- `src/widget/` — Waybar widget shell (CLI, render, pretty, run)
- `src/tooltip.rs` — shared Pango bordered-box renderer (used by
  every vendor's tooltip)
- `packaging/aur/PKGBUILD` — source-build AUR pkg
- `packaging/aur/PKGBUILD-bin` — prebuilt-binary AUR pkg (multi-arch)
- `.github/workflows/release.yml` — tag-driven release (x86_64 + aarch64)
- `tests/anthropic_e2e.rs` — mockito + insta snapshot tests
- `tests/live.rs``#[ignore]`d smoke tests against real APIs