From fb370bc37e9a2b96e17d213869bda0b476604db7 Mon Sep 17 00:00:00 2001 From: kcnewman Date: Sun, 26 Jul 2026 09:49:00 +0000 Subject: [PATCH 01/44] feat: add accessibility, system theme support, CI, and tests - Accessible names for screen readers on panel indicator, dots, and summaries - CSS uses currentColor and opacity for better theme integration - CONTRIBUTING.md with development setup and code style guidelines - GitHub Actions CI: shellcheck, meson build, bats tests - Bats test suite for shell script (JSON structure, required fields, cleanup) --- .github/workflows/ci.yml | 35 +++++++++++++++++++++++ CHANGELOG.md | 13 +++++++++ CONTRIBUTING.md | 61 ++++++++++++++++++++++++++++++++++++++++ README.md | 6 ++++ indicator.js | 12 ++++---- meson.build | 2 +- metadata.json | 2 +- stylesheet.css | 13 ++++++--- tests/freeby.bats | 42 +++++++++++++++++++++++++++ 9 files changed, 174 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 CONTRIBUTING.md create mode 100644 tests/freeby.bats diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..7c78902 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,35 @@ +name: CI + +on: + push: + branches: [main, dev] + pull_request: + branches: [main, dev] + +jobs: + shellcheck: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install shellcheck + run: sudo apt-get install -y shellcheck + - name: Lint shell scripts + run: shellcheck scripts/freeby.sh scripts/copilot-setup.sh + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install dependencies + run: sudo apt-get install -y meson ninja-build libglib2.0-dev-bin python3 + - name: Build + run: meson setup build --prefix=$HOME/.local && meson install -C build + + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install dependencies + run: sudo apt-get install -y bats python3 curl + - name: Run tests + run: bats tests/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 67dda60..b0bf72a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## v1.0.1 + +### Added +- Accessible names for screen readers on panel indicator, dots, and summaries +- System theme support in CSS (uses `currentColor` and opacity for better theme integration) +- CONTRIBUTING.md with development setup and code style guidelines +- GitHub Actions CI: shellcheck, meson build, bats tests +- Bats test suite for shell script (JSON structure, required fields, cleanup) + +### Changed +- CSS colors use `currentColor` where possible for better theme compatibility +- Status dot and summary text use opacity for dimmed states instead of hardcoded colors + ## v1.0.0 ### Fixed diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..d65d184 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,61 @@ +# Contributing + +Thanks for your interest in Freeby. + +## Development + +Clone and install locally: + +```bash +git clone https://github.com/kcnewman/freeby.git && cd freeby +git checkout dev +meson setup build --prefix=$HOME/.local +meson install -C build +``` + +Test your changes by restarting GNOME Shell (Alt+F2, type `r`, Enter) and enabling the extension: + +```bash +gnome-extensions enable freeby@kelvin.local +``` + +## Code style + +- Keep it simple. No unnecessary abstraction. +- Lowercase sentence case for user-facing text. +- Follow existing patterns in the codebase. + +## Commits + +Use [conventional commits](https://www.conventionalcommits.org/): + +- `feat:` new feature +- `fix:` bug fix +- `docs:` documentation only +- `chore:` maintenance, tests, CI + +## Testing + +Test the shell script directly: + +```bash +bash ~/.local/bin/freeby.sh | python3 -m json.tool +``` + +Run shellcheck: + +```bash +shellcheck scripts/freeby.sh scripts/copilot-setup.sh +``` + +## Branches + +- `main` — stable releases +- `dev` — active development + +## Pull requests + +1. Fork and create a branch from `dev` +2. Make your changes +3. Test manually (the extension runs in your live desktop) +4. Open a PR against `dev` diff --git a/README.md b/README.md index 43929f5..9898fd6 100644 --- a/README.md +++ b/README.md @@ -111,4 +111,10 @@ journalctl -f -o cat /usr/bin/gnome-shell --- +## Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and guidelines. + +--- + [MIT](LICENSE) diff --git a/indicator.js b/indicator.js index f323d71..ac688b5 100644 --- a/indicator.js +++ b/indicator.js @@ -21,18 +21,18 @@ class FreebyIndicator extends PanelMenu.Button { for (const k of PROVIDERS) this._prev[k] = null; this._refreshSeq = 0; - this._panelBox = new St.BoxLayout({ style_class: 'panel-status-menu-box freeby-panel' }); - this._label = new St.Label({ text: 'AI', y_align: Clutter.ActorAlign.CENTER, style_class: 'freeby-panel-label' }); + this._panelBox = new St.BoxLayout({ style_class: 'panel-status-menu-box freeby-panel', accessible_name: 'Freeby usage indicator' }); + this._label = new St.Label({ text: 'AI', y_align: Clutter.ActorAlign.CENTER, style_class: 'freeby-panel-label', accessible_name: 'Usage count' }); this._panelBox.add_child(this._label); this.add_child(this._panelBox); this.connect('button-press-event', () => { this._refresh(); return false; }); this._items = {}; for (const k of PROVIDERS) { - const box = new St.BoxLayout({ style_class: 'freeby-item-box' }); - const dot = new St.Label({ text: '\u25CB', style_class: 'freeby-dot freeby-dot-off', y_align: Clutter.ActorAlign.CENTER }); + const box = new St.BoxLayout({ style_class: 'freeby-item-box', accessible_name: `${LABELS[k]} usage` }); + const dot = new St.Label({ text: '\u25CB', style_class: 'freeby-dot freeby-dot-off', y_align: Clutter.ActorAlign.CENTER, accessible_name: `${LABELS[k]} status` }); const name = new St.Label({ text: LABELS[k], style_class: 'freeby-item-name', y_align: Clutter.ActorAlign.CENTER }); - const summary = new St.Label({ text: '\u2014', style_class: 'freeby-item-summary', y_align: Clutter.ActorAlign.CENTER }); + const summary = new St.Label({ text: '\u2014', style_class: 'freeby-item-summary', y_align: Clutter.ActorAlign.CENTER, accessible_name: `${LABELS[k]} summary` }); box.add_child(dot); box.add_child(name); box.add_child(summary); @@ -43,7 +43,7 @@ class FreebyIndicator extends PanelMenu.Button { } this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); - this._statusItem = new PopupMenu.PopupMenuItem('\u21BB Last checked: never', { reactive: true, can_focus: false }); + this._statusItem = new PopupMenu.PopupMenuItem('\u21BB Last checked: never', { reactive: true, can_focus: false, accessible_name: 'Refresh status' }); this._statusItem.label.add_style_class_name('freeby-status'); this._statusItem.connect('activate', () => { this._refresh(); this.menu.open(); }); this.menu.addMenuItem(this._statusItem); diff --git a/meson.build b/meson.build index 8e46ec0..22d6b06 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('freeby', version: '1.0.0', license: 'MIT', +project('freeby', version: '1.0.1', license: 'MIT', meson_version: '>= 0.56.0', default_options: ['warning_level=0']) diff --git a/metadata.json b/metadata.json index f8ccf99..5a8b29a 100644 --- a/metadata.json +++ b/metadata.json @@ -3,7 +3,7 @@ "name": "Freeby", "description": "Minimal panel indicator for local AI coding tool usage (Codex, Cursor, Copilot)", "shell-version": ["45", "46", "47", "48"], - "version": 1, + "version": 2, "settings-schema": "org.gnome.shell.extensions.freeby", "url": "https://github.com/kcnewman/freeby" } diff --git a/stylesheet.css b/stylesheet.css index 0fde099..ac2912b 100644 --- a/stylesheet.css +++ b/stylesheet.css @@ -6,6 +6,7 @@ font-weight: bold; font-size: 10pt; min-width: 16px; + color: currentColor; } .freeby-panel-green { @@ -30,7 +31,8 @@ } .freeby-dot-off { - color: #888; + color: currentColor; + opacity: 0.4; } .freeby-dot-green { @@ -49,15 +51,17 @@ font-weight: bold; font-size: 9pt; min-width: 52px; + color: currentColor; } .freeby-item-summary { font-size: 9pt; - color: #ccc; + color: currentColor; + opacity: 0.8; } .freeby-dim { - color: #888; + opacity: 0.4; } .freeby-green { @@ -74,5 +78,6 @@ .freeby-status { font-size: 9pt; - color: #bbb; + color: currentColor; + opacity: 0.7; } diff --git a/tests/freeby.bats b/tests/freeby.bats new file mode 100644 index 0000000..b092960 --- /dev/null +++ b/tests/freeby.bats @@ -0,0 +1,42 @@ +#!/usr/bin/env bats + +SCRIPT="$BATS_TEST_DIRNAME/../scripts/freeby.sh" + +@test "outputs valid JSON" { + run bash "$SCRIPT" + [ "$status" -eq 0 ] + echo "$output" | python3 -c "import json,sys; json.load(sys.stdin)" +} + +@test "JSON contains codex, cursor, copilot keys" { + run bash "$SCRIPT" + echo "$output" | python3 -c " +import json,sys +d = json.load(sys.stdin) +assert 'codex' in d, 'missing codex' +assert 'cursor' in d, 'missing cursor' +assert 'copilot' in d, 'missing copilot' +" +} + +@test "each provider has required fields" { + run bash "$SCRIPT" + echo "$output" | python3 -c " +import json,sys +d = json.load(sys.stdin) +for k in ('codex','cursor','copilot'): + p = d[k] + assert 'available' in p, f'{k} missing available' + assert 'has_remaining' in p, f'{k} missing has_remaining' + assert 'summary' in p, f'{k} missing summary' + assert isinstance(p['available'], bool), f'{k} available not bool' + assert isinstance(p['has_remaining'], bool), f'{k} has_remaining not bool' +" +} + +@test "script cleans up temp directory" { + before=$(ls /tmp | grep -c 'tmp\.' || true) + run bash "$SCRIPT" + after=$(ls /tmp | grep -c 'tmp\.' || true) + [ "$after" -le "$before" ] +} From caa0346212acaba9417959ab5211380df3d1e32f Mon Sep 17 00:00:00 2001 From: kcnewman Date: Sun, 26 Jul 2026 09:52:27 +0000 Subject: [PATCH 02/44] fix: resolve shellcheck SC2002 (useless cat) --- scripts/freeby.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/freeby.sh b/scripts/freeby.sh index 0a9e53a..4da906b 100755 --- a/scripts/freeby.sh +++ b/scripts/freeby.sh @@ -113,7 +113,7 @@ fetch_copilot() { token="$(gh auth token 2>/dev/null | tr -d '[:space:]')" fi if [ -z "$token" ] && [ -f "$HOME/.config/freeby/copilot-token" ]; then - token="$(cat "$HOME/.config/freeby/copilot-token" 2>/dev/null | tr -d '[:space:]')" + token="$(tr -d '[:space:]' < "$HOME/.config/freeby/copilot-token" 2>/dev/null)" fi if [ -z "$token" ]; then if [ ! -f "$HOME/.config/freeby/copilot-token" ] && ! command -v gh >/dev/null 2>&1; then From adfd377d567ba8283d8470caca37e39cba0f9f4c Mon Sep 17 00:00:00 2001 From: kcnewman Date: Sun, 26 Jul 2026 09:55:00 +0000 Subject: [PATCH 03/44] docs: rewrite README and update CONTRIBUTING for v1.0.1 --- CONTRIBUTING.md | 42 ++++++++++++++++----- README.md | 98 +++++++++++++++++++++++++++++-------------------- 2 files changed, 90 insertions(+), 50 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d65d184..27606ca 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,8 +4,6 @@ Thanks for your interest in Freeby. ## Development -Clone and install locally: - ```bash git clone https://github.com/kcnewman/freeby.git && cd freeby git checkout dev @@ -13,12 +11,35 @@ meson setup build --prefix=$HOME/.local meson install -C build ``` -Test your changes by restarting GNOME Shell (Alt+F2, type `r`, Enter) and enabling the extension: +Restart GNOME Shell (Alt+F2, type `r`, Enter) and enable: ```bash gnome-extensions enable freeby@kelvin.local ``` +## Project structure + +``` +freeby/ +├── extension.js # Entry point (enable/disable) +├── indicator.js # Panel UI, dropdown, refresh +├── prefs.js # Settings UI in Extension Manager +├── scripts/ +│ ├── freeby.sh # Data aggregator (parallel provider fetches) +│ └── copilot-setup.sh # GitHub device-flow auth +├── schemas/ +│ └── *.gschema.xml # GSettings schema +├── build-aux/ +│ └── compile-schemas.sh +├── tests/ +│ └── freeby.bats # Shell script tests +├── .github/workflows/ +│ └── ci.yml # shellcheck, meson build, bats tests +└── docs/ + ├── s1.png + └── s2.png +``` + ## Code style - Keep it simple. No unnecessary abstraction. @@ -36,18 +57,19 @@ Use [conventional commits](https://www.conventionalcommits.org/): ## Testing -Test the shell script directly: - ```bash -bash ~/.local/bin/freeby.sh | python3 -m json.tool -``` - -Run shellcheck: +# test the data script +bash scripts/freeby.sh | python3 -m json.tool -```bash +# run shellcheck shellcheck scripts/freeby.sh scripts/copilot-setup.sh + +# run bats tests +bats tests/ ``` +CI runs automatically on push to `main` or `dev`, and on all pull requests. + ## Branches - `main` — stable releases diff --git a/README.md b/README.md index 9898fd6..8457680 100644 --- a/README.md +++ b/README.md @@ -1,62 +1,75 @@ -# Freeby - -> GNOME Shell extension that tracks remaining usage across your free-tier AI coding tools. - - - - - - -
+

+

Freeby

+

Track your free-tier AI coding tool usage at a glance.

+

+ +

+ Panel indicator + Dropdown menu +

+ +

+ Features · + Providers · + Install · + Settings · + Contributing · + Releases +

--- ### Features -- **Panel indicator** — colored `ai·N` shows available providers at a glance -- **Dropdown** — per-provider usage, limits, and reset countdown -- **Notifications** — desktop alert when a provider hits its limit -- **Auto-refresh on wake** — refreshes immediately after sleep -- **Fast** — all providers queried in parallel (~2s) -- **Configurable** — adjust refresh interval and notifications in settings +| | Feature | Description | +|---|---|---| +| ![#4ade80](https://via.placeholder.com/10/4ade80/000000?text=+) | **Panel indicator** | Colored `ai·N` shows available providers at a glance | +| | **Dropdown** | Per-provider usage, limits, and reset countdown | +| | **Notifications** | Desktop alert when a provider hits its limit | +| | **Auto-refresh on wake** | Refreshes immediately after sleep | +| | **Parallel fetches** | All providers queried in parallel (~2s) | +| | **Configurable** | Adjust refresh interval and notifications in settings | +| | **Accessible** | Screen reader support for all UI elements | +| | **Theme-aware** | Works with light and dark GNOME themes | --- -### Providers +### Supported providers -| | Provider | Source | -|---|---|---| -| ![#f87171](https://via.placeholder.com/10/f87171/000000?text=+) | **Codex** | `~/.codex/auth.json` → `codex-check` CLI | -| ![#4ade80](https://via.placeholder.com/10/4ade80/000000?text=+) | **Cursor** | `~/.config/cursor/auth.json` → Cursor API | -| ![#facc15](https://via.placeholder.com/10/facc15/000000?text=+) | **Copilot** | `gh` CLI → GitHub API | +| | Provider | Auth source | Data source | +|---|---|---|---| +| ![#f87171](https://via.placeholder.com/10/f87171/000000?text=+) | **Codex** | `~/.codex/auth.json` | `codex-check` CLI | +| ![#4ade80](https://via.placeholder.com/10/4ade80/000000?text=+) | **Cursor** | `~/.config/cursor/auth.json` | Cursor API | +| ![#facc15](https://via.placeholder.com/10/facc15/000000?text=+) | **Copilot** | `gh` CLI or `~/.config/freeby/copilot-token` | GitHub API | --- -### Prerequisites +### Install + +**Prerequisites** - GNOME Shell 45+ (Wayland or X11) -- `python3` -- `curl` -- `meson` and `ninja-build` (for building) -- `glib-compile-schemas` (usually pre-installed) -- `npx` (for Codex usage tracking) -- `gh` CLI (for Copilot usage tracking, optional) +- `python3`, `curl`, `meson`, `ninja-build` +- `npx` (for Codex) +- `gh` CLI (for Copilot, optional) -Install build dependencies on Fedora: +
+Fedora ```bash sudo dnf install meson ninja-build python3 curl glib2-devel ``` +
-On Ubuntu/Debian: +
+Ubuntu / Debian ```bash sudo apt install meson ninja-build python3 curl libglib2.0-dev-bin ``` +
---- - -### Install +**Build and install** ```bash git clone https://github.com/kcnewman/freeby.git && cd freeby @@ -64,7 +77,7 @@ meson setup build --prefix=$HOME/.local meson install -C build ``` -Restart your session, then: +Then restart your session and enable: ```bash gnome-extensions enable freeby@kelvin.local @@ -76,10 +89,10 @@ gnome-extensions enable freeby@kelvin.local ### Settings -| Setting | Default | | -|---|---|---| -| Refresh interval | `120s` | min 30s | -| Notifications | `on` | alerts on limit hit | +| Setting | Default | Range | Description | +|---|---|---|---| +| Refresh interval | `120s` | 30–3600s | How often to check usage | +| Notifications | `on` | — | Alert when a provider hits its limit | Configure via Extension Manager or CLI: @@ -105,16 +118,21 @@ rm -f ~/.local/share/glib-2.0/schemas/gschemas.compiled ### Debug ```bash +# test the data script bash ~/.local/bin/freeby.sh | python3 -m json.tool + +# watch extension logs journalctl -f -o cat /usr/bin/gnome-shell ``` --- -## Contributing +### Contributing See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and guidelines. --- +### License + [MIT](LICENSE) From 10f39f2ccbdcc4b8bd33ba6a82577043c04ec35d Mon Sep 17 00:00:00 2001 From: kcnewman Date: Sun, 26 Jul 2026 09:56:23 +0000 Subject: [PATCH 04/44] fix: replace removed messageTray.get_source for GNOME 50 --- indicator.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indicator.js b/indicator.js index ac688b5..9f0eb5c 100644 --- a/indicator.js +++ b/indicator.js @@ -77,8 +77,8 @@ class FreebyIndicator extends PanelMenu.Button { _notify(title, body) { if (!this._settings.get_boolean('notifications-enabled')) return; try { - let src = Main.messageTray.get_source('Freeby'); - if (!src) { src = new Main.messageTray.Source('Freeby', 'dialog-information-symbolic'); Main.messageTray.add(src); } + const src = new Main.messageTray.Source('Freeby', 'dialog-information-symbolic'); + Main.messageTray.add(src); src.addNotification(new Main.messageTray.Notification({ source: src, title, body })); } catch (e) { logError(e, 'freeby: notification failed'); } } From 820b28e8d50367805900eaec2827a6400383d51c Mon Sep 17 00:00:00 2001 From: kcnewman Date: Sun, 26 Jul 2026 10:12:41 +0000 Subject: [PATCH 05/44] docs: fix broken image links with inline SVG data URIs --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8457680..f57a5cf 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ | | Feature | Description | |---|---|---| -| ![#4ade80](https://via.placeholder.com/10/4ade80/000000?text=+) | **Panel indicator** | Colored `ai·N` shows available providers at a glance | +| | **Panel indicator** | Colored `ai·N` shows available providers at a glance | | | **Dropdown** | Per-provider usage, limits, and reset countdown | | | **Notifications** | Desktop alert when a provider hits its limit | | | **Auto-refresh on wake** | Refreshes immediately after sleep | @@ -38,9 +38,9 @@ | | Provider | Auth source | Data source | |---|---|---|---| -| ![#f87171](https://via.placeholder.com/10/f87171/000000?text=+) | **Codex** | `~/.codex/auth.json` | `codex-check` CLI | -| ![#4ade80](https://via.placeholder.com/10/4ade80/000000?text=+) | **Cursor** | `~/.config/cursor/auth.json` | Cursor API | -| ![#facc15](https://via.placeholder.com/10/facc15/000000?text=+) | **Copilot** | `gh` CLI or `~/.config/freeby/copilot-token` | GitHub API | +| | **Codex** | `~/.codex/auth.json` | `codex-check` CLI | +| | **Cursor** | `~/.config/cursor/auth.json` | Cursor API | +| | **Copilot** | `gh` CLI or `~/.config/freeby/copilot-token` | GitHub API | --- From bad080694e122b54171267018a1785171ab92ec4 Mon Sep 17 00:00:00 2001 From: kcnewman Date: Sun, 26 Jul 2026 10:14:27 +0000 Subject: [PATCH 06/44] docs: simplify tables, use emoji for provider colors --- README.md | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index f57a5cf..3e42e07 100644 --- a/README.md +++ b/README.md @@ -21,26 +21,24 @@ ### Features -| | Feature | Description | -|---|---|---| -| | **Panel indicator** | Colored `ai·N` shows available providers at a glance | -| | **Dropdown** | Per-provider usage, limits, and reset countdown | -| | **Notifications** | Desktop alert when a provider hits its limit | -| | **Auto-refresh on wake** | Refreshes immediately after sleep | -| | **Parallel fetches** | All providers queried in parallel (~2s) | -| | **Configurable** | Adjust refresh interval and notifications in settings | -| | **Accessible** | Screen reader support for all UI elements | -| | **Theme-aware** | Works with light and dark GNOME themes | +- **Panel indicator** — colored `ai·N` shows available providers at a glance +- **Dropdown** — per-provider usage, limits, and reset countdown +- **Notifications** — desktop alert when a provider hits its limit +- **Auto-refresh on wake** — refreshes immediately after sleep +- **Parallel fetches** — all providers queried in parallel (~2s) +- **Configurable** — adjust refresh interval and notifications in settings +- **Accessible** — screen reader support for all UI elements +- **Theme-aware** — works with light and dark GNOME themes --- ### Supported providers -| | Provider | Auth source | Data source | -|---|---|---|---| -| | **Codex** | `~/.codex/auth.json` | `codex-check` CLI | -| | **Cursor** | `~/.config/cursor/auth.json` | Cursor API | -| | **Copilot** | `gh` CLI or `~/.config/freeby/copilot-token` | GitHub API | +| Provider | Auth source | Data source | +|---|---|---| +| 🟡 **Codex** | `~/.codex/auth.json` | `codex-check` CLI | +| 🟢 **Cursor** | `~/.config/cursor/auth.json` | Cursor API | +| 🟠 **Copilot** | `gh` CLI or `~/.config/freeby/copilot-token` | GitHub API | --- From e85ee4feae1b3db19eafbc903c9addbe4e7ff254 Mon Sep 17 00:00:00 2001 From: kcnewman Date: Sun, 26 Jul 2026 10:22:37 +0000 Subject: [PATCH 07/44] docs: remove dividers for seamless flow --- README.md | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/README.md b/README.md index 3e42e07..8ecdf50 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,6 @@ Releases

---- - ### Features - **Panel indicator** — colored `ai·N` shows available providers at a glance @@ -30,8 +28,6 @@ - **Accessible** — screen reader support for all UI elements - **Theme-aware** — works with light and dark GNOME themes ---- - ### Supported providers | Provider | Auth source | Data source | @@ -40,8 +36,6 @@ | 🟢 **Cursor** | `~/.config/cursor/auth.json` | Cursor API | | 🟠 **Copilot** | `gh` CLI or `~/.config/freeby/copilot-token` | GitHub API | ---- - ### Install **Prerequisites** @@ -83,8 +77,6 @@ gnome-extensions enable freeby@kelvin.local > **Copilot users:** If `gh` isn't installed, run `copilot-setup.sh` first. ---- - ### Settings | Setting | Default | Range | Description | @@ -99,8 +91,6 @@ gsettings --schemadir ~/.local/share/glib-2.0/schemas \ set org.gnome.shell.extensions.freeby refresh-interval 60 ``` ---- - ### Uninstall ```bash @@ -111,8 +101,6 @@ rm -f ~/.local/share/glib-2.0/schemas/org.gnome.shell.extensions.freeby.gschema. rm -f ~/.local/share/glib-2.0/schemas/gschemas.compiled ``` ---- - ### Debug ```bash @@ -123,14 +111,10 @@ bash ~/.local/bin/freeby.sh | python3 -m json.tool journalctl -f -o cat /usr/bin/gnome-shell ``` ---- - ### Contributing See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and guidelines. ---- - ### License [MIT](LICENSE) From c552a4373398630de2299cea3b2c5c5a1e9a413d Mon Sep 17 00:00:00 2001 From: kcnewman Date: Sun, 26 Jul 2026 10:42:54 +0000 Subject: [PATCH 08/44] fix: add GNOME 50 support and remove deprecated version field --- metadata.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/metadata.json b/metadata.json index 5a8b29a..f131767 100644 --- a/metadata.json +++ b/metadata.json @@ -2,8 +2,7 @@ "uuid": "freeby@kelvin.local", "name": "Freeby", "description": "Minimal panel indicator for local AI coding tool usage (Codex, Cursor, Copilot)", - "shell-version": ["45", "46", "47", "48"], - "version": 2, + "shell-version": ["45", "46", "47", "48", "49", "50"], "settings-schema": "org.gnome.shell.extensions.freeby", "url": "https://github.com/kcnewman/freeby" } From 095efaf8d6e45103763aad46ff068a95f4f2271f Mon Sep 17 00:00:00 2001 From: kcnewman Date: Sun, 26 Jul 2026 10:48:35 +0000 Subject: [PATCH 09/44] fix: remove invalid accessible_name from PopupMenuItem --- indicator.js | 244 +++++++++++++++++++++++++-------------------------- 1 file changed, 122 insertions(+), 122 deletions(-) diff --git a/indicator.js b/indicator.js index 9f0eb5c..3ad2559 100644 --- a/indicator.js +++ b/indicator.js @@ -13,128 +13,128 @@ const LABELS = { codex: 'Codex', cursor: 'Cursor', copilot: 'Copilot' }; const SCRIPT = GLib.build_filenamev([GLib.get_home_dir(), '.local', 'bin', 'freeby.sh']); export const FreebyIndicator = GObject.registerClass( -class FreebyIndicator extends PanelMenu.Button { - _init(settings) { - super._init(0.0, 'Freeby', false); - this._settings = settings; - this._prev = {}; - for (const k of PROVIDERS) this._prev[k] = null; - this._refreshSeq = 0; - - this._panelBox = new St.BoxLayout({ style_class: 'panel-status-menu-box freeby-panel', accessible_name: 'Freeby usage indicator' }); - this._label = new St.Label({ text: 'AI', y_align: Clutter.ActorAlign.CENTER, style_class: 'freeby-panel-label', accessible_name: 'Usage count' }); - this._panelBox.add_child(this._label); - this.add_child(this._panelBox); - this.connect('button-press-event', () => { this._refresh(); return false; }); - - this._items = {}; - for (const k of PROVIDERS) { - const box = new St.BoxLayout({ style_class: 'freeby-item-box', accessible_name: `${LABELS[k]} usage` }); - const dot = new St.Label({ text: '\u25CB', style_class: 'freeby-dot freeby-dot-off', y_align: Clutter.ActorAlign.CENTER, accessible_name: `${LABELS[k]} status` }); - const name = new St.Label({ text: LABELS[k], style_class: 'freeby-item-name', y_align: Clutter.ActorAlign.CENTER }); - const summary = new St.Label({ text: '\u2014', style_class: 'freeby-item-summary', y_align: Clutter.ActorAlign.CENTER, accessible_name: `${LABELS[k]} summary` }); - box.add_child(dot); - box.add_child(name); - box.add_child(summary); - const item = new PopupMenu.PopupMenuItem('', { reactive: false, can_focus: false }); - item.add_child(box); - this.menu.addMenuItem(item); - this._items[k] = { dot, summary }; + class FreebyIndicator extends PanelMenu.Button { + _init(settings) { + super._init(0.0, 'Freeby', false); + this._settings = settings; + this._prev = {}; + for (const k of PROVIDERS) this._prev[k] = null; + this._refreshSeq = 0; + + this._panelBox = new St.BoxLayout({ style_class: 'panel-status-menu-box freeby-panel', accessible_name: 'Freeby usage indicator' }); + this._label = new St.Label({ text: 'AI', y_align: Clutter.ActorAlign.CENTER, style_class: 'freeby-panel-label', accessible_name: 'Usage count' }); + this._panelBox.add_child(this._label); + this.add_child(this._panelBox); + this.connect('button-press-event', () => { this._refresh(); return false; }); + + this._items = {}; + for (const k of PROVIDERS) { + const box = new St.BoxLayout({ style_class: 'freeby-item-box', accessible_name: `${LABELS[k]} usage` }); + const dot = new St.Label({ text: '\u25CB', style_class: 'freeby-dot freeby-dot-off', y_align: Clutter.ActorAlign.CENTER, accessible_name: `${LABELS[k]} status` }); + const name = new St.Label({ text: LABELS[k], style_class: 'freeby-item-name', y_align: Clutter.ActorAlign.CENTER }); + const summary = new St.Label({ text: '\u2014', style_class: 'freeby-item-summary', y_align: Clutter.ActorAlign.CENTER, accessible_name: `${LABELS[k]} summary` }); + box.add_child(dot); + box.add_child(name); + box.add_child(summary); + const item = new PopupMenu.PopupMenuItem('', { reactive: false, can_focus: false }); + item.add_child(box); + this.menu.addMenuItem(item); + this._items[k] = { dot, summary }; + } + + this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); + this._statusItem = new PopupMenu.PopupMenuItem('\u21BB Last checked: never', { reactive: true, can_focus: false }); + this._statusItem.label.add_style_class_name('freeby-status'); + this._statusItem.connect('activate', () => { this._refresh(); this.menu.open(); }); + this.menu.addMenuItem(this._statusItem); + + this._timerId = null; + this._setupTwimer(); + this._refresh(); + this._monitorWake(); } - this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); - this._statusItem = new PopupMenu.PopupMenuItem('\u21BB Last checked: never', { reactive: true, can_focus: false, accessible_name: 'Refresh status' }); - this._statusItem.label.add_style_class_name('freeby-status'); - this._statusItem.connect('activate', () => { this._refresh(); this.menu.open(); }); - this.menu.addMenuItem(this._statusItem); - - this._timerId = null; - this._setupTimer(); - this._refresh(); - this._monitorWake(); - } - - _setupTimer() { - if (this._timerId) GLib.source_remove(this._timerId); - const sec = Math.max(30, this._settings.get_int('refresh-interval')); - this._timerId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, sec, () => { this._refresh(); return GLib.SOURCE_CONTINUE; }); - } - - _monitorWake() { - try { - this._sleepId = Gio.DBus.system.signal_subscribe( - 'org.freedesktop.login1', 'org.freedesktop.login1.Manager', - 'PrepareForSleep', '/org/freedesktop/login1', null, Gio.DBusSignalFlags.NONE, - (_, __, ___, ____, _____, params) => { - if (params.get_child_value(0).get_boolean()) { - log('freeby: woke from sleep, refreshing'); - this._refresh(); - } - }); - } catch (e) { logError(e, 'freeby: could not monitor sleep/wake'); } - } - - _notify(title, body) { - if (!this._settings.get_boolean('notifications-enabled')) return; - try { - const src = new Main.messageTray.Source('Freeby', 'dialog-information-symbolic'); - Main.messageTray.add(src); - src.addNotification(new Main.messageTray.Notification({ source: src, title, body })); - } catch (e) { logError(e, 'freeby: notification failed'); } - } - - _setError() { - this._label.text = 'ai'; - this._label.style_class = 'freeby-panel-label'; - } - - _refresh() { - this._refreshSeq++; - const seq = this._refreshSeq; - let proc; - try { proc = Gio.Subprocess.new(['/bin/bash', SCRIPT], Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_PIPE); } - catch (e) { this._setError(); logError(e, 'freeby: failed to spawn script'); return; } - - proc.communicate_utf8_async(null, null, (p, res) => { - if (seq !== this._refreshSeq) return; - let stdout, stderr; - try { [, stdout, stderr] = p.communicate_utf8_finish(res); } - catch (e) { this._setError(); logError(e, 'freeby: subprocess failed'); return; } - if (!p.get_successful()) { this._setError(); log(`freeby: script error: ${stderr}`); return; } - this._apply(stdout); - }); - } - - _apply(stdout) { - let data; - try { data = JSON.parse(stdout); } - catch (e) { this._setError(); logError(e, 'freeby: bad JSON'); return; } - if (!data || typeof data !== 'object') { this._setError(); log('freeby: empty or invalid data'); return; } - - const active = PROVIDERS.filter(k => data[k]?.has_remaining).length; - const total = PROVIDERS.filter(k => data[k]?.available).length; - this._label.text = active > 0 ? `ai\u00B7${active}` : 'ai'; - this._label.style_class = 'freeby-panel-label' + (active === total && total > 0 ? ' freeby-panel-green' : active > 0 ? ' freeby-panel-yellow' : total > 0 ? ' freeby-panel-red' : ''); - - const hit = PROVIDERS.filter(k => data[k]?.available && !data[k].has_remaining && this._prev[k]?.has_remaining !== false).map(k => LABELS[k]); - this._prev = {}; - for (const k of PROVIDERS) this._prev[k] = data[k] ? { has_remaining: data[k].has_remaining } : null; - if (hit.length) this._notify('Usage limit reached', `${hit.join(', ')} ${hit.length === 1 ? 'has' : 'have'} hit their limit`); - - for (const k of PROVIDERS) { - const d = data[k], { dot, summary } = this._items[k]; - if (!d) { dot.text = '\u25CB'; dot.style_class = 'freeby-dot freeby-dot-off'; summary.text = 'no data'; summary.style_class = 'freeby-item-summary'; } - else if (!d.available) { dot.text = '\u25CB'; dot.style_class = 'freeby-dot freeby-dot-off'; summary.text = d.summary || 'not available'; summary.style_class = 'freeby-item-summary freeby-dim'; } - else if (d.summary?.includes('limit reached')) { dot.text = '\u25CF'; dot.style_class = 'freeby-dot freeby-dot-red'; summary.text = d.summary; summary.style_class = 'freeby-item-summary freeby-red'; } - else if (d.summary?.includes('expired') || d.summary?.includes('parse error')) { dot.text = '\u25CF'; dot.style_class = 'freeby-dot freeby-dot-yellow'; summary.text = d.summary; summary.style_class = 'freeby-item-summary freeby-yellow'; } - else { dot.text = '\u25CF'; dot.style_class = 'freeby-dot freeby-dot-green'; summary.text = d.summary; summary.style_class = 'freeby-item-summary freeby-green'; } + _setupTimer() { + if (this._timerId) GLib.source_remove(this._timerId); + const sec = Math.max(30, this._settings.get_int('refresh-interval')); + this._timerId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, sec, () => { this._refresh(); return GLib.SOURCE_CONTINUE; }); } - this._statusItem.label.text = `\u21BB Last checked: ${GLib.DateTime.new_now_local().format('%H:%M:%S')}`; - } - - destroy() { - if (this._timerId) { GLib.source_remove(this._timerId); this._timerId = null; } - if (this._sleepId) { Gio.DBus.system.signal_unsubscribe(this._sleepId); this._sleepId = null; } - super.destroy(); - } -}); + + _monitorWake() { + try { + this._sleepId = Gio.DBus.system.signal_subscribe( + 'org.freedesktop.login1', 'org.freedesktop.login1.Manager', + 'PrepareForSleep', '/org/freedesktop/login1', null, Gio.DBusSignalFlags.NONE, + (_, __, ___, ____, _____, params) => { + if (params.get_child_value(0).get_boolean()) { + log('freeby: woke from sleep, refreshing'); + this._refresh(); + } + }); + } catch (e) { logError(e, 'freeby: could not monitor sleep/wake'); } + } + + _notify(title, body) { + if (!this._settings.get_boolean('notifications-enabled')) return; + try { + const src = new Main.messageTray.Source('Freeby', 'dialog-information-symbolic'); + Main.messageTray.add(src); + src.addNotification(new Main.messageTray.Notification({ source: src, title, body })); + } catch (e) { logError(e, 'freeby: notification failed'); } + } + + _setError() { + this._label.text = 'ai'; + this._label.style_class = 'freeby-panel-label'; + } + + _refresh() { + this._refreshSeq++; + const seq = this._refreshSeq; + let proc; + try { proc = Gio.Subprocess.new(['/bin/bash', SCRIPT], Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_PIPE); } + catch (e) { this._setError(); logError(e, 'freeby: failed to spawn script'); return; } + + proc.communicate_utf8_async(null, null, (p, res) => { + if (seq !== this._refreshSeq) return; + let stdout, stderr; + try { [, stdout, stderr] = p.communicate_utf8_finish(res); } + catch (e) { this._setError(); logError(e, 'freeby: subprocess failed'); return; } + if (!p.get_successful()) { this._setError(); log(`freeby: script error: ${stderr}`); return; } + this._apply(stdout); + }); + } + + _apply(stdout) { + let data; + try { data = JSON.parse(stdout); } + catch (e) { this._setError(); logError(e, 'freeby: bad JSON'); return; } + if (!data || typeof data !== 'object') { this._setError(); log('freeby: empty or invalid data'); return; } + + const active = PROVIDERS.filter(k => data[k]?.has_remaining).length; + const total = PROVIDERS.filter(k => data[k]?.available).length; + this._label.text = active > 0 ? `ai\u00B7${active}` : 'ai'; + this._label.style_class = 'freeby-panel-label' + (active === total && total > 0 ? ' freeby-panel-green' : active > 0 ? ' freeby-panel-yellow' : total > 0 ? ' freeby-panel-red' : ''); + + const hit = PROVIDERS.filter(k => data[k]?.available && !data[k].has_remaining && this._prev[k]?.has_remaining !== false).map(k => LABELS[k]); + this._prev = {}; + for (const k of PROVIDERS) this._prev[k] = data[k] ? { has_remaining: data[k].has_remaining } : null; + if (hit.length) this._notify('Usage limit reached', `${hit.join(', ')} ${hit.length === 1 ? 'has' : 'have'} hit their limit`); + + for (const k of PROVIDERS) { + const d = data[k], { dot, summary } = this._items[k]; + if (!d) { dot.text = '\u25CB'; dot.style_class = 'freeby-dot freeby-dot-off'; summary.text = 'no data'; summary.style_class = 'freeby-item-summary'; } + else if (!d.available) { dot.text = '\u25CB'; dot.style_class = 'freeby-dot freeby-dot-off'; summary.text = d.summary || 'not available'; summary.style_class = 'freeby-item-summary freeby-dim'; } + else if (d.summary?.includes('limit reached')) { dot.text = '\u25CF'; dot.style_class = 'freeby-dot freeby-dot-red'; summary.text = d.summary; summary.style_class = 'freeby-item-summary freeby-red'; } + else if (d.summary?.includes('expired') || d.summary?.includes('parse error')) { dot.text = '\u25CF'; dot.style_class = 'freeby-dot freeby-dot-yellow'; summary.text = d.summary; summary.style_class = 'freeby-item-summary freeby-yellow'; } + else { dot.text = '\u25CF'; dot.style_class = 'freeby-dot freeby-dot-green'; summary.text = d.summary; summary.style_class = 'freeby-item-summary freeby-green'; } + } + this._statusItem.label.text = `\u21BB Last checked: ${GLib.DateTime.new_now_local().format('%H:%M:%S')}`; + } + + destroy() { + if (this._timerId) { GLib.source_remove(this._timerId); this._timerId = null; } + if (this._sleepId) { Gio.DBus.system.signal_unsubscribe(this._sleepId); this._sleepId = null; } + super.destroy(); + } + }); From d2c2d8624d19dffa18ab54de03fa42dda5ef14f4 Mon Sep 17 00:00:00 2001 From: kcnewman Date: Sun, 26 Jul 2026 10:54:18 +0000 Subject: [PATCH 10/44] fix: keep dropdown open when clicking refresh --- indicator.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indicator.js b/indicator.js index 3ad2559..b2aaa8a 100644 --- a/indicator.js +++ b/indicator.js @@ -45,11 +45,11 @@ export const FreebyIndicator = GObject.registerClass( this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); this._statusItem = new PopupMenu.PopupMenuItem('\u21BB Last checked: never', { reactive: true, can_focus: false }); this._statusItem.label.add_style_class_name('freeby-status'); - this._statusItem.connect('activate', () => { this._refresh(); this.menu.open(); }); + this._statusItem.connect('activate', () => { this._refresh(); GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { this.menu.open(); return GLib.SOURCE_REMOVE; }); }); this.menu.addMenuItem(this._statusItem); this._timerId = null; - this._setupTwimer(); + this._setupTimer(); this._refresh(); this._monitorWake(); } From 822d1636a7acc0c75a47ebfc663ed1428979bd4b Mon Sep 17 00:00:00 2001 From: kcnewman Date: Sun, 26 Jul 2026 10:55:43 +0000 Subject: [PATCH 11/44] release: v1.0.2 --- CHANGELOG.md | 8 ++++++++ meson.build | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0bf72a..5c3f947 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## v1.0.2 + +### Fixed +- Added GNOME 49 and 50 to shell-version compatibility +- Removed deprecated `version` field from metadata.json +- Dropdown now stays open when clicking refresh (deferred reopen) +- Removed invalid `accessible_name` from PopupMenuItem (caused extension to fail loading) + ## v1.0.1 ### Added diff --git a/meson.build b/meson.build index 22d6b06..6af6f8f 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('freeby', version: '1.0.1', license: 'MIT', +project('freeby', version: '1.0.2', license: 'MIT', meson_version: '>= 0.56.0', default_options: ['warning_level=0']) From 8416f323bcb291718b2b99a133160443e5d8568c Mon Sep 17 00:00:00 2001 From: kcnewman Date: Sun, 6 Sep 2026 01:33:10 +0000 Subject: [PATCH 12/44] docs: record usage monitoring roadmap and project rules --- AGENTS.md | 43 ++++++++ docs/planning/v1-notes.md | 153 +++++++++++++++++++++++++++ plan.md | 210 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 406 insertions(+) create mode 100644 AGENTS.md create mode 100644 docs/planning/v1-notes.md create mode 100644 plan.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..71bf8c9 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,43 @@ +# Project guidance + +Applies to the whole repository. Read [plan.md](plan.md) and [CONTRIBUTING.md](CONTRIBUTING.md) before implementation. + +## Product priorities + +- Build a reliable native GNOME usage monitor for Codex, Claude Code, Cursor, and Copilot, including free and paid plans. +- Complete usage monitoring before cost breakdowns, estimates, budgets, or billing features. +- Keep the Freeby name, icon, extension UUID, and settings schema identity during the usage milestones. Defer rebranding until the system works. +- Provide preferences for panel position, providers, refresh behavior, notifications, and history as scheduled in the plan. +- Use `reference-images/` for layout inspiration; adapt to GNOME themes, keyboard access, screen readers, and display scaling. + +## Architecture and correctness + +- Keep GNOME entry points thin; separate provider adapters, usage logic, collection services, UI, and preferences. +- Prefer GJS, Gio/GLib, and native GNOME APIs. Use Meson for installation and packaging; keep development dependencies out of the runtime archive. +- Replace the legacy Bash/embedded-Python collector incrementally. Do not introduce runtime package downloads or unpinned `npx` execution. +- Keep expensive history scans out of the Shell UI process. Bound subprocess lifetimes, prevent overlapping refreshes, and cancel work on disable. +- Store numeric metrics, units, periods, capabilities, source scope, and freshness explicitly. Never infer state from display text. +- Unknown, unsupported, stale, unauthenticated, and exhausted are distinct states. An empty response is not zero usage. +- Keep account-wide limits separate from local activity; never infer subscription usage from token totals or add overlapping data sources. +- Handle cumulative counters, repeated events, partial records, timezone boundaries, and provider-specific cache-token semantics. +- Validate provider APIs and CLI versions against current documentation and fixtures; support partial results without hiding failures. +- Keep credentials and transcript content out of logs, fixtures, caches, and releases. Persist only required usage metadata in XDG locations. + +## Verification + +- Add meaningful regression tests for changed behavior. Default automated tests to synthetic or sanitized fixtures without live accounts or network access. +- While legacy scripts remain, use `bash -n scripts/freeby.sh scripts/copilot-setup.sh`, `shellcheck scripts/freeby.sh scripts/copilot-setup.sh`, and `bats tests/` as appropriate. +- Validate schemas with `glib-compile-schemas --strict --dry-run schemas`; use an isolated temporary prefix for build/install checks. +- Add and document GJS/pure-JavaScript test and lint commands as the new modules land. Do not claim unavailable checks passed. +- Test only as broadly as the change requires; documentation-only changes need document/link checks, not a live desktop installation. +- Verify lifecycle, wake handling, accessibility, packaging, and each advertised GNOME version before a release. Label unsupported or unverified combinations honestly. + +## Milestones and releases + +- Follow the ordered milestones in `plan.md`; keep its checkboxes and evidence current. Historical notes under `docs/planning/` are context, not the active roadmap. +- Use conventional commits and stage only intentional changes. Preserve unrelated edits, local credentials, and user reference assets. +- After each completed milestone, commit the tested changes, tag the milestone, and publish an installable GitHub prerelease as agreed with the user. +- Resolve the intended repository and release branch before publishing. Release artifacts must match the tagged commit and pass the milestone gate. +- Include installation/upgrade instructions, release notes, verification results, known limitations, and a downgrade path. Verify the published tag and downloadable artifact. +- If a required check or release operation is blocked, report the exact blocker and leave the milestone incomplete; never label an unfinished milestone released. +- Planning/documentation setup alone is not a completed product milestone and does not warrant a prerelease. diff --git a/docs/planning/v1-notes.md b/docs/planning/v1-notes.md new file mode 100644 index 0000000..38e1e86 --- /dev/null +++ b/docs/planning/v1-notes.md @@ -0,0 +1,153 @@ +# Freeby v1 planning archive + +Preserved from the original local `plan.md` on 2026-09-06. These are historical +investigation notes, not verified descriptions of the current implementation. +Some claims about disabled providers, completed fixes, quota periods, and +packaging differ from the current code. Revalidate any provider-specific +assumptions before using them. The active roadmap is [../../plan.md](../../plan.md). + +--- + +# Freeby Plan + +## Current state + +- **v1.0.2** released and merged to main +- 2 providers: Codex, Copilot (Cursor disabled — see below) +- Panel shows `ai·N` (count of providers with remaining quota) +- Panel text colored green/yellow/red by availability +- Click panel to refresh +- Dropdown shows per-provider usage and reset countdown +- Desktop notifications on limit hit +- Auto-refresh on wake from sleep +- Configurable refresh interval (gsettings) +- Settings UI in Extension Manager +- Parallel provider fetches (~2s) +- Architecture: extension.js (entry) + indicator.js (UI) + prefs.js (settings) +- CI/CD: GitHub Actions (shellcheck, meson build, bats tests) +- Accessibility: screen reader names, theme-aware CSS + +## Known limitations + +- Codex: uses `codex-check` CLI (npx), can be slow on first run +- Codex: limit is monthly (30 days), not 5h as formatted output suggests +- Codex: uses JSON output (`--json` flag) for reliable parsing +- Copilot: free tier is very limited (200 chat credits/month) +- No persistent storage — usage history not tracked + +## Cursor investigation (disabled in v1.0.2) + +### Problem +The Cursor API (`GetCurrentPeriodUsage`) only tracks paid plan usage. When `overallLimit == 0` and `remainingBonus == false`, it means: +- No paid subscription (`overallLimit: 0`) +- Free bonus tokens exhausted (`remainingBonus: false`) + +The API returns `totalPercentUsed: 0` which is "0% of nothing" — misleading. + +### API response when no subscription +```json +{ + "planUsage": { "totalPercentUsed": 0, "remainingBonus": false }, + "spendLimitUsage": { "overallLimit": 0, "pooledLimit": 0 }, + "displayMessage": "You've used 0% of your included usage" +} +``` + +### Why Cursor CLI shows "get a subscription" +It correctly detects: no paid plan + exhausted free bonus = needs subscription. + +### What the user experienced +- Used free bonus tokens recently (worked fine) +- Now bonus is exhausted, Cursor CLI says "get subscription" +- Extension was showing "0% used, resets in 3d" — misleading + +### Decision +Disable Cursor for now. Re-enable in v2.0.0 with paid subscription detection: +- `overallLimit > 0` → show usage percentage +- `overallLimit == 0 && remainingBonus == false` → "no subscription" +- `overallLimit == 0 && remainingBonus == true` → "free tier, bonus remaining" + +## Things to watch out for + +### API changes +- Cursor API is unofficial — could break without notice +- GitHub Copilot API could change endpoints or auth +- Codex API format might change in new `codex-check` versions (but JSON output is more stable) + +### Auth expiry +- Copilot tokens via `gh` stay valid as long as `gh auth` is active +- Copilot tokens from `copilot-setup` may expire after ~30 days + +### Rate limiting +- Script runs every 2 minutes — shouldn't hit rate limits +- But if user manually refreshes repeatedly, APIs might throttle + +### GNOME Shell compatibility +- Currently targets GNOME 45+ (ESM imports) +- New Shell versions may deprecate APIs (already fixed GNOME 50 messageTray change) +- Test on new GNOME releases before claiming support + +## Done + +- [x] Tooltip on hover — removed (PanelMenu.Button consumes hover events) +- [x] Color panel text — green/yellow/red based on availability +- [x] Click to refresh — panel click triggers refresh +- [x] Notifications — desktop alert on limit hit +- [x] Auto-refresh on wake — monitors systemd PrepareForSleep +- [x] Configurable refresh interval — gsettings (min 30s) +- [x] Settings UI — prefs.js with Extension Manager gear icon +- [x] Parallel fetches — background processes, ~2s total +- [x] Accessibility — screen reader names for all UI elements +- [x] Theme-aware CSS — uses currentColor and opacity +- [x] CI/CD — GitHub Actions with shellcheck, meson build, bats tests +- [x] Documentation — README, CONTRIBUTING, CHANGELOG +- [x] GNOME 50 support — added to shell-version, fixed messageTray API +- [x] Disable Cursor — free tier unsupported, misleading data + +## EGO submission (extensions.gnome.org) + +### Blocking issues + +1. **Scripts must be written in GJS** — Our `freeby.sh` uses bash + curl + python3. The guidelines say scripts "MUST be written in GJS, unless absolutely necessary." Options: + - **A) Rewrite in GJS** — Use Soup.Session for HTTP, native JSON.parse(). Cleanest for review but significant rewrite (~200 lines GJS replacing ~190 lines bash/python). Still needs subprocess for `npx codex-check`. + - **B) Keep bash, drop python3** — Replace python3 JSON parsing with grep/sed/awk. Removes one dependency but adds ~150 lines of fragile bash. Strengthens "scripts are necessary" argument. + - **C) Keep as-is** — Argue bash is necessary because we call system tools (npx, curl). Risky, may be rejected. + +2. **Unnecessary files** — EGO zip should only contain extension files. Currently ships tests/, .github/, CONTRIBUTING.md, CHANGELOG.md, meson.build, build-aux/. + +### Non-blocking fixes needed + +3. **Remove `version` from metadata.json** — Deprecated for EGO (they set it). + +4. **Disconnect button-press-event handler** — `this.connect('button-press-event', ...)` in indicator.js not disconnected in destroy(). + +5. **Cancel subprocess in disable** — If disable called while refresh in-flight, async callback could fire after cleanup. + +### Nice-to-have + +6. ESLint for code style consistency +7. UI following GNOME HIG more closely + +## Future features (v2.0.0+) + +### Cursor paid subscription support +- Re-enable Cursor provider +- Detect paid subscription via `overallLimit > 0` +- Show usage percentage for paid users +- Show "no subscription" for free users with exhausted bonus +- Track `remainingBonus` for free tier status + +### Larger features +- **Historical usage graph** — store daily snapshots and show a small sparkline in the dropdown +- **More providers** — Windsurf, Gemini, Groq, etc. +- **Flatpak support** — detect if tools are installed via Flatpak (different config paths) +- **Custom provider support** — user-added APIs via config + +## Release checklist + +- [ ] Update version in metadata.json +- [ ] Test all providers +- [ ] Verify countdown accuracy +- [ ] Check panel indicator updates +- [ ] Take screenshot for README +- [ ] Create GitHub release with tag diff --git a/plan.md b/plan.md new file mode 100644 index 0000000..4f52b15 --- /dev/null +++ b/plan.md @@ -0,0 +1,210 @@ +# Freeby usage monitor roadmap + +Updated: 2026-09-06. Status: roadmap agreed; implementation milestones have not started. + +## Agreed direction + +Build a reliable native GNOME usage monitor for **Codex, Claude Code, Cursor, +and Copilot**, covering free and paid plans. The first stable release is about +usage: quota windows, reset times, activity history, and model breakdowns where +the provider exposes them. + +Complete and verify the system before introducing a new name or icon. Keep the +current extension UUID and GSettings schema identity throughout these milestones. +Cost breakdowns, provider spending/balances, estimates, and budgets are deferred +to a later phase. Subscription percentages must not be derived from token totals. + +Every completed implementation milestone ends with a conventional commit, a +version tag, and an installable GitHub prerelease. The first stable release must +cover all four providers; earlier prereleases introduce them incrementally. + +## Reference analysis and interface + +The [Claude reference](reference-images/1.jpeg) and +[Codex reference](reference-images/2.png) share this structure: + +1. Provider mark, name, and reported plan. +2. A selector for connected/enabled providers; omit the selector when only one exists. +3. Independently labeled quota windows with percent-used meters and reset countdowns. +4. Seven daily token totals, with today emphasized. +5. Model totals shown as horizontal bars. + +Adapt that hierarchy to GNOME using native St/Clutter widgets, theme-aware colors, +aligned/tabular numerals, restrained separators, visible focus, and accessible +labels. Add a footer with last-successful-update time, refresh, and preferences. +Limit popup height to the monitor work area and scroll content when needed. + +- Quota bars use a fixed 0–100% scale. Activity bars scale against the largest value in the displayed period. +- Daily and model breakdowns must clearly state their period and whether the source is local or account-wide. +- Show data-supported sections only; keep connection errors and recovery guidance visible. +- Distinguish zero activity from unavailable history. Never fabricate models, plans, usage, or historical coverage. +- Show cached data immediately while independently refreshing providers. Mark stale sections with their timestamps. +- Show a useful setup state when no provider is connected. Do not silently hide the extension after a failure. + +## Architecture + +Keep a thin native GNOME extension with modular GJS collection and a versioned +usage contract. Provider code must not manipulate UI actors. UI code must not +parse provider transcripts or depend on provider endpoint response shapes. +Expensive history work belongs in a managed GJS subprocess, not the Shell UI +process. Avoid a persistent background daemon in this first iteration. + +```text +freeby/ +├── AGENTS.md +├── plan.md +├── extension.js # GNOME lifecycle entry +├── prefs.js # Preferences entry +├── metadata.json +├── stylesheet.css +├── src/ +│ ├── core/ # Contract, validation, aggregation, formatting +│ ├── providers/ # Codex, Claude, Cursor, Copilot adapters +│ ├── services/ # Scheduling, cache, processes, notifications +│ ├── collector/ # Managed GJS collection entry +│ ├── ui/ # Indicator, selector, meters, charts +│ └── preferences/ # Configuration pages +├── schemas/ +├── assets/ +├── tests/ +│ ├── unit/ +│ ├── integration/ +│ └── fixtures/ +├── tools/ # Development and packaging commands +├── docs/ # Architecture, provider contracts, release guide +├── reference-images/ # Development references, excluded from archives +├── meson.build +└── .github/workflows/ +``` + +This is a target layout, not a claim that these modules already exist. Move code +in reviewable steps and preserve existing behavior until its replacement is +verified. Keep transitional scripts only while needed; do not ship unused code. + +### Usage contract + +- Include a schema version, stable provider identity, optional account/plan identity, and explicit capabilities. +- Represent each quota window with its unit, reported usage/limit or percentage, duration, reset timestamp, and state. +- Represent activity with dated totals and model aggregates, including provider-specific input/output/cache semantics. +- Track source, account/local scope, measurement period, coverage, and last-successful-update timestamps. +- Track limits and history status independently so local history remains useful when an account endpoint fails. +- Distinguish ready, missing authentication, unsupported data, unavailable data, stale data, and exhausted quota. +- Leave unknown values absent/null; never coerce an empty response to zero or an unlimited allowance to exhaustion. + +### Collection and storage + +- Prefer documented provider interfaces and existing supported CLI authentication; verify versions and account eligibility. +- For Codex, evaluate app-server account/rate-limit reads and available account-usage reads; probe compatibility before using optional methods. +- Use local usage records for supported history/model data; retain explicit scope when account-wide activity is also available. +- Validate Claude, Cursor, and Copilot sources independently. Preserve their real units and advertise only verified capabilities. +- Replace automatic `npx` downloads and the hardcoded helper path with packaged, extension-relative resources. +- Give each provider a deadline, bounded retries/backoff, and independent success/failure handling. +- Parse changed records incrementally; handle repeated events, cumulative counters, partial writes, rotation, and timezone boundaries. +- Keep private usage metadata in XDG state/cache locations using atomic writes, bounded retention, and schema migrations. +- Do not retain credentials, prompts, responses, or complete transcripts in Freeby's history or diagnostics. + +## Known regressions to address + +The preceding repository analysis identified these issues; add focused regression +coverage during the foundation work: + +- Notification construction uses the tray instance as a constructor namespace. +- The sleep signal condition refreshes before suspend rather than on resume. +- An in-flight refresh can apply data after the indicator is destroyed. +- Repeated refreshes can leave overlapping collection processes. +- Empty Codex/Cursor objects can display successful zero usage. +- Summary-text matching can show an exhausted Cursor quota as a green row. +- Initial/error states can trigger incorrect quota-limit notifications. +- The Codex `npx` path has no explicit collection deadline. +- The uninstall instructions remove the shared compiled schema file instead of recompiling remaining schemas. + +## Implementation milestones + +### 1. Foundation and Codex — v2.0.0-alpha.1 + +- [ ] Establish the target module boundaries, versioned contract, development commands, and fixture-based tests. +- [ ] Repair the known lifecycle, notification, quota-state, and install/uninstall regressions. +- [ ] Implement bounded Codex collection, cached results, available quota windows, daily activity, and model totals. +- [ ] Build the native provider header, quota meters, activity/model views, status footer, and loading/error/setup states. +- [ ] Keep existing provider behavior working until deliberately migrated; document any verified data limitations. +- [ ] Verify the complete Codex path, temporary installation, disable/re-enable cleanup, and offline/partial behavior. +- [ ] Commit, tag, publish, and verify the installable alpha.1 prerelease. + +### 2. Claude Code — v2.0.0-alpha.2 + +- [ ] Implement Claude detection, supported authentication/limits, and incremental usage history. +- [ ] Add provider switching and independent partial-data handling using the shared contract. +- [ ] Verify quota windows, daily/model totals, duplicate handling, missing credentials, and expired authentication. +- [ ] Commit, tag, publish, and verify the installable alpha.2 prerelease. + +### 3. Cursor — v2.0.0-alpha.3 + +- [ ] Revalidate current Cursor authentication and usage sources for free and paid accounts. +- [ ] Handle paid allowance, remaining bonus, absent subscription, and unsupported metrics without fabricated percentages. +- [ ] Add all verified usage capabilities to the shared interface and document integration limits. +- [ ] Verify fixtures for empty responses, expired authentication, inaccessible endpoints, and quota exhaustion. +- [ ] Commit, tag, publish, and verify the installable alpha.3 prerelease. + +### 4. Copilot — v2.0.0-alpha.4 + +- [ ] Revalidate current Copilot authentication, entitlement data, and usage units for supported account types. +- [ ] Implement usage collection and available history/model metrics without assuming every account exposes them. +- [ ] Keep request counts, tokens, allowances, and unknown/unlimited states distinct. +- [ ] Verify all four providers together, including partial outages and missing integrations. +- [ ] Commit, tag, publish, and verify the installable alpha.4 prerelease. + +### 5. Preferences and interface polish — v2.0.0-alpha.5 + +- [ ] Expand the existing preferences entry into organized native libadwaita pages. +- [ ] Add left/center/right panel placement and ordering within the selected panel area, with immediate updates. +- [ ] Add provider enablement/order, default provider, refresh controls, and connection diagnostics. +- [ ] Add notification thresholds, deduplication per account/window, and history retention/clear controls. +- [ ] Preserve valid existing settings and document new defaults and migrations. +- [ ] Verify keyboard navigation, screen-reader labels, dark/light themes, long content, scrolling, and display scaling. +- [ ] Commit, tag, publish, and verify the installable alpha.5 prerelease using the existing name and icon. + +### 6. Stabilization — v2.0.0-beta.1, then v2.0.0 + +- [ ] Validate supported GNOME versions; keep metadata aligned with tested compatibility. +- [ ] Verify history accuracy, bounded collection, responsiveness, and all four provider capability declarations. +- [ ] Test clean install, upgrade, disable/re-enable, uninstall, and downgrade with an isolated installation prefix. +- [ ] Finalize README, setup/troubleshooting, architecture/provider docs, screenshots, and release instructions. +- [ ] Commit, tag, publish, and verify beta.1; issue further beta prereleases when feedback requires changes. +- [ ] Resolve release-blocking feedback and publish the first stable usage-monitoring release. + +## Verification and release gate + +Every milestone requires applicable lint/syntax checks, deterministic unit and +integration tests, strict schema validation, and a packaged-install smoke test. +GUI/lifecycle changes also require relevant GNOME desktop checks. Live provider +verification is separate from credential-free CI and must identify which account +types and client versions were actually tested. + +1. Confirm the intended repository and release branch; preserve unrelated worktree changes. +2. Update this plan, the changelog, and the project release version. Do not use the deprecated metadata `version` field as the release source of truth. +3. Complete the milestone checks and record results, remaining limitations, and any unavailable checks. +4. Commit only the intended, tested milestone changes using conventional commits. +5. Tag the commit and build the downloadable extension archive from that exact revision. Exclude tests, development tools, references, and credentials from the runtime archive. +6. Publish the GitHub release as a prerelease for alpha/beta tags, with installation/upgrade instructions, verification evidence, known limitations, and a downgrade path. +7. Verify the remote tag, release status, downloadable artifact, and archive contents; record the tag/commit/release link here. + +Do not mark a milestone complete until its code, checks, commit, and prerelease +are complete. A blocked required check or publication remains explicitly open. +Documentation setup is preparatory work and does not trigger a product prerelease. + +## After usage monitoring is reliable + +- Decide the new name and icon direction, then plan branding and any required identity/settings migration. +- Design cost breakdowns separately, distinguishing provider-reported charges from estimates and including currency, period, and pricing provenance. +- Consider budgets, additional providers, multiple accounts, cross-device history, and a separate dashboard only as later scoped work. + +## References and historical context + +- [Omarchy Agents architecture and panel behavior](https://github.com/omacom/omarchy/blob/quattro/shell/plugins/agents/README.md) +- [Codex app-server documentation](https://learn.chatgpt.com/docs/app-server) +- [GNOME extension review guidelines](https://gjs.guide/extensions/review-guidelines/review-guidelines.html) +- [Archived v1 planning notes](docs/planning/v1-notes.md), including a Cursor no-subscription investigation that requires revalidation. + +Sources describe the state inspected during planning; verify them again before +depending on a provider interface or publishing compatibility claims. If upstream +code or assets are reused, preserve the applicable license and attribution. From a280d8b33e7c2bb0d09982055832342f6a63d3b9 Mon Sep 17 00:00:00 2001 From: kcnewman Date: Sun, 6 Sep 2026 08:17:08 +0000 Subject: [PATCH 13/44] feat: add Codex usage monitoring foundation --- .editorconfig | 12 ++ .github/workflows/ci.yml | 38 ++--- extension.js | 19 ++- meson.build | 11 +- metadata.json | 4 +- package.json | 16 ++ ....gnome.shell.extensions.freeby.gschema.xml | 19 +++ src/collector/main.js | 65 ++++++++ src/core/format.js | 31 ++++ src/core/notifications.js | 24 +++ src/core/usage.js | 140 ++++++++++++++++ src/providers/codex.js | 88 ++++++++++ src/providers/legacy.js | 45 +++++ src/services/files.js | 47 ++++++ src/services/history.js | 135 +++++++++++++++ src/services/http.js | 21 +++ src/services/process.js | 137 +++++++++++++++ src/services/usageService.js | 115 +++++++++++++ src/ui/indicator.js | 156 ++++++++++++++++++ src/ui/widgets.js | 35 ++++ stylesheet.css | 108 +++--------- tests/integration/collector.js | 65 ++++++++ tests/unit/codex.test.js | 52 ++++++ tests/unit/usage.test.js | 82 +++++++++ tools/check.js | 27 +++ tools/package.py | 42 +++++ tools/smoke-shell.py | 95 +++++++++++ 27 files changed, 1512 insertions(+), 117 deletions(-) create mode 100644 .editorconfig create mode 100644 package.json create mode 100644 src/collector/main.js create mode 100644 src/core/format.js create mode 100644 src/core/notifications.js create mode 100644 src/core/usage.js create mode 100644 src/providers/codex.js create mode 100644 src/providers/legacy.js create mode 100644 src/services/files.js create mode 100644 src/services/history.js create mode 100644 src/services/http.js create mode 100644 src/services/process.js create mode 100644 src/services/usageService.js create mode 100644 src/ui/indicator.js create mode 100644 src/ui/widgets.js create mode 100644 tests/integration/collector.js create mode 100644 tests/unit/codex.test.js create mode 100644 tests/unit/usage.test.js create mode 100644 tools/check.js create mode 100644 tools/package.py create mode 100644 tools/smoke-shell.py diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..183669e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +[*.{json,yml,yaml}] +indent_size = 2 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7c78902..202ad28 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,29 +7,23 @@ on: branches: [main, dev] jobs: - shellcheck: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Install shellcheck - run: sudo apt-get install -y shellcheck - - name: Lint shell scripts - run: shellcheck scripts/freeby.sh scripts/copilot-setup.sh - - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Install dependencies - run: sudo apt-get install -y meson ninja-build libglib2.0-dev-bin python3 - - name: Build - run: meson setup build --prefix=$HOME/.local && meson install -C build - - test: + verify: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 - name: Install dependencies - run: sudo apt-get install -y bats python3 curl - - name: Run tests - run: bats tests/ + run: sudo apt-get update && sudo apt-get install -y gjs gir1.2-soup-3.0 libglib2.0-dev-bin meson ninja-build python3 + - name: Verify JavaScript, schemas, fixtures, and package + run: npm run verify + - name: Verify isolated install + run: | + meson setup build --prefix="$RUNNER_TEMP/freeby-install" + meson install -C build + test -f "$RUNNER_TEMP/freeby-install/share/gnome-shell/extensions/freeby@kelvin.local/schemas/gschemas.compiled" + - uses: actions/upload-artifact@v4 + with: + name: freeby-extension + path: dist/*.zip diff --git a/extension.js b/extension.js index 00aae09..1bbc5e7 100644 --- a/extension.js +++ b/extension.js @@ -1,16 +1,29 @@ import { Extension } from 'resource:///org/gnome/shell/extensions/extension.js'; import * as Main from 'resource:///org/gnome/shell/ui/main.js'; -import { FreebyIndicator } from './indicator.js'; +import {FreebyIndicator} from './src/ui/indicator.js'; +import {UsageService} from './src/services/usageService.js'; export default class FreebyExtension extends Extension { enable() { this._settings = this.getSettings('org.gnome.shell.extensions.freeby'); - this._settingsId = this._settings.connect('changed::refresh-interval', () => this._indicator?._setupTimer()); - this._indicator = new FreebyIndicator(this._settings); + this._indicator = new FreebyIndicator(this._settings, () => this.openPreferences()); Main.panel.addToStatusArea(this.uuid, this._indicator); + this._service = new UsageService(this._settings, this.path, () => this._indicator?.render(), alerts => { + for (const alert of alerts) + Main.notify('Freeby usage alert', `${alert.provider}: ${alert.label} ${alert.threshold === 100 ? 'limit reached' : `reached ${alert.threshold}%`}.`); + }); + this._indicator.attach(this._service); + this._settingsId = this._settings.connect('changed', () => { + this._service.configure(); + this._indicator.render(); + this._service.refreshAll(); + }); + this._service.refreshAll(); } disable() { if (this._settingsId) { this._settings.disconnect(this._settingsId); this._settingsId = null; } + this._service?.destroy(); + this._service = null; this._indicator?.destroy(); this._indicator = null; this._settings = null; diff --git a/meson.build b/meson.build index 6af6f8f..6c0968f 100644 --- a/meson.build +++ b/meson.build @@ -1,19 +1,16 @@ -project('freeby', version: '1.0.2', license: 'MIT', +project('freeby', version: '2.0.0-alpha.1', license: 'MIT', meson_version: '>= 0.56.0', default_options: ['warning_level=0']) uuid = 'freeby@kelvin.local' prefix = get_option('prefix') extdir = join_paths(prefix, 'share', 'gnome-shell', 'extensions', uuid) -bindir = join_paths(prefix, 'bin') -schemadir = join_paths(prefix, 'share', 'glib-2.0', 'schemas') +schemadir = join_paths(extdir, 'schemas') -install_data('extension.js', 'indicator.js', 'prefs.js', 'metadata.json', 'stylesheet.css', +install_data('extension.js', 'prefs.js', 'metadata.json', 'stylesheet.css', 'LICENSE', install_dir: extdir) -install_data('scripts/freeby.sh', 'scripts/copilot-setup.sh', - install_dir: bindir, - install_mode: 'rwxr-xr-x') +install_subdir('src', install_dir: extdir) install_data('schemas/org.gnome.shell.extensions.freeby.gschema.xml', install_dir: schemadir) diff --git a/metadata.json b/metadata.json index f131767..6febcdd 100644 --- a/metadata.json +++ b/metadata.json @@ -1,8 +1,8 @@ { "uuid": "freeby@kelvin.local", "name": "Freeby", - "description": "Minimal panel indicator for local AI coding tool usage (Codex, Cursor, Copilot)", - "shell-version": ["45", "46", "47", "48", "49", "50"], + "description": "AI coding usage monitor for account limits, reset windows, local token activity and model breakdowns. Codex is verified; additional providers arrive through version 2 prereleases.", + "shell-version": ["50"], "settings-schema": "org.gnome.shell.extensions.freeby", "url": "https://github.com/kcnewman/freeby" } diff --git a/package.json b/package.json new file mode 100644 index 0000000..efe5214 --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "freeby", + "version": "2.0.0-alpha.1", + "private": true, + "type": "module", + "scripts": { + "test": "node --test tests/unit/*.test.js", + "lint": "node tools/check.js", + "test:integration": "gjs -m tests/integration/collector.js", + "check": "npm run lint && npm test && npm run test:integration", + "pack": "python3 tools/package.py", + "verify": "npm run check && npm run pack", + "smoke:shell": "python3 tools/smoke-shell.py" + }, + "engines": { "node": ">=20" } +} diff --git a/schemas/org.gnome.shell.extensions.freeby.gschema.xml b/schemas/org.gnome.shell.extensions.freeby.gschema.xml index 4af8f75..982981b 100644 --- a/schemas/org.gnome.shell.extensions.freeby.gschema.xml +++ b/schemas/org.gnome.shell.extensions.freeby.gschema.xml @@ -13,5 +13,24 @@ Enable notifications Show desktop notifications when a provider hits its limit + + ['codex'] + Enabled providers in display order + + + 'codex' + Selected usage provider + + + 30 + + Days of usage metadata to retain + + + 90 + + Usage percentage that triggers a warning + + diff --git a/src/collector/main.js b/src/collector/main.js new file mode 100644 index 0000000..fd7b87a --- /dev/null +++ b/src/collector/main.js @@ -0,0 +1,65 @@ +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GLibUnix from 'gi://GLibUnix'; +import System from 'system'; +import {record, section} from '../core/usage.js'; +import {collectCodex} from '../providers/codex.js'; +import {collectLegacy} from '../providers/legacy.js'; +import {fingerprint, findCommand, join, readJson} from '../services/files.js'; +import {scanHistory} from '../services/history.js'; +import {requestJson} from '../services/http.js'; +import {RpcClient, runCommand} from '../services/process.js'; + +const id = ARGV[0]; +if (!['codex', 'cursor', 'copilot'].includes(id)) { + printerr('Usage: gjs -m src/collector/main.js [retention-days]'); + System.exit(2); +} +const retention = Math.max(7, Math.min(90, Number(ARGV[1]) || 30)); +const cancellable = new Gio.Cancellable(); +const loop = new GLib.MainLoop(null, false); +const signal = GLibUnix.signal_add(GLib.PRIORITY_DEFAULT, 15, () => { + cancellable.cancel(); + return GLib.SOURCE_CONTINUE; +}); +const io = { + fingerprint, + scan(provider, suffixes, parser) { + const root = GLib.getenv('CODEX_HOME') || join(GLib.get_home_dir(), '.codex'); + return scanHistory(provider, suffixes.map(suffix => join(root, suffix)), parser, {retention}); + }, + codexClient: () => new RpcClient([findCommand('codex'), 'app-server'], cancellable), + http: (url, options) => requestJson(url, {...options, cancellable}), + async token(provider) { + if (provider === 'cursor') { + const auth = readJson(join(GLib.get_user_config_dir(), 'cursor', 'auth.json')); + return auth?.accessToken || null; + } + try { + const value = await runCommand([findCommand('gh'), 'auth', 'token'], {timeout: 5000, cancellable}); + if (value.trim()) + return value.trim(); + } catch { /* Standalone setup token is also supported. */ } + try { + const [, bytes] = Gio.File.new_for_path(join(GLib.get_user_config_dir(), 'freeby', 'copilot-token')).load_contents(null); + return new TextDecoder().decode(bytes).trim(); + } catch { return null; } + }, +}; + +(async () => { + try { + const result = id === 'codex' ? await collectCodex(io) : await collectLegacy(id, io); + if (!cancellable.is_cancelled()) + print(JSON.stringify(result)); + } catch { + const result = record(id); + result.limits = {...result.limits, ...section('unavailable', 'Could not collect usage. Retry or check provider sign-in.')}; + if (!cancellable.is_cancelled()) + print(JSON.stringify(result)); + } finally { + GLib.source_remove(signal); + loop.quit(); + } +})(); +loop.run(); diff --git a/src/core/format.js b/src/core/format.js new file mode 100644 index 0000000..6c889aa --- /dev/null +++ b/src/core/format.js @@ -0,0 +1,31 @@ +export function tokens(value) { + if (!Number.isFinite(value)) + return '—'; + for (const [unit, divisor] of [['B', 1e9], ['M', 1e6], ['K', 1e3]]) { + if (value >= divisor) + return `${(value / divisor).toFixed(1)}${unit}`; + } + return String(Math.round(value)); +} + +export function resetTime(time, now = Date.now()) { + if (!Number.isFinite(time) || time <= 0) + return 'Reset time unavailable'; + const minutes = Math.ceil((time - now) / 60000); + if (minutes <= 0) + return 'Reset due · awaiting update'; + if (minutes >= 1440) + return `Resets in ${Math.floor(minutes / 1440)}d ${Math.floor(minutes % 1440 / 60)}h`; + if (minutes >= 60) + return `Resets in ${Math.floor(minutes / 60)}h ${minutes % 60}m`; + return `Resets in ${minutes}m`; +} + +export function age(time, now = Date.now()) { + if (!time) + return 'Never updated'; + const minutes = Math.max(0, Math.floor((now - time) / 60000)); + if (!minutes) + return 'Updated just now'; + return minutes < 60 ? `Updated ${minutes}m ago` : `Updated ${Math.floor(minutes / 60)}h ago`; +} diff --git a/src/core/notifications.js b/src/core/notifications.js new file mode 100644 index 0000000..0ebb512 --- /dev/null +++ b/src/core/notifications.js @@ -0,0 +1,24 @@ +export class ThresholdTracker { + constructor() { + this.previous = new Map(); + } + + update(record, threshold = 90) { + if (record.limits.status !== 'ready') + return []; + const alerts = []; + for (const window of record.limits.windows) { + if (window.unlimited || !Number.isFinite(window.usedPercent)) + continue; + const key = `${record.id}:${record.accountKey ?? 'default'}:${window.id}:${window.resetsAt ?? 'unknown'}`; + const previous = this.previous.get(key); + const reached = window.usedPercent >= 100 ? 100 : window.usedPercent >= threshold ? threshold : 0; + if (previous !== undefined && reached > previous) + alerts.push({provider: record.name, label: window.label, threshold: reached}); + this.previous.set(key, Math.max(previous ?? 0, reached)); + } + if (this.previous.size > 200) + this.previous.delete(this.previous.keys().next().value); + return alerts; + } +} diff --git a/src/core/usage.js b/src/core/usage.js new file mode 100644 index 0000000..d1a95cd --- /dev/null +++ b/src/core/usage.js @@ -0,0 +1,140 @@ +export const SCHEMA_VERSION = 1; +export const NAMES = {codex: 'Codex', cursor: 'Cursor', copilot: 'Copilot'}; +export const STATES = new Set(['loading', 'ready', 'partial', 'stale', 'missing-auth', 'unsupported', 'unavailable']); +export const WINDOW_STATES = new Set(['active', 'exhausted', 'unlimited']); + +export function number(value) { + if (value === null || value === undefined || value === '' || typeof value === 'boolean') + return null; + const result = Number(value); + return Number.isFinite(result) && result >= 0 ? result : null; +} + +export function section(status = 'loading', message = '') { + return {status, message, updatedAt: null}; +} + +export function record(id) { + return { + schemaVersion: SCHEMA_VERSION, id, name: NAMES[id] ?? id, plan: null, accountKey: null, + capabilities: {limits: false, history: false, models: false}, + limits: {...section(), scope: 'account', windows: []}, + history: {...section('unsupported', 'This provider does not expose local token history.'), + scope: 'local', period: null, days: [], models: [], source: null}, + }; +} + +export function windowUsage({id, label, usedPercent, used, limit, unit = 'percent', durationMinutes = null, + resetsAt = null, unlimited = false}) { + let percent = number(usedPercent); + used = number(used); + limit = number(limit); + if (percent === null && used !== null && limit > 0) + percent = used / limit * 100; + if (percent === null && !unlimited) + return null; + const normalizedPercent = unlimited ? null : percent; + return {id, label, usedPercent: normalizedPercent, used, limit, unit, unlimited, + state: unlimited ? 'unlimited' : normalizedPercent >= 100 ? 'exhausted' : 'active', + durationMinutes: number(durationMinutes), resetsAt: validTime(resetsAt)}; +} + +export function validTime(value) { + if (value === null || value === undefined || value === '') + return null; + const ms = typeof value === 'number' ? value : Date.parse(value); + return Number.isFinite(ms) && ms > 0 ? ms : null; +} + +export function validateRecord(value, expectedId) { + if (!value || value.schemaVersion !== SCHEMA_VERSION || value.id !== expectedId || !NAMES[value.id]) + throw new Error('Unsupported usage record'); + if (!value.capabilities || ['limits', 'history', 'models'].some(name => typeof value.capabilities[name] !== 'boolean')) + throw new Error('Invalid provider capabilities'); + for (const name of ['limits', 'history']) { + if (!value[name] || !STATES.has(value[name].status)) + throw new Error(`Invalid ${name} state`); + } + if (!Array.isArray(value.limits.windows) || !Array.isArray(value.history.days) || !Array.isArray(value.history.models)) + throw new Error('Invalid usage arrays'); + for (const item of value.limits.windows) { + if (typeof item.id !== 'string' || typeof item.label !== 'string' || !WINDOW_STATES.has(item.state) || + (!item.unlimited && number(item.usedPercent) === null)) + throw new Error('Invalid quota window'); + } + for (const item of [...value.history.days, ...value.history.models]) { + if (number(item.total) === null) + throw new Error('Invalid token count'); + } + return value; +} + +export function mergeRecord(previous, next) { + if (!previous || (previous.accountKey && next.accountKey && previous.accountKey !== next.accountKey)) + return next; + const result = {...next}; + for (const key of ['limits', 'history']) { + const old = previous[key]; + const current = next[key]; + if (['unavailable', 'missing-auth'].includes(current.status) && old?.updatedAt && + ['ready', 'partial', 'stale'].includes(old.status)) { + result[key] = {...old, status: 'stale', message: current.message}; + } + } + result.plan ??= previous.plan; + result.accountKey ??= previous.accountKey; + return result; +} + +export function highestUsage(value) { + if (!value || value.limits.status !== 'ready') + return null; + const values = value.limits.windows.filter(w => !w.unlimited && number(w.usedPercent) !== null).map(w => w.usedPercent); + return values.length ? Math.max(...values) : null; +} + +export function localDate(time) { + const d = new Date(time); + if (!Number.isFinite(d.getTime())) + return null; + return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`; +} + +export function recentDates(now, count = 7) { + const today = new Date(now); + today.setHours(12, 0, 0, 0); + return Array.from({length: count}, (_, i) => { + const day = new Date(today); + day.setDate(day.getDate() - (count - 1 - i)); + return localDate(day); + }); +} + +export function aggregateEvents(events, now = Date.now(), count = 7) { + const dates = recentDates(now, count); + const days = new Map(dates.map(date => [date, {date, total: 0, sessions: 0, events: 0}])); + const models = new Map(); + const seen = new Set(); + const sessions = new Map(dates.map(date => [date, new Set()])); + for (const event of events) { + const day = days.get(event.date); + if (!day || seen.has(event.id)) + continue; + seen.add(event.id); + const tokens = ['input', 'output', 'cacheRead', 'cacheWrite'].map(k => number(event[k]) ?? 0); + const total = tokens.reduce((a, b) => a + b, 0); + if (!total) + continue; + const model = String(event.model || 'Unknown model'); + const bucket = models.get(model) ?? {model, total: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0}; + ['input', 'output', 'cacheRead', 'cacheWrite'].forEach((k, i) => { bucket[k] += tokens[i]; }); + bucket.total += total; + models.set(model, bucket); + day.total += total; + day.events++; + sessions.get(event.date).add(event.session); + day.sessions = sessions.get(event.date).size; + } + return {period: {start: dates[0], end: dates.at(-1)}, days: [...days.values()], + models: [...models.values()].sort((a, b) => b.total - a.total)}; +} diff --git a/src/providers/codex.js b/src/providers/codex.js new file mode 100644 index 0000000..3dcb939 --- /dev/null +++ b/src/providers/codex.js @@ -0,0 +1,88 @@ +import {localDate, number, record, section, windowUsage} from '../core/usage.js'; + +export function codexLimits(response, now = Date.now()) { + const buckets = response?.rateLimitsByLimitId && typeof response.rateLimitsByLimitId === 'object' + ? Object.entries(response.rateLimitsByLimitId) + : [['codex', response?.rateLimits]]; + const windows = []; + for (const [bucketId, bucket] of buckets) { + for (const key of ['primary', 'secondary']) { + const w = bucket?.[key]; + if (!w) + continue; + const mins = number(w.windowDurationMins); + const duration = mins === 10080 ? 'Weekly' : mins === 300 ? 'Session · 5 hours' + : mins ? `${mins >= 60 ? `${mins / 60} hours` : `${mins} minutes`}` : key === 'primary' ? 'Primary window' : 'Secondary window'; + const item = windowUsage({id: `${bucketId}:${key}`, + label: buckets.length > 1 ? `${bucket.limitName || bucketId} · ${duration}` : duration, + usedPercent: w.usedPercent, durationMinutes: mins, + resetsAt: number(w.resetsAt) ? Number(w.resetsAt) * 1000 : null}); + if (item) + windows.push(item); + } + } + return windows.length + ? {...section('ready'), updatedAt: now, scope: 'account', windows} + : {...section('unavailable', 'Codex did not report quota windows for this account.'), scope: 'account', windows: []}; +} + +export function parseCodexEvent(entry, state) { + if (entry?.type === 'session_meta') + state.session = entry.payload?.id || state.session; + if (entry?.type === 'turn_context') + state.model = entry.payload?.model || entry.payload?.model_slug || state.model; + const payload = entry?.payload; + if (payload?.type !== 'token_count' || !payload.info) + return null; + const cumulative = payload.info.total_token_usage; + let usage = payload.info.last_token_usage; + let identity; + if (cumulative && number(cumulative.total_tokens) !== null) { + const total = Number(cumulative.total_tokens); + if (state.cumulative && total <= state.cumulative.total_tokens) + return null; + const previous = state.cumulative ?? {}; + usage = Object.fromEntries(['input_tokens', 'output_tokens', 'cached_input_tokens', 'cache_write_input_tokens'] + .map(k => [k, Math.max(0, (number(cumulative[k]) ?? 0) - (number(previous[k]) ?? 0))])); + state.cumulative = cumulative; + identity = `total:${total}`; + } else { + identity = `${entry.timestamp}:${JSON.stringify(usage)}`; + } + if (!usage || !localDate(entry.timestamp)) + return null; + const input = number(usage.input_tokens) ?? 0; + const cacheRead = Math.min(input, number(usage.cached_input_tokens) ?? 0); + const cacheWrite = Math.min(input - cacheRead, number(usage.cache_write_input_tokens) ?? 0); + return {id: `${state.session}:${identity}`, session: state.session, date: localDate(entry.timestamp), + model: state.model || 'Unknown model', input: input - cacheRead - cacheWrite, + output: number(usage.output_tokens) ?? 0, cacheRead, cacheWrite}; +} + +export async function collectCodex(io) { + const result = record('codex'); + result.capabilities = {limits: true, history: true, models: true}; + result.history = io.scan('codex', ['sessions', 'archived_sessions'], parseCodexEvent); + let rpc; + try { + rpc = io.codexClient(); + await rpc.request('initialize', {clientInfo: {name: 'freeby', version: '2.0.0'}, capabilities: {experimentalApi: false}}); + rpc.notify('initialized', {}); + const account = await rpc.request('account/read', {refreshToken: false}); + if (!account?.account) { + result.limits = {...result.limits, ...section('missing-auth', 'Sign in with codex login to read account limits.')}; + return result; + } + result.plan = account.account.planType ?? account.account.type ?? null; + result.accountKey = io.fingerprint(account.account.email || account.account.chatgptAccountId || result.plan); + const limits = await rpc.request('account/rateLimits/read', {}); + result.limits = {...codexLimits(limits), source: 'Codex app-server'}; + result.plan = limits?.rateLimits?.planType ?? result.plan; + } catch (error) { + result.limits = {...result.limits, ...section(error.code === 'NOT_FOUND' ? 'unsupported' : 'unavailable', + error.code === 'NOT_FOUND' ? 'Install the Codex CLI to read account limits.' : 'Could not read Codex limits. Check CLI sign-in and compatibility.')}; + } finally { + rpc?.close(); + } + return result; +} diff --git a/src/providers/legacy.js b/src/providers/legacy.js new file mode 100644 index 0000000..a3b0659 --- /dev/null +++ b/src/providers/legacy.js @@ -0,0 +1,45 @@ +import {number, record, section, windowUsage} from '../core/usage.js'; + +export async function collectLegacy(id, io) { + const result = record(id); + result.capabilities.limits = true; + const token = await io.token(id); + if (!token) { + result.limits = {...result.limits, ...section('missing-auth', `Sign in to ${result.name} to read usage.`)}; + return result; + } + const response = id === 'cursor' + ? await io.http('https://api2.cursor.sh/aiserver.v1.DashboardService/GetCurrentPeriodUsage', + {method: 'POST', headers: {Authorization: `Bearer ${token}`, 'Connect-Protocol-Version': '1'}, body: {}}) + : await io.http('https://api.github.com/copilot_internal/user', {headers: {Authorization: `Bearer ${token}`, Accept: 'application/json'}}); + const {status, data} = response; + if (status !== 200 || !data || typeof data !== 'object') { + result.limits = {...result.limits, ...section([401, 403].includes(status) ? 'missing-auth' : 'unavailable', + [401, 403].includes(status) ? `Reconnect ${result.name} to read usage.` : `Usage endpoint unavailable (HTTP ${status}).`)}; + return result; + } + if (id === 'cursor') { + const noPlan = number(data.spendLimitUsage?.overallLimit) === 0; + const percent = noPlan ? null : number(data.planUsage?.totalPercentUsed); + const window = windowUsage({id: 'plan', label: 'Included usage', usedPercent: percent, + resetsAt: number(data.billingCycleEnd)}); + if (window) + result.limits.windows.push(window); + } else { + for (const [key, quota] of Object.entries(data.quota_snapshots ?? {})) { + const limit = number(quota.entitlement); + const remaining = number(quota.remaining); + const item = windowUsage({id: key, label: key.replaceAll('_', ' '), unit: 'requests', limit, + used: limit !== null && remaining !== null ? Math.max(0, limit - remaining) : null, + unlimited: quota.unlimited === true, resetsAt: data.quota_reset_date_utc}); + if (item) + result.limits.windows.push(item); + } + result.plan = data.copilot_plan ?? null; + } + result.limits = {...result.limits, ...section(result.limits.windows.length ? 'ready' : 'unavailable', + result.limits.windows.length ? '' : 'No supported allowance was reported. Check the provider dashboard.'), + updatedAt: result.limits.windows.length ? Date.now() : null}; + result.accountKey = io.fingerprint(token); + return result; +} diff --git a/src/services/files.js b/src/services/files.js new file mode 100644 index 0000000..0351529 --- /dev/null +++ b/src/services/files.js @@ -0,0 +1,47 @@ +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; + +export const join = (...parts) => GLib.build_filenamev(parts); +export const fingerprint = value => GLib.compute_checksum_for_string(GLib.ChecksumType.SHA256, String(value), -1); + +export function readJson(path, fallback = null, maxBytes = 16 * 1024 * 1024) { + try { + const file = Gio.File.new_for_path(path); + if (file.query_info('standard::size', Gio.FileQueryInfoFlags.NONE, null).get_size() > maxBytes) + return fallback; + const [, bytes] = file.load_contents(null); + return JSON.parse(new TextDecoder().decode(bytes)); + } catch { + return fallback; + } +} + +export function writeJson(path, value) { + const file = Gio.File.new_for_path(path); + const parent = file.get_parent(); + GLib.mkdir_with_parents(parent.get_path(), 0o700); + file.replace_contents(new TextEncoder().encode(JSON.stringify(value)), null, false, + Gio.FileCreateFlags.PRIVATE | Gio.FileCreateFlags.REPLACE_DESTINATION, null); +} + +export function findCommand(name) { + const found = GLib.find_program_in_path(name); + if (found) + return found; + for (const directory of ['.local/bin', '.cargo/bin', '.npm-global/bin']) { + const path = join(GLib.get_home_dir(), directory, name); + if (GLib.file_test(path, GLib.FileTest.IS_EXECUTABLE)) + return path; + } + const error = new Error(`${name} is not installed`); + error.code = 'NOT_FOUND'; + throw error; +} + +export function stateDirectory() { + return join(GLib.get_user_state_dir(), 'freeby'); +} + +export function cacheDirectory() { + return join(GLib.get_user_cache_dir(), 'freeby'); +} diff --git a/src/services/history.js b/src/services/history.js new file mode 100644 index 0000000..1c8e406 --- /dev/null +++ b/src/services/history.js @@ -0,0 +1,135 @@ +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import {aggregateEvents, recentDates, section} from '../core/usage.js'; +import {cacheDirectory, fingerprint, join, readJson, writeJson} from './files.js'; + +function listFiles(root, output, depth = 0) { + if (depth > 12 || output.length > 20000) + throw new Error('History directory exceeds scan bounds'); + const file = Gio.File.new_for_path(root); + if (!file.query_exists(null)) + return false; + const iterator = file.enumerate_children('standard::name,standard::type,standard::is-symlink,standard::size,time::modified', + Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null); + try { + let info; + while ((info = iterator.next_file(null))) { + if (info.get_is_symlink()) + continue; + const path = join(root, info.get_name()); + if (info.get_file_type() === Gio.FileType.DIRECTORY) + listFiles(path, output, depth + 1); + else if (info.get_name().endsWith('.jsonl')) + output.push({path, size: info.get_size(), mtime: info.get_attribute_uint64('time::modified')}); + } + } finally { + iterator.close(null); + } + return true; +} + +export function scanHistory(id, roots, parse, {retention = 30, now = Date.now(), cachePath = null} = {}) { + const cutoff = recentDates(now, retention)[0]; + const destination = cachePath ?? join(cacheDirectory(), `history-${id}.json`); + let cached = readJson(destination, {}, 64 * 1024 * 1024); + const identity = fingerprint(roots.join('\n')); + if (cached.version !== 2 || cached.identity !== identity || typeof cached.files !== 'object') + cached = {version: 2, identity, files: {}}; + const files = []; + let detected = false; + let partial = false; + let parsed = 0; + const started = GLib.get_monotonic_time(); + for (const root of roots) { + try { detected = listFiles(root, files) || detected; } catch { partial = true; } + } + for (const file of files) { + if ((GLib.get_monotonic_time() - started) / 1000000 > 20) { + partial = true; + break; + } + const key = fingerprint(file.path); + let previous = cached.files[key]; + if (previous?.size === file.size && previous?.mtime === file.mtime) + continue; + // Changed or truncated files are rebuilt; append-only files resume at the last complete line. + if (!previous || file.size <= previous.size) + previous = {offset: 0, state: {session: key}, events: {}}; + let input; + try { + input = Gio.File.new_for_path(file.path).read(null); + input.seek(previous.offset, GLib.SeekType.SET, null); + let offset = previous.offset; + let tail = new Uint8Array(); + let stop = false; + while (!stop) { + const bytes = input.read_bytes(65536, null).toArray(); + if (!bytes.length) + break; + const buffer = new Uint8Array(tail.length + bytes.length); + buffer.set(tail); + buffer.set(bytes, tail.length); + let start = 0; + for (let i = 0; i < buffer.length; i++) { + if (buffer[i] !== 10) + continue; + const line = buffer.subarray(start, i); + offset += i - start + 1; + start = i + 1; + try { + const oldSession = previous.state.session; + const event = parse(JSON.parse(new TextDecoder().decode(line)), previous.state); + // Provider session identifiers are useful only for + // deduplication. Hash them before either parser state or + // derived events reach Freeby's private cache. + if (previous.state.session !== oldSession) + previous.state.session = fingerprint(`${id}:${previous.state.session}`); + if (event && event.date >= cutoff) { + const sanitized = {...event, + id: fingerprint(`${id}:${event.id}`), + session: fingerprint(`${id}:${event.session ?? event.id}`)}; + previous.events[sanitized.id] = sanitized; + } + } catch { partial = true; } + } + tail = buffer.slice(start); + if (tail.length > 4 * 1024 * 1024 || (GLib.get_monotonic_time() - started) / 1000000 > 20) { + partial = true; + stop = true; + } + } + previous.offset = offset; + // A time-limited scan must resume even if the source file has not changed. + previous.size = stop ? -1 : file.size; + previous.mtime = file.mtime; + cached.files[key] = previous; + parsed++; + } catch { partial = true; } finally { input?.close(null); } + } + const events = []; + for (const [key, file] of Object.entries(cached.files)) { + if (!file.events || typeof file.events !== 'object') { + delete cached.files[key]; + partial = true; + continue; + } + for (const [eventKey, event] of Object.entries(file.events)) { + if (event.date < cutoff) + delete file.events[eventKey]; + else + events.push(event); + } + } + try { writeJson(destination, cached); } catch { partial = true; } + const result = {...section(detected ? partial ? 'partial' : 'ready' : 'unsupported', + partial ? 'Some local records could not be read. Totals may be incomplete.' : + detected ? 'Local records only; activity on other devices is not included.' : 'No local usage records found.'), + updatedAt: detected ? now : null, scope: 'local', source: `${id} local usage records`, + ...aggregateEvents(events, now), scannedFiles: parsed}; + if (!detected && !events.length) { + result.days = []; + result.models = []; + result.period = null; + } + return result; +} diff --git a/src/services/http.js b/src/services/http.js new file mode 100644 index 0000000..da349fd --- /dev/null +++ b/src/services/http.js @@ -0,0 +1,21 @@ +import GLib from 'gi://GLib'; +import Soup from 'gi://Soup?version=3.0'; + +export async function requestJson(url, {method = 'GET', headers = {}, body = null, cancellable = null} = {}) { + const session = new Soup.Session({timeout: 12}); + const message = Soup.Message.new(method, url); + for (const [key, value] of Object.entries(headers)) + message.request_headers.append(key, value); + if (body !== null) + message.set_request_body_from_bytes('application/json', new GLib.Bytes(new TextEncoder().encode(JSON.stringify(body)))); + try { + const bytes = await new Promise((resolve, reject) => session.send_and_read_async(message, GLib.PRIORITY_DEFAULT, cancellable, (s, res) => { + try { resolve(s.send_and_read_finish(res)); } catch (error) { reject(error); } + })); + let data = null; + try { data = JSON.parse(new TextDecoder().decode(bytes.toArray())); } catch { /* Status remains available for error handling. */ } + return {status: message.status_code, data}; + } finally { + session.abort(); + } +} diff --git a/src/services/process.js b/src/services/process.js new file mode 100644 index 0000000..965fc10 --- /dev/null +++ b/src/services/process.js @@ -0,0 +1,137 @@ +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; + +export async function runCommand(argv, {input = null, timeout = 15000, cancellable = null} = {}) { + const proc = Gio.Subprocess.new(argv, Gio.SubprocessFlags.STDOUT_PIPE | + Gio.SubprocessFlags.STDERR_SILENCE | (input === null ? 0 : Gio.SubprocessFlags.STDIN_PIPE)); + const local = new Gio.Cancellable(); + const externalId = cancellable?.connect(() => local.cancel()); + if (cancellable?.is_cancelled()) + local.cancel(); + let timer = GLib.timeout_add(GLib.PRIORITY_DEFAULT, timeout, () => { + timer = 0; + local.cancel(); + return GLib.SOURCE_REMOVE; + }); + try { + const text = await new Promise((resolve, reject) => { + proc.communicate_utf8_async(input, local, (p, result) => { + try { + const [, stdout] = p.communicate_utf8_finish(result); + resolve(stdout); + } catch (error) { + reject(error); + } + }); + }); + if (!proc.get_successful()) + throw new Error('Command failed'); + return text; + } finally { + // Give managed collectors time to cancel and reap their own CLI child. + if (local.is_cancelled()) { + proc.send_signal(15); + await new Promise(resolve => GLib.timeout_add(GLib.PRIORITY_DEFAULT, 300, () => { + resolve(); + return GLib.SOURCE_REMOVE; + })); + } + proc.force_exit(); + proc.wait_async(null, (p, result) => { try { p.wait_finish(result); } catch { /* Reap best effort. */ } }); + if (timer) + GLib.source_remove(timer); + if (externalId) + cancellable.disconnect(externalId); + } +} + +export class RpcClient { + constructor(argv, cancellable, timeout = 8000) { + this.proc = Gio.Subprocess.new(argv, Gio.SubprocessFlags.STDIN_PIPE | Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_SILENCE); + this.input = new Gio.DataInputStream({base_stream: this.proc.get_stdout_pipe()}); + this.cancellable = new Gio.Cancellable(); + this.parent = cancellable; + this.parentId = cancellable?.connect(() => this.close()); + this.timeout = timeout; + this.nextId = 0; + this.pending = new Map(); + this.closed = false; + this.read(); + } + + send(value) { + if (this.closed) + throw new Error('RPC closed'); + this.proc.get_stdin_pipe().write_all(new TextEncoder().encode(`${JSON.stringify(value)}\n`), this.cancellable); + } + + notify(method, params) { + this.send({method, params}); + } + + request(method, params = {}) { + return new Promise((resolve, reject) => { + const id = ++this.nextId; + const timer = GLib.timeout_add(GLib.PRIORITY_DEFAULT, this.timeout, () => { + this.pending.delete(id); + reject(new Error('RPC timeout')); + return GLib.SOURCE_REMOVE; + }); + this.pending.set(id, {resolve, reject, timer}); + try { + this.send({id, method, params}); + } catch (error) { + this.pending.delete(id); + GLib.source_remove(timer); + reject(error); + } + }); + } + + read() { + this.input.read_line_async(GLib.PRIORITY_DEFAULT, this.cancellable, (stream, result) => { + try { + const [bytes] = stream.read_line_finish(result); + if (bytes === null) + throw new Error('RPC exited'); + const value = JSON.parse(new TextDecoder().decode(bytes)); + const pending = this.pending.get(value.id); + if (pending) { + this.pending.delete(value.id); + GLib.source_remove(pending.timer); + if (value.error) + pending.reject(new Error('RPC request unavailable')); + else + pending.resolve(value.result); + } + if (!this.closed) + this.read(); + } catch { + this.close(); + } + }); + } + + close() { + if (this.closed) + return; + this.closed = true; + for (const pending of this.pending.values()) { + GLib.source_remove(pending.timer); + pending.reject(new Error('RPC closed')); + } + this.pending.clear(); + this.cancellable.cancel(); + this.proc.force_exit(); + this.proc.wait_async(null, (p, result) => { try { p.wait_finish(result); } catch { /* Reap best effort. */ } }); + // Disconnecting inside a Gio.Cancellable callback deadlocks; defer it. + if (this.parentId) { + const id = this.parentId; + this.parentId = 0; + GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { + this.parent.disconnect(id); + return GLib.SOURCE_REMOVE; + }); + } + } +} diff --git a/src/services/usageService.js b/src/services/usageService.js new file mode 100644 index 0000000..a0c10df --- /dev/null +++ b/src/services/usageService.js @@ -0,0 +1,115 @@ +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import {NAMES, mergeRecord, record, section, validateRecord} from '../core/usage.js'; +import {ThresholdTracker} from '../core/notifications.js'; +import {findCommand, join, readJson, stateDirectory, writeJson} from './files.js'; +import {runCommand} from './process.js'; + +export class UsageService { + constructor(settings, directory, changed, alerts) { + this.settings = settings; + this.directory = directory; + this.changed = changed; + this.alerts = alerts; + this.records = {}; + this.jobs = new Map(); + this.attempts = new Map(); + this.failures = new Map(); + this.thresholds = new ThresholdTracker(); + this.closed = false; + this.enabled = []; + this.configure(); + this.timer = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 15, () => { + for (const id of this.enabled) + this.refresh(id); + return GLib.SOURCE_CONTINUE; + }); + this.sleepId = Gio.DBus.system.signal_subscribe('org.freedesktop.login1', 'org.freedesktop.login1.Manager', + 'PrepareForSleep', '/org/freedesktop/login1', null, Gio.DBusSignalFlags.NONE, + (_connection, _sender, _path, _interface, _signal, params) => { + if (!params.get_child_value(0).get_boolean()) + this.refreshAll(true); + }); + } + + configure() { + this.enabled = [...new Set(this.settings.get_strv('enabled-providers'))].filter(id => NAMES[id]); + for (const [id, job] of this.jobs) { + if (!this.enabled.includes(id)) { + job.cancel(); + this.jobs.delete(id); + } + } + for (const id of this.enabled) { + if (this.records[id]) + continue; + try { + const cached = validateRecord(readJson(join(stateDirectory(), `${id}.json`)), id); + for (const key of ['limits', 'history']) { + if (cached[key].updatedAt) + cached[key] = {...cached[key], ...section('stale', 'Showing saved usage while refreshing.'), updatedAt: cached[key].updatedAt}; + } + this.records[id] = cached; + } catch { this.records[id] = record(id); } + } + } + + refreshAll(force = false) { + for (const id of this.enabled) + this.refresh(id, force); + } + + async refresh(id, force = false) { + if (this.closed || !this.enabled.includes(id) || this.jobs.has(id)) + return; + const now = Date.now(); + const elapsed = now - (this.attempts.get(id) ?? 0); + const wait = Math.min(3600, this.settings.get_int('refresh-interval') * 2 ** (this.failures.get(id) ?? 0)); + if (elapsed >= 0 && elapsed < (force ? 15000 : wait * 1000)) + return; + const job = new Gio.Cancellable(); + this.jobs.set(id, job); + this.attempts.set(id, now); + this.changed(); + try { + const stdout = await runCommand([findCommand('gjs'), '-m', join(this.directory, 'src', 'collector', 'main.js'), + id, String(this.settings.get_int('history-retention-days'))], {cancellable: job, timeout: 45000}); + if (this.closed || job.is_cancelled() || this.jobs.get(id) !== job) + return; + const result = validateRecord(JSON.parse(stdout), id); + const failed = ['unavailable', 'missing-auth'].includes(result.limits.status); + this.failures.set(id, failed ? Math.min(4, (this.failures.get(id) ?? 0) + 1) : 0); + const alerts = this.thresholds.update(result, this.settings.get_int('notification-threshold')); + this.records[id] = mergeRecord(this.records[id], result); + try { writeJson(join(stateDirectory(), `${id}.json`), this.records[id]); } catch { /* Cache failure must not hide current usage. */ } + if (this.settings.get_boolean('notifications-enabled') && alerts.length) + this.alerts(alerts); + } catch { + if (!this.closed && !job.is_cancelled()) { + const failed = record(id); + failed.limits = {...failed.limits, ...section('unavailable', 'Collection failed or timed out. Retry from the panel.')}; + failed.history = {...failed.history, ...section('unavailable', 'History could not be refreshed.')}; + this.records[id] = mergeRecord(this.records[id], failed); + this.failures.set(id, Math.min(4, (this.failures.get(id) ?? 0) + 1)); + } + } finally { + if (this.jobs.get(id) === job) + this.jobs.delete(id); + if (!this.closed) + this.changed(); + } + } + + destroy() { + this.closed = true; + if (this.timer) + GLib.source_remove(this.timer); + if (this.sleepId) + Gio.DBus.system.signal_unsubscribe(this.sleepId); + for (const job of this.jobs.values()) + job.cancel(); + this.jobs.clear(); + this.changed = () => {}; + this.alerts = () => {}; + } +} diff --git a/src/ui/indicator.js b/src/ui/indicator.js new file mode 100644 index 0000000..970c628 --- /dev/null +++ b/src/ui/indicator.js @@ -0,0 +1,156 @@ +import Clutter from 'gi://Clutter'; +import GObject from 'gi://GObject'; +import St from 'gi://St'; +import * as Main from 'resource:///org/gnome/shell/ui/main.js'; +import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js'; +import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js'; +import {NAMES, highestUsage, recentDates} from '../core/usage.js'; +import {age, resetTime, tokens} from '../core/format.js'; +import {button, label, meter, row} from './widgets.js'; + +export const FreebyIndicator = GObject.registerClass(class FreebyIndicator extends PanelMenu.Button { + _init(settings, openPreferences) { + super._init(0.0, 'Freeby usage monitor'); + this._settings = settings; + this._openPreferences = openPreferences; + this._service = null; + this._panelLabel = label('AI', 'freeby-panel-label'); + this.add_child(this._panelLabel); + this.menu.actor.add_style_class_name('freeby-menu'); + const section = new PopupMenu.PopupMenuSection(); + this.menu.addMenuItem(section); + // `content` is an inherited Clutter.Actor property whose value must be + // ClutterContent, so keep the menu actor under an unambiguous name. + this._contentBox = new St.BoxLayout({vertical: true, style_class: 'freeby-content', x_expand: true}); + this._scroll = new St.ScrollView({hscrollbar_policy: St.PolicyType.NEVER, vscrollbar_policy: St.PolicyType.AUTOMATIC, + overlay_scrollbars: true}); + // St.ScrollView is a Clutter actor in GNOME 50. Its inherited + // set_child() targets ClutterContent, so add the scrollable actor as a + // child explicitly. + this._scroll.add_child(this._contentBox); + section.box.add_child(this._scroll); + this.menu.connect('open-state-changed', (_menu, open) => { + if (open) { + this.resize(); + this._service?.refreshAll(); + this.render(); + } + }); + this.render(); + } + + resize() { + const monitor = Main.layoutManager.findMonitorForActor(this) ?? Main.layoutManager.primaryMonitor; + const scale = St.ThemeContext.get_for_stage(global.stage).scale_factor; + this._scroll.style = `max-height: ${Math.max(180, Math.floor(monitor.height / scale) - 120)}px;`; + this._contentBox.style = `width: ${Math.min(420, Math.floor(monitor.width / scale) - 64)}px;`; + } + + attach(service) { + this._service = service; + this.render(); + } + + render() { + if (!this._contentBox) + return; + const focus = global.stage.get_key_focus(); + const restoreName = focus && this._contentBox.contains(focus) ? focus.accessible_name : null; + this._contentBox.destroy_all_children(); + const enabled = this._service?.enabled ?? []; + const requested = this._settings.get_string('default-provider'); + const id = enabled.includes(requested) ? requested : enabled[0]; + const record = this._service?.records[id]; + const percent = highestUsage(record); + this._panelLabel.text = percent === null ? 'AI' : `AI ${Math.round(percent)}%`; + this._panelLabel.style_class = `freeby-panel-label${percent >= 100 ? ' freeby-danger' : percent >= 90 ? ' freeby-warning' : ''}`; + this.accessible_name = `Freeby, ${record?.name ?? 'usage monitor'}${percent === null ? '' : `, ${Math.round(percent)} percent used`}`; + const header = new St.BoxLayout({vertical: true, style_class: 'freeby-header'}); + header.add_child(label(record?.name ?? 'Freeby', 'freeby-title')); + header.add_child(label(record?.plan ? String(record.plan).toUpperCase() : 'USAGE MONITOR', 'freeby-caption')); + this._contentBox.add_child(header); + if (enabled.length > 1) { + const selector = new St.BoxLayout({style_class: 'freeby-selector', x_expand: true}); + for (const provider of enabled) + selector.add_child(button(NAMES[provider], () => this._settings.set_string('default-provider', provider), + {active: provider === id, name: `Show ${NAMES[provider]} usage`})); + this._contentBox.add_child(selector); + } + if (!record) { + this._contentBox.add_child(label('Choose your providers in preferences to get started.', 'freeby-message')); + } else { + this.heading('LIMITS'); + for (const window of record.limits.windows) { + const box = new St.BoxLayout({vertical: true, style_class: 'freeby-window'}); + const value = window.unlimited ? 'Unlimited' : `${Math.round(window.usedPercent)}%`; + box.add_child(row(window.label, value)); + if (!window.unlimited) + box.add_child(meter(window.usedPercent / 100, `${window.label}: ${value} used`, + window.usedPercent >= 100 ? 'freeby-danger' : window.usedPercent >= 90 ? 'freeby-warning' : '')); + box.add_child(label(resetTime(window.resetsAt), 'freeby-caption')); + this._contentBox.add_child(box); + } + if (record.limits.status !== 'ready') + this.message(record.limits.message || (record.limits.status === 'loading' ? 'Checking account limits…' : 'Limits unavailable')); + if (record.limits.status === 'stale') + this.message(`Saved limits · ${age(record.limits.updatedAt).toLowerCase()}`); + if (record.history.days.length) { + this.heading('TOKENS BY DAY'); + this._contentBox.add_child(label(`${record.history.scope === 'account' ? 'Account activity' : 'This device'} · ${record.history.period.start} – ${record.history.period.end}`, 'freeby-caption')); + const max = Math.max(1, ...record.history.days.map(day => day.total)); + const today = recentDates(Date.now(), 1)[0]; + for (const day of record.history.days) { + const line = new St.BoxLayout({style_class: 'freeby-day-row', x_expand: true}); + const dayName = day.date === today ? 'Today' : new Date(`${day.date}T12:00:00`).toLocaleDateString(undefined, {weekday: 'short'}); + line.add_child(label(dayName, 'freeby-day')); + const bar = meter(day.total / max, `${day.date}: ${tokens(day.total)} tokens`); + bar.y_align = Clutter.ActorAlign.CENTER; + line.add_child(bar); + line.add_child(label(tokens(day.total), 'freeby-day-total')); + line.accessible_name = `${dayName}, ${day.total} tokens, ${day.sessions ?? 0} sessions`; + this._contentBox.add_child(line); + } + } + if (record.history.models.length) { + this.heading('TOKENS BY MODEL'); + this._contentBox.add_child(label('Same period as daily activity', 'freeby-caption')); + const max = Math.max(1, ...record.history.models.map(model => model.total)); + for (const model of record.history.models) { + const box = new St.BoxLayout({vertical: true, style_class: 'freeby-model'}); + box.add_child(row(model.model, tokens(model.total))); + box.add_child(meter(model.total / max, `${model.model}: ${model.total} tokens`)); + box.add_child(label(`Input ${tokens(model.input)} · Output ${tokens(model.output)} · Cache ${tokens(model.cacheRead + model.cacheWrite)}`, 'freeby-caption')); + this._contentBox.add_child(box); + } + } + if (record.history.message) + this.message(record.history.message); + } + this.heading(''); + const busy = this._service?.jobs.has(id); + this._contentBox.add_child(label(busy ? 'Refreshing…' : age(record?.limits.updatedAt || record?.history.updatedAt), 'freeby-caption')); + const actions = new St.BoxLayout({style_class: 'freeby-selector'}); + actions.add_child(button('Refresh', () => this._service?.refreshAll(true), {name: 'Refresh usage'})); + actions.add_child(button('Preferences', () => { this.menu.close(); this._openPreferences(); })); + this._contentBox.add_child(actions); + if (restoreName) { + const restore = actor => { + if (actor.can_focus && actor.accessible_name === restoreName) + actor.grab_key_focus(); + for (const child of actor.get_children()) + restore(child); + }; + restore(this._contentBox); + } + } + + heading(text) { + this._contentBox.add_child(label(text, 'freeby-section-title')); + } + + message(text) { + const message = label(text, 'freeby-message'); + message.clutter_text.line_wrap = true; + this._contentBox.add_child(message); + } +}); diff --git a/src/ui/widgets.js b/src/ui/widgets.js new file mode 100644 index 0000000..9d33005 --- /dev/null +++ b/src/ui/widgets.js @@ -0,0 +1,35 @@ +import Atk from 'gi://Atk'; +import Clutter from 'gi://Clutter'; +import St from 'gi://St'; + +export function label(text, style = '', expand = false) { + return new St.Label({text: String(text ?? ''), style_class: style, + y_align: Clutter.ActorAlign.CENTER, x_expand: expand}); +} + +export function row(left, right, style = 'freeby-row') { + const box = new St.BoxLayout({style_class: style, x_expand: true}); + box.add_child(label(left, '', true)); + box.add_child(label(right, 'freeby-number')); + return box; +} + +export function meter(fraction, name, style = '') { + const ratio = Math.max(0, Math.min(1, Number(fraction) || 0)); + const track = new St.Widget({style_class: `freeby-track ${style}`, x_expand: true, + layout_manager: new Clutter.BinLayout(), accessible_name: name, accessible_role: Atk.Role.PROGRESS_BAR}); + const fill = new St.Widget({style_class: 'freeby-fill', x_align: Clutter.ActorAlign.START, + y_expand: true, y_align: Clutter.ActorAlign.FILL}); + track.add_child(fill); + track.connect('notify::allocation', () => { fill.width = Math.round(track.width * ratio); }); + return track; +} + +export function button(text, callback, {active = false, name = text} = {}) { + const actor = new St.Button({label: text, can_focus: true, reactive: true, track_hover: true, + accessible_name: name, style_class: 'freeby-button', x_expand: true}); + if (active) + actor.add_style_pseudo_class('checked'); + actor.connect('clicked', callback); + return actor; +} diff --git a/stylesheet.css b/stylesheet.css index ac2912b..0965c8e 100644 --- a/stylesheet.css +++ b/stylesheet.css @@ -1,83 +1,25 @@ -.freeby-panel { - spacing: 4px; -} - -.freeby-panel-label { - font-weight: bold; - font-size: 10pt; - min-width: 16px; - color: currentColor; -} - -.freeby-panel-green { - color: #4ade80; -} - -.freeby-panel-yellow { - color: #facc15; -} - -.freeby-panel-red { - color: #f87171; -} - -.freeby-item-box { - spacing: 8px; -} - -.freeby-dot { - font-size: 8pt; - min-width: 10px; -} - -.freeby-dot-off { - color: currentColor; - opacity: 0.4; -} - -.freeby-dot-green { - color: #4ade80; -} - -.freeby-dot-yellow { - color: #facc15; -} - -.freeby-dot-red { - color: #f87171; -} - -.freeby-item-name { - font-weight: bold; - font-size: 9pt; - min-width: 52px; - color: currentColor; -} - -.freeby-item-summary { - font-size: 9pt; - color: currentColor; - opacity: 0.8; -} - -.freeby-dim { - opacity: 0.4; -} - -.freeby-green { - color: #4ade80; -} - -.freeby-yellow { - color: #facc15; -} - -.freeby-red { - color: #f87171; -} - -.freeby-status { - font-size: 9pt; - color: currentColor; - opacity: 0.7; -} +.freeby-panel-label { font-weight: bold; font-size: 10pt; } +.freeby-content { padding: 8px 14px 12px; spacing: 12px; } +.freeby-header { spacing: 4px; padding: 4px 0 8px; } +.freeby-title { font-size: 18pt; font-weight: bold; } +.freeby-caption { font-size: 9pt; opacity: 0.65; } +.freeby-selector { spacing: 8px; } +.freeby-button { padding: 9px 12px; border-radius: 8px; background-color: rgba(128, 128, 128, 0.12); } +.freeby-button:hover, .freeby-button:focus { background-color: rgba(128, 128, 128, 0.24); } +.freeby-button:checked { background-color: rgba(128, 128, 128, 0.3); font-weight: bold; } +.freeby-button:focus { box-shadow: inset 0 0 0 2px -st-accent-color; } +.freeby-section-title { font-size: 9pt; font-weight: bold; opacity: 0.65; border-top: 1px solid rgba(128, 128, 128, 0.25); padding-top: 16px; } +.freeby-row { spacing: 12px; } +.freeby-number { font-family: monospace; } +.freeby-window { spacing: 7px; } +.freeby-track { height: 6px; min-width: 20px; border-radius: 3px; background-color: rgba(128, 128, 128, 0.25); } +.freeby-fill { background-color: -st-accent-color; border-radius: 3px; } +.freeby-warning { color: #e5a50a; } +.freeby-danger { color: #e01b24; } +.freeby-warning .freeby-fill { background-color: #e5a50a; } +.freeby-danger .freeby-fill { background-color: #e01b24; } +.freeby-day-row { spacing: 12px; } +.freeby-day { min-width: 44px; font-size: 10pt; } +.freeby-day-total { min-width: 64px; text-align: right; font-family: monospace; font-size: 10pt; } +.freeby-model { spacing: 6px; } +.freeby-message { font-size: 9pt; opacity: 0.75; } diff --git a/tests/integration/collector.js b/tests/integration/collector.js new file mode 100644 index 0000000..6b32b3d --- /dev/null +++ b/tests/integration/collector.js @@ -0,0 +1,65 @@ +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import System from 'system'; +import {scanHistory} from '../../src/services/history.js'; +import {parseCodexEvent} from '../../src/providers/codex.js'; +import {readJson, writeJson, join} from '../../src/services/files.js'; +import {runCommand} from '../../src/services/process.js'; + +function assert(value, message) { + if (!value) + throw new Error(message); +} +const scratch = GLib.dir_make_tmp('freeby-integration-XXXXXX'); +const sessions = join(scratch, 'sessions'); +GLib.mkdir_with_parents(sessions, 0o700); +const fixture = join(sessions, 'synthetic.jsonl'); +const cachePath = join(scratch, 'cache.json'); +const now = new Date('2026-09-06T12:00:00').getTime(); +const event = total => JSON.stringify({type: 'event_msg', timestamp: '2026-09-06T10:00:00Z', payload: {type: 'token_count', + info: {total_token_usage: {input_tokens: total, output_tokens: 0, cached_input_tokens: 0, total_tokens: total}}}}); +const text = `${JSON.stringify({type: 'session_meta', payload: {id: 'synthetic'}})}\n${event(100)}\n`; +GLib.file_set_contents(fixture, text); +const first = scanHistory('codex', [sessions], parseCodexEvent, {now, cachePath}); +assert(first.days.at(-1).total === 100, 'initial scan'); +const second = scanHistory('codex', [sessions], parseCodexEvent, {now, cachePath}); +assert(second.scannedFiles === 0 && second.days.at(-1).total === 100, 'unchanged file must reuse cache'); +GLib.file_set_contents(fixture, `${text}${event(180)}\n{"partial":`); +const third = scanHistory('codex', [sessions], parseCodexEvent, {now, cachePath}); +assert(third.days.at(-1).total === 180, 'append must count delta only'); +const cache = readJson(cachePath); +assert(cache.version === 2, 'versioned cache'); +assert(!JSON.stringify(cache).includes('synthetic'), 'session identity must be sanitized in cache'); +writeJson(join(scratch, 'record.json'), {safe: true}); +assert(readJson(join(scratch, 'record.json')).safe, 'atomic JSON roundtrip'); +print('PASS: GJS history initial scan, cached scan, append, partial line, and private JSON cache'); + +const loop = new GLib.MainLoop(null, false); +let failed = false; +(async () => { + try { + assert((await runCommand(['/bin/echo', 'fixture'])).trim() === 'fixture', 'subprocess output'); + let timedOut = false; + try { await runCommand(['/bin/sleep', '5'], {timeout: 50}); } catch { timedOut = true; } + assert(timedOut, 'subprocess timeout'); + print('PASS: GJS subprocess communication, deadline and termination'); + } catch (error) { + printerr(error.message); + failed = true; + } finally { + loop.quit(); + } +})(); +loop.run(); +function remove(file) { + if (file.query_file_type(Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null) === Gio.FileType.DIRECTORY) { + const entries = file.enumerate_children('standard::name', Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null); + let entry; + while ((entry = entries.next_file(null))) + remove(file.get_child(entry.get_name())); + entries.close(null); + } + file.delete(null); +} +remove(Gio.File.new_for_path(scratch)); +System.exit(failed ? 1 : 0); diff --git a/tests/unit/codex.test.js b/tests/unit/codex.test.js new file mode 100644 index 0000000..261b45e --- /dev/null +++ b/tests/unit/codex.test.js @@ -0,0 +1,52 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {codexLimits, collectCodex, parseCodexEvent} from '../../src/providers/codex.js'; + +const tokenEvent = (input, output, cache, total) => ({timestamp: '2026-09-06T12:00:00Z', type: 'event_msg', payload: {type: 'token_count', + info: {total_token_usage: {input_tokens: input, output_tokens: output, cached_input_tokens: cache, total_tokens: total}}}}); + +test('reads all quota buckets and derives labels from real window durations', () => { + const result = codexLimits({rateLimitsByLimitId: {codex: {primary: {usedPercent: 10, windowDurationMins: 300, resetsAt: 1800000000}, + secondary: {usedPercent: 100, windowDurationMins: 10080}}}}, 100); + assert.equal(result.windows.length, 2); + assert.equal(result.windows[0].resetsAt, 1800000000000); + assert.equal(result.windows[0].durationMinutes, 300); + assert.equal(result.windows[1].label, 'Weekly'); + assert.equal(result.windows[1].usedPercent, 100); + assert.equal(result.status, 'ready'); +}); + +test('empty Codex response is unavailable, not zero usage', () => { + assert.equal(codexLimits({}).status, 'unavailable'); + assert.equal(codexLimits({rateLimits: {primary: {}}}).windows.length, 0); +}); + +test('cumulative token deltas and cached input are counted once', () => { + const state = {session: 'session-1', model: 'model-a'}; + const first = parseCodexEvent(tokenEvent(100, 20, 40, 120), state); + assert.equal(first.input, 60); + assert.equal(first.cacheRead, 40); + assert.equal(parseCodexEvent(tokenEvent(100, 20, 40, 120), state), null); + const second = parseCodexEvent(tokenEvent(180, 35, 60, 215), state); + assert.equal(second.input + second.output + second.cacheRead, 95); +}); + +test('record parsing tracks model and stable session identity', () => { + const state = {session: 'file-hash'}; + parseCodexEvent({type: 'session_meta', payload: {id: 'native-session'}}, state); + parseCodexEvent({type: 'turn_context', payload: {model: 'model-b'}}, state); + const event = parseCodexEvent(tokenEvent(10, 2, 0, 12), state); + assert.equal(event.model, 'model-b'); + assert.equal(event.session, 'native-session'); +}); + +test('local history survives missing account authentication and closes RPC', async () => { + let closed = false; + const history = {status: 'ready', days: [], models: []}; + const result = await collectCodex({scan: () => history, + codexClient: () => ({request: async method => method === 'account/read' ? {account: null} : {}, + notify: () => {}, close: () => { closed = true; }})}); + assert.equal(result.limits.status, 'missing-auth'); + assert.equal(result.history, history); + assert.equal(closed, true); +}); diff --git a/tests/unit/usage.test.js b/tests/unit/usage.test.js new file mode 100644 index 0000000..159cd26 --- /dev/null +++ b/tests/unit/usage.test.js @@ -0,0 +1,82 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {aggregateEvents, highestUsage, mergeRecord, number, recentDates, record, validateRecord, windowUsage} from '../../src/core/usage.js'; +import {resetTime, tokens} from '../../src/core/format.js'; +import {ThresholdTracker} from '../../src/core/notifications.js'; + +test('unknown metrics are not coerced to zero', () => { + for (const value of [null, undefined, '', false, -1, Infinity, 'bad']) + assert.equal(number(value), null); + assert.equal(number(0), 0); + assert.equal(windowUsage({id: 'plan', label: 'Plan', used: 0, limit: 0}), null); + assert.equal(highestUsage(record('codex')), null); +}); + +test('unknown, exhausted and unlimited windows remain distinct', () => { + const exhausted = windowUsage({id: 'x', label: 'X', used: 12, limit: 10}); + assert.equal(exhausted.usedPercent, 120); + assert.equal(exhausted.state, 'exhausted'); + const unlimited = windowUsage({id: 'x', label: 'X', unlimited: true}); + assert.equal(unlimited.usedPercent, null); + assert.equal(unlimited.unlimited, true); + assert.equal(unlimited.state, 'unlimited'); +}); + +test('contract rejects invalid provider IDs and quota values', () => { + assert.throws(() => validateRecord(record('codex'), 'cursor')); + const value = record('codex'); + value.limits.windows.push({id: 'x', label: 'X', state: 'active', usedPercent: null}); + assert.throws(() => validateRecord(value, 'codex')); +}); + +test('failure preserves last successful values as stale, account change discards them', () => { + const old = record('codex'); + old.accountKey = 'one'; + old.limits = {status: 'ready', updatedAt: 100, windows: [{usedPercent: 50}]}; + const next = record('codex'); + next.limits.status = 'unavailable'; + next.limits.message = 'Disconnected'; + const merged = mergeRecord(old, next); + assert.equal(merged.limits.status, 'stale'); + assert.equal(merged.limits.updatedAt, 100); + assert.equal(merged.limits.windows[0].usedPercent, 50); + assert.equal(highestUsage(merged), null); + next.accountKey = 'two'; + assert.equal(mergeRecord(old, next).limits.windows.length, 0); +}); + +test('daily and model totals use the same period and deduplicate events', () => { + const event = {id: 'one', session: 'a', model: 'model-a', date: '2026-09-06', input: 20, output: 5, cacheRead: 10, cacheWrite: 0}; + const result = aggregateEvents([event, event, {...event, id: 'older', date: '2026-08-01'}], new Date('2026-09-06T12:00:00').getTime()); + assert.equal(result.days.length, 7); + assert.equal(result.days.reduce((sum, d) => sum + d.total, 0), 35); + assert.equal(result.models[0].total, 35); + assert.equal(result.days.at(-1).sessions, 1); + assert.equal(result.period.start, '2026-08-31'); +}); + +test('calendar buckets are consecutive across month boundaries', () => { + assert.deepEqual(recentDates(new Date('2026-03-02T12:00:00').getTime(), 3), ['2026-02-28', '2026-03-01', '2026-03-02']); +}); + +test('notifications require a verified crossing and deduplicate warning and limit separately', () => { + const tracker = new ThresholdTracker(); + const value = record('codex'); + value.limits = {status: 'ready', windows: [{id: 'weekly', label: 'Weekly', usedPercent: 95, resetsAt: 500}]}; + assert.deepEqual(tracker.update(value), []); + value.limits.windows[0].usedPercent = 100; + assert.equal(tracker.update(value)[0].threshold, 100); + assert.deepEqual(tracker.update(value), []); + value.limits.status = 'unavailable'; + assert.deepEqual(tracker.update(value), []); + value.limits.status = 'ready'; + value.limits.windows[0].resetsAt = 1000; + assert.deepEqual(tracker.update(value), []); +}); + +test('formatting preserves unknown/reset-due states', () => { + assert.equal(tokens(null), '—'); + assert.equal(tokens(23000000), '23.0M'); + assert.equal(resetTime(null), 'Reset time unavailable'); + assert.match(resetTime(100, 200), /awaiting update/); +}); diff --git a/tools/check.js b/tools/check.js new file mode 100644 index 0000000..2ad9d35 --- /dev/null +++ b/tools/check.js @@ -0,0 +1,27 @@ +import {readFileSync, readdirSync, existsSync} from 'node:fs'; +import {resolve, dirname, join} from 'node:path'; +import {execFileSync} from 'node:child_process'; + +function walk(directory) { + return readdirSync(directory, {withFileTypes: true}).flatMap(entry => entry.isDirectory() + ? walk(join(directory, entry.name)) : [join(directory, entry.name)]); +} +const files = ['extension.js', 'prefs.js', ...walk('src'), ...walk('tests'), ...walk('tools')].filter(file => file.endsWith('.js')); +for (const file of files) { + const text = readFileSync(file, 'utf8'); + execFileSync(process.execPath, ['--check', file], {stdio: 'pipe'}); + if (!text.endsWith('\n') || /[ \t]+$/m.test(text)) + throw new Error(`${file}: trailing whitespace or missing final newline`); + for (const [, target] of text.matchAll(/from ['"]([.][^'"]+)['"]/g)) { + if (!existsSync(resolve(dirname(file), target))) + throw new Error(`${file}: missing import ${target}`); + } +} +const metadata = JSON.parse(readFileSync('metadata.json', 'utf8')); +if (metadata.uuid !== 'freeby@kelvin.local') + throw new Error('Extension identity changed without a migration'); +const version = JSON.parse(readFileSync('package.json', 'utf8')).version; +if (!readFileSync('meson.build', 'utf8').includes(`version: '${version}'`)) + throw new Error('Meson and package versions differ'); +execFileSync('glib-compile-schemas', ['--strict', '--dry-run', 'schemas']); +console.log(`Syntax, imports, formatting, release metadata and schema checks passed (${files.length} JavaScript files).`); diff --git a/tools/package.py b/tools/package.py new file mode 100644 index 0000000..c68eaed --- /dev/null +++ b/tools/package.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +"""Create a deterministic GNOME extension archive from an explicit allowlist.""" +import argparse +import json +from pathlib import Path +import subprocess +import tempfile +import zipfile + + +def package(source, output): + metadata = json.loads((source / 'metadata.json').read_text()) + version = json.loads((source / 'package.json').read_text())['version'] + files = [source / name for name in ('extension.js', 'prefs.js', 'metadata.json', 'stylesheet.css', 'LICENSE')] + files.extend(sorted((source / 'src').rglob('*.js'))) + files.extend(sorted((source / 'schemas').glob('*.xml'))) + output.mkdir(parents=True, exist_ok=True) + archive = output / f'{metadata["uuid"]}-{version}.zip' + with tempfile.TemporaryDirectory(prefix='freeby-schemas-') as scratch: + subprocess.run(['glib-compile-schemas', '--strict', '--targetdir', scratch, str(source / 'schemas')], check=True) + payloads = [(file.relative_to(source).as_posix(), file.read_bytes()) for file in files] + payloads.append(('schemas/gschemas.compiled', (Path(scratch) / 'gschemas.compiled').read_bytes())) + with zipfile.ZipFile(archive, 'w', compression=zipfile.ZIP_DEFLATED) as target: + for name, payload in sorted(payloads): + info = zipfile.ZipInfo(name, date_time=(2020, 1, 1, 0, 0, 0)) + info.compress_type = zipfile.ZIP_DEFLATED + info.external_attr = 0o100644 << 16 + target.writestr(info, payload) + with zipfile.ZipFile(archive) as check: + assert check.testzip() is None + assert 'schemas/gschemas.compiled' in check.namelist() + assert all(not name.startswith(('tests/', 'tools/', 'reference-images/')) for name in check.namelist()) + print(archive.resolve()) + return archive + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--source', type=Path, default=Path(__file__).resolve().parent.parent) + parser.add_argument('--output', type=Path, default=Path('dist')) + args = parser.parse_args() + package(args.source.resolve(), args.output.resolve()) diff --git a/tools/smoke-shell.py b/tools/smoke-shell.py new file mode 100644 index 0000000..4dc32ca --- /dev/null +++ b/tools/smoke-shell.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +"""Exercise the packaged extension in a private headless GNOME session.""" +import argparse +import os +from pathlib import Path +import signal +import subprocess +import tempfile +import time +import zipfile + + +def run(command, env, **options): + return subprocess.run(command, env=env, text=True, capture_output=True, timeout=15, **options) + + +def install(source, archive, prefix, destination): + extension = prefix / 'share' / 'gnome-shell' / 'extensions' / 'freeby@kelvin.local' + if archive: + with zipfile.ZipFile(archive) as payload: + for entry in payload.infolist(): + parts = Path(entry.filename).parts + if entry.is_dir(): + continue + if Path(entry.filename).is_absolute() or '..' in parts: + raise RuntimeError(f'Unsafe archive entry: {entry.filename}') + target = extension.joinpath(*parts) + target.parent.mkdir(parents=True, exist_ok=True) + target.write_bytes(payload.read(entry)) + return + build = destination / 'build' + subprocess.run(['meson', 'setup', str(build), str(source), f'--prefix={prefix}'], check=True, stdout=subprocess.DEVNULL) + subprocess.run(['meson', 'install', '-C', str(build)], check=True, stdout=subprocess.DEVNULL) + + +def smoke(source, archive, destination): + destination.mkdir(parents=True, exist_ok=True) + prefix = destination / 'install' + install(source, archive, prefix, destination) + env = {**os.environ, 'XDG_CONFIG_HOME': str(destination / 'config'), + 'XDG_DATA_HOME': str(prefix / 'share'), 'XDG_CACHE_HOME': str(destination / 'cache'), + 'XDG_STATE_HOME': str(destination / 'state'), 'GSETTINGS_BACKEND': 'keyfile', + 'GSETTINGS_SCHEMA_DIR': str(prefix / 'share/gnome-shell/extensions/freeby@kelvin.local/schemas'), + 'LIBGL_ALWAYS_SOFTWARE': '1'} + # Empty provider list prevents personal authentication/data access during UI tests. + run(['gsettings', 'set', 'org.gnome.shell.extensions.freeby', 'enabled-providers', '[]'], env, check=True) + run(['gsettings', 'set', 'org.gnome.shell', 'enabled-extensions', "['freeby@kelvin.local']"], env, check=True) + run(['gsettings', 'set', 'org.gnome.shell', 'welcome-dialog-last-shown-version', '999'], env) + bus = subprocess.Popen(['dbus-daemon', '--session', '--nofork', '--print-address=1'], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True) + env['DBUS_SESSION_BUS_ADDRESS'] = bus.stdout.readline().strip() + log_path = destination / 'shell.log' + shell = None + try: + with log_path.open('w') as log: + shell = subprocess.Popen(['gnome-shell', '--headless', '--no-x11', '--virtual-monitor', '1280x1024', + '--wayland-display', 'freeby-test', '--debug-control'], env=env, stdout=log, stderr=log, start_new_session=True) + command = ['gdbus', 'call', '--session', '--dest', 'org.gnome.Shell.Extensions', '--object-path', '/org/gnome/Shell/Extensions', + '--method', 'org.gnome.Shell.Extensions.GetExtensionInfo', 'freeby@kelvin.local'] + deadline = time.monotonic() + 30 + info = '' + while time.monotonic() < deadline and shell.poll() is None: + result = run(command, env) + info = result.stdout + if "'state': <1.0>" in info: + break + if "'state': <3.0>" in info: + raise RuntimeError(f'Extension failed: {info}') + time.sleep(0.4) + else: + raise RuntimeError(f'Extension did not become active: {info}') + print('PASS: packaged extension loads in a private GNOME Shell session') + run(['gnome-extensions', 'disable', 'freeby@kelvin.local'], env, check=True) + run(['gnome-extensions', 'enable', 'freeby@kelvin.local'], env, check=True) + print('PASS: extension disable/re-enable') + finally: + if shell and shell.poll() is None: + os.killpg(shell.pid, signal.SIGTERM) + try: + shell.wait(timeout=5) + except subprocess.TimeoutExpired: + os.killpg(shell.pid, signal.SIGKILL) + shell.wait() + bus.terminate() + bus.wait(timeout=5) + print(f'GNOME log: {log_path}') + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--source', type=Path, default=Path(__file__).resolve().parent.parent) + parser.add_argument('--archive', type=Path, help='Test an already-built release archive instead of a Meson install') + parser.add_argument('--output', type=Path) + args = parser.parse_args() + smoke(args.source.resolve(), args.archive.resolve() if args.archive else None, + args.output or Path(tempfile.mkdtemp(prefix='freeby-shell-'))) From 34f273e1268732cba146ebbe2819c8aa345d16dc Mon Sep 17 00:00:00 2001 From: kcnewman Date: Sun, 6 Sep 2026 08:17:15 +0000 Subject: [PATCH 14/44] docs: prepare the alpha.1 milestone release --- CHANGELOG.md | 108 ++++++++++---------------------- CONTRIBUTING.md | 99 +++++++++--------------------- README.md | 159 ++++++++++++++++++++---------------------------- plan.md | 20 +++--- 4 files changed, 143 insertions(+), 243 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c3f947..e9f80d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,89 +1,47 @@ # Changelog -## v1.0.2 +All notable changes are documented here. Freeby uses semantic prerelease versions +while version 2 is developed on the `dev` branch. -### Fixed -- Added GNOME 49 and 50 to shell-version compatibility -- Removed deprecated `version` field from metadata.json -- Dropdown now stays open when clicking refresh (deferred reopen) -- Removed invalid `accessible_name` from PopupMenuItem (caused extension to fail loading) - -## v1.0.1 +## 2.0.0-alpha.1 - 2026-09-06 ### Added -- Accessible names for screen readers on panel indicator, dots, and summaries -- System theme support in CSS (uses `currentColor` and opacity for better theme integration) -- CONTRIBUTING.md with development setup and code style guidelines -- GitHub Actions CI: shellcheck, meson build, bats tests -- Bats test suite for shell script (JSON structure, required fields, cleanup) - -### Changed -- CSS colors use `currentColor` where possible for better theme compatibility -- Status dot and summary text use opacity for dimmed states instead of hardcoded colors - -## v1.0.0 - -### Fixed -- Crash when subprocess returns empty/null output -- Race condition from concurrent refresh calls (clicking during auto-refresh) -- Stale async callback after extension disable -- False "limit reached" notification on first refresh -- Schema not compiled during install (gsettings now works out of the box) -- Codex percentage parser using string instead of boolean for `has_remaining` -- Copilot percentage exceeding 100% on overages -- Cursor showing "resets now" when billing cycle end is unknown -- `copilot-setup.sh` infinite loop (now times out after 5 minutes) -- Shell script temp directory shadowing system `$TMPDIR` -- Shell script hanging forever when curl or npx times out - -### Changed -- Consistent percentage display across all providers -- Metadata: added `version` and `settings-schema` fields -- Metadata: removed unreleased GNOME shell versions from compatibility list -- GSettings: enforced 30–3600s range on refresh interval -- Build: schema compilation now runs automatically during `meson install` -- README: added prerequisites, uninstall instructions, corrected copilot-setup path - -### Security -- Fixed Python code injection risk when `$HOME` contains single quotes -- Removed unused `json_escape` function -## v0.5.0 +- Versioned provider-neutral usage contract with explicit capabilities, source + scope, freshness, quota states, reset timestamps, and token categories. +- Bounded Codex app-server collection for account rate limits. +- Incremental local Codex history scanning with seven-day and model aggregates. +- Private XDG state/cache persistence, stale-data recovery, and refresh backoff. +- Native provider header, quota meters, daily activity, model totals, status + footer, setup/error states, and accessible controls. +- Deterministic archive packaging, unit tests, GJS integration tests, and an + isolated headless GNOME lifecycle smoke test. ### Fixed -- Dropdown now stays open when clicking the refresh icon -- Consistent percentage display for Copilot (was showing token counts) -## v0.4.0 +- Refresh now occurs after resume rather than before suspend. +- Provider jobs no longer overlap or update destroyed UI. +- Notifications require a verified threshold crossing and are deduplicated per + account, quota window, and reset period. +- Missing or empty provider responses are no longer reported as zero usage. +- Runtime `npx` downloads and unbounded collection processes were removed. +- Schemas install inside the extension and uninstall no longer requires deleting + a shared compiled schema file. -### Added -- Desktop notifications on provider limit hit -- Auto-refresh on wake from sleep (systemd PrepareForSleep) -- Configurable refresh interval via gsettings -- Settings UI in Extension Manager (prefs.js) -- Parallel provider fetches (4.6s → 1.9s) - -### Changed -- Split extension.js into extension.js + indicator.js + prefs.js - -## v0.3.0 - -### Added -- Click panel to refresh -- Clickable refresh icon in dropdown -- `has_remaining` field for accurate provider count - -### Fixed -- Consistent comma separator and countdown format -- Codex parsed via `--json` flag for reliable parsing +### Known limitations -## v0.2.0 +- GNOME Shell 50 is the only compatibility target verified for alpha.1. +- Codex activity is local to this device. Account quota limits and local token + history intentionally remain separate scopes. +- Cursor and Copilot adapters are preview-only until alpha.3 and alpha.4. +- Claude Code support is scheduled for alpha.2. -### Added -- Real provider integrations: Codex, Cursor, Copilot -- `copilot-setup.sh` device-flow auth script -- Panel indicator with colored status +## 1.0.2 - 2026-04-27 -## v0.1.0 +- Added GNOME 49 and 50 metadata compatibility and removed deprecated extension + version metadata. +- Kept the dropdown open after manual refresh and fixed an invalid popup + accessibility property. -- Initial release with placeholder data +Earlier release history remains available in the repository's Git tags and +GitHub releases. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 27606ca..b5aa942 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,83 +1,44 @@ -# Contributing +# Contributing to Freeby -Thanks for your interest in Freeby. +Development targets the `dev` branch. `main` is reserved for stable releases. +Keep provider changes small enough to verify independently and use conventional +commit messages such as `feat:`, `fix:`, `test:`, `docs:`, and `chore:`. -## Development +## Development environment -```bash -git clone https://github.com/kcnewman/freeby.git && cd freeby -git checkout dev -meson setup build --prefix=$HOME/.local -meson install -C build -``` - -Restart GNOME Shell (Alt+F2, type `r`, Enter) and enable: +Install Node.js 20 or newer, GJS, Soup 3 introspection data, Meson, Ninja, and the +GLib schema compiler. Then run: ```bash -gnome-extensions enable freeby@kelvin.local +git clone https://github.com/kcnewman/freeby.git +cd freeby +git switch dev +npm run check +npm run pack ``` -## Project structure - -``` -freeby/ -├── extension.js # Entry point (enable/disable) -├── indicator.js # Panel UI, dropdown, refresh -├── prefs.js # Settings UI in Extension Manager -├── scripts/ -│ ├── freeby.sh # Data aggregator (parallel provider fetches) -│ └── copilot-setup.sh # GitHub device-flow auth -├── schemas/ -│ └── *.gschema.xml # GSettings schema -├── build-aux/ -│ └── compile-schemas.sh -├── tests/ -│ └── freeby.bats # Shell script tests -├── .github/workflows/ -│ └── ci.yml # shellcheck, meson build, bats tests -└── docs/ - ├── s1.png - └── s2.png -``` - -## Code style - -- Keep it simple. No unnecessary abstraction. -- Lowercase sentence case for user-facing text. -- Follow existing patterns in the codebase. - -## Commits - -Use [conventional commits](https://www.conventionalcommits.org/): - -- `feat:` new feature -- `fix:` bug fix -- `docs:` documentation only -- `chore:` maintenance, tests, CI - -## Testing +For an isolated installation: ```bash -# test the data script -bash scripts/freeby.sh | python3 -m json.tool - -# run shellcheck -shellcheck scripts/freeby.sh scripts/copilot-setup.sh - -# run bats tests -bats tests/ +meson setup build --prefix=/tmp/freeby-install +meson install -C build ``` -CI runs automatically on push to `main` or `dev`, and on all pull requests. - -## Branches +`python3 tools/smoke-shell.py` additionally verifies lifecycle behavior in a +private headless GNOME Shell session when GNOME Shell is available locally. -- `main` — stable releases -- `dev` — active development +## Design and architecture -## Pull requests +- Keep `extension.js` and `prefs.js` thin GNOME entry points. +- Keep provider response parsing in `src/providers/`, orchestration and storage + in `src/services/`, shared contracts in `src/core/`, and actors in `src/ui/`. +- Preserve source scope and provider units. Unknown data stays unknown; account + limits never derive from local token totals. +- Keep collectors bounded and cancellable. Never download runtime dependencies. +- Fixtures must be synthetic or sanitized. Do not commit tokens, account IDs, + prompts, responses, or complete provider transcripts. +- Preserve GNOME theme behavior, keyboard focus, accessible names, and readable + scaling when changing the panel UI. -1. Fork and create a branch from `dev` -2. Make your changes -3. Test manually (the extension runs in your live desktop) -4. Open a PR against `dev` +The authoritative milestone gates and release process are in [plan.md](plan.md). +Project-wide agent guidance is in [AGENTS.md](AGENTS.md). diff --git a/README.md b/README.md index 8ecdf50..9353d41 100644 --- a/README.md +++ b/README.md @@ -1,120 +1,95 @@ -

-

Freeby

-

Track your free-tier AI coding tool usage at a glance.

-

- -

- Panel indicator - Dropdown menu -

- -

- Features · - Providers · - Install · - Settings · - Contributing · - Releases -

- -### Features - -- **Panel indicator** — colored `ai·N` shows available providers at a glance -- **Dropdown** — per-provider usage, limits, and reset countdown -- **Notifications** — desktop alert when a provider hits its limit -- **Auto-refresh on wake** — refreshes immediately after sleep -- **Parallel fetches** — all providers queried in parallel (~2s) -- **Configurable** — adjust refresh interval and notifications in settings -- **Accessible** — screen reader support for all UI elements -- **Theme-aware** — works with light and dark GNOME themes - -### Supported providers - -| Provider | Auth source | Data source | -|---|---|---| -| 🟡 **Codex** | `~/.codex/auth.json` | `codex-check` CLI | -| 🟢 **Cursor** | `~/.config/cursor/auth.json` | Cursor API | -| 🟠 **Copilot** | `gh` CLI or `~/.config/freeby/copilot-token` | GitHub API | - -### Install - -**Prerequisites** - -- GNOME Shell 45+ (Wayland or X11) -- `python3`, `curl`, `meson`, `ninja-build` -- `npx` (for Codex) -- `gh` CLI (for Copilot, optional) - -
-Fedora +# Freeby -```bash -sudo dnf install meson ninja-build python3 curl glib2-devel -``` -
+Freeby is a native GNOME Shell usage monitor for AI coding tools. It keeps +account limits, reset windows, recent local token activity, and model totals one +click away in the top panel. -
-Ubuntu / Debian +Version 2 is being delivered provider by provider. `v2.0.0-alpha.1` is the Codex +milestone: Codex limits and local activity are verified end to end. Cursor and +Copilot adapters remain available as opt-in previews while their dedicated +milestones are completed; Claude Code follows next. -```bash -sudo apt install meson ninja-build python3 curl libglib2.0-dev-bin -``` -
+## What the Codex milestone includes + +- Account quota windows from the installed Codex CLI's app-server interface. +- Reset countdowns without guessing missing values. +- Seven-day local token activity and per-model input, output, and cache totals. +- Cached results shown as stale while a provider independently refreshes. +- Explicit unavailable, unsupported, missing-authentication, and exhausted states. +- Bounded collectors, refresh backoff, wake refresh, cancellation on disable, and + threshold-crossing notifications. +- A native, theme-aware, keyboard-focusable GNOME panel interface. -**Build and install** +Freeby stores only derived usage metadata under the standard XDG state/cache +directories. It does not copy prompts, responses, transcripts, or credentials. +Local activity covers this device only and must not be interpreted as billing or +subscription usage. + +## Requirements + +- GNOME Shell 50 (the version verified for this prerelease). +- Codex CLI installed and signed in for account limits. +- GJS with Gio/GLib and Soup 3 introspection data. +- Meson, Ninja, and `glib-compile-schemas` when installing from source. + +## Install from source + +Development happens on the `dev` branch: ```bash -git clone https://github.com/kcnewman/freeby.git && cd freeby -meson setup build --prefix=$HOME/.local +git clone https://github.com/kcnewman/freeby.git +cd freeby +git switch dev +meson setup build --prefix="$HOME/.local" meson install -C build +gnome-extensions enable freeby@kelvin.local ``` -Then restart your session and enable: +Log out and back in if GNOME Shell has not discovered the extension. An archive +from a GitHub release can instead be installed with: ```bash -gnome-extensions enable freeby@kelvin.local +gnome-extensions install --force freeby@kelvin.local-2.0.0-alpha.1.zip ``` -> **Copilot users:** If `gh` isn't installed, run `copilot-setup.sh` first. - -### Settings +## Settings -| Setting | Default | Range | Description | -|---|---|---|---| -| Refresh interval | `120s` | 30–3600s | How often to check usage | -| Notifications | `on` | — | Alert when a provider hits its limit | +The alpha.1 preferences window controls refresh frequency and notifications. +The underlying schema also supports the default provider, ordered enabled +providers, history retention, and notification threshold; these receive their +full preferences interface in alpha.5. -Configure via Extension Manager or CLI: +To opt into a preview adapter during development: ```bash -gsettings --schemadir ~/.local/share/glib-2.0/schemas \ - set org.gnome.shell.extensions.freeby refresh-interval 60 +gsettings set org.gnome.shell.extensions.freeby enabled-providers "['codex', 'cursor', 'copilot']" ``` -### Uninstall +Preview providers are not part of the alpha.1 compatibility promise. + +## Verify a checkout ```bash -gnome-extensions disable freeby@kelvin.local -rm -rf ~/.local/share/gnome-shell/extensions/freeby@kelvin.local -rm -f ~/.local/bin/freeby.sh ~/.local/bin/copilot-setup.sh -rm -f ~/.local/share/glib-2.0/schemas/org.gnome.shell.extensions.freeby.gschema.xml -rm -f ~/.local/share/glib-2.0/schemas/gschemas.compiled +npm run check +npm run pack +python3 tools/smoke-shell.py ``` -### Debug +The final command starts a private headless GNOME session and does not enable the +extension in the active desktop. Live-provider checks are intentionally separate +from credential-free automated fixtures. -```bash -# test the data script -bash ~/.local/bin/freeby.sh | python3 -m json.tool +## Uninstall or downgrade -# watch extension logs -journalctl -f -o cat /usr/bin/gnome-shell +```bash +gnome-extensions disable freeby@kelvin.local +rm -rf "$HOME/.local/share/gnome-shell/extensions/freeby@kelvin.local" ``` -### Contributing - -See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and guidelines. - -### License +The extension-local schema is removed with that directory; shared system schema +artifacts are never deleted. To downgrade, install an older release archive with +`gnome-extensions install --force` and restart the session. -[MIT](LICENSE) +See [CONTRIBUTING.md](CONTRIBUTING.md) for development practices and +[plan.md](plan.md) for the ordered provider milestones. Freeby is licensed under +the [MIT License](LICENSE). diff --git a/plan.md b/plan.md index 4f52b15..a183157 100644 --- a/plan.md +++ b/plan.md @@ -1,6 +1,7 @@ # Freeby usage monitor roadmap -Updated: 2026-09-06. Status: roadmap agreed; implementation milestones have not started. +Updated: 2026-09-06. Status: foundation and Codex implementation complete; +alpha.1 release publication pending. ## Agreed direction @@ -122,14 +123,19 @@ coverage during the foundation work: ### 1. Foundation and Codex — v2.0.0-alpha.1 -- [ ] Establish the target module boundaries, versioned contract, development commands, and fixture-based tests. -- [ ] Repair the known lifecycle, notification, quota-state, and install/uninstall regressions. -- [ ] Implement bounded Codex collection, cached results, available quota windows, daily activity, and model totals. -- [ ] Build the native provider header, quota meters, activity/model views, status footer, and loading/error/setup states. -- [ ] Keep existing provider behavior working until deliberately migrated; document any verified data limitations. -- [ ] Verify the complete Codex path, temporary installation, disable/re-enable cleanup, and offline/partial behavior. +- [x] Establish the target module boundaries, versioned contract, development commands, and fixture-based tests. +- [x] Repair the known lifecycle, notification, quota-state, and install/uninstall regressions. +- [x] Implement bounded Codex collection, cached results, available quota windows, daily activity, and model totals. +- [x] Build the native provider header, quota meters, activity/model views, status footer, and loading/error/setup states. +- [x] Keep existing provider behavior working until deliberately migrated; document any verified data limitations. +- [x] Verify the complete Codex path, temporary installation, disable/re-enable cleanup, and offline/partial behavior. - [ ] Commit, tag, publish, and verify the installable alpha.1 prerelease. +Evidence: 13 deterministic unit tests, GJS incremental-history and process +integration checks, strict schema validation, deterministic packaging, an +isolated GNOME Shell 50 load/disable/re-enable smoke test, and a redacted live +Codex CLI check confirming account quota, seven-day activity, and model data. + ### 2. Claude Code — v2.0.0-alpha.2 - [ ] Implement Claude detection, supported authentication/limits, and incremental usage history. From fc3827c9f1f7d55234921af0b94b95040002a807 Mon Sep 17 00:00:00 2001 From: kcnewman Date: Sun, 6 Sep 2026 08:18:16 +0000 Subject: [PATCH 15/44] chore: use canonical repository URL --- CONTRIBUTING.md | 2 +- README.md | 2 +- metadata.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b5aa942..58ed454 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ Install Node.js 20 or newer, GJS, Soup 3 introspection data, Meson, Ninja, and t GLib schema compiler. Then run: ```bash -git clone https://github.com/kcnewman/freeby.git +git clone https://github.com/oo7kc/freeby.git cd freeby git switch dev npm run check diff --git a/README.md b/README.md index 9353d41..95f1538 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ subscription usage. Development happens on the `dev` branch: ```bash -git clone https://github.com/kcnewman/freeby.git +git clone https://github.com/oo7kc/freeby.git cd freeby git switch dev meson setup build --prefix="$HOME/.local" diff --git a/metadata.json b/metadata.json index 6febcdd..2a7c896 100644 --- a/metadata.json +++ b/metadata.json @@ -4,5 +4,5 @@ "description": "AI coding usage monitor for account limits, reset windows, local token activity and model breakdowns. Codex is verified; additional providers arrive through version 2 prereleases.", "shell-version": ["50"], "settings-schema": "org.gnome.shell.extensions.freeby", - "url": "https://github.com/kcnewman/freeby" + "url": "https://github.com/oo7kc/freeby" } From ce641464e7ca43bbe7718f6d84ef290ca3f2acc8 Mon Sep 17 00:00:00 2001 From: kcnewman Date: Sun, 6 Sep 2026 08:19:24 +0000 Subject: [PATCH 16/44] ci: update GitHub actions to Node 24 releases --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 202ad28..a73669d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,8 +10,8 @@ jobs: verify: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@v7 + - uses: actions/setup-node@v7 with: node-version: 22 - name: Install dependencies @@ -23,7 +23,7 @@ jobs: meson setup build --prefix="$RUNNER_TEMP/freeby-install" meson install -C build test -f "$RUNNER_TEMP/freeby-install/share/gnome-shell/extensions/freeby@kelvin.local/schemas/gschemas.compiled" - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: freeby-extension path: dist/*.zip From 6a4bad80cf75a6ff49af16d010b79a34eea8eade Mon Sep 17 00:00:00 2001 From: kcnewman Date: Sun, 6 Sep 2026 08:22:27 +0000 Subject: [PATCH 17/44] docs: record the alpha.1 prerelease --- plan.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/plan.md b/plan.md index a183157..6f978ec 100644 --- a/plan.md +++ b/plan.md @@ -1,7 +1,7 @@ # Freeby usage monitor roadmap -Updated: 2026-09-06. Status: foundation and Codex implementation complete; -alpha.1 release publication pending. +Updated: 2026-09-06. Status: alpha.1 released; Claude Code is the active +alpha.2 milestone. ## Agreed direction @@ -129,12 +129,16 @@ coverage during the foundation work: - [x] Build the native provider header, quota meters, activity/model views, status footer, and loading/error/setup states. - [x] Keep existing provider behavior working until deliberately migrated; document any verified data limitations. - [x] Verify the complete Codex path, temporary installation, disable/re-enable cleanup, and offline/partial behavior. -- [ ] Commit, tag, publish, and verify the installable alpha.1 prerelease. +- [x] Commit, tag, publish, and verify the installable alpha.1 prerelease. Evidence: 13 deterministic unit tests, GJS incremental-history and process integration checks, strict schema validation, deterministic packaging, an isolated GNOME Shell 50 load/disable/re-enable smoke test, and a redacted live Codex CLI check confirming account quota, seven-day activity, and model data. +Released from commit `ce64146` on `dev` as +[`v2.0.0-alpha.1`](https://github.com/oo7kc/freeby/releases/tag/v2.0.0-alpha.1). +The downloaded 22,488-byte archive matched the tagged CI artifact at SHA-256 +`eef63b1978b13f2adda9fe3c172cebfe7584835461f1b07f2b8ffd8b887b56fb`. ### 2. Claude Code — v2.0.0-alpha.2 From 7bd9f2021cbfe88de183f146f38f92dcb3b77bad Mon Sep 17 00:00:00 2001 From: kcnewman Date: Sun, 6 Sep 2026 08:27:49 +0000 Subject: [PATCH 18/44] feat: add Claude Code usage monitoring --- meson.build | 2 +- metadata.json | 2 +- package.json | 2 +- ....gnome.shell.extensions.freeby.gschema.xml | 2 +- src/collector/main.js | 22 ++- src/core/usage.js | 11 +- src/providers/claude.js | 150 ++++++++++++++++++ tests/integration/collector.js | 14 ++ tests/unit/claude.test.js | 68 ++++++++ tests/unit/usage.test.js | 16 +- 10 files changed, 277 insertions(+), 12 deletions(-) create mode 100644 src/providers/claude.js create mode 100644 tests/unit/claude.test.js diff --git a/meson.build b/meson.build index 6c0968f..684694c 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('freeby', version: '2.0.0-alpha.1', license: 'MIT', +project('freeby', version: '2.0.0-alpha.2', license: 'MIT', meson_version: '>= 0.56.0', default_options: ['warning_level=0']) diff --git a/metadata.json b/metadata.json index 2a7c896..959159c 100644 --- a/metadata.json +++ b/metadata.json @@ -1,7 +1,7 @@ { "uuid": "freeby@kelvin.local", "name": "Freeby", - "description": "AI coding usage monitor for account limits, reset windows, local token activity and model breakdowns. Codex is verified; additional providers arrive through version 2 prereleases.", + "description": "AI coding usage monitor for account limits, reset windows, local token activity and model breakdowns. Supports Codex and Claude Code; additional providers arrive through version 2 prereleases.", "shell-version": ["50"], "settings-schema": "org.gnome.shell.extensions.freeby", "url": "https://github.com/oo7kc/freeby" diff --git a/package.json b/package.json index efe5214..ec38f15 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "freeby", - "version": "2.0.0-alpha.1", + "version": "2.0.0-alpha.2", "private": true, "type": "module", "scripts": { diff --git a/schemas/org.gnome.shell.extensions.freeby.gschema.xml b/schemas/org.gnome.shell.extensions.freeby.gschema.xml index 982981b..93a06c8 100644 --- a/schemas/org.gnome.shell.extensions.freeby.gschema.xml +++ b/schemas/org.gnome.shell.extensions.freeby.gschema.xml @@ -14,7 +14,7 @@ Show desktop notifications when a provider hits its limit - ['codex'] + ['codex', 'claude'] Enabled providers in display order diff --git a/src/collector/main.js b/src/collector/main.js index fd7b87a..1512131 100644 --- a/src/collector/main.js +++ b/src/collector/main.js @@ -3,6 +3,7 @@ import GLib from 'gi://GLib'; import GLibUnix from 'gi://GLibUnix'; import System from 'system'; import {record, section} from '../core/usage.js'; +import {collectClaude} from '../providers/claude.js'; import {collectCodex} from '../providers/codex.js'; import {collectLegacy} from '../providers/legacy.js'; import {fingerprint, findCommand, join, readJson} from '../services/files.js'; @@ -11,8 +12,8 @@ import {requestJson} from '../services/http.js'; import {RpcClient, runCommand} from '../services/process.js'; const id = ARGV[0]; -if (!['codex', 'cursor', 'copilot'].includes(id)) { - printerr('Usage: gjs -m src/collector/main.js [retention-days]'); +if (!['codex', 'claude', 'cursor', 'copilot'].includes(id)) { + printerr('Usage: gjs -m src/collector/main.js [retention-days]'); System.exit(2); } const retention = Math.max(7, Math.min(90, Number(ARGV[1]) || 30)); @@ -24,10 +25,22 @@ const signal = GLibUnix.signal_add(GLib.PRIORITY_DEFAULT, 15, () => { }); const io = { fingerprint, + hasCommand(name) { + try { findCommand(name); return true; } catch { return false; } + }, scan(provider, suffixes, parser) { - const root = GLib.getenv('CODEX_HOME') || join(GLib.get_home_dir(), '.codex'); + const root = provider === 'codex' + ? GLib.getenv('CODEX_HOME') || join(GLib.get_home_dir(), '.codex') + : GLib.getenv('CLAUDE_CONFIG_DIR') || join(GLib.get_home_dir(), '.claude'); return scanHistory(provider, suffixes.map(suffix => join(root, suffix)), parser, {retention}); }, + credentials(provider) { + if (provider === 'claude') { + const root = GLib.getenv('CLAUDE_CONFIG_DIR') || join(GLib.get_home_dir(), '.claude'); + return readJson(join(root, '.credentials.json')); + } + return null; + }, codexClient: () => new RpcClient([findCommand('codex'), 'app-server'], cancellable), http: (url, options) => requestJson(url, {...options, cancellable}), async token(provider) { @@ -49,7 +62,8 @@ const io = { (async () => { try { - const result = id === 'codex' ? await collectCodex(io) : await collectLegacy(id, io); + const result = id === 'codex' ? await collectCodex(io) + : id === 'claude' ? await collectClaude(io) : await collectLegacy(id, io); if (!cancellable.is_cancelled()) print(JSON.stringify(result)); } catch { diff --git a/src/core/usage.js b/src/core/usage.js index d1a95cd..dedaaba 100644 --- a/src/core/usage.js +++ b/src/core/usage.js @@ -1,5 +1,5 @@ export const SCHEMA_VERSION = 1; -export const NAMES = {codex: 'Codex', cursor: 'Cursor', copilot: 'Copilot'}; +export const NAMES = {codex: 'Codex', claude: 'Claude Code', cursor: 'Cursor', copilot: 'Copilot'}; export const STATES = new Set(['loading', 'ready', 'partial', 'stale', 'missing-auth', 'unsupported', 'unavailable']); export const WINDOW_STATES = new Set(['active', 'exhausted', 'unlimited']); @@ -42,7 +42,8 @@ export function windowUsage({id, label, usedPercent, used, limit, unit = 'percen export function validTime(value) { if (value === null || value === undefined || value === '') return null; - const ms = typeof value === 'number' ? value : Date.parse(value); + const numeric = typeof value === 'number' || /^\d+(\.\d+)?$/.test(String(value).trim()) ? Number(value) : null; + const ms = numeric !== null ? (numeric < 1e12 ? numeric * 1000 : numeric) : Date.parse(value); return Number.isFinite(ms) && ms > 0 ? ms : null; } @@ -78,7 +79,11 @@ export function mergeRecord(previous, next) { const current = next[key]; if (['unavailable', 'missing-auth'].includes(current.status) && old?.updatedAt && ['ready', 'partial', 'stale'].includes(old.status)) { - result[key] = {...old, status: 'stale', message: current.message}; + const preserved = key === 'limits' + ? {...old, windows: old.windows.filter(window => !window.resetsAt || window.resetsAt > Date.now())} + : old; + if (key !== 'limits' || preserved.windows.length) + result[key] = {...preserved, status: 'stale', message: current.message}; } } result.plan ??= previous.plan; diff --git a/src/providers/claude.js b/src/providers/claude.js new file mode 100644 index 0000000..737fd96 --- /dev/null +++ b/src/providers/claude.js @@ -0,0 +1,150 @@ +import {localDate, number, record, section, validTime, windowUsage} from '../core/usage.js'; + +const USAGE_URL = 'https://api.anthropic.com/api/oauth/usage'; + +function planLabel(tier, subscription) { + const match = String(tier || '').match(/max_(\d+x)/i); + if (match) + return `Max ${match[1]}`; + const value = String(subscription || '').trim(); + return value ? value[0].toUpperCase() + value.slice(1) : null; +} + +export function claudeLogin(credentials) { + const login = credentials?.claudeAiOauth; + if (!login || typeof login !== 'object') + return {token: null, expiresAt: null, plan: null}; + return {token: String(login.accessToken || '') || null, + expiresAt: number(login.expiresAt), + plan: planLabel(login.rateLimitTier, login.subscriptionType)}; +} + +function rawUtilization(value) { + if (value === null || value === undefined || value === '') + return null; + const result = Number(String(value).trim().replace('%', '')); + return Number.isFinite(result) && result >= 0 ? result : null; +} + +function utilization(value, percentScale) { + const result = rawUtilization(value); + if (result === null) + return null; + return Math.min(100, percentScale || result > 1 ? result : result * 100); +} + +function scopedDuration(kind) { + const value = String(kind || '').toLowerCase(); + if (value.includes('month')) + return {label: 'Monthly', minutes: 43200}; + if (value.includes('week') || value.includes('day')) + return {label: 'Weekly', minutes: 10080}; + if (value.includes('hour') || value.includes('session')) + return {label: 'Session', minutes: 300}; + return {label: '', minutes: null}; +} + +export function claudeLimits(payload, now = Date.now()) { + if (!payload || typeof payload !== 'object') + return {...section('unavailable', 'Claude did not report any quota windows.'), scope: 'account', windows: []}; + const session = payload.five_hour; + const weekly = payload.seven_day_oauth_apps ?? payload.seven_day; + const scoped = Array.isArray(payload.limits) ? payload.limits : []; + const raw = [session?.utilization, weekly?.utilization, + ...scoped.map(item => item?.percent)].map(rawUtilization).filter(value => value !== null); + const percentScale = raw.some(value => value >= 1); + const windows = []; + const add = (id, label, bucket, durationMinutes) => { + if (!bucket || typeof bucket !== 'object') + return; + const item = windowUsage({id, label, usedPercent: utilization(bucket.utilization, percentScale), + durationMinutes, resetsAt: validTime(bucket.resets_at)}); + if (item) + windows.push(item); + }; + add('five-hour', 'Session · 5 hours', session, 300); + add('weekly', 'Weekly', weekly, 10080); + const seen = new Set(); + for (const item of scoped) { + const model = item?.scope?.model; + const name = String(model?.display_name || model?.id || '').trim(); + const kind = String(item?.kind || '').trim(); + const key = `${name}:${kind}`; + if (!name || seen.has(key)) + continue; + const duration = scopedDuration(kind); + const value = windowUsage({id: `scoped:${key}`, label: `${name}${duration.label ? ` · ${duration.label}` : ''}`, + usedPercent: utilization(item.percent, percentScale), durationMinutes: duration.minutes, + resetsAt: validTime(item.resets_at)}); + if (value) { + seen.add(key); + windows.push(value); + } + } + return windows.length + ? {...section('ready'), updatedAt: now, scope: 'account', source: 'Anthropic OAuth usage', windows} + : {...section('unavailable', 'Claude did not report any supported quota windows.'), scope: 'account', windows: []}; +} + +export function parseClaudeEvent(entry, state) { + const message = entry?.message && typeof entry.message === 'object' ? entry.message : {}; + if (entry?.type !== 'assistant' && message.role !== 'assistant') + return null; + const usage = message.usage ?? entry.usage; + if (!usage || typeof usage !== 'object') + return null; + if (entry.sessionId) + state.session = entry.sessionId; + const timestamp = entry.timestamp ?? message.timestamp; + const date = localDate(timestamp); + if (!date) + return null; + const input = number(usage.input_tokens ?? usage.inputTokens) ?? 0; + const output = number(usage.output_tokens ?? usage.outputTokens) ?? 0; + const cacheRead = number(usage.cache_read_input_tokens ?? usage.cacheReadInputTokens) ?? 0; + const cacheWrite = number(usage.cache_creation_input_tokens ?? usage.cacheCreationInputTokens) ?? 0; + if (input + output + cacheRead + cacheWrite === 0) + return null; + const model = message.model ?? entry.model ?? state.model ?? 'Unknown model'; + state.model = model; + const identity = message.id ?? entry.messageId ?? entry.uuid ?? entry.requestId ?? `${timestamp}:${JSON.stringify(usage)}`; + return {id: `${state.session}:${identity}`, session: state.session, date, model, + input, output, cacheRead, cacheWrite}; +} + +export async function collectClaude(io) { + const result = record('claude'); + result.capabilities = {limits: true, history: true, models: true}; + result.history = io.scan('claude', ['projects'], parseClaudeEvent); + const login = claudeLogin(io.credentials('claude')); + result.plan = login.plan; + if (!login.token) { + const installed = io.hasCommand?.('claude') !== false; + result.limits = {...result.limits, ...section(installed ? 'missing-auth' : 'unsupported', + installed ? 'Run claude auth login to read account limits.' : 'Install Claude Code, then sign in to read account limits.')}; + return result; + } + result.accountKey = io.fingerprint(login.token); + if (login.expiresAt && login.expiresAt <= Date.now()) { + result.limits = {...result.limits, ...section('missing-auth', 'Claude Code sign-in expired. Start Claude Code or run claude auth login.')}; + return result; + } + try { + const response = await io.http(USAGE_URL, {headers: { + Authorization: `Bearer ${login.token}`, + 'anthropic-beta': 'oauth-2025-04-20', + Accept: 'application/json', + }}); + if (response.status === 200) { + result.limits = claudeLimits(response.data); + } else { + result.limits = {...result.limits, ...section([401, 403].includes(response.status) ? 'missing-auth' : 'unavailable', + [401, 403].includes(response.status) ? 'Reconnect Claude Code to read account limits.' : + response.status === 429 ? 'Anthropic is rate limiting usage checks. Local history is still available.' : + `Claude usage endpoint unavailable (HTTP ${response.status}).`)}; + } + } catch { + result.limits = {...result.limits, ...section('unavailable', 'Could not reach Claude usage. Local history is still available.')}; + } + return result; +} diff --git a/tests/integration/collector.js b/tests/integration/collector.js index 6b32b3d..903def4 100644 --- a/tests/integration/collector.js +++ b/tests/integration/collector.js @@ -3,6 +3,7 @@ import GLib from 'gi://GLib'; import System from 'system'; import {scanHistory} from '../../src/services/history.js'; import {parseCodexEvent} from '../../src/providers/codex.js'; +import {parseClaudeEvent} from '../../src/providers/claude.js'; import {readJson, writeJson, join} from '../../src/services/files.js'; import {runCommand} from '../../src/services/process.js'; @@ -34,6 +35,19 @@ writeJson(join(scratch, 'record.json'), {safe: true}); assert(readJson(join(scratch, 'record.json')).safe, 'atomic JSON roundtrip'); print('PASS: GJS history initial scan, cached scan, append, partial line, and private JSON cache'); +const claudeProjects = join(scratch, 'claude-projects'); +GLib.mkdir_with_parents(claudeProjects, 0o700); +const claudeLine = JSON.stringify({type: 'assistant', sessionId: 'claude-session', timestamp: '2026-09-06T11:00:00Z', + message: {id: 'claude-message', role: 'assistant', model: 'claude-test', usage: {input_tokens: 2, + output_tokens: 3, cache_read_input_tokens: 40, cache_creation_input_tokens: 5}}}); +GLib.file_set_contents(join(claudeProjects, 'session.jsonl'), `${claudeLine}\n${claudeLine}\n`); +const claudeCache = join(scratch, 'claude-cache.json'); +const claude = scanHistory('claude', [claudeProjects], parseClaudeEvent, {now, cachePath: claudeCache}); +assert(claude.days.at(-1).total === 50, 'duplicate Claude messages must count once'); +assert(claude.models[0].cacheRead === 40 && claude.models[0].cacheWrite === 5, 'Claude cache categories'); +assert(!JSON.stringify(readJson(claudeCache)).includes('claude-session'), 'Claude session identity must be sanitized'); +print('PASS: GJS Claude history deduplication, model totals, and private cache'); + const loop = new GLib.MainLoop(null, false); let failed = false; (async () => { diff --git a/tests/unit/claude.test.js b/tests/unit/claude.test.js new file mode 100644 index 0000000..e43a948 --- /dev/null +++ b/tests/unit/claude.test.js @@ -0,0 +1,68 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {claudeLimits, claudeLogin, collectClaude, parseClaudeEvent} from '../../src/providers/claude.js'; + +test('reads percent-scaled standard and model-scoped Claude windows', () => { + const result = claudeLimits({ + five_hour: {utilization: 1, resets_at: '2026-09-06T15:00:00Z'}, + seven_day_oauth_apps: {utilization: 24.5, resets_at: '2026-09-13T10:00:00Z'}, + limits: [ + {kind: 'seven_day_scoped', percent: 70, resets_at: '2026-09-13T10:00:00Z', + scope: {model: {id: 'opus', display_name: 'Opus'}}}, + {kind: 'seven_day_scoped', percent: 70, scope: {model: {id: 'opus', display_name: 'Opus'}}}, + ], + }, 100); + assert.equal(result.status, 'ready'); + assert.equal(result.windows.length, 3); + assert.equal(result.windows[0].usedPercent, 1); + assert.equal(result.windows[0].durationMinutes, 300); + assert.equal(result.windows[2].label, 'Opus · Weekly'); +}); + +test('supports older fractional utilization without turning one percent into full usage', () => { + const result = claudeLimits({five_hour: {utilization: 0.25}, seven_day: {utilization: 0.5}}); + assert.deepEqual(result.windows.map(window => window.usedPercent), [25, 50]); + assert.equal(claudeLimits({}).status, 'unavailable'); +}); + +test('Claude login exposes only the needed token, expiry and display plan', () => { + assert.deepEqual(claudeLogin({}), {token: null, expiresAt: null, plan: null}); + assert.deepEqual(claudeLogin({claudeAiOauth: {accessToken: 'secret', expiresAt: 20, + rateLimitTier: 'default_claude_max_20x', subscriptionType: 'max'}}), + {token: 'secret', expiresAt: 20, plan: 'Max 20x'}); +}); + +test('parses Claude cache categories independently and provides stable message identity', () => { + const state = {session: 'file'}; + const event = parseClaudeEvent({type: 'assistant', sessionId: 'session', timestamp: '2026-09-06T10:00:00Z', + message: {id: 'message', role: 'assistant', model: 'claude-opus', usage: {input_tokens: 2, + output_tokens: 3, cache_read_input_tokens: 40, cache_creation_input_tokens: 5}}}, state); + assert.deepEqual({input: event.input, output: event.output, cacheRead: event.cacheRead, cacheWrite: event.cacheWrite}, + {input: 2, output: 3, cacheRead: 40, cacheWrite: 5}); + assert.equal(event.id, 'session:message'); + assert.equal(event.model, 'claude-opus'); +}); + +test('missing or expired Claude auth preserves local history and skips the endpoint', async () => { + const history = {status: 'ready', days: [], models: []}; + let requests = 0; + const io = credentials => ({scan: () => history, credentials: () => credentials, + fingerprint: () => 'hash', http: async () => { requests++; return {status: 200, data: {}}; }}); + const missing = await collectClaude(io(null)); + assert.equal(missing.limits.status, 'missing-auth'); + assert.equal(missing.history, history); + const expired = await collectClaude(io({claudeAiOauth: {accessToken: 'token', expiresAt: 1}})); + assert.equal(expired.limits.status, 'missing-auth'); + assert.equal(requests, 0); + const absent = await collectClaude({...io(null), hasCommand: () => false}); + assert.equal(absent.limits.status, 'unsupported'); +}); + +test('Claude endpoint authentication failure remains independent from history', async () => { + const result = await collectClaude({scan: () => ({status: 'ready', days: [], models: []}), + credentials: () => ({claudeAiOauth: {accessToken: 'token'}}), fingerprint: () => 'hash', + http: async () => ({status: 401, data: {}})}); + assert.equal(result.capabilities.models, true); + assert.equal(result.limits.status, 'missing-auth'); + assert.equal(result.history.status, 'ready'); +}); diff --git a/tests/unit/usage.test.js b/tests/unit/usage.test.js index 159cd26..ec14532 100644 --- a/tests/unit/usage.test.js +++ b/tests/unit/usage.test.js @@ -1,6 +1,6 @@ import test from 'node:test'; import assert from 'node:assert/strict'; -import {aggregateEvents, highestUsage, mergeRecord, number, recentDates, record, validateRecord, windowUsage} from '../../src/core/usage.js'; +import {aggregateEvents, highestUsage, mergeRecord, number, recentDates, record, validTime, validateRecord, windowUsage} from '../../src/core/usage.js'; import {resetTime, tokens} from '../../src/core/format.js'; import {ThresholdTracker} from '../../src/core/notifications.js'; @@ -45,6 +45,19 @@ test('failure preserves last successful values as stale, account change discards assert.equal(mergeRecord(old, next).limits.windows.length, 0); }); +test('stale quota windows are discarded after their reset', () => { + const old = record('claude'); + old.limits = {status: 'ready', updatedAt: 100, windows: [ + {id: 'expired', resetsAt: 1}, + {id: 'open', resetsAt: Date.now() + 60000}, + ]}; + const next = record('claude'); + next.limits.status = 'unavailable'; + const result = mergeRecord(old, next); + assert.equal(result.limits.status, 'stale'); + assert.deepEqual(result.limits.windows.map(window => window.id), ['open']); +}); + test('daily and model totals use the same period and deduplicate events', () => { const event = {id: 'one', session: 'a', model: 'model-a', date: '2026-09-06', input: 20, output: 5, cacheRead: 10, cacheWrite: 0}; const result = aggregateEvents([event, event, {...event, id: 'older', date: '2026-08-01'}], new Date('2026-09-06T12:00:00').getTime()); @@ -57,6 +70,7 @@ test('daily and model totals use the same period and deduplicate events', () => test('calendar buckets are consecutive across month boundaries', () => { assert.deepEqual(recentDates(new Date('2026-03-02T12:00:00').getTime(), 3), ['2026-02-28', '2026-03-01', '2026-03-02']); + assert.equal(validTime('1800000000'), 1800000000000); }); test('notifications require a verified crossing and deduplicate warning and limit separately', () => { From 48b28acf925efd0aea0fad158ff24a4080e9d8a7 Mon Sep 17 00:00:00 2001 From: kcnewman Date: Sun, 6 Sep 2026 08:27:58 +0000 Subject: [PATCH 19/44] docs: prepare the alpha.2 milestone release --- CHANGELOG.md | 25 ++++++++++++++++++++++++ README.md | 28 ++++++++++++++++----------- docs/providers/claude.md | 41 ++++++++++++++++++++++++++++++++++++++++ plan.md | 15 ++++++++++----- 4 files changed, 93 insertions(+), 16 deletions(-) create mode 100644 docs/providers/claude.md diff --git a/CHANGELOG.md b/CHANGELOG.md index e9f80d6..a89cead 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,31 @@ All notable changes are documented here. Freeby uses semantic prerelease versions while version 2 is developed on the `dev` branch. +## 2.0.0-alpha.2 - 2026-09-06 + +### Added + +- Claude Code detection and saved OAuth sign-in handling, including explicit + missing, absent, and expired authentication states. +- Anthropic 5-hour, weekly, and model-scoped quota parsing with both current + percentage and older fractional utilization normalization. +- Incremental Claude Code transcript scanning with message deduplication and + per-model input, output, cache-read, and cache-write totals. +- Provider switching between Codex and Claude Code using the shared native UI. + +### Changed + +- New installations enable Codex and Claude Code by default. +- Expired cached quota windows are discarded instead of being displayed as stale + after their reset time. + +### Verification limits + +- Claude Code 2.1.218 detection and the unauthenticated path were exercised on + this machine. Quota responses, expired authentication, duplicates, and cache + semantics were verified with synthetic fixtures against the current upstream + interface; no signed-in Claude account was available for a live quota probe. + ## 2.0.0-alpha.1 - 2026-09-06 ### Added diff --git a/README.md b/README.md index 95f1538..1b2735e 100644 --- a/README.md +++ b/README.md @@ -4,16 +4,18 @@ Freeby is a native GNOME Shell usage monitor for AI coding tools. It keeps account limits, reset windows, recent local token activity, and model totals one click away in the top panel. -Version 2 is being delivered provider by provider. `v2.0.0-alpha.1` is the Codex -milestone: Codex limits and local activity are verified end to end. Cursor and -Copilot adapters remain available as opt-in previews while their dedicated -milestones are completed; Claude Code follows next. +Version 2 is being delivered provider by provider. `v2.0.0-alpha.2` adds Claude +Code to the Codex foundation. Cursor and Copilot adapters remain available as +opt-in previews while their dedicated milestones are completed. -## What the Codex milestone includes +## What the current prerelease includes - Account quota windows from the installed Codex CLI's app-server interface. +- Claude Code 5-hour, weekly, and model-scoped limits through its saved OAuth + sign-in, with clear missing/expired-authentication states. - Reset countdowns without guessing missing values. -- Seven-day local token activity and per-model input, output, and cache totals. +- Seven-day local Codex and Claude Code activity with per-model input, output, + cache-read, and cache-write totals. - Cached results shown as stale while a provider independently refreshes. - Explicit unavailable, unsupported, missing-authentication, and exhausted states. - Bounded collectors, refresh backoff, wake refresh, cancellation on disable, and @@ -28,7 +30,9 @@ subscription usage. ## Requirements - GNOME Shell 50 (the version verified for this prerelease). -- Codex CLI installed and signed in for account limits. +- Codex CLI installed and signed in for Codex account limits. +- Claude Code installed and signed in for Claude account limits; local Claude + transcripts remain useful independently. - GJS with Gio/GLib and Soup 3 introspection data. - Meson, Ninja, and `glib-compile-schemas` when installing from source. @@ -49,12 +53,12 @@ Log out and back in if GNOME Shell has not discovered the extension. An archive from a GitHub release can instead be installed with: ```bash -gnome-extensions install --force freeby@kelvin.local-2.0.0-alpha.1.zip +gnome-extensions install --force freeby@kelvin.local-2.0.0-alpha.2.zip ``` ## Settings -The alpha.1 preferences window controls refresh frequency and notifications. +The alpha.2 preferences window controls refresh frequency and notifications. The underlying schema also supports the default provider, ordered enabled providers, history retention, and notification threshold; these receive their full preferences interface in alpha.5. @@ -62,10 +66,12 @@ full preferences interface in alpha.5. To opt into a preview adapter during development: ```bash -gsettings set org.gnome.shell.extensions.freeby enabled-providers "['codex', 'cursor', 'copilot']" +gsettings set org.gnome.shell.extensions.freeby enabled-providers "['codex', 'claude', 'cursor', 'copilot']" ``` -Preview providers are not part of the alpha.1 compatibility promise. +Preview providers are not part of the alpha.2 compatibility promise. See the +[Claude provider notes](docs/providers/claude.md) for scope and compatibility +details. ## Verify a checkout diff --git a/docs/providers/claude.md b/docs/providers/claude.md new file mode 100644 index 0000000..9ce8fb1 --- /dev/null +++ b/docs/providers/claude.md @@ -0,0 +1,41 @@ +# Claude Code provider + +Freeby keeps Claude's account limits and local activity as separate sources. +Failure of one source does not erase valid data from the other. + +## Account limits + +The collector reads only the `claudeAiOauth` fields needed from +`$CLAUDE_CONFIG_DIR/.credentials.json` (or `~/.claude/.credentials.json`) and +sends the access token only to `https://api.anthropic.com/api/oauth/usage`. +It supports the current 5-hour, weekly, and model-scoped response buckets. The +token itself is never cached or logged; only a SHA-256 account fingerprint may +be retained to prevent stale data crossing accounts. + +This OAuth usage interface is used by current Claude Code integrations but is +not a stable public Anthropic API contract. Freeby treats unknown/empty payloads +as unavailable, retains only unexpired stale windows after transient failures, +and keeps local history visible when sign-in expires or the endpoint is offline. + +## Local activity + +Claude Code assistant-message usage is scanned incrementally from +`$CLAUDE_CONFIG_DIR/projects/**/*.jsonl` or `~/.claude/projects/**/*.jsonl`. +Freeby reads timestamps, model identifiers, message/session identifiers, and the +four usage counters only. Prompt and response content is ignored. + +`input_tokens`, `output_tokens`, `cache_read_input_tokens`, and +`cache_creation_input_tokens` are independent Anthropic counters, so Freeby adds +all four to activity totals without subtracting cache tokens from input. Repeated +message IDs are counted once. Session identifiers are hashed before derived data +is written to Freeby's XDG cache. + +History is local to this device, covers the displayed seven-day period, and is +not a measure of subscription quota or billable cost. + +## Alpha.2 verification + +- Claude Code 2.1.218 installation detection and missing-auth behavior: live. +- Standard/scoped limits, percentage normalization, expired auth, endpoint auth + errors, duplicate messages, and token categories: deterministic fixtures. +- Signed-in account quota probe: not available on the development machine. diff --git a/plan.md b/plan.md index 6f978ec..245802a 100644 --- a/plan.md +++ b/plan.md @@ -1,7 +1,7 @@ # Freeby usage monitor roadmap -Updated: 2026-09-06. Status: alpha.1 released; Claude Code is the active -alpha.2 milestone. +Updated: 2026-09-06. Status: Claude Code implementation complete; alpha.2 +release publication pending. ## Agreed direction @@ -142,11 +142,16 @@ The downloaded 22,488-byte archive matched the tagged CI artifact at SHA-256 ### 2. Claude Code — v2.0.0-alpha.2 -- [ ] Implement Claude detection, supported authentication/limits, and incremental usage history. -- [ ] Add provider switching and independent partial-data handling using the shared contract. -- [ ] Verify quota windows, daily/model totals, duplicate handling, missing credentials, and expired authentication. +- [x] Implement Claude detection, supported authentication/limits, and incremental usage history. +- [x] Add provider switching and independent partial-data handling using the shared contract. +- [x] Verify quota windows, daily/model totals, duplicate handling, missing credentials, and expired authentication. - [ ] Commit, tag, publish, and verify the installable alpha.2 prerelease. +Evidence: Claude Code 2.1.218 detection and missing-auth collection were checked +locally. Synthetic quota, expired-auth, repeated-message, cache-token, and +private-cache fixtures pass. An authenticated Claude account was not available, +so the OAuth endpoint path is upstream- and fixture-validated rather than live. + ### 3. Cursor — v2.0.0-alpha.3 - [ ] Revalidate current Cursor authentication and usage sources for free and paid accounts. From 24b513ae2aea5f60545fb562c318c9f5814b7247 Mon Sep 17 00:00:00 2001 From: kcnewman Date: Sun, 6 Sep 2026 08:30:23 +0000 Subject: [PATCH 20/44] docs: record the alpha.2 prerelease --- plan.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/plan.md b/plan.md index 245802a..c70b40b 100644 --- a/plan.md +++ b/plan.md @@ -1,7 +1,7 @@ # Freeby usage monitor roadmap -Updated: 2026-09-06. Status: Claude Code implementation complete; alpha.2 -release publication pending. +Updated: 2026-09-06. Status: alpha.2 released; Cursor is the active alpha.3 +milestone. ## Agreed direction @@ -145,12 +145,16 @@ The downloaded 22,488-byte archive matched the tagged CI artifact at SHA-256 - [x] Implement Claude detection, supported authentication/limits, and incremental usage history. - [x] Add provider switching and independent partial-data handling using the shared contract. - [x] Verify quota windows, daily/model totals, duplicate handling, missing credentials, and expired authentication. -- [ ] Commit, tag, publish, and verify the installable alpha.2 prerelease. +- [x] Commit, tag, publish, and verify the installable alpha.2 prerelease. Evidence: Claude Code 2.1.218 detection and missing-auth collection were checked locally. Synthetic quota, expired-auth, repeated-message, cache-token, and private-cache fixtures pass. An authenticated Claude account was not available, so the OAuth endpoint path is upstream- and fixture-validated rather than live. +Released from commit `48b28ac` on `dev` as +[`v2.0.0-alpha.2`](https://github.com/oo7kc/freeby/releases/tag/v2.0.0-alpha.2). +The downloaded 25,249-byte archive matched the tagged CI artifact at SHA-256 +`04f8417dd0fd0cafdc636293134ce80bf1477a0e6b39342e9f3681bffbef6fae`. ### 3. Cursor — v2.0.0-alpha.3 From 50620737f8270f56f49beaba561d3be991632699 Mon Sep 17 00:00:00 2001 From: kcnewman Date: Sun, 6 Sep 2026 08:37:34 +0000 Subject: [PATCH 21/44] chore: organize repository structure --- .AGENTS/README.md | 17 ++ .../plans/archive}/v1-notes.md | 2 +- plan.md => .AGENTS/plans/roadmap.md | 44 +++-- .AGENTS/references/claude-usage.jpeg | Bin 0 -> 47094 bytes .AGENTS/references/codex-usage.png | Bin 0 -> 82644 bytes .AGENTS/rules/architecture.md | 27 +++ .AGENTS/rules/quality.md | 24 +++ .AGENTS/rules/releases.md | 22 +++ .gitignore | 12 +- AGENTS.md | 45 +---- CHANGELOG.md | 17 ++ CONTRIBUTING.md | 5 +- README.md | 5 +- docs/README.md | 11 ++ docs/architecture.md | 48 +++++ docs/assets/README.md | 8 + .../screenshots/v1/panel-indicator.png} | Bin .../screenshots/v1/usage-menu.png} | Bin docs/providers/README.md | 12 ++ indicator.js | 140 ------------- meson.build | 2 +- package.json | 2 +- scripts/copilot-setup.sh | 79 -------- scripts/freeby.sh | 187 ------------------ src/collector/main.js | 6 +- src/providers/copilot.js | 33 ++++ src/providers/cursor.js | 29 +++ src/providers/legacy.js | 45 ----- tests/freeby.bats | 42 ---- tools/check.js | 32 ++- tools/package.py | 3 +- 31 files changed, 332 insertions(+), 567 deletions(-) create mode 100644 .AGENTS/README.md rename {docs/planning => .AGENTS/plans/archive}/v1-notes.md (98%) rename plan.md => .AGENTS/plans/roadmap.md (89%) create mode 100644 .AGENTS/references/claude-usage.jpeg create mode 100644 .AGENTS/references/codex-usage.png create mode 100644 .AGENTS/rules/architecture.md create mode 100644 .AGENTS/rules/quality.md create mode 100644 .AGENTS/rules/releases.md create mode 100644 docs/README.md create mode 100644 docs/architecture.md create mode 100644 docs/assets/README.md rename docs/{s1.png => assets/screenshots/v1/panel-indicator.png} (100%) rename docs/{s2.png => assets/screenshots/v1/usage-menu.png} (100%) create mode 100644 docs/providers/README.md delete mode 100644 indicator.js delete mode 100644 scripts/copilot-setup.sh delete mode 100755 scripts/freeby.sh create mode 100644 src/providers/copilot.js create mode 100644 src/providers/cursor.js delete mode 100644 src/providers/legacy.js delete mode 100644 tests/freeby.bats diff --git a/.AGENTS/README.md b/.AGENTS/README.md new file mode 100644 index 0000000..2c33f61 --- /dev/null +++ b/.AGENTS/README.md @@ -0,0 +1,17 @@ +# Agent workspace + +This directory is the single home for agent guidance, active plans, historical +planning context, and implementation reference material. The root `AGENTS.md` +exists only because agent tooling discovers that conventional filename. + +Read these always-applicable rules before making changes: + +1. [Product and architecture](rules/architecture.md) +2. [Quality, privacy, and verification](rules/quality.md) +3. [Version control and releases](rules/releases.md) + +Use [plans/roadmap.md](plans/roadmap.md) as the active milestone record. Material +under [plans/archive/](plans/archive/) is historical and must not override the +active roadmap. Visuals under [references/](references/) are design references, +not runtime assets, and are excluded from extension archives by the packaging +allowlist. diff --git a/docs/planning/v1-notes.md b/.AGENTS/plans/archive/v1-notes.md similarity index 98% rename from docs/planning/v1-notes.md rename to .AGENTS/plans/archive/v1-notes.md index 38e1e86..ed28286 100644 --- a/docs/planning/v1-notes.md +++ b/.AGENTS/plans/archive/v1-notes.md @@ -4,7 +4,7 @@ Preserved from the original local `plan.md` on 2026-09-06. These are historical investigation notes, not verified descriptions of the current implementation. Some claims about disabled providers, completed fixes, quota periods, and packaging differ from the current code. Revalidate any provider-specific -assumptions before using them. The active roadmap is [../../plan.md](../../plan.md). +assumptions before using them. The active roadmap is [../roadmap.md](../roadmap.md). --- diff --git a/plan.md b/.AGENTS/plans/roadmap.md similarity index 89% rename from plan.md rename to .AGENTS/plans/roadmap.md index c70b40b..1b6e2be 100644 --- a/plan.md +++ b/.AGENTS/plans/roadmap.md @@ -1,7 +1,7 @@ # Freeby usage monitor roadmap -Updated: 2026-09-06. Status: alpha.2 released; Cursor is the active alpha.3 -milestone. +Updated: 2026-09-06. Status: post-alpha.2 repository organization complete; +Cursor is the active alpha.3 milestone. ## Agreed direction @@ -21,8 +21,8 @@ cover all four providers; earlier prereleases introduce them incrementally. ## Reference analysis and interface -The [Claude reference](reference-images/1.jpeg) and -[Codex reference](reference-images/2.png) share this structure: +The [Claude reference](../references/claude-usage.jpeg) and +[Codex reference](../references/codex-usage.png) share this structure: 1. Provider mark, name, and reported plan. 2. A selector for connected/enabled providers; omit the selector when only one exists. @@ -52,35 +52,31 @@ process. Avoid a persistent background daemon in this first iteration. ```text freeby/ -├── AGENTS.md -├── plan.md +├── .AGENTS/ # Rules, roadmap, archive, design references +├── AGENTS.md # Tool-discovery entrypoint ├── extension.js # GNOME lifecycle entry ├── prefs.js # Preferences entry ├── metadata.json ├── stylesheet.css ├── src/ │ ├── core/ # Contract, validation, aggregation, formatting -│ ├── providers/ # Codex, Claude, Cursor, Copilot adapters +│ ├── providers/ # One adapter per provider │ ├── services/ # Scheduling, cache, processes, notifications │ ├── collector/ # Managed GJS collection entry │ ├── ui/ # Indicator, selector, meters, charts -│ └── preferences/ # Configuration pages ├── schemas/ -├── assets/ ├── tests/ │ ├── unit/ -│ ├── integration/ -│ └── fixtures/ +│ └── integration/ ├── tools/ # Development and packaging commands -├── docs/ # Architecture, provider contracts, release guide -├── reference-images/ # Development references, excluded from archives +├── docs/ # Architecture, provider docs, historical screenshots ├── meson.build └── .github/workflows/ ``` -This is a target layout, not a claim that these modules already exist. Move code -in reviewable steps and preserve existing behavior until its replacement is -verified. Keep transitional scripts only while needed; do not ship unused code. +Keep this layout current as modules land. Add a directory only when it owns real +content, preserve existing behavior during moves, and do not retain superseded +runtime implementations. ### Usage contract @@ -156,6 +152,20 @@ Released from commit `48b28ac` on `dev` as The downloaded 25,249-byte archive matched the tagged CI artifact at SHA-256 `04f8417dd0fd0cafdc636293134ce80bf1477a0e6b39342e9f3681bffbef6fae`. +### Repository organization checkpoint + +- [x] Consolidate agent rules, active/historical plans, and visual references + under `.AGENTS/` while retaining the root discovery entrypoint. +- [x] Remove obsolete v1 runtime/test files and split provider ownership cleanly. +- [x] Add architecture/documentation indexes and enforce the intended layout in + development checks. +- [x] Verify and commit the organization checkpoint on `dev` before alpha.3. + +Evidence: `npm run verify` passed 20 unit tests, the Codex/Claude GJS history +and subprocess integration suite, strict schema/layout/link validation, and a +deterministic alpha.3-dev package. The exact archive loaded, disabled, and +re-enabled without extension errors in an isolated GNOME Shell 50 session. + ### 3. Cursor — v2.0.0-alpha.3 - [ ] Revalidate current Cursor authentication and usage sources for free and paid accounts. @@ -222,7 +232,7 @@ Documentation setup is preparatory work and does not trigger a product prereleas - [Omarchy Agents architecture and panel behavior](https://github.com/omacom/omarchy/blob/quattro/shell/plugins/agents/README.md) - [Codex app-server documentation](https://learn.chatgpt.com/docs/app-server) - [GNOME extension review guidelines](https://gjs.guide/extensions/review-guidelines/review-guidelines.html) -- [Archived v1 planning notes](docs/planning/v1-notes.md), including a Cursor no-subscription investigation that requires revalidation. +- [Archived v1 planning notes](archive/v1-notes.md), including a Cursor no-subscription investigation that requires revalidation. Sources describe the state inspected during planning; verify them again before depending on a provider interface or publishing compatibility claims. If upstream diff --git a/.AGENTS/references/claude-usage.jpeg b/.AGENTS/references/claude-usage.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..65a16234f2a3f74ade20b31e8074f2c5994b6cbc GIT binary patch literal 47094 zcmce81wdTMvM7Nlgd{+43l4(?cL?rGkb&UA9fCV-1QH+vK?Z`mGgufjz~UC%Jy>wJ z;QtIs_U-Q7-FJ82z5jI6r@Bvfbyc0}s_LE@E=Ddsqui5|l9fWaas>tD3Xo7P#!Dsoa4D z$W@K=@d|25`bQ?{Umq7B(m~56pvA^a9XA+;gBoj|{oy>RC%Ka7aMU~Jb2`e^-d6sZ z*2?Wx{>v=+!) zM7Y1UY#0x8siDJLf5^LV7`>{@8X#7~jDAud;K4NYVNI@=3bsF^!>17$qP8FnS!Cjm z`j`Bgg+I|g>EFDOZxsQiqQmJj$c9JpUgekkWs;m;cuw`sriSZ#zrl_u zf15!>S|VzTAiv!5>AFl;vvCQTlZrzeVoJCJjFXe900@$ zBY>di_Y1PHn`OJ z)1i=F!a@Pngm||XvEa|1A0BrwKm9#Mlr?A18)n&6)0k}&hvMCOlIDX$qxMn_{mG-< zR=fMtNe9E4>YL@0X3{mWvolNLGz#BtD zg7^Nke*qW}8QdmyVkj&<7C3Bi-?!ysDTw&!(OZxzpni!F@4Tab8Op*vJYv#R`9(E0 zXk4669{uFViqeVwj*p0^t{G)%cKuH%w~Jc)Yo+%6ku+eZOi;_H7Fb1&dV?x(=a&{F zl7LoP@%Q34bkQ4x5=s{+*EyO)@Ou; z|K~LK8LhlJrP|dUuhWz4K(HH-3l|_4Bzu2yZLpdbkWM)W!u4XukGewQ4vtoOb8|Ud zE>M_Sy1fKDlcrTYWuDUf$!LL~O8MN$q^)Y9iEfQ{Pp@_J;od*#ifrCs$*$yagc0tg z@5JjfEdLBABf7Tl{4TsqUOj3V)A@RjwT5_NUO$Z?A7)}uebgfVTdu5Xooh1JowYg})gOpE8l zR$>0QMrE5(swUXyOjsjBgx1^GD|$b4M@Kkwcf!V@F0lG3;}Gh9q`+#O@$|HewXT}@ zvJSTM6b9$bZ<3W*S>U7)2$8XmnVY;ZTZTtca9}gC-u(mWQBYil2DvL-D^4AD$H#ox z`W#)MT3iU0eFzhxJsD4~Z$kAXfYKYB&5cre{2;HmzMA!?KSCp5rIicNuyt?{-Rbmk zNXN1_H|vl3eQUcit~Fq{zb;m9tWajMtINZYc&Eg2Mfc~G!frD0@hVw&DCzAdLAv&# z^3dwb_4zNTFo;%(Y1FmQ*;q}=)Yog`0;>orC*g6!@|~Wi>wtbn&x`Wdm{0Z4t>tM( z(sEud$KNCI{Cqg^h%{kcz@^e7eMoEdU~X5)jBaxmk*W<|3GX2_lzl zEfUtYg)hiLHF~4X-lT(%9Xpda5Z3s6PJp_LXUW>=irC>ao!ngT3ZE-ciPf@A$D@fp z9?vA%x5O?|gr5m*dN_8m=vwD&ll}m~od&5h0ikH+yxfnK9DE6h@Ap5eLv6%+=l%of zvjC<*$=U&n71ONcfljyLr**H+zcwOkoEyC#Mk*V)Lp1|6!V8MU}dc6U~Z8LF3^ z<924-+;u!yd<3;yi(hucSUBva4@WFUVQnLR{sYcX1Te8()?6|?7b}HYh$ptjeL1uQ zD|UE9af%CYO4WlvrxLZ40a*2Yf^6)$NLmw(UoWkL7h3&Wzz_<`h!uEBEmG35TN8ye?)|QiXQ=7$6Sw`e zt;v?VtGoND-LV!>%MN-E6<`WyZ9Y8J{MTJ6me{n(x3a`6@5Q|O-+^eH`k&AQuQjh= z+eK-yq&%W0O^s4O4bu+7p0b$}CFVvrna<~54f-8;r$+G238^Qhr?dNh9`8o&yY81-f}BD-=htAX zq}WYBZ)eQ~K02)*S)1fb_>*MXH?k?%=C5KX{tA%Pd4w`%)jn>h?77gZu#^nD;_-Fo z!{NG3e~n0@x*6TzPQtVRpmJ$u_hg5WmQ1l7CE+K!q+3gvwE_uM!J!@qD%5i6P$R zLMtA96=Hk9e_*iCAKJDbm(bEV1hrvEknUtkUT$*!2?b@{_1ULPhnd&^IBQ6vwHCe% ze2fyxf;Ucmg2NkNp&PIFb3rqg-@0|qyxCL>=6j=o&^G6fku9BoVR-PzL?c`8bO>@B zZrZdkdGW^{v6O|49s%KVI)~&`5W~SpOC-?ac6`D_=ADfj|Cf9>#JO4OIXyP}j&o?4 zSzcarhJArzoOv$BsF}as+2qK{;%jwjs$mBK5ia08R=Wwg{V~Od6H$4U9I! zv*|)tD@a3P;Y4F9)o+*$k#-zM2w2A0|K4i;zT%C+=)k~huMt1r8LF?;&vcYdn0u|R zf#j8(2jCT;_s=e*9bVrzbKBJ}V+3d+_l*N+(S^7~^Uck_EvA@b^!k|9{L}Rd6tDf? zqcERN_5#|jEf$@%tG%@|$aI0$NoULz_CIBWMsM) zf+4!C$EW+|Lwy_s|ADmCNE(uGR5U*IaH(VNgoQ^K^RHvZHTw?I;ECrUV!rs}+jnTp zw->vHSs59Nmz?j9$XD{UR$ses-EO;*Iqay&l<09YS}u|GW1u4YL5KPeUVeRFwPMO~ zIjqECWm%8HoY?9Q0({H3ixlzBTbiT$3jV~WecUUUCzQAJq_9gxe?9OAd_z;&Mzx}% zXQQ7=f5~)fvK!OQIkh9+Xv5Q%ZRnYX>eHd|8MqNC75BfZdB0Ori<*1=52?6qDKm~m zL)bwL*!#|S1F==QoUMI(=dr$bC!U?hq4zFyA&T<5nQW{XX!^Y$7pFzvVcK6Au)Oz! z%3tnR*m8+tOUrQDi0qjmFLgAPP5fzAle&Y-`pTvYOcQ(TW=?|1#}{*@qxf6K`ZO6O z!%F_MMTA=tetr<>`m}s1k%&0e>$R_)iE(f;!4t3w3)@iW3b9>LIxXL<-b?NAoHUbp zx4l13?%2kX0}sBjh1w#EC1UWznk%CfRM?G%QmxB8$a2tPXwZFJ>rR#n8$XHxESQyn z{>GEZ&M#?dX9L3c5t=iBffsKY2U@W=SuoQS2bFQZ(@xmc1fE zS+TYg4rvnR{Dt;k09(#Xd4?eD3Jcn-!tR?tvubM^$ybzl2ZY$#v5styY_CeyK=YP_ z7v6T%Jk^zk$y0~RPKyS)dsV#W{el&~+}>OvB45C% z%Wb=hzCyx=$7x=6pQfdBY8;Mft6q0cKN__^8m|p7!&|48udC{`YN)cao+X_w-m=}a zJU!d17vXl@0sYT2_?jW1pv zplxcrbMg69US=B1HacTQwyHrUI~4r;JQ&_ieC{}IRNgTeS5VaK;$v{H$ZwUsfBi5s zA(qRhRNG3Sx_bDnubqfuO(m2+r+B;Lu9><(@5h7ckPXeRM|1;w?(6AQK{85A8TEU{pwB!Yp73)Xd0#Wl}g` z3SIt?UQFQyYJT#>)NQk+&%>a)h#A{{Vv@a~u2hC~yb(alg)AzpQ$JfN-nC1@%fg=1d*>hk% zRjL;I&bP8Hic~CuEfM1?MnT}!7-?!}&gnP(3&AOYDTEKAT)1ic-lgh7OvaVfnlM>h zJQSVPI8@dpvB3~LnT06oFm;J{5F2?si_8|)w9S+wIVi}&lCz!ABF*(+Ty8MiDE(Y) zesATdFD28GJ8jI$Ddsghy@!^5X!bjYkV^r4LuH z$lcsxmdFKv1UspCG`XE=Rjhr*%P`6K`d}c3;<@+m&K@|`%3)bbmp;!~AngJrWhk-) zkq&3Ju#(r(>iuLHnOsjofz0S{p1r>LOBS!^|8>W{-&Wo;{D$O}ati#%BnMphlWyX#cz?-?%K|agkgb8^f!A;ukMBj^$Ni{;;@u%VkB)9l z_f=LQlf^%1`k(&)9yF2pKmGmy-S3)I$Pg5(|5N-C4G*$cIZth;LMjFYUoUI122=VB zcz0#bi^%nQx%AeCUADK+xXt)zrDQH}>XX(aY*=!EDL$6q^(d<{%fq$H`cthD2%<=$ zN$#Rg*+bO`kOzJI)iKW$xXoRD7&B_8$_Q1EY-W;W|8O5a?8}R0rZ7ucAug7LFx8=+ znglr;zIWlE{0TA15tw1u(gOz4so8o|6;8v}GpdV* z)t19Lvs4+W2Dk!=UJtP2bH7&e;CO69oej6hmrV}@FcL9-=|e&HpFwuIP|K&ucJ?+XhPP|IayeY6jAymm4S!Pz>n};ckFoiI! zLG_&K6|PP5oSYQTJDw9<8vs;6JqKIp_N*mOb(?@r`SHRB1^ovi`qXM7c_||KvnJH@ z)}0l7%v~hrXO!-Lo*R)^w8ak=g7Kc#z-G&mH}=64AM_4F5_lOHeRuj7@~k8J+aeW9 zHfEin=1bA%aH3pGXYws~xg#Qk7m@+X zpfn-X9^-=!>JDlXW+_xn>G(4#Z!|t`nbJNf6(I z(>1NlFVI(YsLli{63XU%JA*q ztTL(d*w8h-)y-Btg9C)n4R77rkJ{iVImI~9ElI@xfFBkP=%jz2w~xYo><*Iq*zJqy z`BQG>7%u7QUds@rW7mkDN_PmaYa2MP*1kHkGt;lXCMCY7#0t_C!xgBXa6Sy+Ukg-b zpu$p}MaJzn~HQ`D~k-%?_> zKy8OS-Ir|bz;Y)d9xxu<2zj;tA6ZgT@d72p#gofTOKI*q4Tr#ca0XKxYiw_Pa|?^7a#rU1$!scnQvp0$f!b;PM7VStceF@^SXUwwhsSgYS70-)kUC)-rpA=YZM zFxP4p=AmY5osS_EYoc*f4}^6Z={H@VBms;brhyLuBM3}#-tD2ZcsVucI`4NYs^21@ zyL1w-+G^dpoRa>;L2W>#14C2%@qsv)sURUnM%~g?Qe07yX)fE;dgpOh>jlcg)(2WL zlzt53;-vdnY1$VkBVTMd#qD0HmQO0w(8wCRQaly4V;!~{MV{k9 zl|%CPsZf4%{1{D>kemT^eA?+20r~N2a>pEZ%$UQH1$W1|8L~YIh@=KzN2Dmpyq-pa z#_sVAV=nJNVSsRADxLHYCU>I_vM1YG^Jf^3*8ERwq$hwCW2tq<#c(FBDD)eZcABB`vt(l7n)J2JMKp-EpM{8x@)Zhbdn!X9H<93t8O7{H@#^j(VHlLEi zc|>QvLvFTAF-WCo^s_V@sCx7WidH;@w3Yr#Ij=zuDLL8garvz|ejT-p^`PYTb1ML4 z$_C;aF?N!Z04aO$yW`*rNu~Rj?Pa3ps&#KY$?B*=wXYIVKtIBD1??N^x3aPlWBj8- z{Kh;|=L3vP$LNCTzz#u`z(k$;r%#}YQYq%~;?$uHTr707x#tY3bM!b_nFABizFe8b zN6VR^rh`-x)4;0lkIcTD)i%S5Ke{q7FgsIbHAj=!G!(9>S6HVZ$ebKs1mS~poenb= z=j0#Lg-~iovp*}*9lG@8g6{L2~;12Ty5z_(;#Z4AqMH@a0o{$ZoDdo6)0sY91-d!`Oo>x0Q4q~mUEnDCOlRDTv+As!Q!6<15ji&@`=}>Op zDXe-{?u3{VYC#eV)fEg}jqJ@=uL!GtbI3R`%&2oT@4;_DG7-GEQz`P6B#uG+n+A|K z<76J)|1RWH1CT@C<*S`)DO&R+`%f3s*F)yGJ)W(wtRfAL`8kJ0&v%tXwmXa=+ljU2 z{8asTRfb6z>bEFCaSY89tTzKQF^=Hm&2|cL>cGM_r;ADrmSzub&YM;gb7kJ;!tD|M z8h=)@xhnX60WQ@*FkC?{HcpC7Wtj`ZnttD_8qeRniS3I33`#Vpl)#e7aG(MLigyY} zw5ZR()KjBinXy=c9SnlGjIZ)$s>Qd3ot<_nk3`s#zn* zkcUu=C2k_uaxkY6rDN+;$Aw5PsE@dXB}qJ~Z0|rwX0%^0lf6+8$&=X@Jl>+Fe^mK8 zXUVsJuPEOM%jT-W6N=|yxDV45#>Llosq}Xk&JO^^d3mZLz~neS)IP4eLp3lbH z-3ZYrezKDcyPahs4kCQq?ytuFPbx{d?WasOU0EMqw+;|B=c!Hi?mmn^tSj{!xk=Y| z$@?B0qi{6nobv+3^YUft`u75={|oGE>01rR`~5(Z*}0oY`;)aJrhZdLZ+6vw8lhhm zDq3dEr*ej>*zY46GicZt0u=sMMc?GBVG%i3kkY(m-~DG3W9EX;6c(4~p0;_-u?-Y|N%@dJ^W8XTD%_K) zJ?C&@`rTNLf2O92GmP%H1=Wg~hu-WVetC7%2CKbi(8S-URL%K)olkIz@XA>lr^AvI z%JUYF1$?N}mp|0A`%}DU1Zd(7v4Zeb`YDP1iXRV9E zL4GrFPL`{rPgZBfMb}(ZE;j_N3sNnNbJ)_UQ-?x6(jcALMSR|hrY;tH$Dp`>RGEVv zVaY5FTAFAGr)Z=MD!{Enq$D{*qxBdk;N#+YoF|jTmr?EImbi;S00``SV94#`rM$*b zgQ2Z^k`OP;=F{N&h^BmId#fNFt=VC`@_$^hJbf%tT=CTP@v#q<1$Q9E`-p-s=IXRI zz@!nE5zz;sX3yrpTpGxb*Bi@hwtcnN5EE3OOO4!nV`md>^84}^C@?BR z$JA8q=FT?t)z9OS+v5zf$Y9M@O;U|d8KSXkp5xbr+g?lc%ac7gev4KW&GXSO8)cwVGZ8G%QMq+HP&mPqvJS*ovdi~M zSy=fgjNB;ip?n9!f=BT!oi*&VFi|E8Dw3wt(uj{18dVnf-I>fJh%B&BPAKa zlsGR@7NSHh+RXoY#FE9zMOJILvm4BM7<8hR=d~i@yECRAF!e3a@eF;}O)X_{H9>-0 zneo%k+wAB(3fMMw>k*!Gr6haJ7d5#i6A8GByF;5Y6I81LCc>QI79V|8|MiN(n}E{jS-vrmbY%4A-?j%0qFTssB;0CHDwd|Q zcAZZR(0f00WcZ{5pO6@Lwtls+)-ohx%s(|BT0d+=>KHdknI0Vk6-+LIna%5ne+(m_<;!%R=N z>hyJ13s3nmy}pB}lK*-|HZCyMf{e+mC-YnME4%Gl=5ZNMh2jL?o4NCk@-30Fv;1j= zZQ}acHDv63EJ~(n{DMhVkv~271MyKLp^;b&Q?Wo$a3o{xYWRt78(3+S_^K>bA}R)$ ztQVnos<7grHafmsL*?Q81vs!Frt5pI4BD;%S(#`>Shgvw#zIuO_ur7;mCxdWXGP*S z(49x)uU??2C61rV-JJI3AZY_Z+Hlfl6L{k(RfM@IxdNyi__|1euhw#VY(*f?jJd%{ z`3_-SM^_=@umzn(FZ7)BuDSCHk+Rdc9FhW1PL*RBV^vwdmsCH4G;;f7ong#D5q-PW zRGgjg-6#LQTe;)Q*!CA!Yi>={tc_yA$I8ns+0gGc67?y`b^r8gsY`cDk(|jO5TF8ynm$ss**hi#oGM~m3!LXT{bE0B3=wG62iM66kXR6rX_f(oP9>x)(=kF3s$j-eePX?F<4m}tEnKQ~ zQQo1TvOg714k5EFLZirN1R`sCzFQ!VLo}Z$mFFOJ&=Nd`Ea?%=c^ z+44zVCDc6+x#d;+lwrcb5UtQ|s>&W6@hLU2eaGRSUZBlPHwW$=nGN;t$IPy;$!2iE z0tHMjqjuNVM1u}RZ~Azi9CC+A?+r*RN*E^N(%+$%S+*dX3Va)u;O7s*!M&N6Bp!%8 zhC_ObF0+W^F`@a@CGg%gp9FuoE98$dId1?7jaXt1_g3I5$GttBi0g7_F*q@3K)SMH zv96Q}=8qR#aoWx4@5%)e9pfsQ-vQE%j!aIVF}uI($Mfv|PXpI2(YO;#`#+7jA&4AE z)f1ex+y^ZdA(`=i1unN}WQJ+<@1;;uVp>*c4Vc$7Otv2GBDjRA6nTg%CTtTL`%j^ZTLYT6o4UFjxM-sFi|Huo$gdWsN`rDXNo?Io6irvMUVedm4Nal@IbQ+@ ziFgviKd@JuxMiAkFapnd4+G$N|5B#qH{{jebz|IhaAP7X3()ne0D7c?c_rs45?N| zAf1ovlv}X_70TR(ln?d~3F7j%xvNrc5bnph_kF z{PI&u;3p;`SAHgcPKe5Sm!IV3M?ucrJ4EiU?+7-$CVOhV%Q`Y~cg^<3!5u?iBZqTn z_c0H6Y0@`EF;OS+$+Of?yvkol=y&hnxxdB`Z177ksPBF_WA^@;<0oFeFC-+p*Vy0I zi2DP0qE4T)$M28;xJi(>e~$JyU1@lkU{KfnkXtnBldVW^dE&?fc+HmXfCuL!ISm{| zkNygDFDvwe%xIc*AGZZ?-V-C$No5-P*iQ`irW(cna{JN!U27N(h4M{}B~H6`0uJ(I z`@VqfnkUH;uU;P4KFuhgJoA2uUI=P<#&O+f9{bMALa*wE%TDXtVgY4T?l`ZC!_vG1 zjAV1Se~xzWkI0^O0^0@Nr+wW2l&|jN%1fTz8`2wLhi^Av5^@yv-d>OEqN_n~SX*C; za+v`0@uVwX&Ac9eB;I@uCNF!eR@?0x(H@o$Yow2(J6yO;801pE#Q%o#R^NB{Ugq`0 zEc&OPv=J?jjAuk@zJa3jQ2}R1;`L83@Ye17{G<7wJ+6D7d4WPSmn389@xs5HgE-l~ z=N=L);pLC=9KP|miLNc5|7vW5+-b6SLqo$B9?jTXGAhq?*pc{kL*0CSTuh7||J#jK zI!>TVLPF?e#paTcKogy?1@a1GIaqwg|`M0yI? z;}IN=bmd$88Cyg9*D#-%YWjenm^fZV;yQ$(J>^R@Dhsy&imw|xvGCtAbieNR9Y({% zcAC3DVes`kKMDqm&81_iIL#-&WUIs{Ma7&aI!{W@zum-IXWt`BV0HY$zefCJ^(ywd zeb1}-{Wpq#XdAbiE=`yR}SNKmY z^dkgc|KWASgnV7ONNg|JJOEDekn`Pc0)&fE$XNiW0l@{zwU$R!mp#}kA@?q!7r3|c zVG>N29Vl?$l`r8DX#i>f1;mGIZZRFfRrh`p1K>>k?BP&UkJ?9(sZaNf<=#BF6u1R6 z@Qq|L94V824to%#0#~&tAo~~z3pFwMcUG55!Py#?)ibz%?KY9c4iuBh>vQr;MPX%Z z|5@a_$9%{l_a=hpy}5qGLyN%H0OjN~r6_t8H17yh~pMdPcZu&!wVVW<}XlI*LVo0j}C5d#zi$0PG%AcV7{tR`Z=TjWfAG~ zPP1!UYdjXsN)66&#%nyXT}lm(aTh3w28B^=BlpLixlk2O((wtP#dKlJjHtodLG32D z!eNj%#3gfdPaUuqECtYuqa|e(QqnS%iz8dP*+;LpYtZL1L_i{teQ{0L1A*t73MqLR zj~5nj3^_wT`b7|yYu!w^8HZ25A#p#+@}_8>gD9HETP$8IK?m7evX&EXC>0N0%;SS( zSPa#l3^Cr~D@x=-rBr;Oq7(RYdJH_7-Slag#O>DS?Pq8_U#-xm#F#J&Nf`=3J+btR zSF4lp`Weh0$G~ucV~n#N`rvF|pa7kyb)?GrEGd5 zZ!P_3Q89pgq*R9mAH%P~)Vedem|LoK6up(@ohaE03MVTV_6<_Zt1LGAWl`<$ZRk2jF-((Los;wA;oY`>s|BDZD_SGx{U+y602YI!}S{8s{wW#VC z?<42}rRnXgXiiy}R9O~OR)q9M%7eb^X)I|p)Ap)<)p^-4IP4e{(W~4*8bgBFD*9?6?o8>6OHl`>^Zc}GMZ6zAo%Vwus%cR?Z)CFrqIjEbPgdk)(>PW*T`>BR zx5gD6rS!%XMx%FUQeO^=jd6)Y^yI5bk#eZ0#;5$tltqIv^_c0~k_|0v|}n zoXk_Tw>&G!C@d6V)TWN5dspR`+J`6Y-aFK>+*1>O&1r}3S&4((t#EkgY>>uetzRi0 zW#q@`Q)Y&|q*J&%$5$;?eKue+v#rt=<5lB(kJ2>Be2!BZv<+Fetc=5F61}zks$Wyn zPsDrQq?#IGLDlcoN3{%Ra+Wou`cF;l_6V>!XR#sUDx zgG}<4X^84qB25Lfz8F%r_^jGgP0sQ;x+*JCYAC=>9|M}99ULjp~hI78qZS#!V75i zu7~JTusd%{SBBt!?n{d1A9)pScruD`BNA|n=HD^-Y!iCQ|8;Jn4o|Z7rL`}N*;-7t zSoV-zG2HHRUyK}#9Wmcg-xni>%Z?}#TnobKp8WZhR`vmR3sc`v;||u-Fl&8hZpGS{ z((Xrh0YUg+;R+~xS5ZLuO1QKKO)9E zg}(5D2Kh*})l$w0OVtUB^-7#V!+oThYN_rdBJ6BvZoaJ z!NUubqSt3{Mb->1P>g`*zQFVPdP)lF*5*47oDdEA05na*2f5dGodXNq%T0Kv__htH z-;5OL8gfb$>44f$dHSE#-LNC#@%6h_Bq#9N7Qy5Uowhpj1e`23M-GFMp9HEMCk7vH}(cU=Va zc=*_&%zdhv6t~n!W%wK(H2bNzf}LI0DiFMZb9;?Fr?*M!XnMd~E!7{oQmb^e-w_`u+zs7KOo?vd;>3u$0*#jR{5=VIBGD z&MED|+gLS5;k_3q>zqML1Ml-pdbJW6^v{I4w&G89HwUzwiOp!6in3h|!A9+6y3W}( z{yhBnBU`?TUuvyM7rod$u}iE(qXXHp^r~u>BMZwU)l^Yw4tv3Rp&crGmeR~J z3)v(Hs>n2l0VS<~4rM+|I%bK5tT+TtWXiXuK0Xg=R+--n{P&uEhdx;A_cLA+@e3}1 z2w=wF_M)tc3@3tjF#9_*9(v=aIS-l)bfx=jrtAe*uBGzBPHITkN~vbPL>^^?4jaVF zosHj0Lwt>d+@RziQTcLTKEH{UAGb;FZdz0C_(J^~ZYXzIz318ETswQva9^}6=}pz% z!KG|f=a!}I<8dtY#5YhIp+zE%*APT_Z!3*AWu>xFna?aOwWm&$RroN(GT)NVqIlUM zU5GXuGF_(?S#%t@KQ0wBAGJ>~;GkxsI82u{&BBmB@m^(2HHYT_V^zLhRQZ99 z^7#JpkeoSO#w;UeApgzE+YfTEk|;NqN#MH`oW2~{`R3-Fdf9;qha;vDkz0`+fnOq< z5YtZbFvyq?I;Kw4=FS;aev@#z&${`S!PE;Bx3_(Q{Mk!cYaJtCa+#k8UWuxNyg>*J z8{Og29y^9ZhgLMn=9Mxma}1hSJ#{LXBRV&_H{5lydn)1k#_n;sgJ4D&KR%?w+O}gu zZ`JYBf?n><8C`x83owJ5=DEhm*3$X9orVXe!j6W4fs@ZoS}?{sQGLXT@vG zQ~5XJdPgA4lz|V#S>8i`rtnodsHh%Y#igO{!mO#VU725fDXo9kR_x z=*KSWUcOjw{B-A$%z=W*Jw4d4VWLW{OZ6U4&$PHV=0CZq;kNHtQnc88o-5n3AOFg` zeoEqUen-If25Iff;OV;m>;q}4-qy;!{7QIE%vt-{*ab>UYn(vUyxMVoHDi7Qu+9*` zx5-`&&l=g=rbbGd(sH$y*5@#4^c%*;+wtpiRkXsbh*=!o>s#+if^}sT!%{LEo^5PG z{YvF&1~q!Din!LC_$otj2Ap~1e3LQa|(Bzrp**;P2>1R~6G?I&aDY{J*U3>sStU_CB^v6p{AvWi;_xoimHzu=Yb62^r zK4qK2^Hh4r8n?FeZVXCOksz4z8ys;t)hf+kH8`(by=>?%Q1a5D`1Ou!7HVVxGex<^ zB}7yf^C461j+|qG<&eVC>OfY*+!eZ=I;+5AYYA^9;DFH5TSM<%q6625Z2cW5as*Wbl!K=IgRHdBepcTKzZJwc+$tV;T!!f1HYD8eujTIyjd#C6pydLN+cQ$r-Z$jK2vytZ?+{9)PPl6>6d2jz; z6IMA^R>BxSk@_GQ5YSTP+^SNfM(pX@uKUwXd;^e-PD>7L zTrR5oc(4X~NRy{NWd@0+sG_ddp?``|Hyg-jG3Gk6q;EWH0EO@i_+$~15?$J_d6s1p z4wbp8G-Aax5>2o?Wy=8t&8az^F{q1rYufmiz*LR>TQe6Zw7lBVJzQ0na`wj8u0lGI z%Q?+dYF&GszfqMBlPif=ZG=faw^*8T+GFEypLT&aI#4;j(lAXyWLNR*;gIGnsp*7p zfvmpFQsuK;m8Z^uzmo$^b0lfxaCgSCSnNjC62Ya?&s*IFiz+vYsie~<)^^I0ptXs^ zNR|QjW|gr{>ZN5DIkx0y%0?W^W4F4-D54OdwdhgB9;nf4pI)5P;z12{k%C)YbTU;=K<@7U0sQ7BJ8ZNW+{Sx>2UGl;bzpo{4pV61KD;$?9AoB2q9} z2>9G}cP_t=u=<_x&f1HYhTlx0ci#irm&bW$ras+;L%I4c-AE8kU`Q+6BlhqZK;3{ zoD~f+zh9I070#fF8CESB!$@HvX_=;JeCbipWp!w+3aIo*n=EywybfYmT~2NlG0>)# z7!NdF+In4|%LO?txlG!)ifn-|hd93vbm`O^AK5MHT=Azif(flbvOV1_>~RmRL~@$= zOMo9fU7$D>1$Nju9B8w4ok|be2(DY9PZQQ!r%UBVaqSJKxmwobAlLbl$XTOQh~x#z zTx!&aI@QXcjwWB*wX55f=`jgXWi*@ zpp@2zpOtN0fLeu4nF^20D8gl`sIWDZO3A4l4b(*!x(DYeo=9o31Tjh;<%dovl_u*~ zX{(874;iC^IX*nLtdz+LqNS5{Vp@2Z1J&MW=`2a8glGF9 z!pbUT98&|>`-G%Y{id9&5zY&FiZ#PS{&`Q zDZ6^P)|~P2Y!I)q26y?6veRgKv)~HtYT?e=?*`7q)OJNxQhenQQrs{pL~C@Bs+%_K z)%fTCGE^OId&E z_VZ6xzlM+Vc-o?--#g<(@gR1cXUxF0y;m>F9OlJoINPzo*asCjtLEZQNnuT) zJb`H|SG=tcI0V*}HK5Ayf?cZcULi>v`OKIjT;B?Ig2cYWYd9Ec6BgzD_8D`E&9P7( zB17jl|8o}fyOkC4Q;@1tW8nUY$K9>oVF#_Lh9b(jJwg8RunwmpRT>5L!e);$y?3?V z{lxJnhkD(Cq=590KXBK;@H$Ij9|^>m(hA zzMMc0i8pvA?8>L@Yv#P`D++W1{@k>W%dG0y1;s09fQxZKmH4z7@V(40Q-_SR@)Y3P zR(<@Ta6dqh&SjF)Y$Q?Tr)B0IU}>#O{#&JxJsZn2yc$P+J_v_pL-7h^Hcg# zUPZ}6q7F?WzV}#mD^DGE2s0R~7t^ZtDKnL0-DNs#J#+?ZtAfDAk6BF9(9_ zg&~d*EN6FOd*D~HSUQoC&>xm*-~t7e`9q_cPB=QIdN(}$b6NYacNg)U|8T-U5Cw`} z*A|9ElfL-+D&0$|dO*^7&>p{3+|GMbEH`SDMb$ydWA)IQ1Bh<4%6h?|_nR?M4yS}F zYA_nvzgZ+cccBk%iotVuZ*RH+ou6k^k7dI=a ztjJiQIp_G4K;^z4h0NbCbaQFKH!@#(HL6$V%a2pE&2g_s3_`o5xH-t8z^tcGhx~(T zs&BiE{_}ZKV~2c^S@rrz=fEc!)7Jz$?M7xF{#O#GLy=%vhsi_Af(m1KL!AgY8^h0% zol`5m&fwEKS?@*#=}OlXHrLd%5+aKfJ663tq%W1pDx8M8d(oW%WSYCh8C->od;u%5 zB2{{MRH6UPNe-@rbZi*f?Rx}wxJW?uzdrnN%=2eqG1fN8;xvY8k5#l6%i-{MEGsm- zty0s1k`j#x#%)q;xcKO1b}#?eDErzu@puq=1-mk|DL_R7h*Q2V0aqOO?>*F|KU#t4 z-U+OX56`L;UtxcVJERa;vZ7{1tjLnjsjljXLu7h^Vviu!#$hvnG+5!rET(XW+Z!Gp zommDcf^u=%2k8goWM#X-9I8JY+^VwntVeoC|1v=@)<~$92JkbQOh~yy1gAB#Lvffk z+rX3K0v*??KqfsJ*`-9(zc$)8sb&Wgc2OR8Ij&iL;FQmX5|j>< z6MGUoWTDWv4p&vZzK7u#N{JwhHQpKw3h>6LE*0yNXYJfeKZzY10rpqgB5K5ThnQ3( zPvs+OQ>K(38r86>~VU$os?+b;f}>_tuYytjqFrr zGC5yPJD^B0j3rZWtEr_2WGs@H#v^NQ zt)EeliGJ&GWLrv-Yt0O1j%d4Pl-$HGyTn)eY9MAW?IaWqxcIj+rD_kWM=|B(?U<8v zFxcJ7KQ`mD=o=4YHMM!)25T35S_?F+F^iTg%PQ4m@fY|4%FIt2PTNPyQs>V-)}Q*z z0F&>7m#P`F`Nx3$#qT`)Z>qwQ>H8xYi9jsZVA0f46EsndFAybXWx+O1PxM}WIy*$S#!65dB zNziOq_gz#s0@w3NAES`Fk?q3Ei={HEo~H<`r&@u{h)ojsIn_1`?h{q%hQx`iL4Smc zQ_bay9qnvS@z7)r4a>o=F|~z^blPfTkAGoL-!QGhL8R}0J4P%2)1U__k_!}yzRESE zW7PR>RHeI1^1s^yxz(JpXkgtHHdn9&@`=d2Jzr})Yc_mh(~^_J)q0{UuV!RPnIV>n z-D>xND7`nKvaq7O_&Ju7TKeHp4A*r;fT`qJOTV1kvy6kx$@h8D51N@t(1xZGbmIZ# zyAR-P0gYa>$^n%w@UZQvBoCk->zviH_MQ7WJiR(cV!b3q?lO81*w1>VxzNp&pa-d<9^cn0kY+TIdmj&r9Ou{9 z{dOXv7d5O$gLKRJ-z_EU?mBCJZXP-EtPzO6^>v>-F2>aV4r^LUd!jFz!yO})Ag@6U z5e97~^JBVBQk)CXU-?WK+8-_uIK1WKj*C@NOdpmoXf2t4JRjqr$VVF&q$HV1-5+Mr z#tAgM<&(lw#LRJE=EI8%SCYMakl^3{PW)RsPw@9Z`#vyw$%gOhZxS97*B!}B%3u2b z828)zd4iuDr<2`X@@aK#5hLa&iNa%fleHDjgNK8WgOfQcn(T^Iw3fOq!Y$K!W>Hom z`_Yz_E5-OalR2Hkkxj9*o`lx(x@H;^-B#?K(6>QVMpPnxQO9}vDQWq6!m;kw*`fD^bgWF2zxUvtO#6O@93hLrzNf}7p>-$RJC=vjB zv7kwpWVk4Vg<*OWv?1X)>=E7Gd6#W!M71zbHn6tQ_(&76N!lmt=(;T4<#B%{d>mg%Ul` zV5m3+{;_}(5s9g&l>O4`r{L==XBg;W`(`Xg^^vsJG0%F+OR?UYVTZ9Zjg~IdiU(Zh zl&cjtsbaFnGD>=VYgy0zArDw7CXKUyLJ+}sTJNtKZFDbXjxeY8*p2E1?~jaUoH=6t zY2{|D#r_7$vm5tCPQMLAyTpL@ z6YrG^l+X9n&KL=rjiMwz=-wfi3kEXpj}ibL&IME$JegIZMrMhy14#wD5oV!altN&x zAh2Qo2y)V(yVej+1ii$R>2adsFUdI(k_h?i3yCAFBfL(fiSR$n<_Xf4Po?unw5;vln6ATO&?Nmuk$K^QaG}2B%Eg-e;_4Uz@k; z%BFXxsc*$_Y;pGcr}p2WCGl`}tS}6>y*I$?`)>4{t+$ zPU3A1O*gvSaae<|dv_&TXj=#bhD*TE7GGJh(^H_gB}0SNf9eGOz3sVrk~znzL_}eW z69LYRFrt12l4$c6Iel_rO|L0X%c-CDxOFm_zmkhF=lXhhnIG3`{c$lI@ zn#?UDr@iON(Lr*qD3Ujs+?TdKZ8S}>{8Y~8saMRf zrViY4N~g+wtVjfXvB)b8vnr%vJR>2OcLb73p0(pE)sxxm&kxkG#P?{$l6nFSz?LVB zm0&lLu~t^%E|oY!mW0~Q#>)xK*!vcIev6MvC@+m0;_2NHnFfe-IKy-Dl3>_$Wo-MU z^V1jo^K#aFnM4v|cXw^oOS3*mO3*R1*o%h^+6+5YV@cL=B#eHH^hLD3>6@YqwH?8< z;xk~(Olw>?zQQ^|;T`5%oB2w)g~nk%GH~ZL+9ECFKtm?`%&Jpy=q!u>FIguUk8xC2 zI?FgE7qevYj1R*M0-U4&G(z2mlQe8`YqDTmF2=X&<}C_WzPd&xx}n|l9mE-m^@!Pb zSho4PX5nMbl6}aBCvDc|JhRAVa$$zzWevM$ViH0-ath*Qt<-(iD#2%jIsR$+u0HnP zK?mn=9}8o7|Jl75!u3TaB)H8H@8 zNK(|nfgW!S2?I^MQb~6=71k~EH7b54os0RnUAiiZMmEKwzwWJ|@5E8_cTm$lsqdIy zNl17mdkUfNcm#jDCOh17%MXMIJ8mAxe4inc-$AI)*<)xitU?^ycE0){skV3X`uA5q zIUlZSzyQ9ZwgmRBJMoiaBEkJ5&!nh^35`Fz_Fs%1Ns%+Nyd73>lVU~;c+FpN`4$;3 zoEcW!vF$C9^t7AdzZ;MS#XRka@pd|P_(tMR|0Z3-Q2u}2Id$--h&jDurJfiX*IDcz z#paM#qvP-8TU9SRF&`7w524ZD>DaVz7Yb?3V#q@2QGbRTVh)nZWPH$5aNrNW!IcB& zJG5m%4}Y&<(jZRg%^50u%1c;Ya=krV0$0=^1YqmW$ir4sh+lB?wHFQI)#c;2M?30{ zJ*72;vxgFSK|q>Ie!}$Kn_1cBO#Z;ygvZa)baB!)V~VUj1 zn0R$|AyfOu6ZXhfD0xJedbh6rNs z{I589vBjH+X+Zr7E^D14E%^@|^lW@JI=o^(_f5ZMRIpE1gWL^GevD844FH}&xOkH= zl~@O8=%u4*L^H>>db$x?yj=1*RC%!{k+=c5q{MW=UJBHU^0VS%BwEIUG`9U2-~^$j z@%YIH4S|iB@WtUXUNKoQMLd%2epdG9eH#e?doO>oEP4AOGyWmZ6b!9Wc>=PT8JDFW zhUAdabNjbYT}^?AGahQWHx3wINzpBmoOE73#oSCX4~c^JU%J<_+VkZiw|R4h>@(N# z#K(eVy-rT*SLS=X0`^N4Se>}|q)asWL8*)wVul$j)m=&N8Ch`GB}%fm4xl#%s}3&> zu3s-5?M{3YHm724)?f^i^qk=CLLl1hO$-cyHeg4F6< z%Nr$Og@~y*n{`SdP@P~qqltrjROHfb=D4Aw5U2OqpE-3LLxveExPaqECG>)V)}yDAciG1EQI zLt1Ut=wlpCO${aKeqVu3DZb0Rx*2c6iz$wGFY6epvT(Ea$_F~)R%-NK9rg z2J?pXN$5WKxTpBVyIT;S9;lWN;+nHl}Od{AL2$>aRBrHsJG>oJN*2(iV)hICS z4&6Pei++T-{1j~5z`)z&Tu&y|%;3weH&%bY5$>AvvCt&<_{qN;kh|qP6%7rZ%GSG+ z`2&ZIv;8YTnxg%TC_KTA-LZ^WDz?xvRaU9}QT5d21&7^2!H`G}NNw#AW;5z)}U2Do)A z6+K@<@DcD7D|0brYntVlbcES6^VF2|vbky=bq$y{Qupv&O znoV+ZY(=|YXm;p!E@~emD_4U@!*RjwOgm!jTbb^|=I29&kZ!(JJuO2R;>U{0Vdgxr zsN~~V*Lr#n=)Z%4J3Bjj9w*-iPv(eYf&08pms+xB4oqJ&YWN5HWy*MjhPg2%VR>AJ#56|gI-0O zyA>%MK9HhS-WiruYFsokPocfmnJ>!(rXxT@<%nsqj~!q4{Bz*T!Xm6`*$Q9jmed)M z&V%Dx^~B5d4*30ox+#GU`uiRSM#RFLpMY)?zljj9;^-#9g&wk$iv+qqh6&_5@$8ke!F-6Ns~cRYOqo+nVNoz@fJ;6 z5M4k{KmUEeFFO_g1Yy(bC`tp3X;fOgbn#M>0zNm#;bUZ4-bPNjyWz~4 z8?F=Chs&z8!@;Fkf-O9atRE{9Y%BBfXi{D8E6m~31|S1~UiPO*xpTgQC=wjoTFc|& zxj4-B0$DIa;u1)r{G+^5CXwKsWdCROU8PTGy_k3+DSuw@YTdJ`!LqO_V-Gt^@iWe{ zlz9nrL8iF1qusEp7##L13_c4BlDe8mB zCK7kGB*5z;4b<>l#ONoFJ+x5|6-wXD2co!>pkXYGF9Ov-yxRxNy}2#QEDPAiSddi6q#axC@PM6+dzv6d?=wmT{$;`ZLzI69U9xy{*=bU?ki;t9%if$({zi~F z?j-9%O#2emGKprUcUcsQtM^Yb>|Yr>v-*04N1Y*w&9*vNe%SK7FCHG4LYdDx6&}TT z+^HNu)&6jYf4DLJA5v}xEk{SUCFOBE)u%Mn1V}93=&L7VP?t*~%FoWf%9Q)>NZdaQ zX<4}MARb0`u@$poxMF3Yr<9?=Lx7@|K_J1XQ~7E+_`sRM15AfLHXyP&)1rM!7%Hc^ zcPeaM5i<5qOWQw5<-%+-Ai>v3y{bj9P+rl{UFC1rWWol{mlTtSFPVVXj4jvf3U{q6 ziZ%lbu#rO~sIeoYXmI`=9?2a&;IY%4cY1y;bZ34QsQwi{LNtVNvd<4b^X}i*YJ5%U zDqwXKpDJ|qem0PgG|s7zkkMh*hmzhpEfpmiWk+c09q9%WAL_iou+!S4ym+a4se1h` zX%;2k#Z$U@%Km+VxTEgPYKS{h-fMU91w&>HO}5twTpcyXZ({tAX$l&iF@y^$LNZ=` zg7s>p%(bDU3Vy2DS8fF^%$elQpiizGgJjkfvs5*Y=#uB(8l^AwWYj9}c5kXeaav)0 zF%&H?UnDiC6P{#aNc>h5y9r?pS1 z-k$D-8KT(AMb%glKlc2~7SsJF%7DP`yoj<~>f~EBZc4Mrw1oV-LE?Jja!&|{WIMp- zoygA(kzHXX_NXuQqy^&N80fBuCd*m#O?M6PDDF8@<8e--$H^&e7$JS3QPjCa9H$8*Ch5-XMDt>WTBOtv$EoF0W&yiz;gzr0nI;GB4@-*`D53qLZAY??%u z04;~05RfD;T2*5}A5xwmOmy*WUB&QftiE6!bNM#|*N5m&MK@^rH|~1v5~HAhhQ-}5 zF<{jCU!3y8*5&s;SKTg&{dSKH*hKc$T5vO=Vk2s#dCGVF7X5L`Q;$ z+jX?j*7=hqQgZx=XF!$%=?4qM(zIfxQ!ebO1B)?b^FtMv#cXjT=Uje5$ARK|%@@1L zzQc+Q+vX`ys3jHC*?6R9-}L?0DkmSP+>0XXS_pC@85BPCJ^HqsQ-$_GAHB#0Uuas` zi-L0@Ssj1ZvYdUyaf_~G$cH)KDz}}3-uGvvD{exQzJ=?aPvbhP1 zFLY(@1*}D}t<%&*T4rQxv4@-4bAQgMUb4?j8VVMZ?XI66U8ReB{C9eoS)S&W+bsY8 zfF#qTPSMaDItqmD%|B|a*uqG)RFwaKMa&vDzcrzXkqOo2MdW>CJnrXKuL()af zxk$B(%OkihfxBlV$WNi4QP$6sbZQpmtRqgDtWcvBlY{nFbB-02)}D@gW|T?2y291X z{7tY>v8*a)%+vTS(n#J7B67#J-KRH1JHyturhyduKw~OU{Xve!yrOYql=x3rrKgiy zxPp=r(*bvSigFx3T^ikkKEfd<7LcpO7a?}5)MNU|G?`fMxh5-zVjmbaIm4>pIr&^D z|*_4dbBuM8p{_o`y(a9RQGG*cTfg-hk%>Q4sarL zak?y9oZ%l)hB$3fgQeCad2<_%0BTG9xFpD&IhYha+bN})u6?IKT56AXB|SWYcO{Kp z0F_FAMsdkhxlKP}{G@LhmuHZU#Rfr~E`=WcZHQp6-V)krBE3`>@oJW(HYsdYqX8?T zdZL!gYLDWg#IzTCXrq5gVWi*l)+#nS2J|_Ou$S01?e@xB_xVsE7VaK9tt5?f^3+VV zMev~u!3V0}b)QWS~MAl{HP##(gM?7R>~N+_=7DXyZDr82UW93vu?Jd4J;RFYbQ z{RnqsP0Quuvk{rlXeUEeS zdkjuyaGz&sc3-@jc`Q4;&|;&ta(vRz9vxGJUnCUb;DUtlhe{1a$n3nze2i+eB zrTqC!_&EEUY(-U=e^6vN&@*lX7`{F@sA z0)gSw&&a>huqVan@ACMoT^ae)S{E44DS}_-wIxnAhrj~7yKDbP7T~NR#qOR#g~h%8 zYn;pd+Qx} zBylyIQQ%wNKY!gekW@Pfv-FFCh`I0nNcUF**J#Yq504!LkKOy%AOOd?GQQ)CpW6U9 z*TbLO^BW0ab_!DjrjzQ|GdF%C|LVerI?;^Y=L(|x3;h>Vq*D!(3h)-j{5ffOV6Qr| zG3V|SbpQw?2LG3w9fIKTCP8clcbxn84C{iLfHerD`9knf>`4!1f*p#hb2ydHNrf4!36tHv?!k$L^~yf3)EsL{A(c(ZtCxJM<8PXc&uvw;9mTCC(KC0Zitv7{!ao!J)?InB7niyh?(dh37t)Iab{(K|{Z00N z#Q@JL-&xuT;VFVktrcU)i<5&Wi_r>~Kj%fzB`DgI(aeO#`gbdw>IUVGuk#aKPHHi}XM+IM~!C zhn(t(8&u+I&V|!rjeUU}M?V(n-JPn2ea*1m&`r3W+t<=yB|?a@dO!a?k90_16@75V z$6#HgEIK7pE@JR?NW-GxMCj_O;l$AD0mas<&-Qii6>eX?`VN|OZu&&#zut3=mAtXK zWH>SR3wV3y`vF!ACt6om458d-pY7Q;NpD{czr68|y_X9-N#)=(4dX!j^UiF$wxpI_ z;OsjiimkoRPwIYvP3+?3X4Q{b{wy;Wmf>Sy5lG?j1lCgF6G@?#by%hZKM{n)u|7H>z7%)1@pX|Lc?tYLq`5nbH5$CsdH^o>8&A87PL4dLIif5FzRk6#LYpi)sOHT!F^9-vT9n8+ig+KbsZhiDwj*%I;!DC>Jyt#57klv8mPD`F4z^m(2*0zMAsm9qBwH=6T1_G)^+~8!`pW_c<_Y`DD)RA zZ{Z0Y&rE!@3Gn|=M)t7S{te}KaMZ>A)hO?Sey@DMmj_=$TGAH;et7dZo^}uzj_M#A z9ifl^)eJfNAPk{Da?%i9ms_7(**bNA2AX&`4*@wD0M`B^{9d7KEM17}c^<+ayHK*s z3~yN&qi6ubK4iNggzJg%8K}+>_13xfDTi0_&Yy*c+rCk3u6qBCS5A>+Ftf0bDJB!T zpu2PIj8Rt)e=Ky(Gg2q*!kqXh>Vqy#JHgKpM&~Z2V=^LgQu{-4GdrLg*9D%C89pW& z{$sH%=mLc=HTkE=SRUHNAo z+&?u0N7~PPI99B~N68G0inR>#JIxM2OoT zh2r`7&~k>5zecnsUu_-QR?zojaXRIvq@&Xfhg-qZt(|i}@m~0O)`TAN(^P&4pOi9R zAYIFrDXw3%d;iuC+Ec&ifaiWi`nt!Y#Bt#O?CjTLQXTDnMQ*YGmB0DyiZrjs%xK|& z!O5@3%y8j=$9$!Jag*Jsv2y<_j?RrA^hMPb>3NU#SJ60MkqK3!hP3AT#f38?%lfJ- z9$($1n!WA`S?#ajtu+9@(Os$qaDVNuc9-?dMsvPdx77!4fXLNVS6mGB=OM*3kr>Zw zya!obh{laOV!79yB8=%8kNLN_6G-v{q!I}yGFC+3%5g4JAh@*jxHX+_`UBEijRzapM=Ycz5O<_OU$gd(t=)NzGnpgqdux zn{les92*K>!MAwiGc(OsY633(g|YW26BOw&)fo&o&*S9NPu#f~Ba{S=9(0T88 zr|LJgW2?2VR?SSi?n_{oGD$npOZXS9Cw-M|`-XZW#|qVWA-vu7E$^CpMznAO)7n)s zi+pM;Tu5MJ64T>X1T64}kiUnx^Voe{w>~Q0XgAawiiBneZ*DEsE=X)x6{${DLy|Mw z>M6QQl7kSYdGnj56l@lB^{vTAI<^`o*E8qmoKch}#Zv0a{W)Ll9Zqd3QJ0$;f^Sx1 zyDz4*(!Dy*>3F|hcvw4^p#yW~FKzu^vyN9g)HI}m49(re?l?r;n%`FK1SYz+&5p-N z$!*S`On!`OL^w(#`Kv+GsEvC5V6octXO0uJv#QpYhNMI%zXr~ZeY>^&AT`QKW|8NMFXKf*h>)q4Zvn=-7HsY1J@PMo)e zIpOt-!PcX)dFwg}U}F-`emHYA52=q$sKxJsl=-iw53{YN34A_#xEfcZQbnf%zUG6g zrr9WjX2?O+Rf6-U`Ph381sfCiXAdX&c}%xeu!QffQ-!| z_q^Lfv3vQDK*CIB4YKTu`Gw?S&5`vUQz$==nM^#Xabprwllkw`Y{cV6kX_ohExLv)8h{#s3Y0@Lv|EZIYYB}44sIdTvca1ygt$yi(!W>1fB76S*FL6T zPcpwZK2foQuOS_=<+2H@psw}z4T+z4adKfgp_#CPIu=&y@rXU=(=8+^%jZW!C+ALx z0M%r5Jh)l>#>XP$u~)Qw(TO;%u&``$cqj*(20oC@NF}v-MCxoiKKPp5@kn_HUtRhSR`OZz-if+o z@iWY4I`l&C*1|U2P3L8ao@4R~hz=5u{MES5-jke8+~dva>tfZLLy>*-@%+i!$IPJN zO(k+Ji(4M0ilV6d|7C4WBkNWCH^eLIu)q50&2jR(_4g1w(fN31e;?Ku!MX}Dv!M-j z3R~ur8oxO?fv>x-{k|D;SK%^yhtNTLnSWphWKPN2<`UkjW}}n{Y9h)GnPJ@3A#*i2 zRs`I)6%6q(bK#@?hnApl6FlO=$sJ>()bP>WGO-w%ALjNlKg|Z{;vs&3sQH6xIBF#r zh@H!l$tN|D`4aYrvi>MESvSL8hSUPax(y&aIz`j&&u%KQnOPi3m9jhLU*nQ^sqAl@ zbN$qipB@QjPBN@`?DwkCAjMiLxEqgR0p2=vnbr>GdxMd@y{J>cXd_2 z0!DUbBQfF0!CDs`Ml>pZqp2rR`OU}B$mZ-7)WKzDvX0aZ2*fDoKmo1lrJnrj4_2?f-nfFyN*nS5Avsl4~Lgqw`_nnk13T-U9B*xQa^okctoKiPW)jS)g z3VCiPuQ#dp@PaRZCG=QnR-L4e00HvD{KhyJM7RDdlYHQ|S@;T>Qy!eg2Utd2sZP^U zWzo@;jmHx>`Oaw=TGrFo2Zj4VBeq{MRWR$-Ih((%5iDPrJ-Qu=Y8wMG+!J6}pT?S3 zj9Z)i@P%Z_t!SaBGys%U{R_FJZhbMc&u>FZj5aR?DZYawoG@_zUT$4*ln$V^K{qbJ+@NZbHSvmBlo)mKPNhKZ5oX5gF-wKwKSU{>QdEheMugwCmVs~p?QC)+o_L5&|D)!SWRhtOC>!<$-e8oJnMq~htw1;HKm}eeRW&+ z#|C;fxQ0`^_a+~4qcc5OnD?k3$3~lbh5j96Ll10%EC@*CP*zses3$MwBnjqL2ts(T zXEb2}(p`~VnEMb>Gp-}3<9brecFCQ>i5-~MuwbOlp0)<~6Gv6XSmoh^IZz**mp;s`X*oJorm;fx`9f4Gzv_p}ApUleVx`hTe)!ec$= zylHoyNQ2^Yi(3;s`BD`eTA*z2XKD0K_oMA&8IuZDHNP3Wj^;TiXlqv@3khHf)nb*u(f#NT^(@XYiQD>tKN4PW%7_= z!cr^h8d*HWawS#@U~(_ZIf{1Z?yE*ufNJf&Xi+g{`3^GI34p+e`*+bMa8s#}-Q@6H z-M6t)ul^2F)IHlq3cv6i-nf12?Af<@``Ea_e1-~vR|SLea5hNg9=}_caVNFWY4NSy z6dOH%#&=L6Kdw`Pgq}kTv43(7oA$ZOmw_A#|LB2hAJO6A>+AQf(XU1Muh(5eUn4R3 ztJu3w^SAi-sUYy<1N%F|22aub2UR#J5AkADIQc`?fn%Adv5xN`K!Ur(3zEHtzf^k4 zvcVlPd~NSP_!^I7(TzEp<<;XL%FS}}951KcfpXLwL8sl}QmGIWP873X;xIV-d-^%} zPU?NK2;}%o(JOIm{B4_%p<%(C_b!pI81LsKxyX+)(gBXDGqMC;P#oGKHAF4Mt8LU%Dmk5iatjKW#e`HWR|g--te07Um=)FHEfXun*vG z^reI5>kh9Ts&n-cMn0aNHpwEU=+%kQ>oqdfGpYb-k03`NXGUIy=S{OusVOKGwiQSF zP}=Ulz-6&Abc(~7WM2>Jry|i`3>1^k4w`xkJ~@LuHnC1Br=G5Ax*;g-?p?YJ*{u80gx z*$xIEuOkta=9<1zu+N;=xD3r>*Vu-c=9Pb0Z&Bc4@QHoe&gpCxT%CD=(tE?8L!fWH z{C=TM$*5@^?;wToO=yyf`ekc+N7l;Im=sqG6wRh7%n~1-gWA^kd2~gDu0B(^zt|@T?Bb|+MoI#q zR1(rY4so?K#}`~AjaRy#Z_VU-Bs{!DhO#dThWJ7f4y)?RguLg-r7&LQY9LMe;u@q# zx#iU_yr~V&vwy^XG(e|yDHbIq(~DW3`25 zwlaObGY$$g@j1@8*I z1Q_F()C;5FMR&~cSN|~faT|iJ{=R-L$f+5JslV=G2ihR1E@ZM?Bcs>dC^5-K>I^M%rlPp%nl?-jHNMoj>cQ!ax%#oS2TOkT^U zfQEbAJne0)#+=y6QAMWVPLhE4A=S{fVV#{nlQq>Web=}8cCvetxqM$VRz01^7;%fE zWZV%a3Py@ND&CF1d}^$u)tF0Gkl{k$89ODis(8s?UNZ?&9xM8EpPxu0_;>p zHl9~6k)+}-=0UK;B%-tLHO9)bJw>`l`Uq8S?KY)L{qG4SFGcSLUtgME?OygFSE^Yh z=|7R7SAZ|lNJL2^9k|NSFAVM1xE4=W6z)iw>oO=A0DEy6QhgdMdef1WanoY})Y-gN zA#|`b*BGTgy!l71)d!u)FqGfNHq&)#JJM&u(6PFQYz3Mu*Yh|?M;6A5B&SV)c}daO zC(#i1Fu1ywT-YXiXAmvOr?8(Vil8;Pu#ni>iSA99y$8NtTBgQU;v{-`mFB#a(s$6g zU;J{BTxLu_b3bu0)0|Vnt1^bZ_!~^K?}gyXN0@)5;d7G^1^K0%0vg zAi~u{$a-_1RnR0t9-0uL2g|>NqF`%oQHvQCqQkK~M^?ePe*GV|V+?An*?SgMpi9Qc z)Pup%cqwT&|LSW_KRnq&7$&M9t9iXi)txIs0jonzU}5|}Hf`eG7Jl85vQ-9iIdmBD zfb?H|mK}HE{O-xaiDA#SFMRj5HoN|BBY9M3b$7!_wLP-3&jkd|VU{q*k%A8QJa5yD7$iLZ_YG1`~=t*?sa$SGz*8ju?WajwOub=hJ+Hiv0*kPZ~d zHI{J%zy|F198wAt>2qUlsft=HcI}U?3}xiglgo=O8)z_EQY0Gd=vYjor&i{OM;}qB z4`;Y!oWd%~R?x?$<)RIoV}(q!`zyUlA7H%f86f)Dq8N>gNntd1OWZUPe~o9gEHD;% z>;?$L*EVOBTlQCk)zB1yD&{>XTOGOBgcmbJAznf-GMQYp6PAI6wWKt~4ll()DSCb? z{UJ6E9wLeOK20E|L`UysL7(y{A_}RO$$+~_gx`tI+gJYo(!9 zc9nK^B$Ul z{KUD3WoD6k8hHm9U#}Y4_3~OIs zH007;4C_!awPvCj^ng)k-y5_igNjBRNr;1uCT_UnsM#5dMud7sUql{C4NoNvwHqH? z5n7{V&ONTHPYji3hIR(WeSd)|v2{9FWilD>q_-L7)la|B7 z#W#9bZz(-REO$a;Yj$v!FAvSWe&D#VqJ=L*Ysgw4gd(o$i31L&*r=u^jzxC$z%;Ci ziRNk0lpr;>cAafwum;B3JD;bK&M`+%0Y)6KPAA_KG6dwuqjB}6oe?ygiUd!vZ?si- z_2BDr_J+oN_q?lK5mA^KBo=(IW!iqHWs<;Oj({B zB3MT6raf|-e57ZRK1HVNhUm#e855&zFN|2{mRcLHLp>jg4CPjO&!m6jps9YPWG((9(5z_@`Yh5;OX1ot6qml`6l=KpSf+@f8F>as2arTWE(~3Q8iH=C4V&SP!!b!SiZw@z zlH8yj8sS{mt;JOvvq7#fjY}##qnb;&kZexI<1o%)hS$|sOH_SgwiE02OAgP8u&}43 zM1*~0;uWP&iBBUuJ4FYG-h;i&_+$wu7e;|;A)uU`{ft`)ZsePe~FM zA*AX-``^IM>n;UCM_!sB!AQ<=)6f#HX&Sxlg2ro+Rjq&6T5~k*mkcY4OJ{2Cokibq zOBsN8s?Hbt~~cF~_T>5h>oPh*Vr7;|Cx? zc0FQOnI>AnR)ec*h2=nlL9=4yQm#_qgFA*%R0vD}6c4&|OWH%k2NU_AJQo;P4u8tW zJob4wdiaHLpzHrR?`LnY7HmkC)l*eIt8?3HEe04e?y;p?@k7p<4Zuuds&iKAkLGxA!@2YL-m6zE5;*-G z&~;@r#mbAIPib82-|_{DwA^e{5ikqy)RsWeqh_06Hg>lB0itcN!m`PYi@+qKqv=d^ z+oT|7!{Z!sw$MS0`zVO_*!dC9+#h4?pGtH*hVfn6qRiZ)%z&7|l={v5^;`cP9_b>b z&LliOvPC3?NJEm*%4{NTNnx@hWC`P-u=lu(O3t!e-CDi;0Qz#Uomw+?Ua0 zeCDRY5*bh~UPP+h5{$6wS6qs84<+5r7EXy38oP{%VMq0B1e#pUp-C9z%&Nt67)wmikW7~comkR;Nb-z3DbKGQ-DqZ1oJvrA=eW3L#mK)zUcv5Daw&xDlvv0 zBy!%0bJ+fADo_6GhNu`7nobbEd2K5r;cM0$1ODgE_V1U5nmOW{=@!1~KKrY2N06KS ztRe1ip2p3|Z&zYTJ^2OEFCfPnw%aXrVNYPCDv%ck^7?KF-md;)H}fRC(e^6qm~oqMrb;B^wi z3&lbz)F{<`^F?zWXS#G8$sm?H2>PW+!i|T(ovmq(WhL3Mj`D5Qr7*b$LnPdzTcDe~ z2gg{b`YXbz?tKTL8%4}camXT%b@&^Z9zjK@?fqzI<>jxAqNR}AM&z%8aWM z!^%%_1q7LEgf9%*?2x=9R_U4k%9@I#os_;p40BC1m(3)#c>l$8<`xSf>E!7HDb^aZ zrKm`~U7>!@kQ0@TQB8n(zX~a$BymvSXL4 zLP#2Ae&q(62`q^6`t?)S=G>fvZL5a@OLKlp{VsS3IUyeysRd9*R3!cdWKdh%!MFb` zHPeno!vbr7SWNu2O0+Jv{YJ%e z59|?kW8Bg|3iR@zsmfN7w-z_q;|zx5pYZxdt2lDAaA;~lEBcW`9t-i+`AAaY z%Ce#kxjjXrYBl>}i`UyZX3aALp-POd|FU^DPmz)q@N&D2Mg%;ZW*bsRV2@I!jA;7-n&r# zuaN2AMm150+W5VCDqIxE(ZtScVj^R$Fcg6ECZBI`kU>Bf+t1XLgOTu|%>GuYu&L3y zh?ez3@itsU83C_z2R!2O$ZcTyPXRL;8DgmGs@C3e1Ri8HT)0}P%FdQjVnfnyk=d1B!b(rfz8Y9~$1-~Mkz)E&WGF^MKp{?gQcr{^`X^bb1&-~+t0>@Qnj(_G3>D$iFz#Jr4k1(Brg#$`Vj(l zugrg@XUAJj&>vJA`sGWX;gdn#hi~M&gniQDr3I~Gfo^F@jAInC`Zdl~=;XP7IQsxM zkkGm9sV^-z&{G#%d1EHHovkP(Mo>CvlJ1vtvG-9C14(uZJ`v zHWokCfhl^|%(rSSJj*|#z4(!xo4+`II_O<5W}NEh-A}0bB`yuUnXiaqgrnQdrW`_` zs8HpON0o5>kH>=RXeCc1z_a=1RQ}IT5X8^FbVVuFIaj=Ms;^{JL3qo|1qu4*^@RIoO1cx4Kly-DJTrX`0bNKTh1&p(Pu}?vh&w1%Xj(4B+q-A`_P}V;k7$m;LOped-+1#e&zZiZk;(YQSN61NZ_UB-`r5uM+iFCHJ zftk=!89wQ=Aavfnx8x!xFgC@GT1tZ#r-O*zZ0VucXvU@wzFcLUqKbZ$wHz<%CWe2h zKqU-%|^O`xG>Q@h6 zzYH>bsL}v7^XD88LqM9*@=e*2&DGkI2_OZ2$p+s6ZHlDH0RHnVao2Y#Iq zh99L@1=b1gUq!^I5Rv3%LW(8S3-3>*SSx_(NpWLQ{uP1*jBI_->|+1T6X0d`6`0gE z)y?u{p_TaYPz}aNN;5IKE2IYBfIN!^qY%{6PWQ-AqU0sZDZ`hy#9RFe5w97g1H~*` zarI=yqbnxa8tylfeg`e&ANqf^wRhXEbfZe{ke-^#Jh~Q(D8aFS%aM3Rfz;PVj1(??Wj~l6Dz_+lgX22$wk?OJU%zkKe+wrIhCiCbq@>zu znu9klRubE|IPdu|mZEMj)`k9I8X*THFjf*PfOlw~wOSmjje8fC68|L zqpm5=#C)&wTYShl$6KX>B5-bhc=E^=nPPP53(Qsw4UL`cq4*)hGWN+bBi*FgDJ}Hw z7~1E+n-|^1o@Q|wXofXA)u(51rgU;JIuqZLv6flAmOn+^n)E_aKRr9^;bu0laF(bg zEWdeplYhkxB--f=1FtigXXNRES+(BFApWG#+PEyMN2~$M{)r~?3AYmis=HPGoq*|46kWgh z!d^XQvr|gWDawuK`ueW5r$(CChLJhM)%xPjRbsvCPkW!n+QPxsDDef!SiBq$5;M{< zKm&SUWAEcs_Zb=lT6rdo6KmLkF1e~@0=HIxc^k>S3}r~g()c@Gu;UczXI&5T3ll9? zKSAHa8A}66NPEJa?54k2>aA{Z=#+Bh5mX8=xSRztR9voKgHAl}qFB*Ik4d%`o{ctH zBOSKBNzn{;6-VEV!dp#HgYQFkl7g9jP#cwO%dG7@b|`IZ;w{a|5jgGe>hxwDcWyIy zi@;X9a~#tH-qM!{;%gBslE`r|#}Lzb2Gf8CyDq?rhW;oX=;6ZUg$_=c`hB+0__Tb} z+lfoacM#GSPv6I`s?yUq>y=@%^!8;LB118wso4UAE-W7)Bh86cG_R#|q6G-)r8<;L zmj4r0iazjhx2CH26GUONxrTS_nDK{07ZbF|vRE(`+9)OU6!l7?cOR1o9{hg+8w2G0 zwjFdzskNHTgt%;>&Qe%}D%FK{ggyW>S#5H)q<=%|1l_)bIujr&dN@<-IT$zKP600n zdK8=~1ZZdAg&3dEnBW|Mwm<3+Ky~S45{2o6OZ+A>ZnaoWXo66FN#zkg@{b!Ey+W#- z0tllF)FS|=Km_K9D?>jdX^FaFJR*2RL;U3vv@o_ldH(<>ntxI85BUJq-Wov(u0i-9 zE3QJOd8G46=8()+K&zf<{ZBOhr;2}5#XqU$pVSehJ{= zNA*0T`kqn!PbmJUlz&soKdI#()bfw&c}Mj;Bl?~Z{ZAlI`P>8xySpdld?$D?5Z5`(K1FI~^X&(LyCttFzZv0j8Gf;K~}^oEQ~)&M(yc@1u zodObLWD>I#)0fr~01SiBg4TjqSFO;N{-K|#i&Zl%KJf3o>H$;NAKY zVRIG`XbgzA9HVeUu~PJufGLXR$2p{Oj9Q+C9+d+hK(IkbV_C8E=@rIt7UyZx*kuSx SiRkxHS2x>HarGtofB)HTqjJdr literal 0 HcmV?d00001 diff --git a/.AGENTS/references/codex-usage.png b/.AGENTS/references/codex-usage.png new file mode 100644 index 0000000000000000000000000000000000000000..0aa5ef8125f269345623087741327c4fbf59025d GIT binary patch literal 82644 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⋙H2(rkB@#bx^yiacJ(rMX{Gag^n^UyS|p} z-cUpURZ-cvFbK&(=7dnM3J=3z@(Av6^|bK1y6m8O_LWk8uqALgDOB3ipWj@4e0Aas zsL-^;{Jkf}RUpb6W|*<^EnofWPWO|l20(cE?A3SAzAL3zv|4C# z>GBA)^wvO^li-89b!>aT9EpICuUu#mTll5SDt<|J7n0HMh}V=pK&2^DTEZhZZ4 z`tDdOORC;Rh4hw<*_@|@E~8^J3A#Qz43EoT)t6Azf`NygAYd(?e&$~`h{$qNRza6U zQ4+*xO!Yn(b1g&G|8DF@7=xc~hx)V6o(!c%A5fhof+TY+ISokk$6FjmQgr1B`x-4> zXyAC#19bh>>*;S^-W}^G8ssfC32kMFrnuLF>s;I~jB&mo5yMD(uOIo?95EV7n+Q2Z z^Fj5g*>yqj>PldME%hOo91W-|&L~|>_gxU1v>Hdq;^e__|Jy5a+aj$l~qvq*>hk?@9^F0xU^Qe2C$xA;Q0ERWE zwgXTBn0!~FtM1ueb_TjIpW9g3UP(%`N8){^^L0?GsqOc$dw%RARou0+mivE=L`oHd(g|z-yFZ6298dbdnPW% zhI+I}c^_wT@$v!95Q^1`ujkVb!bF)Os4o$mD7Z%*@f52uO6rLq(t0LUB&q(cjx@dFGj^ZfDQ@MJ7wbw;^& z6H=b+fzC|E?1Hmo%|viEW8N{}OMl>OiP|(*yZRoPxcXCgq>-;!pFA!<`smS)0*3{V z2hSeBvvb#VFpMb^g=pN1zy}BrlTKCpUp#;F#Sc}{S(ad!Q!Br&k5-bwx>#RNb4`(R z+nmG9L5hs5h7V;dJkyyxxI$0G+cJz^XUrmDiP$03EaZQM>v6BPIq5F9PgdZ;&2YB2EtBUM@840js`HqlO2nH z)m{JfeEPm;n((D6#t`MJO)@Rg#hCX^Q93&}FlCTWko)1=GapV3<5k`&yWw^yT3>1=54~^s z&(2t!Mu^aL8LoEw>axtOiBX&Q-XDHsZDL293cQ>DUfK497B?`!yUHrS|qgu zT9eoHemOOOSK|=IPXl8^d0Lj6tCh#0`-;qOFD$6=P_I6^ee&~bT8T7lrVOo36Sm5Vk4bsb}cdF@bP*cDOT7 z4hRgczqq-&-bZ0{#7;Cv5U5+R1j*LBYUv(RY0GY^LX6s!SJU+T;pmx;EU_$kzGUq& z$PC5hpE7aW>&I7zkB36f6lI5-7OBUKLgkNfpP)cT&TolLrnS5H!7Ef|1v?>KxxN={ zr>L($?;hDzIpyT^@yEBHT#aUM)C50s120Pl(SitUgqH9cN-bIhFXG+o&7J<^^EWRi z80jb$t%0qwIm++IZK&k~^uzwHcw8&6i~&l_Yb4r80VY6EN8kWHGF}tX?1n)ktx68J zXvZQ^vb-=7>A&RFJ0vVFA53I~aRR!{!4=^8UX}U#X_=HobV_VaSp5D7`|bSj<-^0< z8I=YTPz?;sy*aIC`fKe=X~R15ke(C`uAM~&tPr8H zHX@_KSXLHGNpQD0BMa?75iTpI)9fh4=n3USa40SfU1al`MUu0Lv6Q!O@2Gvg^aTK@7E8EBWQltA zw#y=}cf&5aODJo_*l>n0ztbfUk#2!!vL`L8FhL2$)Y^i(z>p@X;#~&R`6SIzV;|h$ z⪙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 FV1j}4YPbLZ literal 0 HcmV?d00001 diff --git a/.AGENTS/rules/architecture.md b/.AGENTS/rules/architecture.md new file mode 100644 index 0000000..161331d --- /dev/null +++ b/.AGENTS/rules/architecture.md @@ -0,0 +1,27 @@ +--- +description: Product scope and architecture invariants for Freeby +alwaysApply: true +--- + +# Product and architecture + +- Build a reliable native GNOME usage monitor for Codex, Claude Code, Cursor, + and Copilot across free and paid plans. +- Finish usage monitoring before cost, billing, budgets, naming, icon, or schema + identity changes. +- Keep GNOME entry points thin. Separate provider adapters, usage contracts, + services, UI actors, and preferences. +- Prefer GJS, Gio/GLib, Soup, and native GNOME APIs. Never download runtime + dependencies or invoke unpinned package runners. +- Keep expensive history scans in bounded, cancellable collector subprocesses. + Prevent overlapping jobs and ignore results after disable. +- Keep account-wide limits separate from local activity. Never derive quota or + subscription percentages from token totals. +- Store metrics, units, periods, source scope, capabilities, and freshness as + typed fields. Do not infer state from display strings. +- Treat unknown, unsupported, stale, unauthenticated, unlimited, and exhausted + as distinct states. Empty data is not zero usage. +- Preserve provider-specific token/cache semantics, deduplicate repeated events, + and handle cumulative counters, partial writes, rotation, and local dates. +- Adapt `.AGENTS/references/` to GNOME themes, keyboard access, screen readers, + work-area limits, and display scaling; do not copy another panel literally. diff --git a/.AGENTS/rules/quality.md b/.AGENTS/rules/quality.md new file mode 100644 index 0000000..1347a5c --- /dev/null +++ b/.AGENTS/rules/quality.md @@ -0,0 +1,24 @@ +--- +description: Verification and privacy requirements for every change +alwaysApply: true +--- + +# Quality, privacy, and verification + +- Add focused regression coverage for behavior changes. Keep automated fixtures + synthetic or sanitized and network-free. +- Run `npm run check` for JavaScript, schema, unit, and GJS integration checks. + Run `npm run pack` when packaging inputs or release metadata change. +- Use `python3 tools/smoke-shell.py` for GNOME lifecycle changes when a compatible + shell is available. Test the exact archive before release. +- Validate provider interfaces against current first-party documentation, + installed client versions, sanitized fixtures, and live accounts when safely + available. Record unavailable live checks honestly. +- Verify lifecycle, wake handling, accessibility, packaging, and every advertised + GNOME version before release. Do not claim checks that were not run. +- Keep credentials, prompts, responses, raw transcripts, account identities, and + raw session IDs out of logs, fixtures, caches, commits, and release archives. +- Store derived usage metadata only in private XDG state/cache locations. Hash + identifiers used solely for deduplication or account-change detection. +- Preserve unrelated worktree changes and user-owned assets. Stage explicit paths + and inspect the staged diff before committing. diff --git a/.AGENTS/rules/releases.md b/.AGENTS/rules/releases.md new file mode 100644 index 0000000..26213eb --- /dev/null +++ b/.AGENTS/rules/releases.md @@ -0,0 +1,22 @@ +--- +description: Branch, commit, tag, and prerelease workflow +alwaysApply: true +--- + +# Version control and releases + +- Develop on `dev`; reserve `main` for the later stable migration. +- Follow `.AGENTS/plans/roadmap.md` in order and keep its checkboxes and evidence + current. Planning setup alone is not a product milestone. +- Use conventional commits and separate implementation from release-documentation + checkpoints when that improves reviewability. +- A milestone requires applicable tests, strict schema validation, deterministic + packaging, an isolated install, and lifecycle checks for UI changes. +- Push the candidate to `dev` and require green remote CI before tagging. +- Create an annotated semantic prerelease tag from the exact tested commit. +- Publish the CI-built archive as a GitHub prerelease with install/upgrade, + verification, known limitations, and downgrade instructions. +- Verify the remote tag target, prerelease/draft status, downloadable asset, and + checksum. Record the commit, URL, size, and SHA-256 in the roadmap. +- Never mark a milestone complete while a required check or publication is + blocked. Keep stable unreleased until all provider and stabilization gates pass. diff --git a/.gitignore b/.gitignore index 40d87c3..41023e5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,11 @@ -build/ +/build/ +/dist/ +/.cache/ +meson-private/ + +__pycache__/ +*.py[cod] *.swp *.swo *~ -.cache/ -meson-private/ -plan.md +.DS_Store diff --git a/AGENTS.md b/AGENTS.md index 71bf8c9..279670c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,43 +1,4 @@ -# Project guidance +# Agent entrypoint -Applies to the whole repository. Read [plan.md](plan.md) and [CONTRIBUTING.md](CONTRIBUTING.md) before implementation. - -## Product priorities - -- Build a reliable native GNOME usage monitor for Codex, Claude Code, Cursor, and Copilot, including free and paid plans. -- Complete usage monitoring before cost breakdowns, estimates, budgets, or billing features. -- Keep the Freeby name, icon, extension UUID, and settings schema identity during the usage milestones. Defer rebranding until the system works. -- Provide preferences for panel position, providers, refresh behavior, notifications, and history as scheduled in the plan. -- Use `reference-images/` for layout inspiration; adapt to GNOME themes, keyboard access, screen readers, and display scaling. - -## Architecture and correctness - -- Keep GNOME entry points thin; separate provider adapters, usage logic, collection services, UI, and preferences. -- Prefer GJS, Gio/GLib, and native GNOME APIs. Use Meson for installation and packaging; keep development dependencies out of the runtime archive. -- Replace the legacy Bash/embedded-Python collector incrementally. Do not introduce runtime package downloads or unpinned `npx` execution. -- Keep expensive history scans out of the Shell UI process. Bound subprocess lifetimes, prevent overlapping refreshes, and cancel work on disable. -- Store numeric metrics, units, periods, capabilities, source scope, and freshness explicitly. Never infer state from display text. -- Unknown, unsupported, stale, unauthenticated, and exhausted are distinct states. An empty response is not zero usage. -- Keep account-wide limits separate from local activity; never infer subscription usage from token totals or add overlapping data sources. -- Handle cumulative counters, repeated events, partial records, timezone boundaries, and provider-specific cache-token semantics. -- Validate provider APIs and CLI versions against current documentation and fixtures; support partial results without hiding failures. -- Keep credentials and transcript content out of logs, fixtures, caches, and releases. Persist only required usage metadata in XDG locations. - -## Verification - -- Add meaningful regression tests for changed behavior. Default automated tests to synthetic or sanitized fixtures without live accounts or network access. -- While legacy scripts remain, use `bash -n scripts/freeby.sh scripts/copilot-setup.sh`, `shellcheck scripts/freeby.sh scripts/copilot-setup.sh`, and `bats tests/` as appropriate. -- Validate schemas with `glib-compile-schemas --strict --dry-run schemas`; use an isolated temporary prefix for build/install checks. -- Add and document GJS/pure-JavaScript test and lint commands as the new modules land. Do not claim unavailable checks passed. -- Test only as broadly as the change requires; documentation-only changes need document/link checks, not a live desktop installation. -- Verify lifecycle, wake handling, accessibility, packaging, and each advertised GNOME version before a release. Label unsupported or unverified combinations honestly. - -## Milestones and releases - -- Follow the ordered milestones in `plan.md`; keep its checkboxes and evidence current. Historical notes under `docs/planning/` are context, not the active roadmap. -- Use conventional commits and stage only intentional changes. Preserve unrelated edits, local credentials, and user reference assets. -- After each completed milestone, commit the tested changes, tag the milestone, and publish an installable GitHub prerelease as agreed with the user. -- Resolve the intended repository and release branch before publishing. Release artifacts must match the tagged commit and pass the milestone gate. -- Include installation/upgrade instructions, release notes, verification results, known limitations, and a downgrade path. Verify the published tag and downloadable artifact. -- If a required check or release operation is blocked, report the exact blocker and leave the milestone incomplete; never label an unfinished milestone released. -- Planning/documentation setup alone is not a completed product milestone and does not warrant a prerelease. +Before changing this repository, read and follow [.AGENTS/README.md](.AGENTS/README.md). +The active implementation roadmap is [.AGENTS/plans/roadmap.md](.AGENTS/plans/roadmap.md). diff --git a/CHANGELOG.md b/CHANGELOG.md index a89cead..aba4733 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,23 @@ All notable changes are documented here. Freeby uses semantic prerelease versions while version 2 is developed on the `dev` branch. +## Unreleased + +### Changed + +- Consolidated agent rules, the active roadmap, archived planning notes, and + visual implementation references under `.AGENTS/`, with a minimal root + `AGENTS.md` discovery entrypoint. +- Added documentation indexes and a runtime architecture guide; historical v1 + screenshots now live under `docs/assets/screenshots/v1/`. +- Split the temporary combined provider module into dedicated Cursor and Copilot + adapters without changing their preview behavior. + +### Removed + +- Removed the superseded v1 indicator, Bash collector/setup scripts, and their + obsolete Bats suite. These files were no longer installed after alpha.1. + ## 2.0.0-alpha.2 - 2026-09-06 ### Added diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 58ed454..9748c12 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -40,5 +40,6 @@ private headless GNOME Shell session when GNOME Shell is available locally. - Preserve GNOME theme behavior, keyboard focus, accessible names, and readable scaling when changing the panel UI. -The authoritative milestone gates and release process are in [plan.md](plan.md). -Project-wide agent guidance is in [AGENTS.md](AGENTS.md). +The authoritative milestone gates and release process are in +[.AGENTS/plans/roadmap.md](.AGENTS/plans/roadmap.md). Project-wide agent guidance +starts in [AGENTS.md](AGENTS.md), with the substantive rules under `.AGENTS/`. diff --git a/README.md b/README.md index 1b2735e..46bfa59 100644 --- a/README.md +++ b/README.md @@ -97,5 +97,6 @@ artifacts are never deleted. To downgrade, install an older release archive with `gnome-extensions install --force` and restart the session. See [CONTRIBUTING.md](CONTRIBUTING.md) for development practices and -[plan.md](plan.md) for the ordered provider milestones. Freeby is licensed under -the [MIT License](LICENSE). +[.AGENTS/plans/roadmap.md](.AGENTS/plans/roadmap.md) for the ordered provider +milestones. Architecture and provider documentation is indexed in +[docs/README.md](docs/README.md). Freeby is licensed under the [MIT License](LICENSE). diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..91b263e --- /dev/null +++ b/docs/README.md @@ -0,0 +1,11 @@ +# Documentation + +- [Architecture](architecture.md) describes runtime boundaries, collection flow, + storage, and packaging. +- [Provider notes](providers/README.md) record verified capabilities and upstream + compatibility limits. +- [Historical v1 screenshots](assets/screenshots/v1/) are retained for release + history; they do not represent the version 2 interface. + +Agent rules, active milestones, archived plans, and visual implementation +references live in [.AGENTS/](../.AGENTS/README.md). diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..355ceda --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,48 @@ +# Architecture + +Freeby is a GNOME Shell extension with a small trusted UI process and short-lived +collector subprocesses. Provider code never creates GNOME actors, and UI code +never reads provider credentials or transcript formats. + +## Runtime flow + +1. `extension.js` creates the indicator and `UsageService`. +2. `UsageService` loads cached provider records, schedules independent refreshes, + and starts one bounded GJS collector per enabled provider. +3. `src/collector/main.js` supplies shared file, HTTP, authentication, and process + capabilities to one adapter under `src/providers/`. +4. The adapter returns a versioned record from `src/core/usage.js` with limits and + history represented as independent sections. +5. The service validates and merges the record, stores derived metadata, evaluates + notification crossings, and asks the indicator to render typed data. + +## Boundaries + +- `src/core/` owns the provider-neutral contract, aggregation, formatting, and + threshold state. +- `src/providers/` owns one adapter per external provider. +- `src/services/` owns scheduling, process lifetimes, HTTP, history scanning, and + private XDG storage. +- `src/ui/` owns Shell actors and accessibility behavior. +- `prefs.js` is the libadwaita preferences entry point until configuration pages + are split during alpha.5. + +Collectors have deadlines and cancellation. Refresh jobs do not overlap, resume +events refresh only after wake, and results are ignored after extension disable. + +## Privacy and storage + +Freeby stores derived records in `$XDG_STATE_HOME/freeby` and incremental scan +caches in `$XDG_CACHE_HOME/freeby`. Credentials are read only when needed and are +never written by Freeby. Prompt/response content is ignored, while session and +account identifiers used for deduplication are hashed before persistence. + +Account quota and local token activity remain separate scopes. Local activity is +not billing data and never becomes a fabricated subscription percentage. + +## Distribution + +Meson installs only extension entry points, source modules, metadata, styles, +license, and an extension-local schema. `tools/package.py` uses the same explicit +runtime allowlist to produce deterministic ZIP files; agent material, docs, tests, +tools, screenshots, and references cannot enter a release archive. diff --git a/docs/assets/README.md b/docs/assets/README.md new file mode 100644 index 0000000..e52ab23 --- /dev/null +++ b/docs/assets/README.md @@ -0,0 +1,8 @@ +# Documentation assets + +`screenshots/v1/` preserves the original Freeby interface for release history. +Current version 2 screenshots will be added during the alpha.5 interface polish +and stabilization milestones. + +Implementation-only visual references belong under `.AGENTS/references/`, not in +this public-facing documentation asset tree. diff --git a/docs/s1.png b/docs/assets/screenshots/v1/panel-indicator.png similarity index 100% rename from docs/s1.png rename to docs/assets/screenshots/v1/panel-indicator.png diff --git a/docs/s2.png b/docs/assets/screenshots/v1/usage-menu.png similarity index 100% rename from docs/s2.png rename to docs/assets/screenshots/v1/usage-menu.png diff --git a/docs/providers/README.md b/docs/providers/README.md new file mode 100644 index 0000000..28c9ecd --- /dev/null +++ b/docs/providers/README.md @@ -0,0 +1,12 @@ +# Provider documentation + +- [Claude Code](claude.md): account quota plus local daily/model history; released + in `v2.0.0-alpha.2`. +- Codex: account quota through app-server plus local daily/model history; released + in `v2.0.0-alpha.1`. A dedicated compatibility note will be added during + stabilization. +- Cursor: preview adapter pending validation in `v2.0.0-alpha.3`. +- GitHub Copilot: preview adapter pending validation in `v2.0.0-alpha.4`. + +The active release status and verification evidence live in +[the roadmap](../../.AGENTS/plans/roadmap.md). diff --git a/indicator.js b/indicator.js deleted file mode 100644 index b2aaa8a..0000000 --- a/indicator.js +++ /dev/null @@ -1,140 +0,0 @@ -import Clutter from 'gi://Clutter'; -import GLib from 'gi://GLib'; -import Gio from 'gi://Gio'; -import St from 'gi://St'; -import GObject from 'gi://GObject'; - -import * as Main from 'resource:///org/gnome/shell/ui/main.js'; -import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js'; -import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js'; - -const PROVIDERS = ['codex', 'cursor', 'copilot']; -const LABELS = { codex: 'Codex', cursor: 'Cursor', copilot: 'Copilot' }; -const SCRIPT = GLib.build_filenamev([GLib.get_home_dir(), '.local', 'bin', 'freeby.sh']); - -export const FreebyIndicator = GObject.registerClass( - class FreebyIndicator extends PanelMenu.Button { - _init(settings) { - super._init(0.0, 'Freeby', false); - this._settings = settings; - this._prev = {}; - for (const k of PROVIDERS) this._prev[k] = null; - this._refreshSeq = 0; - - this._panelBox = new St.BoxLayout({ style_class: 'panel-status-menu-box freeby-panel', accessible_name: 'Freeby usage indicator' }); - this._label = new St.Label({ text: 'AI', y_align: Clutter.ActorAlign.CENTER, style_class: 'freeby-panel-label', accessible_name: 'Usage count' }); - this._panelBox.add_child(this._label); - this.add_child(this._panelBox); - this.connect('button-press-event', () => { this._refresh(); return false; }); - - this._items = {}; - for (const k of PROVIDERS) { - const box = new St.BoxLayout({ style_class: 'freeby-item-box', accessible_name: `${LABELS[k]} usage` }); - const dot = new St.Label({ text: '\u25CB', style_class: 'freeby-dot freeby-dot-off', y_align: Clutter.ActorAlign.CENTER, accessible_name: `${LABELS[k]} status` }); - const name = new St.Label({ text: LABELS[k], style_class: 'freeby-item-name', y_align: Clutter.ActorAlign.CENTER }); - const summary = new St.Label({ text: '\u2014', style_class: 'freeby-item-summary', y_align: Clutter.ActorAlign.CENTER, accessible_name: `${LABELS[k]} summary` }); - box.add_child(dot); - box.add_child(name); - box.add_child(summary); - const item = new PopupMenu.PopupMenuItem('', { reactive: false, can_focus: false }); - item.add_child(box); - this.menu.addMenuItem(item); - this._items[k] = { dot, summary }; - } - - this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); - this._statusItem = new PopupMenu.PopupMenuItem('\u21BB Last checked: never', { reactive: true, can_focus: false }); - this._statusItem.label.add_style_class_name('freeby-status'); - this._statusItem.connect('activate', () => { this._refresh(); GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { this.menu.open(); return GLib.SOURCE_REMOVE; }); }); - this.menu.addMenuItem(this._statusItem); - - this._timerId = null; - this._setupTimer(); - this._refresh(); - this._monitorWake(); - } - - _setupTimer() { - if (this._timerId) GLib.source_remove(this._timerId); - const sec = Math.max(30, this._settings.get_int('refresh-interval')); - this._timerId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, sec, () => { this._refresh(); return GLib.SOURCE_CONTINUE; }); - } - - _monitorWake() { - try { - this._sleepId = Gio.DBus.system.signal_subscribe( - 'org.freedesktop.login1', 'org.freedesktop.login1.Manager', - 'PrepareForSleep', '/org/freedesktop/login1', null, Gio.DBusSignalFlags.NONE, - (_, __, ___, ____, _____, params) => { - if (params.get_child_value(0).get_boolean()) { - log('freeby: woke from sleep, refreshing'); - this._refresh(); - } - }); - } catch (e) { logError(e, 'freeby: could not monitor sleep/wake'); } - } - - _notify(title, body) { - if (!this._settings.get_boolean('notifications-enabled')) return; - try { - const src = new Main.messageTray.Source('Freeby', 'dialog-information-symbolic'); - Main.messageTray.add(src); - src.addNotification(new Main.messageTray.Notification({ source: src, title, body })); - } catch (e) { logError(e, 'freeby: notification failed'); } - } - - _setError() { - this._label.text = 'ai'; - this._label.style_class = 'freeby-panel-label'; - } - - _refresh() { - this._refreshSeq++; - const seq = this._refreshSeq; - let proc; - try { proc = Gio.Subprocess.new(['/bin/bash', SCRIPT], Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_PIPE); } - catch (e) { this._setError(); logError(e, 'freeby: failed to spawn script'); return; } - - proc.communicate_utf8_async(null, null, (p, res) => { - if (seq !== this._refreshSeq) return; - let stdout, stderr; - try { [, stdout, stderr] = p.communicate_utf8_finish(res); } - catch (e) { this._setError(); logError(e, 'freeby: subprocess failed'); return; } - if (!p.get_successful()) { this._setError(); log(`freeby: script error: ${stderr}`); return; } - this._apply(stdout); - }); - } - - _apply(stdout) { - let data; - try { data = JSON.parse(stdout); } - catch (e) { this._setError(); logError(e, 'freeby: bad JSON'); return; } - if (!data || typeof data !== 'object') { this._setError(); log('freeby: empty or invalid data'); return; } - - const active = PROVIDERS.filter(k => data[k]?.has_remaining).length; - const total = PROVIDERS.filter(k => data[k]?.available).length; - this._label.text = active > 0 ? `ai\u00B7${active}` : 'ai'; - this._label.style_class = 'freeby-panel-label' + (active === total && total > 0 ? ' freeby-panel-green' : active > 0 ? ' freeby-panel-yellow' : total > 0 ? ' freeby-panel-red' : ''); - - const hit = PROVIDERS.filter(k => data[k]?.available && !data[k].has_remaining && this._prev[k]?.has_remaining !== false).map(k => LABELS[k]); - this._prev = {}; - for (const k of PROVIDERS) this._prev[k] = data[k] ? { has_remaining: data[k].has_remaining } : null; - if (hit.length) this._notify('Usage limit reached', `${hit.join(', ')} ${hit.length === 1 ? 'has' : 'have'} hit their limit`); - - for (const k of PROVIDERS) { - const d = data[k], { dot, summary } = this._items[k]; - if (!d) { dot.text = '\u25CB'; dot.style_class = 'freeby-dot freeby-dot-off'; summary.text = 'no data'; summary.style_class = 'freeby-item-summary'; } - else if (!d.available) { dot.text = '\u25CB'; dot.style_class = 'freeby-dot freeby-dot-off'; summary.text = d.summary || 'not available'; summary.style_class = 'freeby-item-summary freeby-dim'; } - else if (d.summary?.includes('limit reached')) { dot.text = '\u25CF'; dot.style_class = 'freeby-dot freeby-dot-red'; summary.text = d.summary; summary.style_class = 'freeby-item-summary freeby-red'; } - else if (d.summary?.includes('expired') || d.summary?.includes('parse error')) { dot.text = '\u25CF'; dot.style_class = 'freeby-dot freeby-dot-yellow'; summary.text = d.summary; summary.style_class = 'freeby-item-summary freeby-yellow'; } - else { dot.text = '\u25CF'; dot.style_class = 'freeby-dot freeby-dot-green'; summary.text = d.summary; summary.style_class = 'freeby-item-summary freeby-green'; } - } - this._statusItem.label.text = `\u21BB Last checked: ${GLib.DateTime.new_now_local().format('%H:%M:%S')}`; - } - - destroy() { - if (this._timerId) { GLib.source_remove(this._timerId); this._timerId = null; } - if (this._sleepId) { Gio.DBus.system.signal_unsubscribe(this._sleepId); this._sleepId = null; } - super.destroy(); - } - }); diff --git a/meson.build b/meson.build index 684694c..8595921 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('freeby', version: '2.0.0-alpha.2', license: 'MIT', +project('freeby', version: '2.0.0-alpha.3-dev.0', license: 'MIT', meson_version: '>= 0.56.0', default_options: ['warning_level=0']) diff --git a/package.json b/package.json index ec38f15..d42f69c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "freeby", - "version": "2.0.0-alpha.2", + "version": "2.0.0-alpha.3-dev.0", "private": true, "type": "module", "scripts": { diff --git a/scripts/copilot-setup.sh b/scripts/copilot-setup.sh deleted file mode 100644 index c872d5f..0000000 --- a/scripts/copilot-setup.sh +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env bash -# One-time GitHub Copilot device-flow authentication. -# Saves token to ~/.config/freeby/copilot-token. -set -euo pipefail - -# --- prereq checks ----------------------------------------------------------- -for cmd in curl python3; do - if ! command -v "$cmd" >/dev/null 2>&1; then - echo "error: $cmd is required but not installed" >&2 - exit 1 - fi -done - -CONFIG_DIR="$HOME/.config/freeby" -TOKEN_FILE="$CONFIG_DIR/copilot-token" -CLIENT_ID="Iv1.b507a08c87ecfe98" -SCOPE="read:user" -MAX_ATTEMPTS=60 - -mkdir -p "$CONFIG_DIR" - -if [ -f "$TOKEN_FILE" ]; then - echo "Token already exists at $TOKEN_FILE" - echo "Delete it and re-run to re-authenticate." - exit 0 -fi - -echo "Requesting device code..." -DEVICE_RESP="$(curl -s -X POST 'https://github.com/login/device/code' \ - -H 'Accept: application/json' \ - -d "client_id=$CLIENT_ID&scope=$SCOPE")" - -DEVICE_CODE="$(printf '%s' "$DEVICE_RESP" | python3 -c "import json,sys; print(json.load(sys.stdin).get('device_code',''))")" -USER_CODE="$(printf '%s' "$DEVICE_RESP" | python3 -c "import json,sys; print(json.load(sys.stdin).get('user_code',''))")" -VERIFICATION_URI="$(printf '%s' "$DEVICE_RESP" | python3 -c "import json,sys; print(json.load(sys.stdin).get('verification_uri',''))")" -INTERVAL="$(printf '%s' "$DEVICE_RESP" | python3 -c "import json,sys; print(json.load(sys.stdin).get('interval',5))")" - -if [ -z "$DEVICE_CODE" ] || [ -z "$USER_CODE" ]; then - echo "Failed to get device code. Response:" - printf '%s\n' "$DEVICE_RESP" - exit 1 -fi - -echo "" -echo "1. Go to: $VERIFICATION_URI" -echo "2. Enter code: $USER_CODE" -echo "" -echo "Waiting for authentication... (timeout after $MAX_ATTEMPTS attempts)" - -attempt=0 -while [ "$attempt" -lt "$MAX_ATTEMPTS" ]; do - attempt=$((attempt + 1)) - sleep "$INTERVAL" - TOKEN_RESP="$(curl -s -X POST 'https://github.com/login/oauth/access_token' \ - -H 'Accept: application/json' \ - -d "client_id=$CLIENT_ID&device_code=$DEVICE_CODE&grant_type=urn:ietf:params:oauth:grant-type:device_code")" - - TOKEN="$(printf '%s' "$TOKEN_RESP" | python3 -c "import json,sys; print(json.load(sys.stdin).get('access_token',''))")" - ERROR="$(printf '%s' "$TOKEN_RESP" | python3 -c "import json,sys; print(json.load(sys.stdin).get('error',''))")" - - if [ -n "$TOKEN" ]; then - printf '%s' "$TOKEN" > "$TOKEN_FILE" - chmod 600 "$TOKEN_FILE" - echo "Token saved to $TOKEN_FILE" - exit 0 - fi - - if [ "$ERROR" != "authorization_pending" ]; then - echo "Error: $ERROR" - printf '%s\n' "$TOKEN_RESP" - exit 1 - fi - - printf '.' -done - -echo "" -echo "Timed out after $MAX_ATTEMPTS attempts. Try again when ready." -exit 1 diff --git a/scripts/freeby.sh b/scripts/freeby.sh deleted file mode 100755 index 4da906b..0000000 --- a/scripts/freeby.sh +++ /dev/null @@ -1,187 +0,0 @@ -#!/usr/bin/env bash -# Aggregates local AI coding tool usage into one JSON blob. -# Run standalone: bash ~/.local/bin/freeby.sh | python3 -m json.tool - -set -uo pipefail - -FREEBY_TMP="$(mktemp -d)" || { echo '{"error":"failed to create temp dir"}'; exit 1; } -trap 'rm -rf "$FREEBY_TMP"' EXIT - -# --- codex ------------------------------------------------------------------- -fetch_codex() { - local out="$FREEBY_TMP/codex" - if [ ! -f "$HOME/.codex/auth.json" ]; then - echo '{"available":false,"has_remaining":false,"summary":"not detected"}' > "$out" - return - fi - local json - json="$(npx --yes codex-check --auth "$HOME/.codex/auth.json" --json 2>/dev/null)" - if [ -z "$json" ]; then - echo '{"available":true,"has_remaining":false,"summary":"auth.json found, codex-check produced no output"}' > "$out" - return - fi - printf '%s' "$json" | python3 -c " -import json, sys -from datetime import datetime, timezone -try: - data = json.load(sys.stdin) - if isinstance(data, list): - if not data: - raise ValueError('empty response from codex-check') - data = data[0] - w = data.get('windows', {}).get('primary', {}) - pct = float(w.get('percentUsed', 0)) - resets_at = w.get('resetsAt', '') - if resets_at: - dt = datetime.fromisoformat(resets_at.replace('Z', '+00:00')) - now = datetime.now(timezone.utc) - diff = dt - now - if diff.total_seconds() <= 0: cd = 'now' - elif diff.days > 0: cd = f'in {diff.days}d {diff.seconds//3600}h' - else: cd = f'in {diff.seconds//3600}h {(diff.seconds%3600)//60}m' - else: cd = 'unknown' - has = pct < 100 - summary = f'limit reached, resets {cd}' if pct >= 100 else f'{min(100, pct):.0f}% used, resets {cd}' - print(json.dumps({'available': True, 'has_remaining': has, 'summary': summary})) -except Exception as e: - print(json.dumps({'available': True, 'has_remaining': False, 'summary': f'parse error: {e}'})) -" > "$out" 2>/dev/null -} - -# --- cursor ------------------------------------------------------------------ -fetch_cursor() { - local out="$FREEBY_TMP/cursor" - local auth_file="$HOME/.config/cursor/auth.json" - if [ ! -f "$auth_file" ]; then - echo '{"available":false,"has_remaining":false,"summary":"not detected"}' > "$out" - return - fi - local token - token="$(python3 -c " -import json, sys -print(json.load(open(sys.argv[1])).get('accessToken','')) -" "$auth_file" 2>/dev/null)" - if [ -z "$token" ]; then - echo '{"available":true,"has_remaining":false,"summary":"auth.json found but no accessToken"}' > "$out" - return - fi - local resp http body - resp="$(curl -s -w '\n%{http_code}' --connect-timeout 5 --max-time 10 \ - -X POST 'https://api2.cursor.sh/aiserver.v1.DashboardService/GetCurrentPeriodUsage' \ - -H "Authorization: Bearer $token" \ - -H 'Content-Type: application/json' \ - -H 'Connect-Protocol-Version: 1' \ - -d '{}' 2>/dev/null)" - http="$(printf '%s' "$resp" | tail -1)" - body="$(printf '%s' "$resp" | sed '$d')" - - if [ "$http" = "401" ]; then - echo '{"available":true,"has_remaining":false,"summary":"token expired, reopen cursor to refresh"}' > "$out" - elif [ "$http" = "200" ] && [ -n "$body" ]; then - printf '%s' "$body" | python3 -c " -import json, sys -from datetime import datetime, timezone -try: - d = json.load(sys.stdin) - pct = float(d.get('planUsage', {}).get('totalPercentUsed', 0)) - end_ms = int(d.get('billingCycleEnd', '0')) - if end_ms == 0: - print(json.dumps({'available': True, 'has_remaining': pct < 100, 'summary': f'{min(100, pct):.0f}% used, resets unknown'})) - else: - dt = datetime.fromtimestamp(end_ms/1000, tz=timezone.utc) - now = datetime.now(timezone.utc) - diff = dt - now - if diff.total_seconds() <= 0: cd = 'now' - elif diff.days > 0: cd = f'in {diff.days}d {diff.seconds//3600}h' - else: cd = f'in {diff.seconds//3600}h {(diff.seconds%3600)//60}m' - has = pct < 100 - summary = f'limit reached, resets {cd}' if pct >= 100 else f'{min(100, pct):.0f}% used, resets {cd}' - print(json.dumps({'available': True, 'has_remaining': has, 'summary': summary})) -except Exception as e: - print(json.dumps({'available': True, 'has_remaining': False, 'summary': f'parse error: {e}'})) -" > "$out" 2>/dev/null - else - printf '{"available":true,"has_remaining":false,"summary":"API returned HTTP %s"}' "$http" > "$out" - fi -} - -# --- copilot ----------------------------------------------------------------- -fetch_copilot() { - local out="$FREEBY_TMP/copilot" - local token="" - if command -v gh >/dev/null 2>&1; then - token="$(gh auth token 2>/dev/null | tr -d '[:space:]')" - fi - if [ -z "$token" ] && [ -f "$HOME/.config/freeby/copilot-token" ]; then - token="$(tr -d '[:space:]' < "$HOME/.config/freeby/copilot-token" 2>/dev/null)" - fi - if [ -z "$token" ]; then - if [ ! -f "$HOME/.config/freeby/copilot-token" ] && ! command -v gh >/dev/null 2>&1; then - echo '{"available":false,"has_remaining":false,"summary":"run copilot-setup to authenticate"}' > "$out" - else - echo '{"available":false,"has_remaining":false,"summary":"not detected"}' > "$out" - fi - return - fi - local resp http body - resp="$(curl -s -w '\n%{http_code}' --connect-timeout 5 --max-time 10 \ - 'https://api.github.com/copilot_internal/user' \ - -H "Authorization: Bearer $token" \ - -H 'Content-Type: application/json' 2>/dev/null)" - http="$(printf '%s' "$resp" | tail -1)" - body="$(printf '%s' "$resp" | sed '$d')" - - if [ "$http" = "401" ] || [ "$http" = "403" ]; then - echo '{"available":true,"has_remaining":false,"summary":"auth expired, re-run copilot-setup"}' > "$out" - elif [ "$http" = "200" ] && [ -n "$body" ]; then - printf '%s' "$body" | python3 -c " -import json, sys -from datetime import datetime, timezone -try: - d = json.load(sys.stdin) - reset_str = d.get('quota_reset_date_utc', '') - dt = datetime.fromisoformat(reset_str.replace('Z', '+00:00')) if reset_str else None - q = d.get('quota_snapshots', {}).get('chat', {}) - if q.get('entitlement', 0) == 0: - q = d.get('quota_snapshots', {}).get('completions', {}) - remaining = q.get('remaining', 0) - entitlement = q.get('entitlement', 0) - used = q.get('credits_used', 0) - if dt: - now = datetime.now(timezone.utc) - diff = dt - now - if diff.total_seconds() <= 0: cd = 'now' - elif diff.days > 0: cd = f'in {diff.days}d {diff.seconds//3600}h' - else: cd = f'in {diff.seconds//3600}h {(diff.seconds%3600)//60}m' - else: cd = 'unknown' - has = remaining > 0 - if entitlement == 0: summary = f'no quota, resets {cd}' - elif remaining <= 0: summary = f'limit reached, resets {cd}' - else: summary = f'{min(100, round(used/entitlement*100))}% used, resets {cd}' - print(json.dumps({'available': True, 'has_remaining': has, 'summary': summary})) -except Exception as e: - print(json.dumps({'available': True, 'has_remaining': False, 'summary': f'parse error: {e}'})) -" > "$out" 2>/dev/null - else - printf '{"available":true,"has_remaining":false,"summary":"API returned HTTP %s"}' "$http" > "$out" - fi -} - -# --- run all in parallel ----------------------------------------------------- -fetch_codex & -fetch_cursor & -fetch_copilot & -wait - -# --- output ------------------------------------------------------------------ -python3 -c " -import json, sys -result = {} -for name in ('codex', 'cursor', 'copilot'): - try: - with open('$FREEBY_TMP/' + name) as f: - result[name] = json.load(f) - except Exception: - result[name] = {'available': False, 'has_remaining': False, 'summary': 'no data'} -print(json.dumps(result)) -" diff --git a/src/collector/main.js b/src/collector/main.js index 1512131..e601545 100644 --- a/src/collector/main.js +++ b/src/collector/main.js @@ -5,7 +5,8 @@ import System from 'system'; import {record, section} from '../core/usage.js'; import {collectClaude} from '../providers/claude.js'; import {collectCodex} from '../providers/codex.js'; -import {collectLegacy} from '../providers/legacy.js'; +import {collectCopilot} from '../providers/copilot.js'; +import {collectCursor} from '../providers/cursor.js'; import {fingerprint, findCommand, join, readJson} from '../services/files.js'; import {scanHistory} from '../services/history.js'; import {requestJson} from '../services/http.js'; @@ -63,7 +64,8 @@ const io = { (async () => { try { const result = id === 'codex' ? await collectCodex(io) - : id === 'claude' ? await collectClaude(io) : await collectLegacy(id, io); + : id === 'claude' ? await collectClaude(io) + : id === 'cursor' ? await collectCursor(io) : await collectCopilot(io); if (!cancellable.is_cancelled()) print(JSON.stringify(result)); } catch { diff --git a/src/providers/copilot.js b/src/providers/copilot.js new file mode 100644 index 0000000..f967b36 --- /dev/null +++ b/src/providers/copilot.js @@ -0,0 +1,33 @@ +import {number, record, section, windowUsage} from '../core/usage.js'; + +export async function collectCopilot(io) { + const result = record('copilot'); + result.capabilities.limits = true; + const token = await io.token('copilot'); + if (!token) { + result.limits = {...result.limits, ...section('missing-auth', 'Sign in to GitHub CLI to read Copilot usage.')}; + return result; + } + const {status, data} = await io.http('https://api.github.com/copilot_internal/user', + {headers: {Authorization: `Bearer ${token}`, Accept: 'application/json'}}); + if (status !== 200 || !data || typeof data !== 'object') { + result.limits = {...result.limits, ...section([401, 403].includes(status) ? 'missing-auth' : 'unavailable', + [401, 403].includes(status) ? 'Reconnect GitHub CLI to read Copilot usage.' : `Usage endpoint unavailable (HTTP ${status}).`)}; + return result; + } + for (const [key, quota] of Object.entries(data.quota_snapshots ?? {})) { + const limit = number(quota.entitlement); + const remaining = number(quota.remaining); + const item = windowUsage({id: key, label: key.replaceAll('_', ' '), unit: 'requests', limit, + used: limit !== null && remaining !== null ? Math.max(0, limit - remaining) : null, + unlimited: quota.unlimited === true, resetsAt: data.quota_reset_date_utc}); + if (item) + result.limits.windows.push(item); + } + result.plan = data.copilot_plan ?? null; + result.limits = {...result.limits, ...section(result.limits.windows.length ? 'ready' : 'unavailable', + result.limits.windows.length ? '' : 'No supported allowance was reported. Check the GitHub Copilot dashboard.'), + updatedAt: result.limits.windows.length ? Date.now() : null}; + result.accountKey = io.fingerprint(token); + return result; +} diff --git a/src/providers/cursor.js b/src/providers/cursor.js new file mode 100644 index 0000000..3578445 --- /dev/null +++ b/src/providers/cursor.js @@ -0,0 +1,29 @@ +import {number, record, section, windowUsage} from '../core/usage.js'; + +export async function collectCursor(io) { + const result = record('cursor'); + result.capabilities.limits = true; + const token = await io.token('cursor'); + if (!token) { + result.limits = {...result.limits, ...section('missing-auth', 'Sign in to Cursor to read usage.')}; + return result; + } + const {status, data} = await io.http('https://api2.cursor.sh/aiserver.v1.DashboardService/GetCurrentPeriodUsage', + {method: 'POST', headers: {Authorization: `Bearer ${token}`, 'Connect-Protocol-Version': '1'}, body: {}}); + if (status !== 200 || !data || typeof data !== 'object') { + result.limits = {...result.limits, ...section([401, 403].includes(status) ? 'missing-auth' : 'unavailable', + [401, 403].includes(status) ? 'Reconnect Cursor to read usage.' : `Usage endpoint unavailable (HTTP ${status}).`)}; + return result; + } + const noPlan = number(data.spendLimitUsage?.overallLimit) === 0; + const percent = noPlan ? null : number(data.planUsage?.totalPercentUsed); + const window = windowUsage({id: 'plan', label: 'Included usage', usedPercent: percent, + resetsAt: number(data.billingCycleEnd)}); + if (window) + result.limits.windows.push(window); + result.limits = {...result.limits, ...section(result.limits.windows.length ? 'ready' : 'unavailable', + result.limits.windows.length ? '' : 'No supported allowance was reported. Check the Cursor dashboard.'), + updatedAt: result.limits.windows.length ? Date.now() : null}; + result.accountKey = io.fingerprint(token); + return result; +} diff --git a/src/providers/legacy.js b/src/providers/legacy.js deleted file mode 100644 index a3b0659..0000000 --- a/src/providers/legacy.js +++ /dev/null @@ -1,45 +0,0 @@ -import {number, record, section, windowUsage} from '../core/usage.js'; - -export async function collectLegacy(id, io) { - const result = record(id); - result.capabilities.limits = true; - const token = await io.token(id); - if (!token) { - result.limits = {...result.limits, ...section('missing-auth', `Sign in to ${result.name} to read usage.`)}; - return result; - } - const response = id === 'cursor' - ? await io.http('https://api2.cursor.sh/aiserver.v1.DashboardService/GetCurrentPeriodUsage', - {method: 'POST', headers: {Authorization: `Bearer ${token}`, 'Connect-Protocol-Version': '1'}, body: {}}) - : await io.http('https://api.github.com/copilot_internal/user', {headers: {Authorization: `Bearer ${token}`, Accept: 'application/json'}}); - const {status, data} = response; - if (status !== 200 || !data || typeof data !== 'object') { - result.limits = {...result.limits, ...section([401, 403].includes(status) ? 'missing-auth' : 'unavailable', - [401, 403].includes(status) ? `Reconnect ${result.name} to read usage.` : `Usage endpoint unavailable (HTTP ${status}).`)}; - return result; - } - if (id === 'cursor') { - const noPlan = number(data.spendLimitUsage?.overallLimit) === 0; - const percent = noPlan ? null : number(data.planUsage?.totalPercentUsed); - const window = windowUsage({id: 'plan', label: 'Included usage', usedPercent: percent, - resetsAt: number(data.billingCycleEnd)}); - if (window) - result.limits.windows.push(window); - } else { - for (const [key, quota] of Object.entries(data.quota_snapshots ?? {})) { - const limit = number(quota.entitlement); - const remaining = number(quota.remaining); - const item = windowUsage({id: key, label: key.replaceAll('_', ' '), unit: 'requests', limit, - used: limit !== null && remaining !== null ? Math.max(0, limit - remaining) : null, - unlimited: quota.unlimited === true, resetsAt: data.quota_reset_date_utc}); - if (item) - result.limits.windows.push(item); - } - result.plan = data.copilot_plan ?? null; - } - result.limits = {...result.limits, ...section(result.limits.windows.length ? 'ready' : 'unavailable', - result.limits.windows.length ? '' : 'No supported allowance was reported. Check the provider dashboard.'), - updatedAt: result.limits.windows.length ? Date.now() : null}; - result.accountKey = io.fingerprint(token); - return result; -} diff --git a/tests/freeby.bats b/tests/freeby.bats deleted file mode 100644 index b092960..0000000 --- a/tests/freeby.bats +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env bats - -SCRIPT="$BATS_TEST_DIRNAME/../scripts/freeby.sh" - -@test "outputs valid JSON" { - run bash "$SCRIPT" - [ "$status" -eq 0 ] - echo "$output" | python3 -c "import json,sys; json.load(sys.stdin)" -} - -@test "JSON contains codex, cursor, copilot keys" { - run bash "$SCRIPT" - echo "$output" | python3 -c " -import json,sys -d = json.load(sys.stdin) -assert 'codex' in d, 'missing codex' -assert 'cursor' in d, 'missing cursor' -assert 'copilot' in d, 'missing copilot' -" -} - -@test "each provider has required fields" { - run bash "$SCRIPT" - echo "$output" | python3 -c " -import json,sys -d = json.load(sys.stdin) -for k in ('codex','cursor','copilot'): - p = d[k] - assert 'available' in p, f'{k} missing available' - assert 'has_remaining' in p, f'{k} missing has_remaining' - assert 'summary' in p, f'{k} missing summary' - assert isinstance(p['available'], bool), f'{k} available not bool' - assert isinstance(p['has_remaining'], bool), f'{k} has_remaining not bool' -" -} - -@test "script cleans up temp directory" { - before=$(ls /tmp | grep -c 'tmp\.' || true) - run bash "$SCRIPT" - after=$(ls /tmp | grep -c 'tmp\.' || true) - [ "$after" -le "$before" ] -} diff --git a/tools/check.js b/tools/check.js index 2ad9d35..fc55be7 100644 --- a/tools/check.js +++ b/tools/check.js @@ -23,5 +23,35 @@ if (metadata.uuid !== 'freeby@kelvin.local') const version = JSON.parse(readFileSync('package.json', 'utf8')).version; if (!readFileSync('meson.build', 'utf8').includes(`version: '${version}'`)) throw new Error('Meson and package versions differ'); + +const required = ['AGENTS.md', '.AGENTS/README.md', '.AGENTS/plans/roadmap.md', + '.AGENTS/rules/architecture.md', '.AGENTS/rules/quality.md', '.AGENTS/rules/releases.md', + 'docs/README.md', 'docs/architecture.md']; +for (const file of required) { + if (!existsSync(file)) + throw new Error(`Missing repository guidance: ${file}`); +} +const obsolete = ['plan.md', 'indicator.js', 'src/providers/legacy.js', 'scripts/freeby.sh', + 'scripts/copilot-setup.sh', 'tests/freeby.bats', 'docs/s1.png', 'docs/s2.png', 'reference-images']; +for (const file of obsolete) { + if (existsSync(file)) + throw new Error(`Obsolete repository path returned: ${file}`); +} +for (const file of walk('.AGENTS/rules')) { + if (readFileSync(file, 'utf8').split('\n').length > 50) + throw new Error(`${file}: agent rules must stay focused and under 50 lines`); +} +const markdown = ['README.md', 'CONTRIBUTING.md', 'AGENTS.md', ...walk('.AGENTS'), ...walk('docs')] + .filter(file => file.endsWith('.md')); +for (const file of markdown) { + const text = readFileSync(file, 'utf8'); + for (const [, target] of text.matchAll(/\[[^\]]+\]\(([^)]+)\)/g)) { + if (/^(?:https?:|#|mailto:)/.test(target)) + continue; + const path = target.split('#')[0]; + if (path && !existsSync(resolve(dirname(file), path))) + throw new Error(`${file}: broken relative link ${target}`); + } +} execFileSync('glib-compile-schemas', ['--strict', '--dry-run', 'schemas']); -console.log(`Syntax, imports, formatting, release metadata and schema checks passed (${files.length} JavaScript files).`); +console.log(`Syntax, imports, formatting, repository layout, links, release metadata and schema checks passed (${files.length} JavaScript files).`); diff --git a/tools/package.py b/tools/package.py index c68eaed..f83e6cb 100644 --- a/tools/package.py +++ b/tools/package.py @@ -29,7 +29,8 @@ def package(source, output): with zipfile.ZipFile(archive) as check: assert check.testzip() is None assert 'schemas/gschemas.compiled' in check.namelist() - assert all(not name.startswith(('tests/', 'tools/', 'reference-images/')) for name in check.namelist()) + assert all(not name.startswith(('.AGENTS/', '.github/', 'docs/', 'tests/', 'tools/')) + for name in check.namelist()) print(archive.resolve()) return archive From caa6e478ce05933dad127843b1987e195c8445c4 Mon Sep 17 00:00:00 2001 From: kcnewman Date: Sun, 6 Sep 2026 09:12:27 +0000 Subject: [PATCH 22/44] fix: restore Codex limits and refine usage panel --- .AGENTS/plans/roadmap.md | 22 ++++++++++- CHANGELOG.md | 16 ++++++++ README.md | 4 +- src/collector/main.js | 4 +- src/core/format.js | 6 +++ src/providers/codex.js | 3 +- src/services/files.js | 72 +++++++++++++++++++++++++++++++--- src/ui/indicator.js | 64 +++++++++++++++++++----------- src/ui/widgets.js | 33 ++++++++++++++-- stylesheet.css | 53 +++++++++++++++---------- tests/integration/collector.js | 19 ++++++++- tests/unit/usage.test.js | 4 +- tools/smoke-shell.py | 55 ++++++++++++++++++++++++-- 13 files changed, 293 insertions(+), 62 deletions(-) diff --git a/.AGENTS/plans/roadmap.md b/.AGENTS/plans/roadmap.md index 1b6e2be..c748f7d 100644 --- a/.AGENTS/plans/roadmap.md +++ b/.AGENTS/plans/roadmap.md @@ -1,7 +1,7 @@ # Freeby usage monitor roadmap -Updated: 2026-09-06. Status: post-alpha.2 repository organization complete; -Cursor is the active alpha.3 milestone. +Updated: 2026-09-06. Status: post-alpha.2 interface/runtime correction complete; +Cursor remains the next alpha.3 milestone. ## Agreed direction @@ -166,6 +166,24 @@ and subprocess integration suite, strict schema/layout/link validation, and a deterministic alpha.3-dev package. The exact archive loaded, disabled, and re-enabled without extension errors in an isolated GNOME Shell 50 session. +### Interface and local-runtime correction checkpoint + +- [x] Match the supplied Omarchy hierarchy with a compact monospaced panel, + visible data status, equal provider tabs, and left-anchored usage graphics. +- [x] Keep the common view within the monitor work area while retaining + automatic scrolling as a small-display and overflow fallback. +- [x] Detect user-local and version-managed Codex installations from GNOME + Shell's minimal environment and use the installation's matching Node runtime. +- [x] Exercise all primary popup widgets with synthetic records and reject GJS + criticals in the isolated lifecycle smoke test. + +Evidence: 20 unit tests and the GJS integration suite pass, including newest +version-manager selection and sibling-runtime launch coverage. A sanitized live +probe with Codex CLI 0.145.0, an fnm-only binary path, no `/usr/bin/node`, and a +GNOME-equivalent minimal `PATH` returned the signed-in Plus plan with three live +quota windows. The populated two-provider popup loaded, disabled, and re-enabled +without extension GJS criticals in a private GNOME Shell 50 session. + ### 3. Cursor — v2.0.0-alpha.3 - [ ] Revalidate current Cursor authentication and usage sources for free and paid accounts. diff --git a/CHANGELOG.md b/CHANGELOG.md index aba4733..2d866da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ while version 2 is developed on the `dev` branch. ### Changed +- Reworked the popup into a compact, sharp-edged, monospaced usage panel based + on the supplied Omarchy references, with equal-width `Claude`/`Codex` tabs, + visible provider status, and filled model-usage rows. +- Reduced vertical density so the normal two-provider view fits the monitor; + scrolling remains an automatic fallback for smaller displays or extra data. - Consolidated agent rules, the active roadmap, archived planning notes, and visual implementation references under `.AGENTS/`, with a minimal root `AGENTS.md` discovery entrypoint. @@ -15,6 +20,17 @@ while version 2 is developed on the `dev` branch. - Split the temporary combined provider module into dedicated Cursor and Copilot adapters without changing their preview behavior. +### Fixed + +- Discover Codex installed through fnm, nvm, mise, asdf, Volta, and common + user-local binary directories even when GNOME Shell starts with a minimal + `PATH`. +- Launch version-managed Codex installations with their matching sibling Node + runtime, restoring live account quota reads on systems without `/usr/bin/node`. +- Anchor daily and model fills to the left edge and render human-readable model + names instead of centered or malformed bars. +- Reset the popup to its top edge whenever it opens. + ### Removed - Removed the superseded v1 indicator, Bash collector/setup scripts, and their diff --git a/README.md b/README.md index 46bfa59..8beee6b 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,9 @@ subscription usage. ## Requirements - GNOME Shell 50 (the version verified for this prerelease). -- Codex CLI installed and signed in for Codex account limits. +- Codex CLI installed and signed in for Codex account limits. System installs + and common user-local/version-manager layouts (including fnm, nvm, mise, + asdf, and Volta) are detected from GNOME Shell's restricted environment. - Claude Code installed and signed in for Claude account limits; local Claude transcripts remain useful independently. - GJS with Gio/GLib and Soup 3 introspection data. diff --git a/src/collector/main.js b/src/collector/main.js index e601545..7836175 100644 --- a/src/collector/main.js +++ b/src/collector/main.js @@ -7,7 +7,7 @@ import {collectClaude} from '../providers/claude.js'; import {collectCodex} from '../providers/codex.js'; import {collectCopilot} from '../providers/copilot.js'; import {collectCursor} from '../providers/cursor.js'; -import {fingerprint, findCommand, join, readJson} from '../services/files.js'; +import {fingerprint, findCommand, join, nodeCli, readJson} from '../services/files.js'; import {scanHistory} from '../services/history.js'; import {requestJson} from '../services/http.js'; import {RpcClient, runCommand} from '../services/process.js'; @@ -42,7 +42,7 @@ const io = { } return null; }, - codexClient: () => new RpcClient([findCommand('codex'), 'app-server'], cancellable), + codexClient: () => new RpcClient(nodeCli('codex', ['app-server']), cancellable), http: (url, options) => requestJson(url, {...options, cancellable}), async token(provider) { if (provider === 'cursor') { diff --git a/src/core/format.js b/src/core/format.js index 6c889aa..99c5768 100644 --- a/src/core/format.js +++ b/src/core/format.js @@ -29,3 +29,9 @@ export function age(time, now = Date.now()) { return 'Updated just now'; return minutes < 60 ? `Updated ${minutes}m ago` : `Updated ${Math.floor(minutes / 60)}h ago`; } + +export function modelName(value) { + const acronyms = new Map([['gpt', 'GPT'], ['api', 'API']]); + return String(value || 'Unknown model').replaceAll(/[-_/]+/g, ' ').split(/\s+/).filter(Boolean) + .map(word => acronyms.get(word.toLowerCase()) ?? word[0].toUpperCase() + word.slice(1)).join(' '); +} diff --git a/src/providers/codex.js b/src/providers/codex.js index 3dcb939..ac54f3d 100644 --- a/src/providers/codex.js +++ b/src/providers/codex.js @@ -13,8 +13,9 @@ export function codexLimits(response, now = Date.now()) { const mins = number(w.windowDurationMins); const duration = mins === 10080 ? 'Weekly' : mins === 300 ? 'Session · 5 hours' : mins ? `${mins >= 60 ? `${mins / 60} hours` : `${mins} minutes`}` : key === 'primary' ? 'Primary window' : 'Secondary window'; + const bucketName = bucket.limitName || (bucketId === 'codex' ? null : bucketId); const item = windowUsage({id: `${bucketId}:${key}`, - label: buckets.length > 1 ? `${bucket.limitName || bucketId} · ${duration}` : duration, + label: buckets.length > 1 && bucketName ? `${bucketName} · ${duration}` : duration, usedPercent: w.usedPercent, durationMinutes: mins, resetsAt: number(w.resetsAt) ? Number(w.resetsAt) * 1000 : null}); if (item) diff --git a/src/services/files.js b/src/services/files.js index 0351529..20f8a7d 100644 --- a/src/services/files.js +++ b/src/services/files.js @@ -24,13 +24,69 @@ export function writeJson(path, value) { Gio.FileCreateFlags.PRIVATE | Gio.FileCreateFlags.REPLACE_DESTINATION, null); } -export function findCommand(name) { - const found = GLib.find_program_in_path(name); +function executable(path) { + return GLib.file_test(path, GLib.FileTest.IS_EXECUTABLE) ? path : null; +} + +function versionParts(value) { + return (String(value).match(/\d+/g) ?? []).map(Number); +} + +function compareVersions(a, b) { + const left = versionParts(a); + const right = versionParts(b); + for (let i = 0; i < Math.max(left.length, right.length); i++) { + const difference = (left[i] ?? 0) - (right[i] ?? 0); + if (difference) + return difference; + } + return String(a).localeCompare(String(b)); +} + +function versionedCommand(root, suffix, name) { + const directory = Gio.File.new_for_path(root); + if (!directory.query_exists(null)) + return null; + const candidates = []; + let iterator; + try { + iterator = directory.enumerate_children('standard::name,standard::type', + Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null); + let info; + while ((info = iterator.next_file(null))) { + if (info.get_file_type() !== Gio.FileType.DIRECTORY) + continue; + const path = executable(join(root, info.get_name(), ...suffix, name)); + if (path) + candidates.push({version: info.get_name(), path}); + } + } catch { + return null; + } finally { + iterator?.close(null); + } + candidates.sort((a, b) => compareVersions(b.version, a.version)); + return candidates[0]?.path ?? null; +} + +export function findCommand(name, {home = GLib.get_home_dir(), usePath = true} = {}) { + const found = usePath ? GLib.find_program_in_path(name) : null; if (found) return found; - for (const directory of ['.local/bin', '.cargo/bin', '.npm-global/bin']) { - const path = join(GLib.get_home_dir(), directory, name); - if (GLib.file_test(path, GLib.FileTest.IS_EXECUTABLE)) + for (const directory of ['.local/bin', '.cargo/bin', '.npm-global/bin', '.volta/bin', '.asdf/shims', + '.local/share/mise/shims', '.bun/bin']) { + const path = executable(join(home, directory, name)); + if (path) + return path; + } + for (const [directory, suffix] of [ + ['.local/share/fnm/node-versions', ['installation', 'bin']], + ['.nvm/versions/node', ['bin']], + ['.local/share/mise/installs/node', ['bin']], + ['.asdf/installs/nodejs', ['bin']], + ]) { + const path = versionedCommand(join(home, directory), suffix, name); + if (path) return path; } const error = new Error(`${name} is not installed`); @@ -38,6 +94,12 @@ export function findCommand(name) { throw error; } +export function nodeCli(name, args = [], options = {}) { + const command = findCommand(name, options); + const runtime = executable(join(GLib.path_get_dirname(command), 'node')); + return runtime ? [runtime, command, ...args] : [command, ...args]; +} + export function stateDirectory() { return join(GLib.get_user_state_dir(), 'freeby'); } diff --git a/src/ui/indicator.js b/src/ui/indicator.js index 970c628..5a0d798 100644 --- a/src/ui/indicator.js +++ b/src/ui/indicator.js @@ -5,8 +5,11 @@ import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js'; import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js'; import {NAMES, highestUsage, recentDates} from '../core/usage.js'; -import {age, resetTime, tokens} from '../core/format.js'; -import {button, label, meter, row} from './widgets.js'; +import {age, modelName, resetTime, tokens} from '../core/format.js'; +import {button, label, meter, modelMeter, row} from './widgets.js'; + +const TAB_NAMES = {claude: 'Claude'}; +const PROVIDER_MARKS = {codex: '>_', claude: '✦', cursor: '⌁', copilot: '◆'}; export const FreebyIndicator = GObject.registerClass(class FreebyIndicator extends PanelMenu.Button { _init(settings, openPreferences) { @@ -34,6 +37,9 @@ export const FreebyIndicator = GObject.registerClass(class FreebyIndicator exten this.resize(); this._service?.refreshAll(); this.render(); + const adjustment = this._scroll.vscroll?.adjustment; + if (adjustment) + adjustment.value = 0; } }); this.render(); @@ -42,8 +48,8 @@ export const FreebyIndicator = GObject.registerClass(class FreebyIndicator exten resize() { const monitor = Main.layoutManager.findMonitorForActor(this) ?? Main.layoutManager.primaryMonitor; const scale = St.ThemeContext.get_for_stage(global.stage).scale_factor; - this._scroll.style = `max-height: ${Math.max(180, Math.floor(monitor.height / scale) - 120)}px;`; - this._contentBox.style = `width: ${Math.min(420, Math.floor(monitor.width / scale) - 64)}px;`; + this._scroll.style = `max-height: ${Math.max(180, Math.floor(monitor.height / scale) - 72)}px;`; + this._contentBox.style = `width: ${Math.min(404, Math.floor(monitor.width / scale) - 48)}px;`; } attach(service) { @@ -62,17 +68,27 @@ export const FreebyIndicator = GObject.registerClass(class FreebyIndicator exten const id = enabled.includes(requested) ? requested : enabled[0]; const record = this._service?.records[id]; const percent = highestUsage(record); - this._panelLabel.text = percent === null ? 'AI' : `AI ${Math.round(percent)}%`; + const busy = this._service?.jobs.has(id); + this._panelLabel.text = percent === null ? 'AI' : `AI · ${Math.round(percent)}%`; this._panelLabel.style_class = `freeby-panel-label${percent >= 100 ? ' freeby-danger' : percent >= 90 ? ' freeby-warning' : ''}`; this.accessible_name = `Freeby, ${record?.name ?? 'usage monitor'}${percent === null ? '' : `, ${Math.round(percent)} percent used`}`; - const header = new St.BoxLayout({vertical: true, style_class: 'freeby-header'}); - header.add_child(label(record?.name ?? 'Freeby', 'freeby-title')); - header.add_child(label(record?.plan ? String(record.plan).toUpperCase() : 'USAGE MONITOR', 'freeby-caption')); + const header = new St.BoxLayout({style_class: 'freeby-header', x_expand: true}); + header.add_child(label(PROVIDER_MARKS[id] ?? 'AI', `freeby-provider-mark freeby-${id}-mark`)); + const identity = new St.BoxLayout({vertical: true, style_class: 'freeby-identity', x_expand: true}); + identity.add_child(label(record?.name ?? 'Freeby', 'freeby-title')); + identity.add_child(label(record?.plan ? String(record.plan).toUpperCase() : 'USAGE MONITOR', 'freeby-caption')); + header.add_child(identity); + const live = record && (['ready', 'partial', 'stale'].includes(record.limits.status) || + ['ready', 'partial', 'stale'].includes(record.history.status)); + const statusText = busy ? 'SYNC' : record?.limits.status === 'ready' ? 'LIVE' : live ? 'LOCAL' : 'SETUP'; + header.add_child(label(`● ${statusText}`, `freeby-status freeby-status-${statusText.toLowerCase()}`)); this._contentBox.add_child(header); if (enabled.length > 1) { - const selector = new St.BoxLayout({style_class: 'freeby-selector', x_expand: true}); + const selector = new St.Widget({style_class: 'freeby-selector', x_expand: true, + layout_manager: new Clutter.BoxLayout({orientation: Clutter.Orientation.HORIZONTAL, + homogeneous: true, spacing: 8})}); for (const provider of enabled) - selector.add_child(button(NAMES[provider], () => this._settings.set_string('default-provider', provider), + selector.add_child(button(TAB_NAMES[provider] ?? NAMES[provider], () => this._settings.set_string('default-provider', provider), {active: provider === id, name: `Show ${NAMES[provider]} usage`})); this._contentBox.add_child(selector); } @@ -103,6 +119,8 @@ export const FreebyIndicator = GObject.registerClass(class FreebyIndicator exten const line = new St.BoxLayout({style_class: 'freeby-day-row', x_expand: true}); const dayName = day.date === today ? 'Today' : new Date(`${day.date}T12:00:00`).toLocaleDateString(undefined, {weekday: 'short'}); line.add_child(label(dayName, 'freeby-day')); + if (day.date === today) + line.add_style_class_name('freeby-today'); const bar = meter(day.total / max, `${day.date}: ${tokens(day.total)} tokens`); bar.y_align = Clutter.ActorAlign.CENTER; line.add_child(bar); @@ -112,24 +130,26 @@ export const FreebyIndicator = GObject.registerClass(class FreebyIndicator exten } } if (record.history.models.length) { - this.heading('TOKENS BY MODEL'); - this._contentBox.add_child(label('Same period as daily activity', 'freeby-caption')); + const modelScope = record.history.scope === 'account' ? 'ACCOUNT' : 'LOCAL'; + this.heading(`TOKENS BY MODEL · 7D ${modelScope}`); const max = Math.max(1, ...record.history.models.map(model => model.total)); - for (const model of record.history.models) { - const box = new St.BoxLayout({vertical: true, style_class: 'freeby-model'}); - box.add_child(row(model.model, tokens(model.total))); - box.add_child(meter(model.total / max, `${model.model}: ${model.total} tokens`)); - box.add_child(label(`Input ${tokens(model.input)} · Output ${tokens(model.output)} · Cache ${tokens(model.cacheRead + model.cacheWrite)}`, 'freeby-caption')); - this._contentBox.add_child(box); + const visibleModels = record.history.models.slice(0, 4); + for (const model of visibleModels) { + const displayName = modelName(model.model); + const accessible = `${displayName}, ${model.total} tokens. Input ${model.input}, output ${model.output}, cache ${model.cacheRead + model.cacheWrite}.`; + this._contentBox.add_child(modelMeter(displayName, tokens(model.total), model.total / max, accessible)); } + if (record.history.models.length > visibleModels.length) + this._contentBox.add_child(label(`Top ${visibleModels.length} of ${record.history.models.length} models`, 'freeby-caption')); } - if (record.history.message) + if (record.history.message && record.history.status !== 'ready') this.message(record.history.message); } - this.heading(''); - const busy = this._service?.jobs.has(id); + this._contentBox.add_child(new St.Widget({style_class: 'freeby-divider'})); this._contentBox.add_child(label(busy ? 'Refreshing…' : age(record?.limits.updatedAt || record?.history.updatedAt), 'freeby-caption')); - const actions = new St.BoxLayout({style_class: 'freeby-selector'}); + const actions = new St.Widget({style_class: 'freeby-actions', x_expand: true, + layout_manager: new Clutter.BoxLayout({orientation: Clutter.Orientation.HORIZONTAL, + homogeneous: true, spacing: 8})}); actions.add_child(button('Refresh', () => this._service?.refreshAll(true), {name: 'Refresh usage'})); actions.add_child(button('Preferences', () => { this.menu.close(); this._openPreferences(); })); this._contentBox.add_child(actions); diff --git a/src/ui/widgets.js b/src/ui/widgets.js index 9d33005..1ed90af 100644 --- a/src/ui/widgets.js +++ b/src/ui/widgets.js @@ -17,11 +17,36 @@ export function row(left, right, style = 'freeby-row') { export function meter(fraction, name, style = '') { const ratio = Math.max(0, Math.min(1, Number(fraction) || 0)); const track = new St.Widget({style_class: `freeby-track ${style}`, x_expand: true, - layout_manager: new Clutter.BinLayout(), accessible_name: name, accessible_role: Atk.Role.PROGRESS_BAR}); - const fill = new St.Widget({style_class: 'freeby-fill', x_align: Clutter.ActorAlign.START, - y_expand: true, y_align: Clutter.ActorAlign.FILL}); + layout_manager: new Clutter.FixedLayout(), accessible_name: name, accessible_role: Atk.Role.PROGRESS_BAR}); + const fill = new St.Widget({style_class: 'freeby-fill'}); + fill.set_position(0, 0); track.add_child(fill); - track.connect('notify::allocation', () => { fill.width = Math.round(track.width * ratio); }); + track.connect('notify::allocation', () => { + fill.set_size(Math.round(Math.max(0, track.width) * ratio), Math.max(0, track.height)); + }); + return track; +} + +export function modelMeter(left, right, fraction, name) { + const ratio = Math.max(0, Math.min(1, Number(fraction) || 0)); + const track = new St.Widget({style_class: 'freeby-model-meter', x_expand: true, + layout_manager: new Clutter.FixedLayout(), accessible_name: name, + accessible_role: Atk.Role.PROGRESS_BAR}); + const fill = new St.Widget({style_class: 'freeby-model-fill'}); + const content = new St.BoxLayout({style_class: 'freeby-model-content'}); + content.add_child(label(left, 'freeby-model-name')); + content.add_child(new St.Widget({x_expand: true})); + content.add_child(label(right, 'freeby-number')); + track.add_child(fill); + track.add_child(content); + track.connect('notify::allocation', () => { + const width = Math.max(0, track.width); + const height = Math.max(0, track.height); + fill.set_position(0, 0); + fill.set_size(Math.round(width * ratio), height); + content.set_position(0, 0); + content.set_size(width, height); + }); return track; } diff --git a/stylesheet.css b/stylesheet.css index 0965c8e..757e4fd 100644 --- a/stylesheet.css +++ b/stylesheet.css @@ -1,25 +1,38 @@ -.freeby-panel-label { font-weight: bold; font-size: 10pt; } -.freeby-content { padding: 8px 14px 12px; spacing: 12px; } -.freeby-header { spacing: 4px; padding: 4px 0 8px; } -.freeby-title { font-size: 18pt; font-weight: bold; } -.freeby-caption { font-size: 9pt; opacity: 0.65; } -.freeby-selector { spacing: 8px; } -.freeby-button { padding: 9px 12px; border-radius: 8px; background-color: rgba(128, 128, 128, 0.12); } -.freeby-button:hover, .freeby-button:focus { background-color: rgba(128, 128, 128, 0.24); } -.freeby-button:checked { background-color: rgba(128, 128, 128, 0.3); font-weight: bold; } +.freeby-panel-label { font-weight: bold; font-size: 10pt; color: #c6dcff; } +.freeby-content { padding: 12px 16px 14px; spacing: 0; border: 2px solid rgba(98, 160, 234, 0.86); border-radius: 2px; background-color: rgba(20, 24, 36, 0.24); font-family: monospace; } +.freeby-header { spacing: 10px; padding: 2px 0 11px; } +.freeby-identity { spacing: 1px; } +.freeby-provider-mark { min-width: 34px; min-height: 34px; padding: 4px; border: 1px solid rgba(98, 160, 234, 0.75); border-radius: 2px; background-color: rgba(98, 160, 234, 0.14); color: #99c1f1; font-family: monospace; font-size: 12pt; font-weight: bold; text-align: center; } +.freeby-claude-mark { border-color: rgba(255, 145, 90, 0.8); background-color: rgba(255, 145, 90, 0.14); color: #ff9f6b; } +.freeby-title { font-size: 16pt; font-weight: bold; } +.freeby-caption { font-size: 8.5pt; opacity: 0.67; font-family: monospace; } +.freeby-status { padding: 4px 7px; border: 1px solid rgba(128, 128, 128, 0.35); border-radius: 2px; font-family: monospace; font-size: 8pt; opacity: 0.82; } +.freeby-status-live { color: #57e389; border-color: rgba(87, 227, 137, 0.5); background-color: rgba(87, 227, 137, 0.08); } +.freeby-status-sync { color: #99c1f1; border-color: rgba(153, 193, 241, 0.5); } +.freeby-status-local { color: #f6d32d; border-color: rgba(246, 211, 45, 0.45); } +.freeby-selector { spacing: 8px; padding-bottom: 11px; } +.freeby-actions { spacing: 8px; padding-top: 8px; } +.freeby-button { padding: 7px 12px; min-height: 26px; border: 1px solid rgba(128, 128, 128, 0.38); border-radius: 1px; background-color: rgba(128, 128, 128, 0.06); } +.freeby-button:hover, .freeby-button:focus { border-color: rgba(98, 160, 234, 0.75); background-color: rgba(98, 160, 234, 0.13); } +.freeby-button:checked { border-color: #62a0ea; background-color: rgba(98, 160, 234, 0.2); color: #c6dcff; font-weight: bold; } .freeby-button:focus { box-shadow: inset 0 0 0 2px -st-accent-color; } -.freeby-section-title { font-size: 9pt; font-weight: bold; opacity: 0.65; border-top: 1px solid rgba(128, 128, 128, 0.25); padding-top: 16px; } -.freeby-row { spacing: 12px; } -.freeby-number { font-family: monospace; } -.freeby-window { spacing: 7px; } -.freeby-track { height: 6px; min-width: 20px; border-radius: 3px; background-color: rgba(128, 128, 128, 0.25); } -.freeby-fill { background-color: -st-accent-color; border-radius: 3px; } +.freeby-section-title { font-size: 8.5pt; font-family: monospace; font-weight: bold; opacity: 0.78; border-top: 1px solid rgba(128, 128, 128, 0.22); padding: 11px 0 7px; } +.freeby-row { spacing: 10px; } +.freeby-number { font-family: monospace; color: #d6e6ff; } +.freeby-window { spacing: 4px; padding-bottom: 8px; } +.freeby-track { height: 5px; min-width: 20px; border-radius: 3px; background-color: rgba(128, 128, 128, 0.24); } +.freeby-fill { background-color: #8fb9e8; border-radius: 3px; } .freeby-warning { color: #e5a50a; } .freeby-danger { color: #e01b24; } .freeby-warning .freeby-fill { background-color: #e5a50a; } .freeby-danger .freeby-fill { background-color: #e01b24; } -.freeby-day-row { spacing: 12px; } -.freeby-day { min-width: 44px; font-size: 10pt; } -.freeby-day-total { min-width: 64px; text-align: right; font-family: monospace; font-size: 10pt; } -.freeby-model { spacing: 6px; } -.freeby-message { font-size: 9pt; opacity: 0.75; } +.freeby-day-row { spacing: 10px; min-height: 27px; } +.freeby-day { min-width: 42px; font-size: 9.5pt; opacity: 0.72; } +.freeby-today .freeby-day { color: #c6dcff; opacity: 1; font-weight: bold; } +.freeby-day-total { min-width: 58px; text-align: right; font-family: monospace; font-size: 9pt; color: #d6e6ff; } +.freeby-model-meter { min-height: 32px; margin-bottom: 6px; background-color: rgba(128, 128, 160, 0.16); } +.freeby-model-fill { background-color: rgba(117, 167, 220, 0.27); } +.freeby-model-content { padding: 7px 10px; } +.freeby-model-name { color: #c6dcff; } +.freeby-message { margin: 0 0 7px; padding: 8px 10px; border-left: 2px solid #f6d32d; border-radius: 3px; background-color: rgba(246, 211, 45, 0.06); font-size: 9pt; opacity: 0.85; } +.freeby-divider { height: 1px; margin-top: 4px; margin-bottom: 8px; background-color: rgba(128, 128, 128, 0.24); } diff --git a/tests/integration/collector.js b/tests/integration/collector.js index 903def4..61d62e1 100644 --- a/tests/integration/collector.js +++ b/tests/integration/collector.js @@ -4,7 +4,7 @@ import System from 'system'; import {scanHistory} from '../../src/services/history.js'; import {parseCodexEvent} from '../../src/providers/codex.js'; import {parseClaudeEvent} from '../../src/providers/claude.js'; -import {readJson, writeJson, join} from '../../src/services/files.js'; +import {findCommand, nodeCli, readJson, writeJson, join} from '../../src/services/files.js'; import {runCommand} from '../../src/services/process.js'; function assert(value, message) { @@ -48,6 +48,23 @@ assert(claude.models[0].cacheRead === 40 && claude.models[0].cacheWrite === 5, ' assert(!JSON.stringify(readJson(claudeCache)).includes('claude-session'), 'Claude session identity must be sanitized'); print('PASS: GJS Claude history deduplication, model totals, and private cache'); +for (const version of ['v20.12.0', 'v24.16.0']) { + const bin = join(scratch, '.local', 'share', 'fnm', 'node-versions', version, 'installation', 'bin'); + GLib.mkdir_with_parents(bin, 0o700); + const command = join(bin, 'fixture-cli'); + GLib.file_set_contents(command, `#!/bin/sh\nprintf '${version}'\n`); + GLib.chmod(command, 0o700); + const runtime = join(bin, 'node'); + GLib.file_set_contents(runtime, '#!/bin/sh\nexit 0\n'); + GLib.chmod(runtime, 0o700); +} +assert(findCommand('fixture-cli', {home: scratch, usePath: false}).includes('v24.16.0'), + 'version-manager lookup must choose the newest installed runtime'); +const fixtureArgv = nodeCli('fixture-cli', ['status'], {home: scratch, usePath: false}); +assert(fixtureArgv[0].endsWith('/node') && fixtureArgv[1].endsWith('/fixture-cli') && fixtureArgv[2] === 'status', + 'version-managed Node CLI must use its sibling runtime'); +print('PASS: GJS version-manager command discovery'); + const loop = new GLib.MainLoop(null, false); let failed = false; (async () => { diff --git a/tests/unit/usage.test.js b/tests/unit/usage.test.js index ec14532..c8b3725 100644 --- a/tests/unit/usage.test.js +++ b/tests/unit/usage.test.js @@ -1,7 +1,7 @@ import test from 'node:test'; import assert from 'node:assert/strict'; import {aggregateEvents, highestUsage, mergeRecord, number, recentDates, record, validTime, validateRecord, windowUsage} from '../../src/core/usage.js'; -import {resetTime, tokens} from '../../src/core/format.js'; +import {modelName, resetTime, tokens} from '../../src/core/format.js'; import {ThresholdTracker} from '../../src/core/notifications.js'; test('unknown metrics are not coerced to zero', () => { @@ -93,4 +93,6 @@ test('formatting preserves unknown/reset-due states', () => { assert.equal(tokens(23000000), '23.0M'); assert.equal(resetTime(null), 'Reset time unavailable'); assert.match(resetTime(100, 200), /awaiting update/); + assert.equal(modelName('gpt-5.6-sol'), 'GPT 5.6 Sol'); + assert.equal(modelName('codex_auto-review'), 'Codex Auto Review'); }); diff --git a/tools/smoke-shell.py b/tools/smoke-shell.py index 4dc32ca..a63b56c 100644 --- a/tools/smoke-shell.py +++ b/tools/smoke-shell.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 """Exercise the packaged extension in a private headless GNOME session.""" import argparse +from datetime import date, timedelta +import json import os from pathlib import Path import signal @@ -33,17 +35,59 @@ def install(source, archive, prefix, destination): subprocess.run(['meson', 'install', '-C', str(build)], check=True, stdout=subprocess.DEVNULL) +def seed_usage(destination): + """Populate non-personal records so every primary popup widget is constructed.""" + now = int(time.time() * 1000) + dates = [(date.today() - timedelta(days=offset)).isoformat() for offset in range(6, -1, -1)] + + def record(provider, name, plan, percentages, models): + days = [{'date': day, 'total': (index + 1) * 125_000, 'sessions': index + 1, 'events': index + 2} + for index, day in enumerate(dates)] + return { + 'schemaVersion': 1, 'id': provider, 'name': name, 'plan': plan, 'accountKey': 'smoke-fixture', + 'capabilities': {'limits': True, 'history': True, 'models': True}, + 'limits': {'status': 'ready', 'message': '', 'updatedAt': now, 'scope': 'account', + 'windows': [ + {'id': f'{provider}:session', 'label': 'Session · 5 hours', 'usedPercent': percentages[0], + 'used': None, 'limit': None, 'unit': 'percent', 'unlimited': False, 'state': 'active', + 'durationMinutes': 300, 'resetsAt': now + 7_200_000}, + {'id': f'{provider}:weekly', 'label': 'Weekly', 'usedPercent': percentages[1], + 'used': None, 'limit': None, 'unit': 'percent', 'unlimited': False, 'state': 'active', + 'durationMinutes': 10_080, 'resetsAt': now + 172_800_000}, + ]}, + 'history': {'status': 'ready', 'message': '', 'updatedAt': now, 'scope': 'local', + 'period': {'start': dates[0], 'end': dates[-1]}, 'days': days, 'models': models, + 'source': 'Synthetic smoke fixture'}, + } + + models = [ + {'model': 'gpt-5.6-sol', 'total': 1_550_000, 'input': 190_000, 'output': 68_000, + 'cacheRead': 1_292_000, 'cacheWrite': 0}, + {'model': 'codex-auto-review', 'total': 980_000, 'input': 97_000, 'output': 8_000, + 'cacheRead': 875_000, 'cacheWrite': 0}, + {'model': 'gpt-6-astra', 'total': 790_000, 'input': 42_000, 'output': 62_000, + 'cacheRead': 686_000, 'cacheWrite': 0}, + ] + target = destination / 'state' / 'freeby' + target.mkdir(parents=True, exist_ok=True) + (target / 'codex.json').write_text(json.dumps(record('codex', 'Codex', 'Pro', (24, 61), models))) + (target / 'claude.json').write_text(json.dumps(record('claude', 'Claude Code', 'Pro', (17, 43), models[:2]))) + + def smoke(source, archive, destination): destination.mkdir(parents=True, exist_ok=True) prefix = destination / 'install' install(source, archive, prefix, destination) - env = {**os.environ, 'XDG_CONFIG_HOME': str(destination / 'config'), + isolated_home = destination / 'home' + isolated_home.mkdir(parents=True, exist_ok=True) + seed_usage(destination) + env = {**os.environ, 'HOME': str(isolated_home), 'PATH': '/usr/bin:/bin', + 'XDG_CONFIG_HOME': str(destination / 'config'), 'XDG_DATA_HOME': str(prefix / 'share'), 'XDG_CACHE_HOME': str(destination / 'cache'), 'XDG_STATE_HOME': str(destination / 'state'), 'GSETTINGS_BACKEND': 'keyfile', 'GSETTINGS_SCHEMA_DIR': str(prefix / 'share/gnome-shell/extensions/freeby@kelvin.local/schemas'), 'LIBGL_ALWAYS_SOFTWARE': '1'} - # Empty provider list prevents personal authentication/data access during UI tests. - run(['gsettings', 'set', 'org.gnome.shell.extensions.freeby', 'enabled-providers', '[]'], env, check=True) + run(['gsettings', 'set', 'org.gnome.shell.extensions.freeby', 'enabled-providers', "['codex', 'claude']"], env, check=True) run(['gsettings', 'set', 'org.gnome.shell', 'enabled-extensions', "['freeby@kelvin.local']"], env, check=True) run(['gsettings', 'set', 'org.gnome.shell', 'welcome-dialog-last-shown-version', '999'], env) bus = subprocess.Popen(['dbus-daemon', '--session', '--nofork', '--print-address=1'], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True) @@ -72,6 +116,8 @@ def smoke(source, archive, destination): run(['gnome-extensions', 'disable', 'freeby@kelvin.local'], env, check=True) run(['gnome-extensions', 'enable', 'freeby@kelvin.local'], env, check=True) print('PASS: extension disable/re-enable') + # Leave no collector running while the private shell itself shuts down. + run(['gnome-extensions', 'disable', 'freeby@kelvin.local'], env, check=True) finally: if shell and shell.poll() is None: os.killpg(shell.pid, signal.SIGTERM) @@ -83,6 +129,9 @@ def smoke(source, archive, destination): bus.terminate() bus.wait(timeout=5) print(f'GNOME log: {log_path}') + contents = log_path.read_text(errors='replace') + if 'Gjs-CRITICAL' in contents and 'freeby@kelvin.local' in contents: + raise RuntimeError(f'Extension emitted a GJS critical; inspect {log_path}') if __name__ == '__main__': From 9034ba14e92c601f3c37c928709bab273c037770 Mon Sep 17 00:00:00 2001 From: kcnewman Date: Sun, 6 Sep 2026 19:02:29 +0000 Subject: [PATCH 23/44] refactor: harden usage monitoring runtime --- .AGENTS/plans/roadmap.md | 26 +++- CHANGELOG.md | 18 +++ docs/architecture.md | 33 +++-- extension.js | 21 +++- prefs.js | 6 +- src/collector/main.js | 66 +++++----- src/core/format.js | 5 +- src/core/usage.js | 185 ++++++++++++++++++++++++---- src/providers/claude.js | 42 +++++-- src/providers/codex.js | 91 ++++++++++---- src/providers/copilot.js | 16 ++- src/providers/cursor.js | 16 ++- src/services/commands.js | 138 +++++++++++++++++++++ src/services/files.js | 101 ++++------------ src/services/history.js | 205 ++++++++++++++++++++++++++------ src/services/http.js | 33 ++++- src/services/process.js | 111 +++++++++++++---- src/services/usageService.js | 179 ++++++++++++++++++---------- src/ui/indicator.js | 192 ++++++++++++++++++------------ src/ui/presentation.js | 25 ++++ src/ui/widgets.js | 10 +- stylesheet.css | 1 + tests/integration/collector.js | 106 +++++++++++++++-- tests/unit/codex.test.js | 23 ++++ tests/unit/presentation.test.js | 25 ++++ tests/unit/usage.test.js | 47 +++++++- tools/check.js | 28 ++++- tools/package.py | 48 ++++++-- tools/smoke-shell.py | 3 +- 29 files changed, 1376 insertions(+), 424 deletions(-) create mode 100644 src/services/commands.js create mode 100644 src/ui/presentation.js create mode 100644 tests/unit/presentation.test.js diff --git a/.AGENTS/plans/roadmap.md b/.AGENTS/plans/roadmap.md index c748f7d..39300af 100644 --- a/.AGENTS/plans/roadmap.md +++ b/.AGENTS/plans/roadmap.md @@ -1,7 +1,7 @@ # Freeby usage monitor roadmap -Updated: 2026-09-06. Status: post-alpha.2 interface/runtime correction complete; -Cursor remains the next alpha.3 milestone. +Updated: 2026-09-06. Status: post-alpha.2 interface/runtime and production +hardening complete; Cursor remains the next alpha.3 milestone. ## Agreed direction @@ -184,6 +184,28 @@ GNOME-equivalent minimal `PATH` returned the signed-in Plus plan with three live quota windows. The populated two-provider popup loaded, disabled, and re-enabled without extension GJS criticals in a private GNOME Shell 50 session. +### Runtime and contract hardening checkpoint + +- [x] Enforce schema sources, freshness, typed fields, collection bounds, and + provider identity before records reach UI state or persistence. +- [x] Keep subprocess environments scoped, failures classified, responses + bounded, and private cache permissions repaired on atomic writes. +- [x] Make incremental history resilient to counter resets, replacement, + source removal, invalid cache data, and stable private session identities. +- [x] Enforce runtime import boundaries and an exact, repository-owned package + allowlist in development tooling. +- [x] Verify deterministic packaging and the exact archive lifecycle before + committing the checkpoint on `dev`. + +Evidence: `npm run verify` passed 25 deterministic unit tests, strict schema, +layout, import-boundary, link, GJS history, process, HTTP-policy, and packaging +checks. Two builds produced the same 35,916-byte archive at SHA-256 +`48a6a6ae7546db9634385388db5a8b1dcc5bcc81135e65e93376187c2ca30d18`. +That exact archive loaded, disabled, and re-enabled without extension errors in +an isolated GNOME Shell 50 session. A sanitized live check with Codex CLI +0.145.0 returned the Plus plan, three account quota windows, seven-day local +activity, and model totals under usage schema version 2. + ### 3. Cursor — v2.0.0-alpha.3 - [ ] Revalidate current Cursor authentication and usage sources for free and paid accounts. diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d866da..8758b9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ while version 2 is developed on the `dev` branch. ### Changed +- Split command discovery from private file storage and launch discovered CLIs + with a process-local environment, explicit deadlines, cancellation reasons, + and output limits. +- Upgraded the provider-neutral usage record to schema version 2 with strict + source, freshness, quota, date, token-component, and collection-bound checks. +- Decomposed panel rendering into focused limit, daily activity, model activity, + status, and footer paths backed by unit-testable presentation decisions. +- Hardened deterministic packaging to reject symlinked or out-of-tree inputs and + verify the archive against its exact runtime allowlist. - Reworked the popup into a compact, sharp-edged, monospaced usage panel based on the supplied Omarchy references, with equal-width `Claude`/`Codex` tabs, visible provider status, and filled model-usage rows. @@ -22,6 +31,15 @@ while version 2 is developed on the `dev` branch. ### Fixed +- Keep one stable private session identity across incremental history scans, + preventing resumed Codex records from inflating session counts. +- Detect cumulative-token resets, same-size history replacements, stale source + removal, invalid cached events, and bounded directory-scan failures without + silently presenting incorrect totals. +- Require HTTPS for provider requests, bound response sizes, and repair private + cache directory/file permissions on every atomic write. +- Distinguish live account data, local-only activity, cached data, active sync, + and setup states while safely truncating long provider/model labels. - Discover Codex installed through fnm, nvm, mise, asdf, Volta, and common user-local binary directories even when GNOME Shell starts with a minimal `PATH`. diff --git a/docs/architecture.md b/docs/architecture.md index 355ceda..ae05e83 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -8,7 +8,9 @@ never reads provider credentials or transcript formats. 1. `extension.js` creates the indicator and `UsageService`. 2. `UsageService` loads cached provider records, schedules independent refreshes, - and starts one bounded GJS collector per enabled provider. + and starts one bounded GJS collector per enabled provider. Command discovery + is isolated in `src/services/commands.js`, including user-local and supported + version-manager paths that may be absent from GNOME Shell's environment. 3. `src/collector/main.js` supplies shared file, HTTP, authentication, and process capabilities to one adapter under `src/providers/`. 4. The adapter returns a versioned record from `src/core/usage.js` with limits and @@ -23,19 +25,28 @@ never reads provider credentials or transcript formats. - `src/providers/` owns one adapter per external provider. - `src/services/` owns scheduling, process lifetimes, HTTP, history scanning, and private XDG storage. -- `src/ui/` owns Shell actors and accessibility behavior. +- `src/ui/` owns Shell actors and accessibility behavior; provider-status and + period presentation decisions remain pure and unit-testable. - `prefs.js` is the libadwaita preferences entry point until configuration pages are split during alpha.5. -Collectors have deadlines and cancellation. Refresh jobs do not overlap, resume -events refresh only after wake, and results are ignored after extension disable. +Collectors have deadlines, bounded output, and cancellation. Child processes +receive a scoped `PATH` without changing the Shell process environment. Refresh +jobs do not overlap, resume events refresh only after wake, and results are +ignored after extension disable. + +The provider-neutral record is currently schema version 2. A record claiming +current, partial, or cached data must include a source and successful-update +timestamp; ready limits must contain at least one typed quota window. Invalid or +oversized records are rejected before they reach the UI or persistent state. ## Privacy and storage -Freeby stores derived records in `$XDG_STATE_HOME/freeby` and incremental scan -caches in `$XDG_CACHE_HOME/freeby`. Credentials are read only when needed and are -never written by Freeby. Prompt/response content is ignored, while session and -account identifiers used for deduplication are hashed before persistence. +Freeby stores derived records in `$XDG_STATE_HOME/freeby` and versioned +incremental scan caches in `$XDG_CACHE_HOME/freeby`, with private directory and +file modes. Credentials are read only when needed and are never written by +Freeby. Prompt/response content is ignored, while session and account identifiers +used for deduplication are hashed once before persistence. Account quota and local token activity remain separate scopes. Local activity is not billing data and never becomes a fabricated subscription percentage. @@ -44,5 +55,7 @@ not billing data and never becomes a fabricated subscription percentage. Meson installs only extension entry points, source modules, metadata, styles, license, and an extension-local schema. `tools/package.py` uses the same explicit -runtime allowlist to produce deterministic ZIP files; agent material, docs, tests, -tools, screenshots, and references cannot enter a release archive. +runtime allowlist to produce deterministic ZIP files. Inputs must be regular, +repository-owned files, and the completed archive is checked against the exact +allowlist; agent material, docs, tests, tools, screenshots, and references cannot +enter a release archive. diff --git a/extension.js b/extension.js index 1bbc5e7..f0f37c1 100644 --- a/extension.js +++ b/extension.js @@ -1,4 +1,4 @@ -import { Extension } from 'resource:///org/gnome/shell/extensions/extension.js'; +import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js'; import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import {FreebyIndicator} from './src/ui/indicator.js'; import {UsageService} from './src/services/usageService.js'; @@ -9,19 +9,28 @@ export default class FreebyExtension extends Extension { this._indicator = new FreebyIndicator(this._settings, () => this.openPreferences()); Main.panel.addToStatusArea(this.uuid, this._indicator); this._service = new UsageService(this._settings, this.path, () => this._indicator?.render(), alerts => { - for (const alert of alerts) - Main.notify('Freeby usage alert', `${alert.provider}: ${alert.label} ${alert.threshold === 100 ? 'limit reached' : `reached ${alert.threshold}%`}.`); + for (const alert of alerts) { + const state = alert.threshold === 100 ? 'limit reached' : `reached ${alert.threshold}%`; + Main.notify('Freeby usage alert', `${alert.provider}: ${alert.label} ${state}.`); + } }); this._indicator.attach(this._service); - this._settingsId = this._settings.connect('changed', () => { + this._settingsIds = []; + this._settingsIds.push(this._settings.connect('changed::enabled-providers', () => { this._service.configure(); this._indicator.render(); this._service.refreshAll(); - }); + })); + this._settingsIds.push(this._settings.connect('changed::default-provider', () => this._indicator.render())); + this._settingsIds.push(this._settings.connect('changed::history-retention-days', () => + this._service.refreshAll(true))); this._service.refreshAll(); } + disable() { - if (this._settingsId) { this._settings.disconnect(this._settingsId); this._settingsId = null; } + for (const id of this._settingsIds ?? []) + this._settings.disconnect(id); + this._settingsIds = null; this._service?.destroy(); this._service = null; this._indicator?.destroy(); diff --git a/prefs.js b/prefs.js index b67f37f..8dd9de9 100644 --- a/prefs.js +++ b/prefs.js @@ -1,7 +1,7 @@ import Adw from 'gi://Adw'; import Gtk from 'gi://Gtk'; -import { ExtensionPreferences, gettext as _ } from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js'; +import {ExtensionPreferences} from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js'; export default class FreebyPreferences extends ExtensionPreferences { fillPreferencesWindow(window) { @@ -10,13 +10,13 @@ export default class FreebyPreferences extends ExtensionPreferences { const page = new Adw.PreferencesPage(); window.add(page); - const group = new Adw.PreferencesGroup({ title: 'General' }); + const group = new Adw.PreferencesGroup({title: 'General'}); page.add(group); const intervalRow = new Adw.SpinRow({ title: 'Refresh interval', subtitle: 'How often to check usage (seconds, min 30)', - adjustment: new Gtk.Adjustment({ lower: 30, upper: 3600, step_increment: 10, page_increment: 60 }), + adjustment: new Gtk.Adjustment({lower: 30, upper: 3600, step_increment: 10, page_increment: 60}), }); settings.bind('refresh-interval', intervalRow, 'value', 0); group.add(intervalRow); diff --git a/src/collector/main.js b/src/collector/main.js index 7836175..83c429e 100644 --- a/src/collector/main.js +++ b/src/collector/main.js @@ -2,79 +2,91 @@ import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import GLibUnix from 'gi://GLibUnix'; import System from 'system'; -import {record, section} from '../core/usage.js'; +import {record, section, validateRecord} from '../core/usage.js'; import {collectClaude} from '../providers/claude.js'; import {collectCodex} from '../providers/codex.js'; import {collectCopilot} from '../providers/copilot.js'; import {collectCursor} from '../providers/cursor.js'; -import {fingerprint, findCommand, join, nodeCli, readJson} from '../services/files.js'; +import {commandSpec, findCommand} from '../services/commands.js'; +import {fingerprint, join, readJson, readText} from '../services/files.js'; import {scanHistory} from '../services/history.js'; import {requestJson} from '../services/http.js'; import {RpcClient, runCommand} from '../services/process.js'; +const COLLECTORS = Object.freeze({codex: collectCodex, claude: collectClaude, + cursor: collectCursor, copilot: collectCopilot}); +const HISTORY_ROOTS = { + codex: GLib.getenv('CODEX_HOME') || join(GLib.get_home_dir(), '.codex'), + claude: GLib.getenv('CLAUDE_CONFIG_DIR') || join(GLib.get_home_dir(), '.claude'), +}; +const token = value => typeof value === 'string' && value.trim().length <= 8192 && + !/[\u0000-\u001f\u007f]/.test(value.trim()) ? value.trim() : null; const id = ARGV[0]; -if (!['codex', 'claude', 'cursor', 'copilot'].includes(id)) { +if (!Object.prototype.hasOwnProperty.call(COLLECTORS, id)) { printerr('Usage: gjs -m src/collector/main.js [retention-days]'); System.exit(2); } -const retention = Math.max(7, Math.min(90, Number(ARGV[1]) || 30)); +const retentionText = ARGV[1] ?? ''; +const requestedRetention = /^\d+$/.test(retentionText) ? Number(retentionText) : 30; +const retention = Math.max(7, Math.min(90, Number.isSafeInteger(requestedRetention) ? requestedRetention : 30)); const cancellable = new Gio.Cancellable(); const loop = new GLib.MainLoop(null, false); -const signal = GLibUnix.signal_add(GLib.PRIORITY_DEFAULT, 15, () => { +let signal = GLibUnix.signal_add(GLib.PRIORITY_DEFAULT, 15, () => { + signal = 0; cancellable.cancel(); - return GLib.SOURCE_CONTINUE; + return GLib.SOURCE_REMOVE; }); const io = { fingerprint, + now: () => Date.now(), hasCommand(name) { try { findCommand(name); return true; } catch { return false; } }, scan(provider, suffixes, parser) { - const root = provider === 'codex' - ? GLib.getenv('CODEX_HOME') || join(GLib.get_home_dir(), '.codex') - : GLib.getenv('CLAUDE_CONFIG_DIR') || join(GLib.get_home_dir(), '.claude'); + const root = HISTORY_ROOTS[provider]; + if (!root) + throw new Error(`Unsupported local history provider: ${provider}`); return scanHistory(provider, suffixes.map(suffix => join(root, suffix)), parser, {retention}); }, credentials(provider) { - if (provider === 'claude') { - const root = GLib.getenv('CLAUDE_CONFIG_DIR') || join(GLib.get_home_dir(), '.claude'); - return readJson(join(root, '.credentials.json')); - } + if (provider === 'claude') + return readJson(join(HISTORY_ROOTS.claude, '.credentials.json'), null, 1024 * 1024); return null; }, - codexClient: () => new RpcClient(nodeCli('codex', ['app-server']), cancellable), + codexClient: () => new RpcClient(commandSpec('codex', ['app-server'], {runtimes: ['node']}), cancellable), http: (url, options) => requestJson(url, {...options, cancellable}), async token(provider) { if (provider === 'cursor') { - const auth = readJson(join(GLib.get_user_config_dir(), 'cursor', 'auth.json')); - return auth?.accessToken || null; + const auth = readJson(join(GLib.get_user_config_dir(), 'cursor', 'auth.json'), null, 1024 * 1024); + return token(auth?.accessToken); } + if (provider !== 'copilot') + return null; try { - const value = await runCommand([findCommand('gh'), 'auth', 'token'], {timeout: 5000, cancellable}); - if (value.trim()) - return value.trim(); + const value = await runCommand(commandSpec('gh', ['auth', 'token']), + {timeout: 5000, cancellable, maxOutputBytes: 64 * 1024}); + const cleaned = token(value); + if (cleaned) + return cleaned; } catch { /* Standalone setup token is also supported. */ } - try { - const [, bytes] = Gio.File.new_for_path(join(GLib.get_user_config_dir(), 'freeby', 'copilot-token')).load_contents(null); - return new TextDecoder().decode(bytes).trim(); - } catch { return null; } + return token(readText(join(GLib.get_user_config_dir(), 'freeby', 'copilot-token'), null, 64 * 1024)); }, }; (async () => { try { - const result = id === 'codex' ? await collectCodex(io) - : id === 'claude' ? await collectClaude(io) - : id === 'cursor' ? await collectCursor(io) : await collectCopilot(io); + const result = validateRecord(await COLLECTORS[id](io), id); if (!cancellable.is_cancelled()) print(JSON.stringify(result)); } catch { const result = record(id); result.limits = {...result.limits, ...section('unavailable', 'Could not collect usage. Retry or check provider sign-in.')}; + result.history = {...result.history, ...section('unavailable', 'Could not collect local history.')}; if (!cancellable.is_cancelled()) print(JSON.stringify(result)); } finally { - GLib.source_remove(signal); + if (signal) + GLib.source_remove(signal); loop.quit(); } })(); diff --git a/src/core/format.js b/src/core/format.js index 99c5768..8995e9d 100644 --- a/src/core/format.js +++ b/src/core/format.js @@ -27,7 +27,10 @@ export function age(time, now = Date.now()) { const minutes = Math.max(0, Math.floor((now - time) / 60000)); if (!minutes) return 'Updated just now'; - return minutes < 60 ? `Updated ${minutes}m ago` : `Updated ${Math.floor(minutes / 60)}h ago`; + if (minutes < 60) + return `Updated ${minutes}m ago`; + const hours = Math.floor(minutes / 60); + return hours < 24 ? `Updated ${hours}h ago` : `Updated ${Math.floor(hours / 24)}d ago`; } export function modelName(value) { diff --git a/src/core/usage.js b/src/core/usage.js index dedaaba..643d612 100644 --- a/src/core/usage.js +++ b/src/core/usage.js @@ -1,10 +1,49 @@ -export const SCHEMA_VERSION = 1; -export const NAMES = {codex: 'Codex', claude: 'Claude Code', cursor: 'Cursor', copilot: 'Copilot'}; -export const STATES = new Set(['loading', 'ready', 'partial', 'stale', 'missing-auth', 'unsupported', 'unavailable']); -export const WINDOW_STATES = new Set(['active', 'exhausted', 'unlimited']); +export const SCHEMA_VERSION = 2; +export const NAMES = Object.freeze({codex: 'Codex', claude: 'Claude Code', cursor: 'Cursor', copilot: 'Copilot'}); + +const STATES = new Set(['loading', 'ready', 'partial', 'stale', 'missing-auth', 'unsupported', 'unavailable']); +const WINDOW_STATES = new Set(['active', 'exhausted', 'unlimited']); + +const MAX_WINDOWS = 32; +const MAX_DAYS = 90; +const MAX_MODELS = 128; +const READY_STATES = new Set(['ready', 'partial', 'stale']); + +function fail(message) { + throw new Error(`Invalid usage record: ${message}`); +} + +function shape(value, field, allowed) { + if (!value || typeof value !== 'object' || Array.isArray(value) || + Object.keys(value).some(key => !allowed.includes(key))) + fail(field); +} + +function text(value, field, {nullable = false, max = 240} = {}) { + if (nullable && value === null) + return; + if (typeof value !== 'string' || !value.trim() || value.length > max || /[\u0000-\u001f\u007f]/.test(value)) + fail(field); +} + +function numeric(value, field, {nullable = false, integer = false} = {}) { + if (nullable && value === null) + return; + if (typeof value !== 'number' || !Number.isFinite(value) || value < 0 || (integer && !Number.isSafeInteger(value))) + fail(field); +} + +function isoDate(value, field) { + const parsed = typeof value === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(value) + ? Date.parse(`${value}T00:00:00Z`) + : NaN; + if (!Number.isFinite(parsed) || new Date(parsed).toISOString().slice(0, 10) !== value) + fail(field); +} export function number(value) { - if (value === null || value === undefined || value === '' || typeof value === 'boolean') + if (value === null || value === undefined || typeof value === 'boolean' || + !['number', 'string'].includes(typeof value) || (typeof value === 'string' && !value.trim())) return null; const result = Number(value); return Number.isFinite(result) && result >= 0 ? result : null; @@ -14,11 +53,17 @@ export function section(status = 'loading', message = '') { return {status, message, updatedAt: null}; } +export function isProvider(id) { + return typeof id === 'string' && Object.prototype.hasOwnProperty.call(NAMES, id); +} + export function record(id) { + if (!isProvider(id)) + throw new Error(`Unsupported provider: ${id}`); return { - schemaVersion: SCHEMA_VERSION, id, name: NAMES[id] ?? id, plan: null, accountKey: null, + schemaVersion: SCHEMA_VERSION, id, name: NAMES[id], plan: null, accountKey: null, capabilities: {limits: false, history: false, models: false}, - limits: {...section(), scope: 'account', windows: []}, + limits: {...section(), scope: 'account', source: null, windows: []}, history: {...section('unsupported', 'This provider does not expose local token history.'), scope: 'local', period: null, days: [], models: [], source: null}, }; @@ -48,57 +93,147 @@ export function validTime(value) { } export function validateRecord(value, expectedId) { - if (!value || value.schemaVersion !== SCHEMA_VERSION || value.id !== expectedId || !NAMES[value.id]) - throw new Error('Unsupported usage record'); - if (!value.capabilities || ['limits', 'history', 'models'].some(name => typeof value.capabilities[name] !== 'boolean')) - throw new Error('Invalid provider capabilities'); + shape(value, 'record shape', [ + 'schemaVersion', 'id', 'name', 'plan', 'accountKey', 'capabilities', 'limits', 'history', + ]); + if (!value || value.schemaVersion !== SCHEMA_VERSION || value.id !== expectedId || !isProvider(value.id)) + fail('provider identity'); + if (value.name !== NAMES[value.id]) + fail('provider name'); + text(value.plan, 'plan', {nullable: true, max: 80}); + if (value.accountKey !== null && (typeof value.accountKey !== 'string' || !/^[a-f0-9]{64}$/.test(value.accountKey))) + fail('account key'); + shape(value.capabilities, 'provider capabilities', ['limits', 'history', 'models']); + if (['limits', 'history', 'models'].some(name => typeof value.capabilities[name] !== 'boolean')) + fail('provider capabilities'); for (const name of ['limits', 'history']) { + shape(value[name], `${name} shape`, name === 'limits' + ? ['status', 'message', 'updatedAt', 'scope', 'source', 'windows'] + : ['status', 'message', 'updatedAt', 'scope', 'source', 'period', 'days', 'models', 'scannedFiles']); if (!value[name] || !STATES.has(value[name].status)) - throw new Error(`Invalid ${name} state`); + fail(`${name} state`); + if (typeof value[name].message !== 'string' || value[name].message.length > 500 || + /[\u0000-\u001f\u007f]/.test(value[name].message)) + fail(`${name} message`); + numeric(value[name].updatedAt, `${name} timestamp`, {nullable: true}); + if (READY_STATES.has(value[name].status) && (!value[name].updatedAt || value[name].updatedAt <= 0)) + fail(`${name} freshness`); + if (!READY_STATES.has(value[name].status) && value[name].updatedAt !== null) + fail(`${name} freshness`); + if (value[name].source !== null) + text(value[name].source, `${name} source`, {max: 160}); + if (READY_STATES.has(value[name].status) && value[name].source === null) + fail(`${name} source`); } + if (value.history.scannedFiles !== undefined) + numeric(value.history.scannedFiles, 'history scanned files', {integer: true}); + if (value.limits.scope !== 'account' || !['local', 'account'].includes(value.history.scope)) + fail('source scope'); if (!Array.isArray(value.limits.windows) || !Array.isArray(value.history.days) || !Array.isArray(value.history.models)) - throw new Error('Invalid usage arrays'); + fail('usage arrays'); + if (value.limits.windows.length > MAX_WINDOWS || value.history.days.length > MAX_DAYS || + value.history.models.length > MAX_MODELS) + fail('collection bounds'); + if (value.limits.windows.length && !value.capabilities.limits) + fail('limits capability'); + if (READY_STATES.has(value.limits.status) && !value.limits.windows.length) + fail('limits readiness'); + if (value.history.days.length && !value.capabilities.history) + fail('history capability'); + if (value.history.models.length && !value.capabilities.models) + fail('models capability'); + const windows = new Set(); for (const item of value.limits.windows) { - if (typeof item.id !== 'string' || typeof item.label !== 'string' || !WINDOW_STATES.has(item.state) || - (!item.unlimited && number(item.usedPercent) === null)) - throw new Error('Invalid quota window'); + shape(item, 'quota window', [ + 'id', 'label', 'usedPercent', 'used', 'limit', 'unit', 'unlimited', 'state', 'durationMinutes', 'resetsAt', + ]); + text(item.id, 'quota id', {max: 160}); + text(item.label, 'quota label', {max: 160}); + text(item.unit, 'quota unit', {max: 40}); + if (typeof item.unlimited !== 'boolean' || !WINDOW_STATES.has(item.state)) + fail('quota state'); + numeric(item.usedPercent, 'quota percentage', {nullable: true}); + numeric(item.used, 'quota usage', {nullable: true}); + numeric(item.limit, 'quota limit', {nullable: true}); + numeric(item.durationMinutes, 'quota duration', {nullable: true, integer: true}); + numeric(item.resetsAt, 'quota reset', {nullable: true, integer: true}); + if (item.unlimited ? item.state !== 'unlimited' || item.usedPercent !== null : item.usedPercent === null || + item.state !== (item.usedPercent >= 100 ? 'exhausted' : 'active')) + fail('quota consistency'); + if (windows.has(item.id)) + fail('duplicate quota'); + windows.add(item.id); } - for (const item of [...value.history.days, ...value.history.models]) { - if (number(item.total) === null) - throw new Error('Invalid token count'); + if (value.history.period === null) { + if (value.history.days.length || value.history.models.length) + fail('history period'); + } else { + shape(value.history.period, 'history period', ['start', 'end']); + isoDate(value.history.period?.start, 'history start date'); + isoDate(value.history.period?.end, 'history end date'); + if (value.history.period.start > value.history.period.end) + fail('history date order'); + } + const dates = new Set(); + let previousDate = null; + for (const item of value.history.days) { + shape(item, 'daily entry', ['date', 'total', 'sessions', 'events']); + isoDate(item.date, 'daily date'); + numeric(item.total, 'daily total', {integer: true}); + numeric(item.sessions, 'daily sessions', {integer: true}); + numeric(item.events, 'daily events', {integer: true}); + if (dates.has(item.date) || (previousDate && item.date <= previousDate) || + item.date < value.history.period.start || item.date > value.history.period.end) + fail('daily ordering'); + dates.add(item.date); + previousDate = item.date; + } + const models = new Set(); + for (const item of value.history.models) { + shape(item, 'model entry', ['model', 'total', 'input', 'output', 'cacheRead', 'cacheWrite']); + text(item.model, 'model name', {max: 160}); + for (const field of ['total', 'input', 'output', 'cacheRead', 'cacheWrite']) + numeric(item[field], `model ${field}`, {integer: true}); + if (models.has(item.model) || item.total !== item.input + item.output + item.cacheRead + item.cacheWrite) + fail('model totals'); + models.add(item.model); } return value; } -export function mergeRecord(previous, next) { +export function mergeRecord(previous, next, now = Date.now()) { if (!previous || (previous.accountKey && next.accountKey && previous.accountKey !== next.accountKey)) return next; const result = {...next}; for (const key of ['limits', 'history']) { const old = previous[key]; const current = next[key]; - if (['unavailable', 'missing-auth'].includes(current.status) && old?.updatedAt && + if (current.status === 'unavailable' && old?.updatedAt && ['ready', 'partial', 'stale'].includes(old.status)) { const preserved = key === 'limits' - ? {...old, windows: old.windows.filter(window => !window.resetsAt || window.resetsAt > Date.now())} + ? {...old, windows: old.windows.filter(window => !window.resetsAt || window.resetsAt > now)} : old; if (key !== 'limits' || preserved.windows.length) result[key] = {...preserved, status: 'stale', message: current.message}; } } - result.plan ??= previous.plan; - result.accountKey ??= previous.accountKey; + if (result.limits.status === 'stale') { + result.plan ??= previous.plan; + result.accountKey ??= previous.accountKey; + } return result; } export function highestUsage(value) { - if (!value || value.limits.status !== 'ready') + if (!value || !['ready', 'partial'].includes(value.limits.status)) return null; const values = value.limits.windows.filter(w => !w.unlimited && number(w.usedPercent) !== null).map(w => w.usedPercent); return values.length ? Math.max(...values) : null; } export function localDate(time) { + if (time === null || time === undefined || time === '') + return null; const d = new Date(time); if (!Number.isFinite(d.getTime())) return null; diff --git a/src/providers/claude.js b/src/providers/claude.js index 737fd96..b66fbc0 100644 --- a/src/providers/claude.js +++ b/src/providers/claude.js @@ -1,12 +1,20 @@ import {localDate, number, record, section, validTime, windowUsage} from '../core/usage.js'; const USAGE_URL = 'https://api.anthropic.com/api/oauth/usage'; +const MAX_WINDOWS = 32; + +function shortText(value, fallback = null, max = 160) { + if (typeof value !== 'string') + return fallback; + const text = value.trim(); + return text && !/[\u0000-\u001f\u007f]/.test(text) ? text.slice(0, max) : fallback; +} function planLabel(tier, subscription) { - const match = String(tier || '').match(/max_(\d+x)/i); + const match = shortText(tier, '')?.match(/max_(\d+x)/i); if (match) return `Max ${match[1]}`; - const value = String(subscription || '').trim(); + const value = shortText(subscription, '', 79); return value ? value[0].toUpperCase() + value.slice(1) : null; } @@ -14,7 +22,7 @@ export function claudeLogin(credentials) { const login = credentials?.claudeAiOauth; if (!login || typeof login !== 'object') return {token: null, expiresAt: null, plan: null}; - return {token: String(login.accessToken || '') || null, + return {token: shortText(login.accessToken, null, 8192), expiresAt: number(login.expiresAt), plan: planLabel(login.rateLimitTier, login.subscriptionType)}; } @@ -46,7 +54,8 @@ function scopedDuration(kind) { export function claudeLimits(payload, now = Date.now()) { if (!payload || typeof payload !== 'object') - return {...section('unavailable', 'Claude did not report any quota windows.'), scope: 'account', windows: []}; + return {...section('unavailable', 'Claude did not report any quota windows.'), + scope: 'account', source: 'Anthropic OAuth usage', windows: []}; const session = payload.five_hour; const weekly = payload.seven_day_oauth_apps ?? payload.seven_day; const scoped = Array.isArray(payload.limits) ? payload.limits : []; @@ -54,6 +63,7 @@ export function claudeLimits(payload, now = Date.now()) { ...scoped.map(item => item?.percent)].map(rawUtilization).filter(value => value !== null); const percentScale = raw.some(value => value >= 1); const windows = []; + let truncated = false; const add = (id, label, bucket, durationMinutes) => { if (!bucket || typeof bucket !== 'object') return; @@ -66,9 +76,13 @@ export function claudeLimits(payload, now = Date.now()) { add('weekly', 'Weekly', weekly, 10080); const seen = new Set(); for (const item of scoped) { + if (windows.length >= MAX_WINDOWS) { + truncated = true; + break; + } const model = item?.scope?.model; - const name = String(model?.display_name || model?.id || '').trim(); - const kind = String(item?.kind || '').trim(); + const name = shortText(model?.display_name ?? model?.id, '', 100); + const kind = shortText(item?.kind, '', 40); const key = `${name}:${kind}`; if (!name || seen.has(key)) continue; @@ -82,8 +96,11 @@ export function claudeLimits(payload, now = Date.now()) { } } return windows.length - ? {...section('ready'), updatedAt: now, scope: 'account', source: 'Anthropic OAuth usage', windows} - : {...section('unavailable', 'Claude did not report any supported quota windows.'), scope: 'account', windows: []}; + ? {...section(truncated ? 'partial' : 'ready', + truncated ? 'Some Claude quota windows were omitted to keep the response bounded.' : ''), + updatedAt: now, scope: 'account', source: 'Anthropic OAuth usage', windows} + : {...section('unavailable', 'Claude did not report any supported quota windows.'), + scope: 'account', source: 'Anthropic OAuth usage', windows: []}; } export function parseClaudeEvent(entry, state) { @@ -93,7 +110,7 @@ export function parseClaudeEvent(entry, state) { const usage = message.usage ?? entry.usage; if (!usage || typeof usage !== 'object') return null; - if (entry.sessionId) + if (typeof entry.sessionId === 'string' && entry.sessionId) state.session = entry.sessionId; const timestamp = entry.timestamp ?? message.timestamp; const date = localDate(timestamp); @@ -105,7 +122,7 @@ export function parseClaudeEvent(entry, state) { const cacheWrite = number(usage.cache_creation_input_tokens ?? usage.cacheCreationInputTokens) ?? 0; if (input + output + cacheRead + cacheWrite === 0) return null; - const model = message.model ?? entry.model ?? state.model ?? 'Unknown model'; + const model = shortText(message.model ?? entry.model ?? state.model, 'Unknown model'); state.model = model; const identity = message.id ?? entry.messageId ?? entry.uuid ?? entry.requestId ?? `${timestamp}:${JSON.stringify(usage)}`; return {id: `${state.session}:${identity}`, session: state.session, date, model, @@ -113,6 +130,7 @@ export function parseClaudeEvent(entry, state) { } export async function collectClaude(io) { + const now = io.now?.() ?? Date.now(); const result = record('claude'); result.capabilities = {limits: true, history: true, models: true}; result.history = io.scan('claude', ['projects'], parseClaudeEvent); @@ -125,7 +143,7 @@ export async function collectClaude(io) { return result; } result.accountKey = io.fingerprint(login.token); - if (login.expiresAt && login.expiresAt <= Date.now()) { + if (login.expiresAt && login.expiresAt <= now) { result.limits = {...result.limits, ...section('missing-auth', 'Claude Code sign-in expired. Start Claude Code or run claude auth login.')}; return result; } @@ -136,7 +154,7 @@ export async function collectClaude(io) { Accept: 'application/json', }}); if (response.status === 200) { - result.limits = claudeLimits(response.data); + result.limits = claudeLimits(response.data, now); } else { result.limits = {...result.limits, ...section([401, 403].includes(response.status) ? 'missing-auth' : 'unavailable', [401, 403].includes(response.status) ? 'Reconnect Claude Code to read account limits.' : diff --git a/src/providers/codex.js b/src/providers/codex.js index ac54f3d..11c69fe 100644 --- a/src/providers/codex.js +++ b/src/providers/codex.js @@ -1,30 +1,64 @@ import {localDate, number, record, section, windowUsage} from '../core/usage.js'; +const MAX_WINDOWS = 32; + +function shortText(value, fallback = null, max = 80) { + if (typeof value !== 'string') + return fallback; + const text = value.trim(); + return text && !/[\u0000-\u001f\u007f]/.test(text) ? text.slice(0, max) : fallback; +} + +function compareBuckets([left], [right]) { + if (left === 'codex') + return -1; + if (right === 'codex') + return 1; + return left.localeCompare(right); +} + export function codexLimits(response, now = Date.now()) { - const buckets = response?.rateLimitsByLimitId && typeof response.rateLimitsByLimitId === 'object' - ? Object.entries(response.rateLimitsByLimitId) - : [['codex', response?.rateLimits]]; + const scoped = response?.rateLimitsByLimitId && typeof response.rateLimitsByLimitId === 'object' && + !Array.isArray(response.rateLimitsByLimitId) + ? Object.entries(response.rateLimitsByLimitId).sort(compareBuckets) + : []; + const buckets = scoped.length ? scoped : [['codex', response?.rateLimits]]; const windows = []; + let truncated = false; for (const [bucketId, bucket] of buckets) { + if (!bucket || typeof bucket !== 'object' || Array.isArray(bucket)) + continue; for (const key of ['primary', 'secondary']) { + if (windows.length >= MAX_WINDOWS) { + truncated = true; + break; + } const w = bucket?.[key]; - if (!w) + if (!w || typeof w !== 'object' || Array.isArray(w)) continue; - const mins = number(w.windowDurationMins); + const reportedMinutes = number(w.windowDurationMins); + const mins = Number.isSafeInteger(reportedMinutes) ? reportedMinutes : null; const duration = mins === 10080 ? 'Weekly' : mins === 300 ? 'Session · 5 hours' : mins ? `${mins >= 60 ? `${mins / 60} hours` : `${mins} minutes`}` : key === 'primary' ? 'Primary window' : 'Secondary window'; - const bucketName = bucket.limitName || (bucketId === 'codex' ? null : bucketId); - const item = windowUsage({id: `${bucketId}:${key}`, + const safeBucketId = shortText(bucketId, 'quota', 120); + const bucketName = shortText(bucket.limitName, + safeBucketId === 'codex' ? null : safeBucketId, 120); + const item = windowUsage({id: `${safeBucketId}:${key}`, label: buckets.length > 1 && bucketName ? `${bucketName} · ${duration}` : duration, usedPercent: w.usedPercent, durationMinutes: mins, - resetsAt: number(w.resetsAt) ? Number(w.resetsAt) * 1000 : null}); + resetsAt: w.resetsAt}); if (item) windows.push(item); } + if (truncated) + break; } return windows.length - ? {...section('ready'), updatedAt: now, scope: 'account', windows} - : {...section('unavailable', 'Codex did not report quota windows for this account.'), scope: 'account', windows: []}; + ? {...section(truncated ? 'partial' : 'ready', + truncated ? 'Some Codex quota windows were omitted to keep the response bounded.' : ''), + updatedAt: now, scope: 'account', source: 'Codex app-server', windows} + : {...section('unavailable', 'Codex did not report quota windows for this account.'), + scope: 'account', source: 'Codex app-server', windows: []}; } export function parseCodexEvent(entry, state) { @@ -39,14 +73,21 @@ export function parseCodexEvent(entry, state) { let usage = payload.info.last_token_usage; let identity; if (cumulative && number(cumulative.total_tokens) !== null) { - const total = Number(cumulative.total_tokens); - if (state.cumulative && total <= state.cumulative.total_tokens) + const fields = ['input_tokens', 'output_tokens', 'cached_input_tokens', 'cache_write_input_tokens']; + const current = Object.fromEntries(fields.map(field => [field, number(cumulative[field]) ?? 0])); + current.total_tokens = Number(cumulative.total_tokens); + if (state.cumulative && current.total_tokens === state.cumulative.total_tokens) return null; - const previous = state.cumulative ?? {}; - usage = Object.fromEntries(['input_tokens', 'output_tokens', 'cached_input_tokens', 'cache_write_input_tokens'] - .map(k => [k, Math.max(0, (number(cumulative[k]) ?? 0) - (number(previous[k]) ?? 0))])); - state.cumulative = cumulative; - identity = `total:${total}`; + if (state.cumulative && current.total_tokens < state.cumulative.total_tokens) { + state.cumulativeEpoch = (number(state.cumulativeEpoch) ?? 0) + 1; + usage = current; + } else { + const previous = state.cumulative ?? {}; + usage = Object.fromEntries(fields.map(field => + [field, Math.max(0, current[field] - (number(previous[field]) ?? 0))])); + } + state.cumulative = current; + identity = `total:${state.cumulativeEpoch ?? 0}:${current.total_tokens}`; } else { identity = `${entry.timestamp}:${JSON.stringify(usage)}`; } @@ -61,24 +102,30 @@ export function parseCodexEvent(entry, state) { } export async function collectCodex(io) { + const now = io.now?.() ?? Date.now(); const result = record('codex'); result.capabilities = {limits: true, history: true, models: true}; result.history = io.scan('codex', ['sessions', 'archived_sessions'], parseCodexEvent); let rpc; try { rpc = io.codexClient(); - await rpc.request('initialize', {clientInfo: {name: 'freeby', version: '2.0.0'}, capabilities: {experimentalApi: false}}); + await rpc.request('initialize', {clientInfo: {name: 'freeby', title: 'Freeby', version: '2.0.0'}, + capabilities: {experimentalApi: false}}); rpc.notify('initialized', {}); const account = await rpc.request('account/read', {refreshToken: false}); if (!account?.account) { result.limits = {...result.limits, ...section('missing-auth', 'Sign in with codex login to read account limits.')}; return result; } - result.plan = account.account.planType ?? account.account.type ?? null; - result.accountKey = io.fingerprint(account.account.email || account.account.chatgptAccountId || result.plan); + result.plan = shortText(account.account.planType ?? account.account.type); + const accountIdentity = shortText(account.account.email ?? account.account.chatgptAccountId, null, 320); + result.accountKey = accountIdentity ? io.fingerprint(accountIdentity) : null; const limits = await rpc.request('account/rateLimits/read', {}); - result.limits = {...codexLimits(limits), source: 'Codex app-server'}; - result.plan = limits?.rateLimits?.planType ?? result.plan; + result.limits = codexLimits(limits, now); + const scopedLimits = limits?.rateLimitsByLimitId && typeof limits.rateLimitsByLimitId === 'object' && + !Array.isArray(limits.rateLimitsByLimitId) ? Object.values(limits.rateLimitsByLimitId) : []; + result.plan = shortText(limits?.rateLimits?.planType ?? + scopedLimits.find(bucket => bucket?.planType)?.planType, result.plan); } catch (error) { result.limits = {...result.limits, ...section(error.code === 'NOT_FOUND' ? 'unsupported' : 'unavailable', error.code === 'NOT_FOUND' ? 'Install the Codex CLI to read account limits.' : 'Could not read Codex limits. Check CLI sign-in and compatibility.')}; diff --git a/src/providers/copilot.js b/src/providers/copilot.js index f967b36..7765418 100644 --- a/src/providers/copilot.js +++ b/src/providers/copilot.js @@ -8,8 +8,16 @@ export async function collectCopilot(io) { result.limits = {...result.limits, ...section('missing-auth', 'Sign in to GitHub CLI to read Copilot usage.')}; return result; } - const {status, data} = await io.http('https://api.github.com/copilot_internal/user', - {headers: {Authorization: `Bearer ${token}`, Accept: 'application/json'}}); + result.accountKey = io.fingerprint(token); + let status; + let data; + try { + ({status, data} = await io.http('https://api.github.com/copilot_internal/user', + {headers: {Authorization: `Bearer ${token}`, Accept: 'application/json'}})); + } catch { + result.limits = {...result.limits, ...section('unavailable', 'Could not reach GitHub Copilot usage. Retry later.')}; + return result; + } if (status !== 200 || !data || typeof data !== 'object') { result.limits = {...result.limits, ...section([401, 403].includes(status) ? 'missing-auth' : 'unavailable', [401, 403].includes(status) ? 'Reconnect GitHub CLI to read Copilot usage.' : `Usage endpoint unavailable (HTTP ${status}).`)}; @@ -27,7 +35,7 @@ export async function collectCopilot(io) { result.plan = data.copilot_plan ?? null; result.limits = {...result.limits, ...section(result.limits.windows.length ? 'ready' : 'unavailable', result.limits.windows.length ? '' : 'No supported allowance was reported. Check the GitHub Copilot dashboard.'), - updatedAt: result.limits.windows.length ? Date.now() : null}; - result.accountKey = io.fingerprint(token); + updatedAt: result.limits.windows.length ? io.now?.() ?? Date.now() : null, + source: result.limits.windows.length ? 'GitHub Copilot entitlement' : null}; return result; } diff --git a/src/providers/cursor.js b/src/providers/cursor.js index 3578445..ed5f884 100644 --- a/src/providers/cursor.js +++ b/src/providers/cursor.js @@ -8,8 +8,16 @@ export async function collectCursor(io) { result.limits = {...result.limits, ...section('missing-auth', 'Sign in to Cursor to read usage.')}; return result; } - const {status, data} = await io.http('https://api2.cursor.sh/aiserver.v1.DashboardService/GetCurrentPeriodUsage', - {method: 'POST', headers: {Authorization: `Bearer ${token}`, 'Connect-Protocol-Version': '1'}, body: {}}); + result.accountKey = io.fingerprint(token); + let status; + let data; + try { + ({status, data} = await io.http('https://api2.cursor.sh/aiserver.v1.DashboardService/GetCurrentPeriodUsage', + {method: 'POST', headers: {Authorization: `Bearer ${token}`, 'Connect-Protocol-Version': '1'}, body: {}})); + } catch { + result.limits = {...result.limits, ...section('unavailable', 'Could not reach Cursor usage. Retry later.')}; + return result; + } if (status !== 200 || !data || typeof data !== 'object') { result.limits = {...result.limits, ...section([401, 403].includes(status) ? 'missing-auth' : 'unavailable', [401, 403].includes(status) ? 'Reconnect Cursor to read usage.' : `Usage endpoint unavailable (HTTP ${status}).`)}; @@ -23,7 +31,7 @@ export async function collectCursor(io) { result.limits.windows.push(window); result.limits = {...result.limits, ...section(result.limits.windows.length ? 'ready' : 'unavailable', result.limits.windows.length ? '' : 'No supported allowance was reported. Check the Cursor dashboard.'), - updatedAt: result.limits.windows.length ? Date.now() : null}; - result.accountKey = io.fingerprint(token); + updatedAt: result.limits.windows.length ? io.now?.() ?? Date.now() : null, + source: result.limits.windows.length ? 'Cursor dashboard' : null}; return result; } diff --git a/src/services/commands.js b/src/services/commands.js new file mode 100644 index 0000000..1034193 --- /dev/null +++ b/src/services/commands.js @@ -0,0 +1,138 @@ +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import {join} from './files.js'; + +const STATIC_DIRECTORIES = [ + '.local/bin', + '.cargo/bin', + '.npm-global/bin', + '.volta/bin', + '.asdf/shims', + '.local/share/mise/shims', + '.bun/bin', +]; + +const VERSIONED_DIRECTORIES = [ + ['.local/share/fnm/node-versions', ['installation', 'bin']], + ['.nvm/versions/node', ['bin']], + ['.local/share/mise/installs/node', ['bin']], + ['.asdf/installs/nodejs', ['bin']], +]; + +function validCommandName(name) { + return typeof name === 'string' && /^[A-Za-z0-9._+-]+$/.test(name) && name !== '.' && name !== '..'; +} + +function executable(path) { + return GLib.file_test(path, GLib.FileTest.IS_REGULAR) && + GLib.file_test(path, GLib.FileTest.IS_EXECUTABLE) ? path : null; +} + +function versionParts(value) { + return (String(value).match(/\d+/g) ?? []).map(Number); +} + +function compareVersions(a, b) { + const left = versionParts(a); + const right = versionParts(b); + for (let index = 0; index < Math.max(left.length, right.length); index++) { + const difference = (left[index] ?? 0) - (right[index] ?? 0); + if (difference) + return difference; + } + return String(a).localeCompare(String(b)); +} + +function commandDirectories(command) { + const directories = []; + let current = command; + for (let depth = 0; depth < 8; depth++) { + const directory = GLib.path_get_dirname(current); + if (!directories.includes(directory)) + directories.push(directory); + if (!GLib.file_test(current, GLib.FileTest.IS_SYMLINK)) + break; + try { + const target = GLib.file_read_link(current); + current = GLib.canonicalize_filename(target, GLib.path_get_dirname(current)); + } catch { + break; + } + } + return directories; +} + +function versionedCommand(root, suffix, name) { + const directory = Gio.File.new_for_path(root); + if (!directory.query_exists(null)) + return null; + + const candidates = []; + let iterator = null; + try { + iterator = directory.enumerate_children('standard::name,standard::type,standard::is-symlink', + Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null); + let info; + while ((info = iterator.next_file(null))) { + if (info.get_is_symlink() || info.get_file_type() !== Gio.FileType.DIRECTORY) + continue; + const path = executable(join(root, info.get_name(), ...suffix, name)); + if (path) + candidates.push({version: info.get_name(), path}); + } + } catch { + return null; + } finally { + iterator?.close(null); + } + + candidates.sort((a, b) => compareVersions(b.version, a.version)); + return candidates[0]?.path ?? null; +} + +export function findCommand(name, {home = GLib.get_home_dir(), usePath = true} = {}) { + if (!validCommandName(name)) + throw new Error('Invalid command name'); + + const found = usePath ? GLib.find_program_in_path(name) : null; + if (found) + return found; + + for (const directory of STATIC_DIRECTORIES) { + const path = executable(join(home, directory, name)); + if (path) + return path; + } + + for (const [directory, suffix] of VERSIONED_DIRECTORIES) { + const path = versionedCommand(join(home, directory), suffix, name); + if (path) + return path; + } + + const error = new Error(`${name} is not installed`); + error.code = 'NOT_FOUND'; + throw error; +} + +export function commandSpec(name, args = [], {runtimes = [], ...options} = {}) { + if (!Array.isArray(args) || args.some(value => typeof value !== 'string' && + (typeof value !== 'number' || !Number.isFinite(value)))) + throw new Error('Command arguments must be strings or numbers'); + if (!Array.isArray(runtimes) || runtimes.some(runtime => !validCommandName(runtime))) + throw new Error('Command runtimes must be command names'); + const command = findCommand(name, options); + const directories = commandDirectories(command); + for (const runtime of runtimes) { + try { + const directory = GLib.path_get_dirname(findCommand(runtime, options)); + if (!directories.includes(directory)) + directories.push(directory); + } catch { + // The command may be native or resolve its own runtime. + } + } + const inheritedPath = GLib.getenv('PATH') || '/usr/local/bin:/usr/bin:/bin'; + const path = [...directories, ...inheritedPath.split(':').filter(entry => entry && !directories.includes(entry))].join(':'); + return {argv: [command, ...args.map(String)], environment: {PATH: path}}; +} diff --git a/src/services/files.js b/src/services/files.js index 20f8a7d..ab92d82 100644 --- a/src/services/files.js +++ b/src/services/files.js @@ -4,100 +4,43 @@ import GLib from 'gi://GLib'; export const join = (...parts) => GLib.build_filenamev(parts); export const fingerprint = value => GLib.compute_checksum_for_string(GLib.ChecksumType.SHA256, String(value), -1); -export function readJson(path, fallback = null, maxBytes = 16 * 1024 * 1024) { +export function readText(path, fallback = null, maxBytes = 16 * 1024 * 1024) { + if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) + throw new Error('File size limit must be a positive integer'); try { const file = Gio.File.new_for_path(path); if (file.query_info('standard::size', Gio.FileQueryInfoFlags.NONE, null).get_size() > maxBytes) return fallback; const [, bytes] = file.load_contents(null); - return JSON.parse(new TextDecoder().decode(bytes)); + if (bytes.length > maxBytes) + return fallback; + return new TextDecoder().decode(bytes); } catch { return fallback; } } -export function writeJson(path, value) { - const file = Gio.File.new_for_path(path); - const parent = file.get_parent(); - GLib.mkdir_with_parents(parent.get_path(), 0o700); - file.replace_contents(new TextEncoder().encode(JSON.stringify(value)), null, false, - Gio.FileCreateFlags.PRIVATE | Gio.FileCreateFlags.REPLACE_DESTINATION, null); -} - -function executable(path) { - return GLib.file_test(path, GLib.FileTest.IS_EXECUTABLE) ? path : null; -} - -function versionParts(value) { - return (String(value).match(/\d+/g) ?? []).map(Number); -} - -function compareVersions(a, b) { - const left = versionParts(a); - const right = versionParts(b); - for (let i = 0; i < Math.max(left.length, right.length); i++) { - const difference = (left[i] ?? 0) - (right[i] ?? 0); - if (difference) - return difference; - } - return String(a).localeCompare(String(b)); -} - -function versionedCommand(root, suffix, name) { - const directory = Gio.File.new_for_path(root); - if (!directory.query_exists(null)) - return null; - const candidates = []; - let iterator; +export function readJson(path, fallback = null, maxBytes = 16 * 1024 * 1024) { + const text = readText(path, null, maxBytes); + if (text === null) + return fallback; try { - iterator = directory.enumerate_children('standard::name,standard::type', - Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null); - let info; - while ((info = iterator.next_file(null))) { - if (info.get_file_type() !== Gio.FileType.DIRECTORY) - continue; - const path = executable(join(root, info.get_name(), ...suffix, name)); - if (path) - candidates.push({version: info.get_name(), path}); - } + return JSON.parse(text); } catch { - return null; - } finally { - iterator?.close(null); - } - candidates.sort((a, b) => compareVersions(b.version, a.version)); - return candidates[0]?.path ?? null; -} - -export function findCommand(name, {home = GLib.get_home_dir(), usePath = true} = {}) { - const found = usePath ? GLib.find_program_in_path(name) : null; - if (found) - return found; - for (const directory of ['.local/bin', '.cargo/bin', '.npm-global/bin', '.volta/bin', '.asdf/shims', - '.local/share/mise/shims', '.bun/bin']) { - const path = executable(join(home, directory, name)); - if (path) - return path; - } - for (const [directory, suffix] of [ - ['.local/share/fnm/node-versions', ['installation', 'bin']], - ['.nvm/versions/node', ['bin']], - ['.local/share/mise/installs/node', ['bin']], - ['.asdf/installs/nodejs', ['bin']], - ]) { - const path = versionedCommand(join(home, directory), suffix, name); - if (path) - return path; + return fallback; } - const error = new Error(`${name} is not installed`); - error.code = 'NOT_FOUND'; - throw error; } -export function nodeCli(name, args = [], options = {}) { - const command = findCommand(name, options); - const runtime = executable(join(GLib.path_get_dirname(command), 'node')); - return runtime ? [runtime, command, ...args] : [command, ...args]; +export function writeJson(path, value) { + const file = Gio.File.new_for_path(path); + const parent = file.get_parent(); + const parentPath = parent?.get_path(); + if (!parentPath || GLib.mkdir_with_parents(parentPath, 0o700) < 0 || GLib.chmod(parentPath, 0o700) < 0) + throw new Error('Could not secure the private data directory'); + file.replace_contents(new TextEncoder().encode(JSON.stringify(value)), null, false, + Gio.FileCreateFlags.PRIVATE | Gio.FileCreateFlags.REPLACE_DESTINATION, null); + if (GLib.chmod(path, 0o600) < 0) + throw new Error('Could not secure the private data file'); } export function stateDirectory() { diff --git a/src/services/history.js b/src/services/history.js index 1c8e406..e9e11c5 100644 --- a/src/services/history.js +++ b/src/services/history.js @@ -3,24 +3,46 @@ import GLib from 'gi://GLib'; import {aggregateEvents, recentDates, section} from '../core/usage.js'; import {cacheDirectory, fingerprint, join, readJson, writeJson} from './files.js'; -function listFiles(root, output, depth = 0) { - if (depth > 12 || output.length > 20000) +const CACHE_VERSION = 4; +const MAX_DEPTH = 12; +const MAX_FILES = 20000; +const MAX_LINE_BYTES = 4 * 1024 * 1024; +const MAX_MODELS = 128; +const SCAN_SECONDS = 20; + +function fileStamp(info) { + const modified = info.get_attribute_uint64('time::modified'); + const modifiedUsec = info.get_attribute_uint32('time::modified-usec'); + const changed = info.get_attribute_uint64('time::changed'); + const changedUsec = info.get_attribute_uint32('time::changed-usec'); + return `${modified}:${modifiedUsec}:${changed}:${changedUsec}`; +} + +function listFiles(root, output, deadline, depth = 0) { + if (depth > MAX_DEPTH || output.length >= MAX_FILES || GLib.get_monotonic_time() > deadline) throw new Error('History directory exceeds scan bounds'); const file = Gio.File.new_for_path(root); if (!file.query_exists(null)) return false; - const iterator = file.enumerate_children('standard::name,standard::type,standard::is-symlink,standard::size,time::modified', + const iterator = file.enumerate_children('standard::name,standard::type,standard::is-symlink,standard::size,' + + 'time::modified,time::modified-usec,time::changed,time::changed-usec,id::file', Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null); try { let info; while ((info = iterator.next_file(null))) { + if (GLib.get_monotonic_time() > deadline) + throw new Error('History directory scan timed out'); if (info.get_is_symlink()) continue; const path = join(root, info.get_name()); if (info.get_file_type() === Gio.FileType.DIRECTORY) - listFiles(path, output, depth + 1); - else if (info.get_name().endsWith('.jsonl')) - output.push({path, size: info.get_size(), mtime: info.get_attribute_uint64('time::modified')}); + listFiles(path, output, deadline, depth + 1); + else if (info.get_file_type() === Gio.FileType.REGULAR && info.get_name().endsWith('.jsonl')) { + if (output.length >= MAX_FILES) + throw new Error('History directory exceeds scan bounds'); + output.push({path, size: info.get_size(), stamp: fileStamp(info), + fileId: info.get_attribute_string('id::file') ?? ''}); + } } } finally { iterator.close(null); @@ -28,33 +50,108 @@ function listFiles(root, output, depth = 0) { return true; } +function privateIdentity(provider, value) { + const text = String(value ?? ''); + return /^h:[a-f0-9]{64}$/.test(text) ? text : `h:${fingerprint(`${provider}:${text}`)}`; +} + +function parserState(value, fallbackSession) { + const session = typeof value?.session === 'string' && /^h:[a-f0-9]{64}$/.test(value.session) + ? value.session + : fallbackSession; + const state = {session}; + if (typeof value?.model === 'string' && value.model.trim() && value.model.length <= 160 && + !/[\u0000-\u001f\u007f]/.test(value.model)) + state.model = value.model; + if (numberValue(value?.cumulativeEpoch) !== null) + state.cumulativeEpoch = numberValue(value.cumulativeEpoch); + if (value?.cumulative && typeof value.cumulative === 'object') { + const fields = ['total_tokens', 'input_tokens', 'output_tokens', 'cached_input_tokens', 'cache_write_input_tokens']; + if (fields.every(field => numberValue(value.cumulative[field]) !== null)) + state.cumulative = Object.fromEntries(fields.map(field => [field, numberValue(value.cumulative[field])])); + } + return state; +} + +function numberValue(value) { + return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 ? value : null; +} + +function cachedFile(value, fallbackSession) { + if (!value || typeof value !== 'object' || !value.events || typeof value.events !== 'object' || + Array.isArray(value.events)) + return null; + const offset = numberValue(value.offset); + const size = value.size === -1 ? -1 : numberValue(value.size); + if (offset === null || size === null || typeof value.stamp !== 'string' || typeof value.fileId !== 'string') + return null; + return {offset, size, stamp: value.stamp, fileId: value.fileId, + state: parserState(value.state, fallbackSession), events: value.events, partial: value.partial === true}; +} + +function validDate(value) { + const parsed = typeof value === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(value) + ? Date.parse(`${value}T00:00:00Z`) + : NaN; + return Number.isFinite(parsed) && new Date(parsed).toISOString().slice(0, 10) === value; +} + +function sanitizedEvent(event, provider, stored = false) { + if (!event || typeof event !== 'object' || typeof event.id !== 'string' || !event.id || + typeof event.session !== 'string' || !event.session || typeof event.model !== 'string' || + !event.model.trim() || event.model.length > 160 || event.id.length > 2048 || event.session.length > 2048 || + /[\u0000-\u001f\u007f]/.test(event.model) || !validDate(event.date) || + (stored && (!/^h:[a-f0-9]{64}$/.test(event.id) || !/^h:[a-f0-9]{64}$/.test(event.session)))) + return null; + const values = Object.fromEntries(['input', 'output', 'cacheRead', 'cacheWrite'] + .map(field => [field, numberValue(event[field])])); + if (Object.values(values).some(value => value === null)) + return null; + return {...values, date: event.date, model: event.model, + id: stored ? event.id : privateIdentity(provider, event.id), + session: stored ? event.session : privateIdentity(provider, event.session)}; +} + export function scanHistory(id, roots, parse, {retention = 30, now = Date.now(), cachePath = null} = {}) { + if (!Number.isSafeInteger(retention) || retention < 7 || retention > 90 || + !Number.isSafeInteger(now) || now <= 0 || !Array.isArray(roots) || !roots.length || + roots.some(root => typeof root !== 'string' || !root) || typeof parse !== 'function' || + (cachePath !== null && (typeof cachePath !== 'string' || !cachePath))) + throw new Error('Invalid history scan options'); const cutoff = recentDates(now, retention)[0]; const destination = cachePath ?? join(cacheDirectory(), `history-${id}.json`); let cached = readJson(destination, {}, 64 * 1024 * 1024); - const identity = fingerprint(roots.join('\n')); - if (cached.version !== 2 || cached.identity !== identity || typeof cached.files !== 'object') - cached = {version: 2, identity, files: {}}; + const identity = fingerprint(JSON.stringify(roots)); + if (cached.version !== CACHE_VERSION || cached.identity !== identity || typeof cached.files !== 'object' || + Array.isArray(cached.files)) + cached = {version: CACHE_VERSION, identity, files: {}, updatedAt: null}; const files = []; let detected = false; let partial = false; let parsed = 0; const started = GLib.get_monotonic_time(); + const deadline = started + SCAN_SECONDS * 1000000; for (const root of roots) { - try { detected = listFiles(root, files) || detected; } catch { partial = true; } + try { detected = listFiles(root, files, deadline) || detected; } catch { partial = true; } } + files.sort((a, b) => a.path.localeCompare(b.path)); + const currentKeys = new Set(files.map(file => fingerprint(file.path))); for (const file of files) { - if ((GLib.get_monotonic_time() - started) / 1000000 > 20) { + if ((GLib.get_monotonic_time() - started) / 1000000 > SCAN_SECONDS) { partial = true; break; } const key = fingerprint(file.path); - let previous = cached.files[key]; - if (previous?.size === file.size && previous?.mtime === file.mtime) + const fallbackSession = privateIdentity(id, key); + let previous = cachedFile(cached.files[key], fallbackSession); + if (previous?.size === file.size && previous.stamp === file.stamp && previous.fileId === file.fileId) { + partial ||= previous.partial; continue; + } // Changed or truncated files are rebuilt; append-only files resume at the last complete line. - if (!previous || file.size <= previous.size) - previous = {offset: 0, state: {session: key}, events: {}}; + if (!previous || file.fileId !== previous.fileId || file.size < previous.size || previous.offset > file.size) + previous = {offset: 0, size: 0, stamp: '', fileId: file.fileId, + state: {session: fallbackSession}, events: {}, partial: false}; let input; try { input = Gio.File.new_for_path(file.path).read(null); @@ -77,56 +174,92 @@ export function scanHistory(id, roots, parse, {retention = 30, now = Date.now(), offset += i - start + 1; start = i + 1; try { + const text = new TextDecoder().decode(line); + if (!text.trim()) + continue; const oldSession = previous.state.session; - const event = parse(JSON.parse(new TextDecoder().decode(line)), previous.state); + const event = parse(JSON.parse(text), previous.state); // Provider session identifiers are useful only for // deduplication. Hash them before either parser state or // derived events reach Freeby's private cache. if (previous.state.session !== oldSession) - previous.state.session = fingerprint(`${id}:${previous.state.session}`); - if (event && event.date >= cutoff) { - const sanitized = {...event, - id: fingerprint(`${id}:${event.id}`), - session: fingerprint(`${id}:${event.session ?? event.id}`)}; - previous.events[sanitized.id] = sanitized; + previous.state.session = privateIdentity(id, previous.state.session); + if (event) { + const sanitized = sanitizedEvent(event, id); + if (!sanitized) + previous.partial = true; + else if (sanitized.date >= cutoff) + previous.events[sanitized.id] = sanitized; } - } catch { partial = true; } + } catch { + previous.partial = true; + } } tail = buffer.slice(start); - if (tail.length > 4 * 1024 * 1024 || (GLib.get_monotonic_time() - started) / 1000000 > 20) { + if (tail.length > MAX_LINE_BYTES || + (GLib.get_monotonic_time() - started) / 1000000 > SCAN_SECONDS) { partial = true; stop = true; } } previous.offset = offset; // A time-limited scan must resume even if the source file has not changed. - previous.size = stop ? -1 : file.size; - previous.mtime = file.mtime; + previous.size = stop ? -1 : Math.max(file.size, offset); + previous.stamp = file.stamp; + previous.fileId = file.fileId; + previous.state = parserState(previous.state, fallbackSession); cached.files[key] = previous; + partial ||= previous.partial; parsed++; } catch { partial = true; } finally { input?.close(null); } } const events = []; for (const [key, file] of Object.entries(cached.files)) { - if (!file.events || typeof file.events !== 'object') { + const normalized = cachedFile(file, privateIdentity(id, key)); + if (!normalized) { delete cached.files[key]; partial = true; continue; } - for (const [eventKey, event] of Object.entries(file.events)) { - if (event.date < cutoff) - delete file.events[eventKey]; - else - events.push(event); + cached.files[key] = normalized; + partial ||= normalized.partial; + for (const [eventKey, event] of Object.entries(normalized.events)) { + const valid = sanitizedEvent(event, id, true); + if (!valid || event.date < cutoff) { + delete normalized.events[eventKey]; + partial ||= !valid; + } else { + if (eventKey !== valid.id) { + delete normalized.events[eventKey]; + partial = true; + } + normalized.events[valid.id] = valid; + events.push(valid); + } } + if (!currentKeys.has(key) && !Object.keys(normalized.events).length) + delete cached.files[key]; } + if (files.length) + cached.updatedAt = now; try { writeJson(destination, cached); } catch { partial = true; } - const result = {...section(detected ? partial ? 'partial' : 'ready' : 'unsupported', + const sourceAvailable = files.length > 0; + const hasCachedEvents = !sourceAvailable && events.length > 0; + const status = sourceAvailable ? partial ? 'partial' : 'ready' : hasCachedEvents ? 'stale' : 'unsupported'; + const result = {...section(status, partial ? 'Some local records could not be read. Totals may be incomplete.' : - detected ? 'Local records only; activity on other devices is not included.' : 'No local usage records found.'), - updatedAt: detected ? now : null, scope: 'local', source: `${id} local usage records`, + sourceAvailable ? 'Local records only; activity on other devices is not included.' : + hasCachedEvents ? 'Showing saved local activity; source records are currently unavailable.' : + detected ? 'No local usage records found yet.' : 'No local usage records found.'), + updatedAt: sourceAvailable ? now : hasCachedEvents ? numberValue(cached.updatedAt) : null, + scope: 'local', source: `${id} local usage records`, ...aggregateEvents(events, now), scannedFiles: parsed}; - if (!detected && !events.length) { + if (result.models.length > MAX_MODELS) { + result.models = result.models.slice(0, MAX_MODELS); + result.status = 'partial'; + result.message = 'Model totals were truncated to keep local history bounded.'; + } + if ((!sourceAvailable || partial) && !events.length) { result.days = []; result.models = []; result.period = null; diff --git a/src/services/http.js b/src/services/http.js index da349fd..034b712 100644 --- a/src/services/http.js +++ b/src/services/http.js @@ -1,17 +1,38 @@ import GLib from 'gi://GLib'; import Soup from 'gi://Soup?version=3.0'; -export async function requestJson(url, {method = 'GET', headers = {}, body = null, cancellable = null} = {}) { - const session = new Soup.Session({timeout: 12}); +const DEFAULT_MAX_RESPONSE_BYTES = 2 * 1024 * 1024; +const MAX_RESPONSE_BYTES = 16 * 1024 * 1024; + +export async function requestJson(url, {method = 'GET', headers = {}, body = null, cancellable = null, + maxResponseBytes = DEFAULT_MAX_RESPONSE_BYTES} = {}) { + if (typeof url !== 'string' || !url.startsWith('https://')) + throw new Error('Only HTTPS provider endpoints are allowed'); + if (!Number.isSafeInteger(maxResponseBytes) || maxResponseBytes <= 0 || maxResponseBytes > MAX_RESPONSE_BYTES) + throw new Error('HTTP response limit is outside the supported range'); + if (typeof method !== 'string' || !/^[A-Z]+$/.test(method) || !headers || + typeof headers !== 'object' || Array.isArray(headers)) + throw new Error('Invalid provider request options'); + for (const [key, value] of Object.entries(headers)) { + if (!/^[A-Za-z0-9-]+$/.test(key) || typeof value !== 'string' || /[\r\n]/.test(value)) + throw new Error('Invalid provider request header'); + } + const message = Soup.Message.new(method, url); - for (const [key, value] of Object.entries(headers)) - message.request_headers.append(key, value); - if (body !== null) - message.set_request_body_from_bytes('application/json', new GLib.Bytes(new TextEncoder().encode(JSON.stringify(body)))); + if (!message) + throw new Error('Invalid provider endpoint'); + const session = new Soup.Session({timeout: 12}); try { + for (const [key, value] of Object.entries(headers)) + message.request_headers.append(key, value); + if (body !== null) + message.set_request_body_from_bytes('application/json', + new GLib.Bytes(new TextEncoder().encode(JSON.stringify(body)))); const bytes = await new Promise((resolve, reject) => session.send_and_read_async(message, GLib.PRIORITY_DEFAULT, cancellable, (s, res) => { try { resolve(s.send_and_read_finish(res)); } catch (error) { reject(error); } })); + if (bytes.get_size() > maxResponseBytes) + throw new Error('Provider response exceeded the configured limit'); let data = null; try { data = JSON.parse(new TextDecoder().decode(bytes.toArray())); } catch { /* Status remains available for error handling. */ } return {status: message.status_code, data}; diff --git a/src/services/process.js b/src/services/process.js index 965fc10..15369bb 100644 --- a/src/services/process.js +++ b/src/services/process.js @@ -1,32 +1,94 @@ import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; -export async function runCommand(argv, {input = null, timeout = 15000, cancellable = null} = {}) { - const proc = Gio.Subprocess.new(argv, Gio.SubprocessFlags.STDOUT_PIPE | - Gio.SubprocessFlags.STDERR_SILENCE | (input === null ? 0 : Gio.SubprocessFlags.STDIN_PIPE)); +const DEFAULT_MAX_OUTPUT_BYTES = 8 * 1024 * 1024; +const MAX_TIMEOUT_MS = 10 * 60 * 1000; +const MAX_OUTPUT_BYTES = 64 * 1024 * 1024; + +export class ProcessError extends Error { + constructor(code, message) { + super(message); + this.name = 'ProcessError'; + this.code = code; + } +} + +function normalizeSpec(spec) { + const value = Array.isArray(spec) ? {argv: spec, environment: {}} : spec; + if (!value || !Array.isArray(value.argv) || !value.argv.length || + value.argv.some(argument => typeof argument !== 'string' || !argument)) + throw new ProcessError('INVALID_COMMAND', 'Command arguments must be non-empty strings'); + if (value.environment && (typeof value.environment !== 'object' || Array.isArray(value.environment))) + throw new ProcessError('INVALID_COMMAND', 'Command environment must be an object'); + for (const [name, entry] of Object.entries(value.environment ?? {})) { + if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name) || typeof entry !== 'string') + throw new ProcessError('INVALID_COMMAND', 'Command environment entries must be named strings'); + } + return {argv: value.argv, environment: value.environment ?? {}}; +} + +function spawn(spec, flags) { + const {argv, environment} = normalizeSpec(spec); + const launcher = new Gio.SubprocessLauncher({flags}); + for (const [name, value] of Object.entries(environment)) + launcher.setenv(name, value, true); + return launcher.spawnv(argv); +} + +function outputSize(value) { + return new TextEncoder().encode(value ?? '').length; +} + +export async function runCommand(spec, {input = null, timeout = 15000, cancellable = null, + maxOutputBytes = DEFAULT_MAX_OUTPUT_BYTES} = {}) { + if (!Number.isSafeInteger(timeout) || timeout <= 0 || timeout > MAX_TIMEOUT_MS || + !Number.isSafeInteger(maxOutputBytes) || maxOutputBytes <= 0 || maxOutputBytes > MAX_OUTPUT_BYTES) + throw new ProcessError('INVALID_OPTIONS', 'Process limits are outside the supported range'); + if (input !== null && typeof input !== 'string') + throw new ProcessError('INVALID_OPTIONS', 'Process input must be text or null'); + + const proc = spawn(spec, Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_SILENCE | + (input === null ? 0 : Gio.SubprocessFlags.STDIN_PIPE)); const local = new Gio.Cancellable(); - const externalId = cancellable?.connect(() => local.cancel()); - if (cancellable?.is_cancelled()) + let completed = false; + let cancellation = null; + const externalId = cancellable?.connect(() => { + cancellation = 'CANCELLED'; local.cancel(); + }); + if (cancellable?.is_cancelled()) { + cancellation = 'CANCELLED'; + local.cancel(); + } let timer = GLib.timeout_add(GLib.PRIORITY_DEFAULT, timeout, () => { timer = 0; + cancellation = 'TIMED_OUT'; local.cancel(); return GLib.SOURCE_REMOVE; }); try { - const text = await new Promise((resolve, reject) => { + const stdout = await new Promise((resolve, reject) => { proc.communicate_utf8_async(input, local, (p, result) => { try { - const [, stdout] = p.communicate_utf8_finish(result); - resolve(stdout); + const [, output] = p.communicate_utf8_finish(result); + completed = true; + resolve(output ?? ''); } catch (error) { reject(error); } }); }); if (!proc.get_successful()) - throw new Error('Command failed'); - return text; + throw new ProcessError('FAILED', 'Command exited unsuccessfully'); + if (outputSize(stdout) > maxOutputBytes) + throw new ProcessError('OUTPUT_LIMIT', 'Command output exceeded the configured limit'); + return stdout; + } catch (error) { + if (cancellation === 'TIMED_OUT') + throw new ProcessError('TIMED_OUT', 'Command timed out'); + if (cancellation === 'CANCELLED') + throw new ProcessError('CANCELLED', 'Command was cancelled'); + throw error; } finally { // Give managed collectors time to cancel and reap their own CLI child. if (local.is_cancelled()) { @@ -36,7 +98,8 @@ export async function runCommand(argv, {input = null, timeout = 15000, cancellab return GLib.SOURCE_REMOVE; })); } - proc.force_exit(); + if (!completed) + proc.force_exit(); proc.wait_async(null, (p, result) => { try { p.wait_finish(result); } catch { /* Reap best effort. */ } }); if (timer) GLib.source_remove(timer); @@ -46,17 +109,21 @@ export async function runCommand(argv, {input = null, timeout = 15000, cancellab } export class RpcClient { - constructor(argv, cancellable, timeout = 8000) { - this.proc = Gio.Subprocess.new(argv, Gio.SubprocessFlags.STDIN_PIPE | Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_SILENCE); + constructor(spec, cancellable, timeout = 8000) { + if (!Number.isSafeInteger(timeout) || timeout <= 0 || timeout > MAX_TIMEOUT_MS) + throw new ProcessError('INVALID_OPTIONS', 'RPC timeout is outside the supported range'); + this.proc = spawn(spec, Gio.SubprocessFlags.STDIN_PIPE | Gio.SubprocessFlags.STDOUT_PIPE | + Gio.SubprocessFlags.STDERR_SILENCE); this.input = new Gio.DataInputStream({base_stream: this.proc.get_stdout_pipe()}); this.cancellable = new Gio.Cancellable(); this.parent = cancellable; - this.parentId = cancellable?.connect(() => this.close()); this.timeout = timeout; this.nextId = 0; this.pending = new Map(); this.closed = false; - this.read(); + this.parentId = cancellable?.connect(() => this.close()) ?? 0; + if (!this.closed) + this.read(); } send(value) { @@ -74,7 +141,7 @@ export class RpcClient { const id = ++this.nextId; const timer = GLib.timeout_add(GLib.PRIORITY_DEFAULT, this.timeout, () => { this.pending.delete(id); - reject(new Error('RPC timeout')); + reject(new ProcessError('RPC_TIMEOUT', `RPC request timed out: ${method}`)); return GLib.SOURCE_REMOVE; }); this.pending.set(id, {resolve, reject, timer}); @@ -99,10 +166,14 @@ export class RpcClient { if (pending) { this.pending.delete(value.id); GLib.source_remove(pending.timer); - if (value.error) - pending.reject(new Error('RPC request unavailable')); - else + if (value.error) { + const message = String(value.error.message || 'RPC request unavailable').slice(0, 240); + const error = new ProcessError('RPC_ERROR', message); + error.rpcCode = value.error.code ?? null; + pending.reject(error); + } else { pending.resolve(value.result); + } } if (!this.closed) this.read(); @@ -118,7 +189,7 @@ export class RpcClient { this.closed = true; for (const pending of this.pending.values()) { GLib.source_remove(pending.timer); - pending.reject(new Error('RPC closed')); + pending.reject(new ProcessError('RPC_CLOSED', 'RPC connection closed')); } this.pending.clear(); this.cancellable.cancel(); diff --git a/src/services/usageService.js b/src/services/usageService.js index a0c10df..247c238 100644 --- a/src/services/usageService.js +++ b/src/services/usageService.js @@ -1,30 +1,35 @@ import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; -import {NAMES, mergeRecord, record, section, validateRecord} from '../core/usage.js'; +import {isProvider, mergeRecord, record, section, validateRecord} from '../core/usage.js'; import {ThresholdTracker} from '../core/notifications.js'; -import {findCommand, join, readJson, stateDirectory, writeJson} from './files.js'; +import {commandSpec} from './commands.js'; +import {join, readJson, stateDirectory, writeJson} from './files.js'; import {runCommand} from './process.js'; +const SCHEDULER_TICK_SECONDS = 15; +const MANUAL_REFRESH_COOLDOWN_MS = 2000; +const MAX_BACKOFF_SECONDS = 3600; + export class UsageService { constructor(settings, directory, changed, alerts) { - this.settings = settings; - this.directory = directory; - this.changed = changed; - this.alerts = alerts; - this.records = {}; - this.jobs = new Map(); - this.attempts = new Map(); - this.failures = new Map(); - this.thresholds = new ThresholdTracker(); - this.closed = false; - this.enabled = []; + this._settings = settings; + this._directory = directory; + this._changed = changed; + this._alerts = alerts; + this._records = {}; + this._jobs = new Map(); + this._attempts = new Map(); + this._failures = new Map(); + this._thresholds = new ThresholdTracker(); + this._closed = false; + this._enabled = []; this.configure(); - this.timer = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 15, () => { - for (const id of this.enabled) + this._timer = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, SCHEDULER_TICK_SECONDS, () => { + for (const id of this._enabled) this.refresh(id); return GLib.SOURCE_CONTINUE; }); - this.sleepId = Gio.DBus.system.signal_subscribe('org.freedesktop.login1', 'org.freedesktop.login1.Manager', + this._sleepId = Gio.DBus.system.signal_subscribe('org.freedesktop.login1', 'org.freedesktop.login1.Manager', 'PrepareForSleep', '/org/freedesktop/login1', null, Gio.DBusSignalFlags.NONE, (_connection, _sender, _path, _interface, _signal, params) => { if (!params.get_child_value(0).get_boolean()) @@ -32,84 +37,136 @@ export class UsageService { }); } + get enabledProviders() { + return [...this._enabled]; + } + + recordFor(id) { + return this._records[id] ?? null; + } + + isRefreshing(id) { + return this._jobs.has(id); + } + + _emitChanged() { + try { + this._changed(); + } catch (error) { + console.error('Freeby could not update its panel', error); + } + } + + _emitAlerts(alerts) { + try { + this._alerts(alerts); + } catch (error) { + console.error('Freeby could not display a usage alert', error); + } + } + configure() { - this.enabled = [...new Set(this.settings.get_strv('enabled-providers'))].filter(id => NAMES[id]); - for (const [id, job] of this.jobs) { - if (!this.enabled.includes(id)) { + if (this._closed) + return; + this._enabled = [...new Set(this._settings.get_strv('enabled-providers'))].filter(isProvider); + for (const [id, job] of this._jobs) { + if (!this._enabled.includes(id)) { job.cancel(); - this.jobs.delete(id); + this._jobs.delete(id); + } + } + for (const id of Object.keys(this._records)) { + if (!this._enabled.includes(id)) { + delete this._records[id]; + this._attempts.delete(id); + this._failures.delete(id); } } - for (const id of this.enabled) { - if (this.records[id]) + for (const id of this._enabled) { + if (this._records[id]) continue; try { const cached = validateRecord(readJson(join(stateDirectory(), `${id}.json`)), id); for (const key of ['limits', 'history']) { - if (cached[key].updatedAt) + if (['ready', 'partial', 'stale'].includes(cached[key].status)) cached[key] = {...cached[key], ...section('stale', 'Showing saved usage while refreshing.'), updatedAt: cached[key].updatedAt}; } - this.records[id] = cached; - } catch { this.records[id] = record(id); } + this._records[id] = cached; + } catch { + this._records[id] = record(id); + } } } refreshAll(force = false) { - for (const id of this.enabled) + for (const id of this._enabled) this.refresh(id, force); } async refresh(id, force = false) { - if (this.closed || !this.enabled.includes(id) || this.jobs.has(id)) + if (this._closed || !this._enabled.includes(id) || this._jobs.has(id)) return; const now = Date.now(); - const elapsed = now - (this.attempts.get(id) ?? 0); - const wait = Math.min(3600, this.settings.get_int('refresh-interval') * 2 ** (this.failures.get(id) ?? 0)); - if (elapsed >= 0 && elapsed < (force ? 15000 : wait * 1000)) + const elapsed = now - (this._attempts.get(id) ?? 0); + const wait = Math.min(MAX_BACKOFF_SECONDS, + this._settings.get_int('refresh-interval') * 2 ** (this._failures.get(id) ?? 0)); + if (elapsed >= 0 && elapsed < (force ? MANUAL_REFRESH_COOLDOWN_MS : wait * 1000)) return; const job = new Gio.Cancellable(); - this.jobs.set(id, job); - this.attempts.set(id, now); - this.changed(); + this._jobs.set(id, job); + this._attempts.set(id, now); + this._emitChanged(); try { - const stdout = await runCommand([findCommand('gjs'), '-m', join(this.directory, 'src', 'collector', 'main.js'), - id, String(this.settings.get_int('history-retention-days'))], {cancellable: job, timeout: 45000}); - if (this.closed || job.is_cancelled() || this.jobs.get(id) !== job) + const collector = commandSpec('gjs', ['-m', join(this._directory, 'src', 'collector', 'main.js'), + id, String(this._settings.get_int('history-retention-days'))]); + const stdout = await runCommand(collector, {cancellable: job, timeout: 45000}); + if (this._closed || job.is_cancelled() || this._jobs.get(id) !== job) return; const result = validateRecord(JSON.parse(stdout), id); const failed = ['unavailable', 'missing-auth'].includes(result.limits.status); - this.failures.set(id, failed ? Math.min(4, (this.failures.get(id) ?? 0) + 1) : 0); - const alerts = this.thresholds.update(result, this.settings.get_int('notification-threshold')); - this.records[id] = mergeRecord(this.records[id], result); - try { writeJson(join(stateDirectory(), `${id}.json`), this.records[id]); } catch { /* Cache failure must not hide current usage. */ } - if (this.settings.get_boolean('notifications-enabled') && alerts.length) - this.alerts(alerts); + this._failures.set(id, failed ? Math.min(4, (this._failures.get(id) ?? 0) + 1) : 0); + const alerts = this._thresholds.update(result, this._settings.get_int('notification-threshold')); + this._records[id] = mergeRecord(this._records[id], result); + try { + writeJson(join(stateDirectory(), `${id}.json`), this._records[id]); + } catch { + // Cache failure must not hide current usage. + } + if (this._settings.get_boolean('notifications-enabled') && alerts.length) + this._emitAlerts(alerts); } catch { - if (!this.closed && !job.is_cancelled()) { - const failed = record(id); - failed.limits = {...failed.limits, ...section('unavailable', 'Collection failed or timed out. Retry from the panel.')}; - failed.history = {...failed.history, ...section('unavailable', 'History could not be refreshed.')}; - this.records[id] = mergeRecord(this.records[id], failed); - this.failures.set(id, Math.min(4, (this.failures.get(id) ?? 0) + 1)); + if (!this._closed && !job.is_cancelled()) { + const failure = record(id); + failure.limits = {...failure.limits, + ...section('unavailable', 'Collection failed or timed out. Retry from the panel.')}; + failure.history = {...failure.history, ...section('unavailable', 'History could not be refreshed.')}; + this._records[id] = mergeRecord(this._records[id], failure); + this._failures.set(id, Math.min(4, (this._failures.get(id) ?? 0) + 1)); } } finally { - if (this.jobs.get(id) === job) - this.jobs.delete(id); - if (!this.closed) - this.changed(); + if (this._jobs.get(id) === job) + this._jobs.delete(id); + if (!this._closed) + this._emitChanged(); } } destroy() { - this.closed = true; - if (this.timer) - GLib.source_remove(this.timer); - if (this.sleepId) - Gio.DBus.system.signal_unsubscribe(this.sleepId); - for (const job of this.jobs.values()) + if (this._closed) + return; + this._closed = true; + if (this._timer) { + GLib.source_remove(this._timer); + this._timer = 0; + } + if (this._sleepId) { + Gio.DBus.system.signal_unsubscribe(this._sleepId); + this._sleepId = 0; + } + for (const job of this._jobs.values()) job.cancel(); - this.jobs.clear(); - this.changed = () => {}; - this.alerts = () => {}; + this._jobs.clear(); + this._changed = () => {}; + this._alerts = () => {}; } } diff --git a/src/ui/indicator.js b/src/ui/indicator.js index 5a0d798..b6bb7fe 100644 --- a/src/ui/indicator.js +++ b/src/ui/indicator.js @@ -6,6 +6,7 @@ import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js'; import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js'; import {NAMES, highestUsage, recentDates} from '../core/usage.js'; import {age, modelName, resetTime, tokens} from '../core/format.js'; +import {latestUpdate, periodDays, providerStatus} from './presentation.js'; import {button, label, meter, modelMeter, row} from './widgets.js'; const TAB_NAMES = {claude: 'Claude'}; @@ -63,112 +64,151 @@ export const FreebyIndicator = GObject.registerClass(class FreebyIndicator exten const focus = global.stage.get_key_focus(); const restoreName = focus && this._contentBox.contains(focus) ? focus.accessible_name : null; this._contentBox.destroy_all_children(); - const enabled = this._service?.enabled ?? []; + const enabled = this._service?.enabledProviders ?? []; const requested = this._settings.get_string('default-provider'); const id = enabled.includes(requested) ? requested : enabled[0]; - const record = this._service?.records[id]; + const record = this._service?.recordFor(id); const percent = highestUsage(record); - const busy = this._service?.jobs.has(id); + const busy = this._service?.isRefreshing(id) ?? false; + this._panelLabel.text = percent === null ? 'AI' : `AI · ${Math.round(percent)}%`; this._panelLabel.style_class = `freeby-panel-label${percent >= 100 ? ' freeby-danger' : percent >= 90 ? ' freeby-warning' : ''}`; this.accessible_name = `Freeby, ${record?.name ?? 'usage monitor'}${percent === null ? '' : `, ${Math.round(percent)} percent used`}`; + + this._renderHeader(id, record, busy); + if (enabled.length > 1) + this._renderSelector(enabled, id); + if (record) { + this._renderLimits(record.limits); + this._renderHistory(record.history); + } else { + this._message('Choose your providers in preferences to get started.'); + } + this._renderFooter(record, busy); + this._restoreFocus(restoreName); + } + + _renderHeader(id, record, busy) { const header = new St.BoxLayout({style_class: 'freeby-header', x_expand: true}); header.add_child(label(PROVIDER_MARKS[id] ?? 'AI', `freeby-provider-mark freeby-${id}-mark`)); const identity = new St.BoxLayout({vertical: true, style_class: 'freeby-identity', x_expand: true}); identity.add_child(label(record?.name ?? 'Freeby', 'freeby-title')); identity.add_child(label(record?.plan ? String(record.plan).toUpperCase() : 'USAGE MONITOR', 'freeby-caption')); header.add_child(identity); - const live = record && (['ready', 'partial', 'stale'].includes(record.limits.status) || - ['ready', 'partial', 'stale'].includes(record.history.status)); - const statusText = busy ? 'SYNC' : record?.limits.status === 'ready' ? 'LIVE' : live ? 'LOCAL' : 'SETUP'; + const statusText = providerStatus(record, busy); header.add_child(label(`● ${statusText}`, `freeby-status freeby-status-${statusText.toLowerCase()}`)); this._contentBox.add_child(header); - if (enabled.length > 1) { - const selector = new St.Widget({style_class: 'freeby-selector', x_expand: true, - layout_manager: new Clutter.BoxLayout({orientation: Clutter.Orientation.HORIZONTAL, - homogeneous: true, spacing: 8})}); - for (const provider of enabled) - selector.add_child(button(TAB_NAMES[provider] ?? NAMES[provider], () => this._settings.set_string('default-provider', provider), - {active: provider === id, name: `Show ${NAMES[provider]} usage`})); - this._contentBox.add_child(selector); + } + + _renderSelector(enabled, selected) { + const selector = new St.Widget({style_class: 'freeby-selector', x_expand: true, + layout_manager: new Clutter.BoxLayout({orientation: Clutter.Orientation.HORIZONTAL, + homogeneous: true, spacing: 8})}); + for (const provider of enabled) { + selector.add_child(button(TAB_NAMES[provider] ?? NAMES[provider], + () => this._settings.set_string('default-provider', provider), + {active: provider === selected, name: `Show ${NAMES[provider]} usage`})); } - if (!record) { - this._contentBox.add_child(label('Choose your providers in preferences to get started.', 'freeby-message')); - } else { - this.heading('LIMITS'); - for (const window of record.limits.windows) { - const box = new St.BoxLayout({vertical: true, style_class: 'freeby-window'}); - const value = window.unlimited ? 'Unlimited' : `${Math.round(window.usedPercent)}%`; - box.add_child(row(window.label, value)); - if (!window.unlimited) - box.add_child(meter(window.usedPercent / 100, `${window.label}: ${value} used`, - window.usedPercent >= 100 ? 'freeby-danger' : window.usedPercent >= 90 ? 'freeby-warning' : '')); + this._contentBox.add_child(selector); + } + + _renderLimits(limits) { + this._heading('LIMITS'); + for (const window of limits.windows) { + const box = new St.BoxLayout({vertical: true, style_class: 'freeby-window'}); + const value = window.unlimited ? 'Unlimited' : `${Math.round(window.usedPercent)}%`; + box.add_child(row(window.label, value)); + if (!window.unlimited) { + const level = window.usedPercent >= 100 ? 'freeby-danger' : + window.usedPercent >= 90 ? 'freeby-warning' : ''; + box.add_child(meter(window.usedPercent / 100, `${window.label}: ${value} used`, level)); box.add_child(label(resetTime(window.resetsAt), 'freeby-caption')); - this._contentBox.add_child(box); - } - if (record.limits.status !== 'ready') - this.message(record.limits.message || (record.limits.status === 'loading' ? 'Checking account limits…' : 'Limits unavailable')); - if (record.limits.status === 'stale') - this.message(`Saved limits · ${age(record.limits.updatedAt).toLowerCase()}`); - if (record.history.days.length) { - this.heading('TOKENS BY DAY'); - this._contentBox.add_child(label(`${record.history.scope === 'account' ? 'Account activity' : 'This device'} · ${record.history.period.start} – ${record.history.period.end}`, 'freeby-caption')); - const max = Math.max(1, ...record.history.days.map(day => day.total)); - const today = recentDates(Date.now(), 1)[0]; - for (const day of record.history.days) { - const line = new St.BoxLayout({style_class: 'freeby-day-row', x_expand: true}); - const dayName = day.date === today ? 'Today' : new Date(`${day.date}T12:00:00`).toLocaleDateString(undefined, {weekday: 'short'}); - line.add_child(label(dayName, 'freeby-day')); - if (day.date === today) - line.add_style_class_name('freeby-today'); - const bar = meter(day.total / max, `${day.date}: ${tokens(day.total)} tokens`); - bar.y_align = Clutter.ActorAlign.CENTER; - line.add_child(bar); - line.add_child(label(tokens(day.total), 'freeby-day-total')); - line.accessible_name = `${dayName}, ${day.total} tokens, ${day.sessions ?? 0} sessions`; - this._contentBox.add_child(line); - } - } - if (record.history.models.length) { - const modelScope = record.history.scope === 'account' ? 'ACCOUNT' : 'LOCAL'; - this.heading(`TOKENS BY MODEL · 7D ${modelScope}`); - const max = Math.max(1, ...record.history.models.map(model => model.total)); - const visibleModels = record.history.models.slice(0, 4); - for (const model of visibleModels) { - const displayName = modelName(model.model); - const accessible = `${displayName}, ${model.total} tokens. Input ${model.input}, output ${model.output}, cache ${model.cacheRead + model.cacheWrite}.`; - this._contentBox.add_child(modelMeter(displayName, tokens(model.total), model.total / max, accessible)); - } - if (record.history.models.length > visibleModels.length) - this._contentBox.add_child(label(`Top ${visibleModels.length} of ${record.history.models.length} models`, 'freeby-caption')); } - if (record.history.message && record.history.status !== 'ready') - this.message(record.history.message); + this._contentBox.add_child(box); } + if (limits.status !== 'ready') { + this._message(limits.message || + (limits.status === 'loading' ? 'Checking account limits…' : 'Limits unavailable')); + } + if (limits.status === 'stale') + this._message(`Saved limits · ${age(limits.updatedAt).toLowerCase()}`); + } + + _renderHistory(history) { + if (history.days.length) + this._renderDays(history); + if (history.models.length) + this._renderModels(history); + if (history.message && history.status !== 'ready') + this._message(history.message); + } + + _renderDays(history) { + this._heading('TOKENS BY DAY'); + const scope = history.scope === 'account' ? 'Account activity' : 'This device'; + this._contentBox.add_child(label(`${scope} · ${history.period.start} – ${history.period.end}`, 'freeby-caption')); + const max = Math.max(1, ...history.days.map(day => day.total)); + const today = recentDates(Date.now(), 1)[0]; + for (const day of history.days) { + const line = new St.BoxLayout({style_class: 'freeby-day-row', x_expand: true}); + const dayName = day.date === today ? 'Today' : + new Date(`${day.date}T12:00:00`).toLocaleDateString(undefined, {weekday: 'short'}); + line.add_child(label(dayName, 'freeby-day')); + if (day.date === today) + line.add_style_class_name('freeby-today'); + const bar = meter(day.total / max, `${day.date}: ${tokens(day.total)} tokens`); + bar.y_align = Clutter.ActorAlign.CENTER; + line.add_child(bar); + line.add_child(label(tokens(day.total), 'freeby-day-total')); + line.accessible_name = `${dayName}, ${day.total} tokens, ${day.sessions ?? 0} sessions`; + this._contentBox.add_child(line); + } + } + + _renderModels(history) { + const modelScope = history.scope === 'account' ? 'ACCOUNT' : 'LOCAL'; + const days = periodDays(history.period); + this._heading(`TOKENS BY MODEL${days ? ` · ${days}D` : ''} ${modelScope}`); + const max = Math.max(1, ...history.models.map(model => model.total)); + const visibleModels = history.models.slice(0, 4); + for (const model of visibleModels) { + const displayName = modelName(model.model); + const cache = model.cacheRead + model.cacheWrite; + const accessible = `${displayName}, ${model.total} tokens. Input ${model.input}, output ${model.output}, cache ${cache}.`; + this._contentBox.add_child(modelMeter(displayName, tokens(model.total), model.total / max, accessible)); + } + if (history.models.length > visibleModels.length) + this._contentBox.add_child(label(`Top ${visibleModels.length} of ${history.models.length} models`, 'freeby-caption')); + } + + _renderFooter(record, busy) { this._contentBox.add_child(new St.Widget({style_class: 'freeby-divider'})); - this._contentBox.add_child(label(busy ? 'Refreshing…' : age(record?.limits.updatedAt || record?.history.updatedAt), 'freeby-caption')); + this._contentBox.add_child(label(busy ? 'Refreshing…' : age(latestUpdate(record)), 'freeby-caption')); const actions = new St.Widget({style_class: 'freeby-actions', x_expand: true, layout_manager: new Clutter.BoxLayout({orientation: Clutter.Orientation.HORIZONTAL, homogeneous: true, spacing: 8})}); actions.add_child(button('Refresh', () => this._service?.refreshAll(true), {name: 'Refresh usage'})); actions.add_child(button('Preferences', () => { this.menu.close(); this._openPreferences(); })); this._contentBox.add_child(actions); - if (restoreName) { - const restore = actor => { - if (actor.can_focus && actor.accessible_name === restoreName) - actor.grab_key_focus(); - for (const child of actor.get_children()) - restore(child); - }; - restore(this._contentBox); - } } - heading(text) { + _restoreFocus(name) { + if (!name) + return; + const visit = actor => { + if (actor.can_focus && actor.accessible_name === name) + actor.grab_key_focus(); + for (const child of actor.get_children()) + visit(child); + }; + visit(this._contentBox); + } + + _heading(text) { this._contentBox.add_child(label(text, 'freeby-section-title')); } - message(text) { + _message(text) { const message = label(text, 'freeby-message'); message.clutter_text.line_wrap = true; this._contentBox.add_child(message); diff --git a/src/ui/presentation.js b/src/ui/presentation.js new file mode 100644 index 0000000..2981738 --- /dev/null +++ b/src/ui/presentation.js @@ -0,0 +1,25 @@ +const CURRENT_STATES = new Set(['ready', 'partial']); + +export function providerStatus(record, refreshing = false) { + if (CURRENT_STATES.has(record?.limits?.status)) + return 'LIVE'; + if (CURRENT_STATES.has(record?.history?.status)) + return 'LOCAL'; + if (record?.limits?.status === 'stale' || record?.history?.status === 'stale') + return 'CACHED'; + return refreshing ? 'SYNC' : 'SETUP'; +} + +export function latestUpdate(record) { + const timestamps = [record?.limits?.updatedAt, record?.history?.updatedAt] + .filter(value => Number.isFinite(value) && value > 0); + return timestamps.length ? Math.max(...timestamps) : null; +} + +export function periodDays(period) { + const start = Date.parse(`${period?.start ?? ''}T00:00:00Z`); + const end = Date.parse(`${period?.end ?? ''}T00:00:00Z`); + if (!Number.isFinite(start) || !Number.isFinite(end) || end < start) + return null; + return Math.round((end - start) / 86400000) + 1; +} diff --git a/src/ui/widgets.js b/src/ui/widgets.js index 1ed90af..4f34b03 100644 --- a/src/ui/widgets.js +++ b/src/ui/widgets.js @@ -1,5 +1,6 @@ import Atk from 'gi://Atk'; import Clutter from 'gi://Clutter'; +import Pango from 'gi://Pango'; import St from 'gi://St'; export function label(text, style = '', expand = false) { @@ -9,7 +10,9 @@ export function label(text, style = '', expand = false) { export function row(left, right, style = 'freeby-row') { const box = new St.BoxLayout({style_class: style, x_expand: true}); - box.add_child(label(left, '', true)); + const title = label(left, '', true); + title.clutter_text.ellipsize = Pango.EllipsizeMode.END; + box.add_child(title); box.add_child(label(right, 'freeby-number')); return box; } @@ -34,8 +37,9 @@ export function modelMeter(left, right, fraction, name) { accessible_role: Atk.Role.PROGRESS_BAR}); const fill = new St.Widget({style_class: 'freeby-model-fill'}); const content = new St.BoxLayout({style_class: 'freeby-model-content'}); - content.add_child(label(left, 'freeby-model-name')); - content.add_child(new St.Widget({x_expand: true})); + const title = label(left, 'freeby-model-name', true); + title.clutter_text.ellipsize = Pango.EllipsizeMode.END; + content.add_child(title); content.add_child(label(right, 'freeby-number')); track.add_child(fill); track.add_child(content); diff --git a/stylesheet.css b/stylesheet.css index 757e4fd..67b419c 100644 --- a/stylesheet.css +++ b/stylesheet.css @@ -10,6 +10,7 @@ .freeby-status-live { color: #57e389; border-color: rgba(87, 227, 137, 0.5); background-color: rgba(87, 227, 137, 0.08); } .freeby-status-sync { color: #99c1f1; border-color: rgba(153, 193, 241, 0.5); } .freeby-status-local { color: #f6d32d; border-color: rgba(246, 211, 45, 0.45); } +.freeby-status-cached { color: #c0bfbc; border-color: rgba(192, 191, 188, 0.4); } .freeby-selector { spacing: 8px; padding-bottom: 11px; } .freeby-actions { spacing: 8px; padding-top: 8px; } .freeby-button { padding: 7px 12px; min-height: 26px; border: 1px solid rgba(128, 128, 128, 0.38); border-radius: 1px; background-color: rgba(128, 128, 128, 0.06); } diff --git a/tests/integration/collector.js b/tests/integration/collector.js index 61d62e1..04821c3 100644 --- a/tests/integration/collector.js +++ b/tests/integration/collector.js @@ -4,8 +4,10 @@ import System from 'system'; import {scanHistory} from '../../src/services/history.js'; import {parseCodexEvent} from '../../src/providers/codex.js'; import {parseClaudeEvent} from '../../src/providers/claude.js'; -import {findCommand, nodeCli, readJson, writeJson, join} from '../../src/services/files.js'; -import {runCommand} from '../../src/services/process.js'; +import {commandSpec, findCommand} from '../../src/services/commands.js'; +import {readJson, writeJson, join} from '../../src/services/files.js'; +import {requestJson} from '../../src/services/http.js'; +import {RpcClient, runCommand} from '../../src/services/process.js'; function assert(value, message) { if (!value) @@ -29,12 +31,37 @@ GLib.file_set_contents(fixture, `${text}${event(180)}\n{"partial":`); const third = scanHistory('codex', [sessions], parseCodexEvent, {now, cachePath}); assert(third.days.at(-1).total === 180, 'append must count delta only'); const cache = readJson(cachePath); -assert(cache.version === 2, 'versioned cache'); +assert(cache.version === 4, 'versioned cache'); assert(!JSON.stringify(cache).includes('synthetic'), 'session identity must be sanitized in cache'); -writeJson(join(scratch, 'record.json'), {safe: true}); -assert(readJson(join(scratch, 'record.json')).safe, 'atomic JSON roundtrip'); +assert(third.days.at(-1).sessions === 1, 'resumed scans must retain one stable private session identity'); +const privateDirectory = join(scratch, 'private-state'); +GLib.mkdir_with_parents(privateDirectory, 0o755); +GLib.chmod(privateDirectory, 0o755); +const privateRecord = join(privateDirectory, 'record.json'); +writeJson(privateRecord, {safe: true}); +assert(readJson(privateRecord).safe, 'atomic JSON roundtrip'); +const directoryInfo = Gio.File.new_for_path(privateDirectory).query_info('unix::mode', Gio.FileQueryInfoFlags.NONE, null); +assert((directoryInfo.get_attribute_uint32('unix::mode') & 0o777) === 0o700, 'private JSON directory mode'); +const stateInfo = Gio.File.new_for_path(privateRecord).query_info('unix::mode', Gio.FileQueryInfoFlags.NONE, null); +assert((stateInfo.get_attribute_uint32('unix::mode') & 0o777) === 0o600, 'private JSON file mode'); print('PASS: GJS history initial scan, cached scan, append, partial line, and private JSON cache'); +const replacementRoot = join(scratch, 'replacement'); +GLib.mkdir_with_parents(replacementRoot, 0o700); +const replacement = join(replacementRoot, 'same-size.jsonl'); +const replacementCache = join(scratch, 'replacement-cache.json'); +GLib.file_set_contents(replacement, `${event(100)}\n`); +assert(scanHistory('codex', [replacementRoot], parseCodexEvent, {now, cachePath: replacementCache}) + .days.at(-1).total === 100, 'same-size baseline'); +GLib.usleep(2000); +GLib.file_set_contents(replacement, `${event(200)}\n`); +assert(scanHistory('codex', [replacementRoot], parseCodexEvent, {now, cachePath: replacementCache}) + .days.at(-1).total === 200, 'same-size replacement must invalidate the cache'); +Gio.File.new_for_path(replacement).delete(null); +const saved = scanHistory('codex', [replacementRoot], parseCodexEvent, {now: now + 1000, cachePath: replacementCache}); +assert(saved.status === 'stale' && saved.days.at(-1).total === 200, 'missing source must retain recent cached history'); +print('PASS: GJS history replacement detection and stale-source recovery'); + const claudeProjects = join(scratch, 'claude-projects'); GLib.mkdir_with_parents(claudeProjects, 0o700); const claudeLine = JSON.stringify({type: 'assistant', sessionId: 'claude-session', timestamp: '2026-09-06T11:00:00Z', @@ -52,17 +79,31 @@ for (const version of ['v20.12.0', 'v24.16.0']) { const bin = join(scratch, '.local', 'share', 'fnm', 'node-versions', version, 'installation', 'bin'); GLib.mkdir_with_parents(bin, 0o700); const command = join(bin, 'fixture-cli'); - GLib.file_set_contents(command, `#!/bin/sh\nprintf '${version}'\n`); + GLib.file_set_contents(command, '#!/usr/bin/env fixture-runtime\n'); GLib.chmod(command, 0o700); - const runtime = join(bin, 'node'); - GLib.file_set_contents(runtime, '#!/bin/sh\nexit 0\n'); + const runtime = join(bin, 'fixture-runtime'); + GLib.file_set_contents(runtime, `#!/bin/sh\nprintf '${version}'\n`); GLib.chmod(runtime, 0o700); } assert(findCommand('fixture-cli', {home: scratch, usePath: false}).includes('v24.16.0'), 'version-manager lookup must choose the newest installed runtime'); -const fixtureArgv = nodeCli('fixture-cli', ['status'], {home: scratch, usePath: false}); -assert(fixtureArgv[0].endsWith('/node') && fixtureArgv[1].endsWith('/fixture-cli') && fixtureArgv[2] === 'status', - 'version-managed Node CLI must use its sibling runtime'); +let invalidName = false; +try { findCommand('..'); } catch { invalidName = true; } +assert(invalidName, 'command discovery must reject path-like command names'); +const fixtureSpec = commandSpec('fixture-cli', ['status'], {home: scratch, usePath: false}); +assert(fixtureSpec.argv[0].endsWith('/fixture-cli') && fixtureSpec.argv[1] === 'status', + 'version-managed command must retain its native entry point'); +assert(fixtureSpec.environment.PATH.split(':')[0].endsWith('/v24.16.0/installation/bin'), + 'version-managed command directory must lead the child search path'); +const npmBin = join(scratch, '.npm-global', 'bin'); +GLib.mkdir_with_parents(npmBin, 0o700); +const runtimeCli = join(npmBin, 'runtime-cli'); +GLib.file_set_contents(runtimeCli, '#!/usr/bin/env fixture-runtime\n'); +GLib.chmod(runtimeCli, 0o700); +const runtimeSpec = commandSpec('runtime-cli', [], + {home: scratch, usePath: false, runtimes: ['fixture-runtime']}); +assert(runtimeSpec.environment.PATH.includes('/v24.16.0/installation/bin'), + 'user-local commands must receive a discovered runtime path'); print('PASS: GJS version-manager command discovery'); const loop = new GLib.MainLoop(null, false); @@ -70,9 +111,50 @@ let failed = false; (async () => { try { assert((await runCommand(['/bin/echo', 'fixture'])).trim() === 'fixture', 'subprocess output'); + assert((await runCommand(fixtureSpec)).trim() === 'v24.16.0', + 'command environment must resolve its sibling runtime'); + assert((await runCommand(runtimeSpec)).trim() === 'v24.16.0', + 'user-local command environment must resolve a version-managed runtime'); + let invalidEnvironment = false; + try { + await runCommand({argv: ['/bin/echo', 'fixture'], environment: {PATH: 10}}); + } catch (error) { + invalidEnvironment = error.code === 'INVALID_COMMAND'; + } + assert(invalidEnvironment, 'subprocess environments must contain only strings'); let timedOut = false; - try { await runCommand(['/bin/sleep', '5'], {timeout: 50}); } catch { timedOut = true; } + try { + await runCommand(['/bin/sleep', '5'], {timeout: 50}); + } catch (error) { + timedOut = error.code === 'TIMED_OUT'; + } assert(timedOut, 'subprocess timeout'); + let limited = false; + try { + await runCommand(['/bin/echo', 'too-large'], {maxOutputBytes: 3}); + } catch (error) { + limited = error.code === 'OUTPUT_LIMIT'; + } + assert(limited, 'subprocess output limit'); + const cancelled = new Gio.Cancellable(); + cancelled.cancel(); + let cancellationReported = false; + try { + await runCommand(['/bin/sleep', '5'], {cancellable: cancelled}); + } catch (error) { + cancellationReported = error.code === 'CANCELLED'; + } + assert(cancellationReported, 'pre-cancelled subprocess must report cancellation'); + const rpc = new RpcClient(['/bin/cat'], cancelled); + assert(rpc.closed, 'pre-cancelled RPC client must close during construction'); + rpc.close(); + let insecureEndpoint = false; + try { + await requestJson('http://example.invalid'); + } catch (error) { + insecureEndpoint = error.message.includes('HTTPS'); + } + assert(insecureEndpoint, 'provider requests must reject insecure endpoints before network access'); print('PASS: GJS subprocess communication, deadline and termination'); } catch (error) { printerr(error.message); diff --git a/tests/unit/codex.test.js b/tests/unit/codex.test.js index 261b45e..2b596a5 100644 --- a/tests/unit/codex.test.js +++ b/tests/unit/codex.test.js @@ -29,6 +29,29 @@ test('cumulative token deltas and cached input are counted once', () => { assert.equal(parseCodexEvent(tokenEvent(100, 20, 40, 120), state), null); const second = parseCodexEvent(tokenEvent(180, 35, 60, 215), state); assert.equal(second.input + second.output + second.cacheRead, 95); + const reset = parseCodexEvent(tokenEvent(20, 5, 8, 25), state); + assert.equal(reset.input + reset.output + reset.cacheRead, 25); + assert.equal(state.cumulativeEpoch, 1); + assert.deepEqual(Object.keys(state.cumulative).sort(), [ + 'cache_write_input_tokens', 'cached_input_tokens', 'input_tokens', 'output_tokens', 'total_tokens', + ]); +}); + +test('multi-bucket response falls back to the compatible quota view when empty', () => { + const result = codexLimits({rateLimitsByLimitId: {}, rateLimits: { + primary: {usedPercent: 12, windowDurationMins: 300}, + }}, 100); + assert.equal(result.status, 'ready'); + assert.equal(result.windows[0].label, 'Session · 5 hours'); +}); + +test('primary Codex limits remain first when the app server reorders buckets', () => { + const result = codexLimits({rateLimitsByLimitId: { + reserve: {limitName: 'Reserve', primary: {usedPercent: 1, windowDurationMins: 10080}}, + codex: {primary: {usedPercent: 2, windowDurationMins: 300}}, + }}); + assert.equal(result.windows[0].id, 'codex:primary'); + assert.equal(result.windows[1].label, 'Reserve · Weekly'); }); test('record parsing tracks model and stable session identity', () => { diff --git a/tests/unit/presentation.test.js b/tests/unit/presentation.test.js new file mode 100644 index 0000000..50df610 --- /dev/null +++ b/tests/unit/presentation.test.js @@ -0,0 +1,25 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {latestUpdate, periodDays, providerStatus} from '../../src/ui/presentation.js'; + +test('provider status distinguishes live, local, cached and setup data', () => { + const value = {limits: {status: 'ready'}, history: {status: 'ready'}}; + assert.equal(providerStatus(value), 'LIVE'); + value.limits.status = 'unavailable'; + assert.equal(providerStatus(value), 'LOCAL'); + value.history.status = 'stale'; + assert.equal(providerStatus(value), 'CACHED'); + value.history.status = 'unsupported'; + assert.equal(providerStatus(value), 'SETUP'); + assert.equal(providerStatus(value, true), 'SYNC'); + assert.equal(providerStatus(null, true), 'SYNC'); + value.limits.status = 'ready'; + assert.equal(providerStatus(value, true), 'LIVE'); +}); + +test('presentation helpers derive bounded period and freshness labels', () => { + assert.equal(latestUpdate({limits: {updatedAt: 10}, history: {updatedAt: 20}}), 20); + assert.equal(latestUpdate(null), null); + assert.equal(periodDays({start: '2026-08-31', end: '2026-09-06'}), 7); + assert.equal(periodDays({start: 'invalid', end: '2026-09-06'}), null); +}); diff --git a/tests/unit/usage.test.js b/tests/unit/usage.test.js index c8b3725..5e8954c 100644 --- a/tests/unit/usage.test.js +++ b/tests/unit/usage.test.js @@ -5,7 +5,7 @@ import {modelName, resetTime, tokens} from '../../src/core/format.js'; import {ThresholdTracker} from '../../src/core/notifications.js'; test('unknown metrics are not coerced to zero', () => { - for (const value of [null, undefined, '', false, -1, Infinity, 'bad']) + for (const value of [null, undefined, '', ' ', false, -1, Infinity, 'bad', [1], {value: 1}]) assert.equal(number(value), null); assert.equal(number(0), 0); assert.equal(windowUsage({id: 'plan', label: 'Plan', used: 0, limit: 0}), null); @@ -23,12 +23,49 @@ test('unknown, exhausted and unlimited windows remain distinct', () => { }); test('contract rejects invalid provider IDs and quota values', () => { + assert.throws(() => record('toString'), /Unsupported provider/); assert.throws(() => validateRecord(record('codex'), 'cursor')); const value = record('codex'); value.limits.windows.push({id: 'x', label: 'X', state: 'active', usedPercent: null}); assert.throws(() => validateRecord(value, 'codex')); }); +test('contract accepts complete records and rejects unsafe cached shapes', () => { + const value = record('codex'); + value.accountKey = 'a'.repeat(64); + value.capabilities = {limits: true, history: true, models: true}; + value.limits = {status: 'ready', message: '', updatedAt: 100, scope: 'account', source: 'synthetic limits', + windows: [windowUsage({id: 'weekly', label: 'Weekly', usedPercent: 20})]}; + value.history = {status: 'ready', message: '', updatedAt: 100, scope: 'local', + source: 'synthetic history', period: {start: '2026-09-05', end: '2026-09-06'}, + days: [{date: '2026-09-05', total: 0, sessions: 0, events: 0}, + {date: '2026-09-06', total: 10, sessions: 1, events: 1}], + models: [{model: 'test-model', total: 10, input: 6, output: 4, cacheRead: 0, cacheWrite: 0}]}; + assert.equal(validateRecord(value, 'codex'), value); + + const invalidDate = structuredClone(value); + invalidDate.history.days[1].date = '2026-99-99'; + assert.throws(() => validateRecord(invalidDate, 'codex'), /daily date/); + const invalidTotal = structuredClone(value); + invalidTotal.history.models[0].total = 11; + assert.throws(() => validateRecord(invalidTotal, 'codex'), /model totals/); + const rawAccount = structuredClone(value); + rawAccount.accountKey = 'user@example.com'; + assert.throws(() => validateRecord(rawAccount, 'codex'), /account key/); + const missingSource = structuredClone(value); + missingSource.limits.source = null; + assert.throws(() => validateRecord(missingSource, 'codex'), /limits source/); + const missingFreshness = structuredClone(value); + missingFreshness.history.updatedAt = 0; + assert.throws(() => validateRecord(missingFreshness, 'codex'), /history freshness/); + const unknownField = structuredClone(value); + unknownField.rawResponse = 'must not persist'; + assert.throws(() => validateRecord(unknownField, 'codex'), /record shape/); + const duplicateQuota = structuredClone(value); + duplicateQuota.limits.windows.push(structuredClone(duplicateQuota.limits.windows[0])); + assert.throws(() => validateRecord(duplicateQuota, 'codex'), /duplicate quota/); +}); + test('failure preserves last successful values as stale, account change discards them', () => { const old = record('codex'); old.accountKey = 'one'; @@ -43,17 +80,21 @@ test('failure preserves last successful values as stale, account change discards assert.equal(highestUsage(merged), null); next.accountKey = 'two'; assert.equal(mergeRecord(old, next).limits.windows.length, 0); + next.accountKey = null; + next.limits.status = 'missing-auth'; + assert.equal(mergeRecord(old, next).limits.windows.length, 0); + assert.equal(mergeRecord(old, next).accountKey, null); }); test('stale quota windows are discarded after their reset', () => { const old = record('claude'); old.limits = {status: 'ready', updatedAt: 100, windows: [ {id: 'expired', resetsAt: 1}, - {id: 'open', resetsAt: Date.now() + 60000}, + {id: 'open', resetsAt: 2000}, ]}; const next = record('claude'); next.limits.status = 'unavailable'; - const result = mergeRecord(old, next); + const result = mergeRecord(old, next, 1000); assert.equal(result.limits.status, 'stale'); assert.deepEqual(result.limits.windows.map(window => window.id), ['open']); }); diff --git a/tools/check.js b/tools/check.js index fc55be7..bc3b6fc 100644 --- a/tools/check.js +++ b/tools/check.js @@ -1,5 +1,5 @@ import {readFileSync, readdirSync, existsSync} from 'node:fs'; -import {resolve, dirname, join} from 'node:path'; +import {resolve, dirname, join, relative} from 'node:path'; import {execFileSync} from 'node:child_process'; function walk(directory) { @@ -7,16 +7,37 @@ function walk(directory) { ? walk(join(directory, entry.name)) : [join(directory, entry.name)]); } const files = ['extension.js', 'prefs.js', ...walk('src'), ...walk('tests'), ...walk('tools')].filter(file => file.endsWith('.js')); +for (const file of walk('src')) { + if (!file.endsWith('.js')) + throw new Error(`Unexpected non-runtime file under src/: ${file}`); +} +const allowedRuntimeImports = { + core: new Set(['core']), + providers: new Set(['core', 'providers']), + services: new Set(['core', 'services']), + collector: new Set(['core', 'providers', 'services', 'collector']), + ui: new Set(['core', 'ui']), +}; +const runtimeLayer = file => file.startsWith('src/') ? file.split('/')[1] : null; for (const file of files) { const text = readFileSync(file, 'utf8'); execFileSync(process.execPath, ['--check', file], {stdio: 'pipe'}); if (!text.endsWith('\n') || /[ \t]+$/m.test(text)) throw new Error(`${file}: trailing whitespace or missing final newline`); for (const [, target] of text.matchAll(/from ['"]([.][^'"]+)['"]/g)) { - if (!existsSync(resolve(dirname(file), target))) + const absolute = resolve(dirname(file), target); + if (!existsSync(absolute)) throw new Error(`${file}: missing import ${target}`); + const source = runtimeLayer(file); + const destination = runtimeLayer(relative('.', absolute)); + if (source && destination && !allowedRuntimeImports[source]?.has(destination)) + throw new Error(`${file}: ${source} modules cannot import the ${destination} layer`); } } +for (const entrypoint of ['extension.js', 'prefs.js']) { + if (readFileSync(entrypoint, 'utf8').split('\n').length > 120) + throw new Error(`${entrypoint}: GNOME entry points must remain thin`); +} const metadata = JSON.parse(readFileSync('metadata.json', 'utf8')); if (metadata.uuid !== 'freeby@kelvin.local') throw new Error('Extension identity changed without a migration'); @@ -31,6 +52,9 @@ for (const file of required) { if (!existsSync(file)) throw new Error(`Missing repository guidance: ${file}`); } +const schemas = walk('schemas').filter(file => file.endsWith('.xml')); +if (schemas.length !== 1 || schemas[0] !== 'schemas/org.gnome.shell.extensions.freeby.gschema.xml') + throw new Error('Unexpected extension schema input'); const obsolete = ['plan.md', 'indicator.js', 'src/providers/legacy.js', 'scripts/freeby.sh', 'scripts/copilot-setup.sh', 'tests/freeby.bats', 'docs/s1.png', 'docs/s2.png', 'reference-images']; for (const file of obsolete) { diff --git a/tools/package.py b/tools/package.py index f83e6cb..e86f04b 100644 --- a/tools/package.py +++ b/tools/package.py @@ -3,34 +3,64 @@ import argparse import json from pathlib import Path +import re import subprocess import tempfile import zipfile +def checked_file(source, path): + """Return a regular, repository-owned packaging input.""" + if path.is_symlink() or not path.is_file(): + raise RuntimeError(f'Invalid packaging input: {path}') + try: + path.resolve().relative_to(source) + except ValueError as error: + raise RuntimeError(f'Packaging input escapes the source tree: {path}') from error + return path + + def package(source, output): - metadata = json.loads((source / 'metadata.json').read_text()) - version = json.loads((source / 'package.json').read_text())['version'] + source = source.resolve() + metadata_file = checked_file(source, source / 'metadata.json') + package_file = checked_file(source, source / 'package.json') + metadata = json.loads(metadata_file.read_text()) + version = json.loads(package_file.read_text())['version'] + uuid = metadata.get('uuid') + if not isinstance(uuid, str) or not re.fullmatch(r'[A-Za-z0-9._-]+@[A-Za-z0-9._-]+', uuid): + raise RuntimeError('metadata.json contains an invalid extension UUID') + if not isinstance(version, str) or not re.fullmatch(r'[0-9A-Za-z][0-9A-Za-z.+-]*', version): + raise RuntimeError('package.json contains an invalid release version') files = [source / name for name in ('extension.js', 'prefs.js', 'metadata.json', 'stylesheet.css', 'LICENSE')] files.extend(sorted((source / 'src').rglob('*.js'))) - files.extend(sorted((source / 'schemas').glob('*.xml'))) + files.append(source / 'schemas' / 'org.gnome.shell.extensions.freeby.gschema.xml') + files = [checked_file(source, file) for file in files] output.mkdir(parents=True, exist_ok=True) - archive = output / f'{metadata["uuid"]}-{version}.zip' + archive = output / f'{uuid}-{version}.zip' + if archive.is_symlink(): + raise RuntimeError(f'Refusing to overwrite archive symlink: {archive}') with tempfile.TemporaryDirectory(prefix='freeby-schemas-') as scratch: subprocess.run(['glib-compile-schemas', '--strict', '--targetdir', scratch, str(source / 'schemas')], check=True) payloads = [(file.relative_to(source).as_posix(), file.read_bytes()) for file in files] payloads.append(('schemas/gschemas.compiled', (Path(scratch) / 'gschemas.compiled').read_bytes())) - with zipfile.ZipFile(archive, 'w', compression=zipfile.ZIP_DEFLATED) as target: + expected = {name for name, _payload in payloads} + if len(expected) != len(payloads): + raise RuntimeError('Duplicate paths in extension package') + with zipfile.ZipFile(archive, 'w', compression=zipfile.ZIP_DEFLATED, compresslevel=9) as target: for name, payload in sorted(payloads): info = zipfile.ZipInfo(name, date_time=(2020, 1, 1, 0, 0, 0)) + info.create_system = 3 info.compress_type = zipfile.ZIP_DEFLATED info.external_attr = 0o100644 << 16 target.writestr(info, payload) with zipfile.ZipFile(archive) as check: - assert check.testzip() is None - assert 'schemas/gschemas.compiled' in check.namelist() - assert all(not name.startswith(('.AGENTS/', '.github/', 'docs/', 'tests/', 'tools/')) - for name in check.namelist()) + names = check.namelist() + if check.testzip() is not None: + raise RuntimeError('Extension archive failed its integrity check') + if set(names) != expected: + raise RuntimeError('Extension archive contents differ from the runtime allowlist') + if any(name.startswith(('.AGENTS/', '.github/', 'docs/', 'tests/', 'tools/')) for name in names): + raise RuntimeError('Development-only content entered the extension archive') print(archive.resolve()) return archive diff --git a/tools/smoke-shell.py b/tools/smoke-shell.py index a63b56c..d361e8d 100644 --- a/tools/smoke-shell.py +++ b/tools/smoke-shell.py @@ -44,9 +44,10 @@ def record(provider, name, plan, percentages, models): days = [{'date': day, 'total': (index + 1) * 125_000, 'sessions': index + 1, 'events': index + 2} for index, day in enumerate(dates)] return { - 'schemaVersion': 1, 'id': provider, 'name': name, 'plan': plan, 'accountKey': 'smoke-fixture', + 'schemaVersion': 2, 'id': provider, 'name': name, 'plan': plan, 'accountKey': '0' * 64, 'capabilities': {'limits': True, 'history': True, 'models': True}, 'limits': {'status': 'ready', 'message': '', 'updatedAt': now, 'scope': 'account', + 'source': 'Synthetic smoke fixture', 'windows': [ {'id': f'{provider}:session', 'label': 'Session · 5 hours', 'usedPercent': percentages[0], 'used': None, 'limit': None, 'unit': 'percent', 'unlimited': False, 'state': 'active', From e69daed324b15ff051750f62ca9c1333dfbb0555 Mon Sep 17 00:00:00 2001 From: kcnewman Date: Sun, 6 Sep 2026 19:15:48 +0000 Subject: [PATCH 24/44] fix: compact the usage popup --- .AGENTS/plans/roadmap.md | 20 ++++++---- CHANGELOG.md | 14 ++++--- src/ui/indicator.js | 68 +++++++++++++++++++++------------ src/ui/presentation.js | 16 ++++++++ src/ui/widgets.js | 31 +++++++++++++++ stylesheet.css | 65 +++++++++++++++++-------------- tests/unit/presentation.test.js | 16 +++++++- 7 files changed, 164 insertions(+), 66 deletions(-) diff --git a/.AGENTS/plans/roadmap.md b/.AGENTS/plans/roadmap.md index 39300af..e74ae39 100644 --- a/.AGENTS/plans/roadmap.md +++ b/.AGENTS/plans/roadmap.md @@ -1,7 +1,7 @@ # Freeby usage monitor roadmap -Updated: 2026-09-06. Status: post-alpha.2 interface/runtime and production -hardening complete; Cursor remains the next alpha.3 milestone. +Updated: 2026-09-06. Status: post-alpha.2 compact interface/runtime and +production hardening complete; Cursor remains the next alpha.3 milestone. ## Agreed direction @@ -168,10 +168,13 @@ re-enabled without extension errors in an isolated GNOME Shell 50 session. ### Interface and local-runtime correction checkpoint -- [x] Match the supplied Omarchy hierarchy with a compact monospaced panel, - visible data status, equal provider tabs, and left-anchored usage graphics. -- [x] Keep the common view within the monitor work area while retaining - automatic scrolling as a small-display and overflow fallback. +- [x] Match the supplied Omarchy hierarchy with a compact, theme-integrated + panel, visible data status, equal provider tabs, and left-anchored graphics. +- [x] Keep limits and freshness in the default view, move daily/model activity + behind an accessible disclosure, and retain scrolling only as a constrained + display or expanded-content fallback. +- [x] Inherit the user's Shell font and accent, place the subtle accent on the + popup perimeter, and consolidate actions into a compact footer. - [x] Detect user-local and version-managed Codex installations from GNOME Shell's minimal environment and use the installation's matching Node runtime. - [x] Exercise all primary popup widgets with synthetic records and reject GJS @@ -182,7 +185,10 @@ version-manager selection and sibling-runtime launch coverage. A sanitized live probe with Codex CLI 0.145.0, an fnm-only binary path, no `/usr/bin/node`, and a GNOME-equivalent minimal `PATH` returned the signed-in Plus plan with three live quota windows. The populated two-provider popup loaded, disabled, and re-enabled -without extension GJS criticals in a private GNOME Shell 50 session. +without extension GJS criticals in a private GNOME Shell 50 session. A follow-up +compactness pass retained that lifecycle coverage, added deterministic activity +summary tests, and smoke-tested the exact packaged archive with the theme-derived +perimeter and collapsed details structure. ### Runtime and contract hardening checkpoint diff --git a/CHANGELOG.md b/CHANGELOG.md index 8758b9b..64b63a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,11 +16,15 @@ while version 2 is developed on the `dev` branch. status, and footer paths backed by unit-testable presentation decisions. - Hardened deterministic packaging to reject symlinked or out-of-tree inputs and verify the archive against its exact runtime allowlist. -- Reworked the popup into a compact, sharp-edged, monospaced usage panel based - on the supplied Omarchy references, with equal-width `Claude`/`Codex` tabs, - visible provider status, and filled model-usage rows. -- Reduced vertical density so the normal two-provider view fits the monitor; - scrolling remains an automatic fallback for smaller displays or extra data. +- Reworked the popup into a compact, theme-integrated usage panel based on the + supplied Omarchy references, with equal-width `Claude`/`Codex` tabs, visible + provider status, and filled model-usage rows. +- Inherit the user's Shell font and accent color, with a restrained accent on + the popup's actual perimeter instead of a nested hard-coded blue frame. +- Keep limits and freshness visible in the default view while placing seven-day + and model activity behind an accessible disclosure control; scrolling remains + an automatic fallback for the expanded view and smaller displays. +- Consolidate freshness, refresh, and settings into a single compact footer. - Consolidated agent rules, the active roadmap, archived planning notes, and visual implementation references under `.AGENTS/`, with a minimal root `AGENTS.md` discovery entrypoint. diff --git a/src/ui/indicator.js b/src/ui/indicator.js index b6bb7fe..fdb1a02 100644 --- a/src/ui/indicator.js +++ b/src/ui/indicator.js @@ -6,8 +6,8 @@ import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js'; import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js'; import {NAMES, highestUsage, recentDates} from '../core/usage.js'; import {age, modelName, resetTime, tokens} from '../core/format.js'; -import {latestUpdate, periodDays, providerStatus} from './presentation.js'; -import {button, label, meter, modelMeter, row} from './widgets.js'; +import {historyOverview, latestUpdate, periodDays, providerStatus} from './presentation.js'; +import {actionButton, button, disclosureButton, label, meter, modelMeter, row} from './widgets.js'; const TAB_NAMES = {claude: 'Claude'}; const PROVIDER_MARKS = {codex: '>_', claude: '✦', cursor: '⌁', copilot: '◆'}; @@ -18,6 +18,7 @@ export const FreebyIndicator = GObject.registerClass(class FreebyIndicator exten this._settings = settings; this._openPreferences = openPreferences; this._service = null; + this._detailsExpanded = false; this._panelLabel = label('AI', 'freeby-panel-label'); this.add_child(this._panelLabel); this.menu.actor.add_style_class_name('freeby-menu'); @@ -41,6 +42,8 @@ export const FreebyIndicator = GObject.registerClass(class FreebyIndicator exten const adjustment = this._scroll.vscroll?.adjustment; if (adjustment) adjustment.value = 0; + } else { + this._detailsExpanded = false; } }); this.render(); @@ -49,8 +52,10 @@ export const FreebyIndicator = GObject.registerClass(class FreebyIndicator exten resize() { const monitor = Main.layoutManager.findMonitorForActor(this) ?? Main.layoutManager.primaryMonitor; const scale = St.ThemeContext.get_for_stage(global.stage).scale_factor; - this._scroll.style = `max-height: ${Math.max(180, Math.floor(monitor.height / scale) - 72)}px;`; - this._contentBox.style = `width: ${Math.min(404, Math.floor(monitor.width / scale) - 48)}px;`; + const availableHeight = Math.max(240, Math.floor(monitor.height / scale) - 96); + const availableWidth = Math.max(240, Math.floor(monitor.width / scale) - 32); + this._scroll.style = `max-height: ${Math.min(600, availableHeight)}px;`; + this._contentBox.style = `width: ${Math.min(348, availableWidth)}px;`; } attach(service) { @@ -135,18 +140,33 @@ export const FreebyIndicator = GObject.registerClass(class FreebyIndicator exten } _renderHistory(history) { - if (history.days.length) - this._renderDays(history); - if (history.models.length) - this._renderModels(history); + const overview = historyOverview(history); + if (overview) { + const summary = [overview.days ? `${overview.days} days` : null, + tokens(overview.total), overview.scope].filter(Boolean).join(' · '); + this._contentBox.add_child(disclosureButton(summary, this._detailsExpanded, () => { + this._detailsExpanded = !this._detailsExpanded; + this.render(); + })); + + // Construct the complete view even while collapsed so lifecycle smoke + // coverage continues to exercise every primary widget. + const details = new St.BoxLayout({vertical: true, style_class: 'freeby-details', + x_expand: true, visible: this._detailsExpanded}); + if (history.days.length) + this._renderDays(history, details); + if (history.models.length) + this._renderModels(history, details); + this._contentBox.add_child(details); + } if (history.message && history.status !== 'ready') this._message(history.message); } - _renderDays(history) { - this._heading('TOKENS BY DAY'); + _renderDays(history, parent = this._contentBox) { + this._heading('TOKENS BY DAY', parent); const scope = history.scope === 'account' ? 'Account activity' : 'This device'; - this._contentBox.add_child(label(`${scope} · ${history.period.start} – ${history.period.end}`, 'freeby-caption')); + parent.add_child(label(`${scope} · ${history.period.start} – ${history.period.end}`, 'freeby-caption')); const max = Math.max(1, ...history.days.map(day => day.total)); const today = recentDates(Date.now(), 1)[0]; for (const day of history.days) { @@ -161,34 +181,32 @@ export const FreebyIndicator = GObject.registerClass(class FreebyIndicator exten line.add_child(bar); line.add_child(label(tokens(day.total), 'freeby-day-total')); line.accessible_name = `${dayName}, ${day.total} tokens, ${day.sessions ?? 0} sessions`; - this._contentBox.add_child(line); + parent.add_child(line); } } - _renderModels(history) { + _renderModels(history, parent = this._contentBox) { const modelScope = history.scope === 'account' ? 'ACCOUNT' : 'LOCAL'; const days = periodDays(history.period); - this._heading(`TOKENS BY MODEL${days ? ` · ${days}D` : ''} ${modelScope}`); + this._heading(`TOKENS BY MODEL${days ? ` · ${days}D` : ''} ${modelScope}`, parent); const max = Math.max(1, ...history.models.map(model => model.total)); const visibleModels = history.models.slice(0, 4); for (const model of visibleModels) { const displayName = modelName(model.model); const cache = model.cacheRead + model.cacheWrite; const accessible = `${displayName}, ${model.total} tokens. Input ${model.input}, output ${model.output}, cache ${cache}.`; - this._contentBox.add_child(modelMeter(displayName, tokens(model.total), model.total / max, accessible)); + parent.add_child(modelMeter(displayName, tokens(model.total), model.total / max, accessible)); } if (history.models.length > visibleModels.length) - this._contentBox.add_child(label(`Top ${visibleModels.length} of ${history.models.length} models`, 'freeby-caption')); + parent.add_child(label(`Top ${visibleModels.length} of ${history.models.length} models`, 'freeby-caption')); } _renderFooter(record, busy) { - this._contentBox.add_child(new St.Widget({style_class: 'freeby-divider'})); - this._contentBox.add_child(label(busy ? 'Refreshing…' : age(latestUpdate(record)), 'freeby-caption')); - const actions = new St.Widget({style_class: 'freeby-actions', x_expand: true, - layout_manager: new Clutter.BoxLayout({orientation: Clutter.Orientation.HORIZONTAL, - homogeneous: true, spacing: 8})}); - actions.add_child(button('Refresh', () => this._service?.refreshAll(true), {name: 'Refresh usage'})); - actions.add_child(button('Preferences', () => { this.menu.close(); this._openPreferences(); })); + const actions = new St.BoxLayout({style_class: 'freeby-actions', x_expand: true}); + actions.add_child(label(busy ? 'Refreshing…' : age(latestUpdate(record)), 'freeby-caption', true)); + actions.add_child(actionButton('Refresh', () => this._service?.refreshAll(true), 'Refresh usage')); + actions.add_child(actionButton('Settings', () => { this.menu.close(); this._openPreferences(); }, + 'Open extension settings')); this._contentBox.add_child(actions); } @@ -204,8 +222,8 @@ export const FreebyIndicator = GObject.registerClass(class FreebyIndicator exten visit(this._contentBox); } - _heading(text) { - this._contentBox.add_child(label(text, 'freeby-section-title')); + _heading(text, parent = this._contentBox) { + parent.add_child(label(text, 'freeby-section-title')); } _message(text) { diff --git a/src/ui/presentation.js b/src/ui/presentation.js index 2981738..fc9424d 100644 --- a/src/ui/presentation.js +++ b/src/ui/presentation.js @@ -23,3 +23,19 @@ export function periodDays(period) { return null; return Math.round((end - start) / 86400000) + 1; } + +export function historyOverview(history) { + const days = Array.isArray(history?.days) ? history.days : []; + const models = Array.isArray(history?.models) ? history.models : []; + const values = days.length ? days : models; + if (!values.length) + return null; + + const total = values.reduce((sum, item) => sum + + (Number.isFinite(item?.total) ? item.total : 0), 0); + return { + days: periodDays(history.period), + scope: history.scope === 'account' ? 'account' : 'local', + total, + }; +} diff --git a/src/ui/widgets.js b/src/ui/widgets.js index 4f34b03..7ba6db8 100644 --- a/src/ui/widgets.js +++ b/src/ui/widgets.js @@ -62,3 +62,34 @@ export function button(text, callback, {active = false, name = text} = {}) { actor.connect('clicked', callback); return actor; } + +export function disclosureButton(summary, expanded, callback) { + const actor = new St.Button({ + accessible_name: 'Activity details', + accessible_role: Atk.Role.TOGGLE_BUTTON, + can_focus: true, + checked: expanded, + reactive: true, + style_class: 'freeby-disclosure', + toggle_mode: true, + track_hover: true, + x_expand: true, + }); + const content = new St.BoxLayout({style_class: 'freeby-disclosure-content', x_expand: true}); + content.add_child(label('Activity', 'freeby-disclosure-title', true)); + content.add_child(label(summary, 'freeby-disclosure-summary')); + content.add_child(new St.Icon({ + icon_name: expanded ? 'pan-up-symbolic' : 'pan-down-symbolic', + style_class: 'freeby-disclosure-icon', + })); + actor.set_child(content); + actor.connect('clicked', callback); + return actor; +} + +export function actionButton(text, callback, name = text) { + const actor = button(text, callback, {name}); + actor.x_expand = false; + actor.add_style_class_name('freeby-action-button'); + return actor; +} diff --git a/stylesheet.css b/stylesheet.css index 67b419c..9a66740 100644 --- a/stylesheet.css +++ b/stylesheet.css @@ -1,39 +1,48 @@ -.freeby-panel-label { font-weight: bold; font-size: 10pt; color: #c6dcff; } -.freeby-content { padding: 12px 16px 14px; spacing: 0; border: 2px solid rgba(98, 160, 234, 0.86); border-radius: 2px; background-color: rgba(20, 24, 36, 0.24); font-family: monospace; } -.freeby-header { spacing: 10px; padding: 2px 0 11px; } +.freeby-menu .popup-menu-content { border-color: st-transparentize(st-lighten(-st-accent-color, 20%), 0.58); } +.freeby-panel-label { font-weight: bold; font-size: 10pt; } +.freeby-content { padding: 9px 12px 10px; spacing: 0; } +.freeby-header { spacing: 8px; padding: 1px 0 8px; } .freeby-identity { spacing: 1px; } -.freeby-provider-mark { min-width: 34px; min-height: 34px; padding: 4px; border: 1px solid rgba(98, 160, 234, 0.75); border-radius: 2px; background-color: rgba(98, 160, 234, 0.14); color: #99c1f1; font-family: monospace; font-size: 12pt; font-weight: bold; text-align: center; } -.freeby-claude-mark { border-color: rgba(255, 145, 90, 0.8); background-color: rgba(255, 145, 90, 0.14); color: #ff9f6b; } -.freeby-title { font-size: 16pt; font-weight: bold; } -.freeby-caption { font-size: 8.5pt; opacity: 0.67; font-family: monospace; } -.freeby-status { padding: 4px 7px; border: 1px solid rgba(128, 128, 128, 0.35); border-radius: 2px; font-family: monospace; font-size: 8pt; opacity: 0.82; } +.freeby-provider-mark { min-width: 28px; min-height: 28px; padding: 3px; border-radius: 6px; background-color: st-transparentize(-st-accent-color, 0.88); color: st-lighten(-st-accent-color, 20%); font-size: 11pt; font-weight: bold; text-align: center; } +.freeby-claude-mark { background-color: rgba(255, 145, 90, 0.12); color: #ff9f6b; } +.freeby-title { font-size: 13pt; font-weight: bold; } +.freeby-caption { font-size: 8.5pt; opacity: 0.66; } +.freeby-status { padding: 3px 6px; border: 1px solid rgba(128, 128, 128, 0.30); border-radius: 5px; font-size: 8pt; opacity: 0.84; } .freeby-status-live { color: #57e389; border-color: rgba(87, 227, 137, 0.5); background-color: rgba(87, 227, 137, 0.08); } -.freeby-status-sync { color: #99c1f1; border-color: rgba(153, 193, 241, 0.5); } +.freeby-status-sync { color: st-lighten(-st-accent-color, 20%); border-color: st-transparentize(-st-accent-color, 0.5); } .freeby-status-local { color: #f6d32d; border-color: rgba(246, 211, 45, 0.45); } .freeby-status-cached { color: #c0bfbc; border-color: rgba(192, 191, 188, 0.4); } -.freeby-selector { spacing: 8px; padding-bottom: 11px; } -.freeby-actions { spacing: 8px; padding-top: 8px; } -.freeby-button { padding: 7px 12px; min-height: 26px; border: 1px solid rgba(128, 128, 128, 0.38); border-radius: 1px; background-color: rgba(128, 128, 128, 0.06); } -.freeby-button:hover, .freeby-button:focus { border-color: rgba(98, 160, 234, 0.75); background-color: rgba(98, 160, 234, 0.13); } -.freeby-button:checked { border-color: #62a0ea; background-color: rgba(98, 160, 234, 0.2); color: #c6dcff; font-weight: bold; } +.freeby-selector { spacing: 6px; padding-bottom: 8px; } +.freeby-actions { spacing: 6px; padding-top: 8px; margin-top: 5px; border-top: 1px solid rgba(128, 128, 128, 0.20); } +.freeby-button { padding: 5px 10px; min-height: 24px; border: 1px solid rgba(128, 128, 128, 0.28); border-radius: 6px; background-color: rgba(128, 128, 128, 0.05); } +.freeby-button:hover, .freeby-button:focus { border-color: st-transparentize(st-lighten(-st-accent-color, 20%), 0.35); background-color: st-transparentize(-st-accent-color, 0.88); } +.freeby-button:checked { border-color: st-transparentize(st-lighten(-st-accent-color, 20%), 0.25); background-color: st-transparentize(-st-accent-color, 0.82); font-weight: bold; } .freeby-button:focus { box-shadow: inset 0 0 0 2px -st-accent-color; } -.freeby-section-title { font-size: 8.5pt; font-family: monospace; font-weight: bold; opacity: 0.78; border-top: 1px solid rgba(128, 128, 128, 0.22); padding: 11px 0 7px; } +.freeby-action-button { padding: 3px 8px; min-height: 22px; font-size: 8.5pt; } +.freeby-section-title { font-size: 8.5pt; font-weight: bold; opacity: 0.76; border-top: 1px solid rgba(128, 128, 128, 0.20); padding: 8px 0 5px; } .freeby-row { spacing: 10px; } -.freeby-number { font-family: monospace; color: #d6e6ff; } -.freeby-window { spacing: 4px; padding-bottom: 8px; } -.freeby-track { height: 5px; min-width: 20px; border-radius: 3px; background-color: rgba(128, 128, 128, 0.24); } -.freeby-fill { background-color: #8fb9e8; border-radius: 3px; } +.freeby-number { opacity: 0.9; } +.freeby-window { spacing: 3px; padding-bottom: 6px; } +.freeby-track { height: 4px; min-width: 20px; border-radius: 3px; background-color: rgba(128, 128, 128, 0.22); } +.freeby-fill { background-color: st-lighten(-st-accent-color, 20%); border-radius: 3px; } .freeby-warning { color: #e5a50a; } .freeby-danger { color: #e01b24; } .freeby-warning .freeby-fill { background-color: #e5a50a; } .freeby-danger .freeby-fill { background-color: #e01b24; } -.freeby-day-row { spacing: 10px; min-height: 27px; } -.freeby-day { min-width: 42px; font-size: 9.5pt; opacity: 0.72; } -.freeby-today .freeby-day { color: #c6dcff; opacity: 1; font-weight: bold; } -.freeby-day-total { min-width: 58px; text-align: right; font-family: monospace; font-size: 9pt; color: #d6e6ff; } -.freeby-model-meter { min-height: 32px; margin-bottom: 6px; background-color: rgba(128, 128, 160, 0.16); } -.freeby-model-fill { background-color: rgba(117, 167, 220, 0.27); } -.freeby-model-content { padding: 7px 10px; } -.freeby-model-name { color: #c6dcff; } +.freeby-disclosure { margin-top: 2px; padding: 6px 8px; min-height: 26px; border: 1px solid rgba(128, 128, 128, 0.22); border-radius: 6px; background-color: rgba(128, 128, 128, 0.04); } +.freeby-disclosure:hover, .freeby-disclosure:focus { border-color: st-transparentize(st-lighten(-st-accent-color, 20%), 0.40); background-color: st-transparentize(-st-accent-color, 0.90); } +.freeby-disclosure:focus { box-shadow: inset 0 0 0 2px -st-accent-color; } +.freeby-disclosure-content { spacing: 6px; } +.freeby-disclosure-title { font-weight: bold; } +.freeby-disclosure-summary { font-size: 8.5pt; opacity: 0.62; } +.freeby-disclosure-icon { icon-size: 12px; opacity: 0.72; } +.freeby-details { spacing: 0; padding-top: 2px; } +.freeby-day-row { spacing: 8px; min-height: 23px; } +.freeby-day { min-width: 38px; font-size: 9pt; opacity: 0.70; } +.freeby-today .freeby-day { color: st-lighten(-st-accent-color, 20%); opacity: 1; font-weight: bold; } +.freeby-day-total { min-width: 52px; text-align: right; font-size: 8.5pt; opacity: 0.9; } +.freeby-model-meter { min-height: 28px; margin-bottom: 5px; background-color: rgba(128, 128, 160, 0.13); } +.freeby-model-fill { background-color: st-transparentize(-st-accent-color, 0.70); } +.freeby-model-content { padding: 5px 8px; } +.freeby-model-name { opacity: 0.92; } .freeby-message { margin: 0 0 7px; padding: 8px 10px; border-left: 2px solid #f6d32d; border-radius: 3px; background-color: rgba(246, 211, 45, 0.06); font-size: 9pt; opacity: 0.85; } -.freeby-divider { height: 1px; margin-top: 4px; margin-bottom: 8px; background-color: rgba(128, 128, 128, 0.24); } diff --git a/tests/unit/presentation.test.js b/tests/unit/presentation.test.js index 50df610..716bcf9 100644 --- a/tests/unit/presentation.test.js +++ b/tests/unit/presentation.test.js @@ -1,6 +1,6 @@ import test from 'node:test'; import assert from 'node:assert/strict'; -import {latestUpdate, periodDays, providerStatus} from '../../src/ui/presentation.js'; +import {historyOverview, latestUpdate, periodDays, providerStatus} from '../../src/ui/presentation.js'; test('provider status distinguishes live, local, cached and setup data', () => { const value = {limits: {status: 'ready'}, history: {status: 'ready'}}; @@ -23,3 +23,17 @@ test('presentation helpers derive bounded period and freshness labels', () => { assert.equal(periodDays({start: '2026-08-31', end: '2026-09-06'}), 7); assert.equal(periodDays({start: 'invalid', end: '2026-09-06'}), null); }); + +test('history overview summarizes daily activity without double-counting models', () => { + const history = { + scope: 'local', + period: {start: '2026-08-31', end: '2026-09-06'}, + days: [{total: 10}, {total: 20}], + models: [{total: 30}, {total: 40}], + }; + assert.deepEqual(historyOverview(history), {days: 7, scope: 'local', total: 30}); + assert.deepEqual(historyOverview({...history, scope: 'account', days: []}), + {days: 7, scope: 'account', total: 70}); + assert.equal(historyOverview({...history, days: [], models: []}), null); + assert.equal(historyOverview(null), null); +}); From d5cb3a170e998cb46980dbb6224fb06d23f06eeb Mon Sep 17 00:00:00 2001 From: kcnewman Date: Mon, 7 Sep 2026 05:59:37 +0000 Subject: [PATCH 25/44] refactor: adopt UsageBeam product identity --- .AGENTS/plans/roadmap.md | 47 ++++++--- .AGENTS/rules/architecture.md | 6 +- .github/workflows/ci.yml | 6 +- CHANGELOG.md | 8 +- CONTRIBUTING.md | 4 +- README.md | 39 ++++---- docs/architecture.md | 16 +++- docs/providers/claude.md | 10 +- extension.js | 12 ++- meson.build | 6 +- metadata.json | 6 +- package.json | 2 +- prefs.js | 4 +- ...me.shell.extensions.usagebeam.gschema.xml} | 6 +- src/collector/main.js | 3 +- src/providers/codex.js | 2 +- src/services/files.js | 40 +++++++- src/services/history.js | 2 +- src/services/migration.js | 60 ++++++++++++ src/services/usageService.js | 4 +- src/ui/indicator.js | 58 +++++------ src/ui/widgets.js | 32 +++---- stylesheet.css | 96 +++++++++---------- tests/integration/collector.js | 34 ++++++- tools/check.js | 6 +- tools/package.py | 7 +- tools/smoke-shell.py | 28 +++--- 27 files changed, 363 insertions(+), 181 deletions(-) rename schemas/{org.gnome.shell.extensions.freeby.gschema.xml => org.gnome.shell.extensions.usagebeam.gschema.xml} (89%) create mode 100644 src/services/migration.js diff --git a/.AGENTS/plans/roadmap.md b/.AGENTS/plans/roadmap.md index e74ae39..c1a8048 100644 --- a/.AGENTS/plans/roadmap.md +++ b/.AGENTS/plans/roadmap.md @@ -1,7 +1,7 @@ -# Freeby usage monitor roadmap +# UsageBeam usage monitor roadmap -Updated: 2026-09-06. Status: post-alpha.2 compact interface/runtime and -production hardening complete; Cursor remains the next alpha.3 milestone. +Updated: 2026-09-07. Status: UsageBeam identity migration complete on `dev`; +Cursor remains the next alpha.3 provider milestone. ## Agreed direction @@ -10,10 +10,11 @@ and Copilot**, covering free and paid plans. The first stable release is about usage: quota windows, reset times, activity history, and model breakdowns where the provider exposes them. -Complete and verify the system before introducing a new name or icon. Keep the -current extension UUID and GSettings schema identity throughout these milestones. -Cost breakdowns, provider spending/balances, estimates, and budgets are deferred -to a later phase. Subscription percentages must not be derived from token totals. +The canonical product name is **UsageBeam**, with extension UUID +`usagebeam@oo7kc.github.io` and schema `org.gnome.shell.extensions.usagebeam`. +Keep those identities stable from alpha.3 onward. Icon work, cost breakdowns, +provider spending/balances, estimates, and budgets are deferred to a later phase. +Subscription percentages must not be derived from token totals. Every completed implementation milestone ends with a conventional commit, a version tag, and an installable GitHub prerelease. The first stable release must @@ -51,7 +52,7 @@ Expensive history work belongs in a managed GJS subprocess, not the Shell UI process. Avoid a persistent background daemon in this first iteration. ```text -freeby/ +usagebeam/ ├── .AGENTS/ # Rules, roadmap, archive, design references ├── AGENTS.md # Tool-discovery entrypoint ├── extension.js # GNOME lifecycle entry @@ -98,7 +99,7 @@ runtime implementations. - Give each provider a deadline, bounded retries/backoff, and independent success/failure handling. - Parse changed records incrementally; handle repeated events, cumulative counters, partial writes, rotation, and timezone boundaries. - Keep private usage metadata in XDG state/cache locations using atomic writes, bounded retention, and schema migrations. -- Do not retain credentials, prompts, responses, or complete transcripts in Freeby's history or diagnostics. +- Do not retain credentials, prompts, responses, or complete transcripts in UsageBeam's history or diagnostics. ## Known regressions to address @@ -212,6 +213,30 @@ an isolated GNOME Shell 50 session. A sanitized live check with Codex CLI 0.145.0 returned the Plus plan, three account quota windows, seven-day local activity, and model totals under usage schema version 2. +### Product identity checkpoint + +- [x] Rename the active product, runtime classes/styles, package, schema, archive, + CI artifact, and documentation from Freeby to UsageBeam. +- [x] Adopt `usagebeam@oo7kc.github.io` and + `org.gnome.shell.extensions.usagebeam` as the permanent extension identities. +- [x] Migrate recognized derived state, history caches, and explicitly changed + preferences without copying credentials or overwriting new UsageBeam values. +- [x] Verify a clean install, former-alpha migration, deterministic packaging, + and disable/re-enable lifecycle; commit the checkpoint on `dev` without moving + or tagging `main`. + +Evidence: `npm run check` passed 26 unit tests, strict schema/layout/identity +validation, and the GJS history, migration, command, process, and HTTP-policy +integration suite. A live local migration copied four recognized derived files +and two explicitly changed settings, retained private `0600` file modes, matched +the original data checksums, and preserved the 30-second refresh preference. +Two builds produced the same 38,098-byte archive at SHA-256 +`d92595924b705125cebc6bd09dc9689f6562522a9cf9ffaba98db7d666f0926a`. +That exact UsageBeam archive loaded, disabled, and re-enabled without extension +errors in a private GNOME Shell 50 session. The former local extension package +was removed and the new UUID was installed and enabled for the next login; GNOME +Shell 50 cannot discover a brand-new UUID in an already-running Wayland session. + ### 3. Cursor — v2.0.0-alpha.3 - [ ] Revalidate current Cursor authentication and usage sources for free and paid accounts. @@ -236,7 +261,7 @@ activity, and model totals under usage schema version 2. - [ ] Add notification thresholds, deduplication per account/window, and history retention/clear controls. - [ ] Preserve valid existing settings and document new defaults and migrations. - [ ] Verify keyboard navigation, screen-reader labels, dark/light themes, long content, scrolling, and display scaling. -- [ ] Commit, tag, publish, and verify the installable alpha.5 prerelease using the existing name and icon. +- [ ] Commit, tag, publish, and verify the installable alpha.5 prerelease using the finalized name and existing icon. ### 6. Stabilization — v2.0.0-beta.1, then v2.0.0 @@ -269,7 +294,7 @@ Documentation setup is preparatory work and does not trigger a product prereleas ## After usage monitoring is reliable -- Decide the new name and icon direction, then plan branding and any required identity/settings migration. +- Design a distinctive UsageBeam icon without changing the finalized extension identity. - Design cost breakdowns separately, distinguishing provider-reported charges from estimates and including currency, period, and pricing provenance. - Consider budgets, additional providers, multiple accounts, cross-device history, and a separate dashboard only as later scoped work. diff --git a/.AGENTS/rules/architecture.md b/.AGENTS/rules/architecture.md index 161331d..084830d 100644 --- a/.AGENTS/rules/architecture.md +++ b/.AGENTS/rules/architecture.md @@ -1,5 +1,5 @@ --- -description: Product scope and architecture invariants for Freeby +description: Product scope and architecture invariants for UsageBeam alwaysApply: true --- @@ -7,8 +7,8 @@ alwaysApply: true - Build a reliable native GNOME usage monitor for Codex, Claude Code, Cursor, and Copilot across free and paid plans. -- Finish usage monitoring before cost, billing, budgets, naming, icon, or schema - identity changes. +- Keep the finalized UsageBeam identity stable. Finish usage monitoring before + cost, billing, budgets, or icon work. - Keep GNOME entry points thin. Separate provider adapters, usage contracts, services, UI actors, and preferences. - Prefer GJS, Gio/GLib, Soup, and native GNOME APIs. Never download runtime diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a73669d..d8299a1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,10 +20,10 @@ jobs: run: npm run verify - name: Verify isolated install run: | - meson setup build --prefix="$RUNNER_TEMP/freeby-install" + meson setup build --prefix="$RUNNER_TEMP/usagebeam-install" meson install -C build - test -f "$RUNNER_TEMP/freeby-install/share/gnome-shell/extensions/freeby@kelvin.local/schemas/gschemas.compiled" + test -f "$RUNNER_TEMP/usagebeam-install/share/gnome-shell/extensions/usagebeam@oo7kc.github.io/schemas/gschemas.compiled" - uses: actions/upload-artifact@v7 with: - name: freeby-extension + name: usagebeam-extension path: dist/*.zip diff --git a/CHANGELOG.md b/CHANGELOG.md index 64b63a8..358db37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,18 @@ # Changelog -All notable changes are documented here. Freeby uses semantic prerelease versions +All notable changes are documented here. UsageBeam uses semantic prerelease versions while version 2 is developed on the `dev` branch. ## Unreleased ### Changed +- Renamed the product from Freeby to UsageBeam and adopted the permanent GNOME + extension UUID `usagebeam@oo7kc.github.io` and settings schema + `org.gnome.shell.extensions.usagebeam` ahead of alpha.3. +- Migrate recognized derived state, incremental history caches, and explicitly + changed preferences from the former alpha identity without moving credentials + or overwriting values already set for UsageBeam. - Split command discovery from private file storage and launch discovered CLIs with a process-local environment, explicit deadlines, cancellation reasons, and output limits. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9748c12..9f4ec7d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ -# Contributing to Freeby +# Contributing to UsageBeam Development targets the `dev` branch. `main` is reserved for stable releases. Keep provider changes small enough to verify independently and use conventional @@ -20,7 +20,7 @@ npm run pack For an isolated installation: ```bash -meson setup build --prefix=/tmp/freeby-install +meson setup build --prefix=/tmp/usagebeam-install meson install -C build ``` diff --git a/README.md b/README.md index 8beee6b..58004e7 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ -# Freeby +# UsageBeam -Freeby is a native GNOME Shell usage monitor for AI coding tools. It keeps +UsageBeam is a native GNOME Shell usage monitor for AI coding tools. It keeps account limits, reset windows, recent local token activity, and model totals one click away in the top panel. -Version 2 is being delivered provider by provider. `v2.0.0-alpha.2` adds Claude -Code to the Codex foundation. Cursor and Copilot adapters remain available as -opt-in previews while their dedicated milestones are completed. +Version 2 is being delivered provider by provider. The former Freeby identity +was used through `v2.0.0-alpha.2`; development now uses the permanent UsageBeam +identity. Cursor and Copilot adapters remain opt-in previews while their +dedicated milestones are completed. ## What the current prerelease includes @@ -22,7 +23,7 @@ opt-in previews while their dedicated milestones are completed. threshold-crossing notifications. - A native, theme-aware, keyboard-focusable GNOME panel interface. -Freeby stores only derived usage metadata under the standard XDG state/cache +UsageBeam stores only derived usage metadata under the standard XDG state/cache directories. It does not copy prompts, responses, transcripts, or credentials. Local activity covers this device only and must not be interpreted as billing or subscription usage. @@ -48,16 +49,21 @@ cd freeby git switch dev meson setup build --prefix="$HOME/.local" meson install -C build -gnome-extensions enable freeby@kelvin.local +gnome-extensions enable usagebeam@oo7kc.github.io ``` -Log out and back in if GNOME Shell has not discovered the extension. An archive -from a GitHub release can instead be installed with: +Log out and back in if GNOME Shell has not discovered the extension. Starting +with alpha.3, a downloaded release archive can instead be installed with: ```bash -gnome-extensions install --force freeby@kelvin.local-2.0.0-alpha.2.zip +gnome-extensions install --force usagebeam@oo7kc.github.io-VERSION.zip ``` +The identity migration copies recognized derived usage data and explicitly +changed settings from an installed Freeby alpha without overwriting existing +UsageBeam values. It does not copy credentials. After confirming UsageBeam is +active, the former `freeby@kelvin.local` extension can be uninstalled. + ## Settings The alpha.2 preferences window controls refresh frequency and notifications. @@ -68,7 +74,7 @@ full preferences interface in alpha.5. To opt into a preview adapter during development: ```bash -gsettings set org.gnome.shell.extensions.freeby enabled-providers "['codex', 'claude', 'cursor', 'copilot']" +gsettings set org.gnome.shell.extensions.usagebeam enabled-providers "['codex', 'claude', 'cursor', 'copilot']" ``` Preview providers are not part of the alpha.2 compatibility promise. See the @@ -90,15 +96,16 @@ from credential-free automated fixtures. ## Uninstall or downgrade ```bash -gnome-extensions disable freeby@kelvin.local -rm -rf "$HOME/.local/share/gnome-shell/extensions/freeby@kelvin.local" +gnome-extensions disable usagebeam@oo7kc.github.io +gnome-extensions uninstall usagebeam@oo7kc.github.io ``` -The extension-local schema is removed with that directory; shared system schema +The extension-local schema is removed with the extension; shared system schema artifacts are never deleted. To downgrade, install an older release archive with -`gnome-extensions install --force` and restart the session. +`gnome-extensions install --force` and restart the session. Releases through +alpha.2 use the former UUID and therefore install as a separate extension. See [CONTRIBUTING.md](CONTRIBUTING.md) for development practices and [.AGENTS/plans/roadmap.md](.AGENTS/plans/roadmap.md) for the ordered provider milestones. Architecture and provider documentation is indexed in -[docs/README.md](docs/README.md). Freeby is licensed under the [MIT License](LICENSE). +[docs/README.md](docs/README.md). UsageBeam is licensed under the [MIT License](LICENSE). diff --git a/docs/architecture.md b/docs/architecture.md index ae05e83..9357de8 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,6 +1,6 @@ # Architecture -Freeby is a GNOME Shell extension with a small trusted UI process and short-lived +UsageBeam is a GNOME Shell extension with a small trusted UI process and short-lived collector subprocesses. Provider code never creates GNOME actors, and UI code never reads provider credentials or transcript formats. @@ -42,12 +42,18 @@ oversized records are rejected before they reach the UI or persistent state. ## Privacy and storage -Freeby stores derived records in `$XDG_STATE_HOME/freeby` and versioned -incremental scan caches in `$XDG_CACHE_HOME/freeby`, with private directory and -file modes. Credentials are read only when needed and are never written by -Freeby. Prompt/response content is ignored, while session and account identifiers +UsageBeam stores derived records in `$XDG_STATE_HOME/usagebeam` and versioned +incremental scan caches in `$XDG_CACHE_HOME/usagebeam`, with private directory +and file modes. Credentials are read only when needed and are never written by +UsageBeam. Prompt/response content is ignored, while session and account identifiers used for deduplication are hashed once before persistence. +On the first start after the pre-alpha identity change, UsageBeam copies only +recognized provider records and incremental history caches from the former +directories when the corresponding destination is absent. Explicitly changed +preferences are imported from an installed former schema under the same +non-overwrite rule. Credentials and unrelated files are never migrated. + Account quota and local token activity remain separate scopes. Local activity is not billing data and never becomes a fabricated subscription percentage. diff --git a/docs/providers/claude.md b/docs/providers/claude.md index 9ce8fb1..4bf6dc8 100644 --- a/docs/providers/claude.md +++ b/docs/providers/claude.md @@ -1,6 +1,6 @@ # Claude Code provider -Freeby keeps Claude's account limits and local activity as separate sources. +UsageBeam keeps Claude's account limits and local activity as separate sources. Failure of one source does not erase valid data from the other. ## Account limits @@ -13,7 +13,7 @@ token itself is never cached or logged; only a SHA-256 account fingerprint may be retained to prevent stale data crossing accounts. This OAuth usage interface is used by current Claude Code integrations but is -not a stable public Anthropic API contract. Freeby treats unknown/empty payloads +not a stable public Anthropic API contract. UsageBeam treats unknown/empty payloads as unavailable, retains only unexpired stale windows after transient failures, and keeps local history visible when sign-in expires or the endpoint is offline. @@ -21,14 +21,14 @@ and keeps local history visible when sign-in expires or the endpoint is offline. Claude Code assistant-message usage is scanned incrementally from `$CLAUDE_CONFIG_DIR/projects/**/*.jsonl` or `~/.claude/projects/**/*.jsonl`. -Freeby reads timestamps, model identifiers, message/session identifiers, and the +UsageBeam reads timestamps, model identifiers, message/session identifiers, and the four usage counters only. Prompt and response content is ignored. `input_tokens`, `output_tokens`, `cache_read_input_tokens`, and -`cache_creation_input_tokens` are independent Anthropic counters, so Freeby adds +`cache_creation_input_tokens` are independent Anthropic counters, so UsageBeam adds all four to activity totals without subtracting cache tokens from input. Repeated message IDs are counted once. Session identifiers are hashed before derived data -is written to Freeby's XDG cache. +is written to UsageBeam's XDG cache. History is local to this device, covers the displayed seven-day period, and is not a measure of subscription quota or billable cost. diff --git a/extension.js b/extension.js index f0f37c1..b66c745 100644 --- a/extension.js +++ b/extension.js @@ -1,17 +1,19 @@ import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js'; import * as Main from 'resource:///org/gnome/shell/ui/main.js'; -import {FreebyIndicator} from './src/ui/indicator.js'; +import {UsageBeamIndicator} from './src/ui/indicator.js'; +import {migrateLegacyInstall} from './src/services/migration.js'; import {UsageService} from './src/services/usageService.js'; -export default class FreebyExtension extends Extension { +export default class UsageBeamExtension extends Extension { enable() { - this._settings = this.getSettings('org.gnome.shell.extensions.freeby'); - this._indicator = new FreebyIndicator(this._settings, () => this.openPreferences()); + this._settings = this.getSettings('org.gnome.shell.extensions.usagebeam'); + migrateLegacyInstall(this._settings); + this._indicator = new UsageBeamIndicator(this._settings, () => this.openPreferences()); Main.panel.addToStatusArea(this.uuid, this._indicator); this._service = new UsageService(this._settings, this.path, () => this._indicator?.render(), alerts => { for (const alert of alerts) { const state = alert.threshold === 100 ? 'limit reached' : `reached ${alert.threshold}%`; - Main.notify('Freeby usage alert', `${alert.provider}: ${alert.label} ${state}.`); + Main.notify('UsageBeam usage alert', `${alert.provider}: ${alert.label} ${state}.`); } }); this._indicator.attach(this._service); diff --git a/meson.build b/meson.build index 8595921..49ad819 100644 --- a/meson.build +++ b/meson.build @@ -1,8 +1,8 @@ -project('freeby', version: '2.0.0-alpha.3-dev.0', license: 'MIT', +project('usagebeam', version: '2.0.0-alpha.3-dev.0', license: 'MIT', meson_version: '>= 0.56.0', default_options: ['warning_level=0']) -uuid = 'freeby@kelvin.local' +uuid = 'usagebeam@oo7kc.github.io' prefix = get_option('prefix') extdir = join_paths(prefix, 'share', 'gnome-shell', 'extensions', uuid) schemadir = join_paths(extdir, 'schemas') @@ -12,7 +12,7 @@ install_data('extension.js', 'prefs.js', 'metadata.json', 'stylesheet.css', 'LIC install_subdir('src', install_dir: extdir) -install_data('schemas/org.gnome.shell.extensions.freeby.gschema.xml', +install_data('schemas/org.gnome.shell.extensions.usagebeam.gschema.xml', install_dir: schemadir) meson.add_install_script('build-aux/compile-schemas.sh', schemadir) diff --git a/metadata.json b/metadata.json index 959159c..5dedec7 100644 --- a/metadata.json +++ b/metadata.json @@ -1,8 +1,8 @@ { - "uuid": "freeby@kelvin.local", - "name": "Freeby", + "uuid": "usagebeam@oo7kc.github.io", + "name": "UsageBeam", "description": "AI coding usage monitor for account limits, reset windows, local token activity and model breakdowns. Supports Codex and Claude Code; additional providers arrive through version 2 prereleases.", "shell-version": ["50"], - "settings-schema": "org.gnome.shell.extensions.freeby", + "settings-schema": "org.gnome.shell.extensions.usagebeam", "url": "https://github.com/oo7kc/freeby" } diff --git a/package.json b/package.json index d42f69c..6699c7d 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "freeby", + "name": "usagebeam", "version": "2.0.0-alpha.3-dev.0", "private": true, "type": "module", diff --git a/prefs.js b/prefs.js index 8dd9de9..db0e11d 100644 --- a/prefs.js +++ b/prefs.js @@ -3,9 +3,9 @@ import Gtk from 'gi://Gtk'; import {ExtensionPreferences} from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js'; -export default class FreebyPreferences extends ExtensionPreferences { +export default class UsageBeamPreferences extends ExtensionPreferences { fillPreferencesWindow(window) { - const settings = this.getSettings('org.gnome.shell.extensions.freeby'); + const settings = this.getSettings('org.gnome.shell.extensions.usagebeam'); const page = new Adw.PreferencesPage(); window.add(page); diff --git a/schemas/org.gnome.shell.extensions.freeby.gschema.xml b/schemas/org.gnome.shell.extensions.usagebeam.gschema.xml similarity index 89% rename from schemas/org.gnome.shell.extensions.freeby.gschema.xml rename to schemas/org.gnome.shell.extensions.usagebeam.gschema.xml index 93a06c8..f03f664 100644 --- a/schemas/org.gnome.shell.extensions.freeby.gschema.xml +++ b/schemas/org.gnome.shell.extensions.usagebeam.gschema.xml @@ -1,7 +1,7 @@ - - + + 120 diff --git a/src/collector/main.js b/src/collector/main.js index 83c429e..a01f68b 100644 --- a/src/collector/main.js +++ b/src/collector/main.js @@ -69,7 +69,8 @@ const io = { if (cleaned) return cleaned; } catch { /* Standalone setup token is also supported. */ } - return token(readText(join(GLib.get_user_config_dir(), 'freeby', 'copilot-token'), null, 64 * 1024)); + return token(readText(join(GLib.get_user_config_dir(), 'usagebeam', 'copilot-token'), null, 64 * 1024)) ?? + token(readText(join(GLib.get_user_config_dir(), 'freeby', 'copilot-token'), null, 64 * 1024)); }, }; diff --git a/src/providers/codex.js b/src/providers/codex.js index 11c69fe..226b0fa 100644 --- a/src/providers/codex.js +++ b/src/providers/codex.js @@ -109,7 +109,7 @@ export async function collectCodex(io) { let rpc; try { rpc = io.codexClient(); - await rpc.request('initialize', {clientInfo: {name: 'freeby', title: 'Freeby', version: '2.0.0'}, + await rpc.request('initialize', {clientInfo: {name: 'usagebeam', title: 'UsageBeam', version: '2.0.0'}, capabilities: {experimentalApi: false}}); rpc.notify('initialized', {}); const account = await rpc.request('account/read', {refreshToken: false}); diff --git a/src/services/files.js b/src/services/files.js index ab92d82..69f4010 100644 --- a/src/services/files.js +++ b/src/services/files.js @@ -4,6 +4,10 @@ import GLib from 'gi://GLib'; export const join = (...parts) => GLib.build_filenamev(parts); export const fingerprint = value => GLib.compute_checksum_for_string(GLib.ChecksumType.SHA256, String(value), -1); +const PRODUCT_DIRECTORY = 'usagebeam'; +const LEGACY_DIRECTORY = 'freeby'; +const PROVIDERS = Object.freeze(['codex', 'claude', 'cursor', 'copilot']); + export function readText(path, fallback = null, maxBytes = 16 * 1024 * 1024) { if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) throw new Error('File size limit must be a positive integer'); @@ -44,9 +48,41 @@ export function writeJson(path, value) { } export function stateDirectory() { - return join(GLib.get_user_state_dir(), 'freeby'); + return join(GLib.get_user_state_dir(), PRODUCT_DIRECTORY); } export function cacheDirectory() { - return join(GLib.get_user_cache_dir(), 'freeby'); + return join(GLib.get_user_cache_dir(), PRODUCT_DIRECTORY); +} + +export function migrateLegacyData({ + legacyState = join(GLib.get_user_state_dir(), LEGACY_DIRECTORY), + legacyCache = join(GLib.get_user_cache_dir(), LEGACY_DIRECTORY), + state = stateDirectory(), + cache = cacheDirectory(), +} = {}) { + const groups = [ + {source: legacyState, destination: state, names: PROVIDERS.map(id => `${id}.json`), + maxBytes: 16 * 1024 * 1024}, + {source: legacyCache, destination: cache, names: PROVIDERS.map(id => `history-${id}.json`), + maxBytes: 64 * 1024 * 1024}, + ]; + let migrated = 0; + for (const group of groups) { + for (const name of group.names) { + const destination = join(group.destination, name); + if (Gio.File.new_for_path(destination).query_exists(null)) + continue; + const value = readJson(join(group.source, name), null, group.maxBytes); + if (value === null) + continue; + try { + writeJson(destination, value); + migrated++; + } catch { + // Migration is best-effort; collection can rebuild derived data. + } + } + } + return migrated; } diff --git a/src/services/history.js b/src/services/history.js index e9e11c5..e512139 100644 --- a/src/services/history.js +++ b/src/services/history.js @@ -181,7 +181,7 @@ export function scanHistory(id, roots, parse, {retention = 30, now = Date.now(), const event = parse(JSON.parse(text), previous.state); // Provider session identifiers are useful only for // deduplication. Hash them before either parser state or - // derived events reach Freeby's private cache. + // derived events reach UsageBeam's private cache. if (previous.state.session !== oldSession) previous.state.session = privateIdentity(id, previous.state.session); if (event) { diff --git a/src/services/migration.js b/src/services/migration.js new file mode 100644 index 0000000..70c1278 --- /dev/null +++ b/src/services/migration.js @@ -0,0 +1,60 @@ +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import {join, migrateLegacyData} from './files.js'; + +const LEGACY_UUID = 'freeby@kelvin.local'; +const LEGACY_SCHEMA = 'org.gnome.shell.extensions.freeby'; +const SETTINGS = Object.freeze([ + 'refresh-interval', + 'notifications-enabled', + 'enabled-providers', + 'default-provider', + 'history-retention-days', + 'notification-threshold', +]); + +function legacySchemaDirectories() { + return [GLib.get_user_data_dir(), ...GLib.get_system_data_dirs()] + .map(directory => join(directory, 'gnome-shell', 'extensions', LEGACY_UUID, 'schemas')); +} + +function legacySettings(directories = legacySchemaDirectories()) { + for (const directory of directories) { + if (!Gio.File.new_for_path(directory).query_exists(null)) + continue; + try { + const source = Gio.SettingsSchemaSource.new_from_directory( + directory, Gio.SettingsSchemaSource.get_default(), false); + const schema = source.lookup(LEGACY_SCHEMA, false); + if (schema) + return new Gio.Settings({settings_schema: schema}); + } catch { + // A missing or invalid former installation must not block startup. + } + } + return null; +} + +export function migrateLegacySettings(settings, directories = undefined) { + const legacy = legacySettings(directories); + if (!legacy) + return 0; + return copyLegacySettings(settings, legacy); +} + +export function copyLegacySettings(settings, legacy) { + let migrated = 0; + for (const key of SETTINGS) { + if (settings.get_user_value(key) !== null) + continue; + const value = legacy.get_user_value(key); + if (value !== null && settings.set_value(key, value)) + migrated++; + } + return migrated; +} + +export function migrateLegacyInstall(settings) { + migrateLegacyData(); + migrateLegacySettings(settings); +} diff --git a/src/services/usageService.js b/src/services/usageService.js index 247c238..84ab248 100644 --- a/src/services/usageService.js +++ b/src/services/usageService.js @@ -53,7 +53,7 @@ export class UsageService { try { this._changed(); } catch (error) { - console.error('Freeby could not update its panel', error); + console.error('UsageBeam could not update its panel', error); } } @@ -61,7 +61,7 @@ export class UsageService { try { this._alerts(alerts); } catch (error) { - console.error('Freeby could not display a usage alert', error); + console.error('UsageBeam could not display a usage alert', error); } } diff --git a/src/ui/indicator.js b/src/ui/indicator.js index fdb1a02..d5b1db2 100644 --- a/src/ui/indicator.js +++ b/src/ui/indicator.js @@ -12,21 +12,21 @@ import {actionButton, button, disclosureButton, label, meter, modelMeter, row} f const TAB_NAMES = {claude: 'Claude'}; const PROVIDER_MARKS = {codex: '>_', claude: '✦', cursor: '⌁', copilot: '◆'}; -export const FreebyIndicator = GObject.registerClass(class FreebyIndicator extends PanelMenu.Button { +export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator extends PanelMenu.Button { _init(settings, openPreferences) { - super._init(0.0, 'Freeby usage monitor'); + super._init(0.0, 'UsageBeam usage monitor'); this._settings = settings; this._openPreferences = openPreferences; this._service = null; this._detailsExpanded = false; - this._panelLabel = label('AI', 'freeby-panel-label'); + this._panelLabel = label('AI', 'usagebeam-panel-label'); this.add_child(this._panelLabel); - this.menu.actor.add_style_class_name('freeby-menu'); + this.menu.actor.add_style_class_name('usagebeam-menu'); const section = new PopupMenu.PopupMenuSection(); this.menu.addMenuItem(section); // `content` is an inherited Clutter.Actor property whose value must be // ClutterContent, so keep the menu actor under an unambiguous name. - this._contentBox = new St.BoxLayout({vertical: true, style_class: 'freeby-content', x_expand: true}); + this._contentBox = new St.BoxLayout({vertical: true, style_class: 'usagebeam-content', x_expand: true}); this._scroll = new St.ScrollView({hscrollbar_policy: St.PolicyType.NEVER, vscrollbar_policy: St.PolicyType.AUTOMATIC, overlay_scrollbars: true}); // St.ScrollView is a Clutter actor in GNOME 50. Its inherited @@ -77,8 +77,8 @@ export const FreebyIndicator = GObject.registerClass(class FreebyIndicator exten const busy = this._service?.isRefreshing(id) ?? false; this._panelLabel.text = percent === null ? 'AI' : `AI · ${Math.round(percent)}%`; - this._panelLabel.style_class = `freeby-panel-label${percent >= 100 ? ' freeby-danger' : percent >= 90 ? ' freeby-warning' : ''}`; - this.accessible_name = `Freeby, ${record?.name ?? 'usage monitor'}${percent === null ? '' : `, ${Math.round(percent)} percent used`}`; + this._panelLabel.style_class = `usagebeam-panel-label${percent >= 100 ? ' usagebeam-danger' : percent >= 90 ? ' usagebeam-warning' : ''}`; + this.accessible_name = `UsageBeam, ${record?.name ?? 'usage monitor'}${percent === null ? '' : `, ${Math.round(percent)} percent used`}`; this._renderHeader(id, record, busy); if (enabled.length > 1) @@ -94,19 +94,19 @@ export const FreebyIndicator = GObject.registerClass(class FreebyIndicator exten } _renderHeader(id, record, busy) { - const header = new St.BoxLayout({style_class: 'freeby-header', x_expand: true}); - header.add_child(label(PROVIDER_MARKS[id] ?? 'AI', `freeby-provider-mark freeby-${id}-mark`)); - const identity = new St.BoxLayout({vertical: true, style_class: 'freeby-identity', x_expand: true}); - identity.add_child(label(record?.name ?? 'Freeby', 'freeby-title')); - identity.add_child(label(record?.plan ? String(record.plan).toUpperCase() : 'USAGE MONITOR', 'freeby-caption')); + const header = new St.BoxLayout({style_class: 'usagebeam-header', x_expand: true}); + header.add_child(label(PROVIDER_MARKS[id] ?? 'AI', `usagebeam-provider-mark usagebeam-${id}-mark`)); + const identity = new St.BoxLayout({vertical: true, style_class: 'usagebeam-identity', x_expand: true}); + identity.add_child(label(record?.name ?? 'UsageBeam', 'usagebeam-title')); + identity.add_child(label(record?.plan ? String(record.plan).toUpperCase() : 'USAGE MONITOR', 'usagebeam-caption')); header.add_child(identity); const statusText = providerStatus(record, busy); - header.add_child(label(`● ${statusText}`, `freeby-status freeby-status-${statusText.toLowerCase()}`)); + header.add_child(label(`● ${statusText}`, `usagebeam-status usagebeam-status-${statusText.toLowerCase()}`)); this._contentBox.add_child(header); } _renderSelector(enabled, selected) { - const selector = new St.Widget({style_class: 'freeby-selector', x_expand: true, + const selector = new St.Widget({style_class: 'usagebeam-selector', x_expand: true, layout_manager: new Clutter.BoxLayout({orientation: Clutter.Orientation.HORIZONTAL, homogeneous: true, spacing: 8})}); for (const provider of enabled) { @@ -120,14 +120,14 @@ export const FreebyIndicator = GObject.registerClass(class FreebyIndicator exten _renderLimits(limits) { this._heading('LIMITS'); for (const window of limits.windows) { - const box = new St.BoxLayout({vertical: true, style_class: 'freeby-window'}); + const box = new St.BoxLayout({vertical: true, style_class: 'usagebeam-window'}); const value = window.unlimited ? 'Unlimited' : `${Math.round(window.usedPercent)}%`; box.add_child(row(window.label, value)); if (!window.unlimited) { - const level = window.usedPercent >= 100 ? 'freeby-danger' : - window.usedPercent >= 90 ? 'freeby-warning' : ''; + const level = window.usedPercent >= 100 ? 'usagebeam-danger' : + window.usedPercent >= 90 ? 'usagebeam-warning' : ''; box.add_child(meter(window.usedPercent / 100, `${window.label}: ${value} used`, level)); - box.add_child(label(resetTime(window.resetsAt), 'freeby-caption')); + box.add_child(label(resetTime(window.resetsAt), 'usagebeam-caption')); } this._contentBox.add_child(box); } @@ -151,7 +151,7 @@ export const FreebyIndicator = GObject.registerClass(class FreebyIndicator exten // Construct the complete view even while collapsed so lifecycle smoke // coverage continues to exercise every primary widget. - const details = new St.BoxLayout({vertical: true, style_class: 'freeby-details', + const details = new St.BoxLayout({vertical: true, style_class: 'usagebeam-details', x_expand: true, visible: this._detailsExpanded}); if (history.days.length) this._renderDays(history, details); @@ -166,20 +166,20 @@ export const FreebyIndicator = GObject.registerClass(class FreebyIndicator exten _renderDays(history, parent = this._contentBox) { this._heading('TOKENS BY DAY', parent); const scope = history.scope === 'account' ? 'Account activity' : 'This device'; - parent.add_child(label(`${scope} · ${history.period.start} – ${history.period.end}`, 'freeby-caption')); + parent.add_child(label(`${scope} · ${history.period.start} – ${history.period.end}`, 'usagebeam-caption')); const max = Math.max(1, ...history.days.map(day => day.total)); const today = recentDates(Date.now(), 1)[0]; for (const day of history.days) { - const line = new St.BoxLayout({style_class: 'freeby-day-row', x_expand: true}); + const line = new St.BoxLayout({style_class: 'usagebeam-day-row', x_expand: true}); const dayName = day.date === today ? 'Today' : new Date(`${day.date}T12:00:00`).toLocaleDateString(undefined, {weekday: 'short'}); - line.add_child(label(dayName, 'freeby-day')); + line.add_child(label(dayName, 'usagebeam-day')); if (day.date === today) - line.add_style_class_name('freeby-today'); + line.add_style_class_name('usagebeam-today'); const bar = meter(day.total / max, `${day.date}: ${tokens(day.total)} tokens`); bar.y_align = Clutter.ActorAlign.CENTER; line.add_child(bar); - line.add_child(label(tokens(day.total), 'freeby-day-total')); + line.add_child(label(tokens(day.total), 'usagebeam-day-total')); line.accessible_name = `${dayName}, ${day.total} tokens, ${day.sessions ?? 0} sessions`; parent.add_child(line); } @@ -198,12 +198,12 @@ export const FreebyIndicator = GObject.registerClass(class FreebyIndicator exten parent.add_child(modelMeter(displayName, tokens(model.total), model.total / max, accessible)); } if (history.models.length > visibleModels.length) - parent.add_child(label(`Top ${visibleModels.length} of ${history.models.length} models`, 'freeby-caption')); + parent.add_child(label(`Top ${visibleModels.length} of ${history.models.length} models`, 'usagebeam-caption')); } _renderFooter(record, busy) { - const actions = new St.BoxLayout({style_class: 'freeby-actions', x_expand: true}); - actions.add_child(label(busy ? 'Refreshing…' : age(latestUpdate(record)), 'freeby-caption', true)); + const actions = new St.BoxLayout({style_class: 'usagebeam-actions', x_expand: true}); + actions.add_child(label(busy ? 'Refreshing…' : age(latestUpdate(record)), 'usagebeam-caption', true)); actions.add_child(actionButton('Refresh', () => this._service?.refreshAll(true), 'Refresh usage')); actions.add_child(actionButton('Settings', () => { this.menu.close(); this._openPreferences(); }, 'Open extension settings')); @@ -223,11 +223,11 @@ export const FreebyIndicator = GObject.registerClass(class FreebyIndicator exten } _heading(text, parent = this._contentBox) { - parent.add_child(label(text, 'freeby-section-title')); + parent.add_child(label(text, 'usagebeam-section-title')); } _message(text) { - const message = label(text, 'freeby-message'); + const message = label(text, 'usagebeam-message'); message.clutter_text.line_wrap = true; this._contentBox.add_child(message); } diff --git a/src/ui/widgets.js b/src/ui/widgets.js index 7ba6db8..c7a09c5 100644 --- a/src/ui/widgets.js +++ b/src/ui/widgets.js @@ -8,20 +8,20 @@ export function label(text, style = '', expand = false) { y_align: Clutter.ActorAlign.CENTER, x_expand: expand}); } -export function row(left, right, style = 'freeby-row') { +export function row(left, right, style = 'usagebeam-row') { const box = new St.BoxLayout({style_class: style, x_expand: true}); const title = label(left, '', true); title.clutter_text.ellipsize = Pango.EllipsizeMode.END; box.add_child(title); - box.add_child(label(right, 'freeby-number')); + box.add_child(label(right, 'usagebeam-number')); return box; } export function meter(fraction, name, style = '') { const ratio = Math.max(0, Math.min(1, Number(fraction) || 0)); - const track = new St.Widget({style_class: `freeby-track ${style}`, x_expand: true, + const track = new St.Widget({style_class: `usagebeam-track ${style}`, x_expand: true, layout_manager: new Clutter.FixedLayout(), accessible_name: name, accessible_role: Atk.Role.PROGRESS_BAR}); - const fill = new St.Widget({style_class: 'freeby-fill'}); + const fill = new St.Widget({style_class: 'usagebeam-fill'}); fill.set_position(0, 0); track.add_child(fill); track.connect('notify::allocation', () => { @@ -32,15 +32,15 @@ export function meter(fraction, name, style = '') { export function modelMeter(left, right, fraction, name) { const ratio = Math.max(0, Math.min(1, Number(fraction) || 0)); - const track = new St.Widget({style_class: 'freeby-model-meter', x_expand: true, + const track = new St.Widget({style_class: 'usagebeam-model-meter', x_expand: true, layout_manager: new Clutter.FixedLayout(), accessible_name: name, accessible_role: Atk.Role.PROGRESS_BAR}); - const fill = new St.Widget({style_class: 'freeby-model-fill'}); - const content = new St.BoxLayout({style_class: 'freeby-model-content'}); - const title = label(left, 'freeby-model-name', true); + const fill = new St.Widget({style_class: 'usagebeam-model-fill'}); + const content = new St.BoxLayout({style_class: 'usagebeam-model-content'}); + const title = label(left, 'usagebeam-model-name', true); title.clutter_text.ellipsize = Pango.EllipsizeMode.END; content.add_child(title); - content.add_child(label(right, 'freeby-number')); + content.add_child(label(right, 'usagebeam-number')); track.add_child(fill); track.add_child(content); track.connect('notify::allocation', () => { @@ -56,7 +56,7 @@ export function modelMeter(left, right, fraction, name) { export function button(text, callback, {active = false, name = text} = {}) { const actor = new St.Button({label: text, can_focus: true, reactive: true, track_hover: true, - accessible_name: name, style_class: 'freeby-button', x_expand: true}); + accessible_name: name, style_class: 'usagebeam-button', x_expand: true}); if (active) actor.add_style_pseudo_class('checked'); actor.connect('clicked', callback); @@ -70,17 +70,17 @@ export function disclosureButton(summary, expanded, callback) { can_focus: true, checked: expanded, reactive: true, - style_class: 'freeby-disclosure', + style_class: 'usagebeam-disclosure', toggle_mode: true, track_hover: true, x_expand: true, }); - const content = new St.BoxLayout({style_class: 'freeby-disclosure-content', x_expand: true}); - content.add_child(label('Activity', 'freeby-disclosure-title', true)); - content.add_child(label(summary, 'freeby-disclosure-summary')); + const content = new St.BoxLayout({style_class: 'usagebeam-disclosure-content', x_expand: true}); + content.add_child(label('Activity', 'usagebeam-disclosure-title', true)); + content.add_child(label(summary, 'usagebeam-disclosure-summary')); content.add_child(new St.Icon({ icon_name: expanded ? 'pan-up-symbolic' : 'pan-down-symbolic', - style_class: 'freeby-disclosure-icon', + style_class: 'usagebeam-disclosure-icon', })); actor.set_child(content); actor.connect('clicked', callback); @@ -90,6 +90,6 @@ export function disclosureButton(summary, expanded, callback) { export function actionButton(text, callback, name = text) { const actor = button(text, callback, {name}); actor.x_expand = false; - actor.add_style_class_name('freeby-action-button'); + actor.add_style_class_name('usagebeam-action-button'); return actor; } diff --git a/stylesheet.css b/stylesheet.css index 9a66740..52d8fbe 100644 --- a/stylesheet.css +++ b/stylesheet.css @@ -1,48 +1,48 @@ -.freeby-menu .popup-menu-content { border-color: st-transparentize(st-lighten(-st-accent-color, 20%), 0.58); } -.freeby-panel-label { font-weight: bold; font-size: 10pt; } -.freeby-content { padding: 9px 12px 10px; spacing: 0; } -.freeby-header { spacing: 8px; padding: 1px 0 8px; } -.freeby-identity { spacing: 1px; } -.freeby-provider-mark { min-width: 28px; min-height: 28px; padding: 3px; border-radius: 6px; background-color: st-transparentize(-st-accent-color, 0.88); color: st-lighten(-st-accent-color, 20%); font-size: 11pt; font-weight: bold; text-align: center; } -.freeby-claude-mark { background-color: rgba(255, 145, 90, 0.12); color: #ff9f6b; } -.freeby-title { font-size: 13pt; font-weight: bold; } -.freeby-caption { font-size: 8.5pt; opacity: 0.66; } -.freeby-status { padding: 3px 6px; border: 1px solid rgba(128, 128, 128, 0.30); border-radius: 5px; font-size: 8pt; opacity: 0.84; } -.freeby-status-live { color: #57e389; border-color: rgba(87, 227, 137, 0.5); background-color: rgba(87, 227, 137, 0.08); } -.freeby-status-sync { color: st-lighten(-st-accent-color, 20%); border-color: st-transparentize(-st-accent-color, 0.5); } -.freeby-status-local { color: #f6d32d; border-color: rgba(246, 211, 45, 0.45); } -.freeby-status-cached { color: #c0bfbc; border-color: rgba(192, 191, 188, 0.4); } -.freeby-selector { spacing: 6px; padding-bottom: 8px; } -.freeby-actions { spacing: 6px; padding-top: 8px; margin-top: 5px; border-top: 1px solid rgba(128, 128, 128, 0.20); } -.freeby-button { padding: 5px 10px; min-height: 24px; border: 1px solid rgba(128, 128, 128, 0.28); border-radius: 6px; background-color: rgba(128, 128, 128, 0.05); } -.freeby-button:hover, .freeby-button:focus { border-color: st-transparentize(st-lighten(-st-accent-color, 20%), 0.35); background-color: st-transparentize(-st-accent-color, 0.88); } -.freeby-button:checked { border-color: st-transparentize(st-lighten(-st-accent-color, 20%), 0.25); background-color: st-transparentize(-st-accent-color, 0.82); font-weight: bold; } -.freeby-button:focus { box-shadow: inset 0 0 0 2px -st-accent-color; } -.freeby-action-button { padding: 3px 8px; min-height: 22px; font-size: 8.5pt; } -.freeby-section-title { font-size: 8.5pt; font-weight: bold; opacity: 0.76; border-top: 1px solid rgba(128, 128, 128, 0.20); padding: 8px 0 5px; } -.freeby-row { spacing: 10px; } -.freeby-number { opacity: 0.9; } -.freeby-window { spacing: 3px; padding-bottom: 6px; } -.freeby-track { height: 4px; min-width: 20px; border-radius: 3px; background-color: rgba(128, 128, 128, 0.22); } -.freeby-fill { background-color: st-lighten(-st-accent-color, 20%); border-radius: 3px; } -.freeby-warning { color: #e5a50a; } -.freeby-danger { color: #e01b24; } -.freeby-warning .freeby-fill { background-color: #e5a50a; } -.freeby-danger .freeby-fill { background-color: #e01b24; } -.freeby-disclosure { margin-top: 2px; padding: 6px 8px; min-height: 26px; border: 1px solid rgba(128, 128, 128, 0.22); border-radius: 6px; background-color: rgba(128, 128, 128, 0.04); } -.freeby-disclosure:hover, .freeby-disclosure:focus { border-color: st-transparentize(st-lighten(-st-accent-color, 20%), 0.40); background-color: st-transparentize(-st-accent-color, 0.90); } -.freeby-disclosure:focus { box-shadow: inset 0 0 0 2px -st-accent-color; } -.freeby-disclosure-content { spacing: 6px; } -.freeby-disclosure-title { font-weight: bold; } -.freeby-disclosure-summary { font-size: 8.5pt; opacity: 0.62; } -.freeby-disclosure-icon { icon-size: 12px; opacity: 0.72; } -.freeby-details { spacing: 0; padding-top: 2px; } -.freeby-day-row { spacing: 8px; min-height: 23px; } -.freeby-day { min-width: 38px; font-size: 9pt; opacity: 0.70; } -.freeby-today .freeby-day { color: st-lighten(-st-accent-color, 20%); opacity: 1; font-weight: bold; } -.freeby-day-total { min-width: 52px; text-align: right; font-size: 8.5pt; opacity: 0.9; } -.freeby-model-meter { min-height: 28px; margin-bottom: 5px; background-color: rgba(128, 128, 160, 0.13); } -.freeby-model-fill { background-color: st-transparentize(-st-accent-color, 0.70); } -.freeby-model-content { padding: 5px 8px; } -.freeby-model-name { opacity: 0.92; } -.freeby-message { margin: 0 0 7px; padding: 8px 10px; border-left: 2px solid #f6d32d; border-radius: 3px; background-color: rgba(246, 211, 45, 0.06); font-size: 9pt; opacity: 0.85; } +.usagebeam-menu .popup-menu-content { border-color: st-transparentize(st-lighten(-st-accent-color, 20%), 0.58); } +.usagebeam-panel-label { font-weight: bold; font-size: 10pt; } +.usagebeam-content { padding: 9px 12px 10px; spacing: 0; } +.usagebeam-header { spacing: 8px; padding: 1px 0 8px; } +.usagebeam-identity { spacing: 1px; } +.usagebeam-provider-mark { min-width: 28px; min-height: 28px; padding: 3px; border-radius: 6px; background-color: st-transparentize(-st-accent-color, 0.88); color: st-lighten(-st-accent-color, 20%); font-size: 11pt; font-weight: bold; text-align: center; } +.usagebeam-claude-mark { background-color: rgba(255, 145, 90, 0.12); color: #ff9f6b; } +.usagebeam-title { font-size: 13pt; font-weight: bold; } +.usagebeam-caption { font-size: 8.5pt; opacity: 0.66; } +.usagebeam-status { padding: 3px 6px; border: 1px solid rgba(128, 128, 128, 0.30); border-radius: 5px; font-size: 8pt; opacity: 0.84; } +.usagebeam-status-live { color: #57e389; border-color: rgba(87, 227, 137, 0.5); background-color: rgba(87, 227, 137, 0.08); } +.usagebeam-status-sync { color: st-lighten(-st-accent-color, 20%); border-color: st-transparentize(-st-accent-color, 0.5); } +.usagebeam-status-local { color: #f6d32d; border-color: rgba(246, 211, 45, 0.45); } +.usagebeam-status-cached { color: #c0bfbc; border-color: rgba(192, 191, 188, 0.4); } +.usagebeam-selector { spacing: 6px; padding-bottom: 8px; } +.usagebeam-actions { spacing: 6px; padding-top: 8px; margin-top: 5px; border-top: 1px solid rgba(128, 128, 128, 0.20); } +.usagebeam-button { padding: 5px 10px; min-height: 24px; border: 1px solid rgba(128, 128, 128, 0.28); border-radius: 6px; background-color: rgba(128, 128, 128, 0.05); } +.usagebeam-button:hover, .usagebeam-button:focus { border-color: st-transparentize(st-lighten(-st-accent-color, 20%), 0.35); background-color: st-transparentize(-st-accent-color, 0.88); } +.usagebeam-button:checked { border-color: st-transparentize(st-lighten(-st-accent-color, 20%), 0.25); background-color: st-transparentize(-st-accent-color, 0.82); font-weight: bold; } +.usagebeam-button:focus { box-shadow: inset 0 0 0 2px -st-accent-color; } +.usagebeam-action-button { padding: 3px 8px; min-height: 22px; font-size: 8.5pt; } +.usagebeam-section-title { font-size: 8.5pt; font-weight: bold; opacity: 0.76; border-top: 1px solid rgba(128, 128, 128, 0.20); padding: 8px 0 5px; } +.usagebeam-row { spacing: 10px; } +.usagebeam-number { opacity: 0.9; } +.usagebeam-window { spacing: 3px; padding-bottom: 6px; } +.usagebeam-track { height: 4px; min-width: 20px; border-radius: 3px; background-color: rgba(128, 128, 128, 0.22); } +.usagebeam-fill { background-color: st-lighten(-st-accent-color, 20%); border-radius: 3px; } +.usagebeam-warning { color: #e5a50a; } +.usagebeam-danger { color: #e01b24; } +.usagebeam-warning .usagebeam-fill { background-color: #e5a50a; } +.usagebeam-danger .usagebeam-fill { background-color: #e01b24; } +.usagebeam-disclosure { margin-top: 2px; padding: 6px 8px; min-height: 26px; border: 1px solid rgba(128, 128, 128, 0.22); border-radius: 6px; background-color: rgba(128, 128, 128, 0.04); } +.usagebeam-disclosure:hover, .usagebeam-disclosure:focus { border-color: st-transparentize(st-lighten(-st-accent-color, 20%), 0.40); background-color: st-transparentize(-st-accent-color, 0.90); } +.usagebeam-disclosure:focus { box-shadow: inset 0 0 0 2px -st-accent-color; } +.usagebeam-disclosure-content { spacing: 6px; } +.usagebeam-disclosure-title { font-weight: bold; } +.usagebeam-disclosure-summary { font-size: 8.5pt; opacity: 0.62; } +.usagebeam-disclosure-icon { icon-size: 12px; opacity: 0.72; } +.usagebeam-details { spacing: 0; padding-top: 2px; } +.usagebeam-day-row { spacing: 8px; min-height: 23px; } +.usagebeam-day { min-width: 38px; font-size: 9pt; opacity: 0.70; } +.usagebeam-today .usagebeam-day { color: st-lighten(-st-accent-color, 20%); opacity: 1; font-weight: bold; } +.usagebeam-day-total { min-width: 52px; text-align: right; font-size: 8.5pt; opacity: 0.9; } +.usagebeam-model-meter { min-height: 28px; margin-bottom: 5px; background-color: rgba(128, 128, 160, 0.13); } +.usagebeam-model-fill { background-color: st-transparentize(-st-accent-color, 0.70); } +.usagebeam-model-content { padding: 5px 8px; } +.usagebeam-model-name { opacity: 0.92; } +.usagebeam-message { margin: 0 0 7px; padding: 8px 10px; border-left: 2px solid #f6d32d; border-radius: 3px; background-color: rgba(246, 211, 45, 0.06); font-size: 9pt; opacity: 0.85; } diff --git a/tests/integration/collector.js b/tests/integration/collector.js index 04821c3..2700997 100644 --- a/tests/integration/collector.js +++ b/tests/integration/collector.js @@ -5,15 +5,16 @@ import {scanHistory} from '../../src/services/history.js'; import {parseCodexEvent} from '../../src/providers/codex.js'; import {parseClaudeEvent} from '../../src/providers/claude.js'; import {commandSpec, findCommand} from '../../src/services/commands.js'; -import {readJson, writeJson, join} from '../../src/services/files.js'; +import {join, migrateLegacyData, readJson, writeJson} from '../../src/services/files.js'; import {requestJson} from '../../src/services/http.js'; +import {copyLegacySettings} from '../../src/services/migration.js'; import {RpcClient, runCommand} from '../../src/services/process.js'; function assert(value, message) { if (!value) throw new Error(message); } -const scratch = GLib.dir_make_tmp('freeby-integration-XXXXXX'); +const scratch = GLib.dir_make_tmp('usagebeam-integration-XXXXXX'); const sessions = join(scratch, 'sessions'); GLib.mkdir_with_parents(sessions, 0o700); const fixture = join(sessions, 'synthetic.jsonl'); @@ -46,6 +47,35 @@ const stateInfo = Gio.File.new_for_path(privateRecord).query_info('unix::mode', assert((stateInfo.get_attribute_uint32('unix::mode') & 0o777) === 0o600, 'private JSON file mode'); print('PASS: GJS history initial scan, cached scan, append, partial line, and private JSON cache'); +const legacyState = join(scratch, 'legacy-state'); +const legacyCache = join(scratch, 'legacy-cache'); +const migratedState = join(scratch, 'usagebeam-state'); +const migratedCache = join(scratch, 'usagebeam-cache'); +writeJson(join(legacyState, 'codex.json'), {provider: 'codex'}); +writeJson(join(legacyCache, 'history-codex.json'), {version: 4}); +writeJson(join(legacyState, 'unrelated.json'), {private: true}); +assert(migrateLegacyData({legacyState, legacyCache, state: migratedState, cache: migratedCache}) === 2, + 'legacy migration must copy only recognized derived data'); +assert(readJson(join(migratedState, 'codex.json')).provider === 'codex', 'provider state migration'); +assert(readJson(join(migratedCache, 'history-codex.json')).version === 4, 'history cache migration'); +assert(!Gio.File.new_for_path(join(migratedState, 'unrelated.json')).query_exists(null), + 'legacy migration must ignore unrelated files'); +assert(migrateLegacyData({legacyState, legacyCache, state: migratedState, cache: migratedCache}) === 0, + 'legacy migration must not overwrite migrated data'); +print('PASS: GJS legacy derived-data migration'); + +const currentSettings = new Map([['default-provider', 'claude']]); +const formerSettings = new Map([['default-provider', 'codex'], ['refresh-interval', 30]]); +const fakeSettings = values => ({ + get_user_value: key => values.get(key) ?? null, + set_value(key, value) { values.set(key, value); return true; }, +}); +assert(copyLegacySettings(fakeSettings(currentSettings), fakeSettings(formerSettings)) === 1, + 'settings migration must copy only missing explicit values'); +assert(currentSettings.get('default-provider') === 'claude' && currentSettings.get('refresh-interval') === 30, + 'settings migration must preserve UsageBeam values'); +print('PASS: GJS legacy settings migration policy'); + const replacementRoot = join(scratch, 'replacement'); GLib.mkdir_with_parents(replacementRoot, 0o700); const replacement = join(replacementRoot, 'same-size.jsonl'); diff --git a/tools/check.js b/tools/check.js index bc3b6fc..8b35c96 100644 --- a/tools/check.js +++ b/tools/check.js @@ -39,8 +39,10 @@ for (const entrypoint of ['extension.js', 'prefs.js']) { throw new Error(`${entrypoint}: GNOME entry points must remain thin`); } const metadata = JSON.parse(readFileSync('metadata.json', 'utf8')); -if (metadata.uuid !== 'freeby@kelvin.local') +if (metadata.uuid !== 'usagebeam@oo7kc.github.io') throw new Error('Extension identity changed without a migration'); +if (metadata.name !== 'UsageBeam' || metadata['settings-schema'] !== 'org.gnome.shell.extensions.usagebeam') + throw new Error('UsageBeam product metadata is inconsistent'); const version = JSON.parse(readFileSync('package.json', 'utf8')).version; if (!readFileSync('meson.build', 'utf8').includes(`version: '${version}'`)) throw new Error('Meson and package versions differ'); @@ -53,7 +55,7 @@ for (const file of required) { throw new Error(`Missing repository guidance: ${file}`); } const schemas = walk('schemas').filter(file => file.endsWith('.xml')); -if (schemas.length !== 1 || schemas[0] !== 'schemas/org.gnome.shell.extensions.freeby.gschema.xml') +if (schemas.length !== 1 || schemas[0] !== 'schemas/org.gnome.shell.extensions.usagebeam.gschema.xml') throw new Error('Unexpected extension schema input'); const obsolete = ['plan.md', 'indicator.js', 'src/providers/legacy.js', 'scripts/freeby.sh', 'scripts/copilot-setup.sh', 'tests/freeby.bats', 'docs/s1.png', 'docs/s2.png', 'reference-images']; diff --git a/tools/package.py b/tools/package.py index e86f04b..bb3fb66 100644 --- a/tools/package.py +++ b/tools/package.py @@ -33,13 +33,16 @@ def package(source, output): raise RuntimeError('package.json contains an invalid release version') files = [source / name for name in ('extension.js', 'prefs.js', 'metadata.json', 'stylesheet.css', 'LICENSE')] files.extend(sorted((source / 'src').rglob('*.js'))) - files.append(source / 'schemas' / 'org.gnome.shell.extensions.freeby.gschema.xml') + schema = metadata.get('settings-schema') + if schema != 'org.gnome.shell.extensions.usagebeam': + raise RuntimeError('metadata.json contains an unexpected settings schema') + files.append(source / 'schemas' / f'{schema}.gschema.xml') files = [checked_file(source, file) for file in files] output.mkdir(parents=True, exist_ok=True) archive = output / f'{uuid}-{version}.zip' if archive.is_symlink(): raise RuntimeError(f'Refusing to overwrite archive symlink: {archive}') - with tempfile.TemporaryDirectory(prefix='freeby-schemas-') as scratch: + with tempfile.TemporaryDirectory(prefix='usagebeam-schemas-') as scratch: subprocess.run(['glib-compile-schemas', '--strict', '--targetdir', scratch, str(source / 'schemas')], check=True) payloads = [(file.relative_to(source).as_posix(), file.read_bytes()) for file in files] payloads.append(('schemas/gschemas.compiled', (Path(scratch) / 'gschemas.compiled').read_bytes())) diff --git a/tools/smoke-shell.py b/tools/smoke-shell.py index d361e8d..c53a121 100644 --- a/tools/smoke-shell.py +++ b/tools/smoke-shell.py @@ -11,13 +11,17 @@ import time import zipfile +UUID = 'usagebeam@oo7kc.github.io' +SCHEMA = 'org.gnome.shell.extensions.usagebeam' +PRODUCT_DIRECTORY = 'usagebeam' + def run(command, env, **options): return subprocess.run(command, env=env, text=True, capture_output=True, timeout=15, **options) def install(source, archive, prefix, destination): - extension = prefix / 'share' / 'gnome-shell' / 'extensions' / 'freeby@kelvin.local' + extension = prefix / 'share' / 'gnome-shell' / 'extensions' / UUID if archive: with zipfile.ZipFile(archive) as payload: for entry in payload.infolist(): @@ -69,7 +73,7 @@ def record(provider, name, plan, percentages, models): {'model': 'gpt-6-astra', 'total': 790_000, 'input': 42_000, 'output': 62_000, 'cacheRead': 686_000, 'cacheWrite': 0}, ] - target = destination / 'state' / 'freeby' + target = destination / 'state' / PRODUCT_DIRECTORY target.mkdir(parents=True, exist_ok=True) (target / 'codex.json').write_text(json.dumps(record('codex', 'Codex', 'Pro', (24, 61), models))) (target / 'claude.json').write_text(json.dumps(record('claude', 'Claude Code', 'Pro', (17, 43), models[:2]))) @@ -86,10 +90,10 @@ def smoke(source, archive, destination): 'XDG_CONFIG_HOME': str(destination / 'config'), 'XDG_DATA_HOME': str(prefix / 'share'), 'XDG_CACHE_HOME': str(destination / 'cache'), 'XDG_STATE_HOME': str(destination / 'state'), 'GSETTINGS_BACKEND': 'keyfile', - 'GSETTINGS_SCHEMA_DIR': str(prefix / 'share/gnome-shell/extensions/freeby@kelvin.local/schemas'), + 'GSETTINGS_SCHEMA_DIR': str(prefix / 'share/gnome-shell/extensions' / UUID / 'schemas'), 'LIBGL_ALWAYS_SOFTWARE': '1'} - run(['gsettings', 'set', 'org.gnome.shell.extensions.freeby', 'enabled-providers', "['codex', 'claude']"], env, check=True) - run(['gsettings', 'set', 'org.gnome.shell', 'enabled-extensions', "['freeby@kelvin.local']"], env, check=True) + run(['gsettings', 'set', SCHEMA, 'enabled-providers', "['codex', 'claude']"], env, check=True) + run(['gsettings', 'set', 'org.gnome.shell', 'enabled-extensions', f"['{UUID}']"], env, check=True) run(['gsettings', 'set', 'org.gnome.shell', 'welcome-dialog-last-shown-version', '999'], env) bus = subprocess.Popen(['dbus-daemon', '--session', '--nofork', '--print-address=1'], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True) env['DBUS_SESSION_BUS_ADDRESS'] = bus.stdout.readline().strip() @@ -98,9 +102,9 @@ def smoke(source, archive, destination): try: with log_path.open('w') as log: shell = subprocess.Popen(['gnome-shell', '--headless', '--no-x11', '--virtual-monitor', '1280x1024', - '--wayland-display', 'freeby-test', '--debug-control'], env=env, stdout=log, stderr=log, start_new_session=True) + '--wayland-display', 'usagebeam-test', '--debug-control'], env=env, stdout=log, stderr=log, start_new_session=True) command = ['gdbus', 'call', '--session', '--dest', 'org.gnome.Shell.Extensions', '--object-path', '/org/gnome/Shell/Extensions', - '--method', 'org.gnome.Shell.Extensions.GetExtensionInfo', 'freeby@kelvin.local'] + '--method', 'org.gnome.Shell.Extensions.GetExtensionInfo', UUID] deadline = time.monotonic() + 30 info = '' while time.monotonic() < deadline and shell.poll() is None: @@ -114,11 +118,11 @@ def smoke(source, archive, destination): else: raise RuntimeError(f'Extension did not become active: {info}') print('PASS: packaged extension loads in a private GNOME Shell session') - run(['gnome-extensions', 'disable', 'freeby@kelvin.local'], env, check=True) - run(['gnome-extensions', 'enable', 'freeby@kelvin.local'], env, check=True) + run(['gnome-extensions', 'disable', UUID], env, check=True) + run(['gnome-extensions', 'enable', UUID], env, check=True) print('PASS: extension disable/re-enable') # Leave no collector running while the private shell itself shuts down. - run(['gnome-extensions', 'disable', 'freeby@kelvin.local'], env, check=True) + run(['gnome-extensions', 'disable', UUID], env, check=True) finally: if shell and shell.poll() is None: os.killpg(shell.pid, signal.SIGTERM) @@ -131,7 +135,7 @@ def smoke(source, archive, destination): bus.wait(timeout=5) print(f'GNOME log: {log_path}') contents = log_path.read_text(errors='replace') - if 'Gjs-CRITICAL' in contents and 'freeby@kelvin.local' in contents: + if 'Gjs-CRITICAL' in contents and UUID in contents: raise RuntimeError(f'Extension emitted a GJS critical; inspect {log_path}') @@ -142,4 +146,4 @@ def smoke(source, archive, destination): parser.add_argument('--output', type=Path) args = parser.parse_args() smoke(args.source.resolve(), args.archive.resolve() if args.archive else None, - args.output or Path(tempfile.mkdtemp(prefix='freeby-shell-'))) + args.output or Path(tempfile.mkdtemp(prefix='usagebeam-shell-'))) From d544e9489e7553885f7c283de14096f993922779 Mon Sep 17 00:00:00 2001 From: kcnewman Date: Mon, 7 Sep 2026 06:30:38 +0000 Subject: [PATCH 26/44] feat: refine active provider usage interface --- .AGENTS/plans/roadmap.md | 39 +- CHANGELOG.md | 13 +- README.md | 11 +- docs/architecture.md | 15 +- extension.js | 26 +- icons/claude.svg | 8 + icons/codex-symbolic.svg | 6 + meson.build | 1 + prefs.js | 16 + ...ome.shell.extensions.usagebeam.gschema.xml | 9 + src/core/format.js | 13 + src/ui/indicator.js | 101 ++--- src/ui/presentation.js | 39 ++ src/ui/widgets.js | 60 +++ stylesheet.css | 347 +++++++++++++++--- tests/unit/presentation.test.js | 29 +- tests/unit/usage.test.js | 7 +- tools/check.js | 4 + tools/package.py | 1 + tools/smoke-shell.py | 4 + 20 files changed, 628 insertions(+), 121 deletions(-) create mode 100644 icons/claude.svg create mode 100644 icons/codex-symbolic.svg diff --git a/.AGENTS/plans/roadmap.md b/.AGENTS/plans/roadmap.md index c1a8048..11ad9ac 100644 --- a/.AGENTS/plans/roadmap.md +++ b/.AGENTS/plans/roadmap.md @@ -1,7 +1,8 @@ # UsageBeam usage monitor roadmap -Updated: 2026-09-07. Status: UsageBeam identity migration complete on `dev`; -Cursor remains the next alpha.3 provider milestone. +Updated: 2026-09-07. Status: the UsageBeam identity and active-provider +interface refinements are complete on `dev`; Cursor remains the next alpha.3 +provider milestone. ## Agreed direction @@ -28,13 +29,14 @@ The [Claude reference](../references/claude-usage.jpeg) and 1. Provider mark, name, and reported plan. 2. A selector for connected/enabled providers; omit the selector when only one exists. 3. Independently labeled quota windows with percent-used meters and reset countdowns. -4. Seven daily token totals, with today emphasized. +4. A seven-column daily token chart, with today emphasized. 5. Model totals shown as horizontal bars. Adapt that hierarchy to GNOME using native St/Clutter widgets, theme-aware colors, aligned/tabular numerals, restrained separators, visible focus, and accessible labels. Add a footer with last-successful-update time, refresh, and preferences. -Limit popup height to the monitor work area and scroll content when needed. +Keep activity collapsed by default; when opened, expand the popup naturally +without a nested scrolling region. - Quota bars use a fixed 0–100% scale. Activity bars scale against the largest value in the displayed period. - Daily and model breakdowns must clearly state their period and whether the source is local or account-wide. @@ -172,8 +174,8 @@ re-enabled without extension errors in an isolated GNOME Shell 50 session. - [x] Match the supplied Omarchy hierarchy with a compact, theme-integrated panel, visible data status, equal provider tabs, and left-anchored graphics. - [x] Keep limits and freshness in the default view, move daily/model activity - behind an accessible disclosure, and retain scrolling only as a constrained - display or expanded-content fallback. + behind an accessible disclosure, and let the complete activity view expand + without a nested scrollbar. - [x] Inherit the user's Shell font and accent, place the subtle accent on the popup perimeter, and consolidate actions into a compact footer. - [x] Detect user-local and version-managed Codex installations from GNOME @@ -237,6 +239,27 @@ errors in a private GNOME Shell 50 session. The former local extension package was removed and the new UUID was installed and enabled for the next login; GNOME Shell 50 cannot discover a brand-new UUID in an already-running Wayland session. +### Active-provider interface checkpoint + +- [x] Replace the generic panel label with one compact active-provider readout: + adaptive provider icon, short name, highest current quota, and reset countdown. +- [x] Put quota reset countdowns beside their percentages, rename the Codex + reserve window to `Weekly reserve` for display, and remove redundant reset rows. +- [x] Render seven-day activity as an accessible, softly colored column chart + while retaining explicit local/account scope and measurement dates. +- [x] Remove the popup's internal scrolling region so activity expands to its + complete natural height, while remaining collapsed by default. +- [x] Add immediate left/right-of-calendar placement in native preferences and + package the supplied Claude and adaptive Codex SVG marks. + +Evidence: `npm run check` passed 28 deterministic unit tests, strict schema, +layout, identity, and GJS integration checks. Two builds produced the same +41,049-byte archive at SHA-256 +`0622c2af111f7f1985e99340af32fb99c1dd4020bb9f2f03ccee774c1973e542`. +That exact archive loaded with populated Codex and Claude records, moved live +from the left to the right of the calendar, disabled, and re-enabled without +extension errors in a private GNOME Shell 50 session. + ### 3. Cursor — v2.0.0-alpha.3 - [ ] Revalidate current Cursor authentication and usage sources for free and paid accounts. @@ -256,11 +279,11 @@ Shell 50 cannot discover a brand-new UUID in an already-running Wayland session. ### 5. Preferences and interface polish — v2.0.0-alpha.5 - [ ] Expand the existing preferences entry into organized native libadwaita pages. -- [ ] Add left/center/right panel placement and ordering within the selected panel area, with immediate updates. +- [x] Add immediate left/right-of-calendar panel placement for the compact active-provider readout. - [ ] Add provider enablement/order, default provider, refresh controls, and connection diagnostics. - [ ] Add notification thresholds, deduplication per account/window, and history retention/clear controls. - [ ] Preserve valid existing settings and document new defaults and migrations. -- [ ] Verify keyboard navigation, screen-reader labels, dark/light themes, long content, scrolling, and display scaling. +- [ ] Verify keyboard navigation, screen-reader labels, dark/light themes, long content, natural popup expansion, and display scaling. - [ ] Commit, tag, publish, and verify the installable alpha.5 prerelease using the finalized name and existing icon. ### 6. Stabilization — v2.0.0-beta.1, then v2.0.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 358db37..c501a30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,9 +28,15 @@ while version 2 is developed on the `dev` branch. - Inherit the user's Shell font and accent color, with a restrained accent on the popup's actual perimeter instead of a nested hard-coded blue frame. - Keep limits and freshness visible in the default view while placing seven-day - and model activity behind an accessible disclosure control; scrolling remains - an automatic fallback for the expanded view and smaller displays. + and model activity behind an accessible disclosure control; the complete view + now expands naturally without a nested scrollbar. - Consolidate freshness, refresh, and settings into a single compact footer. +- Replace the generic panel label with a single active-provider readout showing + its supplied icon, short name, highest current quota, and reset countdown. +- Place quota reset countdowns inline, display Codex reserve usage as `Weekly + reserve`, and render daily token history as a softly colored seven-column chart. +- Add native preferences for immediate placement directly left or right of the + Shell calendar. - Consolidated agent rules, the active roadmap, archived planning notes, and visual implementation references under `.AGENTS/`, with a minimal root `AGENTS.md` discovery entrypoint. @@ -57,7 +63,8 @@ while version 2 is developed on the `dev` branch. runtime, restoring live account quota reads on systems without `/usr/bin/node`. - Anchor daily and model fills to the left edge and render human-readable model names instead of centered or malformed bars. -- Reset the popup to its top edge whenever it opens. +- Preserve the user's Shell typeface while strengthening text contrast and + hierarchy for cleaner rendering. ### Removed diff --git a/README.md b/README.md index 58004e7..6964edc 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,8 @@ dedicated milestones are completed. - Explicit unavailable, unsupported, missing-authentication, and exhausted states. - Bounded collectors, refresh backoff, wake refresh, cancellation on disable, and threshold-crossing notifications. -- A native, theme-aware, keyboard-focusable GNOME panel interface. +- A native, theme-aware, keyboard-focusable interface with a compact active-provider + panel readout and expandable seven-day activity chart. UsageBeam stores only derived usage metadata under the standard XDG state/cache directories. It does not copy prompts, responses, transcripts, or credentials. @@ -66,10 +67,10 @@ active, the former `freeby@kelvin.local` extension can be uninstalled. ## Settings -The alpha.2 preferences window controls refresh frequency and notifications. -The underlying schema also supports the default provider, ordered enabled -providers, history retention, and notification threshold; these receive their -full preferences interface in alpha.5. +The development preferences window controls left/right-of-calendar placement, +refresh frequency, and notifications. The underlying schema also supports the +default provider, ordered enabled providers, history retention, and notification +threshold; these receive their full preferences interface in alpha.5. To opt into a preview adapter during development: diff --git a/docs/architecture.md b/docs/architecture.md index 9357de8..44238fb 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -6,7 +6,8 @@ never reads provider credentials or transcript formats. ## Runtime flow -1. `extension.js` creates the indicator and `UsageService`. +1. `extension.js` creates the indicator, places it beside the Shell calendar + according to preferences, and creates `UsageService`. 2. `UsageService` loads cached provider records, schedules independent refreshes, and starts one bounded GJS collector per enabled provider. Command discovery is isolated in `src/services/commands.js`, including user-local and supported @@ -59,9 +60,9 @@ not billing data and never becomes a fabricated subscription percentage. ## Distribution -Meson installs only extension entry points, source modules, metadata, styles, -license, and an extension-local schema. `tools/package.py` uses the same explicit -runtime allowlist to produce deterministic ZIP files. Inputs must be regular, -repository-owned files, and the completed archive is checked against the exact -allowlist; agent material, docs, tests, tools, screenshots, and references cannot -enter a release archive. +Meson installs only extension entry points, source modules, provider SVG marks, +metadata, styles, license, and an extension-local schema. `tools/package.py` +uses the same explicit runtime allowlist to produce deterministic ZIP files. +Inputs must be regular, repository-owned files, and the completed archive is +checked against the exact allowlist; agent material, docs, tests, tools, +screenshots, and references cannot enter a release archive. diff --git a/extension.js b/extension.js index b66c745..bcf84e8 100644 --- a/extension.js +++ b/extension.js @@ -8,8 +8,9 @@ export default class UsageBeamExtension extends Extension { enable() { this._settings = this.getSettings('org.gnome.shell.extensions.usagebeam'); migrateLegacyInstall(this._settings); - this._indicator = new UsageBeamIndicator(this._settings, () => this.openPreferences()); - Main.panel.addToStatusArea(this.uuid, this._indicator); + this._indicator = new UsageBeamIndicator(this._settings, this.path, () => this.openPreferences()); + Main.panel.addToStatusArea(this.uuid, this._indicator, 0, 'center'); + this._placeIndicator(); this._service = new UsageService(this._settings, this.path, () => this._indicator?.render(), alerts => { for (const alert of alerts) { const state = alert.threshold === 100 ? 'limit reached' : `reached ${alert.threshold}%`; @@ -24,11 +25,32 @@ export default class UsageBeamExtension extends Extension { this._service.refreshAll(); })); this._settingsIds.push(this._settings.connect('changed::default-provider', () => this._indicator.render())); + this._settingsIds.push(this._settings.connect('changed::panel-position', () => this._placeIndicator())); this._settingsIds.push(this._settings.connect('changed::history-retention-days', () => this._service.refreshAll(true))); this._service.refreshAll(); } + _placeIndicator() { + const container = this._indicator?.container; + if (!container) + return; + const calendar = Main.panel.statusArea.dateMenu?.container; + const panelBox = calendar?.get_parent() ?? container.get_parent(); + if (!panelBox) + return; + const parent = container.get_parent(); + if (parent) + parent.remove_child(container); + const siblings = panelBox.get_children(); + const calendarIndex = calendar ? siblings.indexOf(calendar) : -1; + const side = this._settings.get_string('panel-position'); + const position = calendarIndex < 0 + ? (side === 'left-of-calendar' ? 0 : siblings.length) + : calendarIndex + (side === 'right-of-calendar' ? 1 : 0); + panelBox.insert_child_at_index(container, position); + } + disable() { for (const id of this._settingsIds ?? []) this._settings.disconnect(id); diff --git a/icons/claude.svg b/icons/claude.svg new file mode 100644 index 0000000..0489ce8 --- /dev/null +++ b/icons/claude.svg @@ -0,0 +1,8 @@ + + Claude + + diff --git a/icons/codex-symbolic.svg b/icons/codex-symbolic.svg new file mode 100644 index 0000000..0f55071 --- /dev/null +++ b/icons/codex-symbolic.svg @@ -0,0 +1,6 @@ + + Codex + + diff --git a/meson.build b/meson.build index 49ad819..5116c41 100644 --- a/meson.build +++ b/meson.build @@ -11,6 +11,7 @@ install_data('extension.js', 'prefs.js', 'metadata.json', 'stylesheet.css', 'LIC install_dir: extdir) install_subdir('src', install_dir: extdir) +install_subdir('icons', install_dir: extdir) install_data('schemas/org.gnome.shell.extensions.usagebeam.gschema.xml', install_dir: schemadir) diff --git a/prefs.js b/prefs.js index db0e11d..0cef873 100644 --- a/prefs.js +++ b/prefs.js @@ -13,6 +13,22 @@ export default class UsageBeamPreferences extends ExtensionPreferences { const group = new Adw.PreferencesGroup({title: 'General'}); page.add(group); + const positions = ['left-of-calendar', 'right-of-calendar']; + const positionRow = new Adw.ComboRow({ + title: 'Panel position', + subtitle: 'Place the active provider beside the calendar', + model: Gtk.StringList.new(['Left of calendar', 'Right of calendar']), + }); + const syncPosition = () => { + const selected = positions.indexOf(settings.get_string('panel-position')); + positionRow.selected = selected >= 0 ? selected : 1; + }; + syncPosition(); + positionRow.connect('notify::selected', () => + settings.set_string('panel-position', positions[positionRow.selected])); + settings.connect('changed::panel-position', syncPosition); + group.add(positionRow); + const intervalRow = new Adw.SpinRow({ title: 'Refresh interval', subtitle: 'How often to check usage (seconds, min 30)', diff --git a/schemas/org.gnome.shell.extensions.usagebeam.gschema.xml b/schemas/org.gnome.shell.extensions.usagebeam.gschema.xml index f03f664..c3b025b 100644 --- a/schemas/org.gnome.shell.extensions.usagebeam.gschema.xml +++ b/schemas/org.gnome.shell.extensions.usagebeam.gschema.xml @@ -21,6 +21,15 @@ 'codex' Selected usage provider + + + + + + 'right-of-calendar' + Position beside the calendar + Place the UsageBeam indicator immediately left or right of the panel calendar + 30 diff --git a/src/core/format.js b/src/core/format.js index 8995e9d..5b91b1b 100644 --- a/src/core/format.js +++ b/src/core/format.js @@ -8,6 +8,10 @@ export function tokens(value) { return String(Math.round(value)); } +export function compactTokens(value) { + return tokens(value).replace(/\.0(?=[BMK]$)/, ''); +} + export function resetTime(time, now = Date.now()) { if (!Number.isFinite(time) || time <= 0) return 'Reset time unavailable'; @@ -21,6 +25,15 @@ export function resetTime(time, now = Date.now()) { return `Resets in ${minutes}m`; } +export function resetCountdown(time, now = Date.now()) { + const value = resetTime(time, now); + if (value.startsWith('Resets in ')) + return value.slice('Resets in '.length); + if (value.startsWith('Reset due')) + return 'due'; + return null; +} + export function age(time, now = Date.now()) { if (!time) return 'Never updated'; diff --git a/src/ui/indicator.js b/src/ui/indicator.js index d5b1db2..4959bcd 100644 --- a/src/ui/indicator.js +++ b/src/ui/indicator.js @@ -4,44 +4,38 @@ import St from 'gi://St'; import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js'; import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js'; -import {NAMES, highestUsage, recentDates} from '../core/usage.js'; -import {age, modelName, resetTime, tokens} from '../core/format.js'; -import {historyOverview, latestUpdate, periodDays, providerStatus} from './presentation.js'; -import {actionButton, button, disclosureButton, label, meter, modelMeter, row} from './widgets.js'; +import {NAMES, recentDates} from '../core/usage.js'; +import {age, modelName, tokens} from '../core/format.js'; +import {historyOverview, latestUpdate, panelQuota, periodDays, providerStatus, + quotaPresentation} from './presentation.js'; +import {actionButton, button, dayChart, disclosureButton, label, meter, modelMeter, + providerIcon, row} from './widgets.js'; const TAB_NAMES = {claude: 'Claude'}; const PROVIDER_MARKS = {codex: '>_', claude: '✦', cursor: '⌁', copilot: '◆'}; export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator extends PanelMenu.Button { - _init(settings, openPreferences) { + _init(settings, extensionPath, openPreferences) { super._init(0.0, 'UsageBeam usage monitor'); this._settings = settings; + this._extensionPath = extensionPath; this._openPreferences = openPreferences; this._service = null; this._detailsExpanded = false; - this._panelLabel = label('AI', 'usagebeam-panel-label'); - this.add_child(this._panelLabel); + this._panelStatus = new St.BoxLayout({style_class: 'usagebeam-panel-status'}); + this.add_child(this._panelStatus); this.menu.actor.add_style_class_name('usagebeam-menu'); const section = new PopupMenu.PopupMenuSection(); this.menu.addMenuItem(section); // `content` is an inherited Clutter.Actor property whose value must be // ClutterContent, so keep the menu actor under an unambiguous name. this._contentBox = new St.BoxLayout({vertical: true, style_class: 'usagebeam-content', x_expand: true}); - this._scroll = new St.ScrollView({hscrollbar_policy: St.PolicyType.NEVER, vscrollbar_policy: St.PolicyType.AUTOMATIC, - overlay_scrollbars: true}); - // St.ScrollView is a Clutter actor in GNOME 50. Its inherited - // set_child() targets ClutterContent, so add the scrollable actor as a - // child explicitly. - this._scroll.add_child(this._contentBox); - section.box.add_child(this._scroll); + section.box.add_child(this._contentBox); this.menu.connect('open-state-changed', (_menu, open) => { if (open) { this.resize(); this._service?.refreshAll(); this.render(); - const adjustment = this._scroll.vscroll?.adjustment; - if (adjustment) - adjustment.value = 0; } else { this._detailsExpanded = false; } @@ -52,10 +46,8 @@ export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator resize() { const monitor = Main.layoutManager.findMonitorForActor(this) ?? Main.layoutManager.primaryMonitor; const scale = St.ThemeContext.get_for_stage(global.stage).scale_factor; - const availableHeight = Math.max(240, Math.floor(monitor.height / scale) - 96); const availableWidth = Math.max(240, Math.floor(monitor.width / scale) - 32); - this._scroll.style = `max-height: ${Math.min(600, availableHeight)}px;`; - this._contentBox.style = `width: ${Math.min(348, availableWidth)}px;`; + this._contentBox.style = `width: ${Math.min(392, availableWidth)}px;`; } attach(service) { @@ -73,12 +65,9 @@ export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator const requested = this._settings.get_string('default-provider'); const id = enabled.includes(requested) ? requested : enabled[0]; const record = this._service?.recordFor(id); - const percent = highestUsage(record); const busy = this._service?.isRefreshing(id) ?? false; - this._panelLabel.text = percent === null ? 'AI' : `AI · ${Math.round(percent)}%`; - this._panelLabel.style_class = `usagebeam-panel-label${percent >= 100 ? ' usagebeam-danger' : percent >= 90 ? ' usagebeam-warning' : ''}`; - this.accessible_name = `UsageBeam, ${record?.name ?? 'usage monitor'}${percent === null ? '' : `, ${Math.round(percent)} percent used`}`; + this._renderPanelStatus(id, record); this._renderHeader(id, record, busy); if (enabled.length > 1) @@ -93,11 +82,39 @@ export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator this._restoreFocus(restoreName); } + _renderPanelStatus(id, record) { + this._panelStatus.destroy_all_children(); + const name = TAB_NAMES[id] ?? NAMES[id] ?? 'UsageBeam'; + const icon = providerIcon(id, this._extensionPath, 'usagebeam-panel-icon'); + if (icon) + this._panelStatus.add_child(icon); + else + this._panelStatus.add_child(label(PROVIDER_MARKS[id] ?? 'AI', 'usagebeam-panel-mark')); + this._panelStatus.add_child(label(name, 'usagebeam-panel-provider')); + const quota = panelQuota(record); + if (quota) { + this._panelStatus.add_child(label(`${quota.percent}%`, + `usagebeam-panel-value${quota.percent >= 100 ? ' usagebeam-danger' : + quota.percent >= 90 ? ' usagebeam-warning' : ''}`)); + if (quota.reset) { + this._panelStatus.add_child(label('·', 'usagebeam-panel-separator')); + this._panelStatus.add_child(label(quota.reset, 'usagebeam-panel-reset')); + } + } + const resetDescription = quota?.reset === 'due' ? ', reset due' : + quota?.reset ? `, resets in ${quota.reset}` : ''; + this.accessible_name = `UsageBeam, ${name}${quota ? `, ${quota.percent} percent used${resetDescription}` : ''}`; + } + _renderHeader(id, record, busy) { const header = new St.BoxLayout({style_class: 'usagebeam-header', x_expand: true}); - header.add_child(label(PROVIDER_MARKS[id] ?? 'AI', `usagebeam-provider-mark usagebeam-${id}-mark`)); + const icon = providerIcon(id, this._extensionPath, 'usagebeam-header-icon'); + if (icon) + header.add_child(icon); + else + header.add_child(label(PROVIDER_MARKS[id] ?? 'AI', 'usagebeam-provider-mark')); const identity = new St.BoxLayout({vertical: true, style_class: 'usagebeam-identity', x_expand: true}); - identity.add_child(label(record?.name ?? 'UsageBeam', 'usagebeam-title')); + identity.add_child(label(TAB_NAMES[id] ?? record?.name ?? 'UsageBeam', 'usagebeam-title')); identity.add_child(label(record?.plan ? String(record.plan).toUpperCase() : 'USAGE MONITOR', 'usagebeam-caption')); header.add_child(identity); const statusText = providerStatus(record, busy); @@ -121,13 +138,18 @@ export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator this._heading('LIMITS'); for (const window of limits.windows) { const box = new St.BoxLayout({vertical: true, style_class: 'usagebeam-window'}); - const value = window.unlimited ? 'Unlimited' : `${Math.round(window.usedPercent)}%`; - box.add_child(row(window.label, value)); + const view = quotaPresentation(window); + if (!view) + continue; + const summary = [view.value, view.reset].filter(Boolean).join(' · '); + box.add_child(row(view.name, summary, 'usagebeam-limit-row')); if (!window.unlimited) { const level = window.usedPercent >= 100 ? 'usagebeam-danger' : window.usedPercent >= 90 ? 'usagebeam-warning' : ''; - box.add_child(meter(window.usedPercent / 100, `${window.label}: ${value} used`, level)); - box.add_child(label(resetTime(window.resetsAt), 'usagebeam-caption')); + const resetDescription = view.reset === 'due' ? ', reset due' : + view.reset ? `, resets in ${view.reset}` : ''; + box.add_child(meter(window.usedPercent / 100, + `${view.name}: ${view.value} used${resetDescription}`, level)); } this._contentBox.add_child(box); } @@ -164,25 +186,12 @@ export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator } _renderDays(history, parent = this._contentBox) { - this._heading('TOKENS BY DAY', parent); + const overview = historyOverview(history); + this._heading(`LAST ${overview?.days ?? history.days.length} DAYS · ${tokens(overview?.total ?? 0)} TOKENS`, parent); const scope = history.scope === 'account' ? 'Account activity' : 'This device'; parent.add_child(label(`${scope} · ${history.period.start} – ${history.period.end}`, 'usagebeam-caption')); - const max = Math.max(1, ...history.days.map(day => day.total)); const today = recentDates(Date.now(), 1)[0]; - for (const day of history.days) { - const line = new St.BoxLayout({style_class: 'usagebeam-day-row', x_expand: true}); - const dayName = day.date === today ? 'Today' : - new Date(`${day.date}T12:00:00`).toLocaleDateString(undefined, {weekday: 'short'}); - line.add_child(label(dayName, 'usagebeam-day')); - if (day.date === today) - line.add_style_class_name('usagebeam-today'); - const bar = meter(day.total / max, `${day.date}: ${tokens(day.total)} tokens`); - bar.y_align = Clutter.ActorAlign.CENTER; - line.add_child(bar); - line.add_child(label(tokens(day.total), 'usagebeam-day-total')); - line.accessible_name = `${dayName}, ${day.total} tokens, ${day.sessions ?? 0} sessions`; - parent.add_child(line); - } + parent.add_child(dayChart(history.days, today)); } _renderModels(history, parent = this._contentBox) { diff --git a/src/ui/presentation.js b/src/ui/presentation.js index fc9424d..566c8fb 100644 --- a/src/ui/presentation.js +++ b/src/ui/presentation.js @@ -1,3 +1,5 @@ +import {resetCountdown} from '../core/format.js'; + const CURRENT_STATES = new Set(['ready', 'partial']); export function providerStatus(record, refreshing = false) { @@ -39,3 +41,40 @@ export function historyOverview(history) { total, }; } + +export function quotaName(window) { + const source = `${window?.id ?? ''} ${window?.label ?? ''}`.toLowerCase(); + if (source.includes('reserve') && + (source.includes('weekly') || window?.durationMinutes === 10080)) + return 'Weekly reserve'; + return String(window?.label ?? 'Usage limit'); +} + +export function quotaPresentation(window, now = Date.now()) { + if (!window) + return null; + if (window.unlimited) + return {name: quotaName(window), value: 'Unlimited', reset: null}; + if (!Number.isFinite(window.usedPercent)) + return null; + return { + name: quotaName(window), + value: `${Math.round(window.usedPercent)}%`, + reset: resetCountdown(window.resetsAt, now), + }; +} + +export function panelQuota(record, now = Date.now()) { + if (!CURRENT_STATES.has(record?.limits?.status)) + return null; + const candidates = record.limits.windows + .filter(window => !window.unlimited && Number.isFinite(window.usedPercent)) + .sort((left, right) => right.usedPercent - left.usedPercent); + const window = candidates[0]; + if (!window) + return null; + return { + percent: Math.round(window.usedPercent), + reset: resetCountdown(window.resetsAt, now), + }; +} diff --git a/src/ui/widgets.js b/src/ui/widgets.js index c7a09c5..a2ab04e 100644 --- a/src/ui/widgets.js +++ b/src/ui/widgets.js @@ -1,13 +1,29 @@ import Atk from 'gi://Atk'; import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; import Pango from 'gi://Pango'; import St from 'gi://St'; +import {compactTokens} from '../core/format.js'; + +const PROVIDER_ICONS = new Set(['claude', 'codex']); +const PROVIDER_ICON_FILES = {claude: 'claude.svg', codex: 'codex-symbolic.svg'}; export function label(text, style = '', expand = false) { return new St.Label({text: String(text ?? ''), style_class: style, y_align: Clutter.ActorAlign.CENTER, x_expand: expand}); } +export function providerIcon(provider, extensionPath, style = '') { + if (!PROVIDER_ICONS.has(provider)) + return null; + return new St.Icon({ + gicon: new Gio.FileIcon({ + file: Gio.File.new_for_path(`${extensionPath}/icons/${PROVIDER_ICON_FILES[provider]}`), + }), + style_class: `usagebeam-provider-icon usagebeam-${provider}-icon ${style}`.trim(), + }); +} + export function row(left, right, style = 'usagebeam-row') { const box = new St.BoxLayout({style_class: style, x_expand: true}); const title = label(left, '', true); @@ -54,6 +70,50 @@ export function modelMeter(left, right, fraction, name) { return track; } +export function dayChart(days, today) { + const values = Array.isArray(days) ? days : []; + const max = Math.max(1, ...values.map(day => day.total)); + const chart = new St.Widget({ + style_class: 'usagebeam-day-chart', + x_expand: true, + layout_manager: new Clutter.BoxLayout({ + homogeneous: true, + orientation: Clutter.Orientation.HORIZONTAL, + spacing: 6, + }), + accessible_name: 'Daily token activity chart', + accessible_role: Atk.Role.PANEL, + }); + for (const day of values) { + const isToday = day.date === today; + const weekday = new Date(`${day.date}T12:00:00`).toLocaleDateString(undefined, {weekday: 'short'}); + const column = new St.BoxLayout({ + vertical: true, + style_class: `usagebeam-chart-column${isToday ? ' usagebeam-today' : ''}`, + x_expand: true, + accessible_name: `${isToday ? 'Today, ' : ''}${weekday}, ${day.total} tokens, ${day.sessions ?? 0} sessions`, + }); + column.add_child(label(compactTokens(day.total), 'usagebeam-chart-value')); + const plot = new St.Widget({ + style_class: 'usagebeam-chart-plot', + x_expand: true, + layout_manager: new Clutter.BinLayout(), + }); + const bar = new St.Widget({ + style_class: 'usagebeam-chart-bar', + x_align: Clutter.ActorAlign.FILL, + y_align: Clutter.ActorAlign.END, + visible: day.total > 0, + height: Math.max(3, Math.round(day.total / max * 58)), + }); + plot.add_child(bar); + column.add_child(plot); + column.add_child(label(weekday, 'usagebeam-chart-day')); + chart.add_child(column); + } + return chart; +} + export function button(text, callback, {active = false, name = text} = {}) { const actor = new St.Button({label: text, can_focus: true, reactive: true, track_hover: true, accessible_name: name, style_class: 'usagebeam-button', x_expand: true}); diff --git a/stylesheet.css b/stylesheet.css index 52d8fbe..8fb2cfc 100644 --- a/stylesheet.css +++ b/stylesheet.css @@ -1,48 +1,299 @@ -.usagebeam-menu .popup-menu-content { border-color: st-transparentize(st-lighten(-st-accent-color, 20%), 0.58); } -.usagebeam-panel-label { font-weight: bold; font-size: 10pt; } -.usagebeam-content { padding: 9px 12px 10px; spacing: 0; } -.usagebeam-header { spacing: 8px; padding: 1px 0 8px; } -.usagebeam-identity { spacing: 1px; } -.usagebeam-provider-mark { min-width: 28px; min-height: 28px; padding: 3px; border-radius: 6px; background-color: st-transparentize(-st-accent-color, 0.88); color: st-lighten(-st-accent-color, 20%); font-size: 11pt; font-weight: bold; text-align: center; } -.usagebeam-claude-mark { background-color: rgba(255, 145, 90, 0.12); color: #ff9f6b; } -.usagebeam-title { font-size: 13pt; font-weight: bold; } -.usagebeam-caption { font-size: 8.5pt; opacity: 0.66; } -.usagebeam-status { padding: 3px 6px; border: 1px solid rgba(128, 128, 128, 0.30); border-radius: 5px; font-size: 8pt; opacity: 0.84; } -.usagebeam-status-live { color: #57e389; border-color: rgba(87, 227, 137, 0.5); background-color: rgba(87, 227, 137, 0.08); } -.usagebeam-status-sync { color: st-lighten(-st-accent-color, 20%); border-color: st-transparentize(-st-accent-color, 0.5); } -.usagebeam-status-local { color: #f6d32d; border-color: rgba(246, 211, 45, 0.45); } -.usagebeam-status-cached { color: #c0bfbc; border-color: rgba(192, 191, 188, 0.4); } -.usagebeam-selector { spacing: 6px; padding-bottom: 8px; } -.usagebeam-actions { spacing: 6px; padding-top: 8px; margin-top: 5px; border-top: 1px solid rgba(128, 128, 128, 0.20); } -.usagebeam-button { padding: 5px 10px; min-height: 24px; border: 1px solid rgba(128, 128, 128, 0.28); border-radius: 6px; background-color: rgba(128, 128, 128, 0.05); } -.usagebeam-button:hover, .usagebeam-button:focus { border-color: st-transparentize(st-lighten(-st-accent-color, 20%), 0.35); background-color: st-transparentize(-st-accent-color, 0.88); } -.usagebeam-button:checked { border-color: st-transparentize(st-lighten(-st-accent-color, 20%), 0.25); background-color: st-transparentize(-st-accent-color, 0.82); font-weight: bold; } -.usagebeam-button:focus { box-shadow: inset 0 0 0 2px -st-accent-color; } -.usagebeam-action-button { padding: 3px 8px; min-height: 22px; font-size: 8.5pt; } -.usagebeam-section-title { font-size: 8.5pt; font-weight: bold; opacity: 0.76; border-top: 1px solid rgba(128, 128, 128, 0.20); padding: 8px 0 5px; } -.usagebeam-row { spacing: 10px; } -.usagebeam-number { opacity: 0.9; } -.usagebeam-window { spacing: 3px; padding-bottom: 6px; } -.usagebeam-track { height: 4px; min-width: 20px; border-radius: 3px; background-color: rgba(128, 128, 128, 0.22); } -.usagebeam-fill { background-color: st-lighten(-st-accent-color, 20%); border-radius: 3px; } -.usagebeam-warning { color: #e5a50a; } -.usagebeam-danger { color: #e01b24; } -.usagebeam-warning .usagebeam-fill { background-color: #e5a50a; } -.usagebeam-danger .usagebeam-fill { background-color: #e01b24; } -.usagebeam-disclosure { margin-top: 2px; padding: 6px 8px; min-height: 26px; border: 1px solid rgba(128, 128, 128, 0.22); border-radius: 6px; background-color: rgba(128, 128, 128, 0.04); } -.usagebeam-disclosure:hover, .usagebeam-disclosure:focus { border-color: st-transparentize(st-lighten(-st-accent-color, 20%), 0.40); background-color: st-transparentize(-st-accent-color, 0.90); } -.usagebeam-disclosure:focus { box-shadow: inset 0 0 0 2px -st-accent-color; } -.usagebeam-disclosure-content { spacing: 6px; } -.usagebeam-disclosure-title { font-weight: bold; } -.usagebeam-disclosure-summary { font-size: 8.5pt; opacity: 0.62; } -.usagebeam-disclosure-icon { icon-size: 12px; opacity: 0.72; } -.usagebeam-details { spacing: 0; padding-top: 2px; } -.usagebeam-day-row { spacing: 8px; min-height: 23px; } -.usagebeam-day { min-width: 38px; font-size: 9pt; opacity: 0.70; } -.usagebeam-today .usagebeam-day { color: st-lighten(-st-accent-color, 20%); opacity: 1; font-weight: bold; } -.usagebeam-day-total { min-width: 52px; text-align: right; font-size: 8.5pt; opacity: 0.9; } -.usagebeam-model-meter { min-height: 28px; margin-bottom: 5px; background-color: rgba(128, 128, 160, 0.13); } -.usagebeam-model-fill { background-color: st-transparentize(-st-accent-color, 0.70); } -.usagebeam-model-content { padding: 5px 8px; } -.usagebeam-model-name { opacity: 0.92; } -.usagebeam-message { margin: 0 0 7px; padding: 8px 10px; border-left: 2px solid #f6d32d; border-radius: 3px; background-color: rgba(246, 211, 45, 0.06); font-size: 9pt; opacity: 0.85; } +.usagebeam-menu .popup-menu-content { + border-color: st-transparentize(st-lighten(-st-accent-color, 18%), 0.66); +} + +.usagebeam-panel-status { + spacing: 5px; +} + +.usagebeam-provider-icon { + icon-size: 20px; +} + +.usagebeam-panel-icon { + icon-size: 15px; +} + +.usagebeam-panel-provider, +.usagebeam-panel-value, +.usagebeam-panel-reset, +.usagebeam-panel-mark { + font-size: 9.5pt; +} + +.usagebeam-panel-provider { + font-weight: 600; +} + +.usagebeam-panel-value, +.usagebeam-panel-reset { + color: st-lighten(-st-accent-color, 24%); + font-weight: 500; +} + +.usagebeam-panel-separator { + opacity: 0.42; +} + +.usagebeam-content { + padding: 10px 13px 11px; + spacing: 0; +} + +.usagebeam-header { + spacing: 9px; + padding: 1px 0 9px; +} + +.usagebeam-header-icon { + icon-size: 27px; + margin: 1px 2px 1px 0; +} + +.usagebeam-identity { + spacing: 0; +} + +.usagebeam-provider-mark { + min-width: 27px; + min-height: 27px; + color: st-lighten(-st-accent-color, 20%); + font-size: 10pt; + font-weight: 600; + text-align: center; +} + +.usagebeam-title { + font-size: 12.5pt; + font-weight: 650; +} + +.usagebeam-caption { + font-size: 8.5pt; + opacity: 0.72; +} + +.usagebeam-status { + padding: 2px 1px; + font-size: 7.5pt; + font-weight: 600; + opacity: 0.88; +} + +.usagebeam-status-live { + color: #57e389; +} + +.usagebeam-status-sync { + color: st-lighten(-st-accent-color, 20%); +} + +.usagebeam-status-local { + color: #f6d32d; +} + +.usagebeam-status-cached { + color: #c0bfbc; +} + +.usagebeam-selector { + spacing: 7px; + padding-bottom: 8px; +} + +.usagebeam-button { + min-height: 24px; + padding: 5px 10px; + border: 1px solid rgba(128, 128, 128, 0.24); + border-radius: 5px; + background-color: rgba(128, 128, 128, 0.035); +} + +.usagebeam-button:hover, +.usagebeam-button:focus { + border-color: st-transparentize(st-lighten(-st-accent-color, 20%), 0.45); + background-color: st-transparentize(-st-accent-color, 0.91); +} + +.usagebeam-button:checked { + border-color: st-transparentize(st-lighten(-st-accent-color, 20%), 0.40); + background-color: st-transparentize(-st-accent-color, 0.86); + font-weight: 600; +} + +.usagebeam-button:focus, +.usagebeam-disclosure:focus { + box-shadow: inset 0 0 0 2px st-transparentize(-st-accent-color, 0.28); +} + +.usagebeam-section-title { + padding: 8px 0 5px; + border-top: 1px solid rgba(128, 128, 128, 0.19); + font-size: 8pt; + font-weight: 650; + opacity: 0.86; +} + +.usagebeam-row, +.usagebeam-limit-row { + spacing: 10px; +} + +.usagebeam-number { + font-weight: 500; + opacity: 0.94; +} + +.usagebeam-limit-row .usagebeam-number { + color: st-lighten(-st-accent-color, 25%); +} + +.usagebeam-window { + spacing: 4px; + padding-bottom: 7px; +} + +.usagebeam-track { + height: 4px; + min-width: 20px; + border-radius: 3px; + background-color: rgba(128, 128, 128, 0.19); +} + +.usagebeam-fill { + border-radius: 3px; + background-color: st-transparentize(st-lighten(-st-accent-color, 22%), 0.18); +} + +.usagebeam-warning { + color: #e5a50a; +} + +.usagebeam-danger { + color: #e01b24; +} + +.usagebeam-warning .usagebeam-fill { + background-color: #e5a50a; +} + +.usagebeam-danger .usagebeam-fill { + background-color: #e01b24; +} + +.usagebeam-disclosure { + min-height: 27px; + margin-top: 1px; + padding: 7px 1px; + border-top: 1px solid rgba(128, 128, 128, 0.19); + border-radius: 4px; + background-color: transparent; +} + +.usagebeam-disclosure:hover, +.usagebeam-disclosure:focus { + background-color: rgba(128, 128, 128, 0.065); +} + +.usagebeam-disclosure-content { + spacing: 6px; +} + +.usagebeam-disclosure-title { + font-weight: 600; +} + +.usagebeam-disclosure-summary { + font-size: 8.5pt; + opacity: 0.72; +} + +.usagebeam-disclosure-icon { + icon-size: 11px; + opacity: 0.68; +} + +.usagebeam-details { + spacing: 0; + padding-top: 1px; +} + +.usagebeam-day-chart { + min-height: 88px; + padding: 5px 0 7px; +} + +.usagebeam-chart-column { + spacing: 4px; +} + +.usagebeam-chart-value, +.usagebeam-chart-day { + font-size: 7.5pt; + text-align: center; + opacity: 0.70; +} + +.usagebeam-chart-plot { + height: 58px; +} + +.usagebeam-chart-bar { + margin: 0 4px; + border-radius: 3px 3px 1px 1px; + background-color: st-transparentize(st-lighten(-st-accent-color, 28%), 0.46); +} + +.usagebeam-today .usagebeam-chart-value, +.usagebeam-today .usagebeam-chart-day { + color: st-lighten(-st-accent-color, 25%); + font-weight: 600; + opacity: 1; +} + +.usagebeam-today .usagebeam-chart-bar { + background-color: st-transparentize(st-lighten(-st-accent-color, 25%), 0.22); +} + +.usagebeam-model-meter { + min-height: 27px; + margin-bottom: 5px; + border-radius: 3px; + background-color: rgba(128, 128, 160, 0.11); +} + +.usagebeam-model-fill { + border-radius: 3px; + background-color: st-transparentize(st-lighten(-st-accent-color, 17%), 0.72); +} + +.usagebeam-model-content { + padding: 5px 8px; +} + +.usagebeam-model-name { + opacity: 0.96; +} + +.usagebeam-actions { + spacing: 6px; + padding-top: 8px; + margin-top: 3px; + border-top: 1px solid rgba(128, 128, 128, 0.19); +} + +.usagebeam-action-button { + min-height: 21px; + padding: 3px 8px; + font-size: 8.5pt; +} + +.usagebeam-message { + margin: 0 0 7px; + padding: 8px 10px; + border-left: 2px solid #f6d32d; + border-radius: 3px; + background-color: rgba(246, 211, 45, 0.06); + font-size: 9pt; + opacity: 0.9; +} diff --git a/tests/unit/presentation.test.js b/tests/unit/presentation.test.js index 716bcf9..ca8876c 100644 --- a/tests/unit/presentation.test.js +++ b/tests/unit/presentation.test.js @@ -1,6 +1,7 @@ import test from 'node:test'; import assert from 'node:assert/strict'; -import {historyOverview, latestUpdate, periodDays, providerStatus} from '../../src/ui/presentation.js'; +import {historyOverview, latestUpdate, panelQuota, periodDays, providerStatus, + quotaName, quotaPresentation} from '../../src/ui/presentation.js'; test('provider status distinguishes live, local, cached and setup data', () => { const value = {limits: {status: 'ready'}, history: {status: 'ready'}}; @@ -37,3 +38,29 @@ test('history overview summarizes daily activity without double-counting models' assert.equal(historyOverview({...history, days: [], models: []}), null); assert.equal(historyOverview(null), null); }); + +test('quota presentation keeps names concise and reset times inline', () => { + const now = 1_000_000; + const reserve = {id: 'gpt-reserve:primary', label: 'Reserve · Weekly', + durationMinutes: 10080, usedPercent: 18.2, resetsAt: now + 4 * 86400000 + 22 * 3600000}; + assert.equal(quotaName(reserve), 'Weekly reserve'); + assert.deepEqual(quotaPresentation(reserve, now), { + name: 'Weekly reserve', value: '18%', reset: '4d 22h', + }); + assert.deepEqual(quotaPresentation({id: 'credits', label: 'Credits', unlimited: true}, now), { + name: 'Credits', value: 'Unlimited', reset: null, + }); + assert.equal(quotaPresentation({id: 'broken', label: 'Broken'}, now), null); +}); + +test('panel quota reports only the highest current quota window', () => { + const now = 1_000_000; + const record = {limits: {status: 'ready', windows: [ + {usedPercent: 15, resetsAt: now + 3600000}, + {usedPercent: 46, resetsAt: now + 15 * 3600000 + 59 * 60000}, + {usedPercent: null, unlimited: true}, + ]}}; + assert.deepEqual(panelQuota(record, now), {percent: 46, reset: '15h 59m'}); + assert.equal(panelQuota({limits: {...record.limits, status: 'stale'}}, now), null); + assert.equal(panelQuota(null, now), null); +}); diff --git a/tests/unit/usage.test.js b/tests/unit/usage.test.js index 5e8954c..cd436ea 100644 --- a/tests/unit/usage.test.js +++ b/tests/unit/usage.test.js @@ -1,7 +1,7 @@ import test from 'node:test'; import assert from 'node:assert/strict'; import {aggregateEvents, highestUsage, mergeRecord, number, recentDates, record, validTime, validateRecord, windowUsage} from '../../src/core/usage.js'; -import {modelName, resetTime, tokens} from '../../src/core/format.js'; +import {compactTokens, modelName, resetCountdown, resetTime, tokens} from '../../src/core/format.js'; import {ThresholdTracker} from '../../src/core/notifications.js'; test('unknown metrics are not coerced to zero', () => { @@ -132,8 +132,13 @@ test('notifications require a verified crossing and deduplicate warning and limi test('formatting preserves unknown/reset-due states', () => { assert.equal(tokens(null), '—'); assert.equal(tokens(23000000), '23.0M'); + assert.equal(compactTokens(186000000), '186M'); + assert.equal(compactTokens(56300000), '56.3M'); assert.equal(resetTime(null), 'Reset time unavailable'); assert.match(resetTime(100, 200), /awaiting update/); + assert.equal(resetCountdown(null), null); + assert.equal(resetCountdown(100, 200), 'due'); + assert.equal(resetCountdown(100 + 4 * 86400000 + 22 * 3600000, 100), '4d 22h'); assert.equal(modelName('gpt-5.6-sol'), 'GPT 5.6 Sol'); assert.equal(modelName('codex_auto-review'), 'Codex Auto Review'); }); diff --git a/tools/check.js b/tools/check.js index 8b35c96..4bd954e 100644 --- a/tools/check.js +++ b/tools/check.js @@ -57,6 +57,10 @@ for (const file of required) { const schemas = walk('schemas').filter(file => file.endsWith('.xml')); if (schemas.length !== 1 || schemas[0] !== 'schemas/org.gnome.shell.extensions.usagebeam.gschema.xml') throw new Error('Unexpected extension schema input'); +const icons = walk('icons'); +if (icons.length !== 2 || !icons.includes('icons/claude.svg') || + !icons.includes('icons/codex-symbolic.svg')) + throw new Error('Unexpected runtime icon input'); const obsolete = ['plan.md', 'indicator.js', 'src/providers/legacy.js', 'scripts/freeby.sh', 'scripts/copilot-setup.sh', 'tests/freeby.bats', 'docs/s1.png', 'docs/s2.png', 'reference-images']; for (const file of obsolete) { diff --git a/tools/package.py b/tools/package.py index bb3fb66..593047e 100644 --- a/tools/package.py +++ b/tools/package.py @@ -33,6 +33,7 @@ def package(source, output): raise RuntimeError('package.json contains an invalid release version') files = [source / name for name in ('extension.js', 'prefs.js', 'metadata.json', 'stylesheet.css', 'LICENSE')] files.extend(sorted((source / 'src').rglob('*.js'))) + files.extend(sorted((source / 'icons').glob('*.svg'))) schema = metadata.get('settings-schema') if schema != 'org.gnome.shell.extensions.usagebeam': raise RuntimeError('metadata.json contains an unexpected settings schema') diff --git a/tools/smoke-shell.py b/tools/smoke-shell.py index c53a121..5d02150 100644 --- a/tools/smoke-shell.py +++ b/tools/smoke-shell.py @@ -93,6 +93,7 @@ def smoke(source, archive, destination): 'GSETTINGS_SCHEMA_DIR': str(prefix / 'share/gnome-shell/extensions' / UUID / 'schemas'), 'LIBGL_ALWAYS_SOFTWARE': '1'} run(['gsettings', 'set', SCHEMA, 'enabled-providers', "['codex', 'claude']"], env, check=True) + run(['gsettings', 'set', SCHEMA, 'panel-position', "'left-of-calendar'"], env, check=True) run(['gsettings', 'set', 'org.gnome.shell', 'enabled-extensions', f"['{UUID}']"], env, check=True) run(['gsettings', 'set', 'org.gnome.shell', 'welcome-dialog-last-shown-version', '999'], env) bus = subprocess.Popen(['dbus-daemon', '--session', '--nofork', '--print-address=1'], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True) @@ -118,6 +119,9 @@ def smoke(source, archive, destination): else: raise RuntimeError(f'Extension did not become active: {info}') print('PASS: packaged extension loads in a private GNOME Shell session') + run(['gsettings', 'set', SCHEMA, 'panel-position', "'right-of-calendar'"], env, check=True) + time.sleep(0.2) + print('PASS: live calendar-side placement update') run(['gnome-extensions', 'disable', UUID], env, check=True) run(['gnome-extensions', 'enable', UUID], env, check=True) print('PASS: extension disable/re-enable') From e18275adfe0768decbd379e79cac5a03fc1fecd0 Mon Sep 17 00:00:00 2001 From: kcnewman Date: Mon, 7 Sep 2026 13:26:33 +0000 Subject: [PATCH 27/44] fix: correct usage popup visual geometry --- .AGENTS/plans/roadmap.md | 20 +++ CHANGELOG.md | 9 ++ src/core/format.js | 15 ++ src/ui/indicator.js | 30 +++- src/ui/presentation.js | 15 ++ src/ui/widgets.js | 34 ++-- stylesheet.css | 277 +++++++++++++++++++++----------- tests/unit/presentation.test.js | 8 +- tests/unit/usage.test.js | 5 +- 9 files changed, 300 insertions(+), 113 deletions(-) diff --git a/.AGENTS/plans/roadmap.md b/.AGENTS/plans/roadmap.md index 11ad9ac..9a137cf 100644 --- a/.AGENTS/plans/roadmap.md +++ b/.AGENTS/plans/roadmap.md @@ -260,6 +260,26 @@ That exact archive loaded with populated Codex and Claude records, moved live from the left to the right of the calendar, disabled, and re-enabled without extension errors in a private GNOME Shell 50 session. +### Visual QA correction checkpoint + +- [x] Correct the daily chart's zero-width bar allocation and keep every + non-zero column visibly bottom-aligned against the period maximum. +- [x] Center the popup around its panel trigger and retain GNOME's work-area + clamping at screen edges. +- [x] Give the activity disclosure a complete border and stable right-aligned + metadata group; align quota percentages and resets in tabular columns. +- [x] Replace raw ISO ranges with concise locale-aware dates and refine the + surface into light/dark accent-tinted slate variants with mixed system and + system-monospace typography. + +Evidence: `npm run check` passed 29 deterministic unit tests, including chart +geometry and date-presentation coverage, plus the strict schema/layout and GJS +integration suite. +Two builds produced the same 42,120-byte archive at SHA-256 +`11a51d84ed710e30cfc633fb6d6c7b5f8c6a7479c8e40dca95350e0f9d1526bc`. +The exact archive loaded, changed calendar side live, disabled, and re-enabled +without extension or stylesheet errors in a private GNOME Shell 50 session. + ### 3. Cursor — v2.0.0-alpha.3 - [ ] Revalidate current Cursor authentication and usage sources for free and paid accounts. diff --git a/CHANGELOG.md b/CHANGELOG.md index c501a30..dc32b04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,9 @@ while version 2 is developed on the `dev` branch. reserve`, and render daily token history as a softly colored seven-column chart. - Add native preferences for immediate placement directly left or right of the Shell calendar. +- Refine the interface into an accent-tinted slate surface with an explicit + perimeter, mixed system/monospace typography, stronger hierarchy, and a + bordered activity disclosure in both light and dark Shell color schemes. - Consolidated agent rules, the active roadmap, archived planning notes, and visual implementation references under `.AGENTS/`, with a minimal root `AGENTS.md` discovery entrypoint. @@ -65,6 +68,12 @@ while version 2 is developed on the `dev` branch. names instead of centered or malformed bars. - Preserve the user's Shell typeface while strengthening text contrast and hierarchy for cleaner rendering. +- Allocate daily chart bars explicitly so non-zero days always receive visible + width and bottom-aligned height. +- Center the popup on the active-provider panel indicator instead of anchoring + its left edge to the trigger. +- Align limit percentages, separators, and reset countdowns in fixed tabular + columns, and format activity periods as concise locale-aware date ranges. ### Removed diff --git a/src/core/format.js b/src/core/format.js index 5b91b1b..254b52d 100644 --- a/src/core/format.js +++ b/src/core/format.js @@ -12,6 +12,21 @@ export function compactTokens(value) { return tokens(value).replace(/\.0(?=[BMK]$)/, ''); } +export function dateRange(period, locale = undefined) { + const start = new Date(`${period?.start ?? ''}T12:00:00`); + const end = new Date(`${period?.end ?? ''}T12:00:00`); + if (!Number.isFinite(start.getTime()) || !Number.isFinite(end.getTime()) || end < start) + return 'Dates unavailable'; + const formatter = new Intl.DateTimeFormat(locale, { + day: 'numeric', + month: 'short', + year: 'numeric', + }); + return typeof formatter.formatRange === 'function' + ? formatter.formatRange(start, end) + : `${formatter.format(start)} – ${formatter.format(end)}`; +} + export function resetTime(time, now = Date.now()) { if (!Number.isFinite(time) || time <= 0) return 'Reset time unavailable'; diff --git a/src/ui/indicator.js b/src/ui/indicator.js index 4959bcd..ea7f50a 100644 --- a/src/ui/indicator.js +++ b/src/ui/indicator.js @@ -5,18 +5,18 @@ import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js'; import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js'; import {NAMES, recentDates} from '../core/usage.js'; -import {age, modelName, tokens} from '../core/format.js'; +import {age, dateRange, modelName, tokens} from '../core/format.js'; import {historyOverview, latestUpdate, panelQuota, periodDays, providerStatus, quotaPresentation} from './presentation.js'; import {actionButton, button, dayChart, disclosureButton, label, meter, modelMeter, - providerIcon, row} from './widgets.js'; + limitRow, providerIcon} from './widgets.js'; const TAB_NAMES = {claude: 'Claude'}; const PROVIDER_MARKS = {codex: '>_', claude: '✦', cursor: '⌁', copilot: '◆'}; export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator extends PanelMenu.Button { _init(settings, extensionPath, openPreferences) { - super._init(0.0, 'UsageBeam usage monitor'); + super._init(0.5, 'UsageBeam usage monitor'); this._settings = settings; this._extensionPath = extensionPath; this._openPreferences = openPreferences; @@ -25,6 +25,11 @@ export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator this._panelStatus = new St.BoxLayout({style_class: 'usagebeam-panel-status'}); this.add_child(this._panelStatus); this.menu.actor.add_style_class_name('usagebeam-menu'); + this._shellSettings = St.Settings.get(); + this._shellSettings.connectObject( + 'notify::color-scheme', () => this._syncColorScheme(), + 'notify::shell-color-scheme', () => this._syncColorScheme(), this); + this._syncColorScheme(); const section = new PopupMenu.PopupMenuSection(); this.menu.addMenuItem(section); // `content` is an inherited Clutter.Actor property whose value must be @@ -43,6 +48,14 @@ export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator this.render(); } + _syncColorScheme() { + const {colorScheme, shellColorScheme} = this._shellSettings; + const light = shellColorScheme === 'prefer-light' || + (shellColorScheme !== 'prefer-dark' && colorScheme === St.SystemColorScheme.PREFER_LIGHT); + this.menu.actor.remove_style_class_name(light ? 'usagebeam-dark' : 'usagebeam-light'); + this.menu.actor.add_style_class_name(light ? 'usagebeam-light' : 'usagebeam-dark'); + } + resize() { const monitor = Main.layoutManager.findMonitorForActor(this) ?? Main.layoutManager.primaryMonitor; const scale = St.ThemeContext.get_for_stage(global.stage).scale_factor; @@ -141,8 +154,7 @@ export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator const view = quotaPresentation(window); if (!view) continue; - const summary = [view.value, view.reset].filter(Boolean).join(' · '); - box.add_child(row(view.name, summary, 'usagebeam-limit-row')); + box.add_child(limitRow(view.name, view.value, view.reset)); if (!window.unlimited) { const level = window.usedPercent >= 100 ? 'usagebeam-danger' : window.usedPercent >= 90 ? 'usagebeam-warning' : ''; @@ -189,7 +201,7 @@ export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator const overview = historyOverview(history); this._heading(`LAST ${overview?.days ?? history.days.length} DAYS · ${tokens(overview?.total ?? 0)} TOKENS`, parent); const scope = history.scope === 'account' ? 'Account activity' : 'This device'; - parent.add_child(label(`${scope} · ${history.period.start} – ${history.period.end}`, 'usagebeam-caption')); + parent.add_child(label(`${scope} · ${dateRange(history.period)}`, 'usagebeam-caption')); const today = recentDates(Date.now(), 1)[0]; parent.add_child(dayChart(history.days, today)); } @@ -240,4 +252,10 @@ export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator message.clutter_text.line_wrap = true; this._contentBox.add_child(message); } + + destroy() { + this._shellSettings?.disconnectObject(this); + this._shellSettings = null; + super.destroy(); + } }); diff --git a/src/ui/presentation.js b/src/ui/presentation.js index 566c8fb..4d3b0d8 100644 --- a/src/ui/presentation.js +++ b/src/ui/presentation.js @@ -78,3 +78,18 @@ export function panelQuota(record, now = Date.now()) { reset: resetCountdown(window.resetsAt, now), }; } + +export function chartBarGeometry(value, maximum, width, height, inset = 4) { + const availableWidth = Math.max(0, width - inset * 2); + const availableHeight = Math.max(0, height); + const ratio = maximum > 0 && value > 0 ? Math.min(1, value / maximum) : 0; + const barHeight = ratio && availableHeight + ? Math.min(availableHeight, Math.max(3, Math.round(availableHeight * ratio))) + : 0; + return { + x: inset, + y: availableHeight - barHeight, + width: availableWidth, + height: barHeight, + }; +} diff --git a/src/ui/widgets.js b/src/ui/widgets.js index a2ab04e..df3739e 100644 --- a/src/ui/widgets.js +++ b/src/ui/widgets.js @@ -4,6 +4,7 @@ import Gio from 'gi://Gio'; import Pango from 'gi://Pango'; import St from 'gi://St'; import {compactTokens} from '../core/format.js'; +import {chartBarGeometry} from './presentation.js'; const PROVIDER_ICONS = new Set(['claude', 'codex']); const PROVIDER_ICON_FILES = {claude: 'claude.svg', codex: 'codex-symbolic.svg'}; @@ -24,12 +25,20 @@ export function providerIcon(provider, extensionPath, style = '') { }); } -export function row(left, right, style = 'usagebeam-row') { - const box = new St.BoxLayout({style_class: style, x_expand: true}); - const title = label(left, '', true); +export function limitRow(name, value, reset) { + const box = new St.BoxLayout({style_class: 'usagebeam-limit-row', x_expand: true}); + const title = label(name, 'usagebeam-limit-name', true); title.clutter_text.ellipsize = Pango.EllipsizeMode.END; box.add_child(title); - box.add_child(label(right, 'usagebeam-number')); + const metrics = new St.BoxLayout({style_class: 'usagebeam-limit-metrics'}); + if (reset) { + metrics.add_child(label(value, 'usagebeam-limit-percent')); + metrics.add_child(label('·', 'usagebeam-limit-separator')); + metrics.add_child(label(reset, 'usagebeam-limit-reset')); + } else { + metrics.add_child(label(value, 'usagebeam-limit-value')); + } + box.add_child(metrics); return box; } @@ -97,16 +106,19 @@ export function dayChart(days, today) { const plot = new St.Widget({ style_class: 'usagebeam-chart-plot', x_expand: true, - layout_manager: new Clutter.BinLayout(), + clip_to_allocation: true, + layout_manager: new Clutter.FixedLayout(), }); const bar = new St.Widget({ style_class: 'usagebeam-chart-bar', - x_align: Clutter.ActorAlign.FILL, - y_align: Clutter.ActorAlign.END, visible: day.total > 0, - height: Math.max(3, Math.round(day.total / max * 58)), }); plot.add_child(bar); + plot.connect('notify::allocation', () => { + const geometry = chartBarGeometry(day.total, max, plot.width, plot.height); + bar.set_position(geometry.x, geometry.y); + bar.set_size(geometry.width, geometry.height); + }); column.add_child(plot); column.add_child(label(weekday, 'usagebeam-chart-day')); chart.add_child(column); @@ -137,11 +149,13 @@ export function disclosureButton(summary, expanded, callback) { }); const content = new St.BoxLayout({style_class: 'usagebeam-disclosure-content', x_expand: true}); content.add_child(label('Activity', 'usagebeam-disclosure-title', true)); - content.add_child(label(summary, 'usagebeam-disclosure-summary')); - content.add_child(new St.Icon({ + const meta = new St.BoxLayout({style_class: 'usagebeam-disclosure-meta'}); + meta.add_child(label(summary, 'usagebeam-disclosure-summary')); + meta.add_child(new St.Icon({ icon_name: expanded ? 'pan-up-symbolic' : 'pan-down-symbolic', style_class: 'usagebeam-disclosure-icon', })); + content.add_child(meta); actor.set_child(content); actor.connect('clicked', callback); return actor; diff --git a/stylesheet.css b/stylesheet.css index 8fb2cfc..a505b01 100644 --- a/stylesheet.css +++ b/stylesheet.css @@ -1,9 +1,23 @@ .usagebeam-menu .popup-menu-content { - border-color: st-transparentize(st-lighten(-st-accent-color, 18%), 0.66); + border: 1px solid st-transparentize(st-lighten(-st-accent-color, 20%), 0.34); + border-radius: 16px; + box-shadow: 0 8px 24px 4px rgba(0, 0, 0, 0.34); +} + +.usagebeam-menu.usagebeam-dark .popup-menu-content { + color: #edf4f6; + background-color: st-mix(-st-accent-color, #182b35, 6%); +} + +.usagebeam-menu.usagebeam-light .popup-menu-content { + color: #17252c; + background-color: st-mix(-st-accent-color, #f4f8f9, 4%); } .usagebeam-panel-status { spacing: 5px; + font-family: monospace; + font-feature-settings: "tnum"; } .usagebeam-provider-icon { @@ -22,31 +36,31 @@ } .usagebeam-panel-provider { - font-weight: 600; + font-weight: 650; } .usagebeam-panel-value, .usagebeam-panel-reset { - color: st-lighten(-st-accent-color, 24%); - font-weight: 500; + color: st-lighten(-st-accent-color, 25%); + font-weight: 550; } .usagebeam-panel-separator { - opacity: 0.42; + opacity: 0.5; } .usagebeam-content { - padding: 10px 13px 11px; + padding: 12px 14px 12px; spacing: 0; } .usagebeam-header { - spacing: 9px; - padding: 1px 0 9px; + spacing: 10px; + padding: 1px 0 10px; } .usagebeam-header-icon { - icon-size: 27px; + icon-size: 29px; margin: 1px 2px 1px 0; } @@ -55,162 +69,211 @@ } .usagebeam-provider-mark { - min-width: 27px; - min-height: 27px; - color: st-lighten(-st-accent-color, 20%); + min-width: 29px; + min-height: 29px; + color: st-lighten(-st-accent-color, 22%); + font-family: monospace; font-size: 10pt; - font-weight: 600; + font-weight: 650; text-align: center; } .usagebeam-title { - font-size: 12.5pt; - font-weight: 650; + font-size: 14pt; + font-weight: 700; } .usagebeam-caption { + font-family: monospace; + font-feature-settings: "tnum"; font-size: 8.5pt; - opacity: 0.72; + opacity: 0.76; } .usagebeam-status { padding: 2px 1px; + font-family: monospace; font-size: 7.5pt; - font-weight: 600; - opacity: 0.88; + font-weight: 650; + opacity: 0.94; } .usagebeam-status-live { - color: #57e389; + color: #62e6a0; } .usagebeam-status-sync { - color: st-lighten(-st-accent-color, 20%); + color: st-lighten(-st-accent-color, 25%); } .usagebeam-status-local { - color: #f6d32d; + color: #f8d66d; } .usagebeam-status-cached { - color: #c0bfbc; + opacity: 0.72; } .usagebeam-selector { - spacing: 7px; - padding-bottom: 8px; + spacing: 8px; + padding-bottom: 10px; } .usagebeam-button { - min-height: 24px; + min-height: 27px; padding: 5px 10px; - border: 1px solid rgba(128, 128, 128, 0.24); - border-radius: 5px; - background-color: rgba(128, 128, 128, 0.035); + border: 1px solid rgba(128, 150, 160, 0.30); + border-radius: 6px; + background-color: rgba(128, 150, 160, 0.055); } .usagebeam-button:hover, .usagebeam-button:focus { - border-color: st-transparentize(st-lighten(-st-accent-color, 20%), 0.45); - background-color: st-transparentize(-st-accent-color, 0.91); + border-color: st-transparentize(st-lighten(-st-accent-color, 22%), 0.34); + background-color: st-transparentize(-st-accent-color, 0.88); } .usagebeam-button:checked { - border-color: st-transparentize(st-lighten(-st-accent-color, 20%), 0.40); - background-color: st-transparentize(-st-accent-color, 0.86); - font-weight: 600; + border-color: st-transparentize(st-lighten(-st-accent-color, 24%), 0.24); + background-color: st-transparentize(-st-accent-color, 0.80); + font-weight: 650; } .usagebeam-button:focus, .usagebeam-disclosure:focus { - box-shadow: inset 0 0 0 2px st-transparentize(-st-accent-color, 0.28); + box-shadow: inset 0 0 0 2px st-transparentize(-st-accent-color, 0.24); } .usagebeam-section-title { - padding: 8px 0 5px; - border-top: 1px solid rgba(128, 128, 128, 0.19); - font-size: 8pt; - font-weight: 650; - opacity: 0.86; + padding: 10px 0 6px; + border-top: 1px solid rgba(128, 150, 160, 0.22); + font-family: monospace; + font-size: 8.5pt; + font-weight: 700; + opacity: 0.92; } -.usagebeam-row, .usagebeam-limit-row { + min-height: 21px; spacing: 10px; } -.usagebeam-number { - font-weight: 500; - opacity: 0.94; +.usagebeam-limit-name { + font-size: 10.5pt; + font-weight: 520; } -.usagebeam-limit-row .usagebeam-number { - color: st-lighten(-st-accent-color, 25%); +.usagebeam-limit-metrics { + spacing: 5px; + font-family: monospace; + font-feature-settings: "tnum"; +} + +.usagebeam-limit-percent, +.usagebeam-limit-reset, +.usagebeam-limit-value { + color: st-mix(-st-accent-color, #d7e4eb, 28%); + font-size: 9.5pt; + font-weight: 550; + text-align: right; +} + +.usagebeam-limit-percent { + width: 36px; +} + +.usagebeam-limit-separator { + width: 6px; + opacity: 0.48; + text-align: center; +} + +.usagebeam-limit-reset { + width: 62px; +} + +.usagebeam-limit-value { + width: 109px; +} + +.usagebeam-number { + font-family: monospace; + font-feature-settings: "tnum"; + font-weight: 550; } .usagebeam-window { - spacing: 4px; - padding-bottom: 7px; + spacing: 5px; + padding-bottom: 8px; } .usagebeam-track { - height: 4px; + height: 5px; min-width: 20px; border-radius: 3px; - background-color: rgba(128, 128, 128, 0.19); + background-color: rgba(143, 166, 176, 0.22); } .usagebeam-fill { border-radius: 3px; - background-color: st-transparentize(st-lighten(-st-accent-color, 22%), 0.18); + background-color: st-mix(-st-accent-color, #b7cbd5, 42%); } .usagebeam-warning { - color: #e5a50a; + color: #f6c85f; } .usagebeam-danger { - color: #e01b24; + color: #ff6b88; } .usagebeam-warning .usagebeam-fill { - background-color: #e5a50a; + background-color: #f6c85f; } .usagebeam-danger .usagebeam-fill { - background-color: #e01b24; + background-color: #ff6b88; } .usagebeam-disclosure { - min-height: 27px; - margin-top: 1px; - padding: 7px 1px; - border-top: 1px solid rgba(128, 128, 128, 0.19); - border-radius: 4px; - background-color: transparent; + min-height: 31px; + margin: 3px 0 4px; + padding: 7px 9px; + border: 1px solid rgba(128, 150, 160, 0.30); + border-radius: 7px; + background-color: rgba(128, 150, 160, 0.065); } .usagebeam-disclosure:hover, -.usagebeam-disclosure:focus { - background-color: rgba(128, 128, 128, 0.065); +.usagebeam-disclosure:focus, +.usagebeam-disclosure:checked { + border-color: st-transparentize(st-lighten(-st-accent-color, 22%), 0.35); + background-color: st-transparentize(-st-accent-color, 0.89); } .usagebeam-disclosure-content { - spacing: 6px; + spacing: 10px; } .usagebeam-disclosure-title { - font-weight: 600; + font-size: 10pt; + font-weight: 650; +} + +.usagebeam-disclosure-meta { + spacing: 7px; } .usagebeam-disclosure-summary { + font-family: monospace; + font-feature-settings: "tnum"; font-size: 8.5pt; - opacity: 0.72; + opacity: 0.82; } .usagebeam-disclosure-icon { - icon-size: 11px; - opacity: 0.68; + icon-size: 10px; + opacity: 0.76; } .usagebeam-details { @@ -219,8 +282,8 @@ } .usagebeam-day-chart { - min-height: 88px; - padding: 5px 0 7px; + min-height: 100px; + padding: 7px 0 9px; } .usagebeam-chart-column { @@ -229,42 +292,52 @@ .usagebeam-chart-value, .usagebeam-chart-day { - font-size: 7.5pt; + font-family: monospace; + font-feature-settings: "tnum"; text-align: center; - opacity: 0.70; +} + +.usagebeam-chart-value { + font-size: 8pt; + opacity: 0.79; +} + +.usagebeam-chart-day { + font-size: 8.5pt; + opacity: 0.72; } .usagebeam-chart-plot { - height: 58px; + height: 64px; } .usagebeam-chart-bar { - margin: 0 4px; - border-radius: 3px 3px 1px 1px; - background-color: st-transparentize(st-lighten(-st-accent-color, 28%), 0.46); + border-radius: 4px 4px 2px 2px; + background-color: st-mix(-st-accent-color, #adbec7, 24%); } .usagebeam-today .usagebeam-chart-value, .usagebeam-today .usagebeam-chart-day { - color: st-lighten(-st-accent-color, 25%); - font-weight: 600; + color: st-mix(-st-accent-color, #e4f1f6, 40%); + font-weight: 700; opacity: 1; } .usagebeam-today .usagebeam-chart-bar { - background-color: st-transparentize(st-lighten(-st-accent-color, 25%), 0.22); + background-color: st-mix(-st-accent-color, #c7dce6, 52%); } .usagebeam-model-meter { - min-height: 27px; - margin-bottom: 5px; - border-radius: 3px; - background-color: rgba(128, 128, 160, 0.11); + min-height: 29px; + margin-bottom: 6px; + border: 1px solid rgba(128, 150, 160, 0.12); + border-radius: 4px; + background-color: rgba(128, 150, 160, 0.11); } .usagebeam-model-fill { border-radius: 3px; - background-color: st-transparentize(st-lighten(-st-accent-color, 17%), 0.72); + background-color: st-transparentize(st-mix(-st-accent-color, #aebfc8, 36%), 0.34); } .usagebeam-model-content { @@ -272,28 +345,42 @@ } .usagebeam-model-name { - opacity: 0.96; + font-weight: 520; } .usagebeam-actions { - spacing: 6px; - padding-top: 8px; - margin-top: 3px; - border-top: 1px solid rgba(128, 128, 128, 0.19); + spacing: 7px; + padding-top: 9px; + margin-top: 4px; + border-top: 1px solid rgba(128, 150, 160, 0.22); } .usagebeam-action-button { - min-height: 21px; - padding: 3px 8px; + min-height: 23px; + padding: 3px 9px; font-size: 8.5pt; } .usagebeam-message { - margin: 0 0 7px; + margin: 0 0 8px; padding: 8px 10px; - border-left: 2px solid #f6d32d; - border-radius: 3px; - background-color: rgba(246, 211, 45, 0.06); + border-left: 2px solid #f6c85f; + border-radius: 4px; + background-color: rgba(246, 200, 95, 0.08); font-size: 9pt; - opacity: 0.9; + opacity: 0.92; +} + +.usagebeam-menu.usagebeam-light .usagebeam-limit-percent, +.usagebeam-menu.usagebeam-light .usagebeam-limit-reset, +.usagebeam-menu.usagebeam-light .usagebeam-limit-value { + color: st-mix(-st-accent-color, #263b46, 38%); +} + +.usagebeam-menu.usagebeam-light .usagebeam-track { + background-color: rgba(55, 76, 86, 0.18); +} + +.usagebeam-menu.usagebeam-light .usagebeam-chart-bar { + background-color: st-mix(-st-accent-color, #6f8793, 28%); } diff --git a/tests/unit/presentation.test.js b/tests/unit/presentation.test.js index ca8876c..ccaf195 100644 --- a/tests/unit/presentation.test.js +++ b/tests/unit/presentation.test.js @@ -1,6 +1,6 @@ import test from 'node:test'; import assert from 'node:assert/strict'; -import {historyOverview, latestUpdate, panelQuota, periodDays, providerStatus, +import {chartBarGeometry, historyOverview, latestUpdate, panelQuota, periodDays, providerStatus, quotaName, quotaPresentation} from '../../src/ui/presentation.js'; test('provider status distinguishes live, local, cached and setup data', () => { @@ -64,3 +64,9 @@ test('panel quota reports only the highest current quota window', () => { assert.equal(panelQuota({limits: {...record.limits, status: 'stale'}}, now), null); assert.equal(panelQuota(null, now), null); }); + +test('chart geometry gives every non-zero day visible width and bottom alignment', () => { + assert.deepEqual(chartBarGeometry(70, 70, 50, 64), {x: 4, y: 0, width: 42, height: 64}); + assert.deepEqual(chartBarGeometry(21.7, 70, 50, 64), {x: 4, y: 44, width: 42, height: 20}); + assert.deepEqual(chartBarGeometry(0, 70, 50, 64), {x: 4, y: 64, width: 42, height: 0}); +}); diff --git a/tests/unit/usage.test.js b/tests/unit/usage.test.js index cd436ea..5113ec9 100644 --- a/tests/unit/usage.test.js +++ b/tests/unit/usage.test.js @@ -1,7 +1,7 @@ import test from 'node:test'; import assert from 'node:assert/strict'; import {aggregateEvents, highestUsage, mergeRecord, number, recentDates, record, validTime, validateRecord, windowUsage} from '../../src/core/usage.js'; -import {compactTokens, modelName, resetCountdown, resetTime, tokens} from '../../src/core/format.js'; +import {compactTokens, dateRange, modelName, resetCountdown, resetTime, tokens} from '../../src/core/format.js'; import {ThresholdTracker} from '../../src/core/notifications.js'; test('unknown metrics are not coerced to zero', () => { @@ -134,6 +134,9 @@ test('formatting preserves unknown/reset-due states', () => { assert.equal(tokens(23000000), '23.0M'); assert.equal(compactTokens(186000000), '186M'); assert.equal(compactTokens(56300000), '56.3M'); + assert.match(dateRange({start: '2026-09-01', end: '2026-09-07'}, 'en-US'), + /^Sep 1.*7, 2026$/); + assert.equal(dateRange({start: 'bad', end: '2026-09-07'}, 'en-US'), 'Dates unavailable'); assert.equal(resetTime(null), 'Reset time unavailable'); assert.match(resetTime(100, 200), /awaiting update/); assert.equal(resetCountdown(null), null); From 49c5710e1d2c3b4e68d10c42f42a878f5a51ae46 Mon Sep 17 00:00:00 2001 From: kcnewman Date: Mon, 7 Sep 2026 18:13:29 +0000 Subject: [PATCH 28/44] fix: stabilize panel placement and compact usage layout --- .AGENTS/plans/roadmap.md | 30 +++ CHANGELOG.md | 21 +- CONTRIBUTING.md | 12 +- README.md | 20 +- extension.js | 19 +- prefs.js | 9 +- ...ome.shell.extensions.usagebeam.gschema.xml | 7 +- src/ui/bar.js | 51 ++++ src/ui/indicator.js | 39 ++-- src/ui/panelPlacement.js | 18 ++ src/ui/widgets.js | 74 +++--- stylesheet.css | 95 +++++--- tests/shell/extension.js | 218 ++++++++++++++++++ tests/shell/metadata.json | 6 + tools/smoke-shell.py | 75 ++++-- 15 files changed, 541 insertions(+), 153 deletions(-) create mode 100644 src/ui/bar.js create mode 100644 src/ui/panelPlacement.js create mode 100644 tests/shell/extension.js create mode 100644 tests/shell/metadata.json diff --git a/.AGENTS/plans/roadmap.md b/.AGENTS/plans/roadmap.md index 9a137cf..29ee9c0 100644 --- a/.AGENTS/plans/roadmap.md +++ b/.AGENTS/plans/roadmap.md @@ -280,6 +280,36 @@ Two builds produced the same 42,120-byte archive at SHA-256 The exact archive loaded, changed calendar side live, disabled, and re-enabled without extension or stylesheet errors in a private GNOME Shell 50 session. +### Stable placement and compact typography checkpoint + +- [x] Add left, center, and right panel areas while preserving both calendar-side + choices and the existing default. Keep provider/quota changes within a fixed, + font-relative slot so the calendar stays still. +- [x] Verify the combined clock/indicator midpoint in the native center box. +- [x] Use locally installed SF Pro Text and SF Pro Display, enlarge activity + captions and summary text, and keep quota columns right-aligned without ellipses. +- [x] Allocate bars without feeding their sizes back into preferred-size requests; + restore compact model rows and natural expansion without an inner scrollbar. +- [x] Remove the duplicate rectangular shadow and preserve the rounded perimeter + and light/dark surfaces. +- [x] Add a test-only private-Shell companion that opens the popup, exercises the + disclosure, checks rendered geometry across repeated layouts and provider + changes, and captures both themes. Exclude it from runtime archives. + +Evidence (2026-09-07): `npm run check` passed 29 unit tests plus syntax, strict +schema, repository, and GJS integration checks. Repeated builds produced the same +43,637-byte archive, SHA-256 +`b376f99e78d882173a6dca2c6937daea3e2cbac1079fda719d634d6b39c43952`. +The exact archive passed private GNOME Shell 50 lifecycle and real open-popup +checks at normal size, 125% text size, and on a 2× virtual monitor. Model rows +measured 29 logical pixels normally and 32 with enlarged text, without growth +across repeated layouts. All five placements preserved indicator width and clock +position across Codex, Claude, and unavailable quota states. Screenshots were +inspected for SF Pro rendering, chart fills, compact rows, and the outer border. +Physical/fractional monitor combinations and other panel-layout extensions still +require desktop QA. This is a dev-only correction, not a completed provider +milestone or a stable release. + ### 3. Cursor — v2.0.0-alpha.3 - [ ] Revalidate current Cursor authentication and usage sources for free and paid accounts. diff --git a/CHANGELOG.md b/CHANGELOG.md index dc32b04..787bdeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,7 +25,7 @@ while version 2 is developed on the `dev` branch. - Reworked the popup into a compact, theme-integrated usage panel based on the supplied Omarchy references, with equal-width `Claude`/`Codex` tabs, visible provider status, and filled model-usage rows. -- Inherit the user's Shell font and accent color, with a restrained accent on +- Inherit the user's Shell accent color, with a restrained accent on the popup's actual perimeter instead of a nested hard-coded blue frame. - Keep limits and freshness visible in the default view while placing seven-day and model activity behind an accessible disclosure control; the complete view @@ -35,10 +35,11 @@ while version 2 is developed on the `dev` branch. its supplied icon, short name, highest current quota, and reset countdown. - Place quota reset countdowns inline, display Codex reserve usage as `Weekly reserve`, and render daily token history as a softly colored seven-column chart. -- Add native preferences for immediate placement directly left or right of the - Shell calendar. +- Add five immediate panel positions: left, center, right, left of calendar, + and right of calendar. Reserve a stable indicator width across providers and + quota states; center the combined clock/indicator group in the center box. - Refine the interface into an accent-tinted slate surface with an explicit - perimeter, mixed system/monospace typography, stronger hierarchy, and a + perimeter, locally installed SF Pro Text/Display typography, stronger hierarchy, and a bordered activity disclosure in both light and dark Shell color schemes. - Consolidated agent rules, the active roadmap, archived planning notes, and visual implementation references under `.AGENTS/`, with a minimal root @@ -66,8 +67,16 @@ while version 2 is developed on the `dev` branch. runtime, restoring live account quota reads on systems without `/usr/bin/node`. - Anchor daily and model fills to the left edge and render human-readable model names instead of centered or malformed bars. -- Preserve the user's Shell typeface while strengthening text contrast and - hierarchy for cleaner rendering. +- Enlarge activity headings, dates, and summary text with proportional SF Pro + typography, falling back to a system font when SF Pro is unavailable. +- Replace allocation-notification sizing loops with a native bar actor whose + fill never affects preferred size, preventing growing model rows and layout + flicker. Keep model rows compact and chart columns bottom aligned. +- Remove the duplicate rectangular popup shadow, retaining the rounded outer + border. Prevent quota numbers and separators from ellipsizing and right-align + numeric text within stable columns. +- Add private-Shell regression tests that open the actual popup, measure all five + positions, switch providers, verify repeated allocation, and capture both themes. - Allocate daily chart bars explicitly so non-zero days always receive visible width and bottom-aligned height. - Center the popup on the active-provider panel indicator instead of anchoring diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9f4ec7d..b02cc80 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,8 +24,16 @@ meson setup build --prefix=/tmp/usagebeam-install meson install -C build ``` -`python3 tools/smoke-shell.py` additionally verifies lifecycle behavior in a -private headless GNOME Shell session when GNOME Shell is available locally. +`python3 tools/smoke-shell.py` additionally verifies lifecycle and actual popup +geometry in a private headless GNOME Shell session. The test-only companion under +`tests/shell/` injects synthetic usage, exercises all five panel positions and +provider changes, expands activity, and checks bar sizes, text alignment, and +layout stability. JSON measurements and light/dark screenshots are saved in the +printed temporary directory. The companion is never included in release archives; +it requires no unsafe Shell evaluation or access to your live desktop. +Use `--archive dist/ARCHIVE.zip` to test the exact packaged payload. +Add `--scale 2` for a 2× virtual monitor or `--text-scale 1.25` for enlarged +accessibility text. Physical/fractional monitor scaling still needs desktop QA. ## Design and architecture diff --git a/README.md b/README.md index 6964edc..b412380 100644 --- a/README.md +++ b/README.md @@ -67,8 +67,17 @@ active, the former `freeby@kelvin.local` extension can be uninstalled. ## Settings -The development preferences window controls left/right-of-calendar placement, -refresh frequency, and notifications. The underlying schema also supports the +The development preferences window offers five panel positions: left, center, +right, left of calendar, and right of calendar. The center group shares a midpoint +between the clock and indicator; the indicator reserves a constant width so +provider changes do not move the clock. Placement follows GNOME's panel boxes +(other panel-layout extensions may override their arrangement). + +The popup uses locally installed SF Pro Text, with SF Pro Display for the provider +heading and system font fallback when unavailable. Fonts are not bundled or +downloaded. Activity expands fully with compact model rows and no inner scrollbar. + +Preferences also control refresh frequency and notifications. The schema supports the default provider, ordered enabled providers, history retention, and notification threshold; these receive their full preferences interface in alpha.5. @@ -90,9 +99,10 @@ npm run pack python3 tools/smoke-shell.py ``` -The final command starts a private headless GNOME session and does not enable the -extension in the active desktop. Live-provider checks are intentionally separate -from credential-free automated fixtures. +The final command starts a private headless GNOME session, opens the popup with +synthetic data, verifies allocation and placement, and saves light/dark screenshots +in a temporary directory. It does not enable the extension in the active desktop. +Live-provider checks are intentionally separate from these credential-free tests. ## Uninstall or downgrade diff --git a/extension.js b/extension.js index bcf84e8..145bd13 100644 --- a/extension.js +++ b/extension.js @@ -1,6 +1,7 @@ import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js'; import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import {UsageBeamIndicator} from './src/ui/indicator.js'; +import {placeIndicator} from './src/ui/panelPlacement.js'; import {migrateLegacyInstall} from './src/services/migration.js'; import {UsageService} from './src/services/usageService.js'; @@ -32,23 +33,7 @@ export default class UsageBeamExtension extends Extension { } _placeIndicator() { - const container = this._indicator?.container; - if (!container) - return; - const calendar = Main.panel.statusArea.dateMenu?.container; - const panelBox = calendar?.get_parent() ?? container.get_parent(); - if (!panelBox) - return; - const parent = container.get_parent(); - if (parent) - parent.remove_child(container); - const siblings = panelBox.get_children(); - const calendarIndex = calendar ? siblings.indexOf(calendar) : -1; - const side = this._settings.get_string('panel-position'); - const position = calendarIndex < 0 - ? (side === 'left-of-calendar' ? 0 : siblings.length) - : calendarIndex + (side === 'right-of-calendar' ? 1 : 0); - panelBox.insert_child_at_index(container, position); + placeIndicator(this._indicator, this._settings.get_string('panel-position')); } disable() { diff --git a/prefs.js b/prefs.js index 0cef873..58bb949 100644 --- a/prefs.js +++ b/prefs.js @@ -13,15 +13,16 @@ export default class UsageBeamPreferences extends ExtensionPreferences { const group = new Adw.PreferencesGroup({title: 'General'}); page.add(group); - const positions = ['left-of-calendar', 'right-of-calendar']; + const positions = ['left', 'center', 'right', 'left-of-calendar', 'right-of-calendar']; const positionRow = new Adw.ComboRow({ title: 'Panel position', - subtitle: 'Place the active provider beside the calendar', - model: Gtk.StringList.new(['Left of calendar', 'Right of calendar']), + subtitle: 'Choose a panel area or place usage beside the calendar', + model: Gtk.StringList.new(['Left panel', 'Center panel', 'Right panel', + 'Left of calendar', 'Right of calendar']), }); const syncPosition = () => { const selected = positions.indexOf(settings.get_string('panel-position')); - positionRow.selected = selected >= 0 ? selected : 1; + positionRow.selected = selected >= 0 ? selected : 4; }; syncPosition(); positionRow.connect('notify::selected', () => diff --git a/schemas/org.gnome.shell.extensions.usagebeam.gschema.xml b/schemas/org.gnome.shell.extensions.usagebeam.gschema.xml index c3b025b..b4c7494 100644 --- a/schemas/org.gnome.shell.extensions.usagebeam.gschema.xml +++ b/schemas/org.gnome.shell.extensions.usagebeam.gschema.xml @@ -23,12 +23,15 @@ + + + 'right-of-calendar' - Position beside the calendar - Place the UsageBeam indicator immediately left or right of the panel calendar + Panel position + Place UsageBeam in the left, center, or right panel area, or immediately beside the calendar 30 diff --git a/src/ui/bar.js b/src/ui/bar.js new file mode 100644 index 0000000..b336299 --- /dev/null +++ b/src/ui/bar.js @@ -0,0 +1,51 @@ +import Atk from 'gi://Atk'; +import Clutter from 'gi://Clutter'; +import GObject from 'gi://GObject'; +import St from 'gi://St'; +import {chartBarGeometry} from './presentation.js'; + +// Fills are allocated, never sized from allocation notifications. Their geometry +// must not contribute to preferred size or trigger a layout feedback loop. +export const UsageBeamBar = GObject.registerClass(class UsageBeamBar extends St.Widget { + _init({fraction, style, fillStyle, name, content = null, vertical = false}) { + super._init({style_class: style, x_expand: true, + accessible_name: name, accessible_role: Atk.Role.PROGRESS_BAR}); + this._fraction = Math.max(0, Math.min(1, Number(fraction) || 0)); + this._vertical = vertical; + this._fill = new St.Widget({style_class: fillStyle, visible: this._fraction > 0}); + this._overlay = content; + this.add_child(this._fill); + if (content) + this.add_child(content); + } + + vfunc_get_preferred_width(forHeight) { + const node = this.get_theme_node(); + const sizes = this._overlay?.get_preferred_width(node.adjust_for_height(forHeight)) ?? [0, 0]; + return node.adjust_preferred_width(...sizes); + } + + vfunc_get_preferred_height(forWidth) { + const node = this.get_theme_node(); + const sizes = this._overlay?.get_preferred_height(node.adjust_for_width(forWidth)) ?? [0, 0]; + return node.adjust_preferred_height(...sizes); + } + + vfunc_allocate(box) { + this.set_allocation(box); + const contentBox = this.get_theme_node().get_content_box(box); + const [width, height] = contentBox.get_size(); + const scale = St.ThemeContext.get_for_stage(this.get_stage()).scale_factor; + const geometry = this._vertical + ? Object.fromEntries(Object.entries(chartBarGeometry(this._fraction, 1, width / scale, height / scale)) + .map(([key, value]) => [key, value * scale])) + : {x: 0, y: 0, width: Math.round(width * this._fraction), height}; + const fillBox = new Clutter.ActorBox(); + fillBox.x1 = contentBox.x1 + geometry.x; + fillBox.y1 = contentBox.y1 + geometry.y; + fillBox.x2 = fillBox.x1 + geometry.width; + fillBox.y2 = fillBox.y1 + geometry.height; + this._fill.allocate(fillBox); + this._overlay?.allocate(contentBox); + } +}); diff --git a/src/ui/indicator.js b/src/ui/indicator.js index ea7f50a..068142b 100644 --- a/src/ui/indicator.js +++ b/src/ui/indicator.js @@ -1,6 +1,7 @@ import Clutter from 'gi://Clutter'; import GObject from 'gi://GObject'; import St from 'gi://St'; +import Pango from 'gi://Pango'; import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js'; import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js'; @@ -9,7 +10,7 @@ import {age, dateRange, modelName, tokens} from '../core/format.js'; import {historyOverview, latestUpdate, panelQuota, periodDays, providerStatus, quotaPresentation} from './presentation.js'; import {actionButton, button, dayChart, disclosureButton, label, meter, modelMeter, - limitRow, providerIcon} from './widgets.js'; + limitRow, metricLabel, providerIcon} from './widgets.js'; const TAB_NAMES = {claude: 'Claude'}; const PROVIDER_MARKS = {codex: '>_', claude: '✦', cursor: '⌁', copilot: '◆'}; @@ -23,6 +24,15 @@ export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator this._service = null; this._detailsExpanded = false; this._panelStatus = new St.BoxLayout({style_class: 'usagebeam-panel-status'}); + this._panelIcon = new St.Bin({style_class: 'usagebeam-panel-icon-slot', y_align: Clutter.ActorAlign.CENTER}); + this._panelProvider = label('UsageBeam', 'usagebeam-panel-provider', true); + this._panelProvider.clutter_text.ellipsize = Pango.EllipsizeMode.END; + this._panelValue = metricLabel('—', 'usagebeam-panel-value'); + this._panelReset = metricLabel('—', 'usagebeam-panel-reset'); + for (const actor of [this._panelIcon, this._panelProvider, this._panelValue, + label('·', 'usagebeam-panel-separator'), this._panelReset]) + this._panelStatus.add_child(actor); + this._panelProviderId = null; this.add_child(this._panelStatus); this.menu.actor.add_style_class_name('usagebeam-menu'); this._shellSettings = St.Settings.get(); @@ -96,24 +106,19 @@ export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator } _renderPanelStatus(id, record) { - this._panelStatus.destroy_all_children(); const name = TAB_NAMES[id] ?? NAMES[id] ?? 'UsageBeam'; - const icon = providerIcon(id, this._extensionPath, 'usagebeam-panel-icon'); - if (icon) - this._panelStatus.add_child(icon); - else - this._panelStatus.add_child(label(PROVIDER_MARKS[id] ?? 'AI', 'usagebeam-panel-mark')); - this._panelStatus.add_child(label(name, 'usagebeam-panel-provider')); - const quota = panelQuota(record); - if (quota) { - this._panelStatus.add_child(label(`${quota.percent}%`, - `usagebeam-panel-value${quota.percent >= 100 ? ' usagebeam-danger' : - quota.percent >= 90 ? ' usagebeam-warning' : ''}`)); - if (quota.reset) { - this._panelStatus.add_child(label('·', 'usagebeam-panel-separator')); - this._panelStatus.add_child(label(quota.reset, 'usagebeam-panel-reset')); - } + if (this._panelProviderId !== id) { + this._panelIcon.get_child()?.destroy(); + this._panelIcon.set_child(providerIcon(id, this._extensionPath, 'usagebeam-panel-icon') ?? + label(PROVIDER_MARKS[id] ?? 'AI', 'usagebeam-panel-mark')); + this._panelProviderId = id; } + this._panelProvider.text = name; + const quota = panelQuota(record); + this._panelValue.text = quota ? `${quota.percent}%` : '—'; + this._panelValue.style_class = `usagebeam-panel-value${quota?.percent >= 100 ? ' usagebeam-danger' : + quota?.percent >= 90 ? ' usagebeam-warning' : ''}`; + this._panelReset.text = quota?.reset ?? '—'; const resetDescription = quota?.reset === 'due' ? ', reset due' : quota?.reset ? `, resets in ${quota.reset}` : ''; this.accessible_name = `UsageBeam, ${name}${quota ? `, ${quota.percent} percent used${resetDescription}` : ''}`; diff --git a/src/ui/panelPlacement.js b/src/ui/panelPlacement.js new file mode 100644 index 0000000..7b76971 --- /dev/null +++ b/src/ui/panelPlacement.js @@ -0,0 +1,18 @@ +import * as Main from 'resource:///org/gnome/shell/ui/main.js'; + +export function placeIndicator(indicator, position) { + const container = indicator.container; + const calendar = Main.panel.statusArea.dateMenu?.container; + const boxes = {left: Main.panel._leftBox, center: Main.panel._centerBox, right: Main.panel._rightBox}; + const besideCalendar = position === 'left-of-calendar' || position === 'right-of-calendar'; + const target = besideCalendar ? calendar?.get_parent() ?? boxes.center : boxes[position] ?? boxes.center; + // Reparent only this extension. GNOME centers the combined calendar/indicator + // group in its center box; a constant indicator width keeps the clock steady. + container.get_parent()?.remove_child(container); + const siblings = target.get_children(); + const calendarIndex = siblings.indexOf(calendar); + const index = besideCalendar && calendarIndex >= 0 + ? calendarIndex + (position === 'right-of-calendar' ? 1 : 0) + : position === 'right' ? 0 : siblings.length; + target.insert_child_at_index(container, index); +} diff --git a/src/ui/widgets.js b/src/ui/widgets.js index df3739e..2dd0651 100644 --- a/src/ui/widgets.js +++ b/src/ui/widgets.js @@ -4,7 +4,7 @@ import Gio from 'gi://Gio'; import Pango from 'gi://Pango'; import St from 'gi://St'; import {compactTokens} from '../core/format.js'; -import {chartBarGeometry} from './presentation.js'; +import {UsageBeamBar} from './bar.js'; const PROVIDER_ICONS = new Set(['claude', 'codex']); const PROVIDER_ICON_FILES = {claude: 'claude.svg', codex: 'codex-symbolic.svg'}; @@ -14,6 +14,18 @@ export function label(text, style = '', expand = false) { y_align: Clutter.ActorAlign.CENTER, x_expand: expand}); } +export function metricLabel(text, style = '', expand = false) { + const actor = label(text, style, expand); + actor.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; + return actor; +} + +function metricColumn(text, style) { + const child = metricLabel(text, '', true); + child.x_align = Clutter.ActorAlign.END; + return new St.Bin({style_class: style, child}); +} + export function providerIcon(provider, extensionPath, style = '') { if (!PROVIDER_ICONS.has(provider)) return null; @@ -32,51 +44,28 @@ export function limitRow(name, value, reset) { box.add_child(title); const metrics = new St.BoxLayout({style_class: 'usagebeam-limit-metrics'}); if (reset) { - metrics.add_child(label(value, 'usagebeam-limit-percent')); - metrics.add_child(label('·', 'usagebeam-limit-separator')); - metrics.add_child(label(reset, 'usagebeam-limit-reset')); + metrics.add_child(metricColumn(value, 'usagebeam-limit-percent')); + metrics.add_child(metricColumn('·', 'usagebeam-limit-separator')); + metrics.add_child(metricColumn(reset, 'usagebeam-limit-reset')); } else { - metrics.add_child(label(value, 'usagebeam-limit-value')); + metrics.add_child(metricColumn(value, 'usagebeam-limit-value')); } box.add_child(metrics); return box; } export function meter(fraction, name, style = '') { - const ratio = Math.max(0, Math.min(1, Number(fraction) || 0)); - const track = new St.Widget({style_class: `usagebeam-track ${style}`, x_expand: true, - layout_manager: new Clutter.FixedLayout(), accessible_name: name, accessible_role: Atk.Role.PROGRESS_BAR}); - const fill = new St.Widget({style_class: 'usagebeam-fill'}); - fill.set_position(0, 0); - track.add_child(fill); - track.connect('notify::allocation', () => { - fill.set_size(Math.round(Math.max(0, track.width) * ratio), Math.max(0, track.height)); - }); - return track; + return new UsageBeamBar({fraction, name, style: `usagebeam-track ${style}`, fillStyle: 'usagebeam-fill'}); } export function modelMeter(left, right, fraction, name) { - const ratio = Math.max(0, Math.min(1, Number(fraction) || 0)); - const track = new St.Widget({style_class: 'usagebeam-model-meter', x_expand: true, - layout_manager: new Clutter.FixedLayout(), accessible_name: name, - accessible_role: Atk.Role.PROGRESS_BAR}); - const fill = new St.Widget({style_class: 'usagebeam-model-fill'}); const content = new St.BoxLayout({style_class: 'usagebeam-model-content'}); const title = label(left, 'usagebeam-model-name', true); title.clutter_text.ellipsize = Pango.EllipsizeMode.END; content.add_child(title); - content.add_child(label(right, 'usagebeam-number')); - track.add_child(fill); - track.add_child(content); - track.connect('notify::allocation', () => { - const width = Math.max(0, track.width); - const height = Math.max(0, track.height); - fill.set_position(0, 0); - fill.set_size(Math.round(width * ratio), height); - content.set_position(0, 0); - content.set_size(width, height); - }); - return track; + content.add_child(metricLabel(right, 'usagebeam-number')); + return new UsageBeamBar({fraction, name, content, + style: 'usagebeam-model-meter', fillStyle: 'usagebeam-model-fill'}); } export function dayChart(days, today) { @@ -103,21 +92,12 @@ export function dayChart(days, today) { accessible_name: `${isToday ? 'Today, ' : ''}${weekday}, ${day.total} tokens, ${day.sessions ?? 0} sessions`, }); column.add_child(label(compactTokens(day.total), 'usagebeam-chart-value')); - const plot = new St.Widget({ - style_class: 'usagebeam-chart-plot', - x_expand: true, - clip_to_allocation: true, - layout_manager: new Clutter.FixedLayout(), - }); - const bar = new St.Widget({ - style_class: 'usagebeam-chart-bar', - visible: day.total > 0, - }); - plot.add_child(bar); - plot.connect('notify::allocation', () => { - const geometry = chartBarGeometry(day.total, max, plot.width, plot.height); - bar.set_position(geometry.x, geometry.y); - bar.set_size(geometry.width, geometry.height); + const plot = new UsageBeamBar({ + fraction: day.total / max, + name: `${day.date}: ${day.total} tokens`, + style: 'usagebeam-chart-plot', + fillStyle: 'usagebeam-chart-bar', + vertical: true, }); column.add_child(plot); column.add_child(label(weekday, 'usagebeam-chart-day')); diff --git a/stylesheet.css b/stylesheet.css index a505b01..16b0848 100644 --- a/stylesheet.css +++ b/stylesheet.css @@ -1,7 +1,23 @@ +.usagebeam-menu { + font-family: "SF Pro Text"; + font-size: 10.5pt; + font-feature-settings: "tnum"; + -arrow-background-color: transparent; + -arrow-border-color: transparent; + -arrow-border-width: 0px; + -arrow-rise: 6px; + -arrow-base: 0px; + background-color: transparent; + box-shadow: none; +} + .usagebeam-menu .popup-menu-content { + font-family: "SF Pro Text"; + font-size: 10.5pt; + font-feature-settings: "tnum"; border: 1px solid st-transparentize(st-lighten(-st-accent-color, 20%), 0.34); - border-radius: 16px; - box-shadow: 0 8px 24px 4px rgba(0, 0, 0, 0.34); + border-radius: 12px; + box-shadow: none; } .usagebeam-menu.usagebeam-dark .popup-menu-content { @@ -16,10 +32,16 @@ .usagebeam-panel-status { spacing: 5px; - font-family: monospace; + width: 15em; + font-family: "SF Pro Text"; + font-size: 9.5pt; font-feature-settings: "tnum"; } +.usagebeam-panel-icon-slot { width: 16px; } +.usagebeam-panel-value { min-width: 3.2em; text-align: right; } +.usagebeam-panel-reset { min-width: 4.8em; text-align: right; } + .usagebeam-provider-icon { icon-size: 20px; } @@ -72,28 +94,27 @@ min-width: 29px; min-height: 29px; color: st-lighten(-st-accent-color, 22%); - font-family: monospace; + font-family: "SF Pro Text"; font-size: 10pt; font-weight: 650; text-align: center; } .usagebeam-title { + font-family: "SF Pro Display"; font-size: 14pt; font-weight: 700; } .usagebeam-caption { - font-family: monospace; font-feature-settings: "tnum"; - font-size: 8.5pt; + font-size: 9.5pt; opacity: 0.76; } .usagebeam-status { padding: 2px 1px; - font-family: monospace; - font-size: 7.5pt; + font-size: 8.5pt; font-weight: 650; opacity: 0.94; } @@ -120,8 +141,8 @@ } .usagebeam-button { - min-height: 27px; - padding: 5px 10px; + min-height: 23px; + padding: 4px 10px; border: 1px solid rgba(128, 150, 160, 0.30); border-radius: 6px; background-color: rgba(128, 150, 160, 0.055); @@ -145,10 +166,9 @@ } .usagebeam-section-title { - padding: 10px 0 6px; + padding: 8px 0 6px; border-top: 1px solid rgba(128, 150, 160, 0.22); - font-family: monospace; - font-size: 8.5pt; + font-size: 9.5pt; font-weight: 700; opacity: 0.92; } @@ -165,7 +185,6 @@ .usagebeam-limit-metrics { spacing: 5px; - font-family: monospace; font-feature-settings: "tnum"; } @@ -173,31 +192,30 @@ .usagebeam-limit-reset, .usagebeam-limit-value { color: st-mix(-st-accent-color, #d7e4eb, 28%); - font-size: 9.5pt; + font-size: 10.5pt; font-weight: 550; text-align: right; } .usagebeam-limit-percent { - width: 36px; + min-width: 3.2em; } .usagebeam-limit-separator { - width: 6px; + min-width: 0.6em; opacity: 0.48; text-align: center; } .usagebeam-limit-reset { - width: 62px; + min-width: 4.8em; } .usagebeam-limit-value { - width: 109px; + min-width: 8.6em; } .usagebeam-number { - font-family: monospace; font-feature-settings: "tnum"; font-weight: 550; } @@ -236,9 +254,9 @@ } .usagebeam-disclosure { - min-height: 31px; + min-height: 25px; margin: 3px 0 4px; - padding: 7px 9px; + padding: 6px 9px; border: 1px solid rgba(128, 150, 160, 0.30); border-radius: 7px; background-color: rgba(128, 150, 160, 0.065); @@ -265,9 +283,8 @@ } .usagebeam-disclosure-summary { - font-family: monospace; font-feature-settings: "tnum"; - font-size: 8.5pt; + font-size: 9.5pt; opacity: 0.82; } @@ -282,8 +299,7 @@ } .usagebeam-day-chart { - min-height: 100px; - padding: 7px 0 9px; + padding: 7px 0 8px; } .usagebeam-chart-column { @@ -292,23 +308,22 @@ .usagebeam-chart-value, .usagebeam-chart-day { - font-family: monospace; font-feature-settings: "tnum"; text-align: center; } .usagebeam-chart-value { - font-size: 8pt; + font-size: 9pt; opacity: 0.79; } .usagebeam-chart-day { - font-size: 8.5pt; + font-size: 9.5pt; opacity: 0.72; } .usagebeam-chart-plot { - height: 64px; + height: 52px; } .usagebeam-chart-bar { @@ -328,8 +343,8 @@ } .usagebeam-model-meter { - min-height: 29px; - margin-bottom: 6px; + min-height: 27px; + margin-bottom: 5px; border: 1px solid rgba(128, 150, 160, 0.12); border-radius: 4px; background-color: rgba(128, 150, 160, 0.11); @@ -341,7 +356,8 @@ } .usagebeam-model-content { - padding: 5px 8px; + spacing: 10px; + padding: 4px 8px; } .usagebeam-model-name { @@ -356,9 +372,9 @@ } .usagebeam-action-button { - min-height: 23px; + min-height: 21px; padding: 3px 9px; - font-size: 8.5pt; + font-size: 9.5pt; } .usagebeam-message { @@ -384,3 +400,12 @@ .usagebeam-menu.usagebeam-light .usagebeam-chart-bar { background-color: st-mix(-st-accent-color, #6f8793, 28%); } + +.usagebeam-menu.usagebeam-light .usagebeam-today .usagebeam-chart-value, +.usagebeam-menu.usagebeam-light .usagebeam-today .usagebeam-chart-day { + color: st-mix(-st-accent-color, #263b46, 38%); +} + +.usagebeam-menu.usagebeam-light .usagebeam-status-live { + color: #167345; +} diff --git a/tests/shell/extension.js b/tests/shell/extension.js new file mode 100644 index 0000000..f99ca46 --- /dev/null +++ b/tests/shell/extension.js @@ -0,0 +1,218 @@ +// Loaded only by tools/smoke-shell.py in its isolated, synthetic desktop. +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; +import * as Main from 'resource:///org/gnome/shell/ui/main.js'; +import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js'; + +const UUID = 'usagebeam@oo7kc.github.io'; +const descendants = actor => [actor, ...actor.get_children().flatMap(descendants)]; +const matching = (actor, style) => descendants(actor).filter(child => child.has_style_class_name?.(style)); +const bounds = actor => { + const [x, y] = actor.get_transformed_position(); + const [width, height] = actor.get_transformed_size(); + return {x, y, width, height}; +}; +const assert = (condition, message) => { + if (!condition) + throw new Error(message); +}; + +export default class UsageBeamUITest extends Extension { + enable() { + this._timers = new Map(); + this._output = GLib.getenv('USAGEBEAM_TEST_OUTPUT'); + if (!this._output) + throw new Error('UI tests require a private test output directory'); + this._run().then(result => this._save({ok: true, ...result})).catch(async error => { + try { + await this._screenshot('failure'); + } finally { + this._save({ok: false, error: `${error}\n${error.stack ?? ''}`}); + } + }); + } + + _save(result) { + GLib.file_set_contents(`${this._output}/ui-results.json`, JSON.stringify(result, null, 2)); + } + + _wait(milliseconds = 150) { + return new Promise((resolve, reject) => { + const id = GLib.timeout_add(GLib.PRIORITY_DEFAULT, milliseconds, () => { + this._timers.delete(id); + resolve(); + return GLib.SOURCE_REMOVE; + }); + this._timers.set(id, reject); + }); + } + + async _screenshot(name) { + const file = Gio.File.new_for_path(`${this._output}/${name}.png`); + const stream = file.replace(null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, null); + try { + await new Shell.Screenshot().screenshot(false, stream); + } finally { + stream.close(null); + } + } + + _displayCall(method, parameters = null) { + return new Promise((resolve, reject) => Gio.DBus.session.call('org.gnome.Mutter.DisplayConfig', + '/org/gnome/Mutter/DisplayConfig', 'org.gnome.Mutter.DisplayConfig', method, + parameters, null, Gio.DBusCallFlags.NONE, 5000, null, (connection, result) => { + try { + resolve(connection.call_finish(result).deep_unpack()); + } catch (error) { + reject(error); + } + })); + } + + async _run() { + let indicator; + for (let attempt = 0; attempt < 80 && !indicator; attempt++) { + await this._wait(); + indicator = Main.panel.statusArea[UUID]; + } + assert(indicator, 'UsageBeam did not appear in the panel'); + indicator._service.destroy(); + const [, fixture] = GLib.file_get_contents(`${this._output}/ui-fixtures.json`); + const records = JSON.parse(new TextDecoder().decode(fixture)); + indicator.attach({enabledProviders: ['codex', 'claude'], recordFor: id => records[id], + isRefreshing: () => false, refreshAll: () => {}}); + const settings = indicator._settings; + const calendar = Main.panel.statusArea.dateMenu.container; + const positions = ['left', 'right', 'center', 'left-of-calendar', 'right-of-calendar']; + const placement = []; + Main.overview.hide(); + await this._wait(500); + // Configure only this private virtual monitor, temporarily. Setting just + // St's scale factor would scale lengths without updating the font DPI. + const expectedScale = Number(GLib.getenv('USAGEBEAM_TEST_SCALE') ?? 1); + if (expectedScale !== 1) { + const [serial, monitors] = await this._displayCall('GetCurrentState'); + const [[connector], modes] = monitors[0]; + const [mode] = modes.find(item => item[5].includes(expectedScale)) ?? []; + assert(mode, `Virtual monitor does not support scale ${expectedScale}`); + await this._displayCall('ApplyMonitorsConfig', new GLib.Variant('(uua(iiduba(ssa{sv}))a{sv})', + [serial, 1, [[0, 0, expectedScale, 0, true, [[connector, mode, {}]]]], {}])); + await this._wait(500); + } + const [, , logicalMonitors] = await this._displayCall('GetCurrentState'); + const monitorScale = logicalMonitors[0][2]; + assert(monitorScale === expectedScale, `Expected monitor scale ${expectedScale}, got ${monitorScale}`); + for (const position of positions) { + settings.set_string('panel-position', position); + settings.set_string('default-provider', 'codex'); + await this._wait(); + const parent = position === 'left' ? Main.panel._leftBox : position === 'right' + ? Main.panel._rightBox : Main.panel._centerBox; + assert(indicator.container.get_parent() === parent, `${position}: wrong panel area`); + const before = bounds(indicator.container); + const clockBefore = bounds(calendar); + // Check both a different provider and unavailable quota text. + const original = records.claude; + for (const available of [true, false]) { + records.claude = available ? original : {...original, + limits: {...original.limits, windows: []}}; + settings.set_string('default-provider', 'claude'); + indicator.render(); + await this._wait(); + assert(Math.abs(bounds(calendar).x - clockBefore.x) <= 1, + `${position}: calendar moved on provider change`); + assert(Math.abs(bounds(indicator.container).width - before.width) <= 1, + `${position}: indicator width changed`); + assert(!indicator._panelProvider.clutter_text.get_layout().is_ellipsized(), + `${position}: panel provider name clipped: ${JSON.stringify({ + status: indicator._panelStatus.width, + children: indicator._panelStatus.get_children().map(actor => + ({style: actor.style_class, width: actor.width, preferred: actor.get_preferred_width(-1)})), + })}`); + } + records.claude = original; + if (position.includes('calendar')) { + const children = parent.get_children(); + const offset = children.indexOf(indicator.container) - children.indexOf(calendar); + assert(offset === (position === 'left-of-calendar' ? -1 : 1), 'Calendar adjacency lost'); + } + if (parent === Main.panel._centerBox) { + const left = Math.min(before.x, clockBefore.x); + const right = Math.max(before.x + before.width, clockBefore.x + clockBefore.width); + const panel = bounds(Main.panel); + assert(Math.abs((left + right) / 2 - (panel.x + panel.width / 2)) <= 1, + `${position}: combined calendar/indicator midpoint is not centered`); + } + placement.push({position, indicator: before, calendar: clockBefore}); + } + settings.set_string('default-provider', 'codex'); + indicator.menu.open(0); + await this._wait(); + await this._screenshot('collapsed'); + matching(indicator._contentBox, 'usagebeam-disclosure')[0].emit('clicked', 1); + await this._wait(); + assert(indicator._detailsExpanded, 'Activity button did not expand'); + const content = indicator._contentBox; + const scale = St.ThemeContext.get_for_stage(global.stage).scale_factor; + const interfaceSettings = new Gio.Settings({schema_id: 'org.gnome.desktop.interface'}); + const textScale = interfaceSettings.get_double('text-scaling-factor'); + const initial = bounds(indicator.menu.actor); + assert(initial.height / scale < 680 * textScale, `Expanded menu too tall: ${initial.height / scale}`); + assert(!descendants(content).some(actor => actor instanceof St.ScrollView), 'Activity is scrollable'); + const models = matching(content, 'usagebeam-model-meter'); + assert(models.length === 3, 'Expected three synthetic models'); + for (const actor of models) { + assert(actor.height / scale >= 27 && actor.height / scale <= 40 * textScale, + `Model row height is not compact: ${actor.height / scale}`); + assert(actor._fill.width > 0 && actor._fill.height > 0, 'Model fill is empty'); + } + const plots = matching(content, 'usagebeam-chart-plot'); + assert(plots.length === 7, 'Expected seven chart columns'); + for (const plot of plots) { + assert(plot.height / scale === 52, `Unexpected chart height: ${plot.height / scale}`); + assert(plot._fill.visible === (plot._fraction > 0), 'Incorrect zero/nonzero bar visibility'); + if (plot._fraction > 0) + assert(plot._fill.width > 0 && plot._fill.height > 0, 'Daily bar has no area'); + } + for (const style of ['usagebeam-limit-percent', 'usagebeam-limit-reset', 'usagebeam-limit-separator']) { + const actors = matching(content, style); + for (const actor of actors) { + const text = actor.get_child(); + assert(!text.clutter_text.get_layout().is_ellipsized(), `${style}: clipped text`); + assert(Math.abs(bounds(actor).x - bounds(actors[0]).x) <= 1, `${style}: misaligned column`); + const edge = bounds(text).x + text.width; + const firstText = actors[0].get_child(); + assert(Math.abs(edge - bounds(firstText).x - firstText.width) <= 1, + `${style}: numbers are not right aligned (${edge} vs ${bounds(firstText).x + firstText.width})`); + } + } + const heights = models.map(actor => actor.height); + for (let frame = 0; frame < 8; frame++) { + content.queue_relayout(); + await this._wait(60); + assert(Math.abs(bounds(indicator.menu.actor).height - initial.height) <= 1, + 'Menu geometry drifts on repeated layout'); + models.forEach((actor, index) => assert(actor.height === heights[index], 'Model row grows on layout')); + } + const font = content.get_theme_node().get_font().to_string(); + assert(font.includes('SF Pro Text'), `Requested font family was overridden: ${font}`); + await this._screenshot('expanded-dark'); + indicator.menu.actor.remove_style_class_name('usagebeam-dark'); + indicator.menu.actor.add_style_class_name('usagebeam-light'); + await this._wait(); + await this._screenshot('expanded-light'); + indicator.menu.close(0); + return {placement, expanded: initial, modelHeights: heights, + font, scale, monitorScale, textScale}; + } + + disable() { + for (const [id, reject] of this._timers) { + GLib.source_remove(id); + reject(new Error('UI check disabled before completion')); + } + this._timers.clear(); + } +} diff --git a/tests/shell/metadata.json b/tests/shell/metadata.json new file mode 100644 index 0000000..37a8e15 --- /dev/null +++ b/tests/shell/metadata.json @@ -0,0 +1,6 @@ +{ + "uuid": "usagebeam-ui-test@tests.invalid", + "name": "UsageBeam private UI checks", + "description": "Test-only companion; never shipped in the product archive.", + "shell-version": ["50"] +} diff --git a/tools/smoke-shell.py b/tools/smoke-shell.py index 5d02150..da3fb67 100644 --- a/tools/smoke-shell.py +++ b/tools/smoke-shell.py @@ -6,14 +6,17 @@ import os from pathlib import Path import signal +import shutil import subprocess import tempfile import time import zipfile +from xml.sax.saxutils import escape UUID = 'usagebeam@oo7kc.github.io' SCHEMA = 'org.gnome.shell.extensions.usagebeam' PRODUCT_DIRECTORY = 'usagebeam' +TEST_UUID = 'usagebeam-ui-test@tests.invalid' def run(command, env, **options): @@ -45,7 +48,8 @@ def seed_usage(destination): dates = [(date.today() - timedelta(days=offset)).isoformat() for offset in range(6, -1, -1)] def record(provider, name, plan, percentages, models): - days = [{'date': day, 'total': (index + 1) * 125_000, 'sessions': index + 1, 'events': index + 2} + days = [{'date': day, 'total': [0, 0, 0, 0, 0, 70_700_000, 38_500_000][index], + 'sessions': index + 1, 'events': index + 2} for index, day in enumerate(dates)] return { 'schemaVersion': 2, 'id': provider, 'name': name, 'plan': plan, 'accountKey': '0' * 64, @@ -59,6 +63,9 @@ def record(provider, name, plan, percentages, models): {'id': f'{provider}:weekly', 'label': 'Weekly', 'usedPercent': percentages[1], 'used': None, 'limit': None, 'unit': 'percent', 'unlimited': False, 'state': 'active', 'durationMinutes': 10_080, 'resetsAt': now + 172_800_000}, + {'id': f'{provider}:reserve', 'label': 'Weekly reserve', 'usedPercent': 0, + 'used': None, 'limit': None, 'unit': 'percent', 'unlimited': False, 'state': 'active', + 'durationMinutes': 10_080, 'resetsAt': now + 604_800_000}, ]}, 'history': {'status': 'ready', 'message': '', 'updatedAt': now, 'scope': 'local', 'period': {'start': dates[0], 'end': dates[-1]}, 'days': days, 'models': models, @@ -66,35 +73,52 @@ def record(provider, name, plan, percentages, models): } models = [ - {'model': 'gpt-5.6-sol', 'total': 1_550_000, 'input': 190_000, 'output': 68_000, - 'cacheRead': 1_292_000, 'cacheWrite': 0}, - {'model': 'codex-auto-review', 'total': 980_000, 'input': 97_000, 'output': 8_000, - 'cacheRead': 875_000, 'cacheWrite': 0}, - {'model': 'gpt-6-astra', 'total': 790_000, 'input': 42_000, 'output': 62_000, - 'cacheRead': 686_000, 'cacheWrite': 0}, + {'model': 'gpt-5.6-sol', 'total': 77_200_000, 'input': 190_000, 'output': 68_000, + 'cacheRead': 76_942_000, 'cacheWrite': 0}, + {'model': 'codex-auto-review', 'total': 24_100_000, 'input': 97_000, 'output': 8_000, + 'cacheRead': 23_995_000, 'cacheWrite': 0}, + {'model': 'gpt-6-astra', 'total': 7_900_000, 'input': 42_000, 'output': 62_000, + 'cacheRead': 7_796_000, 'cacheWrite': 0}, ] target = destination / 'state' / PRODUCT_DIRECTORY target.mkdir(parents=True, exist_ok=True) - (target / 'codex.json').write_text(json.dumps(record('codex', 'Codex', 'Pro', (24, 61), models))) - (target / 'claude.json').write_text(json.dumps(record('claude', 'Claude Code', 'Pro', (17, 43), models[:2]))) + records = {'codex': record('codex', 'Codex', 'Plus', (46, 7), models), + 'claude': record('claude', 'Claude Code', 'Pro', (100, 43), models)} + for provider, data in records.items(): + (target / f'{provider}.json').write_text(json.dumps(data)) + # Separate fixtures survive asynchronous collector updates to the cache. + (destination / 'ui-fixtures.json').write_text(json.dumps(records)) -def smoke(source, archive, destination): +def smoke(source, archive, destination, scale=1, text_scale=1.0): destination.mkdir(parents=True, exist_ok=True) prefix = destination / 'install' install(source, archive, prefix, destination) + shutil.copytree(source / 'tests/shell', prefix / 'share/gnome-shell/extensions' / TEST_UUID) isolated_home = destination / 'home' isolated_home.mkdir(parents=True, exist_ok=True) + runtime = destination / 'runtime' + runtime.mkdir(mode=0o700) seed_usage(destination) env = {**os.environ, 'HOME': str(isolated_home), 'PATH': '/usr/bin:/bin', 'XDG_CONFIG_HOME': str(destination / 'config'), 'XDG_DATA_HOME': str(prefix / 'share'), 'XDG_CACHE_HOME': str(destination / 'cache'), 'XDG_STATE_HOME': str(destination / 'state'), 'GSETTINGS_BACKEND': 'keyfile', + 'XDG_RUNTIME_DIR': str(runtime), 'GSETTINGS_SCHEMA_DIR': str(prefix / 'share/gnome-shell/extensions' / UUID / 'schemas'), - 'LIBGL_ALWAYS_SOFTWARE': '1'} + 'LIBGL_ALWAYS_SOFTWARE': '1', 'USAGEBEAM_TEST_OUTPUT': str(destination), + 'USAGEBEAM_TEST_SCALE': str(scale)} + # Use locally installed fonts without copying or distributing licensed files. + font = run(['fc-match', '-f', '%{file}', 'SF Pro Text'], os.environ, check=True).stdout + font_config = destination / 'fonts.conf' + font_config.write_text('/etc/fonts/fonts.conf' + f'{escape(str(Path(font).parent))}') + env['FONTCONFIG_FILE'] = str(font_config) + run(['gsettings', 'set', 'org.gnome.desktop.interface', 'scaling-factor', str(scale)], env, check=True) + run(['gsettings', 'set', 'org.gnome.desktop.interface', 'text-scaling-factor', str(text_scale)], env, check=True) run(['gsettings', 'set', SCHEMA, 'enabled-providers', "['codex', 'claude']"], env, check=True) run(['gsettings', 'set', SCHEMA, 'panel-position', "'left-of-calendar'"], env, check=True) - run(['gsettings', 'set', 'org.gnome.shell', 'enabled-extensions', f"['{UUID}']"], env, check=True) + run(['gsettings', 'set', 'org.gnome.shell', 'enabled-extensions', f"['{UUID}', '{TEST_UUID}']"], env, check=True) run(['gsettings', 'set', 'org.gnome.shell', 'welcome-dialog-last-shown-version', '999'], env) bus = subprocess.Popen(['dbus-daemon', '--session', '--nofork', '--print-address=1'], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True) env['DBUS_SESSION_BUS_ADDRESS'] = bus.stdout.readline().strip() @@ -102,8 +126,8 @@ def smoke(source, archive, destination): shell = None try: with log_path.open('w') as log: - shell = subprocess.Popen(['gnome-shell', '--headless', '--no-x11', '--virtual-monitor', '1280x1024', - '--wayland-display', 'usagebeam-test', '--debug-control'], env=env, stdout=log, stderr=log, start_new_session=True) + shell = subprocess.Popen(['gnome-shell', '--headless', '--no-x11', '--virtual-monitor', f'{1280 * scale}x{1024 * scale}', + '--wayland-display', 'usagebeam-test'], env=env, stdout=log, stderr=log, start_new_session=True) command = ['gdbus', 'call', '--session', '--dest', 'org.gnome.Shell.Extensions', '--object-path', '/org/gnome/Shell/Extensions', '--method', 'org.gnome.Shell.Extensions.GetExtensionInfo', UUID] deadline = time.monotonic() + 30 @@ -119,9 +143,19 @@ def smoke(source, archive, destination): else: raise RuntimeError(f'Extension did not become active: {info}') print('PASS: packaged extension loads in a private GNOME Shell session') - run(['gsettings', 'set', SCHEMA, 'panel-position', "'right-of-calendar'"], env, check=True) - time.sleep(0.2) - print('PASS: live calendar-side placement update') + results_path = destination / 'ui-results.json' + deadline = time.monotonic() + 25 + while time.monotonic() < deadline and not results_path.exists() and shell.poll() is None: + time.sleep(0.2) + if not results_path.exists(): + raise RuntimeError(f'UI tests did not complete; inspect {log_path}') + results = json.loads(results_path.read_text()) + if not results['ok']: + raise RuntimeError(f"UI check failed: {results['error']}") + print('PASS: five positions, stable provider width, and combined calendar centering') + print('PASS: opened popup, compact bars, aligned text, repeated allocation, and theme screenshots') + print(f'UI geometry and screenshots: {destination}') + run(['gnome-extensions', 'disable', TEST_UUID], env, check=True) run(['gnome-extensions', 'disable', UUID], env, check=True) run(['gnome-extensions', 'enable', UUID], env, check=True) print('PASS: extension disable/re-enable') @@ -141,6 +175,8 @@ def smoke(source, archive, destination): contents = log_path.read_text(errors='replace') if 'Gjs-CRITICAL' in contents and UUID in contents: raise RuntimeError(f'Extension emitted a GJS critical; inspect {log_path}') + if 'St-WARNING' in contents or 'UsageBeam could not' in contents or 'JS ERROR' in contents: + raise RuntimeError(f'UI, stylesheet, or JavaScript warning; inspect {log_path}') if __name__ == '__main__': @@ -148,6 +184,9 @@ def smoke(source, archive, destination): parser.add_argument('--source', type=Path, default=Path(__file__).resolve().parent.parent) parser.add_argument('--archive', type=Path, help='Test an already-built release archive instead of a Meson install') parser.add_argument('--output', type=Path) + parser.add_argument('--scale', type=int, choices=[1, 2], default=1, help='Private virtual-monitor scale') + parser.add_argument('--text-scale', type=float, choices=[1.0, 1.25], default=1.0, help='Accessibility text scale') args = parser.parse_args() smoke(args.source.resolve(), args.archive.resolve() if args.archive else None, - args.output or Path(tempfile.mkdtemp(prefix='usagebeam-shell-'))) + args.output.resolve() if args.output else Path(tempfile.mkdtemp(prefix='usagebeam-shell-')), + args.scale, args.text_scale) From a2656be3b693a78c28f7ef09f50a133f54d24447 Mon Sep 17 00:00:00 2001 From: kcnewman Date: Mon, 7 Sep 2026 18:38:14 +0000 Subject: [PATCH 29/44] fix: refine usage typography and panel centering --- .AGENTS/plans/roadmap.md | 20 ++--- CHANGELOG.md | 15 ++-- CONTRIBUTING.md | 2 +- README.md | 11 +-- extension.js | 3 +- prefs.js | 8 +- ...ome.shell.extensions.usagebeam.gschema.xml | 3 +- src/ui/indicator.js | 12 ++- src/ui/panelPlacement.js | 57 ++++++++++++--- src/ui/widgets.js | 19 ++++- stylesheet.css | 73 +++++++++++++++---- tests/shell/extension.js | 39 ++++++++-- tools/smoke-shell.py | 2 +- 13 files changed, 198 insertions(+), 66 deletions(-) diff --git a/.AGENTS/plans/roadmap.md b/.AGENTS/plans/roadmap.md index 29ee9c0..2519241 100644 --- a/.AGENTS/plans/roadmap.md +++ b/.AGENTS/plans/roadmap.md @@ -282,12 +282,14 @@ without extension or stylesheet errors in a private GNOME Shell 50 session. ### Stable placement and compact typography checkpoint -- [x] Add left, center, and right panel areas while preserving both calendar-side - choices and the existing default. Keep provider/quota changes within a fixed, +- [x] Add left and right panel areas while preserving both calendar-side choices + and the existing default. Keep provider/quota changes within a fixed, font-relative slot so the calendar stays still. -- [x] Verify the combined clock/indicator midpoint in the native center box. -- [x] Use locally installed SF Pro Text and SF Pro Display, enlarge activity - captions and summary text, and keep quota columns right-aligned without ellipses. +- [x] Put the gap between the calendar and indicator on the exact panel midpoint. +- [x] Use locally installed SF Pro Text and SF Pro Display, establish distinct + primary/secondary type levels, and keep quota columns aligned without ellipses. +- [x] Replace baseline-dependent middle-dot glyphs with consistently sized dot + actors centered between panel, quota, and activity metadata. - [x] Allocate bars without feeding their sizes back into preferred-size requests; restore compact model rows and natural expansion without an inner scrollbar. - [x] Remove the duplicate rectangular shadow and preserve the rounded perimeter @@ -298,12 +300,12 @@ without extension or stylesheet errors in a private GNOME Shell 50 session. Evidence (2026-09-07): `npm run check` passed 29 unit tests plus syntax, strict schema, repository, and GJS integration checks. Repeated builds produced the same -43,637-byte archive, SHA-256 -`b376f99e78d882173a6dca2c6937daea3e2cbac1079fda719d634d6b39c43952`. +44,391-byte archive, SHA-256 +`718e72e2921c573746b79136dadce72e611f38beff357ac4a9bb27c5a8964cda`. The exact archive passed private GNOME Shell 50 lifecycle and real open-popup checks at normal size, 125% text size, and on a 2× virtual monitor. Model rows -measured 29 logical pixels normally and 32 with enlarged text, without growth -across repeated layouts. All five placements preserved indicator width and clock +measured 29 logical pixels at normal and enlarged text settings, without growth +across repeated layouts. All four placements preserved indicator width and clock position across Codex, Claude, and unavailable quota states. Screenshots were inspected for SF Pro rendering, chart fills, compact rows, and the outer border. Physical/fractional monitor combinations and other panel-layout extensions still diff --git a/CHANGELOG.md b/CHANGELOG.md index 787bdeb..e974706 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,12 +35,12 @@ while version 2 is developed on the `dev` branch. its supplied icon, short name, highest current quota, and reset countdown. - Place quota reset countdowns inline, display Codex reserve usage as `Weekly reserve`, and render daily token history as a softly colored seven-column chart. -- Add five immediate panel positions: left, center, right, left of calendar, - and right of calendar. Reserve a stable indicator width across providers and - quota states; center the combined clock/indicator group in the center box. +- Add four immediate panel positions: left, right, left of calendar, and right + of calendar. Reserve a stable indicator width across providers and quota states; + place the calendar/indicator gap exactly on the panel midpoint. - Refine the interface into an accent-tinted slate surface with an explicit - perimeter, locally installed SF Pro Text/Display typography, stronger hierarchy, and a - bordered activity disclosure in both light and dark Shell color schemes. + perimeter, locally installed SF Pro Text/Display typography, stronger hierarchy, + and a bordered activity disclosure in both light and dark Shell color schemes. - Consolidated agent rules, the active roadmap, archived planning notes, and visual implementation references under `.AGENTS/`, with a minimal root `AGENTS.md` discovery entrypoint. @@ -75,8 +75,11 @@ while version 2 is developed on the `dev` branch. - Remove the duplicate rectangular popup shadow, retaining the rounded outer border. Prevent quota numbers and separators from ellipsizing and right-align numeric text within stable columns. -- Add private-Shell regression tests that open the actual popup, measure all five +- Add private-Shell regression tests that open the actual popup, measure all four positions, switch providers, verify repeated allocation, and capture both themes. +- Establish a clearer information hierarchy by reducing secondary quota, reset, + activity-summary, and model-total type. Replace text glyph separators with + consistently sized, optically centered dots in the menu and panel indicator. - Allocate daily chart bars explicitly so non-zero days always receive visible width and bottom-aligned height. - Center the popup on the active-provider panel indicator instead of anchoring diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b02cc80..7748788 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,7 +26,7 @@ meson install -C build `python3 tools/smoke-shell.py` additionally verifies lifecycle and actual popup geometry in a private headless GNOME Shell session. The test-only companion under -`tests/shell/` injects synthetic usage, exercises all five panel positions and +`tests/shell/` injects synthetic usage, exercises all four panel positions and provider changes, expands activity, and checks bar sizes, text alignment, and layout stability. JSON measurements and light/dark screenshots are saved in the printed temporary directory. The companion is never included in release archives; diff --git a/README.md b/README.md index b412380..749c005 100644 --- a/README.md +++ b/README.md @@ -67,11 +67,12 @@ active, the former `freeby@kelvin.local` extension can be uninstalled. ## Settings -The development preferences window offers five panel positions: left, center, -right, left of calendar, and right of calendar. The center group shares a midpoint -between the clock and indicator; the indicator reserves a constant width so -provider changes do not move the clock. Placement follows GNOME's panel boxes -(other panel-layout extensions may override their arrangement). +The development preferences window offers four panel positions: left, right, +left of calendar, and right of calendar. For calendar-side placement, the gap +between the clock and indicator sits exactly on the panel midpoint; the indicator +reserves a constant width so provider changes do not move the clock. Placement +follows GNOME's panel boxes (other panel-layout extensions may override their +arrangement). The popup uses locally installed SF Pro Text, with SF Pro Display for the provider heading and system font fallback when unavailable. Fonts are not bundled or diff --git a/extension.js b/extension.js index 145bd13..74bc509 100644 --- a/extension.js +++ b/extension.js @@ -1,7 +1,7 @@ import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js'; import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import {UsageBeamIndicator} from './src/ui/indicator.js'; -import {placeIndicator} from './src/ui/panelPlacement.js'; +import {clearIndicatorPlacement, placeIndicator} from './src/ui/panelPlacement.js'; import {migrateLegacyInstall} from './src/services/migration.js'; import {UsageService} from './src/services/usageService.js'; @@ -42,6 +42,7 @@ export default class UsageBeamExtension extends Extension { this._settingsIds = null; this._service?.destroy(); this._service = null; + clearIndicatorPlacement(this._indicator); this._indicator?.destroy(); this._indicator = null; this._settings = null; diff --git a/prefs.js b/prefs.js index 58bb949..05732d6 100644 --- a/prefs.js +++ b/prefs.js @@ -13,16 +13,16 @@ export default class UsageBeamPreferences extends ExtensionPreferences { const group = new Adw.PreferencesGroup({title: 'General'}); page.add(group); - const positions = ['left', 'center', 'right', 'left-of-calendar', 'right-of-calendar']; + const positions = ['left', 'right', 'left-of-calendar', 'right-of-calendar']; const positionRow = new Adw.ComboRow({ title: 'Panel position', - subtitle: 'Choose a panel area or place usage beside the calendar', - model: Gtk.StringList.new(['Left panel', 'Center panel', 'Right panel', + subtitle: 'Choose a side area or place usage beside the calendar', + model: Gtk.StringList.new(['Left panel', 'Right panel', 'Left of calendar', 'Right of calendar']), }); const syncPosition = () => { const selected = positions.indexOf(settings.get_string('panel-position')); - positionRow.selected = selected >= 0 ? selected : 4; + positionRow.selected = selected >= 0 ? selected : 3; }; syncPosition(); positionRow.connect('notify::selected', () => diff --git a/schemas/org.gnome.shell.extensions.usagebeam.gschema.xml b/schemas/org.gnome.shell.extensions.usagebeam.gschema.xml index b4c7494..18191df 100644 --- a/schemas/org.gnome.shell.extensions.usagebeam.gschema.xml +++ b/schemas/org.gnome.shell.extensions.usagebeam.gschema.xml @@ -24,14 +24,13 @@ - 'right-of-calendar' Panel position - Place UsageBeam in the left, center, or right panel area, or immediately beside the calendar + Place UsageBeam in the left or right panel area, or immediately beside the calendar 30 diff --git a/src/ui/indicator.js b/src/ui/indicator.js index 068142b..61f996d 100644 --- a/src/ui/indicator.js +++ b/src/ui/indicator.js @@ -10,7 +10,7 @@ import {age, dateRange, modelName, tokens} from '../core/format.js'; import {historyOverview, latestUpdate, panelQuota, periodDays, providerStatus, quotaPresentation} from './presentation.js'; import {actionButton, button, dayChart, disclosureButton, label, meter, modelMeter, - limitRow, metricLabel, providerIcon} from './widgets.js'; + limitRow, metricLabel, providerIcon, separatorDot} from './widgets.js'; const TAB_NAMES = {claude: 'Claude'}; const PROVIDER_MARKS = {codex: '>_', claude: '✦', cursor: '⌁', copilot: '◆'}; @@ -29,8 +29,9 @@ export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator this._panelProvider.clutter_text.ellipsize = Pango.EllipsizeMode.END; this._panelValue = metricLabel('—', 'usagebeam-panel-value'); this._panelReset = metricLabel('—', 'usagebeam-panel-reset'); + this._panelSeparator = separatorDot('usagebeam-panel-separator'); for (const actor of [this._panelIcon, this._panelProvider, this._panelValue, - label('·', 'usagebeam-panel-separator'), this._panelReset]) + this._panelSeparator, this._panelReset]) this._panelStatus.add_child(actor); this._panelProviderId = null; this.add_child(this._panelStatus); @@ -181,8 +182,11 @@ export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator _renderHistory(history) { const overview = historyOverview(history); if (overview) { - const summary = [overview.days ? `${overview.days} days` : null, - tokens(overview.total), overview.scope].filter(Boolean).join(' · '); + const summary = [ + overview.days ? {text: `${overview.days} days`, role: 'period'} : null, + {text: tokens(overview.total), role: 'tokens'}, + {text: overview.scope, role: 'source'}, + ].filter(Boolean); this._contentBox.add_child(disclosureButton(summary, this._detailsExpanded, () => { this._detailsExpanded = !this._detailsExpanded; this.render(); diff --git a/src/ui/panelPlacement.js b/src/ui/panelPlacement.js index 7b76971..89f42ff 100644 --- a/src/ui/panelPlacement.js +++ b/src/ui/panelPlacement.js @@ -1,18 +1,57 @@ +import St from 'gi://St'; import * as Main from 'resource:///org/gnome/shell/ui/main.js'; +const CALENDAR_POSITIONS = new Set(['left-of-calendar', 'right-of-calendar']); + +export function clearIndicatorPlacement(indicator) { + const state = indicator?._usageBeamPlacement; + if (!state) + return; + for (const [actor, signal] of state.signals) + actor.disconnect(signal); + for (const spacer of state.spacers) + spacer.destroy(); + delete indicator._usageBeamPlacement; +} + +function centerCalendarGap(indicator, calendar, target, position) { + // GNOME centers the entire center box. Balance its outer edge by the width + // difference so the boundary between these unequal actors lands at 50%. + const leading = new St.Widget({style_class: 'usagebeam-center-balance'}); + const trailing = new St.Widget({style_class: 'usagebeam-center-balance'}); + const leftIsIndicator = position === 'left-of-calendar'; + const sync = () => { + const indicatorWidth = indicator.container.get_preferred_width(-1)[1]; + const calendarWidth = calendar.get_preferred_width(-1)[1]; + const difference = indicatorWidth - calendarWidth; + leading.set_width(Math.max(0, leftIsIndicator ? -difference : difference)); + trailing.set_width(Math.max(0, leftIsIndicator ? difference : -difference)); + }; + const calendarIndex = target.get_children().indexOf(calendar); + target.insert_child_at_index(leading, calendarIndex); + target.insert_child_at_index(indicator.container, calendarIndex + (leftIsIndicator ? 1 : 2)); + target.insert_child_at_index(trailing, calendarIndex + 3); + const signals = [indicator.container, calendar].map(actor => + [actor, actor.connect('notify::width', sync)]); + indicator._usageBeamPlacement = {signals, spacers: [leading, trailing]}; + sync(); +} + export function placeIndicator(indicator, position) { + clearIndicatorPlacement(indicator); const container = indicator.container; const calendar = Main.panel.statusArea.dateMenu?.container; - const boxes = {left: Main.panel._leftBox, center: Main.panel._centerBox, right: Main.panel._rightBox}; - const besideCalendar = position === 'left-of-calendar' || position === 'right-of-calendar'; - const target = besideCalendar ? calendar?.get_parent() ?? boxes.center : boxes[position] ?? boxes.center; - // Reparent only this extension. GNOME centers the combined calendar/indicator - // group in its center box; a constant indicator width keeps the clock steady. + const boxes = {left: Main.panel._leftBox, right: Main.panel._rightBox}; + const resolved = CALENDAR_POSITIONS.has(position) || position in boxes + ? position : 'right-of-calendar'; + const besideCalendar = CALENDAR_POSITIONS.has(resolved); + const target = besideCalendar ? calendar?.get_parent() ?? Main.panel._centerBox : boxes[resolved]; container.get_parent()?.remove_child(container); + if (besideCalendar && calendar) { + centerCalendarGap(indicator, calendar, target, resolved); + return; + } const siblings = target.get_children(); - const calendarIndex = siblings.indexOf(calendar); - const index = besideCalendar && calendarIndex >= 0 - ? calendarIndex + (position === 'right-of-calendar' ? 1 : 0) - : position === 'right' ? 0 : siblings.length; + const index = resolved === 'right' ? 0 : siblings.length; target.insert_child_at_index(container, index); } diff --git a/src/ui/widgets.js b/src/ui/widgets.js index 2dd0651..c80d3bf 100644 --- a/src/ui/widgets.js +++ b/src/ui/widgets.js @@ -20,6 +20,16 @@ export function metricLabel(text, style = '', expand = false) { return actor; } +export function separatorDot(style = '') { + const dot = new St.Widget({style_class: 'usagebeam-separator-dot-core'}); + return new St.Bin({ + child: dot, + style_class: `usagebeam-separator-dot ${style}`.trim(), + x_align: Clutter.ActorAlign.CENTER, + y_align: Clutter.ActorAlign.CENTER, + }); +} + function metricColumn(text, style) { const child = metricLabel(text, '', true); child.x_align = Clutter.ActorAlign.END; @@ -45,7 +55,7 @@ export function limitRow(name, value, reset) { const metrics = new St.BoxLayout({style_class: 'usagebeam-limit-metrics'}); if (reset) { metrics.add_child(metricColumn(value, 'usagebeam-limit-percent')); - metrics.add_child(metricColumn('·', 'usagebeam-limit-separator')); + metrics.add_child(separatorDot('usagebeam-limit-separator')); metrics.add_child(metricColumn(reset, 'usagebeam-limit-reset')); } else { metrics.add_child(metricColumn(value, 'usagebeam-limit-value')); @@ -130,7 +140,12 @@ export function disclosureButton(summary, expanded, callback) { const content = new St.BoxLayout({style_class: 'usagebeam-disclosure-content', x_expand: true}); content.add_child(label('Activity', 'usagebeam-disclosure-title', true)); const meta = new St.BoxLayout({style_class: 'usagebeam-disclosure-meta'}); - meta.add_child(label(summary, 'usagebeam-disclosure-summary')); + summary.forEach(({text, role}, index) => { + if (index) + meta.add_child(separatorDot('usagebeam-disclosure-separator')); + meta.add_child(metricLabel(text, + `usagebeam-disclosure-summary usagebeam-disclosure-${role}`)); + }); meta.add_child(new St.Icon({ icon_name: expanded ? 'pan-up-symbolic' : 'pan-down-symbolic', style_class: 'usagebeam-disclosure-icon', diff --git a/stylesheet.css b/stylesheet.css index 16b0848..ba45c50 100644 --- a/stylesheet.css +++ b/stylesheet.css @@ -39,8 +39,6 @@ } .usagebeam-panel-icon-slot { width: 16px; } -.usagebeam-panel-value { min-width: 3.2em; text-align: right; } -.usagebeam-panel-reset { min-width: 4.8em; text-align: right; } .usagebeam-provider-icon { icon-size: 20px; @@ -51,8 +49,6 @@ } .usagebeam-panel-provider, -.usagebeam-panel-value, -.usagebeam-panel-reset, .usagebeam-panel-mark { font-size: 9.5pt; } @@ -64,11 +60,32 @@ .usagebeam-panel-value, .usagebeam-panel-reset { color: st-lighten(-st-accent-color, 25%); - font-weight: 550; + font-size: 8.75pt; +} + +.usagebeam-panel-value { + font-weight: 620; +} + +.usagebeam-panel-reset { + font-weight: 500; + opacity: 0.86; +} + +.usagebeam-separator-dot { + min-width: 4px; + min-height: 4px; } -.usagebeam-panel-separator { - opacity: 0.5; +.usagebeam-separator-dot-core { + width: 4px; + height: 4px; + border-radius: 2px; + background-color: rgba(220, 232, 237, 0.62); +} + +.usagebeam-panel-separator .usagebeam-separator-dot-core { + background-color: rgba(255, 255, 255, 0.58); } .usagebeam-content { @@ -192,27 +209,28 @@ .usagebeam-limit-reset, .usagebeam-limit-value { color: st-mix(-st-accent-color, #d7e4eb, 28%); - font-size: 10.5pt; - font-weight: 550; + font-size: 9.5pt; text-align: right; } .usagebeam-limit-percent { min-width: 3.2em; + font-weight: 620; } .usagebeam-limit-separator { - min-width: 0.6em; - opacity: 0.48; - text-align: center; + min-width: 0.8em; } .usagebeam-limit-reset { min-width: 4.8em; + font-weight: 500; + opacity: 0.84; } .usagebeam-limit-value { min-width: 8.6em; + font-weight: 550; } .usagebeam-number { @@ -279,13 +297,28 @@ } .usagebeam-disclosure-meta { - spacing: 7px; + spacing: 5px; } .usagebeam-disclosure-summary { font-feature-settings: "tnum"; - font-size: 9.5pt; - opacity: 0.82; + font-size: 8.75pt; + font-weight: 500; + opacity: 0.76; +} + +.usagebeam-disclosure-tokens { + font-weight: 650; + opacity: 0.94; +} + +.usagebeam-disclosure-source { + font-size: 8.5pt; + opacity: 0.64; +} + +.usagebeam-disclosure-separator { + min-width: 4px; } .usagebeam-disclosure-icon { @@ -358,12 +391,18 @@ .usagebeam-model-content { spacing: 10px; padding: 4px 8px; + font-size: 9.25pt; } .usagebeam-model-name { font-weight: 520; } +.usagebeam-model-content .usagebeam-number { + font-size: 8.75pt; + font-weight: 650; +} + .usagebeam-actions { spacing: 7px; padding-top: 9px; @@ -393,6 +432,10 @@ color: st-mix(-st-accent-color, #263b46, 38%); } +.usagebeam-menu.usagebeam-light .usagebeam-separator-dot-core { + background-color: rgba(38, 59, 70, 0.54); +} + .usagebeam-menu.usagebeam-light .usagebeam-track { background-color: rgba(55, 76, 86, 0.18); } diff --git a/tests/shell/extension.js b/tests/shell/extension.js index f99ca46..13091d4 100644 --- a/tests/shell/extension.js +++ b/tests/shell/extension.js @@ -85,7 +85,7 @@ export default class UsageBeamUITest extends Extension { isRefreshing: () => false, refreshAll: () => {}}); const settings = indicator._settings; const calendar = Main.panel.statusArea.dateMenu.container; - const positions = ['left', 'right', 'center', 'left-of-calendar', 'right-of-calendar']; + const positions = ['left', 'right', 'left-of-calendar', 'right-of-calendar']; const placement = []; Main.overview.hide(); await this._wait(500); @@ -138,12 +138,13 @@ export default class UsageBeamUITest extends Extension { const offset = children.indexOf(indicator.container) - children.indexOf(calendar); assert(offset === (position === 'left-of-calendar' ? -1 : 1), 'Calendar adjacency lost'); } - if (parent === Main.panel._centerBox) { - const left = Math.min(before.x, clockBefore.x); - const right = Math.max(before.x + before.width, clockBefore.x + clockBefore.width); + if (position.includes('calendar')) { + const left = position === 'left-of-calendar' ? before : clockBefore; + const right = position === 'left-of-calendar' ? clockBefore : before; const panel = bounds(Main.panel); - assert(Math.abs((left + right) / 2 - (panel.x + panel.width / 2)) <= 1, - `${position}: combined calendar/indicator midpoint is not centered`); + const gapCenter = (left.x + left.width + right.x) / 2; + assert(Math.abs(gapCenter - (panel.x + panel.width / 2)) <= 1, + `${position}: calendar/indicator gap is not centered (${gapCenter})`); } placement.push({position, indicator: before, calendar: clockBefore}); } @@ -176,7 +177,7 @@ export default class UsageBeamUITest extends Extension { if (plot._fraction > 0) assert(plot._fill.width > 0 && plot._fill.height > 0, 'Daily bar has no area'); } - for (const style of ['usagebeam-limit-percent', 'usagebeam-limit-reset', 'usagebeam-limit-separator']) { + for (const style of ['usagebeam-limit-percent', 'usagebeam-limit-reset']) { const actors = matching(content, style); for (const actor of actors) { const text = actor.get_child(); @@ -188,6 +189,30 @@ export default class UsageBeamUITest extends Extension { `${style}: numbers are not right aligned (${edge} vs ${bounds(firstText).x + firstText.width})`); } } + const dots = [indicator._panelSeparator, ...matching(content, 'usagebeam-separator-dot')]; + for (const dot of dots) { + const dotBounds = bounds(dot); + const coreBounds = bounds(dot.get_child()); + assert(Math.abs(coreBounds.x + coreBounds.width / 2 - (dotBounds.x + dotBounds.width / 2)) <= 1 && + Math.abs(coreBounds.y + coreBounds.height / 2 - (dotBounds.y + dotBounds.height / 2)) <= 1, + 'Separator dot is not optically centered'); + } + const valueBounds = bounds(indicator._panelValue); + const separatorBounds = bounds(indicator._panelSeparator); + const resetBounds = bounds(indicator._panelReset); + assert(Math.abs(separatorBounds.x - (valueBounds.x + valueBounds.width) - + (resetBounds.x - separatorBounds.x - separatorBounds.width)) <= 1, + 'Panel separator does not have equal spacing'); + const limitName = matching(content, 'usagebeam-limit-name')[0]; + const limitValue = matching(content, 'usagebeam-limit-percent')[0].get_child(); + assert(limitValue.get_theme_node().get_font().get_size() < + limitName.get_theme_node().get_font().get_size(), 'Limit metrics lack font hierarchy'); + const activityParts = matching(content, 'usagebeam-disclosure-summary'); + assert(activityParts.length === 3, 'Activity summary is not split into semantic parts'); + const modelContent = matching(content, 'usagebeam-model-content')[0]; + const [modelName, modelTotal] = modelContent.get_children(); + assert(modelTotal.get_theme_node().get_font().get_size() < + modelName.get_theme_node().get_font().get_size(), 'Model row lacks font hierarchy'); const heights = models.map(actor => actor.height); for (let frame = 0; frame < 8; frame++) { content.queue_relayout(); diff --git a/tools/smoke-shell.py b/tools/smoke-shell.py index da3fb67..5894d81 100644 --- a/tools/smoke-shell.py +++ b/tools/smoke-shell.py @@ -152,7 +152,7 @@ def smoke(source, archive, destination, scale=1, text_scale=1.0): results = json.loads(results_path.read_text()) if not results['ok']: raise RuntimeError(f"UI check failed: {results['error']}") - print('PASS: five positions, stable provider width, and combined calendar centering') + print('PASS: four positions, stable provider width, and exact calendar-gap centering') print('PASS: opened popup, compact bars, aligned text, repeated allocation, and theme screenshots') print(f'UI geometry and screenshots: {destination}') run(['gnome-extensions', 'disable', TEST_UUID], env, check=True) From 9666a43becea8c915de3a76f2c05d40c4b6086a6 Mon Sep 17 00:00:00 2001 From: kcnewman Date: Mon, 7 Sep 2026 18:59:13 +0000 Subject: [PATCH 30/44] refactor: harden verified usage surface --- src/collector/main.js | 30 +++--------------- src/core/usage.js | 9 +----- src/providers/copilot.js | 41 ------------------------ src/providers/cursor.js | 37 ---------------------- src/services/files.js | 4 +-- src/ui/indicator.js | 16 +++++----- src/ui/presentation.js | 18 +++++++++++ src/ui/widgets.js | 5 +-- stylesheet.css | 56 +++++++++++++++++++++++---------- tests/shell/extension.js | 9 ++++++ tests/unit/presentation.test.js | 14 ++++++++- tests/unit/usage.test.js | 7 ++--- tools/smoke-shell.py | 15 +++++---- 13 files changed, 110 insertions(+), 151 deletions(-) delete mode 100644 src/providers/copilot.js delete mode 100644 src/providers/cursor.js diff --git a/src/collector/main.js b/src/collector/main.js index a01f68b..3bfbd0b 100644 --- a/src/collector/main.js +++ b/src/collector/main.js @@ -5,25 +5,20 @@ import System from 'system'; import {record, section, validateRecord} from '../core/usage.js'; import {collectClaude} from '../providers/claude.js'; import {collectCodex} from '../providers/codex.js'; -import {collectCopilot} from '../providers/copilot.js'; -import {collectCursor} from '../providers/cursor.js'; import {commandSpec, findCommand} from '../services/commands.js'; -import {fingerprint, join, readJson, readText} from '../services/files.js'; +import {fingerprint, join, readJson} from '../services/files.js'; import {scanHistory} from '../services/history.js'; import {requestJson} from '../services/http.js'; -import {RpcClient, runCommand} from '../services/process.js'; +import {RpcClient} from '../services/process.js'; -const COLLECTORS = Object.freeze({codex: collectCodex, claude: collectClaude, - cursor: collectCursor, copilot: collectCopilot}); +const COLLECTORS = Object.freeze({codex: collectCodex, claude: collectClaude}); const HISTORY_ROOTS = { codex: GLib.getenv('CODEX_HOME') || join(GLib.get_home_dir(), '.codex'), claude: GLib.getenv('CLAUDE_CONFIG_DIR') || join(GLib.get_home_dir(), '.claude'), }; -const token = value => typeof value === 'string' && value.trim().length <= 8192 && - !/[\u0000-\u001f\u007f]/.test(value.trim()) ? value.trim() : null; const id = ARGV[0]; if (!Object.prototype.hasOwnProperty.call(COLLECTORS, id)) { - printerr('Usage: gjs -m src/collector/main.js [retention-days]'); + printerr('Usage: gjs -m src/collector/main.js [retention-days]'); System.exit(2); } const retentionText = ARGV[1] ?? ''; @@ -55,23 +50,6 @@ const io = { }, codexClient: () => new RpcClient(commandSpec('codex', ['app-server'], {runtimes: ['node']}), cancellable), http: (url, options) => requestJson(url, {...options, cancellable}), - async token(provider) { - if (provider === 'cursor') { - const auth = readJson(join(GLib.get_user_config_dir(), 'cursor', 'auth.json'), null, 1024 * 1024); - return token(auth?.accessToken); - } - if (provider !== 'copilot') - return null; - try { - const value = await runCommand(commandSpec('gh', ['auth', 'token']), - {timeout: 5000, cancellable, maxOutputBytes: 64 * 1024}); - const cleaned = token(value); - if (cleaned) - return cleaned; - } catch { /* Standalone setup token is also supported. */ } - return token(readText(join(GLib.get_user_config_dir(), 'usagebeam', 'copilot-token'), null, 64 * 1024)) ?? - token(readText(join(GLib.get_user_config_dir(), 'freeby', 'copilot-token'), null, 64 * 1024)); - }, }; (async () => { diff --git a/src/core/usage.js b/src/core/usage.js index 643d612..0ec49b5 100644 --- a/src/core/usage.js +++ b/src/core/usage.js @@ -1,5 +1,5 @@ export const SCHEMA_VERSION = 2; -export const NAMES = Object.freeze({codex: 'Codex', claude: 'Claude Code', cursor: 'Cursor', copilot: 'Copilot'}); +export const NAMES = Object.freeze({codex: 'Codex', claude: 'Claude Code'}); const STATES = new Set(['loading', 'ready', 'partial', 'stale', 'missing-auth', 'unsupported', 'unavailable']); const WINDOW_STATES = new Set(['active', 'exhausted', 'unlimited']); @@ -224,13 +224,6 @@ export function mergeRecord(previous, next, now = Date.now()) { return result; } -export function highestUsage(value) { - if (!value || !['ready', 'partial'].includes(value.limits.status)) - return null; - const values = value.limits.windows.filter(w => !w.unlimited && number(w.usedPercent) !== null).map(w => w.usedPercent); - return values.length ? Math.max(...values) : null; -} - export function localDate(time) { if (time === null || time === undefined || time === '') return null; diff --git a/src/providers/copilot.js b/src/providers/copilot.js deleted file mode 100644 index 7765418..0000000 --- a/src/providers/copilot.js +++ /dev/null @@ -1,41 +0,0 @@ -import {number, record, section, windowUsage} from '../core/usage.js'; - -export async function collectCopilot(io) { - const result = record('copilot'); - result.capabilities.limits = true; - const token = await io.token('copilot'); - if (!token) { - result.limits = {...result.limits, ...section('missing-auth', 'Sign in to GitHub CLI to read Copilot usage.')}; - return result; - } - result.accountKey = io.fingerprint(token); - let status; - let data; - try { - ({status, data} = await io.http('https://api.github.com/copilot_internal/user', - {headers: {Authorization: `Bearer ${token}`, Accept: 'application/json'}})); - } catch { - result.limits = {...result.limits, ...section('unavailable', 'Could not reach GitHub Copilot usage. Retry later.')}; - return result; - } - if (status !== 200 || !data || typeof data !== 'object') { - result.limits = {...result.limits, ...section([401, 403].includes(status) ? 'missing-auth' : 'unavailable', - [401, 403].includes(status) ? 'Reconnect GitHub CLI to read Copilot usage.' : `Usage endpoint unavailable (HTTP ${status}).`)}; - return result; - } - for (const [key, quota] of Object.entries(data.quota_snapshots ?? {})) { - const limit = number(quota.entitlement); - const remaining = number(quota.remaining); - const item = windowUsage({id: key, label: key.replaceAll('_', ' '), unit: 'requests', limit, - used: limit !== null && remaining !== null ? Math.max(0, limit - remaining) : null, - unlimited: quota.unlimited === true, resetsAt: data.quota_reset_date_utc}); - if (item) - result.limits.windows.push(item); - } - result.plan = data.copilot_plan ?? null; - result.limits = {...result.limits, ...section(result.limits.windows.length ? 'ready' : 'unavailable', - result.limits.windows.length ? '' : 'No supported allowance was reported. Check the GitHub Copilot dashboard.'), - updatedAt: result.limits.windows.length ? io.now?.() ?? Date.now() : null, - source: result.limits.windows.length ? 'GitHub Copilot entitlement' : null}; - return result; -} diff --git a/src/providers/cursor.js b/src/providers/cursor.js deleted file mode 100644 index ed5f884..0000000 --- a/src/providers/cursor.js +++ /dev/null @@ -1,37 +0,0 @@ -import {number, record, section, windowUsage} from '../core/usage.js'; - -export async function collectCursor(io) { - const result = record('cursor'); - result.capabilities.limits = true; - const token = await io.token('cursor'); - if (!token) { - result.limits = {...result.limits, ...section('missing-auth', 'Sign in to Cursor to read usage.')}; - return result; - } - result.accountKey = io.fingerprint(token); - let status; - let data; - try { - ({status, data} = await io.http('https://api2.cursor.sh/aiserver.v1.DashboardService/GetCurrentPeriodUsage', - {method: 'POST', headers: {Authorization: `Bearer ${token}`, 'Connect-Protocol-Version': '1'}, body: {}})); - } catch { - result.limits = {...result.limits, ...section('unavailable', 'Could not reach Cursor usage. Retry later.')}; - return result; - } - if (status !== 200 || !data || typeof data !== 'object') { - result.limits = {...result.limits, ...section([401, 403].includes(status) ? 'missing-auth' : 'unavailable', - [401, 403].includes(status) ? 'Reconnect Cursor to read usage.' : `Usage endpoint unavailable (HTTP ${status}).`)}; - return result; - } - const noPlan = number(data.spendLimitUsage?.overallLimit) === 0; - const percent = noPlan ? null : number(data.planUsage?.totalPercentUsed); - const window = windowUsage({id: 'plan', label: 'Included usage', usedPercent: percent, - resetsAt: number(data.billingCycleEnd)}); - if (window) - result.limits.windows.push(window); - result.limits = {...result.limits, ...section(result.limits.windows.length ? 'ready' : 'unavailable', - result.limits.windows.length ? '' : 'No supported allowance was reported. Check the Cursor dashboard.'), - updatedAt: result.limits.windows.length ? io.now?.() ?? Date.now() : null, - source: result.limits.windows.length ? 'Cursor dashboard' : null}; - return result; -} diff --git a/src/services/files.js b/src/services/files.js index 69f4010..fc12096 100644 --- a/src/services/files.js +++ b/src/services/files.js @@ -6,9 +6,9 @@ export const fingerprint = value => GLib.compute_checksum_for_string(GLib.Checks const PRODUCT_DIRECTORY = 'usagebeam'; const LEGACY_DIRECTORY = 'freeby'; -const PROVIDERS = Object.freeze(['codex', 'claude', 'cursor', 'copilot']); +const PROVIDERS = Object.freeze(['codex', 'claude']); -export function readText(path, fallback = null, maxBytes = 16 * 1024 * 1024) { +function readText(path, fallback = null, maxBytes = 16 * 1024 * 1024) { if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) throw new Error('File size limit must be a positive integer'); try { diff --git a/src/ui/indicator.js b/src/ui/indicator.js index 61f996d..ee8931e 100644 --- a/src/ui/indicator.js +++ b/src/ui/indicator.js @@ -8,12 +8,12 @@ import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js'; import {NAMES, recentDates} from '../core/usage.js'; import {age, dateRange, modelName, tokens} from '../core/format.js'; import {historyOverview, latestUpdate, panelQuota, periodDays, providerStatus, - quotaPresentation} from './presentation.js'; + quotaPresentation, quotaSeverity} from './presentation.js'; import {actionButton, button, dayChart, disclosureButton, label, meter, modelMeter, limitRow, metricLabel, providerIcon, separatorDot} from './widgets.js'; const TAB_NAMES = {claude: 'Claude'}; -const PROVIDER_MARKS = {codex: '>_', claude: '✦', cursor: '⌁', copilot: '◆'}; +const PROVIDER_MARKS = {codex: '>_', claude: '✦'}; export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator extends PanelMenu.Button { _init(settings, extensionPath, openPreferences) { @@ -117,8 +117,8 @@ export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator this._panelProvider.text = name; const quota = panelQuota(record); this._panelValue.text = quota ? `${quota.percent}%` : '—'; - this._panelValue.style_class = `usagebeam-panel-value${quota?.percent >= 100 ? ' usagebeam-danger' : - quota?.percent >= 90 ? ' usagebeam-warning' : ''}`; + const severity = quotaSeverity(quota?.percent); + this._panelValue.style_class = `usagebeam-panel-value${severity ? ` usagebeam-${severity}` : ''}`; this._panelReset.text = quota?.reset ?? '—'; const resetDescription = quota?.reset === 'due' ? ', reset due' : quota?.reset ? `, resets in ${quota.reset}` : ''; @@ -160,14 +160,14 @@ export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator const view = quotaPresentation(window); if (!view) continue; - box.add_child(limitRow(view.name, view.value, view.reset)); + const severity = quotaSeverity(window.usedPercent); + box.add_child(limitRow(view.name, view.value, view.reset, severity)); if (!window.unlimited) { - const level = window.usedPercent >= 100 ? 'usagebeam-danger' : - window.usedPercent >= 90 ? 'usagebeam-warning' : ''; const resetDescription = view.reset === 'due' ? ', reset due' : view.reset ? `, resets in ${view.reset}` : ''; box.add_child(meter(window.usedPercent / 100, - `${view.name}: ${view.value} used${resetDescription}`, level)); + `${view.name}: ${view.value} used${resetDescription}`, + severity ? `usagebeam-${severity}` : '')); } this._contentBox.add_child(box); } diff --git a/src/ui/presentation.js b/src/ui/presentation.js index 4d3b0d8..1926032 100644 --- a/src/ui/presentation.js +++ b/src/ui/presentation.js @@ -2,6 +2,24 @@ import {resetCountdown} from '../core/format.js'; const CURRENT_STATES = new Set(['ready', 'partial']); +export const QUOTA_THRESHOLDS = Object.freeze({ + caution: 80, + warning: 90, + danger: 100, +}); + +export function quotaSeverity(percent) { + if (!Number.isFinite(percent)) + return null; + if (percent >= QUOTA_THRESHOLDS.danger) + return 'danger'; + if (percent >= QUOTA_THRESHOLDS.warning) + return 'warning'; + if (percent >= QUOTA_THRESHOLDS.caution) + return 'caution'; + return null; +} + export function providerStatus(record, refreshing = false) { if (CURRENT_STATES.has(record?.limits?.status)) return 'LIVE'; diff --git a/src/ui/widgets.js b/src/ui/widgets.js index c80d3bf..e902590 100644 --- a/src/ui/widgets.js +++ b/src/ui/widgets.js @@ -47,14 +47,15 @@ export function providerIcon(provider, extensionPath, style = '') { }); } -export function limitRow(name, value, reset) { +export function limitRow(name, value, reset, severity = null) { const box = new St.BoxLayout({style_class: 'usagebeam-limit-row', x_expand: true}); const title = label(name, 'usagebeam-limit-name', true); title.clutter_text.ellipsize = Pango.EllipsizeMode.END; box.add_child(title); const metrics = new St.BoxLayout({style_class: 'usagebeam-limit-metrics'}); if (reset) { - metrics.add_child(metricColumn(value, 'usagebeam-limit-percent')); + metrics.add_child(metricColumn(value, + `usagebeam-limit-percent${severity ? ` usagebeam-${severity}` : ''}`)); metrics.add_child(separatorDot('usagebeam-limit-separator')); metrics.add_child(metricColumn(reset, 'usagebeam-limit-reset')); } else { diff --git a/stylesheet.css b/stylesheet.css index ba45c50..4ab802d 100644 --- a/stylesheet.css +++ b/stylesheet.css @@ -255,22 +255,6 @@ background-color: st-mix(-st-accent-color, #b7cbd5, 42%); } -.usagebeam-warning { - color: #f6c85f; -} - -.usagebeam-danger { - color: #ff6b88; -} - -.usagebeam-warning .usagebeam-fill { - background-color: #f6c85f; -} - -.usagebeam-danger .usagebeam-fill { - background-color: #ff6b88; -} - .usagebeam-disclosure { min-height: 25px; margin: 3px 0 4px; @@ -452,3 +436,43 @@ .usagebeam-menu.usagebeam-light .usagebeam-status-live { color: #167345; } + +/* Quota severity is deliberately encoded in both color and numeric text. */ +.usagebeam-panel-value.usagebeam-caution, +.usagebeam-menu .usagebeam-limit-percent.usagebeam-caution { + color: #e8c65d; +} + +.usagebeam-panel-value.usagebeam-warning, +.usagebeam-menu .usagebeam-limit-percent.usagebeam-warning { + color: #ee9d50; +} + +.usagebeam-panel-value.usagebeam-danger, +.usagebeam-menu .usagebeam-limit-percent.usagebeam-danger { + color: #f05f78; +} + +.usagebeam-track.usagebeam-caution .usagebeam-fill { + background-color: #e8c65d; +} + +.usagebeam-track.usagebeam-warning .usagebeam-fill { + background-color: #ee9d50; +} + +.usagebeam-track.usagebeam-danger .usagebeam-fill { + background-color: #f05f78; +} + +.usagebeam-menu.usagebeam-light .usagebeam-limit-percent.usagebeam-caution { + color: #7a5b00; +} + +.usagebeam-menu.usagebeam-light .usagebeam-limit-percent.usagebeam-warning { + color: #9a4f00; +} + +.usagebeam-menu.usagebeam-light .usagebeam-limit-percent.usagebeam-danger { + color: #b12643; +} diff --git a/tests/shell/extension.js b/tests/shell/extension.js index 13091d4..3990209 100644 --- a/tests/shell/extension.js +++ b/tests/shell/extension.js @@ -209,6 +209,15 @@ export default class UsageBeamUITest extends Extension { limitName.get_theme_node().get_font().get_size(), 'Limit metrics lack font hierarchy'); const activityParts = matching(content, 'usagebeam-disclosure-summary'); assert(activityParts.length === 3, 'Activity summary is not split into semantic parts'); + for (const severity of ['caution', 'warning', 'danger']) { + const actors = matching(content, `usagebeam-${severity}`); + assert(actors.some(actor => actor.has_style_class_name('usagebeam-limit-percent')), + `${severity}: percentage does not expose quota severity`); + assert(actors.some(actor => actor.has_style_class_name('usagebeam-track')), + `${severity}: meter does not expose quota severity`); + } + assert(indicator._panelValue.has_style_class_name('usagebeam-danger'), + 'Panel indicator does not expose the highest quota severity'); const modelContent = matching(content, 'usagebeam-model-content')[0]; const [modelName, modelTotal] = modelContent.get_children(); assert(modelTotal.get_theme_node().get_font().get_size() < diff --git a/tests/unit/presentation.test.js b/tests/unit/presentation.test.js index ccaf195..c55fe25 100644 --- a/tests/unit/presentation.test.js +++ b/tests/unit/presentation.test.js @@ -1,7 +1,7 @@ import test from 'node:test'; import assert from 'node:assert/strict'; import {chartBarGeometry, historyOverview, latestUpdate, panelQuota, periodDays, providerStatus, - quotaName, quotaPresentation} from '../../src/ui/presentation.js'; + quotaName, quotaPresentation, quotaSeverity, QUOTA_THRESHOLDS} from '../../src/ui/presentation.js'; test('provider status distinguishes live, local, cached and setup data', () => { const value = {limits: {status: 'ready'}, history: {status: 'ready'}}; @@ -65,6 +65,18 @@ test('panel quota reports only the highest current quota window', () => { assert.equal(panelQuota(null, now), null); }); +test('quota severity uses explicit caution, warning and danger thresholds', () => { + assert.deepEqual(QUOTA_THRESHOLDS, {caution: 80, warning: 90, danger: 100}); + for (const value of [null, undefined, NaN, 0, 79.9]) + assert.equal(quotaSeverity(value), null); + assert.equal(quotaSeverity(80), 'caution'); + assert.equal(quotaSeverity(89.9), 'caution'); + assert.equal(quotaSeverity(90), 'warning'); + assert.equal(quotaSeverity(99.9), 'warning'); + assert.equal(quotaSeverity(100), 'danger'); + assert.equal(quotaSeverity(125), 'danger'); +}); + test('chart geometry gives every non-zero day visible width and bottom alignment', () => { assert.deepEqual(chartBarGeometry(70, 70, 50, 64), {x: 4, y: 0, width: 42, height: 64}); assert.deepEqual(chartBarGeometry(21.7, 70, 50, 64), {x: 4, y: 44, width: 42, height: 20}); diff --git a/tests/unit/usage.test.js b/tests/unit/usage.test.js index 5113ec9..09ebe18 100644 --- a/tests/unit/usage.test.js +++ b/tests/unit/usage.test.js @@ -1,6 +1,6 @@ import test from 'node:test'; import assert from 'node:assert/strict'; -import {aggregateEvents, highestUsage, mergeRecord, number, recentDates, record, validTime, validateRecord, windowUsage} from '../../src/core/usage.js'; +import {aggregateEvents, mergeRecord, number, recentDates, record, validTime, validateRecord, windowUsage} from '../../src/core/usage.js'; import {compactTokens, dateRange, modelName, resetCountdown, resetTime, tokens} from '../../src/core/format.js'; import {ThresholdTracker} from '../../src/core/notifications.js'; @@ -9,7 +9,6 @@ test('unknown metrics are not coerced to zero', () => { assert.equal(number(value), null); assert.equal(number(0), 0); assert.equal(windowUsage({id: 'plan', label: 'Plan', used: 0, limit: 0}), null); - assert.equal(highestUsage(record('codex')), null); }); test('unknown, exhausted and unlimited windows remain distinct', () => { @@ -24,7 +23,8 @@ test('unknown, exhausted and unlimited windows remain distinct', () => { test('contract rejects invalid provider IDs and quota values', () => { assert.throws(() => record('toString'), /Unsupported provider/); - assert.throws(() => validateRecord(record('codex'), 'cursor')); + assert.throws(() => record('cursor'), /Unsupported provider/); + assert.throws(() => validateRecord(record('codex'), 'claude')); const value = record('codex'); value.limits.windows.push({id: 'x', label: 'X', state: 'active', usedPercent: null}); assert.throws(() => validateRecord(value, 'codex')); @@ -77,7 +77,6 @@ test('failure preserves last successful values as stale, account change discards assert.equal(merged.limits.status, 'stale'); assert.equal(merged.limits.updatedAt, 100); assert.equal(merged.limits.windows[0].usedPercent, 50); - assert.equal(highestUsage(merged), null); next.accountKey = 'two'; assert.equal(mergeRecord(old, next).limits.windows.length, 0); next.accountKey = null; diff --git a/tools/smoke-shell.py b/tools/smoke-shell.py index 5894d81..c6f6792 100644 --- a/tools/smoke-shell.py +++ b/tools/smoke-shell.py @@ -58,13 +58,16 @@ def record(provider, name, plan, percentages, models): 'source': 'Synthetic smoke fixture', 'windows': [ {'id': f'{provider}:session', 'label': 'Session · 5 hours', 'usedPercent': percentages[0], - 'used': None, 'limit': None, 'unit': 'percent', 'unlimited': False, 'state': 'active', + 'used': None, 'limit': None, 'unit': 'percent', 'unlimited': False, + 'state': 'exhausted' if percentages[0] >= 100 else 'active', 'durationMinutes': 300, 'resetsAt': now + 7_200_000}, {'id': f'{provider}:weekly', 'label': 'Weekly', 'usedPercent': percentages[1], - 'used': None, 'limit': None, 'unit': 'percent', 'unlimited': False, 'state': 'active', + 'used': None, 'limit': None, 'unit': 'percent', 'unlimited': False, + 'state': 'exhausted' if percentages[1] >= 100 else 'active', 'durationMinutes': 10_080, 'resetsAt': now + 172_800_000}, - {'id': f'{provider}:reserve', 'label': 'Weekly reserve', 'usedPercent': 0, - 'used': None, 'limit': None, 'unit': 'percent', 'unlimited': False, 'state': 'active', + {'id': f'{provider}:reserve', 'label': 'Weekly reserve', 'usedPercent': percentages[2], + 'used': None, 'limit': None, 'unit': 'percent', 'unlimited': False, + 'state': 'exhausted' if percentages[2] >= 100 else 'active', 'durationMinutes': 10_080, 'resetsAt': now + 604_800_000}, ]}, 'history': {'status': 'ready', 'message': '', 'updatedAt': now, 'scope': 'local', @@ -82,8 +85,8 @@ def record(provider, name, plan, percentages, models): ] target = destination / 'state' / PRODUCT_DIRECTORY target.mkdir(parents=True, exist_ok=True) - records = {'codex': record('codex', 'Codex', 'Plus', (46, 7), models), - 'claude': record('claude', 'Claude Code', 'Pro', (100, 43), models)} + records = {'codex': record('codex', 'Codex', 'Plus', (100, 94, 83), models), + 'claude': record('claude', 'Claude Code', 'Pro', (46, 7, 0), models)} for provider, data in records.items(): (target / f'{provider}.json').write_text(json.dumps(data)) # Separate fixtures survive asynchronous collector updates to the cache. From 847b9b3d2bb3b8e45c79539f46817a35582a9120 Mon Sep 17 00:00:00 2001 From: kcnewman Date: Mon, 7 Sep 2026 18:59:50 +0000 Subject: [PATCH 31/44] docs: establish product-ready repository --- .AGENTS/README.md | 15 +- .AGENTS/plans/archive/v1-notes.md | 153 ------ .AGENTS/plans/roadmap.md | 488 ++++-------------- CHANGELOG.md | 177 ++----- CONTRIBUTING.md | 53 -- README.md | 189 +++---- docs/README.md | 11 - docs/architecture.md | 16 +- docs/assets/README.md | 8 - .../assets/screenshots/v1/panel-indicator.png | Bin 405726 -> 0 bytes docs/assets/screenshots/v1/usage-menu.png | Bin 429329 -> 0 bytes docs/providers/README.md | 12 - docs/providers/claude.md | 4 +- docs/providers/codex.md | 41 ++ metadata.json | 2 +- tools/check.js | 11 +- tools/package.py | 2 +- 17 files changed, 324 insertions(+), 858 deletions(-) delete mode 100644 .AGENTS/plans/archive/v1-notes.md delete mode 100644 CONTRIBUTING.md delete mode 100644 docs/README.md delete mode 100644 docs/assets/README.md delete mode 100644 docs/assets/screenshots/v1/panel-indicator.png delete mode 100644 docs/assets/screenshots/v1/usage-menu.png delete mode 100644 docs/providers/README.md create mode 100644 docs/providers/codex.md diff --git a/.AGENTS/README.md b/.AGENTS/README.md index 2c33f61..f5c8da4 100644 --- a/.AGENTS/README.md +++ b/.AGENTS/README.md @@ -1,17 +1,14 @@ # Agent workspace -This directory is the single home for agent guidance, active plans, historical -planning context, and implementation reference material. The root `AGENTS.md` -exists only because agent tooling discovers that conventional filename. +This directory contains the repository's durable agent guidance, active roadmap, +and visual product references. The root `AGENTS.md` is the discovery entrypoint. -Read these always-applicable rules before making changes: +Read these rules before changing the repository: 1. [Product and architecture](rules/architecture.md) 2. [Quality, privacy, and verification](rules/quality.md) 3. [Version control and releases](rules/releases.md) -Use [plans/roadmap.md](plans/roadmap.md) as the active milestone record. Material -under [plans/archive/](plans/archive/) is historical and must not override the -active roadmap. Visuals under [references/](references/) are design references, -not runtime assets, and are excluded from extension archives by the packaging -allowlist. +Use [plans/roadmap.md](plans/roadmap.md) for current product scope and milestone +gates. Images under [references/](references/) inform the visual language but are +never runtime or release assets. diff --git a/.AGENTS/plans/archive/v1-notes.md b/.AGENTS/plans/archive/v1-notes.md deleted file mode 100644 index ed28286..0000000 --- a/.AGENTS/plans/archive/v1-notes.md +++ /dev/null @@ -1,153 +0,0 @@ -# Freeby v1 planning archive - -Preserved from the original local `plan.md` on 2026-09-06. These are historical -investigation notes, not verified descriptions of the current implementation. -Some claims about disabled providers, completed fixes, quota periods, and -packaging differ from the current code. Revalidate any provider-specific -assumptions before using them. The active roadmap is [../roadmap.md](../roadmap.md). - ---- - -# Freeby Plan - -## Current state - -- **v1.0.2** released and merged to main -- 2 providers: Codex, Copilot (Cursor disabled — see below) -- Panel shows `ai·N` (count of providers with remaining quota) -- Panel text colored green/yellow/red by availability -- Click panel to refresh -- Dropdown shows per-provider usage and reset countdown -- Desktop notifications on limit hit -- Auto-refresh on wake from sleep -- Configurable refresh interval (gsettings) -- Settings UI in Extension Manager -- Parallel provider fetches (~2s) -- Architecture: extension.js (entry) + indicator.js (UI) + prefs.js (settings) -- CI/CD: GitHub Actions (shellcheck, meson build, bats tests) -- Accessibility: screen reader names, theme-aware CSS - -## Known limitations - -- Codex: uses `codex-check` CLI (npx), can be slow on first run -- Codex: limit is monthly (30 days), not 5h as formatted output suggests -- Codex: uses JSON output (`--json` flag) for reliable parsing -- Copilot: free tier is very limited (200 chat credits/month) -- No persistent storage — usage history not tracked - -## Cursor investigation (disabled in v1.0.2) - -### Problem -The Cursor API (`GetCurrentPeriodUsage`) only tracks paid plan usage. When `overallLimit == 0` and `remainingBonus == false`, it means: -- No paid subscription (`overallLimit: 0`) -- Free bonus tokens exhausted (`remainingBonus: false`) - -The API returns `totalPercentUsed: 0` which is "0% of nothing" — misleading. - -### API response when no subscription -```json -{ - "planUsage": { "totalPercentUsed": 0, "remainingBonus": false }, - "spendLimitUsage": { "overallLimit": 0, "pooledLimit": 0 }, - "displayMessage": "You've used 0% of your included usage" -} -``` - -### Why Cursor CLI shows "get a subscription" -It correctly detects: no paid plan + exhausted free bonus = needs subscription. - -### What the user experienced -- Used free bonus tokens recently (worked fine) -- Now bonus is exhausted, Cursor CLI says "get subscription" -- Extension was showing "0% used, resets in 3d" — misleading - -### Decision -Disable Cursor for now. Re-enable in v2.0.0 with paid subscription detection: -- `overallLimit > 0` → show usage percentage -- `overallLimit == 0 && remainingBonus == false` → "no subscription" -- `overallLimit == 0 && remainingBonus == true` → "free tier, bonus remaining" - -## Things to watch out for - -### API changes -- Cursor API is unofficial — could break without notice -- GitHub Copilot API could change endpoints or auth -- Codex API format might change in new `codex-check` versions (but JSON output is more stable) - -### Auth expiry -- Copilot tokens via `gh` stay valid as long as `gh auth` is active -- Copilot tokens from `copilot-setup` may expire after ~30 days - -### Rate limiting -- Script runs every 2 minutes — shouldn't hit rate limits -- But if user manually refreshes repeatedly, APIs might throttle - -### GNOME Shell compatibility -- Currently targets GNOME 45+ (ESM imports) -- New Shell versions may deprecate APIs (already fixed GNOME 50 messageTray change) -- Test on new GNOME releases before claiming support - -## Done - -- [x] Tooltip on hover — removed (PanelMenu.Button consumes hover events) -- [x] Color panel text — green/yellow/red based on availability -- [x] Click to refresh — panel click triggers refresh -- [x] Notifications — desktop alert on limit hit -- [x] Auto-refresh on wake — monitors systemd PrepareForSleep -- [x] Configurable refresh interval — gsettings (min 30s) -- [x] Settings UI — prefs.js with Extension Manager gear icon -- [x] Parallel fetches — background processes, ~2s total -- [x] Accessibility — screen reader names for all UI elements -- [x] Theme-aware CSS — uses currentColor and opacity -- [x] CI/CD — GitHub Actions with shellcheck, meson build, bats tests -- [x] Documentation — README, CONTRIBUTING, CHANGELOG -- [x] GNOME 50 support — added to shell-version, fixed messageTray API -- [x] Disable Cursor — free tier unsupported, misleading data - -## EGO submission (extensions.gnome.org) - -### Blocking issues - -1. **Scripts must be written in GJS** — Our `freeby.sh` uses bash + curl + python3. The guidelines say scripts "MUST be written in GJS, unless absolutely necessary." Options: - - **A) Rewrite in GJS** — Use Soup.Session for HTTP, native JSON.parse(). Cleanest for review but significant rewrite (~200 lines GJS replacing ~190 lines bash/python). Still needs subprocess for `npx codex-check`. - - **B) Keep bash, drop python3** — Replace python3 JSON parsing with grep/sed/awk. Removes one dependency but adds ~150 lines of fragile bash. Strengthens "scripts are necessary" argument. - - **C) Keep as-is** — Argue bash is necessary because we call system tools (npx, curl). Risky, may be rejected. - -2. **Unnecessary files** — EGO zip should only contain extension files. Currently ships tests/, .github/, CONTRIBUTING.md, CHANGELOG.md, meson.build, build-aux/. - -### Non-blocking fixes needed - -3. **Remove `version` from metadata.json** — Deprecated for EGO (they set it). - -4. **Disconnect button-press-event handler** — `this.connect('button-press-event', ...)` in indicator.js not disconnected in destroy(). - -5. **Cancel subprocess in disable** — If disable called while refresh in-flight, async callback could fire after cleanup. - -### Nice-to-have - -6. ESLint for code style consistency -7. UI following GNOME HIG more closely - -## Future features (v2.0.0+) - -### Cursor paid subscription support -- Re-enable Cursor provider -- Detect paid subscription via `overallLimit > 0` -- Show usage percentage for paid users -- Show "no subscription" for free users with exhausted bonus -- Track `remainingBonus` for free tier status - -### Larger features -- **Historical usage graph** — store daily snapshots and show a small sparkline in the dropdown -- **More providers** — Windsurf, Gemini, Groq, etc. -- **Flatpak support** — detect if tools are installed via Flatpak (different config paths) -- **Custom provider support** — user-added APIs via config - -## Release checklist - -- [ ] Update version in metadata.json -- [ ] Test all providers -- [ ] Verify countdown accuracy -- [ ] Check panel indicator updates -- [ ] Take screenshot for README -- [ ] Create GitHub release with tag diff --git a/.AGENTS/plans/roadmap.md b/.AGENTS/plans/roadmap.md index 2519241..6122b08 100644 --- a/.AGENTS/plans/roadmap.md +++ b/.AGENTS/plans/roadmap.md @@ -1,385 +1,103 @@ -# UsageBeam usage monitor roadmap - -Updated: 2026-09-07. Status: the UsageBeam identity and active-provider -interface refinements are complete on `dev`; Cursor remains the next alpha.3 -provider milestone. - -## Agreed direction - -Build a reliable native GNOME usage monitor for **Codex, Claude Code, Cursor, -and Copilot**, covering free and paid plans. The first stable release is about -usage: quota windows, reset times, activity history, and model breakdowns where -the provider exposes them. - -The canonical product name is **UsageBeam**, with extension UUID -`usagebeam@oo7kc.github.io` and schema `org.gnome.shell.extensions.usagebeam`. -Keep those identities stable from alpha.3 onward. Icon work, cost breakdowns, -provider spending/balances, estimates, and budgets are deferred to a later phase. -Subscription percentages must not be derived from token totals. - -Every completed implementation milestone ends with a conventional commit, a -version tag, and an installable GitHub prerelease. The first stable release must -cover all four providers; earlier prereleases introduce them incrementally. - -## Reference analysis and interface - -The [Claude reference](../references/claude-usage.jpeg) and -[Codex reference](../references/codex-usage.png) share this structure: - -1. Provider mark, name, and reported plan. -2. A selector for connected/enabled providers; omit the selector when only one exists. -3. Independently labeled quota windows with percent-used meters and reset countdowns. -4. A seven-column daily token chart, with today emphasized. -5. Model totals shown as horizontal bars. - -Adapt that hierarchy to GNOME using native St/Clutter widgets, theme-aware colors, -aligned/tabular numerals, restrained separators, visible focus, and accessible -labels. Add a footer with last-successful-update time, refresh, and preferences. -Keep activity collapsed by default; when opened, expand the popup naturally -without a nested scrolling region. - -- Quota bars use a fixed 0–100% scale. Activity bars scale against the largest value in the displayed period. -- Daily and model breakdowns must clearly state their period and whether the source is local or account-wide. -- Show data-supported sections only; keep connection errors and recovery guidance visible. -- Distinguish zero activity from unavailable history. Never fabricate models, plans, usage, or historical coverage. -- Show cached data immediately while independently refreshing providers. Mark stale sections with their timestamps. -- Show a useful setup state when no provider is connected. Do not silently hide the extension after a failure. - -## Architecture - -Keep a thin native GNOME extension with modular GJS collection and a versioned -usage contract. Provider code must not manipulate UI actors. UI code must not -parse provider transcripts or depend on provider endpoint response shapes. -Expensive history work belongs in a managed GJS subprocess, not the Shell UI -process. Avoid a persistent background daemon in this first iteration. - -```text -usagebeam/ -├── .AGENTS/ # Rules, roadmap, archive, design references -├── AGENTS.md # Tool-discovery entrypoint -├── extension.js # GNOME lifecycle entry -├── prefs.js # Preferences entry -├── metadata.json -├── stylesheet.css -├── src/ -│ ├── core/ # Contract, validation, aggregation, formatting -│ ├── providers/ # One adapter per provider -│ ├── services/ # Scheduling, cache, processes, notifications -│ ├── collector/ # Managed GJS collection entry -│ ├── ui/ # Indicator, selector, meters, charts -├── schemas/ -├── tests/ -│ ├── unit/ -│ └── integration/ -├── tools/ # Development and packaging commands -├── docs/ # Architecture, provider docs, historical screenshots -├── meson.build -└── .github/workflows/ -``` - -Keep this layout current as modules land. Add a directory only when it owns real -content, preserve existing behavior during moves, and do not retain superseded -runtime implementations. - -### Usage contract - -- Include a schema version, stable provider identity, optional account/plan identity, and explicit capabilities. -- Represent each quota window with its unit, reported usage/limit or percentage, duration, reset timestamp, and state. -- Represent activity with dated totals and model aggregates, including provider-specific input/output/cache semantics. -- Track source, account/local scope, measurement period, coverage, and last-successful-update timestamps. -- Track limits and history status independently so local history remains useful when an account endpoint fails. -- Distinguish ready, missing authentication, unsupported data, unavailable data, stale data, and exhausted quota. -- Leave unknown values absent/null; never coerce an empty response to zero or an unlimited allowance to exhaustion. - -### Collection and storage - -- Prefer documented provider interfaces and existing supported CLI authentication; verify versions and account eligibility. -- For Codex, evaluate app-server account/rate-limit reads and available account-usage reads; probe compatibility before using optional methods. -- Use local usage records for supported history/model data; retain explicit scope when account-wide activity is also available. -- Validate Claude, Cursor, and Copilot sources independently. Preserve their real units and advertise only verified capabilities. -- Replace automatic `npx` downloads and the hardcoded helper path with packaged, extension-relative resources. -- Give each provider a deadline, bounded retries/backoff, and independent success/failure handling. -- Parse changed records incrementally; handle repeated events, cumulative counters, partial writes, rotation, and timezone boundaries. -- Keep private usage metadata in XDG state/cache locations using atomic writes, bounded retention, and schema migrations. -- Do not retain credentials, prompts, responses, or complete transcripts in UsageBeam's history or diagnostics. - -## Known regressions to address - -The preceding repository analysis identified these issues; add focused regression -coverage during the foundation work: - -- Notification construction uses the tray instance as a constructor namespace. -- The sleep signal condition refreshes before suspend rather than on resume. -- An in-flight refresh can apply data after the indicator is destroyed. -- Repeated refreshes can leave overlapping collection processes. -- Empty Codex/Cursor objects can display successful zero usage. -- Summary-text matching can show an exhausted Cursor quota as a green row. -- Initial/error states can trigger incorrect quota-limit notifications. -- The Codex `npx` path has no explicit collection deadline. -- The uninstall instructions remove the shared compiled schema file instead of recompiling remaining schemas. - -## Implementation milestones - -### 1. Foundation and Codex — v2.0.0-alpha.1 - -- [x] Establish the target module boundaries, versioned contract, development commands, and fixture-based tests. -- [x] Repair the known lifecycle, notification, quota-state, and install/uninstall regressions. -- [x] Implement bounded Codex collection, cached results, available quota windows, daily activity, and model totals. -- [x] Build the native provider header, quota meters, activity/model views, status footer, and loading/error/setup states. -- [x] Keep existing provider behavior working until deliberately migrated; document any verified data limitations. -- [x] Verify the complete Codex path, temporary installation, disable/re-enable cleanup, and offline/partial behavior. -- [x] Commit, tag, publish, and verify the installable alpha.1 prerelease. - -Evidence: 13 deterministic unit tests, GJS incremental-history and process -integration checks, strict schema validation, deterministic packaging, an -isolated GNOME Shell 50 load/disable/re-enable smoke test, and a redacted live -Codex CLI check confirming account quota, seven-day activity, and model data. -Released from commit `ce64146` on `dev` as -[`v2.0.0-alpha.1`](https://github.com/oo7kc/freeby/releases/tag/v2.0.0-alpha.1). -The downloaded 22,488-byte archive matched the tagged CI artifact at SHA-256 -`eef63b1978b13f2adda9fe3c172cebfe7584835461f1b07f2b8ffd8b887b56fb`. - -### 2. Claude Code — v2.0.0-alpha.2 - -- [x] Implement Claude detection, supported authentication/limits, and incremental usage history. -- [x] Add provider switching and independent partial-data handling using the shared contract. -- [x] Verify quota windows, daily/model totals, duplicate handling, missing credentials, and expired authentication. -- [x] Commit, tag, publish, and verify the installable alpha.2 prerelease. - -Evidence: Claude Code 2.1.218 detection and missing-auth collection were checked -locally. Synthetic quota, expired-auth, repeated-message, cache-token, and -private-cache fixtures pass. An authenticated Claude account was not available, -so the OAuth endpoint path is upstream- and fixture-validated rather than live. -Released from commit `48b28ac` on `dev` as -[`v2.0.0-alpha.2`](https://github.com/oo7kc/freeby/releases/tag/v2.0.0-alpha.2). -The downloaded 25,249-byte archive matched the tagged CI artifact at SHA-256 -`04f8417dd0fd0cafdc636293134ce80bf1477a0e6b39342e9f3681bffbef6fae`. - -### Repository organization checkpoint - -- [x] Consolidate agent rules, active/historical plans, and visual references - under `.AGENTS/` while retaining the root discovery entrypoint. -- [x] Remove obsolete v1 runtime/test files and split provider ownership cleanly. -- [x] Add architecture/documentation indexes and enforce the intended layout in - development checks. -- [x] Verify and commit the organization checkpoint on `dev` before alpha.3. - -Evidence: `npm run verify` passed 20 unit tests, the Codex/Claude GJS history -and subprocess integration suite, strict schema/layout/link validation, and a -deterministic alpha.3-dev package. The exact archive loaded, disabled, and -re-enabled without extension errors in an isolated GNOME Shell 50 session. - -### Interface and local-runtime correction checkpoint - -- [x] Match the supplied Omarchy hierarchy with a compact, theme-integrated - panel, visible data status, equal provider tabs, and left-anchored graphics. -- [x] Keep limits and freshness in the default view, move daily/model activity - behind an accessible disclosure, and let the complete activity view expand - without a nested scrollbar. -- [x] Inherit the user's Shell font and accent, place the subtle accent on the - popup perimeter, and consolidate actions into a compact footer. -- [x] Detect user-local and version-managed Codex installations from GNOME - Shell's minimal environment and use the installation's matching Node runtime. -- [x] Exercise all primary popup widgets with synthetic records and reject GJS - criticals in the isolated lifecycle smoke test. - -Evidence: 20 unit tests and the GJS integration suite pass, including newest -version-manager selection and sibling-runtime launch coverage. A sanitized live -probe with Codex CLI 0.145.0, an fnm-only binary path, no `/usr/bin/node`, and a -GNOME-equivalent minimal `PATH` returned the signed-in Plus plan with three live -quota windows. The populated two-provider popup loaded, disabled, and re-enabled -without extension GJS criticals in a private GNOME Shell 50 session. A follow-up -compactness pass retained that lifecycle coverage, added deterministic activity -summary tests, and smoke-tested the exact packaged archive with the theme-derived -perimeter and collapsed details structure. - -### Runtime and contract hardening checkpoint - -- [x] Enforce schema sources, freshness, typed fields, collection bounds, and - provider identity before records reach UI state or persistence. -- [x] Keep subprocess environments scoped, failures classified, responses - bounded, and private cache permissions repaired on atomic writes. -- [x] Make incremental history resilient to counter resets, replacement, - source removal, invalid cache data, and stable private session identities. -- [x] Enforce runtime import boundaries and an exact, repository-owned package - allowlist in development tooling. -- [x] Verify deterministic packaging and the exact archive lifecycle before - committing the checkpoint on `dev`. - -Evidence: `npm run verify` passed 25 deterministic unit tests, strict schema, -layout, import-boundary, link, GJS history, process, HTTP-policy, and packaging -checks. Two builds produced the same 35,916-byte archive at SHA-256 -`48a6a6ae7546db9634385388db5a8b1dcc5bcc81135e65e93376187c2ca30d18`. -That exact archive loaded, disabled, and re-enabled without extension errors in -an isolated GNOME Shell 50 session. A sanitized live check with Codex CLI -0.145.0 returned the Plus plan, three account quota windows, seven-day local -activity, and model totals under usage schema version 2. - -### Product identity checkpoint - -- [x] Rename the active product, runtime classes/styles, package, schema, archive, - CI artifact, and documentation from Freeby to UsageBeam. -- [x] Adopt `usagebeam@oo7kc.github.io` and - `org.gnome.shell.extensions.usagebeam` as the permanent extension identities. -- [x] Migrate recognized derived state, history caches, and explicitly changed - preferences without copying credentials or overwriting new UsageBeam values. -- [x] Verify a clean install, former-alpha migration, deterministic packaging, - and disable/re-enable lifecycle; commit the checkpoint on `dev` without moving - or tagging `main`. - -Evidence: `npm run check` passed 26 unit tests, strict schema/layout/identity -validation, and the GJS history, migration, command, process, and HTTP-policy -integration suite. A live local migration copied four recognized derived files -and two explicitly changed settings, retained private `0600` file modes, matched -the original data checksums, and preserved the 30-second refresh preference. -Two builds produced the same 38,098-byte archive at SHA-256 -`d92595924b705125cebc6bd09dc9689f6562522a9cf9ffaba98db7d666f0926a`. -That exact UsageBeam archive loaded, disabled, and re-enabled without extension -errors in a private GNOME Shell 50 session. The former local extension package -was removed and the new UUID was installed and enabled for the next login; GNOME -Shell 50 cannot discover a brand-new UUID in an already-running Wayland session. - -### Active-provider interface checkpoint - -- [x] Replace the generic panel label with one compact active-provider readout: - adaptive provider icon, short name, highest current quota, and reset countdown. -- [x] Put quota reset countdowns beside their percentages, rename the Codex - reserve window to `Weekly reserve` for display, and remove redundant reset rows. -- [x] Render seven-day activity as an accessible, softly colored column chart - while retaining explicit local/account scope and measurement dates. -- [x] Remove the popup's internal scrolling region so activity expands to its - complete natural height, while remaining collapsed by default. -- [x] Add immediate left/right-of-calendar placement in native preferences and - package the supplied Claude and adaptive Codex SVG marks. - -Evidence: `npm run check` passed 28 deterministic unit tests, strict schema, -layout, identity, and GJS integration checks. Two builds produced the same -41,049-byte archive at SHA-256 -`0622c2af111f7f1985e99340af32fb99c1dd4020bb9f2f03ccee774c1973e542`. -That exact archive loaded with populated Codex and Claude records, moved live -from the left to the right of the calendar, disabled, and re-enabled without -extension errors in a private GNOME Shell 50 session. - -### Visual QA correction checkpoint - -- [x] Correct the daily chart's zero-width bar allocation and keep every - non-zero column visibly bottom-aligned against the period maximum. -- [x] Center the popup around its panel trigger and retain GNOME's work-area - clamping at screen edges. -- [x] Give the activity disclosure a complete border and stable right-aligned - metadata group; align quota percentages and resets in tabular columns. -- [x] Replace raw ISO ranges with concise locale-aware dates and refine the - surface into light/dark accent-tinted slate variants with mixed system and - system-monospace typography. - -Evidence: `npm run check` passed 29 deterministic unit tests, including chart -geometry and date-presentation coverage, plus the strict schema/layout and GJS -integration suite. -Two builds produced the same 42,120-byte archive at SHA-256 -`11a51d84ed710e30cfc633fb6d6c7b5f8c6a7479c8e40dca95350e0f9d1526bc`. -The exact archive loaded, changed calendar side live, disabled, and re-enabled -without extension or stylesheet errors in a private GNOME Shell 50 session. - -### Stable placement and compact typography checkpoint - -- [x] Add left and right panel areas while preserving both calendar-side choices - and the existing default. Keep provider/quota changes within a fixed, - font-relative slot so the calendar stays still. -- [x] Put the gap between the calendar and indicator on the exact panel midpoint. -- [x] Use locally installed SF Pro Text and SF Pro Display, establish distinct - primary/secondary type levels, and keep quota columns aligned without ellipses. -- [x] Replace baseline-dependent middle-dot glyphs with consistently sized dot - actors centered between panel, quota, and activity metadata. -- [x] Allocate bars without feeding their sizes back into preferred-size requests; - restore compact model rows and natural expansion without an inner scrollbar. -- [x] Remove the duplicate rectangular shadow and preserve the rounded perimeter - and light/dark surfaces. -- [x] Add a test-only private-Shell companion that opens the popup, exercises the - disclosure, checks rendered geometry across repeated layouts and provider - changes, and captures both themes. Exclude it from runtime archives. - -Evidence (2026-09-07): `npm run check` passed 29 unit tests plus syntax, strict -schema, repository, and GJS integration checks. Repeated builds produced the same -44,391-byte archive, SHA-256 -`718e72e2921c573746b79136dadce72e611f38beff357ac4a9bb27c5a8964cda`. -The exact archive passed private GNOME Shell 50 lifecycle and real open-popup -checks at normal size, 125% text size, and on a 2× virtual monitor. Model rows -measured 29 logical pixels at normal and enlarged text settings, without growth -across repeated layouts. All four placements preserved indicator width and clock -position across Codex, Claude, and unavailable quota states. Screenshots were -inspected for SF Pro rendering, chart fills, compact rows, and the outer border. -Physical/fractional monitor combinations and other panel-layout extensions still -require desktop QA. This is a dev-only correction, not a completed provider -milestone or a stable release. - -### 3. Cursor — v2.0.0-alpha.3 - -- [ ] Revalidate current Cursor authentication and usage sources for free and paid accounts. -- [ ] Handle paid allowance, remaining bonus, absent subscription, and unsupported metrics without fabricated percentages. -- [ ] Add all verified usage capabilities to the shared interface and document integration limits. -- [ ] Verify fixtures for empty responses, expired authentication, inaccessible endpoints, and quota exhaustion. -- [ ] Commit, tag, publish, and verify the installable alpha.3 prerelease. - -### 4. Copilot — v2.0.0-alpha.4 - -- [ ] Revalidate current Copilot authentication, entitlement data, and usage units for supported account types. -- [ ] Implement usage collection and available history/model metrics without assuming every account exposes them. -- [ ] Keep request counts, tokens, allowances, and unknown/unlimited states distinct. -- [ ] Verify all four providers together, including partial outages and missing integrations. -- [ ] Commit, tag, publish, and verify the installable alpha.4 prerelease. - -### 5. Preferences and interface polish — v2.0.0-alpha.5 - -- [ ] Expand the existing preferences entry into organized native libadwaita pages. -- [x] Add immediate left/right-of-calendar panel placement for the compact active-provider readout. -- [ ] Add provider enablement/order, default provider, refresh controls, and connection diagnostics. -- [ ] Add notification thresholds, deduplication per account/window, and history retention/clear controls. -- [ ] Preserve valid existing settings and document new defaults and migrations. -- [ ] Verify keyboard navigation, screen-reader labels, dark/light themes, long content, natural popup expansion, and display scaling. -- [ ] Commit, tag, publish, and verify the installable alpha.5 prerelease using the finalized name and existing icon. - -### 6. Stabilization — v2.0.0-beta.1, then v2.0.0 - -- [ ] Validate supported GNOME versions; keep metadata aligned with tested compatibility. -- [ ] Verify history accuracy, bounded collection, responsiveness, and all four provider capability declarations. -- [ ] Test clean install, upgrade, disable/re-enable, uninstall, and downgrade with an isolated installation prefix. -- [ ] Finalize README, setup/troubleshooting, architecture/provider docs, screenshots, and release instructions. -- [ ] Commit, tag, publish, and verify beta.1; issue further beta prereleases when feedback requires changes. -- [ ] Resolve release-blocking feedback and publish the first stable usage-monitoring release. - -## Verification and release gate - -Every milestone requires applicable lint/syntax checks, deterministic unit and -integration tests, strict schema validation, and a packaged-install smoke test. -GUI/lifecycle changes also require relevant GNOME desktop checks. Live provider -verification is separate from credential-free CI and must identify which account -types and client versions were actually tested. - -1. Confirm the intended repository and release branch; preserve unrelated worktree changes. -2. Update this plan, the changelog, and the project release version. Do not use the deprecated metadata `version` field as the release source of truth. -3. Complete the milestone checks and record results, remaining limitations, and any unavailable checks. -4. Commit only the intended, tested milestone changes using conventional commits. -5. Tag the commit and build the downloadable extension archive from that exact revision. Exclude tests, development tools, references, and credentials from the runtime archive. -6. Publish the GitHub release as a prerelease for alpha/beta tags, with installation/upgrade instructions, verification evidence, known limitations, and a downgrade path. -7. Verify the remote tag, release status, downloadable artifact, and archive contents; record the tag/commit/release link here. - -Do not mark a milestone complete until its code, checks, commit, and prerelease -are complete. A blocked required check or publication remains explicitly open. -Documentation setup is preparatory work and does not trigger a product prerelease. - -## After usage monitoring is reliable - -- Design a distinctive UsageBeam icon without changing the finalized extension identity. -- Design cost breakdowns separately, distinguishing provider-reported charges from estimates and including currency, period, and pricing provenance. -- Consider budgets, additional providers, multiple accounts, cross-device history, and a separate dashboard only as later scoped work. - -## References and historical context - -- [Omarchy Agents architecture and panel behavior](https://github.com/omacom/omarchy/blob/quattro/shell/plugins/agents/README.md) -- [Codex app-server documentation](https://learn.chatgpt.com/docs/app-server) -- [GNOME extension review guidelines](https://gjs.guide/extensions/review-guidelines/review-guidelines.html) -- [Archived v1 planning notes](archive/v1-notes.md), including a Cursor no-subscription investigation that requires revalidation. - -Sources describe the state inspected during planning; verify them again before -depending on a provider interface or publishing compatibility claims. If upstream -code or assets are reused, preserve the applicable license and attribution. +# UsageBeam product roadmap + +## Product contract + +UsageBeam is a native GNOME Shell monitor for AI coding usage. It reports only +provider-backed account limits and derived local activity, keeps those scopes +visibly separate, and remains useful when one source is temporarily unavailable. + +The stable product identity is: + +- Name: UsageBeam +- Extension UUID: `usagebeam@oo7kc.github.io` +- Settings schema: `org.gnome.shell.extensions.usagebeam` +- Repository owner: `oo7kc` + +Cost, billing, budgets, and a new icon remain outside the usage-monitoring +release. A provider adapter does not ship until its authentication, units, +unsupported states, and failure behavior have been validated. + +## Current product surface + +- [x] Codex account limits through its installed CLI app-server. +- [x] Codex seven-day local token and model activity. +- [x] Claude Code supported account limits through its saved OAuth sign-in. +- [x] Claude Code seven-day local token and model activity. +- [x] Independent live, local, cached, syncing, and setup states. +- [x] Compact active-provider indicator and naturally expanding detail view. +- [x] Fixed left/right areas plus exact left/right calendar placement. +- [x] Theme-derived light/dark surfaces, accessible controls, and quota severity. +- [x] Private, bounded, cancellable collection with strict record validation. +- [x] Deterministic packaging and isolated GNOME lifecycle coverage. + +Released foundations: + +- [`v2.0.0-alpha.1`](https://github.com/oo7kc/freeby/releases/tag/v2.0.0-alpha.1): + Codex and the provider-neutral platform. +- [`v2.0.0-alpha.2`](https://github.com/oo7kc/freeby/releases/tag/v2.0.0-alpha.2): + Claude Code and provider switching. + +## Milestone 3 — Cursor + +- [ ] Revalidate authentication and usage sources on current free and paid + accounts. +- [ ] Preserve reported units and distinguish paid allowance, bonus usage, + missing subscription, unknown values, and exhaustion. +- [ ] Add only verified capabilities to the shared contract and interface. +- [ ] Cover empty payloads, expired authentication, unavailable endpoints, + changed upstream shapes, and quota exhaustion. +- [ ] Complete the release gate and publish `v2.0.0-alpha.3`. + +## Milestone 4 — GitHub Copilot + +- [ ] Revalidate authentication, entitlement data, and usage units for supported + account types. +- [ ] Keep request counts, tokens, allowances, unknown values, and unlimited + states distinct. +- [ ] Implement only verified limit and activity capabilities. +- [ ] Verify all supported providers together under partial outages and missing + integrations. +- [ ] Complete the release gate and publish `v2.0.0-alpha.4`. + +## Milestone 5 — Preferences and diagnostics + +- [ ] Add provider enablement, order, and default-provider controls. +- [ ] Add connection diagnostics and actionable recovery guidance. +- [ ] Add notification threshold, history retention, and clear-data controls. +- [ ] Preserve existing settings through schema changes and document defaults. +- [ ] Complete keyboard, screen-reader, scaling, long-content, and theme QA. +- [ ] Complete the release gate and publish `v2.0.0-alpha.5`. + +## Stabilization + +- [ ] Validate every advertised GNOME Shell version. +- [ ] Verify activity accuracy, bounded collection, responsiveness, migration, + clean install, upgrade, disable/re-enable, uninstall, and downgrade. +- [ ] Complete provider compatibility notes and product screenshots. +- [ ] Publish and verify `v2.0.0-beta.1`; resolve release-blocking feedback. +- [ ] Publish the first stable usage-monitoring release. + +## Release gate + +Every milestone must satisfy all applicable gates before its checkbox is closed: + +1. Strict syntax, import-boundary, schema, repository-layout, unit, and GJS + integration checks. +2. Deterministic packaging from the exact candidate revision. +3. Installation and disable/re-enable testing of that exact archive in an + isolated GNOME Shell session. +4. Live provider verification that records the tested client version and account + scope; unavailable live access remains an explicit limitation. +5. A conventional commit, green remote CI, annotated prerelease tag, downloadable + archive, checksum, upgrade notes, and downgrade path. + +Synthetic tests must remain network-free and contain no credentials, prompts, +responses, raw transcripts, account identities, or raw session identifiers. + +## After usage monitoring + +- Create a distinctive product icon without changing the established UUID. +- Design cost breakdowns separately, distinguishing provider-reported charges + from estimates and retaining currency, period, and pricing provenance. +- Scope budgets, multiple accounts, cross-device history, and a larger dashboard + only after the usage-monitoring release is reliable. diff --git a/CHANGELOG.md b/CHANGELOG.md index e974706..f68dc04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,154 +1,92 @@ # Changelog -All notable changes are documented here. UsageBeam uses semantic prerelease versions -while version 2 is developed on the `dev` branch. +All notable UsageBeam changes are documented here. ## Unreleased +### Added + +- UsageBeam product identity with the permanent extension UUID + `usagebeam@oo7kc.github.io` and settings schema + `org.gnome.shell.extensions.usagebeam`. +- Active-provider panel readout with provider icon, highest current quota, and + reset countdown. +- Left area, right area, left-of-calendar, and right-of-calendar placement. +- Expandable seven-day activity chart and compact per-model token totals. +- Native preferences for placement, refresh interval, and notifications. +- Explicit quota severity states: caution at 80%, warning at 90%, and exhausted + at 100%. + ### Changed -- Renamed the product from Freeby to UsageBeam and adopted the permanent GNOME - extension UUID `usagebeam@oo7kc.github.io` and settings schema - `org.gnome.shell.extensions.usagebeam` ahead of alpha.3. -- Migrate recognized derived state, incremental history caches, and explicitly - changed preferences from the former alpha identity without moving credentials - or overwriting values already set for UsageBeam. -- Split command discovery from private file storage and launch discovered CLIs - with a process-local environment, explicit deadlines, cancellation reasons, - and output limits. -- Upgraded the provider-neutral usage record to schema version 2 with strict - source, freshness, quota, date, token-component, and collection-bound checks. -- Decomposed panel rendering into focused limit, daily activity, model activity, - status, and footer paths backed by unit-testable presentation decisions. -- Hardened deterministic packaging to reject symlinked or out-of-tree inputs and - verify the archive against its exact runtime allowlist. -- Reworked the popup into a compact, theme-integrated usage panel based on the - supplied Omarchy references, with equal-width `Claude`/`Codex` tabs, visible - provider status, and filled model-usage rows. -- Inherit the user's Shell accent color, with a restrained accent on - the popup's actual perimeter instead of a nested hard-coded blue frame. -- Keep limits and freshness visible in the default view while placing seven-day - and model activity behind an accessible disclosure control; the complete view - now expands naturally without a nested scrollbar. -- Consolidate freshness, refresh, and settings into a single compact footer. -- Replace the generic panel label with a single active-provider readout showing - its supplied icon, short name, highest current quota, and reset countdown. -- Place quota reset countdowns inline, display Codex reserve usage as `Weekly - reserve`, and render daily token history as a softly colored seven-column chart. -- Add four immediate panel positions: left, right, left of calendar, and right - of calendar. Reserve a stable indicator width across providers and quota states; - place the calendar/indicator gap exactly on the panel midpoint. -- Refine the interface into an accent-tinted slate surface with an explicit - perimeter, locally installed SF Pro Text/Display typography, stronger hierarchy, - and a bordered activity disclosure in both light and dark Shell color schemes. -- Consolidated agent rules, the active roadmap, archived planning notes, and - visual implementation references under `.AGENTS/`, with a minimal root - `AGENTS.md` discovery entrypoint. -- Added documentation indexes and a runtime architecture guide; historical v1 - screenshots now live under `docs/assets/screenshots/v1/`. -- Split the temporary combined provider module into dedicated Cursor and Copilot - adapters without changing their preview behavior. +- Rebuilt the popup as a compact, accent-aware GNOME surface with SF Pro + typography when available, aligned quota metrics, and stable panel geometry. +- Account limits, local activity, and cached results now expose their scope and + freshness independently. +- Codex discovery supports common user-local and Node version-manager layouts. +- Provider records use a strict versioned contract with bounded collection, + typed quota states, source attribution, and private derived storage. +- Recognized Freeby alpha settings and derived data migrate without copying + credentials or overwriting existing UsageBeam data. ### Fixed -- Keep one stable private session identity across incremental history scans, - preventing resumed Codex records from inflating session counts. -- Detect cumulative-token resets, same-size history replacements, stale source - removal, invalid cached events, and bounded directory-scan failures without - silently presenting incorrect totals. -- Require HTTPS for provider requests, bound response sizes, and repair private - cache directory/file permissions on every atomic write. -- Distinguish live account data, local-only activity, cached data, active sync, - and setup states while safely truncating long provider/model labels. -- Discover Codex installed through fnm, nvm, mise, asdf, Volta, and common - user-local binary directories even when GNOME Shell starts with a minimal - `PATH`. -- Launch version-managed Codex installations with their matching sibling Node - runtime, restoring live account quota reads on systems without `/usr/bin/node`. -- Anchor daily and model fills to the left edge and render human-readable model - names instead of centered or malformed bars. -- Enlarge activity headings, dates, and summary text with proportional SF Pro - typography, falling back to a system font when SF Pro is unavailable. -- Replace allocation-notification sizing loops with a native bar actor whose - fill never affects preferred size, preventing growing model rows and layout - flicker. Keep model rows compact and chart columns bottom aligned. -- Remove the duplicate rectangular popup shadow, retaining the rounded outer - border. Prevent quota numbers and separators from ellipsizing and right-align - numeric text within stable columns. -- Add private-Shell regression tests that open the actual popup, measure all four - positions, switch providers, verify repeated allocation, and capture both themes. -- Establish a clearer information hierarchy by reducing secondary quota, reset, - activity-summary, and model-total type. Replace text glyph separators with - consistently sized, optically centered dots in the menu and panel indicator. -- Allocate daily chart bars explicitly so non-zero days always receive visible - width and bottom-aligned height. -- Center the popup on the active-provider panel indicator instead of anchoring - its left edge to the trigger. -- Align limit percentages, separators, and reset countdowns in fixed tabular - columns, and format activity periods as concise locale-aware date ranges. +- Daily chart columns allocate visible bottom-aligned bars for non-zero activity. +- Model fills remain compact and stable across repeated layout passes. +- Calendar placement remains fixed while providers and quota values change. +- Reset values, percentages, and separator dots retain consistent alignment. +- Expired quota windows are not presented as current cached data. +- Empty, malformed, or unavailable provider responses are never shown as zero + usage. ### Removed -- Removed the superseded v1 indicator, Bash collector/setup scripts, and their - obsolete Bats suite. These files were no longer installed after alpha.1. +- Unverified Cursor and Copilot preview adapters. Providers now ship only after + their data sources and failure states have completed validation. +- Superseded runtime scripts and historical UI artifacts that were not part of + the product. ## 2.0.0-alpha.2 - 2026-09-06 ### Added -- Claude Code detection and saved OAuth sign-in handling, including explicit - missing, absent, and expired authentication states. -- Anthropic 5-hour, weekly, and model-scoped quota parsing with both current - percentage and older fractional utilization normalization. -- Incremental Claude Code transcript scanning with message deduplication and - per-model input, output, cache-read, and cache-write totals. -- Provider switching between Codex and Claude Code using the shared native UI. +- Claude Code installation and saved OAuth sign-in detection. +- Supported 5-hour, weekly, and model-scoped Claude quota windows. +- Incremental Claude Code activity with per-model input, output, cache-read, and + cache-write totals. +- Shared provider switching between Codex and Claude Code. -### Changed +### Fixed -- New installations enable Codex and Claude Code by default. -- Expired cached quota windows are discarded instead of being displayed as stale - after their reset time. +- Expired cached quota windows are discarded after their reset time. +- Missing, absent, and expired Claude authentication remain distinct states. -### Verification limits +### Verification scope -- Claude Code 2.1.218 detection and the unauthenticated path were exercised on - this machine. Quota responses, expired authentication, duplicates, and cache - semantics were verified with synthetic fixtures against the current upstream - interface; no signed-in Claude account was available for a live quota probe. +- Claude Code 2.1.218 installation detection and missing-authentication behavior + were exercised locally. +- Quota payloads, expired authentication, duplicate messages, and cache-token + semantics were verified with deterministic fixtures. +- A signed-in Claude account was not available for a live quota probe. ## 2.0.0-alpha.1 - 2026-09-06 ### Added -- Versioned provider-neutral usage contract with explicit capabilities, source - scope, freshness, quota states, reset timestamps, and token categories. +- Provider-neutral usage contract with explicit capabilities, source scope, + freshness, quota state, reset timestamp, and token categories. - Bounded Codex app-server collection for account rate limits. -- Incremental local Codex history scanning with seven-day and model aggregates. +- Incremental local Codex history with seven-day and model aggregates. - Private XDG state/cache persistence, stale-data recovery, and refresh backoff. -- Native provider header, quota meters, daily activity, model totals, status - footer, setup/error states, and accessible controls. -- Deterministic archive packaging, unit tests, GJS integration tests, and an - isolated headless GNOME lifecycle smoke test. +- Deterministic packaging and isolated GNOME lifecycle verification. ### Fixed -- Refresh now occurs after resume rather than before suspend. -- Provider jobs no longer overlap or update destroyed UI. -- Notifications require a verified threshold crossing and are deduplicated per +- Refresh occurs after resume instead of before suspend. +- Provider jobs do not overlap or update a destroyed interface. +- Notifications require a verified threshold crossing and deduplicate per account, quota window, and reset period. -- Missing or empty provider responses are no longer reported as zero usage. -- Runtime `npx` downloads and unbounded collection processes were removed. -- Schemas install inside the extension and uninstall no longer requires deleting - a shared compiled schema file. - -### Known limitations - -- GNOME Shell 50 is the only compatibility target verified for alpha.1. -- Codex activity is local to this device. Account quota limits and local token - history intentionally remain separate scopes. -- Cursor and Copilot adapters are preview-only until alpha.3 and alpha.4. -- Claude Code support is scheduled for alpha.2. +- Runtime package downloads and unbounded collection processes were removed. ## 1.0.2 - 2026-04-27 @@ -157,5 +95,4 @@ while version 2 is developed on the `dev` branch. - Kept the dropdown open after manual refresh and fixed an invalid popup accessibility property. -Earlier release history remains available in the repository's Git tags and -GitHub releases. +Earlier history remains available in the repository's Git tags and releases. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 7748788..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,53 +0,0 @@ -# Contributing to UsageBeam - -Development targets the `dev` branch. `main` is reserved for stable releases. -Keep provider changes small enough to verify independently and use conventional -commit messages such as `feat:`, `fix:`, `test:`, `docs:`, and `chore:`. - -## Development environment - -Install Node.js 20 or newer, GJS, Soup 3 introspection data, Meson, Ninja, and the -GLib schema compiler. Then run: - -```bash -git clone https://github.com/oo7kc/freeby.git -cd freeby -git switch dev -npm run check -npm run pack -``` - -For an isolated installation: - -```bash -meson setup build --prefix=/tmp/usagebeam-install -meson install -C build -``` - -`python3 tools/smoke-shell.py` additionally verifies lifecycle and actual popup -geometry in a private headless GNOME Shell session. The test-only companion under -`tests/shell/` injects synthetic usage, exercises all four panel positions and -provider changes, expands activity, and checks bar sizes, text alignment, and -layout stability. JSON measurements and light/dark screenshots are saved in the -printed temporary directory. The companion is never included in release archives; -it requires no unsafe Shell evaluation or access to your live desktop. -Use `--archive dist/ARCHIVE.zip` to test the exact packaged payload. -Add `--scale 2` for a 2× virtual monitor or `--text-scale 1.25` for enlarged -accessibility text. Physical/fractional monitor scaling still needs desktop QA. - -## Design and architecture - -- Keep `extension.js` and `prefs.js` thin GNOME entry points. -- Keep provider response parsing in `src/providers/`, orchestration and storage - in `src/services/`, shared contracts in `src/core/`, and actors in `src/ui/`. -- Preserve source scope and provider units. Unknown data stays unknown; account - limits never derive from local token totals. -- Keep collectors bounded and cancellable. Never download runtime dependencies. -- Fixtures must be synthetic or sanitized. Do not commit tokens, account IDs, - prompts, responses, or complete provider transcripts. -- Preserve GNOME theme behavior, keyboard focus, accessible names, and readable - scaling when changing the panel UI. - -The authoritative milestone gates and release process are in -[.AGENTS/plans/roadmap.md](.AGENTS/plans/roadmap.md). Project-wide agent guidance -starts in [AGENTS.md](AGENTS.md), with the substantive rules under `.AGENTS/`. diff --git a/README.md b/README.md index 749c005..f58578b 100644 --- a/README.md +++ b/README.md @@ -1,123 +1,128 @@ # UsageBeam -UsageBeam is a native GNOME Shell usage monitor for AI coding tools. It keeps -account limits, reset windows, recent local token activity, and model totals one -click away in the top panel. - -Version 2 is being delivered provider by provider. The former Freeby identity -was used through `v2.0.0-alpha.2`; development now uses the permanent UsageBeam -identity. Cursor and Copilot adapters remain opt-in previews while their -dedicated milestones are completed. - -## What the current prerelease includes - -- Account quota windows from the installed Codex CLI's app-server interface. -- Claude Code 5-hour, weekly, and model-scoped limits through its saved OAuth - sign-in, with clear missing/expired-authentication states. -- Reset countdowns without guessing missing values. -- Seven-day local Codex and Claude Code activity with per-model input, output, - cache-read, and cache-write totals. -- Cached results shown as stale while a provider independently refreshes. -- Explicit unavailable, unsupported, missing-authentication, and exhausted states. -- Bounded collectors, refresh backoff, wake refresh, cancellation on disable, and - threshold-crossing notifications. -- A native, theme-aware, keyboard-focusable interface with a compact active-provider - panel readout and expandable seven-day activity chart. - -UsageBeam stores only derived usage metadata under the standard XDG state/cache -directories. It does not copy prompts, responses, transcripts, or credentials. -Local activity covers this device only and must not be interpreted as billing or -subscription usage. +UsageBeam puts Codex and Claude Code usage where it is easiest to see: in the +GNOME top panel. The compact indicator shows the active provider's highest quota +and reset countdown; its popup reveals every reported limit plus seven days of +local token and model activity. -## Requirements +## At a glance -- GNOME Shell 50 (the version verified for this prerelease). -- Codex CLI installed and signed in for Codex account limits. System installs - and common user-local/version-manager layouts (including fnm, nvm, mise, - asdf, and Volta) are detected from GNOME Shell's restricted environment. -- Claude Code installed and signed in for Claude account limits; local Claude - transcripts remain useful independently. -- GJS with Gio/GLib and Soup 3 introspection data. -- Meson, Ninja, and `glib-compile-schemas` when installing from source. +- Live account limits and reset windows for Codex and Claude Code. +- A stable, single-provider panel indicator that does not move the clock when + its content changes. +- Four panel placements: left area, right area, left of calendar, or right of + calendar. +- Seven-day local activity chart and per-model token totals. +- Independent live, local, cached, syncing, and setup states. +- Light and dark surfaces derived from the active GNOME accent color. +- Private local storage with no prompt, response, transcript, or credential + retention. -## Install from source +UsageBeam displays only data reported by a provider or found in local usage +records. Local activity covers this device; it is not billing data and is never +converted into an account quota. -Development happens on the `dev` branch: +## Supported providers -```bash -git clone https://github.com/oo7kc/freeby.git -cd freeby -git switch dev -meson setup build --prefix="$HOME/.local" -meson install -C build -gnome-extensions enable usagebeam@oo7kc.github.io -``` +### Codex -Log out and back in if GNOME Shell has not discovered the extension. Starting -with alpha.3, a downloaded release archive can instead be installed with: +UsageBeam reads account quota windows through the installed Codex CLI app-server +and scans local Codex session records for activity. The CLI must be installed +and signed in to show live limits. Common user-local, fnm, nvm, mise, asdf, and +Volta installations are discovered even when GNOME Shell has a restricted +`PATH`. -```bash -gnome-extensions install --force usagebeam@oo7kc.github.io-VERSION.zip -``` +### Claude Code -The identity migration copies recognized derived usage data and explicitly -changed settings from an installed Freeby alpha without overwriting existing -UsageBeam values. It does not copy credentials. After confirming UsageBeam is -active, the former `freeby@kelvin.local` extension can be uninstalled. +UsageBeam reads supported account limits from Claude Code's saved OAuth sign-in +and scans local Claude Code project records for activity. Account limits require +an active sign-in; local activity can remain available independently. -## Settings +See the detailed [Codex](docs/providers/codex.md) and +[Claude Code](docs/providers/claude.md) provider notes for source and +compatibility details. + +## Usage semantics + +Quota bars use a fixed 0–100% scale and retain their numeric percentage: -The development preferences window offers four panel positions: left, right, -left of calendar, and right of calendar. For calendar-side placement, the gap -between the clock and indicator sits exactly on the panel midpoint; the indicator -reserves a constant width so provider changes do not move the clock. Placement -follows GNOME's panel boxes (other panel-layout extensions may override their -arrangement). +- Below 80% uses the normal theme accent. +- 80–89% uses a restrained yellow caution state. +- 90–99% uses an orange warning state. +- 100% and above uses a red exhausted state. + +Color reinforces the value; it never replaces the percentage or state text. +Activity bars are relative to the largest value in the displayed seven-day +period and do not represent account limits. + +## Requirements -The popup uses locally installed SF Pro Text, with SF Pro Display for the provider -heading and system font fallback when unavailable. Fonts are not bundled or -downloaded. Activity expands fully with compact model rows and no inner scrollbar. +- GNOME Shell 50. +- GJS with Gio, GLib, and Soup 3 introspection data. +- The Codex CLI and/or Claude Code, installed and signed in for account limits. +- SF Pro Text and SF Pro Display are used when installed; GNOME's system fallback + is used otherwise. UsageBeam does not bundle or download fonts. -Preferences also control refresh frequency and notifications. The schema supports the -default provider, ordered enabled providers, history retention, and notification -threshold; these receive their full preferences interface in alpha.5. +## Install -To opt into a preview adapter during development: +Download the UsageBeam ZIP from [GitHub Releases](https://github.com/oo7kc/freeby/releases), +then run: ```bash -gsettings set org.gnome.shell.extensions.usagebeam enabled-providers "['codex', 'claude', 'cursor', 'copilot']" +gnome-extensions install --force ./usagebeam@oo7kc.github.io-VERSION.zip +gnome-extensions enable usagebeam@oo7kc.github.io ``` -Preview providers are not part of the alpha.2 compatibility promise. See the -[Claude provider notes](docs/providers/claude.md) for scope and compatibility -details. +Log out and back in when installing UsageBeam for the first time so GNOME Shell +can discover the new extension identity. + +## Settings -## Verify a checkout +Open the preferences window from the popup or with: ```bash -npm run check -npm run pack -python3 tools/smoke-shell.py +gnome-extensions prefs usagebeam@oo7kc.github.io ``` -The final command starts a private headless GNOME session, opens the popup with -synthetic data, verifies allocation and placement, and saves light/dark screenshots -in a temporary directory. It does not enable the extension in the active desktop. -Live-provider checks are intentionally separate from these credential-free tests. +Preferences control panel placement, refresh frequency, and quota notifications. +Calendar-side placement keeps the gap between the indicator and clock on the +panel midpoint. The indicator reserves a stable width so switching providers or +loading new quota data does not move the calendar. + +## Privacy and storage + +UsageBeam processes provider data locally and stores only derived usage metadata: + +- State: `$XDG_STATE_HOME/usagebeam` +- Incremental history cache: `$XDG_CACHE_HOME/usagebeam` -## Uninstall or downgrade +Files are created with private permissions. Credentials are read only when a +provider requires them and are never copied, cached, or logged. Account and +session identifiers used for change detection or deduplication are hashed before +persistence. Prompt and response content is ignored. + +Recognized settings and derived data from older Freeby alpha builds are migrated +once without overwriting existing UsageBeam data. Credentials are excluded from +that migration. + +## Troubleshooting + +- **Limits show Setup:** open the provider's CLI and confirm it is signed in. +- **Activity is local only:** this is expected; UsageBeam does not merge records + from other devices. +- **Values show Cached:** the last valid values remain visible during a temporary + provider or network failure. +- **The indicator is absent after installation:** log out and back in, then enable + the extension again. +- **Another panel extension changes placement:** UsageBeam follows GNOME's panel + boxes, so layout extensions may override the final arrangement. + +## Remove ```bash gnome-extensions disable usagebeam@oo7kc.github.io gnome-extensions uninstall usagebeam@oo7kc.github.io ``` -The extension-local schema is removed with the extension; shared system schema -artifacts are never deleted. To downgrade, install an older release archive with -`gnome-extensions install --force` and restart the session. Releases through -alpha.2 use the former UUID and therefore install as a separate extension. - -See [CONTRIBUTING.md](CONTRIBUTING.md) for development practices and -[.AGENTS/plans/roadmap.md](.AGENTS/plans/roadmap.md) for the ordered provider -milestones. Architecture and provider documentation is indexed in -[docs/README.md](docs/README.md). UsageBeam is licensed under the [MIT License](LICENSE). +UsageBeam is licensed under the [MIT License](LICENSE). Its runtime design and +security boundaries are documented in [Architecture](docs/architecture.md). diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 91b263e..0000000 --- a/docs/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Documentation - -- [Architecture](architecture.md) describes runtime boundaries, collection flow, - storage, and packaging. -- [Provider notes](providers/README.md) record verified capabilities and upstream - compatibility limits. -- [Historical v1 screenshots](assets/screenshots/v1/) are retained for release - history; they do not represent the version 2 interface. - -Agent rules, active milestones, archived plans, and visual implementation -references live in [.AGENTS/](../.AGENTS/README.md). diff --git a/docs/architecture.md b/docs/architecture.md index 44238fb..ca2ed70 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -28,8 +28,8 @@ never reads provider credentials or transcript formats. private XDG storage. - `src/ui/` owns Shell actors and accessibility behavior; provider-status and period presentation decisions remain pure and unit-testable. -- `prefs.js` is the libadwaita preferences entry point until configuration pages - are split during alpha.5. +- `prefs.js` is the native libadwaita preferences entry point for placement, + refresh, and notification controls. Collectors have deadlines, bounded output, and cancellation. Child processes receive a scoped `PATH` without changing the Shell process environment. Refresh @@ -60,9 +60,9 @@ not billing data and never becomes a fabricated subscription percentage. ## Distribution -Meson installs only extension entry points, source modules, provider SVG marks, -metadata, styles, license, and an extension-local schema. `tools/package.py` -uses the same explicit runtime allowlist to produce deterministic ZIP files. -Inputs must be regular, repository-owned files, and the completed archive is -checked against the exact allowlist; agent material, docs, tests, tools, -screenshots, and references cannot enter a release archive. +Meson installs only extension entry points, source modules, verified provider +adapters, provider SVG marks, metadata, styles, license, and an extension-local +schema. `tools/package.py` uses the same explicit runtime allowlist to produce +deterministic ZIP files. Inputs must be regular, repository-owned files, and the +completed archive is checked against that allowlist; repository guidance, docs, +tests, tools, and references cannot enter a release archive. diff --git a/docs/assets/README.md b/docs/assets/README.md deleted file mode 100644 index e52ab23..0000000 --- a/docs/assets/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Documentation assets - -`screenshots/v1/` preserves the original Freeby interface for release history. -Current version 2 screenshots will be added during the alpha.5 interface polish -and stabilization milestones. - -Implementation-only visual references belong under `.AGENTS/references/`, not in -this public-facing documentation asset tree. diff --git a/docs/assets/screenshots/v1/panel-indicator.png b/docs/assets/screenshots/v1/panel-indicator.png deleted file mode 100644 index 02694cc7c3d90a016dbd29dcaf0b2ee5704b644a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 405726 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%0A_+5rF85MYdDNB0H#;(r+nfG}q95%l?_nXE_Zl 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ξmyE<4I%?pLqwj9 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


*?lI#qzqD^3}~9mfTR@ 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 S2Y!yv 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%0AK~5!4#qU>Zu9UYUE`D9 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

g5PzkN5y+G~E|ICj(zjUU=82 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

4Qi&ft&SM**Q4Lo5g6c}x|P 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%0D2Uuc!rP)iFP-NqBAE!C+x}R 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 diff --git a/docs/providers/README.md b/docs/providers/README.md deleted file mode 100644 index 28c9ecd..0000000 --- a/docs/providers/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# Provider documentation - -- [Claude Code](claude.md): account quota plus local daily/model history; released - in `v2.0.0-alpha.2`. -- Codex: account quota through app-server plus local daily/model history; released - in `v2.0.0-alpha.1`. A dedicated compatibility note will be added during - stabilization. -- Cursor: preview adapter pending validation in `v2.0.0-alpha.3`. -- GitHub Copilot: preview adapter pending validation in `v2.0.0-alpha.4`. - -The active release status and verification evidence live in -[the roadmap](../../.AGENTS/plans/roadmap.md). diff --git a/docs/providers/claude.md b/docs/providers/claude.md index 4bf6dc8..fc061bc 100644 --- a/docs/providers/claude.md +++ b/docs/providers/claude.md @@ -33,9 +33,9 @@ is written to UsageBeam's XDG cache. History is local to this device, covers the displayed seven-day period, and is not a measure of subscription quota or billable cost. -## Alpha.2 verification +## Verification status - Claude Code 2.1.218 installation detection and missing-auth behavior: live. - Standard/scoped limits, percentage normalization, expired auth, endpoint auth errors, duplicate messages, and token categories: deterministic fixtures. -- Signed-in account quota probe: not available on the development machine. +- Signed-in account quota probe: not yet available for live verification. diff --git a/docs/providers/codex.md b/docs/providers/codex.md new file mode 100644 index 0000000..5c626be --- /dev/null +++ b/docs/providers/codex.md @@ -0,0 +1,41 @@ +# Codex provider + +UsageBeam keeps Codex account limits and local activity as separate sources. +Failure of either source does not turn missing data into zero usage. + +## Account limits + +The collector launches the installed Codex CLI app-server, completes its normal +initialization handshake, reads the current signed-in account, and requests +account rate limits. It supports the reported session, weekly, reserve, and +scoped quota windows without inventing windows that are absent from the response. + +UsageBeam does not persist the account email or account identifier. When an +identifier is available, it is reduced to a SHA-256 fingerprint used only to +prevent cached values from crossing accounts. + +The CLI is discovered from the Shell `PATH`, common user-local directories, and +fnm, nvm, mise, asdf, and Volta layouts. When a version-managed CLI requires +Node, its sibling runtime is added only to the collector process environment. + +## Local activity + +Codex JSONL records are scanned incrementally from `sessions/` and +`archived_sessions/` under `$CODEX_HOME` or `~/.codex`. UsageBeam reads timestamps, +models, session identifiers, and token counters. Prompt and response content is +ignored. + +Cumulative counters are converted into individual deltas, including counter +reset handling. Cached-input and cache-write counters retain their own +categories. Session identifiers are hashed before derived events are persisted. + +History covers the displayed period on this device. It is not account-wide +subscription usage or billable cost. + +## Verification status + +- Codex CLI 0.145.0: live account, quota, and local-history verification. +- Session/weekly/reserve parsing, cumulative resets, rotated/replaced records, + duplicate events, and version-managed runtime discovery: deterministic tests. +- GNOME Shell 50 restricted-environment discovery: isolated lifecycle and + sanitized live verification. diff --git a/metadata.json b/metadata.json index 5dedec7..7ea9310 100644 --- a/metadata.json +++ b/metadata.json @@ -1,7 +1,7 @@ { "uuid": "usagebeam@oo7kc.github.io", "name": "UsageBeam", - "description": "AI coding usage monitor for account limits, reset windows, local token activity and model breakdowns. Supports Codex and Claude Code; additional providers arrive through version 2 prereleases.", + "description": "Private, at-a-glance usage monitoring for Codex and Claude Code, with account limits, reset windows, local token activity and model breakdowns.", "shell-version": ["50"], "settings-schema": "org.gnome.shell.extensions.usagebeam", "url": "https://github.com/oo7kc/freeby" diff --git a/tools/check.js b/tools/check.js index 4bd954e..3068785 100644 --- a/tools/check.js +++ b/tools/check.js @@ -49,7 +49,7 @@ if (!readFileSync('meson.build', 'utf8').includes(`version: '${version}'`)) const required = ['AGENTS.md', '.AGENTS/README.md', '.AGENTS/plans/roadmap.md', '.AGENTS/rules/architecture.md', '.AGENTS/rules/quality.md', '.AGENTS/rules/releases.md', - 'docs/README.md', 'docs/architecture.md']; + 'docs/architecture.md', 'docs/providers/codex.md', 'docs/providers/claude.md']; for (const file of required) { if (!existsSync(file)) throw new Error(`Missing repository guidance: ${file}`); @@ -61,8 +61,13 @@ const icons = walk('icons'); if (icons.length !== 2 || !icons.includes('icons/claude.svg') || !icons.includes('icons/codex-symbolic.svg')) throw new Error('Unexpected runtime icon input'); +const providerFiles = walk('src/providers').filter(file => file.endsWith('.js')).sort(); +if (providerFiles.join(',') !== 'src/providers/claude.js,src/providers/codex.js') + throw new Error(`Unverified provider adapters must not ship: ${providerFiles.join(', ')}`); const obsolete = ['plan.md', 'indicator.js', 'src/providers/legacy.js', 'scripts/freeby.sh', - 'scripts/copilot-setup.sh', 'tests/freeby.bats', 'docs/s1.png', 'docs/s2.png', 'reference-images']; + 'src/providers/cursor.js', 'src/providers/copilot.js', 'scripts/copilot-setup.sh', + 'tests/freeby.bats', 'docs/README.md', 'docs/assets', 'docs/providers/README.md', + 'docs/s1.png', 'docs/s2.png', 'reference-images', 'CONTRIBUTING.md']; for (const file of obsolete) { if (existsSync(file)) throw new Error(`Obsolete repository path returned: ${file}`); @@ -71,7 +76,7 @@ for (const file of walk('.AGENTS/rules')) { if (readFileSync(file, 'utf8').split('\n').length > 50) throw new Error(`${file}: agent rules must stay focused and under 50 lines`); } -const markdown = ['README.md', 'CONTRIBUTING.md', 'AGENTS.md', ...walk('.AGENTS'), ...walk('docs')] +const markdown = ['README.md', 'CHANGELOG.md', 'AGENTS.md', ...walk('.AGENTS'), ...walk('docs')] .filter(file => file.endsWith('.md')); for (const file of markdown) { const text = readFileSync(file, 'utf8'); diff --git a/tools/package.py b/tools/package.py index 593047e..ec2cfb5 100644 --- a/tools/package.py +++ b/tools/package.py @@ -64,7 +64,7 @@ def package(source, output): if set(names) != expected: raise RuntimeError('Extension archive contents differ from the runtime allowlist') if any(name.startswith(('.AGENTS/', '.github/', 'docs/', 'tests/', 'tools/')) for name in names): - raise RuntimeError('Development-only content entered the extension archive') + raise RuntimeError('Repository-only content entered the extension archive') print(archive.resolve()) return archive From 3c61e56481751ed274daae335b5c9f09e9e49f19 Mon Sep 17 00:00:00 2001 From: kcnewman Date: Mon, 7 Sep 2026 19:24:00 +0000 Subject: [PATCH 32/44] fix: tighten panel spacing and activity scope --- CHANGELOG.md | 4 ++++ src/core/format.js | 15 --------------- src/ui/indicator.js | 9 +++++---- src/ui/widgets.js | 6 +++--- stylesheet.css | 26 ++++++++++++++++++++------ tests/shell/extension.js | 38 ++++++++++++++++++++++++++++++++++---- tests/unit/usage.test.js | 5 +---- 7 files changed, 67 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f68dc04..d133006 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,10 @@ All notable UsageBeam changes are documented here. - Model fills remain compact and stable across repeated layout passes. - Calendar placement remains fixed while providers and quota values change. - Reset values, percentages, and separator dots retain consistent alignment. +- The active-provider readout uses compact internal and calendar-side spacing, + while retaining a fixed width across provider changes. +- Seven-day activity emphasizes its local/account scope without repeating a + date range already represented by the chart. - Expired quota windows are not presented as current cached data. - Empty, malformed, or unavailable provider responses are never shown as zero usage. diff --git a/src/core/format.js b/src/core/format.js index 254b52d..5b91b1b 100644 --- a/src/core/format.js +++ b/src/core/format.js @@ -12,21 +12,6 @@ export function compactTokens(value) { return tokens(value).replace(/\.0(?=[BMK]$)/, ''); } -export function dateRange(period, locale = undefined) { - const start = new Date(`${period?.start ?? ''}T12:00:00`); - const end = new Date(`${period?.end ?? ''}T12:00:00`); - if (!Number.isFinite(start.getTime()) || !Number.isFinite(end.getTime()) || end < start) - return 'Dates unavailable'; - const formatter = new Intl.DateTimeFormat(locale, { - day: 'numeric', - month: 'short', - year: 'numeric', - }); - return typeof formatter.formatRange === 'function' - ? formatter.formatRange(start, end) - : `${formatter.format(start)} – ${formatter.format(end)}`; -} - export function resetTime(time, now = Date.now()) { if (!Number.isFinite(time) || time <= 0) return 'Reset time unavailable'; diff --git a/src/ui/indicator.js b/src/ui/indicator.js index ee8931e..3d4bdbd 100644 --- a/src/ui/indicator.js +++ b/src/ui/indicator.js @@ -6,7 +6,7 @@ import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js'; import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js'; import {NAMES, recentDates} from '../core/usage.js'; -import {age, dateRange, modelName, tokens} from '../core/format.js'; +import {age, modelName, tokens} from '../core/format.js'; import {historyOverview, latestUpdate, panelQuota, periodDays, providerStatus, quotaPresentation, quotaSeverity} from './presentation.js'; import {actionButton, button, dayChart, disclosureButton, label, meter, modelMeter, @@ -18,6 +18,7 @@ const PROVIDER_MARKS = {codex: '>_', claude: '✦'}; export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator extends PanelMenu.Button { _init(settings, extensionPath, openPreferences) { super._init(0.5, 'UsageBeam usage monitor'); + this.add_style_class_name('usagebeam-panel-button'); this._settings = settings; this._extensionPath = extensionPath; this._openPreferences = openPreferences; @@ -25,10 +26,10 @@ export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator this._detailsExpanded = false; this._panelStatus = new St.BoxLayout({style_class: 'usagebeam-panel-status'}); this._panelIcon = new St.Bin({style_class: 'usagebeam-panel-icon-slot', y_align: Clutter.ActorAlign.CENTER}); - this._panelProvider = label('UsageBeam', 'usagebeam-panel-provider', true); + this._panelProvider = label('UsageBeam', 'usagebeam-panel-provider'); this._panelProvider.clutter_text.ellipsize = Pango.EllipsizeMode.END; this._panelValue = metricLabel('—', 'usagebeam-panel-value'); - this._panelReset = metricLabel('—', 'usagebeam-panel-reset'); + this._panelReset = metricLabel('—', 'usagebeam-panel-reset', true); this._panelSeparator = separatorDot('usagebeam-panel-separator'); for (const actor of [this._panelIcon, this._panelProvider, this._panelValue, this._panelSeparator, this._panelReset]) @@ -210,7 +211,7 @@ export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator const overview = historyOverview(history); this._heading(`LAST ${overview?.days ?? history.days.length} DAYS · ${tokens(overview?.total ?? 0)} TOKENS`, parent); const scope = history.scope === 'account' ? 'Account activity' : 'This device'; - parent.add_child(label(`${scope} · ${dateRange(history.period)}`, 'usagebeam-caption')); + parent.add_child(label(scope, 'usagebeam-history-scope')); const today = recentDates(Date.now(), 1)[0]; parent.add_child(dayChart(history.days, today)); } diff --git a/src/ui/widgets.js b/src/ui/widgets.js index e902590..56764d5 100644 --- a/src/ui/widgets.js +++ b/src/ui/widgets.js @@ -30,9 +30,9 @@ export function separatorDot(style = '') { }); } -function metricColumn(text, style) { +function metricColumn(text, style, alignment = Clutter.ActorAlign.END) { const child = metricLabel(text, '', true); - child.x_align = Clutter.ActorAlign.END; + child.x_align = alignment; return new St.Bin({style_class: style, child}); } @@ -57,7 +57,7 @@ export function limitRow(name, value, reset, severity = null) { metrics.add_child(metricColumn(value, `usagebeam-limit-percent${severity ? ` usagebeam-${severity}` : ''}`)); metrics.add_child(separatorDot('usagebeam-limit-separator')); - metrics.add_child(metricColumn(reset, 'usagebeam-limit-reset')); + metrics.add_child(metricColumn(reset, 'usagebeam-limit-reset', Clutter.ActorAlign.START)); } else { metrics.add_child(metricColumn(value, 'usagebeam-limit-value')); } diff --git a/stylesheet.css b/stylesheet.css index 4ab802d..df43fb7 100644 --- a/stylesheet.css +++ b/stylesheet.css @@ -30,9 +30,14 @@ background-color: st-mix(-st-accent-color, #f4f8f9, 4%); } +.usagebeam-panel-button { + -natural-hpadding: 4px; + -minimum-hpadding: 3px; +} + .usagebeam-panel-status { - spacing: 5px; - width: 15em; + spacing: 4px; + width: 12.5em; font-family: "SF Pro Text"; font-size: 9.5pt; font-feature-settings: "tnum"; @@ -70,6 +75,7 @@ .usagebeam-panel-reset { font-weight: 500; opacity: 0.86; + text-align: left; } .usagebeam-separator-dot { @@ -201,7 +207,7 @@ } .usagebeam-limit-metrics { - spacing: 5px; + spacing: 4px; font-feature-settings: "tnum"; } @@ -214,18 +220,19 @@ } .usagebeam-limit-percent { - min-width: 3.2em; + min-width: 3em; font-weight: 620; } .usagebeam-limit-separator { - min-width: 0.8em; + min-width: 4px; } .usagebeam-limit-reset { - min-width: 4.8em; + min-width: 4.3em; font-weight: 500; opacity: 0.84; + text-align: left; } .usagebeam-limit-value { @@ -315,6 +322,13 @@ padding-top: 1px; } +.usagebeam-history-scope { + padding-bottom: 1px; + font-size: 9.75pt; + font-weight: 650; + opacity: 0.94; +} + .usagebeam-day-chart { padding: 7px 0 8px; } diff --git a/tests/shell/extension.js b/tests/shell/extension.js index 3990209..740b4bc 100644 --- a/tests/shell/extension.js +++ b/tests/shell/extension.js @@ -179,14 +179,17 @@ export default class UsageBeamUITest extends Extension { } for (const style of ['usagebeam-limit-percent', 'usagebeam-limit-reset']) { const actors = matching(content, style); + const firstText = actors[0].get_child(); + const firstEdge = style === 'usagebeam-limit-percent' + ? bounds(firstText).x + firstText.width : bounds(firstText).x; for (const actor of actors) { const text = actor.get_child(); assert(!text.clutter_text.get_layout().is_ellipsized(), `${style}: clipped text`); assert(Math.abs(bounds(actor).x - bounds(actors[0]).x) <= 1, `${style}: misaligned column`); - const edge = bounds(text).x + text.width; - const firstText = actors[0].get_child(); - assert(Math.abs(edge - bounds(firstText).x - firstText.width) <= 1, - `${style}: numbers are not right aligned (${edge} vs ${bounds(firstText).x + firstText.width})`); + const edge = style === 'usagebeam-limit-percent' + ? bounds(text).x + text.width : bounds(text).x; + assert(Math.abs(edge - firstEdge) <= 1, + `${style}: text alignment drifted (${edge} vs ${firstEdge})`); } } const dots = [indicator._panelSeparator, ...matching(content, 'usagebeam-separator-dot')]; @@ -203,12 +206,39 @@ export default class UsageBeamUITest extends Extension { assert(Math.abs(separatorBounds.x - (valueBounds.x + valueBounds.width) - (resetBounds.x - separatorBounds.x - separatorBounds.width)) <= 1, 'Panel separator does not have equal spacing'); + const providerBounds = bounds(indicator._panelProvider); + assert(valueBounds.x - providerBounds.x - providerBounds.width <= 6 * scale, + 'Panel provider and percentage are spaced too far apart'); + assert(bounds(indicator.container).width / scale < 180, + `Panel indicator is not compact: ${bounds(indicator.container).width / scale}`); + const clockLabels = descendants(calendar).filter(actor => + actor instanceof St.Label && actor.visible && actor.mapped && actor.width > 0); + assert(clockLabels.length > 0, 'Calendar has no visible label'); + const clockRight = Math.max(...clockLabels.map(actor => bounds(actor).x + actor.width)); + const calendarGap = bounds(indicator._panelIcon).x - clockRight; + assert(calendarGap >= 0 && calendarGap <= 20 * scale, + `Calendar/provider visual gap is not compact: ${calendarGap / scale}`); + for (const metrics of matching(content, 'usagebeam-limit-metrics')) { + if (metrics.get_children().length !== 3) + continue; + const [percent, dot, reset] = metrics.get_children(); + const percentText = bounds(percent.get_child()); + const dotCore = bounds(dot.get_child()); + const resetText = bounds(reset.get_child()); + const beforeDot = dotCore.x - percentText.x - percentText.width; + const afterDot = resetText.x - dotCore.x - dotCore.width; + assert(Math.abs(beforeDot - afterDot) <= 1, + `Limit separator spacing is uneven: ${beforeDot} vs ${afterDot}`); + } const limitName = matching(content, 'usagebeam-limit-name')[0]; const limitValue = matching(content, 'usagebeam-limit-percent')[0].get_child(); assert(limitValue.get_theme_node().get_font().get_size() < limitName.get_theme_node().get_font().get_size(), 'Limit metrics lack font hierarchy'); const activityParts = matching(content, 'usagebeam-disclosure-summary'); assert(activityParts.length === 3, 'Activity summary is not split into semantic parts'); + const historyScope = matching(content, 'usagebeam-history-scope'); + assert(historyScope.length === 1 && historyScope[0].text === 'This device', + 'Expanded activity should emphasize source scope without a date range'); for (const severity of ['caution', 'warning', 'danger']) { const actors = matching(content, `usagebeam-${severity}`); assert(actors.some(actor => actor.has_style_class_name('usagebeam-limit-percent')), diff --git a/tests/unit/usage.test.js b/tests/unit/usage.test.js index 09ebe18..144e3b0 100644 --- a/tests/unit/usage.test.js +++ b/tests/unit/usage.test.js @@ -1,7 +1,7 @@ import test from 'node:test'; import assert from 'node:assert/strict'; import {aggregateEvents, mergeRecord, number, recentDates, record, validTime, validateRecord, windowUsage} from '../../src/core/usage.js'; -import {compactTokens, dateRange, modelName, resetCountdown, resetTime, tokens} from '../../src/core/format.js'; +import {compactTokens, modelName, resetCountdown, resetTime, tokens} from '../../src/core/format.js'; import {ThresholdTracker} from '../../src/core/notifications.js'; test('unknown metrics are not coerced to zero', () => { @@ -133,9 +133,6 @@ test('formatting preserves unknown/reset-due states', () => { assert.equal(tokens(23000000), '23.0M'); assert.equal(compactTokens(186000000), '186M'); assert.equal(compactTokens(56300000), '56.3M'); - assert.match(dateRange({start: '2026-09-01', end: '2026-09-07'}, 'en-US'), - /^Sep 1.*7, 2026$/); - assert.equal(dateRange({start: 'bad', end: '2026-09-07'}, 'en-US'), 'Dates unavailable'); assert.equal(resetTime(null), 'Reset time unavailable'); assert.match(resetTime(100, 200), /awaiting update/); assert.equal(resetCountdown(null), null); From f8adc0907ae380e340c6b18f245c779f68561bb9 Mon Sep 17 00:00:00 2001 From: kcnewman Date: Mon, 7 Sep 2026 23:20:25 +0000 Subject: [PATCH 33/44] fix: simplify activity and usage alerts --- CHANGELOG.md | 6 ++++ README.md | 24 +++++-------- extension.js | 8 ++--- prefs.js | 2 +- ...ome.shell.extensions.usagebeam.gschema.xml | 4 +-- src/core/notifications.js | 12 ++++++- src/core/thresholds.js | 26 ++++++++++++++ src/ui/indicator.js | 12 ++----- src/ui/presentation.js | 22 ++---------- src/ui/widgets.js | 12 ++----- stylesheet.css | 34 +----------------- tests/shell/extension.js | 16 ++++++--- tests/unit/presentation.test.js | 16 +++++++-- tests/unit/usage.test.js | 35 ++++++++++++++----- 14 files changed, 119 insertions(+), 110 deletions(-) create mode 100644 src/core/thresholds.js diff --git a/CHANGELOG.md b/CHANGELOG.md index d133006..c4c5979 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,12 @@ All notable UsageBeam changes are documented here. while retaining a fixed width across provider changes. - Seven-day activity emphasizes its local/account scope without repeating a date range already represented by the chart. +- The activity disclosure now shows only its title and chevron; period, totals, + and source remain in the expanded content where they are needed. +- Usage alerts now progress through configured, warning, and exhausted milestones + once per quota period and combine simultaneous crossings into one notification. +- Quota labels use compact `5H Session` and `Weekly Reserve` names, while expanded + activity keeps scope implicit and gives the chart more breathing room. - Expired quota windows are not presented as current cached data. - Empty, malformed, or unavailable provider responses are never shown as zero usage. diff --git a/README.md b/README.md index f58578b..c08fcca 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,11 @@ GNOME top panel. The compact indicator shows the active provider's highest quota and reset countdown; its popup reveals every reported limit plus seven days of local token and model activity. +The product concept was adapted for GNOME Shell from the +[Agents plugin in Omarchy](https://github.com/omacom/omarchy/blob/quattro/shell/plugins/agents/README.md). +UsageBeam is an independent implementation designed around GNOME's native panel, +preferences, accessibility, and lifecycle conventions. + ## At a glance - Live account limits and reset windows for Codex and Claude Code. @@ -42,19 +47,6 @@ See the detailed [Codex](docs/providers/codex.md) and [Claude Code](docs/providers/claude.md) provider notes for source and compatibility details. -## Usage semantics - -Quota bars use a fixed 0–100% scale and retain their numeric percentage: - -- Below 80% uses the normal theme accent. -- 80–89% uses a restrained yellow caution state. -- 90–99% uses an orange warning state. -- 100% and above uses a red exhausted state. - -Color reinforces the value; it never replaces the percentage or state text. -Activity bars are relative to the largest value in the displayed seven-day -period and do not represent account limits. - ## Requirements - GNOME Shell 50. @@ -69,12 +61,14 @@ Download the UsageBeam ZIP from [GitHub Releases](https://github.com/oo7kc/freeb then run: ```bash -gnome-extensions install --force ./usagebeam@oo7kc.github.io-VERSION.zip +gnome-extensions install ./usagebeam@oo7kc.github.io-VERSION.zip gnome-extensions enable usagebeam@oo7kc.github.io ``` Log out and back in when installing UsageBeam for the first time so GNOME Shell -can discover the new extension identity. +can discover the new extension identity. When replacing an existing UsageBeam +installation, add `--force`; it tells `gnome-extensions` that overwriting the +installed copy is intentional. ## Settings diff --git a/extension.js b/extension.js index 74bc509..a71a532 100644 --- a/extension.js +++ b/extension.js @@ -1,5 +1,6 @@ import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js'; import * as Main from 'resource:///org/gnome/shell/ui/main.js'; +import {notificationBody} from './src/core/notifications.js'; import {UsageBeamIndicator} from './src/ui/indicator.js'; import {clearIndicatorPlacement, placeIndicator} from './src/ui/panelPlacement.js'; import {migrateLegacyInstall} from './src/services/migration.js'; @@ -13,10 +14,9 @@ export default class UsageBeamExtension extends Extension { Main.panel.addToStatusArea(this.uuid, this._indicator, 0, 'center'); this._placeIndicator(); this._service = new UsageService(this._settings, this.path, () => this._indicator?.render(), alerts => { - for (const alert of alerts) { - const state = alert.threshold === 100 ? 'limit reached' : `reached ${alert.threshold}%`; - Main.notify('UsageBeam usage alert', `${alert.provider}: ${alert.label} ${state}.`); - } + const message = notificationBody(alerts); + if (message) + Main.notify('UsageBeam usage alert', message); }); this._indicator.attach(this._service); this._settingsIds = []; diff --git a/prefs.js b/prefs.js index 05732d6..c892c61 100644 --- a/prefs.js +++ b/prefs.js @@ -40,7 +40,7 @@ export default class UsageBeamPreferences extends ExtensionPreferences { const notifyRow = new Adw.SwitchRow({ title: 'Notifications', - subtitle: 'Alert when a provider hits its limit', + subtitle: 'Alert once when usage crosses each warning milestone', }); settings.bind('notifications-enabled', notifyRow, 'active', 0); group.add(notifyRow); diff --git a/schemas/org.gnome.shell.extensions.usagebeam.gschema.xml b/schemas/org.gnome.shell.extensions.usagebeam.gschema.xml index 18191df..131bb67 100644 --- a/schemas/org.gnome.shell.extensions.usagebeam.gschema.xml +++ b/schemas/org.gnome.shell.extensions.usagebeam.gschema.xml @@ -11,7 +11,7 @@ true

Enable notifications - Show desktop notifications when a provider hits its limit + Show one desktop notification when usage crosses each configured warning milestone ['codex', 'claude'] @@ -40,7 +40,7 @@ 90 - Usage percentage that triggers a warning + First usage percentage that triggers a warning diff --git a/src/core/notifications.js b/src/core/notifications.js index 0ebb512..e5d8b93 100644 --- a/src/core/notifications.js +++ b/src/core/notifications.js @@ -1,3 +1,12 @@ +import {notificationMilestones} from './thresholds.js'; + +export function notificationBody(alerts) { + return [...new Set(alerts.map(alert => { + const state = alert.threshold >= 100 ? 'limit reached' : `reached ${alert.threshold}%`; + return `${alert.provider}: ${alert.label} ${state}.`; + }))].join('\n'); +} + export class ThresholdTracker { constructor() { this.previous = new Map(); @@ -7,12 +16,13 @@ export class ThresholdTracker { if (record.limits.status !== 'ready') return []; const alerts = []; + const milestones = notificationMilestones(threshold); for (const window of record.limits.windows) { if (window.unlimited || !Number.isFinite(window.usedPercent)) continue; const key = `${record.id}:${record.accountKey ?? 'default'}:${window.id}:${window.resetsAt ?? 'unknown'}`; const previous = this.previous.get(key); - const reached = window.usedPercent >= 100 ? 100 : window.usedPercent >= threshold ? threshold : 0; + const reached = milestones.filter(value => window.usedPercent >= value).at(-1) ?? 0; if (previous !== undefined && reached > previous) alerts.push({provider: record.name, label: window.label, threshold: reached}); this.previous.set(key, Math.max(previous ?? 0, reached)); diff --git a/src/core/thresholds.js b/src/core/thresholds.js new file mode 100644 index 0000000..57c988a --- /dev/null +++ b/src/core/thresholds.js @@ -0,0 +1,26 @@ +export const QUOTA_THRESHOLDS = Object.freeze({ + caution: 80, + warning: 90, + danger: 100, +}); + +export function quotaSeverity(percent) { + if (!Number.isFinite(percent)) + return null; + if (percent >= QUOTA_THRESHOLDS.danger) + return 'danger'; + if (percent >= QUOTA_THRESHOLDS.warning) + return 'warning'; + if (percent >= QUOTA_THRESHOLDS.caution) + return 'caution'; + return null; +} + +export function notificationMilestones(minimum = QUOTA_THRESHOLDS.warning) { + const floor = Number.isFinite(minimum) + ? Math.max(0, Math.min(100, Math.round(minimum))) + : QUOTA_THRESHOLDS.warning; + return [...new Set([floor, ...Object.values(QUOTA_THRESHOLDS)])] + .filter(value => value >= floor) + .sort((left, right) => left - right); +} diff --git a/src/ui/indicator.js b/src/ui/indicator.js index 3d4bdbd..f9b080d 100644 --- a/src/ui/indicator.js +++ b/src/ui/indicator.js @@ -7,8 +7,9 @@ import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js'; import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js'; import {NAMES, recentDates} from '../core/usage.js'; import {age, modelName, tokens} from '../core/format.js'; +import {quotaSeverity} from '../core/thresholds.js'; import {historyOverview, latestUpdate, panelQuota, periodDays, providerStatus, - quotaPresentation, quotaSeverity} from './presentation.js'; + quotaPresentation} from './presentation.js'; import {actionButton, button, dayChart, disclosureButton, label, meter, modelMeter, limitRow, metricLabel, providerIcon, separatorDot} from './widgets.js'; @@ -183,12 +184,7 @@ export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator _renderHistory(history) { const overview = historyOverview(history); if (overview) { - const summary = [ - overview.days ? {text: `${overview.days} days`, role: 'period'} : null, - {text: tokens(overview.total), role: 'tokens'}, - {text: overview.scope, role: 'source'}, - ].filter(Boolean); - this._contentBox.add_child(disclosureButton(summary, this._detailsExpanded, () => { + this._contentBox.add_child(disclosureButton(this._detailsExpanded, () => { this._detailsExpanded = !this._detailsExpanded; this.render(); })); @@ -210,8 +206,6 @@ export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator _renderDays(history, parent = this._contentBox) { const overview = historyOverview(history); this._heading(`LAST ${overview?.days ?? history.days.length} DAYS · ${tokens(overview?.total ?? 0)} TOKENS`, parent); - const scope = history.scope === 'account' ? 'Account activity' : 'This device'; - parent.add_child(label(scope, 'usagebeam-history-scope')); const today = recentDates(Date.now(), 1)[0]; parent.add_child(dayChart(history.days, today)); } diff --git a/src/ui/presentation.js b/src/ui/presentation.js index 1926032..a44de5b 100644 --- a/src/ui/presentation.js +++ b/src/ui/presentation.js @@ -2,24 +2,6 @@ import {resetCountdown} from '../core/format.js'; const CURRENT_STATES = new Set(['ready', 'partial']); -export const QUOTA_THRESHOLDS = Object.freeze({ - caution: 80, - warning: 90, - danger: 100, -}); - -export function quotaSeverity(percent) { - if (!Number.isFinite(percent)) - return null; - if (percent >= QUOTA_THRESHOLDS.danger) - return 'danger'; - if (percent >= QUOTA_THRESHOLDS.warning) - return 'warning'; - if (percent >= QUOTA_THRESHOLDS.caution) - return 'caution'; - return null; -} - export function providerStatus(record, refreshing = false) { if (CURRENT_STATES.has(record?.limits?.status)) return 'LIVE'; @@ -62,9 +44,11 @@ export function historyOverview(history) { export function quotaName(window) { const source = `${window?.id ?? ''} ${window?.label ?? ''}`.toLowerCase(); + if (source.includes('five-hour') || source.includes('5 hours')) + return '5H Session'; if (source.includes('reserve') && (source.includes('weekly') || window?.durationMinutes === 10080)) - return 'Weekly reserve'; + return 'Weekly Reserve'; return String(window?.label ?? 'Usage limit'); } diff --git a/src/ui/widgets.js b/src/ui/widgets.js index 56764d5..0b8be00 100644 --- a/src/ui/widgets.js +++ b/src/ui/widgets.js @@ -126,7 +126,7 @@ export function button(text, callback, {active = false, name = text} = {}) { return actor; } -export function disclosureButton(summary, expanded, callback) { +export function disclosureButton(expanded, callback) { const actor = new St.Button({ accessible_name: 'Activity details', accessible_role: Atk.Role.TOGGLE_BUTTON, @@ -140,18 +140,10 @@ export function disclosureButton(summary, expanded, callback) { }); const content = new St.BoxLayout({style_class: 'usagebeam-disclosure-content', x_expand: true}); content.add_child(label('Activity', 'usagebeam-disclosure-title', true)); - const meta = new St.BoxLayout({style_class: 'usagebeam-disclosure-meta'}); - summary.forEach(({text, role}, index) => { - if (index) - meta.add_child(separatorDot('usagebeam-disclosure-separator')); - meta.add_child(metricLabel(text, - `usagebeam-disclosure-summary usagebeam-disclosure-${role}`)); - }); - meta.add_child(new St.Icon({ + content.add_child(new St.Icon({ icon_name: expanded ? 'pan-up-symbolic' : 'pan-down-symbolic', style_class: 'usagebeam-disclosure-icon', })); - content.add_child(meta); actor.set_child(content); actor.connect('clicked', callback); return actor; diff --git a/stylesheet.css b/stylesheet.css index df43fb7..8b02c11 100644 --- a/stylesheet.css +++ b/stylesheet.css @@ -279,7 +279,7 @@ } .usagebeam-disclosure-content { - spacing: 10px; + spacing: 8px; } .usagebeam-disclosure-title { @@ -287,31 +287,6 @@ font-weight: 650; } -.usagebeam-disclosure-meta { - spacing: 5px; -} - -.usagebeam-disclosure-summary { - font-feature-settings: "tnum"; - font-size: 8.75pt; - font-weight: 500; - opacity: 0.76; -} - -.usagebeam-disclosure-tokens { - font-weight: 650; - opacity: 0.94; -} - -.usagebeam-disclosure-source { - font-size: 8.5pt; - opacity: 0.64; -} - -.usagebeam-disclosure-separator { - min-width: 4px; -} - .usagebeam-disclosure-icon { icon-size: 10px; opacity: 0.76; @@ -322,13 +297,6 @@ padding-top: 1px; } -.usagebeam-history-scope { - padding-bottom: 1px; - font-size: 9.75pt; - font-weight: 650; - opacity: 0.94; -} - .usagebeam-day-chart { padding: 7px 0 8px; } diff --git a/tests/shell/extension.js b/tests/shell/extension.js index 740b4bc..3762cda 100644 --- a/tests/shell/extension.js +++ b/tests/shell/extension.js @@ -234,11 +234,17 @@ export default class UsageBeamUITest extends Extension { const limitValue = matching(content, 'usagebeam-limit-percent')[0].get_child(); assert(limitValue.get_theme_node().get_font().get_size() < limitName.get_theme_node().get_font().get_size(), 'Limit metrics lack font hierarchy'); - const activityParts = matching(content, 'usagebeam-disclosure-summary'); - assert(activityParts.length === 3, 'Activity summary is not split into semantic parts'); - const historyScope = matching(content, 'usagebeam-history-scope'); - assert(historyScope.length === 1 && historyScope[0].text === 'This device', - 'Expanded activity should emphasize source scope without a date range'); + assert(matching(content, 'usagebeam-disclosure-summary').length === 0, + 'Collapsed activity control should not repeat detail metadata'); + assert(matching(content, 'usagebeam-disclosure-icon').length === 1, + 'Activity control should retain its disclosure affordance'); + assert(matching(content, 'usagebeam-history-scope').length === 0, + 'Expanded activity should not repeat its local source scope'); + const todayColumns = matching(content, 'usagebeam-today'); + assert(todayColumns.length === 1, + 'Daily activity should distinguish exactly one current-day column'); + assert(matching(content, 'usagebeam-chart-bar').length === 7, + 'Every daily activity column should use the same bar style'); for (const severity of ['caution', 'warning', 'danger']) { const actors = matching(content, `usagebeam-${severity}`); assert(actors.some(actor => actor.has_style_class_name('usagebeam-limit-percent')), diff --git a/tests/unit/presentation.test.js b/tests/unit/presentation.test.js index c55fe25..7e663fc 100644 --- a/tests/unit/presentation.test.js +++ b/tests/unit/presentation.test.js @@ -1,7 +1,8 @@ import test from 'node:test'; import assert from 'node:assert/strict'; import {chartBarGeometry, historyOverview, latestUpdate, panelQuota, periodDays, providerStatus, - quotaName, quotaPresentation, quotaSeverity, QUOTA_THRESHOLDS} from '../../src/ui/presentation.js'; + quotaName, quotaPresentation} from '../../src/ui/presentation.js'; +import {notificationMilestones, quotaSeverity, QUOTA_THRESHOLDS} from '../../src/core/thresholds.js'; test('provider status distinguishes live, local, cached and setup data', () => { const value = {limits: {status: 'ready'}, history: {status: 'ready'}}; @@ -43,16 +44,25 @@ test('quota presentation keeps names concise and reset times inline', () => { const now = 1_000_000; const reserve = {id: 'gpt-reserve:primary', label: 'Reserve · Weekly', durationMinutes: 10080, usedPercent: 18.2, resetsAt: now + 4 * 86400000 + 22 * 3600000}; - assert.equal(quotaName(reserve), 'Weekly reserve'); + assert.equal(quotaName(reserve), 'Weekly Reserve'); assert.deepEqual(quotaPresentation(reserve, now), { - name: 'Weekly reserve', value: '18%', reset: '4d 22h', + name: 'Weekly Reserve', value: '18%', reset: '4d 22h', }); + assert.equal(quotaName({id: 'five-hour', label: 'Session · 5 hours', durationMinutes: 300}), + '5H Session'); assert.deepEqual(quotaPresentation({id: 'credits', label: 'Credits', unlimited: true}, now), { name: 'Credits', value: 'Unlimited', reset: null, }); assert.equal(quotaPresentation({id: 'broken', label: 'Broken'}, now), null); }); +test('notification milestones follow quota semantics without duplicate levels', () => { + assert.deepEqual(notificationMilestones(80), [80, 90, 100]); + assert.deepEqual(notificationMilestones(90), [90, 100]); + assert.deepEqual(notificationMilestones(95), [95, 100]); + assert.deepEqual(notificationMilestones(NaN), [90, 100]); +}); + test('panel quota reports only the highest current quota window', () => { const now = 1_000_000; const record = {limits: {status: 'ready', windows: [ diff --git a/tests/unit/usage.test.js b/tests/unit/usage.test.js index 144e3b0..82764c1 100644 --- a/tests/unit/usage.test.js +++ b/tests/unit/usage.test.js @@ -2,7 +2,7 @@ import test from 'node:test'; import assert from 'node:assert/strict'; import {aggregateEvents, mergeRecord, number, recentDates, record, validTime, validateRecord, windowUsage} from '../../src/core/usage.js'; import {compactTokens, modelName, resetCountdown, resetTime, tokens} from '../../src/core/format.js'; -import {ThresholdTracker} from '../../src/core/notifications.js'; +import {notificationBody, ThresholdTracker} from '../../src/core/notifications.js'; test('unknown metrics are not coerced to zero', () => { for (const value of [null, undefined, '', ' ', false, -1, Infinity, 'bad', [1], {value: 1}]) @@ -113,19 +113,38 @@ test('calendar buckets are consecutive across month boundaries', () => { assert.equal(validTime('1800000000'), 1800000000000); }); -test('notifications require a verified crossing and deduplicate warning and limit separately', () => { +test('notifications emit every configured milestone once per quota period', () => { const tracker = new ThresholdTracker(); const value = record('codex'); - value.limits = {status: 'ready', windows: [{id: 'weekly', label: 'Weekly', usedPercent: 95, resetsAt: 500}]}; - assert.deepEqual(tracker.update(value), []); + value.limits = {status: 'ready', windows: [{id: 'weekly', label: 'Weekly', usedPercent: 79, resetsAt: 500}]}; + assert.deepEqual(tracker.update(value, 80), []); + value.limits.windows[0].usedPercent = 80; + assert.equal(tracker.update(value, 80)[0].threshold, 80); + assert.deepEqual(tracker.update(value, 80), []); + value.limits.windows[0].usedPercent = 90; + assert.equal(tracker.update(value, 80)[0].threshold, 90); + assert.deepEqual(tracker.update(value, 80), []); value.limits.windows[0].usedPercent = 100; - assert.equal(tracker.update(value)[0].threshold, 100); - assert.deepEqual(tracker.update(value), []); + assert.equal(tracker.update(value, 80)[0].threshold, 100); + assert.deepEqual(tracker.update(value, 80), []); + value.limits.windows[0].usedPercent = 50; + assert.deepEqual(tracker.update(value, 80), []); + value.limits.windows[0].usedPercent = 100; + assert.deepEqual(tracker.update(value, 80), []); value.limits.status = 'unavailable'; - assert.deepEqual(tracker.update(value), []); + assert.deepEqual(tracker.update(value, 80), []); value.limits.status = 'ready'; value.limits.windows[0].resetsAt = 1000; - assert.deepEqual(tracker.update(value), []); + assert.deepEqual(tracker.update(value, 80), []); +}); + +test('simultaneous and repeated alerts produce one concise notification body', () => { + assert.equal(notificationBody([ + {provider: 'Codex', label: 'Weekly', threshold: 90}, + {provider: 'Codex', label: 'Weekly', threshold: 90}, + {provider: 'Codex', label: '5H Session', threshold: 100}, + ]), 'Codex: Weekly reached 90%.\nCodex: 5H Session limit reached.'); + assert.equal(notificationBody([]), ''); }); test('formatting preserves unknown/reset-due states', () => { From 6e6cef4d1080967dde84a565936a5f28bd648641 Mon Sep 17 00:00:00 2001 From: kcnewman Date: Tue, 8 Sep 2026 07:24:10 +0000 Subject: [PATCH 34/44] fix: anchor both calendar positions and clarify resets --- CHANGELOG.md | 4 ++ src/ui/indicator.js | 21 ++++-- src/ui/panelPlacement.js | 1 + src/ui/presentation.js | 4 +- src/ui/widgets.js | 20 +----- stylesheet.css | 45 +++++-------- tests/shell/extension.js | 110 ++++++++++++++++++-------------- tests/unit/presentation.test.js | 8 ++- 8 files changed, 106 insertions(+), 107 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4c5979..75d17fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,10 @@ All notable UsageBeam changes are documented here. ### Fixed +- Calendar-side readouts anchor toward the clock on both sides, keeping reserved + width outside the visible gap when provider names and countdowns change. +- Each quota now shows its reset countdown below the bar as `Resets in …`, with + its percentage right-aligned above the bar. - Daily chart columns allocate visible bottom-aligned bars for non-zero activity. - Model fills remain compact and stable across repeated layout passes. - Calendar placement remains fixed while providers and quota values change. diff --git a/src/ui/indicator.js b/src/ui/indicator.js index f9b080d..d755426 100644 --- a/src/ui/indicator.js +++ b/src/ui/indicator.js @@ -25,18 +25,21 @@ export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator this._openPreferences = openPreferences; this._service = null; this._detailsExpanded = false; - this._panelStatus = new St.BoxLayout({style_class: 'usagebeam-panel-status'}); + this._panelStatus = new St.BoxLayout({style_class: 'usagebeam-panel-status', + x_expand: true, x_align: Clutter.ActorAlign.START}); this._panelIcon = new St.Bin({style_class: 'usagebeam-panel-icon-slot', y_align: Clutter.ActorAlign.CENTER}); this._panelProvider = label('UsageBeam', 'usagebeam-panel-provider'); this._panelProvider.clutter_text.ellipsize = Pango.EllipsizeMode.END; this._panelValue = metricLabel('—', 'usagebeam-panel-value'); - this._panelReset = metricLabel('—', 'usagebeam-panel-reset', true); + this._panelReset = metricLabel('—', 'usagebeam-panel-reset'); this._panelSeparator = separatorDot('usagebeam-panel-separator'); for (const actor of [this._panelIcon, this._panelProvider, this._panelValue, this._panelSeparator, this._panelReset]) this._panelStatus.add_child(actor); this._panelProviderId = null; - this.add_child(this._panelStatus); + // Reserve stable panel space separately from the naturally sized readout. + // The unused space belongs on the side away from the calendar. + this.add_child(new St.Bin({style_class: 'usagebeam-panel-slot', child: this._panelStatus})); this.menu.actor.add_style_class_name('usagebeam-menu'); this._shellSettings = St.Settings.get(); this._shellSettings.connectObject( @@ -81,6 +84,11 @@ export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator this.render(); } + alignPanelContent(position) { + this._panelStatus.x_align = position === 'left-of-calendar' + ? Clutter.ActorAlign.END : Clutter.ActorAlign.START; + } + render() { if (!this._contentBox) return; @@ -163,13 +171,12 @@ export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator if (!view) continue; const severity = quotaSeverity(window.usedPercent); - box.add_child(limitRow(view.name, view.value, view.reset, severity)); + box.add_child(limitRow(view.name, view.value, severity)); if (!window.unlimited) { - const resetDescription = view.reset === 'due' ? ', reset due' : - view.reset ? `, resets in ${view.reset}` : ''; box.add_child(meter(window.usedPercent / 100, - `${view.name}: ${view.value} used${resetDescription}`, + `${view.name}: ${view.value} used, ${view.reset.toLowerCase()}`, severity ? `usagebeam-${severity}` : '')); + box.add_child(label(view.reset, 'usagebeam-limit-reset')); } this._contentBox.add_child(box); } diff --git a/src/ui/panelPlacement.js b/src/ui/panelPlacement.js index 89f42ff..d295a87 100644 --- a/src/ui/panelPlacement.js +++ b/src/ui/panelPlacement.js @@ -44,6 +44,7 @@ export function placeIndicator(indicator, position) { const boxes = {left: Main.panel._leftBox, right: Main.panel._rightBox}; const resolved = CALENDAR_POSITIONS.has(position) || position in boxes ? position : 'right-of-calendar'; + indicator.alignPanelContent(resolved); const besideCalendar = CALENDAR_POSITIONS.has(resolved); const target = besideCalendar ? calendar?.get_parent() ?? Main.panel._centerBox : boxes[resolved]; container.get_parent()?.remove_child(container); diff --git a/src/ui/presentation.js b/src/ui/presentation.js index a44de5b..f7f0724 100644 --- a/src/ui/presentation.js +++ b/src/ui/presentation.js @@ -1,4 +1,4 @@ -import {resetCountdown} from '../core/format.js'; +import {resetCountdown, resetTime} from '../core/format.js'; const CURRENT_STATES = new Set(['ready', 'partial']); @@ -62,7 +62,7 @@ export function quotaPresentation(window, now = Date.now()) { return { name: quotaName(window), value: `${Math.round(window.usedPercent)}%`, - reset: resetCountdown(window.resetsAt, now), + reset: resetTime(window.resetsAt, now), }; } diff --git a/src/ui/widgets.js b/src/ui/widgets.js index 0b8be00..f058768 100644 --- a/src/ui/widgets.js +++ b/src/ui/widgets.js @@ -30,12 +30,6 @@ export function separatorDot(style = '') { }); } -function metricColumn(text, style, alignment = Clutter.ActorAlign.END) { - const child = metricLabel(text, '', true); - child.x_align = alignment; - return new St.Bin({style_class: style, child}); -} - export function providerIcon(provider, extensionPath, style = '') { if (!PROVIDER_ICONS.has(provider)) return null; @@ -47,21 +41,13 @@ export function providerIcon(provider, extensionPath, style = '') { }); } -export function limitRow(name, value, reset, severity = null) { +export function limitRow(name, value, severity = null) { const box = new St.BoxLayout({style_class: 'usagebeam-limit-row', x_expand: true}); const title = label(name, 'usagebeam-limit-name', true); title.clutter_text.ellipsize = Pango.EllipsizeMode.END; box.add_child(title); - const metrics = new St.BoxLayout({style_class: 'usagebeam-limit-metrics'}); - if (reset) { - metrics.add_child(metricColumn(value, - `usagebeam-limit-percent${severity ? ` usagebeam-${severity}` : ''}`)); - metrics.add_child(separatorDot('usagebeam-limit-separator')); - metrics.add_child(metricColumn(reset, 'usagebeam-limit-reset', Clutter.ActorAlign.START)); - } else { - metrics.add_child(metricColumn(value, 'usagebeam-limit-value')); - } - box.add_child(metrics); + box.add_child(metricLabel(value, + `usagebeam-limit-percent${severity ? ` usagebeam-${severity}` : ''}`)); return box; } diff --git a/stylesheet.css b/stylesheet.css index 8b02c11..c8f8702 100644 --- a/stylesheet.css +++ b/stylesheet.css @@ -35,14 +35,17 @@ -minimum-hpadding: 3px; } -.usagebeam-panel-status { - spacing: 4px; +.usagebeam-panel-slot { width: 12.5em; font-family: "SF Pro Text"; font-size: 9.5pt; font-feature-settings: "tnum"; } +.usagebeam-panel-status { + spacing: 4px; +} + .usagebeam-panel-icon-slot { width: 16px; } .usagebeam-provider-icon { @@ -206,47 +209,27 @@ font-weight: 520; } -.usagebeam-limit-metrics { - spacing: 4px; - font-feature-settings: "tnum"; -} - -.usagebeam-limit-percent, -.usagebeam-limit-reset, -.usagebeam-limit-value { +.usagebeam-limit-percent { color: st-mix(-st-accent-color, #d7e4eb, 28%); font-size: 9.5pt; - text-align: right; -} - -.usagebeam-limit-percent { - min-width: 3em; font-weight: 620; -} - -.usagebeam-limit-separator { - min-width: 4px; + text-align: right; } .usagebeam-limit-reset { - min-width: 4.3em; + color: st-mix(-st-accent-color, #9baab4, 12%); + font-size: 9.5pt; font-weight: 500; - opacity: 0.84; text-align: left; } -.usagebeam-limit-value { - min-width: 8.6em; - font-weight: 550; -} - .usagebeam-number { font-feature-settings: "tnum"; font-weight: 550; } .usagebeam-window { - spacing: 5px; + spacing: 4px; padding-bottom: 8px; } @@ -392,12 +375,14 @@ opacity: 0.92; } -.usagebeam-menu.usagebeam-light .usagebeam-limit-percent, -.usagebeam-menu.usagebeam-light .usagebeam-limit-reset, -.usagebeam-menu.usagebeam-light .usagebeam-limit-value { +.usagebeam-menu.usagebeam-light .usagebeam-limit-percent { color: st-mix(-st-accent-color, #263b46, 38%); } +.usagebeam-menu.usagebeam-light .usagebeam-limit-reset { + color: st-mix(-st-accent-color, #586b76, 12%); +} + .usagebeam-menu.usagebeam-light .usagebeam-separator-dot-core { background-color: rgba(38, 59, 70, 0.54); } diff --git a/tests/shell/extension.js b/tests/shell/extension.js index 3762cda..83f95e5 100644 --- a/tests/shell/extension.js +++ b/tests/shell/extension.js @@ -14,6 +14,8 @@ const bounds = actor => { const [width, height] = actor.get_transformed_size(); return {x, y, width, height}; }; +// Clutter reports logical sizes; Pango pixel sizes can include monitor scale. +const textWidth = actor => actor.clutter_text.get_preferred_width(-1)[1]; const assert = (condition, message) => { if (!condition) throw new Error(message); @@ -71,6 +73,40 @@ export default class UsageBeamUITest extends Extension { })); } + _checkPanelSpacing(indicator, calendar, position, textScale) { + const scale = St.ThemeContext.get_for_stage(global.stage).scale_factor; + const provider = bounds(indicator._panelProvider); + const value = bounds(indicator._panelValue); + const separator = bounds(indicator._panelSeparator); + const reset = bounds(indicator._panelReset); + assert(value.x - provider.x - provider.width <= 6 * scale, + `${position}: provider and percentage are spaced too far apart`); + assert(Math.abs(separator.x - value.x - value.width - + (reset.x - separator.x - separator.width)) <= 1, + `${position}: panel separator spacing is uneven`); + assert(bounds(indicator.container).width / scale < 180 * textScale, + `${position}: panel indicator is not compact`); + for (const actor of [indicator._panelProvider, indicator._panelValue, indicator._panelReset]) { + assert(!actor.clutter_text.get_layout().is_ellipsized(), `${position}: panel text clipped`); + assert(textWidth(actor) <= actor.width + 1, + `${position}: panel text exceeds its allocation`); + } + if (!position.includes('calendar')) + return null; + const clockLabels = descendants(calendar).filter(actor => + actor instanceof St.Label && actor.visible && actor.mapped && actor.width > 0); + assert(clockLabels.length > 0, 'Calendar has no visible label'); + const clockLeft = Math.min(...clockLabels.map(actor => bounds(actor).x)); + const clockRight = Math.max(...clockLabels.map(actor => bounds(actor).x + actor.width)); + // Measure the visible text, not the label's allocated expansion space. + const resetRight = reset.x + textWidth(indicator._panelReset); + const gap = position === 'left-of-calendar' ? clockLeft - resetRight + : bounds(indicator._panelIcon).x - clockRight; + assert(gap >= 0 && gap <= 20 * scale, + `${position}: calendar/readout visual gap is not compact: ${gap / scale}`); + return gap / scale; + } + async _run() { let indicator; for (let attempt = 0; attempt < 80 && !indicator; attempt++) { @@ -104,6 +140,8 @@ export default class UsageBeamUITest extends Extension { const [, , logicalMonitors] = await this._displayCall('GetCurrentState'); const monitorScale = logicalMonitors[0][2]; assert(monitorScale === expectedScale, `Expected monitor scale ${expectedScale}, got ${monitorScale}`); + const interfaceSettings = new Gio.Settings({schema_id: 'org.gnome.desktop.interface'}); + const textScale = interfaceSettings.get_double('text-scaling-factor'); for (const position of positions) { settings.set_string('panel-position', position); settings.set_string('default-provider', 'codex'); @@ -113,6 +151,7 @@ export default class UsageBeamUITest extends Extension { assert(indicator.container.get_parent() === parent, `${position}: wrong panel area`); const before = bounds(indicator.container); const clockBefore = bounds(calendar); + const gaps = [this._checkPanelSpacing(indicator, calendar, position, textScale)]; // Check both a different provider and unavailable quota text. const original = records.claude; for (const available of [true, false]) { @@ -121,6 +160,7 @@ export default class UsageBeamUITest extends Extension { settings.set_string('default-provider', 'claude'); indicator.render(); await this._wait(); + gaps.push(this._checkPanelSpacing(indicator, calendar, position, textScale)); assert(Math.abs(bounds(calendar).x - clockBefore.x) <= 1, `${position}: calendar moved on provider change`); assert(Math.abs(bounds(indicator.container).width - before.width) <= 1, @@ -146,7 +186,11 @@ export default class UsageBeamUITest extends Extension { assert(Math.abs(gapCenter - (panel.x + panel.width / 2)) <= 1, `${position}: calendar/indicator gap is not centered (${gapCenter})`); } - placement.push({position, indicator: before, calendar: clockBefore}); + settings.set_string('default-provider', 'codex'); + await this._wait(); + if (position.includes('calendar')) + await this._screenshot(position); + placement.push({position, indicator: before, calendar: clockBefore, visualGaps: gaps}); } settings.set_string('default-provider', 'codex'); indicator.menu.open(0); @@ -157,10 +201,8 @@ export default class UsageBeamUITest extends Extension { assert(indicator._detailsExpanded, 'Activity button did not expand'); const content = indicator._contentBox; const scale = St.ThemeContext.get_for_stage(global.stage).scale_factor; - const interfaceSettings = new Gio.Settings({schema_id: 'org.gnome.desktop.interface'}); - const textScale = interfaceSettings.get_double('text-scaling-factor'); const initial = bounds(indicator.menu.actor); - assert(initial.height / scale < 680 * textScale, `Expanded menu too tall: ${initial.height / scale}`); + assert(initial.height / scale < 720 * textScale, `Expanded menu too tall: ${initial.height / scale}`); assert(!descendants(content).some(actor => actor instanceof St.ScrollView), 'Activity is scrollable'); const models = matching(content, 'usagebeam-model-meter'); assert(models.length === 3, 'Expected three synthetic models'); @@ -177,20 +219,20 @@ export default class UsageBeamUITest extends Extension { if (plot._fraction > 0) assert(plot._fill.width > 0 && plot._fill.height > 0, 'Daily bar has no area'); } - for (const style of ['usagebeam-limit-percent', 'usagebeam-limit-reset']) { - const actors = matching(content, style); - const firstText = actors[0].get_child(); - const firstEdge = style === 'usagebeam-limit-percent' - ? bounds(firstText).x + firstText.width : bounds(firstText).x; - for (const actor of actors) { - const text = actor.get_child(); - assert(!text.clutter_text.get_layout().is_ellipsized(), `${style}: clipped text`); - assert(Math.abs(bounds(actor).x - bounds(actors[0]).x) <= 1, `${style}: misaligned column`); - const edge = style === 'usagebeam-limit-percent' - ? bounds(text).x + text.width : bounds(text).x; - assert(Math.abs(edge - firstEdge) <= 1, - `${style}: text alignment drifted (${edge} vs ${firstEdge})`); - } + for (const window of matching(content, 'usagebeam-window')) { + const [row, track, reset] = window.get_children(); + const percent = matching(row, 'usagebeam-limit-percent')[0]; + assert(!percent.clutter_text.get_layout().is_ellipsized(), 'Quota percentage clipped'); + assert(!reset.clutter_text.get_layout().is_ellipsized(), 'Reset description clipped'); + const trackBox = bounds(track); + const resetBox = bounds(reset); + const valueBox = bounds(percent); + const valueWidth = textWidth(percent); + assert(Math.abs(valueBox.x + valueWidth - trackBox.x - trackBox.width) <= 1, + 'Quota percentage does not align with the right edge of its bar'); + assert(resetBox.y >= trackBox.y + trackBox.height && Math.abs(resetBox.x - trackBox.x) <= 1, + 'Reset description should be left-aligned below its bar'); + assert(/^Resets in \d+[dhm]/.test(reset.text), 'Reset description lacks its explanatory prefix'); } const dots = [indicator._panelSeparator, ...matching(content, 'usagebeam-separator-dot')]; for (const dot of dots) { @@ -200,38 +242,8 @@ export default class UsageBeamUITest extends Extension { Math.abs(coreBounds.y + coreBounds.height / 2 - (dotBounds.y + dotBounds.height / 2)) <= 1, 'Separator dot is not optically centered'); } - const valueBounds = bounds(indicator._panelValue); - const separatorBounds = bounds(indicator._panelSeparator); - const resetBounds = bounds(indicator._panelReset); - assert(Math.abs(separatorBounds.x - (valueBounds.x + valueBounds.width) - - (resetBounds.x - separatorBounds.x - separatorBounds.width)) <= 1, - 'Panel separator does not have equal spacing'); - const providerBounds = bounds(indicator._panelProvider); - assert(valueBounds.x - providerBounds.x - providerBounds.width <= 6 * scale, - 'Panel provider and percentage are spaced too far apart'); - assert(bounds(indicator.container).width / scale < 180, - `Panel indicator is not compact: ${bounds(indicator.container).width / scale}`); - const clockLabels = descendants(calendar).filter(actor => - actor instanceof St.Label && actor.visible && actor.mapped && actor.width > 0); - assert(clockLabels.length > 0, 'Calendar has no visible label'); - const clockRight = Math.max(...clockLabels.map(actor => bounds(actor).x + actor.width)); - const calendarGap = bounds(indicator._panelIcon).x - clockRight; - assert(calendarGap >= 0 && calendarGap <= 20 * scale, - `Calendar/provider visual gap is not compact: ${calendarGap / scale}`); - for (const metrics of matching(content, 'usagebeam-limit-metrics')) { - if (metrics.get_children().length !== 3) - continue; - const [percent, dot, reset] = metrics.get_children(); - const percentText = bounds(percent.get_child()); - const dotCore = bounds(dot.get_child()); - const resetText = bounds(reset.get_child()); - const beforeDot = dotCore.x - percentText.x - percentText.width; - const afterDot = resetText.x - dotCore.x - dotCore.width; - assert(Math.abs(beforeDot - afterDot) <= 1, - `Limit separator spacing is uneven: ${beforeDot} vs ${afterDot}`); - } const limitName = matching(content, 'usagebeam-limit-name')[0]; - const limitValue = matching(content, 'usagebeam-limit-percent')[0].get_child(); + const limitValue = matching(content, 'usagebeam-limit-percent')[0]; assert(limitValue.get_theme_node().get_font().get_size() < limitName.get_theme_node().get_font().get_size(), 'Limit metrics lack font hierarchy'); assert(matching(content, 'usagebeam-disclosure-summary').length === 0, diff --git a/tests/unit/presentation.test.js b/tests/unit/presentation.test.js index 7e663fc..a2b3c13 100644 --- a/tests/unit/presentation.test.js +++ b/tests/unit/presentation.test.js @@ -40,13 +40,13 @@ test('history overview summarizes daily activity without double-counting models' assert.equal(historyOverview(null), null); }); -test('quota presentation keeps names concise and reset times inline', () => { +test('quota presentation keeps names concise and reset descriptions explicit', () => { const now = 1_000_000; const reserve = {id: 'gpt-reserve:primary', label: 'Reserve · Weekly', durationMinutes: 10080, usedPercent: 18.2, resetsAt: now + 4 * 86400000 + 22 * 3600000}; assert.equal(quotaName(reserve), 'Weekly Reserve'); assert.deepEqual(quotaPresentation(reserve, now), { - name: 'Weekly Reserve', value: '18%', reset: '4d 22h', + name: 'Weekly Reserve', value: '18%', reset: 'Resets in 4d 22h', }); assert.equal(quotaName({id: 'five-hour', label: 'Session · 5 hours', durationMinutes: 300}), '5H Session'); @@ -54,6 +54,10 @@ test('quota presentation keeps names concise and reset times inline', () => { name: 'Credits', value: 'Unlimited', reset: null, }); assert.equal(quotaPresentation({id: 'broken', label: 'Broken'}, now), null); + assert.equal(quotaPresentation({...reserve, resetsAt: now - 1}, now).reset, + 'Reset due · awaiting update'); + assert.equal(quotaPresentation({...reserve, resetsAt: null}, now).reset, + 'Reset time unavailable'); }); test('notification milestones follow quota semantics without duplicate levels', () => { From 0a0a9c9ff201d87119ea277c95602317c6fc2f21 Mon Sep 17 00:00:00 2001 From: kcnewman Date: Tue, 8 Sep 2026 18:58:44 +0000 Subject: [PATCH 35/44] fix: bound collection and recover rewritten history --- .github/workflows/ci.yml | 2 + package.json | 1 + src/core/notifications.js | 6 +- src/services/history.js | 63 ++++++++-- src/services/process.js | 105 +++++++++------- src/services/streams.js | 79 ++++++++++++ tests/integration/collector.js | 2 +- tests/integration/stress.js | 213 +++++++++++++++++++++++++++++++++ tests/unit/usage.test.js | 14 +++ 9 files changed, 432 insertions(+), 53 deletions(-) create mode 100644 src/services/streams.js create mode 100644 tests/integration/stress.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d8299a1..443765f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,8 @@ jobs: run: sudo apt-get update && sudo apt-get install -y gjs gir1.2-soup-3.0 libglib2.0-dev-bin meson ninja-build python3 - name: Verify JavaScript, schemas, fixtures, and package run: npm run verify + - name: Stress history, notifications, and subprocess bounds + run: npm run test:stress - name: Verify isolated install run: | meson setup build --prefix="$RUNNER_TEMP/usagebeam-install" diff --git a/package.json b/package.json index 6699c7d..992f777 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "test": "node --test tests/unit/*.test.js", "lint": "node tools/check.js", "test:integration": "gjs -m tests/integration/collector.js", + "test:stress": "gjs -m tests/integration/stress.js", "check": "npm run lint && npm test && npm run test:integration", "pack": "python3 tools/package.py", "verify": "npm run check && npm run pack", diff --git a/src/core/notifications.js b/src/core/notifications.js index e5d8b93..056509b 100644 --- a/src/core/notifications.js +++ b/src/core/notifications.js @@ -13,7 +13,7 @@ export class ThresholdTracker { } update(record, threshold = 90) { - if (record.limits.status !== 'ready') + if (!['ready', 'partial'].includes(record.limits.status)) return []; const alerts = []; const milestones = notificationMilestones(threshold); @@ -25,9 +25,11 @@ export class ThresholdTracker { const reached = milestones.filter(value => window.usedPercent >= value).at(-1) ?? 0; if (previous !== undefined && reached > previous) alerts.push({provider: record.name, label: window.label, threshold: reached}); + // Refresh insertion order so active windows survive eviction of old periods. + this.previous.delete(key); this.previous.set(key, Math.max(previous ?? 0, reached)); } - if (this.previous.size > 200) + while (this.previous.size > 200) this.previous.delete(this.previous.keys().next().value); return alerts; } diff --git a/src/services/history.js b/src/services/history.js index e512139..9283ffc 100644 --- a/src/services/history.js +++ b/src/services/history.js @@ -3,7 +3,7 @@ import GLib from 'gi://GLib'; import {aggregateEvents, recentDates, section} from '../core/usage.js'; import {cacheDirectory, fingerprint, join, readJson, writeJson} from './files.js'; -const CACHE_VERSION = 4; +const CACHE_VERSION = 5; const MAX_DEPTH = 12; const MAX_FILES = 20000; const MAX_LINE_BYTES = 4 * 1024 * 1024; @@ -86,9 +86,32 @@ function cachedFile(value, fallbackSession) { if (offset === null || size === null || typeof value.stamp !== 'string' || typeof value.fileId !== 'string') return null; return {offset, size, stamp: value.stamp, fileId: value.fileId, + digest: typeof value.digest === 'string' && /^[a-f0-9]{64}$/.test(value.digest) ? value.digest : null, + skipping: value.skipping === true, state: parserState(value.state, fallbackSession), events: value.events, partial: value.partial === true}; } +function newFileState(fileId, session) { + return {offset: 0, size: 0, stamp: '', fileId, digest: null, skipping: false, + state: {session}, events: {}, partial: false}; +} + +function hashPrefix(input, length, deadline) { + const checksum = new GLib.Checksum(GLib.ChecksumType.SHA256); + input.seek(0, GLib.SeekType.SET, null); + let remaining = length; + while (remaining > 0) { + if (GLib.get_monotonic_time() > deadline) + throw new Error('History validation timed out'); + const bytes = input.read_bytes(Math.min(65536, remaining), null).toArray(); + if (!bytes.length) + throw new Error('History changed while validating'); + checksum.update(bytes); + remaining -= bytes.length; + } + return checksum; +} + function validDate(value) { const parsed = typeof value === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(value) ? Date.parse(`${value}T00:00:00Z`) @@ -122,7 +145,7 @@ export function scanHistory(id, roots, parse, {retention = 30, now = Date.now(), const destination = cachePath ?? join(cacheDirectory(), `history-${id}.json`); let cached = readJson(destination, {}, 64 * 1024 * 1024); const identity = fingerprint(JSON.stringify(roots)); - if (cached.version !== CACHE_VERSION || cached.identity !== identity || typeof cached.files !== 'object' || + if (cached?.version !== CACHE_VERSION || cached.identity !== identity || !cached.files || typeof cached.files !== 'object' || Array.isArray(cached.files)) cached = {version: CACHE_VERSION, identity, files: {}, updatedAt: null}; const files = []; @@ -149,12 +172,20 @@ export function scanHistory(id, roots, parse, {retention = 30, now = Date.now(), continue; } // Changed or truncated files are rebuilt; append-only files resume at the last complete line. - if (!previous || file.fileId !== previous.fileId || file.size < previous.size || previous.offset > file.size) - previous = {offset: 0, size: 0, stamp: '', fileId: file.fileId, - state: {session: fallbackSession}, events: {}, partial: false}; + if (!previous || file.fileId !== previous.fileId || file.size < previous.size || previous.offset > file.size || + (file.size === previous.size && file.stamp !== previous.stamp)) + previous = newFileState(file.fileId, fallbackSession); let input; try { input = Gio.File.new_for_path(file.path).read(null); + // Verify the entire committed prefix before resuming. In-place rewrites + // can grow as well as shrink, so size/mtime or sampled bytes are not proof + // that a file is append-only. Only a digest reaches the private cache. + let checksum = hashPrefix(input, previous.offset, deadline); + if (previous.offset && (!previous.digest || checksum.copy().get_string() !== previous.digest)) { + previous = newFileState(file.fileId, fallbackSession); + checksum = hashPrefix(input, 0, deadline); + } input.seek(previous.offset, GLib.SeekType.SET, null); let offset = previous.offset; let tail = new Uint8Array(); @@ -173,6 +204,11 @@ export function scanHistory(id, roots, parse, {retention = 30, now = Date.now(), const line = buffer.subarray(start, i); offset += i - start + 1; start = i + 1; + if (previous.skipping || line.length > MAX_LINE_BYTES) { + previous.skipping = false; + previous.partial = true; + continue; + } try { const text = new TextDecoder().decode(line); if (!text.trim()) @@ -195,14 +231,25 @@ export function scanHistory(id, roots, parse, {retention = 30, now = Date.now(), previous.partial = true; } } + checksum.update(buffer.subarray(0, start)); tail = buffer.slice(start); - if (tail.length > MAX_LINE_BYTES || - (GLib.get_monotonic_time() - started) / 1000000 > SCAN_SECONDS) { + if (tail.length > MAX_LINE_BYTES || previous.skipping) { + // Consume, rather than retry, oversized lines. Persist the skip + // state across deadlines and appends, never treating their suffix + // as a new JSON record. Keep processing later complete records. + previous.partial = true; + previous.skipping = true; + checksum.update(tail); + offset += tail.length; + tail = new Uint8Array(); + } + if ((GLib.get_monotonic_time() - started) / 1000000 > SCAN_SECONDS) { partial = true; stop = true; } } previous.offset = offset; + previous.digest = checksum.get_string(); // A time-limited scan must resume even if the source file has not changed. previous.size = stop ? -1 : Math.max(file.size, offset); previous.stamp = file.stamp; @@ -215,7 +262,7 @@ export function scanHistory(id, roots, parse, {retention = 30, now = Date.now(), } const events = []; for (const [key, file] of Object.entries(cached.files)) { - const normalized = cachedFile(file, privateIdentity(id, key)); + const normalized = /^[a-f0-9]{64}$/.test(key) ? cachedFile(file, privateIdentity(id, key)) : null; if (!normalized) { delete cached.files[key]; partial = true; diff --git a/src/services/process.js b/src/services/process.js index 15369bb..58d55bd 100644 --- a/src/services/process.js +++ b/src/services/process.js @@ -1,5 +1,6 @@ import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; +import {readLines, readText, waitProcess, writeText} from './streams.js'; const DEFAULT_MAX_OUTPUT_BYTES = 8 * 1024 * 1024; const MAX_TIMEOUT_MS = 10 * 60 * 1000; @@ -35,10 +36,6 @@ function spawn(spec, flags) { return launcher.spawnv(argv); } -function outputSize(value) { - return new TextEncoder().encode(value ?? '').length; -} - export async function runCommand(spec, {input = null, timeout = 15000, cancellable = null, maxOutputBytes = DEFAULT_MAX_OUTPUT_BYTES} = {}) { if (!Number.isSafeInteger(timeout) || timeout <= 0 || timeout > MAX_TIMEOUT_MS || @@ -67,21 +64,21 @@ export async function runCommand(spec, {input = null, timeout = 15000, cancellab return GLib.SOURCE_REMOVE; }); try { - const stdout = await new Promise((resolve, reject) => { - proc.communicate_utf8_async(input, local, (p, result) => { - try { - const [, output] = p.communicate_utf8_finish(result); - completed = true; - resolve(output ?? ''); - } catch (error) { - reject(error); - } - }); - }); + const writeInput = async () => { + if (input === null) + return; + const stdin = proc.get_stdin_pipe(); + await writeText(stdin, input, local); + stdin.close(null); + }; + const [stdout] = await Promise.all([ + readText(proc.get_stdout_pipe(), maxOutputBytes, local, + () => new ProcessError('OUTPUT_LIMIT', 'Command output exceeded the configured limit')), + writeInput(), + waitProcess(proc, local).then(() => { completed = true; }), + ]); if (!proc.get_successful()) throw new ProcessError('FAILED', 'Command exited unsuccessfully'); - if (outputSize(stdout) > maxOutputBytes) - throw new ProcessError('OUTPUT_LIMIT', 'Command output exceeded the configured limit'); return stdout; } catch (error) { if (cancellation === 'TIMED_OUT') @@ -90,21 +87,28 @@ export async function runCommand(spec, {input = null, timeout = 15000, cancellab throw new ProcessError('CANCELLED', 'Command was cancelled'); throw error; } finally { - // Give managed collectors time to cancel and reap their own CLI child. - if (local.is_cancelled()) { - proc.send_signal(15); - await new Promise(resolve => GLib.timeout_add(GLib.PRIORITY_DEFAULT, 300, () => { - resolve(); - return GLib.SOURCE_REMOVE; - })); - } - if (!completed) - proc.force_exit(); - proc.wait_async(null, (p, result) => { try { p.wait_finish(result); } catch { /* Reap best effort. */ } }); if (timer) GLib.source_remove(timer); if (externalId) cancellable.disconnect(externalId); + // Abort remaining stream operations on every failure, including output overflow. + local.cancel(); + // Give managed collectors time to cancel and reap their own CLI child. + if (!completed) { + proc.send_signal(15); + let killTimer = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 300, () => { + killTimer = 0; + proc.force_exit(); + return GLib.SOURCE_REMOVE; + }); + let reaped = false; + try { await waitProcess(proc); reaped = true; } finally { + if (killTimer) + GLib.source_remove(killTimer); + if (!reaped) + proc.force_exit(); + } + } } } @@ -114,13 +118,16 @@ export class RpcClient { throw new ProcessError('INVALID_OPTIONS', 'RPC timeout is outside the supported range'); this.proc = spawn(spec, Gio.SubprocessFlags.STDIN_PIPE | Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_SILENCE); - this.input = new Gio.DataInputStream({base_stream: this.proc.get_stdout_pipe()}); + this.input = this.proc.get_stdout_pipe(); this.cancellable = new Gio.Cancellable(); this.parent = cancellable; this.timeout = timeout; this.nextId = 0; this.pending = new Map(); this.closed = false; + this._writes = Promise.resolve(); + this._queuedBytes = 0; + this._queuedMessages = 0; this.parentId = cancellable?.connect(() => this.close()) ?? 0; if (!this.closed) this.read(); @@ -129,7 +136,19 @@ export class RpcClient { send(value) { if (this.closed) throw new Error('RPC closed'); - this.proc.get_stdin_pipe().write_all(new TextEncoder().encode(`${JSON.stringify(value)}\n`), this.cancellable); + const text = `${JSON.stringify(value)}\n`; + const size = new TextEncoder().encode(text).length; + if (this._queuedMessages >= 64 || this._queuedBytes + size > DEFAULT_MAX_OUTPUT_BYTES) + throw new ProcessError('OUTPUT_LIMIT', 'RPC request exceeded the configured limit'); + this._queuedBytes += size; + this._queuedMessages++; + this._writes = this._writes.then(() => { + if (!this.closed) + return writeText(this.proc.get_stdin_pipe(), text, this.cancellable); + }).catch(() => this.close()).finally(() => { + this._queuedBytes -= size; + this._queuedMessages--; + }); } notify(method, params) { @@ -137,6 +156,8 @@ export class RpcClient { } request(method, params = {}) { + if (this.closed || this.pending.size >= 64) + return Promise.reject(new ProcessError('RPC_CLOSED', 'RPC unavailable or too many pending requests')); return new Promise((resolve, reject) => { const id = ++this.nextId; const timer = GLib.timeout_add(GLib.PRIORITY_DEFAULT, this.timeout, () => { @@ -155,13 +176,13 @@ export class RpcClient { }); } - read() { - this.input.read_line_async(GLib.PRIORITY_DEFAULT, this.cancellable, (stream, result) => { - try { - const [bytes] = stream.read_line_finish(result); - if (bytes === null) - throw new Error('RPC exited'); - const value = JSON.parse(new TextDecoder().decode(bytes)); + async read() { + try { + for await (const line of readLines(this.input, DEFAULT_MAX_OUTPUT_BYTES, this.cancellable, + () => new ProcessError('OUTPUT_LIMIT', 'RPC response exceeded the configured limit'))) { + if (this.closed) + return; + const value = JSON.parse(line); const pending = this.pending.get(value.id); if (pending) { this.pending.delete(value.id); @@ -175,12 +196,12 @@ export class RpcClient { pending.resolve(value.result); } } - if (!this.closed) - this.read(); - } catch { - this.close(); } - }); + } catch { + // Malformed, oversized and interrupted streams all close pending work. + } finally { + this.close(); + } } close() { diff --git a/src/services/streams.js b/src/services/streams.js new file mode 100644 index 0000000..1c9d115 --- /dev/null +++ b/src/services/streams.js @@ -0,0 +1,79 @@ +import GLib from 'gi://GLib'; + +const CHUNK_BYTES = 65536; + +export function readBytes(stream, cancellable) { + return new Promise((resolve, reject) => { + stream.read_bytes_async(CHUNK_BYTES, GLib.PRIORITY_DEFAULT, cancellable, (input, result) => { + try { resolve(input.read_bytes_finish(result).toArray()); } catch (error) { reject(error); } + }); + }); +} + +export async function readText(stream, maxBytes, cancellable, limitError) { + const parts = []; + let size = 0; + while (true) { + const bytes = await readBytes(stream, cancellable); + if (!bytes.length) + break; + size += bytes.length; + if (size > maxBytes) + throw limitError(); + parts.push(bytes); + } + // GJS does not support TextDecoder's streaming option. Decode only after + // joining the already-bounded bytes so split multibyte characters survive. + const output = new Uint8Array(size); + let offset = 0; + for (const part of parts) { + output.set(part, offset); + offset += part.length; + } + return new TextDecoder('utf-8', {fatal: true}).decode(output); +} + +// RPC lines are bounded before decoding or parsing, including unterminated lines. +export async function* readLines(stream, maxBytes, cancellable, limitError) { + let tail = new Uint8Array(); + while (true) { + const bytes = await readBytes(stream, cancellable); + if (!bytes.length) { + if (tail.length) + throw new Error('Incomplete RPC message'); + return; + } + const buffer = new Uint8Array(tail.length + bytes.length); + buffer.set(tail); + buffer.set(bytes, tail.length); + let start = 0; + for (let i = 0; i < buffer.length; i++) { + if (i - start > maxBytes) + throw limitError(); + if (buffer[i] === 10) { + yield new TextDecoder('utf-8', {fatal: true}).decode(buffer.subarray(start, i)); + start = i + 1; + } + } + tail = buffer.slice(start); + if (tail.length > maxBytes) + throw limitError(); + } +} + +export function writeText(stream, text, cancellable) { + return new Promise((resolve, reject) => { + stream.write_all_async(new TextEncoder().encode(text), GLib.PRIORITY_DEFAULT, cancellable, + (output, result) => { + try { output.write_all_finish(result); resolve(); } catch (error) { reject(error); } + }); + }); +} + +export function waitProcess(proc, cancellable = null) { + return new Promise((resolve, reject) => { + proc.wait_async(cancellable, (child, result) => { + try { child.wait_finish(result); resolve(); } catch (error) { reject(error); } + }); + }); +} diff --git a/tests/integration/collector.js b/tests/integration/collector.js index 2700997..d36e5dc 100644 --- a/tests/integration/collector.js +++ b/tests/integration/collector.js @@ -32,7 +32,7 @@ GLib.file_set_contents(fixture, `${text}${event(180)}\n{"partial":`); const third = scanHistory('codex', [sessions], parseCodexEvent, {now, cachePath}); assert(third.days.at(-1).total === 180, 'append must count delta only'); const cache = readJson(cachePath); -assert(cache.version === 4, 'versioned cache'); +assert(cache.version === 5, 'versioned cache'); assert(!JSON.stringify(cache).includes('synthetic'), 'session identity must be sanitized in cache'); assert(third.days.at(-1).sessions === 1, 'resumed scans must retain one stable private session identity'); const privateDirectory = join(scratch, 'private-state'); diff --git a/tests/integration/stress.js b/tests/integration/stress.js new file mode 100644 index 0000000..4a36ce3 --- /dev/null +++ b/tests/integration/stress.js @@ -0,0 +1,213 @@ +// Explicit stress suite, separate from the fast release checks. Synthetic data only. +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import System from 'system'; +import {ThresholdTracker} from '../../src/core/notifications.js'; +import {scanHistory} from '../../src/services/history.js'; +import {parseCodexEvent} from '../../src/providers/codex.js'; +import {join, readJson} from '../../src/services/files.js'; +import {RpcClient, runCommand} from '../../src/services/process.js'; + +const scratch = GLib.dir_make_tmp('usagebeam-data-stress-XXXXXX'); +const now = new Date('2026-09-06T12:00:00Z').getTime(); +const results = []; +const assert = (value, message) => { if (!value) throw new Error(message); }; +const event = total => JSON.stringify({type: 'event_msg', timestamp: '2026-09-06T10:00:00Z', payload: {type: 'token_count', + info: {total_token_usage: {input_tokens: total, output_tokens: 0, cached_input_tokens: 0, total_tokens: total}}}}); +const total = record => record.days.reduce((sum, day) => sum + day.total, 0); +const fixture = name => { + const root = join(scratch, name); + GLib.mkdir_with_parents(root, 0o700); + const cachePath = join(scratch, `${name}-cache.json`); + return {root, path: join(root, 'fixture.jsonl'), cachePath, + scan: () => scanHistory('codex', [root], parseCodexEvent, {now, cachePath})}; +}; +async function test(name, callback) { + const start = GLib.get_monotonic_time(); + try { + const details = await callback(); + results.push({name, ok: true, details}); + } catch (error) { + results.push({name, ok: false, error: error.message}); + } + results.at(-1).milliseconds = Math.round((GLib.get_monotonic_time() - start) / 1000); + print(JSON.stringify(results.at(-1))); +} +function remove(file) { + if (file.query_file_type(Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null) === Gio.FileType.DIRECTORY) { + const iterator = file.enumerate_children('standard::name', Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null); + try { + let entry; + while ((entry = iterator.next_file(null))) + remove(file.get_child(entry.get_name())); + } finally { + iterator.close(null); + } + } + file.delete(null); +} + +async function run() { + await test('50,000 cumulative events across 50 files; warm cache and privacy', () => { + const source = fixture('volume'); + const contents = Array.from({length: 1000}, (_, index) => event((index + 1) * 10)).join('\n') + '\n'; + for (let file = 0; file < 50; file++) + GLib.file_set_contents(join(source.root, `${file}.jsonl`), contents); + const cold = source.scan(); + assert(total(cold) === 500000 && cold.status === 'ready', 'Cold scan lost cumulative events'); + const warm = source.scan(); + assert(total(warm) === 500000 && warm.scannedFiles === 0, 'Warm scan recounted unchanged files'); + assert(!JSON.stringify(readJson(source.cachePath)).includes(source.root), 'Cache exposes source paths'); + return {events: 50000, files: 50, total: total(warm)}; + }); + await test('same-inode same-length rewrite invalidates cached totals', () => { + const source = fixture('rewrite'); + GLib.file_set_contents(source.path, `${event(100)}\n`); + assert(total(source.scan()) === 100, 'Baseline differs'); + GLib.usleep(2000); + const stream = Gio.File.new_for_path(source.path).open_readwrite(null); + try { + stream.get_output_stream().write_all(new TextEncoder().encode(`${event(200)}\n`), null); + } finally { + stream.close(null); + } + const actual = total(source.scan()); + assert(actual === 200, `Expected 200 after in-place rewrite, got ${actual}`); + }); + await test('oversized local line does not permanently hide later valid usage', () => { + const source = fixture('oversized'); + GLib.file_set_contents(source.path, `${event(100)}\n${' '.repeat(5 * 1024 * 1024)}\n${event(200)}\n`); + source.scan(); + const next = source.scan(); + assert(total(next) === 200, `Expected recovery to 200, got ${total(next)} (${next.status})`); + }); + await test('growing in-place rewrite verifies the entire committed prefix', () => { + const source = fixture('growing-rewrite'); + GLib.file_set_contents(source.path, `${event(100)}\n`); + assert(total(source.scan()) === 100, 'Baseline differs'); + const stream = Gio.File.new_for_path(source.path).open_readwrite(null); + try { + stream.get_output_stream().write_all(new TextEncoder().encode(`${event(300)}\n${event(400)}\n`), null); + } finally { stream.close(null); } + assert(total(source.scan()) === 400, 'Growing rewrite was mistaken for an append'); + }); + await test('oversized unterminated record resumes past its later newline', () => { + const source = fixture('oversized-append'); + GLib.file_set_contents(source.path, `${event(100)}\n${' '.repeat(5 * 1024 * 1024)}`); + assert(total(source.scan()) === 100, 'Oversized incomplete line changed usage'); + const output = Gio.File.new_for_path(source.path).append_to(Gio.FileCreateFlags.NONE, null); + try { + output.write_all(new TextEncoder().encode(`remaining oversized text\n${event(200)}\n`), null); + } finally { output.close(null); } + const result = source.scan(); + assert(total(result) === 200 && result.status === 'partial', 'Skip state was not retained across scans'); + }); + await test('null or malformed cache is safely rebuilt', () => { + const source = fixture('bad-cache'); + GLib.file_set_contents(source.path, `${event(100)}\n`); + for (const value of ['null', '{"files":null}', 'not json']) { + GLib.file_set_contents(source.cachePath, value); + assert(total(source.scan()) === 100, 'Bad cache prevented source recovery'); + } + }); + await test('malformed JSON preserves readable events with partial status', () => { + const source = fixture('malformed'); + GLib.file_set_contents(source.path, `${event(100)}\nnot json\n${event(200)}\n`); + const result = source.scan(); + assert(total(result) === 200 && result.status === 'partial', 'Malformed line corrupted readable usage'); + }); + await test('incomplete write resumes after append without double counting', () => { + const source = fixture('append'); + const next = `${event(200)}\n`; + GLib.file_set_contents(source.path, `${event(100)}\n${next.slice(0, 50)}`); + assert(total(source.scan()) === 100, 'Partial write was counted'); + const output = Gio.File.new_for_path(source.path).append_to(Gio.FileCreateFlags.NONE, null); + try { + output.write_all(new TextEncoder().encode(next.slice(50)), null); + } finally { + output.close(null); + } + assert(total(source.scan()) === 200 && total(source.scan()) === 200, 'Append lost or duplicated usage'); + }); + const record = (percent, period = 1, count = 1) => ({id: 'codex', name: 'Codex', accountKey: '0'.repeat(64), + limits: {status: 'ready', windows: Array.from({length: count}, (_, index) => ({id: `window-${index}`, + label: `Allowance ${index}`, resetsAt: period, usedPercent: percent, unlimited: false}))}}); + await test('10,000 repeated reads emit each milestone exactly once per period', () => { + const tracker = new ThresholdTracker(); + const alerts = []; + for (const percent of [0, 80, 79, 80, 90, 89, 90, 100]) + alerts.push(...tracker.update(record(percent), 80)); + for (let index = 0; index < 10000; index++) + alerts.push(...tracker.update(record(index % 2 ? 100 : 79), 80)); + assert(alerts.map(alert => alert.threshold).join(',') === '80,90,100', 'Milestone alerts repeat'); + }); + await test('notification state stays bounded after 1,000 quota resets', () => { + const tracker = new ThresholdTracker(); + for (let period = 1; period <= 1000; period++) + tracker.update(record(20, period, 32), 80); + assert(tracker.previous.size <= 200, `Expected at most 200 retained keys, got ${tracker.previous.size}`); + }); + await test('20 concurrent subprocesses and 20 RPC lifecycles', async () => { + const output = await Promise.all(Array.from({length: 20}, () => runCommand(['/bin/echo', 'synthetic']))); + assert(output.every(value => value.trim() === 'synthetic'), 'Concurrent output mismatch'); + for (let index = 0; index < 20; index++) { + const rpc = new RpcClient(['/bin/cat'], null, 500); + try { await rpc.request('synthetic'); } finally { rpc.close(); } + assert(rpc.closed && rpc.pending.size === 0, 'RPC retained requests after close'); + } + }); + await test('20 in-flight cancellations terminate promptly', async () => { + const jobs = Array.from({length: 20}, async () => { + const cancellation = new Gio.Cancellable(); + const promise = runCommand(['/bin/sleep', '5'], {cancellable: cancellation}); + cancellation.cancel(); + try { await promise; return false; } catch (error) { return error.code === 'CANCELLED'; } + }); + assert((await Promise.all(jobs)).every(Boolean), 'Cancellation was lost'); + }); + await test('stdout cap is enforced before producer exit', async () => { + // Finite output and a short deadline: exercises pressure without risking OOM. + let code; + try { + await runCommand(['/usr/bin/python3', '-c', + 'import sys,time; sys.stdout.write("x" * 1048576); sys.stdout.flush(); time.sleep(2)'], + {maxOutputBytes: 1024, timeout: 1000}); + } catch (error) { code = error.code; } + assert(code === 'OUTPUT_LIMIT', `Expected early OUTPUT_LIMIT, got ${code}`); + }); + await test('Unicode crossing read boundaries and large stdin round-trip intact', async () => { + const input = 'x'.repeat(65535) + '🛰'.repeat(20000); + const actual = await runCommand(['/bin/cat'], {input}); + assert(actual === input, 'Chunk boundaries corrupted Unicode'); + }); + await test('RPC output and queued writes remain bounded', async () => { + const rpc = new RpcClient(['/usr/bin/python3', '-c', + 'import sys,time; sys.stdout.write("x" * 9437184); sys.stdout.flush(); time.sleep(2)'], null, 5000); + try { + let closed = false; + try { await rpc.request('synthetic'); } catch (error) { closed = error.code === 'RPC_CLOSED'; } + assert(closed && rpc.pending.size === 0, 'Oversized RPC response was not rejected'); + } finally { rpc.close(); } + const stalled = new RpcClient(['/bin/sleep', '5'], null, 500); + try { + let bounded = false; + try { + for (let index = 0; index < 65; index++) + stalled.notify('synthetic', {value: index}); + } catch (error) { bounded = error.code === 'OUTPUT_LIMIT'; } + assert(bounded, 'RPC notification queue is unbounded'); + } finally { stalled.close(); } + }); +} +const loop = new GLib.MainLoop(null, false); +run().catch(error => results.push({name: 'stress harness', ok: false, error: error.message})).finally(() => { + try { + if (ARGV[0]) + GLib.file_set_contents(ARGV[0], JSON.stringify(results, null, 2)); + remove(Gio.File.new_for_path(scratch)); + } finally { + loop.quit(); + } +}); +loop.run(); +System.exit(results.every(result => result.ok) ? 0 : 1); diff --git a/tests/unit/usage.test.js b/tests/unit/usage.test.js index 82764c1..83ce845 100644 --- a/tests/unit/usage.test.js +++ b/tests/unit/usage.test.js @@ -147,6 +147,20 @@ test('simultaneous and repeated alerts produce one concise notification body', ( assert.equal(notificationBody([]), ''); }); +test('notification eviction retains recently refreshed windows and accepts fresh partial data', () => { + const tracker = new ThresholdTracker(); + const current = (id, percent) => ({id: 'codex', name: 'Codex', limits: {status: 'partial', windows: [ + {id, label: id, usedPercent: percent, resetsAt: 1000}, + ]}}); + tracker.update(current('active', 79), 80); + assert.equal(tracker.update(current('active', 90), 80).length, 1); + for (let id = 0; id < 500; id++) { + tracker.update(current(String(id), 0), 80); + assert.deepEqual(tracker.update(current('active', 90), 80), []); + } + assert.equal(tracker.previous.size, 200); +}); + test('formatting preserves unknown/reset-due states', () => { assert.equal(tokens(null), '—'); assert.equal(tokens(23000000), '23.0M'); From 357bc5d99a837fe322b8a9bfc9bfba3bedbd2371 Mon Sep 17 00:00:00 2001 From: kcnewman Date: Tue, 8 Sep 2026 22:53:21 +0000 Subject: [PATCH 36/44] fix: adapt usage popup across GNOME work areas --- src/ui/indicator.js | 132 +++++++++++++++++++++------ src/ui/layoutPolicy.js | 24 +++++ src/ui/panelPlacement.js | 50 ++++++++--- src/ui/widgets.js | 18 ++++ stylesheet.css | 20 +++++ tests/shell/extension.js | 5 ++ tests/shell/stress.js | 182 ++++++++++++++++++++++++++++++++++++++ tests/unit/layout.test.js | 25 ++++++ tools/smoke-shell.py | 77 +++++++++++++--- 9 files changed, 483 insertions(+), 50 deletions(-) create mode 100644 src/ui/layoutPolicy.js create mode 100644 tests/shell/stress.js create mode 100644 tests/unit/layout.test.js diff --git a/src/ui/indicator.js b/src/ui/indicator.js index d755426..047a4df 100644 --- a/src/ui/indicator.js +++ b/src/ui/indicator.js @@ -8,10 +8,11 @@ import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js'; import {NAMES, recentDates} from '../core/usage.js'; import {age, modelName, tokens} from '../core/format.js'; import {quotaSeverity} from '../core/thresholds.js'; +import {initialLayout, nextLayout, pageSlice} from './layoutPolicy.js'; import {historyOverview, latestUpdate, panelQuota, periodDays, providerStatus, quotaPresentation} from './presentation.js'; import {actionButton, button, dayChart, disclosureButton, label, meter, modelMeter, - limitRow, metricLabel, providerIcon, separatorDot} from './widgets.js'; + limitRow, metricLabel, pageControls, providerIcon, separatorDot} from './widgets.js'; const TAB_NAMES = {claude: 'Claude'}; const PROVIDER_MARKS = {codex: '>_', claude: '✦'}; @@ -25,6 +26,10 @@ export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator this._openPreferences = openPreferences; this._service = null; this._detailsExpanded = false; + this._limitPage = 0; + this._activityPage = 0; + this._modelPage = 0; + this._sectionPage = 0; this._panelStatus = new St.BoxLayout({style_class: 'usagebeam-panel-status', x_expand: true, x_align: Clutter.ActorAlign.START}); this._panelIcon = new St.Bin({style_class: 'usagebeam-panel-icon-slot', y_align: Clutter.ActorAlign.CENTER}); @@ -52,13 +57,18 @@ export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator // ClutterContent, so keep the menu actor under an unambiguous name. this._contentBox = new St.BoxLayout({vertical: true, style_class: 'usagebeam-content', x_expand: true}); section.box.add_child(this._contentBox); + const restyle = () => { this._layout = null; this.render(); }; + Main.layoutManager.connectObject('monitors-changed', restyle, this); + St.ThemeContext.get_for_stage(global.stage).connectObject('changed', restyle, this); this.menu.connect('open-state-changed', (_menu, open) => { if (open) { + this._layout = null; this.resize(); this._service?.refreshAll(); this.render(); } else { this._detailsExpanded = false; + this._layout = null; } }); this.render(); @@ -74,9 +84,12 @@ export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator resize() { const monitor = Main.layoutManager.findMonitorForActor(this) ?? Main.layoutManager.primaryMonitor; + const work = Main.layoutManager.getWorkAreaForMonitor(monitor.index); const scale = St.ThemeContext.get_for_stage(global.stage).scale_factor; - const availableWidth = Math.max(240, Math.floor(monitor.width / scale) - 32); + const availableWidth = Math.max(160, Math.floor(work.width / scale) - 64); this._contentBox.style = `width: ${Math.min(392, availableWidth)}px;`; + // Allow for the native popup frame, panel gap, and our content padding. + this._contentHeightBudget = Math.max(80, work.height - 48 * scale); } attach(service) { @@ -94,26 +107,65 @@ export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator return; const focus = global.stage.get_key_focus(); const restoreName = focus && this._contentBox.contains(focus) ? focus.accessible_name : null; - this._contentBox.destroy_all_children(); const enabled = this._service?.enabledProviders ?? []; const requested = this._settings.get_string('default-provider'); const id = enabled.includes(requested) ? requested : enabled[0]; const record = this._service?.recordFor(id); const busy = this._service?.isRefreshing(id) ?? false; - + if (this._renderedProvider !== id) { + this._limitPage = this._activityPage = this._modelPage = this._sectionPage = 0; + this._layout = null; + this._renderedProvider = id; + } + const layoutKey = JSON.stringify([record?.plan, record?.limits.status, record?.limits.message, + record?.limits.windows.map(window => [window.label, window.unlimited]), + record?.history.status, record?.history.message, + record?.history.days.length, record?.history.models.map(model => model.model)]); + if (this._layoutKey !== layoutKey) { + this._layoutKey = layoutKey; + this._layout = null; + this._limitPage = this._modelPage = this._sectionPage = 0; + } this._renderPanelStatus(id, record); + this.resize(); + // Keep page sizes stable while navigating, even on a shorter last page. + // A new opening, provider, theme or monitor starts a fresh measurement. + this._layout ??= initialLayout(); + // Fit before the next paint. Never rebuild from allocation callbacks, + // which caused the old bar flicker and recursive growth. + while (this._layout) { + this._contentBox.destroy_all_children(); + this._contentBox.style_class = `usagebeam-content${this._layout.compact ? ' usagebeam-compact' : ''}`; + this._buildContent(enabled, id, record, busy); + const width = this._contentBox.get_preferred_width(-1)[1]; + if (this._contentBox.get_preferred_height(width)[1] <= this._contentHeightBudget) + break; + const next = nextLayout(this._layout, this._detailsExpanded); + if (!next) + break; + this._layout = next; + } + this._restoreFocus(restoreName); + } + _buildContent(enabled, id, record, busy) { this._renderHeader(id, record, busy); if (enabled.length > 1) this._renderSelector(enabled, id); if (record) { - this._renderLimits(record.limits); - this._renderHistory(record.history); + if (this._layout.splitSections) + this._contentBox.add_child(pageControls('View', this._sectionPage, 2, value => { + this._sectionPage = value; + this.render(); + })); + if (!this._layout.splitSections || this._sectionPage === 0) + this._renderLimits(record.limits); + if (!this._layout.splitSections || this._sectionPage === 1) + this._renderHistory(record.history); } else { this._message('Choose your providers in preferences to get started.'); } this._renderFooter(record, busy); - this._restoreFocus(restoreName); } _renderPanelStatus(id, record) { @@ -144,7 +196,9 @@ export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator header.add_child(label(PROVIDER_MARKS[id] ?? 'AI', 'usagebeam-provider-mark')); const identity = new St.BoxLayout({vertical: true, style_class: 'usagebeam-identity', x_expand: true}); identity.add_child(label(TAB_NAMES[id] ?? record?.name ?? 'UsageBeam', 'usagebeam-title')); - identity.add_child(label(record?.plan ? String(record.plan).toUpperCase() : 'USAGE MONITOR', 'usagebeam-caption')); + const plan = label(record?.plan ? String(record.plan).toUpperCase() : 'USAGE MONITOR', 'usagebeam-caption'); + plan.clutter_text.ellipsize = Pango.EllipsizeMode.END; + identity.add_child(plan); header.add_child(identity); const statusText = providerStatus(record, busy); header.add_child(label(`● ${statusText}`, `usagebeam-status usagebeam-status-${statusText.toLowerCase()}`)); @@ -165,7 +219,8 @@ export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator _renderLimits(limits) { this._heading('LIMITS'); - for (const window of limits.windows) { + const page = pageSlice(limits.windows.filter(window => quotaPresentation(window)), this._limitPage, this._layout.limits); + for (const window of page.items) { const box = new St.BoxLayout({vertical: true, style_class: 'usagebeam-window'}); const view = quotaPresentation(window); if (!view) @@ -180,6 +235,11 @@ export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator } this._contentBox.add_child(box); } + if (page.pages > 1) + this._contentBox.add_child(pageControls('Limits', page.page, page.pages, value => { + this._limitPage = value; + this.render(); + })); if (limits.status !== 'ready') { this._message(limits.message || (limits.status === 'loading' ? 'Checking account limits…' : 'Limits unavailable')); @@ -193,23 +253,33 @@ export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator if (overview) { this._contentBox.add_child(disclosureButton(this._detailsExpanded, () => { this._detailsExpanded = !this._detailsExpanded; + this._layout = null; + this._limitPage = this._activityPage = this._modelPage = this._sectionPage = 0; this.render(); })); - // Construct the complete view even while collapsed so lifecycle smoke - // coverage continues to exercise every primary widget. - const details = new St.BoxLayout({vertical: true, style_class: 'usagebeam-details', - x_expand: true, visible: this._detailsExpanded}); - if (history.days.length) - this._renderDays(history, details); - if (history.models.length) - this._renderModels(history, details); - this._contentBox.add_child(details); + if (this._detailsExpanded) + this._renderActivity(history); } if (history.message && history.status !== 'ready') this._message(history.message); } + _renderActivity(history) { + const details = new St.BoxLayout({vertical: true, style_class: 'usagebeam-details', x_expand: true}); + const split = this._layout.splitActivity && history.days.length && history.models.length; + if (split) + details.add_child(pageControls('Activity', this._activityPage, 2, value => { + this._activityPage = value; + this.render(); + })); + if (history.days.length && (!split || this._activityPage === 0)) + this._renderDays(history, details); + if (history.models.length && (!split || this._activityPage === 1)) + this._renderModels(history, details); + this._contentBox.add_child(details); + } + _renderDays(history, parent = this._contentBox) { const overview = historyOverview(history); this._heading(`LAST ${overview?.days ?? history.days.length} DAYS · ${tokens(overview?.total ?? 0)} TOKENS`, parent); @@ -222,15 +292,18 @@ export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator const days = periodDays(history.period); this._heading(`TOKENS BY MODEL${days ? ` · ${days}D` : ''} ${modelScope}`, parent); const max = Math.max(1, ...history.models.map(model => model.total)); - const visibleModels = history.models.slice(0, 4); - for (const model of visibleModels) { + const page = pageSlice(history.models, this._modelPage, this._layout.models); + for (const model of page.items) { const displayName = modelName(model.model); const cache = model.cacheRead + model.cacheWrite; const accessible = `${displayName}, ${model.total} tokens. Input ${model.input}, output ${model.output}, cache ${cache}.`; parent.add_child(modelMeter(displayName, tokens(model.total), model.total / max, accessible)); } - if (history.models.length > visibleModels.length) - parent.add_child(label(`Top ${visibleModels.length} of ${history.models.length} models`, 'usagebeam-caption')); + if (page.pages > 1) + parent.add_child(pageControls('Models', page.page, page.pages, value => { + this._modelPage = value; + this.render(); + })); } _renderFooter(record, busy) { @@ -245,13 +318,14 @@ export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator _restoreFocus(name) { if (!name) return; - const visit = actor => { - if (actor.can_focus && actor.accessible_name === name) - actor.grab_key_focus(); - for (const child of actor.get_children()) - visit(child); - }; + const actors = []; + const visit = actor => { actors.push(actor); actor.get_children().forEach(visit); }; visit(this._contentBox); + const opposite = name.startsWith('Next ') ? name.replace('Next ', 'Previous ') + : name.startsWith('Previous ') ? name.replace('Previous ', 'Next ') : null; + const target = [name, opposite].filter(Boolean).map(candidate => + actors.find(actor => actor.can_focus && actor.accessible_name === candidate)).find(Boolean); + target?.grab_key_focus(); } _heading(text, parent = this._contentBox) { @@ -265,6 +339,8 @@ export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator } destroy() { + Main.layoutManager.disconnectObject(this); + St.ThemeContext.get_for_stage(global.stage).disconnectObject(this); this._shellSettings?.disconnectObject(this); this._shellSettings = null; super.destroy(); diff --git a/src/ui/layoutPolicy.js b/src/ui/layoutPolicy.js new file mode 100644 index 0000000..b18ece8 --- /dev/null +++ b/src/ui/layoutPolicy.js @@ -0,0 +1,24 @@ +// Progressively reduce simultaneous content, never the user's font size. +export function initialLayout() { + return {limits: 3, models: 4, splitActivity: false, compact: false, splitSections: false}; +} + +export function nextLayout(layout, expanded) { + if (expanded && !layout.splitActivity) + return {...layout, splitActivity: true}; + if (layout.limits > 1) + return {...layout, limits: layout.limits - 1}; + if (!layout.compact) + return {...layout, compact: true}; + if (expanded && layout.models > 1) + return {...layout, models: layout.models - 1}; + if (expanded && !layout.splitSections) + return {...layout, splitSections: true}; + return null; +} + +export function pageSlice(items, requested, size) { + const pages = Math.max(1, Math.ceil(items.length / size)); + const page = Math.max(0, Math.min(pages - 1, requested)); + return {items: items.slice(page * size, (page + 1) * size), page, pages}; +} diff --git a/src/ui/panelPlacement.js b/src/ui/panelPlacement.js index d295a87..95ddf7a 100644 --- a/src/ui/panelPlacement.js +++ b/src/ui/panelPlacement.js @@ -15,26 +15,54 @@ export function clearIndicatorPlacement(indicator) { } function centerCalendarGap(indicator, calendar, target, position) { - // GNOME centers the entire center box. Balance its outer edge by the width - // difference so the boundary between these unequal actors lands at 50%. + // GNOME centers the whole box, including actors owned by other extensions. + // Balance every visible sibling around the calendar/readout boundary. const leading = new St.Widget({style_class: 'usagebeam-center-balance'}); const trailing = new St.Widget({style_class: 'usagebeam-center-balance'}); const leftIsIndicator = position === 'left-of-calendar'; + const state = {signals: [], spacers: [leading, trailing]}; + const watched = new Map(); const sync = () => { - const indicatorWidth = indicator.container.get_preferred_width(-1)[1]; - const calendarWidth = calendar.get_preferred_width(-1)[1]; - const difference = indicatorWidth - calendarWidth; - leading.set_width(Math.max(0, leftIsIndicator ? -difference : difference)); - trailing.set_width(Math.max(0, leftIsIndicator ? difference : -difference)); + const children = target.get_children().filter(actor => actor.visible); + const widths = children.map(actor => state.spacers.includes(actor) ? 0 : actor.get_preferred_width(-1)[1]); + const index = children.indexOf(leftIsIndicator ? indicator.container : calendar); + if (index < 0) + return; + const spacing = target.get_theme_node().get_length('spacing'); + const boundary = widths.slice(0, index + 1).reduce((sum, width) => sum + width, 0) + spacing * (index + 0.5); + const total = widths.reduce((sum, width) => sum + width, 0) + spacing * Math.max(0, children.length - 1); + const difference = 2 * boundary - total; + leading.set_width(Math.max(0, -difference)); + trailing.set_width(Math.max(0, difference)); }; const calendarIndex = target.get_children().indexOf(calendar); target.insert_child_at_index(leading, calendarIndex); target.insert_child_at_index(indicator.container, calendarIndex + (leftIsIndicator ? 1 : 2)); target.insert_child_at_index(trailing, calendarIndex + 3); - const signals = [indicator.container, calendar].map(actor => - [actor, actor.connect('notify::width', sync)]); - indicator._usageBeamPlacement = {signals, spacers: [leading, trailing]}; - sync(); + const observe = () => { + const children = target.get_children().filter(actor => !state.spacers.includes(actor)); + for (const [actor, signals] of watched) { + if (children.includes(actor)) + continue; + for (const signal of signals) { + actor.disconnect(signal); + state.signals = state.signals.filter(pair => pair[0] !== actor || pair[1] !== signal); + } + watched.delete(actor); + } + for (const actor of children) { + if (watched.has(actor)) + continue; + const signals = ['notify::width', 'notify::visible'].map(name => actor.connect(name, sync)); + watched.set(actor, signals); + state.signals.push(...signals.map(signal => [actor, signal])); + } + sync(); + }; + for (const signal of ['child-added', 'child-removed']) + state.signals.push([target, target.connect(signal, observe)]); + indicator._usageBeamPlacement = state; + observe(); } export function placeIndicator(indicator, position) { diff --git a/src/ui/widgets.js b/src/ui/widgets.js index f058768..0e28d35 100644 --- a/src/ui/widgets.js +++ b/src/ui/widgets.js @@ -141,3 +141,21 @@ export function actionButton(text, callback, name = text) { actor.add_style_class_name('usagebeam-action-button'); return actor; } + +export function pageControls(name, page, pages, changed) { + const row = new St.BoxLayout({style_class: 'usagebeam-pagination', x_expand: true}); + for (const direction of [-1, 0, 1]) { + if (!direction) { + row.add_child(label(`${name} · ${page + 1} / ${pages}`, 'usagebeam-page-label', true)); + continue; + } + const control = actionButton(direction < 0 ? '‹' : '›', () => changed(page + direction), + `${direction < 0 ? 'Previous' : 'Next'} ${name.toLowerCase()} page`); + control.reactive = page + direction >= 0 && page + direction < pages; + control.can_focus = control.reactive; + if (!control.reactive) + control.add_style_pseudo_class('insensitive'); + row.add_child(control); + } + return row; +} diff --git a/stylesheet.css b/stylesheet.css index c8f8702..0017600 100644 --- a/stylesheet.css +++ b/stylesheet.css @@ -359,6 +359,26 @@ border-top: 1px solid rgba(128, 150, 160, 0.22); } +.usagebeam-pagination { + spacing: 8px; + padding: 3px 0 7px; +} + +.usagebeam-page-label { + text-align: center; + font-size: 9pt; + opacity: 0.8; +} + +.usagebeam-pagination .usagebeam-button:insensitive { + opacity: 0.35; +} + +.usagebeam-compact .usagebeam-header { padding-bottom: 5px; } +.usagebeam-compact .usagebeam-selector { padding-bottom: 5px; } +.usagebeam-compact .usagebeam-window { padding-bottom: 4px; } +.usagebeam-compact .usagebeam-message { padding: 4px 8px; margin-bottom: 4px; } + .usagebeam-action-button { min-height: 21px; padding: 3px 9px; diff --git a/tests/shell/extension.js b/tests/shell/extension.js index 83f95e5..a372ea8 100644 --- a/tests/shell/extension.js +++ b/tests/shell/extension.js @@ -5,6 +5,7 @@ import Shell from 'gi://Shell'; import St from 'gi://St'; import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js'; +import {runStress} from './stress.js'; const UUID = 'usagebeam@oo7kc.github.io'; const descendants = actor => [actor, ...actor.get_children().flatMap(descendants)]; @@ -142,6 +143,10 @@ export default class UsageBeamUITest extends Extension { assert(monitorScale === expectedScale, `Expected monitor scale ${expectedScale}, got ${monitorScale}`); const interfaceSettings = new Gio.Settings({schema_id: 'org.gnome.desktop.interface'}); const textScale = interfaceSettings.get_double('text-scaling-factor'); + if (GLib.getenv('USAGEBEAM_STRESS') === '1') + return runStress({indicator, records, settings, calendar, + wait: milliseconds => this._wait(milliseconds), screenshot: name => this._screenshot(name), + monitorScale, textScale}); for (const position of positions) { settings.set_string('panel-position', position); settings.set_string('default-provider', 'codex'); diff --git a/tests/shell/stress.js b/tests/shell/stress.js new file mode 100644 index 0000000..1ebfab2 --- /dev/null +++ b/tests/shell/stress.js @@ -0,0 +1,182 @@ +// Runs only in the synthetic private desktop; never imported by the product. +import St from 'gi://St'; +import * as Main from 'resource:///org/gnome/shell/ui/main.js'; + +const descendants = actor => [actor, ...actor.get_children().flatMap(descendants)]; +const matching = (actor, style) => descendants(actor).filter(child => child.has_style_class_name?.(style)); +const bounds = actor => { + const [x, y] = actor.get_transformed_position(); + const [width, height] = actor.get_transformed_size(); + return {x, y, width, height}; +}; +const copy = value => JSON.parse(JSON.stringify(value)); + +export async function runStress({indicator, records, settings, calendar, wait, screenshot, monitorScale, textScale}) { + const checks = []; + const geometry = []; + const check = (name, ok, details = {}) => checks.push({name, ok: Boolean(ok), ...details}); + const baseline = copy(records); + const work = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex); + const scale = St.ThemeContext.get_for_stage(global.stage).scale_factor; + const textFits = actor => actor.clutter_text.get_preferred_width(-1)[1] <= actor.width + 1; + const fit = name => { + const popup = bounds(indicator.menu.actor); + const footer = bounds(indicator._contentBox.get_last_child()); + const inside = rect => rect.x >= work.x - 1 && rect.y >= work.y - 1 && + rect.x + rect.width <= work.x + work.width + 1 && + rect.y + rect.height <= work.y + work.height + 1; + check(`${name}: popup and footer fit work area`, inside(popup) && inside(footer), {popup, footer}); + geometry.push({name, popup, footer}); + }; + + for (const position of ['left', 'right', 'left-of-calendar', 'right-of-calendar']) { + settings.set_string('panel-position', position); + settings.set_string('default-provider', 'codex'); + await wait(150); + const clock = bounds(calendar); + const slot = bounds(indicator.container); + for (const provider of ['claude', 'codex']) { + settings.set_string('default-provider', provider); + await wait(100); + check(`${position}/${provider}: clock and slot stable`, + Math.abs(bounds(calendar).x - clock.x) <= 1 && + Math.abs(bounds(indicator.container).width - slot.width) <= 1); + check(`${position}/${provider}: panel labels fit`, + [indicator._panelProvider, indicator._panelValue, indicator._panelReset].every(textFits)); + } + if (position.includes('calendar')) { + const panel = bounds(Main.panel); + const left = position === 'left-of-calendar' ? slot : clock; + const right = position === 'left-of-calendar' ? clock : slot; + const center = (left.x + left.width + right.x) / 2; + check(`${position}: gap centered`, Math.abs(center - panel.x - panel.width / 2) <= 1); + } + indicator.menu.open(0); + await wait(100); + fit(`${position}/collapsed`); + indicator.menu.close(0); + } + indicator.menu.open(0); + matching(indicator._contentBox, 'usagebeam-disclosure')[0].emit('clicked', 1); + await wait(150); + fit('expanded'); + const normalHeight = bounds(indicator.menu.actor).height; + const actorCount = descendants(indicator._contentBox).length; + await screenshot('stress-expanded'); + const plots = matching(indicator._contentBox, 'usagebeam-chart-plot'); + check('seven daily bars allocate correctly', plots.length === 7 && plots.every(plot => + plot.height / scale === 52 && (plot._fraction === 0 || (plot._fill.width > 0 && plot._fill.height > 0)))); + check('activity has no nested scrolling', !descendants(indicator._contentBox).some(actor => actor instanceof St.ScrollView)); + for (let iteration = 0; iteration < 100; iteration++) { + settings.set_string('default-provider', iteration % 2 ? 'codex' : 'claude'); + indicator.render(); + matching(indicator._contentBox, 'usagebeam-disclosure')[0].emit('clicked', 1); + matching(indicator._contentBox, 'usagebeam-disclosure')[0].emit('clicked', 1); + await wait(16); + } + check('100 refresh/switch/expand cycles retain actor count', descendants(indicator._contentBox).length === actorCount); + check('100 refresh/switch/expand cycles retain height', Math.abs(bounds(indicator.menu.actor).height - normalHeight) <= 1); + + for (const state of ['loading', 'unavailable', 'missing-auth', 'unsupported', 'stale', 'partial']) { + records.codex = copy(baseline.codex); + records.codex.limits.status = state; + records.codex.history.status = state; + records.codex.limits.message = 'Synthetic provider state; no account was contacted.'; + records.codex.history.message = 'Synthetic local history state.'; + if (!['stale', 'partial'].includes(state)) + records.codex.limits.windows = []; + indicator.render(); + await wait(70); + fit(state); + check(`${state}: panel text fits`, [indicator._panelValue, indicator._panelReset].every(textFits)); + } + + records.codex = copy(baseline.codex); + records.codex.plan = 'A long synthetic subscription plan '.repeat(2); + records.codex.limits.windows[0].label = 'A provider-specific allowance with an unusually long display name'; + records.codex.history.models[0].model = 'synthetic-model-with-a-very-long-name-and-preview-version-2026'; + indicator.render(); + await wait(100); + fit('long-labels'); + check('long labels retain bounded popup width', bounds(indicator.menu.actor).width <= work.width); + + records.codex = copy(baseline.codex); + records.codex.limits.windows = Array.from({length: 32}, (_, index) => + ({...baseline.codex.limits.windows[0], id: `synthetic-${index}`, label: `Allowance ${index + 1}`})); + indicator.render(); + await wait(100); + fit('maximum-32-allowances'); + const seenAllowances = new Set(); + for (let page = 0; page < 32; page++) { + matching(indicator._contentBox, 'usagebeam-limit-name').forEach(actor => seenAllowances.add(actor.text)); + const next = descendants(indicator._contentBox).find(actor => + actor.accessible_name === 'Next limits page' && actor.reactive); + if (!next) + break; + next.grab_key_focus(); + next.emit('clicked', 1); + await wait(60); + fit(`allowances-page-${page + 2}`); + } + check('all 32 allowances reachable without scrolling', seenAllowances.size === 32, {seen: seenAllowances.size}); + check('quota pagination retains keyboard focus', Boolean(global.stage.get_key_focus()?.can_focus)); + records.codex = copy(baseline.codex); + records.codex.history.models = Array.from({length: 12}, (_, index) => + ({...baseline.codex.history.models[0], model: `synthetic-model-${index + 1}`})); + indicator._limitPage = 0; + indicator._sectionPage = 1; + indicator._activityPage = 1; + indicator.render(); + await wait(100); + const seenModels = new Set(); + for (let page = 0; page < 12; page++) { + matching(indicator._contentBox, 'usagebeam-model-name').forEach(actor => seenModels.add(actor.text)); + const next = descendants(indicator._contentBox).find(actor => + actor.accessible_name === 'Next models page' && actor.reactive); + if (!next) + break; + next.emit('clicked', 1); + await wait(60); + fit(`models-page-${page + 2}`); + } + check('all model pages reachable without scrolling', seenModels.size === 12, {seen: seenModels.size}); + indicator._activityPage = indicator._modelPage = 0; + records.codex = copy(baseline.codex); + indicator.render(); + await wait(100); + indicator.menu.actor.remove_style_class_name('usagebeam-dark'); + indicator.menu.actor.add_style_class_name('usagebeam-light'); + await wait(100); + fit('light-theme'); + await screenshot('stress-light'); + indicator.menu.close(0); + + // Other center-panel extensions must not invalidate our alignment math. + const neighbor = new St.Bin({width: 80, height: 20}); + Main.panel._centerBox.add_child(neighbor); + try { + for (const position of ['left-of-calendar', 'right-of-calendar']) { + settings.set_string('panel-position', position); + await wait(100); + const slot = bounds(indicator.container); + const clock = bounds(calendar); + const panel = bounds(Main.panel); + const center = position === 'left-of-calendar' ? (slot.x + slot.width + clock.x) / 2 + : (clock.x + clock.width + slot.x) / 2; + check(`${position}: centered with neighboring extension`, Math.abs(center - panel.x - panel.width / 2) <= 1, + {offset: center - panel.x - panel.width / 2}); + neighbor.width = 120; + await wait(100); + const afterSlot = bounds(indicator.container); + const afterClock = bounds(calendar); + const afterCenter = position === 'left-of-calendar' ? (afterSlot.x + afterSlot.width + afterClock.x) / 2 + : (afterClock.x + afterClock.width + afterSlot.x) / 2; + check(`${position}: neighboring resize remains centered`, Math.abs(afterCenter - panel.x - panel.width / 2) <= 1); + neighbor.width = 80; + } + } finally { + neighbor.destroy(); + } + return {ok: checks.every(result => result.ok), checks, geometry, + workArea: {x: work.x, y: work.y, width: work.width, height: work.height}, monitorScale, textScale}; +} diff --git a/tests/unit/layout.test.js b/tests/unit/layout.test.js new file mode 100644 index 0000000..d87bce8 --- /dev/null +++ b/tests/unit/layout.test.js @@ -0,0 +1,25 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {initialLayout, nextLayout, pageSlice} from '../../src/ui/layoutPolicy.js'; + +test('adaptive layout is finite and preserves readable text rather than scaling it down', () => { + for (const expanded of [false, true]) { + let layout = initialLayout(); + let attempts = 0; + while (layout) { + assert.ok(layout.limits >= 1 && layout.models >= 1); + assert.ok(++attempts <= 10, 'Layout must converge without allocation feedback'); + layout = nextLayout(layout, expanded); + } + } +}); + +test('pagination reaches every item and clamps pages after a data change', () => { + const items = Array.from({length: 32}, (_, index) => index); + const seen = []; + for (let page = 0; page < 11; page++) + seen.push(...pageSlice(items, page, 3).items); + assert.deepEqual(seen, items); + assert.deepEqual(pageSlice([1], 10, 3), {items: [1], page: 0, pages: 1}); + assert.deepEqual(pageSlice([], -1, 3), {items: [], page: 0, pages: 1}); +}); diff --git a/tools/smoke-shell.py b/tools/smoke-shell.py index c6f6792..ee1d9dc 100644 --- a/tools/smoke-shell.py +++ b/tools/smoke-shell.py @@ -93,7 +93,8 @@ def record(provider, name, plan, percentages, models): (destination / 'ui-fixtures.json').write_text(json.dumps(records)) -def smoke(source, archive, destination, scale=1, text_scale=1.0): +def smoke(source, archive, destination, scale=1, text_scale=1.0, *, + width=1280, height=1024, system_fonts=False, stress=False): destination.mkdir(parents=True, exist_ok=True) prefix = destination / 'install' install(source, archive, prefix, destination) @@ -110,13 +111,21 @@ def smoke(source, archive, destination, scale=1, text_scale=1.0): 'XDG_RUNTIME_DIR': str(runtime), 'GSETTINGS_SCHEMA_DIR': str(prefix / 'share/gnome-shell/extensions' / UUID / 'schemas'), 'LIBGL_ALWAYS_SOFTWARE': '1', 'USAGEBEAM_TEST_OUTPUT': str(destination), - 'USAGEBEAM_TEST_SCALE': str(scale)} + 'USAGEBEAM_TEST_SCALE': str(scale), + 'USAGEBEAM_STRESS': '1' if stress else '0'} + # Never let inherited provider paths expose the real account to a test shell. + for name in ('CODEX_HOME', 'CLAUDE_CONFIG_DIR'): + env.pop(name, None) # Use locally installed fonts without copying or distributing licensed files. font = run(['fc-match', '-f', '%{file}', 'SF Pro Text'], os.environ, check=True).stdout font_config = destination / 'fonts.conf' + extra_fonts = '' if system_fonts else f'{escape(str(Path(font).parent))}' font_config.write_text('/etc/fonts/fonts.conf' - f'{escape(str(Path(font).parent))}') + f'{extra_fonts}') env['FONTCONFIG_FILE'] = str(font_config) + resolved_font = run(['fc-match', '-f', '%{family}', 'SF Pro Text'], env, check=True).stdout + if system_fonts and 'SF Pro' in resolved_font: + raise RuntimeError('System-font case still resolves SF Pro; font fallback was not isolated') run(['gsettings', 'set', 'org.gnome.desktop.interface', 'scaling-factor', str(scale)], env, check=True) run(['gsettings', 'set', 'org.gnome.desktop.interface', 'text-scaling-factor', str(text_scale)], env, check=True) run(['gsettings', 'set', SCHEMA, 'enabled-providers', "['codex', 'claude']"], env, check=True) @@ -129,7 +138,7 @@ def smoke(source, archive, destination, scale=1, text_scale=1.0): shell = None try: with log_path.open('w') as log: - shell = subprocess.Popen(['gnome-shell', '--headless', '--no-x11', '--virtual-monitor', f'{1280 * scale}x{1024 * scale}', + shell = subprocess.Popen(['gnome-shell', '--headless', '--no-x11', '--virtual-monitor', f'{width * scale}x{height * scale}', '--wayland-display', 'usagebeam-test'], env=env, stdout=log, stderr=log, start_new_session=True) command = ['gdbus', 'call', '--session', '--dest', 'org.gnome.Shell.Extensions', '--object-path', '/org/gnome/Shell/Extensions', '--method', 'org.gnome.Shell.Extensions.GetExtensionInfo', UUID] @@ -147,21 +156,29 @@ def smoke(source, archive, destination, scale=1, text_scale=1.0): raise RuntimeError(f'Extension did not become active: {info}') print('PASS: packaged extension loads in a private GNOME Shell session') results_path = destination / 'ui-results.json' - deadline = time.monotonic() + 25 + deadline = time.monotonic() + (90 if stress else 25) while time.monotonic() < deadline and not results_path.exists() and shell.poll() is None: time.sleep(0.2) if not results_path.exists(): raise RuntimeError(f'UI tests did not complete; inspect {log_path}') results = json.loads(results_path.read_text()) - if not results['ok']: + if not results['ok'] and not stress: raise RuntimeError(f"UI check failed: {results['error']}") - print('PASS: four positions, stable provider width, and exact calendar-gap centering') - print('PASS: opened popup, compact bars, aligned text, repeated allocation, and theme screenshots') + if results['ok']: + print('PASS: private-shell UI assertions') print(f'UI geometry and screenshots: {destination}') run(['gnome-extensions', 'disable', TEST_UUID], env, check=True) - run(['gnome-extensions', 'disable', UUID], env, check=True) - run(['gnome-extensions', 'enable', UUID], env, check=True) - print('PASS: extension disable/re-enable') + cycles = 10 if stress else 1 + for cycle in range(cycles): + run(['gnome-extensions', 'disable', UUID], env, check=True) + disabled = run(command, env, check=True).stdout + if "'state': <2.0>" not in disabled: + raise RuntimeError(f'Disable cycle {cycle + 1} did not reach INACTIVE') + run(['gnome-extensions', 'enable', UUID], env, check=True) + enabled = run(command, env, check=True).stdout + if "'state': <1.0>" not in enabled: + raise RuntimeError(f'Enable cycle {cycle + 1} did not reach ACTIVE') + print(f'PASS: {cycles} verified disable/re-enable cycles') # Leave no collector running while the private shell itself shuts down. run(['gnome-extensions', 'disable', UUID], env, check=True) finally: @@ -180,6 +197,35 @@ def smoke(source, archive, destination, scale=1, text_scale=1.0): raise RuntimeError(f'Extension emitted a GJS critical; inspect {log_path}') if 'St-WARNING' in contents or 'UsageBeam could not' in contents or 'JS ERROR' in contents: raise RuntimeError(f'UI, stylesheet, or JavaScript warning; inspect {log_path}') + results.update(resolvedFont=resolved_font, lifecycleCycles=cycles) + (destination / 'ui-results.json').write_text(json.dumps(results, indent=2)) + if not results['ok']: + raise RuntimeError(f"UI stress failures; inspect {destination / 'ui-results.json'}") + + +def matrix(source, archive, destination, selected=None): + cases = [ + ('desktop-sf', 1280, 1024, 1, 1.0, False), + ('laptop-fallback', 1366, 768, 1, 1.0, True), + ('small-fallback', 1024, 600, 1, 1.0, True), + ('large-text', 1920, 1080, 1, 1.5, True), + ('hidpi-fallback', 1920, 1080, 2, 1.0, True), + ('hidpi-large-text', 1280, 1024, 2, 1.25, False), + ] + outcomes = [] + for name, width, height, scale, text_scale, fallback in cases: + if selected and name != selected: + continue + print(f'CASE: {name} ({width}x{height} logical, scale {scale}, text {text_scale})', flush=True) + try: + smoke(source, archive, destination / name, scale, text_scale, + width=width, height=height, system_fonts=fallback, stress=True) + outcomes.append({'case': name, 'ok': True}) + except (RuntimeError, subprocess.SubprocessError) as error: + outcomes.append({'case': name, 'ok': False, 'error': str(error)}) + print(f'FAIL: {name}: {error}', flush=True) + (destination / 'matrix-results.json').write_text(json.dumps(outcomes, indent=2)) + return all(result['ok'] for result in outcomes) if __name__ == '__main__': @@ -187,9 +233,18 @@ def smoke(source, archive, destination, scale=1, text_scale=1.0): parser.add_argument('--source', type=Path, default=Path(__file__).resolve().parent.parent) parser.add_argument('--archive', type=Path, help='Test an already-built release archive instead of a Meson install') parser.add_argument('--output', type=Path) + parser.add_argument('--matrix', action='store_true', help='Run isolated portability stress cases') + parser.add_argument('--case', choices=['desktop-sf', 'laptop-fallback', 'small-fallback', + 'large-text', 'hidpi-fallback', 'hidpi-large-text'], help='Select one matrix case') parser.add_argument('--scale', type=int, choices=[1, 2], default=1, help='Private virtual-monitor scale') parser.add_argument('--text-scale', type=float, choices=[1.0, 1.25], default=1.0, help='Accessibility text scale') args = parser.parse_args() + if args.case and not args.matrix: + parser.error('--case requires --matrix') + if args.matrix: + output = args.output.resolve() if args.output else Path(tempfile.mkdtemp(prefix='usagebeam-stress-')) + raise SystemExit(0 if matrix(args.source.resolve(), args.archive.resolve() if args.archive else None, + output, args.case) else 1) smoke(args.source.resolve(), args.archive.resolve() if args.archive else None, args.output.resolve() if args.output else Path(tempfile.mkdtemp(prefix='usagebeam-shell-')), args.scale, args.text_scale) From 27934c0e00da9a5d442695def826c565b23cfe2d Mon Sep 17 00:00:00 2001 From: kcnewman Date: Tue, 8 Sep 2026 22:54:04 +0000 Subject: [PATCH 37/44] perf: keep RPC stream buffering linear --- src/services/process.js | 2 +- src/services/streams.js | 36 ++++++++++++++++++++++++------------ 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/services/process.js b/src/services/process.js index 58d55bd..c2b0203 100644 --- a/src/services/process.js +++ b/src/services/process.js @@ -95,7 +95,7 @@ export async function runCommand(spec, {input = null, timeout = 15000, cancellab local.cancel(); // Give managed collectors time to cancel and reap their own CLI child. if (!completed) { - proc.send_signal(15); + try { proc.send_signal(15); } catch { /* The child exited between callbacks. */ } let killTimer = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 300, () => { killTimer = 0; proc.force_exit(); diff --git a/src/services/streams.js b/src/services/streams.js index 1c9d115..6f90496 100644 --- a/src/services/streams.js +++ b/src/services/streams.js @@ -35,28 +35,40 @@ export async function readText(stream, maxBytes, cancellable, limitError) { // RPC lines are bounded before decoding or parsing, including unterminated lines. export async function* readLines(stream, maxBytes, cancellable, limitError) { - let tail = new Uint8Array(); + let parts = []; + let size = 0; while (true) { const bytes = await readBytes(stream, cancellable); if (!bytes.length) { - if (tail.length) + if (size) throw new Error('Incomplete RPC message'); return; } - const buffer = new Uint8Array(tail.length + bytes.length); - buffer.set(tail); - buffer.set(bytes, tail.length); let start = 0; - for (let i = 0; i < buffer.length; i++) { - if (i - start > maxBytes) - throw limitError(); - if (buffer[i] === 10) { - yield new TextDecoder('utf-8', {fatal: true}).decode(buffer.subarray(start, i)); + for (let i = 0; i < bytes.length; i++) { + if (bytes[i] === 10) { + const part = bytes.subarray(start, i); + size += part.length; + if (size > maxBytes) + throw limitError(); + const line = new Uint8Array(size); + let offset = 0; + for (const entry of [...parts, part]) { + line.set(entry, offset); + offset += entry.length; + } + yield new TextDecoder('utf-8', {fatal: true}).decode(line); + parts = []; + size = 0; start = i + 1; } } - tail = buffer.slice(start); - if (tail.length > maxBytes) + if (start < bytes.length) { + const part = bytes.slice(start); + parts.push(part); + size += part.length; + } + if (size > maxBytes) throw limitError(); } } From 42fa16a8f82168be37e3247af0e3f7280c30fc34 Mon Sep 17 00:00:00 2001 From: kcnewman Date: Tue, 8 Sep 2026 22:57:57 +0000 Subject: [PATCH 38/44] docs: record production hardening verification --- .AGENTS/plans/roadmap.md | 41 ++++++++++++++++++++++++++++++++++++++++ CHANGELOG.md | 9 +++++++++ README.md | 3 +++ docs/architecture.md | 19 +++++++++++++++++++ 4 files changed, 72 insertions(+) diff --git a/.AGENTS/plans/roadmap.md b/.AGENTS/plans/roadmap.md index 6122b08..45fcacf 100644 --- a/.AGENTS/plans/roadmap.md +++ b/.AGENTS/plans/roadmap.md @@ -77,6 +77,47 @@ Released foundations: - [ ] Publish and verify `v2.0.0-beta.1`; resolve release-blocking feedback. - [ ] Publish the first stable usage-monitoring release. +### Portability stress checkpoint — 2026-09-08 + +The initial `6e6cef4` diagnostic reproduced six release blockers. Hardened +candidate `27934c0` was then verified on GNOME Shell 50.1 using archive SHA-256 +`edef685e21b69c34c816df0325397435a95e015147222214d190a6e6348e1958` +(46,530 bytes). + +- Standard checks: 35 unit tests and the GJS collector integration suite passed. +- Six private desktops exercised 1024×600 through 1920×1080 logical sizes, + 1×/2× monitor scale, 100%/125%/150% text, SF Pro and verified Noto Sans fallback. + All 396 UI assertions passed against the exact archive. +- All 600 refresh/provider-switch/expand cycles and 60 verified disable/re-enable + cycles completed without an extension JavaScript error or Shell crash. +- Fifteen synthetic stress cases passed, including 50,000-event history, cache + rewrites, partial and oversized records, 10,000 notification observations, + concurrent subprocess/RPC work, bounded output, Unicode boundaries, and + in-flight cancellation. + +Resolved blockers: + +- [x] Constrained popup layouts paginate limits and activity before exceeding + the work area; every one of 32 quota windows and 12 model rows remains keyboard + reachable without nested scrolling. +- [x] Calendar-gap balancing tracks visible center-panel neighbors, additions, + removals, visibility, and width changes on both calendar sides. +- [x] History cache version 5 verifies complete committed prefixes, rebuilding + same-length and growing in-place rewrites instead of retaining stale totals. +- [x] Oversized complete and incomplete history lines are skipped safely while + later valid records remain available and totals remain explicitly partial. +- [x] Notification milestone state uses bounded LRU eviction and remains at or + below 200 entries across many windows and reset periods. +- [x] Subprocess and RPC limits are enforced during bounded asynchronous reads; + queued writes, pending calls, cancellation, and child reaping are also bounded. + +Reproduce with `npm run test:stress` and +`python3 tools/smoke-shell.py --archive dist/usagebeam@oo7kc.github.io-2.0.0-alpha.3-dev.0.zip --matrix`. +The optional suites do not replace `npm run check`. Test output belongs in +temporary directories, not the release archive. Real GPU drivers, mixed-monitor +hotplug, fractional scaling, non-English/RTL desktops and live provider failures +remain unverified. GNOME versions other than the advertised 50 are not covered. + ## Release gate Every milestone must satisfy all applicable gates before its checkbox is closed: diff --git a/CHANGELOG.md b/CHANGELOG.md index 75d17fc..0a4872f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,15 @@ All notable UsageBeam changes are documented here. ### Fixed +- Expanded menus adapt to smaller work areas with keyboard-accessible pagination, + preserving access to all quotas and model totals without nested scrolling. +- Calendar-gap centering now accounts for neighboring panel extensions and their + size changes. +- History totals recover from same-length and growing in-place rewrites, malformed + caches, and oversized lines followed by valid usage records. +- Subprocess and RPC output limits are enforced during reads; queued RPC writes + are bounded and cancellation reaps child processes. +- Notification period tracking stays bounded while retaining active milestones. - Calendar-side readouts anchor toward the clock on both sides, keeping reserved width outside the visible gap when provider names and countdowns change. - Each quota now shows its reset countdown below the bar as `Resets in …`, with diff --git a/README.md b/README.md index c08fcca..322a363 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,9 @@ UsageBeam displays only data reported by a provider or found in local usage records. Local activity covers this device; it is not billing data and is never converted into an account quota. +Activity expands in full when space allows. On smaller screens, compact page +controls keep every quota and model total reachable without a scrolling popup. + ## Supported providers ### Codex diff --git a/docs/architecture.md b/docs/architecture.md index ca2ed70..027a055 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -36,6 +36,18 @@ receive a scoped `PATH` without changing the Shell process environment. Refresh jobs do not overlap, resume events refresh only after wake, and results are ignored after extension disable. +Subprocess output is capped during asynchronous reads, before decoding or parsing. +RPC reads, queued writes, and pending requests are bounded independently; cancellation +reaps the managed child. Notification tracking uses a bounded least-recently-used +set of quota-period milestones and accepts current partial quota responses. + +Popup layout measures native actor heights against the monitor work area before +painting. Normal displays retain the full expanded activity view; constrained +layouts progressively paginate limits, daily/model activity, and model totals. +Page sizes remain stable during navigation, all records remain reachable, and +keyboard focus survives page changes. No nested scroll view or allocation-driven +rebuild loop is used. Calendar balancing includes other visible center-panel actors. + The provider-neutral record is currently schema version 2. A record claiming current, partial, or cached data must include a source and successful-update timestamp; ready limits must contain at least one typed quota window. Invalid or @@ -49,6 +61,13 @@ and file modes. Credentials are read only when needed and are never written by UsageBeam. Prompt/response content is ignored, while session and account identifiers used for deduplication are hashed once before persistence. +History cache version 5 stores a digest of each committed file prefix. Changed +files must match that digest before an incremental read resumes; rewrites rebuild +their derived totals. Oversized lines are skipped within the scan deadline, with +continuation state retained until their newline. Later valid records remain readable, +and incomplete totals stay explicitly marked partial. Older caches rebuild from +source without modifying the provider's files. + On the first start after the pre-alpha identity change, UsageBeam copies only recognized provider records and incremental history caches from the former directories when the corresponding destination is absent. Explicitly changed From 713bbf2ee24f92d7b951a0b724c7d986354e6c8e Mon Sep 17 00:00:00 2001 From: kcnewman Date: Tue, 8 Sep 2026 23:22:46 +0000 Subject: [PATCH 39/44] fix: prioritize short panel quota windows --- src/ui/indicator.js | 7 +++++-- src/ui/presentation.js | 16 ++++++++++++---- stylesheet.css | 5 +++++ tests/shell/extension.js | 33 ++++++++++++++++++++++++++++++++- tests/shell/stress.js | 4 +++- tests/unit/presentation.test.js | 17 +++++++++++++---- 6 files changed, 70 insertions(+), 12 deletions(-) diff --git a/src/ui/indicator.js b/src/ui/indicator.js index 047a4df..385a182 100644 --- a/src/ui/indicator.js +++ b/src/ui/indicator.js @@ -38,8 +38,11 @@ export const UsageBeamIndicator = GObject.registerClass(class UsageBeamIndicator this._panelValue = metricLabel('—', 'usagebeam-panel-value'); this._panelReset = metricLabel('—', 'usagebeam-panel-reset'); this._panelSeparator = separatorDot('usagebeam-panel-separator'); - for (const actor of [this._panelIcon, this._panelProvider, this._panelValue, - this._panelSeparator, this._panelReset]) + this._panelMetrics = new St.BoxLayout({style_class: 'usagebeam-panel-metrics', + y_align: Clutter.ActorAlign.CENTER}); + for (const actor of [this._panelValue, this._panelSeparator, this._panelReset]) + this._panelMetrics.add_child(actor); + for (const actor of [this._panelIcon, this._panelProvider, this._panelMetrics]) this._panelStatus.add_child(actor); this._panelProviderId = null; // Reserve stable panel space separately from the naturally sized readout. diff --git a/src/ui/presentation.js b/src/ui/presentation.js index f7f0724..262c583 100644 --- a/src/ui/presentation.js +++ b/src/ui/presentation.js @@ -69,10 +69,18 @@ export function quotaPresentation(window, now = Date.now()) { export function panelQuota(record, now = Date.now()) { if (!CURRENT_STATES.has(record?.limits?.status)) return null; - const candidates = record.limits.windows - .filter(window => !window.unlimited && Number.isFinite(window.usedPercent)) - .sort((left, right) => right.usedPercent - left.usedPercent); - const window = candidates[0]; + let window = null; + let shortest = Infinity; + for (const candidate of record.limits.windows) { + if (candidate.unlimited || !Number.isFinite(candidate.usedPercent)) + continue; + const duration = Number.isFinite(candidate.durationMinutes) && + candidate.durationMinutes > 0 ? candidate.durationMinutes : Infinity; + if (!window || duration < shortest) { + window = candidate; + shortest = duration; + } + } if (!window) return null; return { diff --git a/stylesheet.css b/stylesheet.css index 0017600..f4bfb90 100644 --- a/stylesheet.css +++ b/stylesheet.css @@ -46,6 +46,11 @@ spacing: 4px; } +.usagebeam-panel-metrics { + spacing: 4px; + padding-top: 2px; +} + .usagebeam-panel-icon-slot { width: 16px; } .usagebeam-provider-icon { diff --git a/tests/shell/extension.js b/tests/shell/extension.js index a372ea8..1ff6e98 100644 --- a/tests/shell/extension.js +++ b/tests/shell/extension.js @@ -1,6 +1,7 @@ // Loaded only by tools/smoke-shell.py in its isolated, synthetic desktop. import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; +import Pango from 'gi://Pango'; import Shell from 'gi://Shell'; import St from 'gi://St'; import * as Main from 'resource:///org/gnome/shell/ui/main.js'; @@ -17,6 +18,8 @@ const bounds = actor => { }; // Clutter reports logical sizes; Pango pixel sizes can include monitor scale. const textWidth = actor => actor.clutter_text.get_preferred_width(-1)[1]; +const textBaseline = actor => bounds(actor.clutter_text).y + + actor.clutter_text.get_layout().get_baseline() / Pango.SCALE; const assert = (condition, message) => { if (!condition) throw new Error(message); @@ -80,6 +83,12 @@ export default class UsageBeamUITest extends Extension { const value = bounds(indicator._panelValue); const separator = bounds(indicator._panelSeparator); const reset = bounds(indicator._panelReset); + const providerBaseline = textBaseline(indicator._panelProvider); + for (const actor of [indicator._panelValue, indicator._panelReset]) { + const offset = textBaseline(actor) - providerBaseline; + assert(Math.abs(offset) <= 1, + `${position}: panel text baseline differs by ${offset}px`); + } assert(value.x - provider.x - provider.width <= 6 * scale, `${position}: provider and percentage are spaced too far apart`); assert(Math.abs(separator.x - value.x - value.width - @@ -146,6 +155,7 @@ export default class UsageBeamUITest extends Extension { if (GLib.getenv('USAGEBEAM_STRESS') === '1') return runStress({indicator, records, settings, calendar, wait: milliseconds => this._wait(milliseconds), screenshot: name => this._screenshot(name), + checkPanel: position => this._checkPanelSpacing(indicator, calendar, position, textScale), monitorScale, textScale}); for (const position of positions) { settings.set_string('panel-position', position); @@ -197,6 +207,27 @@ export default class UsageBeamUITest extends Extension { await this._screenshot(position); placement.push({position, indicator: before, calendar: clockBefore, visualGaps: gaps}); } + for (const provider of ['codex', 'claude']) { + const windows = records[provider].limits.windows; + const shortest = windows.reduce((selected, window) => + window.durationMinutes < selected.durationMinutes ? window : selected); + const longer = windows.find(window => window.durationMinutes > shortest.durationMinutes); + const original = [shortest.usedPercent, longer.usedPercent]; + settings.set_string('default-provider', provider); + shortest.usedPercent = 0; + longer.usedPercent = 100; + indicator.render(); + await this._wait(); + assert(indicator._panelValue.text === '0%', + `${provider}: panel did not prefer the freshly reset short window`); + shortest.usedPercent = 100; + longer.usedPercent = 0; + indicator.render(); + await this._wait(); + assert(indicator._panelValue.text === '100%', + `${provider}: panel dropped the exhausted short window`); + [shortest.usedPercent, longer.usedPercent] = original; + } settings.set_string('default-provider', 'codex'); indicator.menu.open(0); await this._wait(); @@ -270,7 +301,7 @@ export default class UsageBeamUITest extends Extension { `${severity}: meter does not expose quota severity`); } assert(indicator._panelValue.has_style_class_name('usagebeam-danger'), - 'Panel indicator does not expose the highest quota severity'); + 'Panel indicator does not expose the shortest quota severity'); const modelContent = matching(content, 'usagebeam-model-content')[0]; const [modelName, modelTotal] = modelContent.get_children(); assert(modelTotal.get_theme_node().get_font().get_size() < diff --git a/tests/shell/stress.js b/tests/shell/stress.js index 1ebfab2..f238a8e 100644 --- a/tests/shell/stress.js +++ b/tests/shell/stress.js @@ -11,7 +11,8 @@ const bounds = actor => { }; const copy = value => JSON.parse(JSON.stringify(value)); -export async function runStress({indicator, records, settings, calendar, wait, screenshot, monitorScale, textScale}) { +export async function runStress({indicator, records, settings, calendar, wait, screenshot, + checkPanel, monitorScale, textScale}) { const checks = []; const geometry = []; const check = (name, ok, details = {}) => checks.push({name, ok: Boolean(ok), ...details}); @@ -43,6 +44,7 @@ export async function runStress({indicator, records, settings, calendar, wait, s Math.abs(bounds(indicator.container).width - slot.width) <= 1); check(`${position}/${provider}: panel labels fit`, [indicator._panelProvider, indicator._panelValue, indicator._panelReset].every(textFits)); + checkPanel(position); } if (position.includes('calendar')) { const panel = bounds(Main.panel); diff --git a/tests/unit/presentation.test.js b/tests/unit/presentation.test.js index a2b3c13..23d187e 100644 --- a/tests/unit/presentation.test.js +++ b/tests/unit/presentation.test.js @@ -67,14 +67,23 @@ test('notification milestones follow quota semantics without duplicate levels', assert.deepEqual(notificationMilestones(NaN), [90, 100]); }); -test('panel quota reports only the highest current quota window', () => { +test('panel quota prefers the shortest current quota window at every usage level', () => { const now = 1_000_000; const record = {limits: {status: 'ready', windows: [ - {usedPercent: 15, resetsAt: now + 3600000}, - {usedPercent: 46, resetsAt: now + 15 * 3600000 + 59 * 60000}, + {id: 'weekly', usedPercent: 96, durationMinutes: 10080, + resetsAt: now + 15 * 3600000 + 59 * 60000}, + {id: 'five-hour', usedPercent: 15, durationMinutes: 300, + resetsAt: now + 3600000}, {usedPercent: null, unlimited: true}, ]}}; - assert.deepEqual(panelQuota(record, now), {percent: 46, reset: '15h 59m'}); + assert.deepEqual(panelQuota(record, now), {percent: 15, reset: '1h 0m'}); + record.limits.windows[1].usedPercent = 100; + assert.deepEqual(panelQuota(record, now), {percent: 100, reset: '1h 0m'}); + record.limits.windows = [ + {id: 'provider-first', usedPercent: 20}, + {id: 'provider-second', usedPercent: 90}, + ]; + assert.deepEqual(panelQuota(record, now), {percent: 20, reset: null}); assert.equal(panelQuota({limits: {...record.limits, status: 'stale'}}, now), null); assert.equal(panelQuota(null, now), null); }); From ca25dc8ec39c751d19021a59c24f1885ea23c35d Mon Sep 17 00:00:00 2001 From: kcnewman Date: Tue, 8 Sep 2026 23:23:28 +0000 Subject: [PATCH 40/44] docs: focus repository on product guidance --- .AGENTS/README.md | 14 --- .AGENTS/plans/roadmap.md | 144 --------------------------- .AGENTS/references/claude-usage.jpeg | Bin 47094 -> 0 bytes .AGENTS/references/codex-usage.png | Bin 82644 -> 0 bytes .AGENTS/rules/architecture.md | 27 ----- .AGENTS/rules/quality.md | 24 ----- .AGENTS/rules/releases.md | 22 ---- .gitignore | 6 ++ AGENTS.md | 4 - CHANGELOG.md | 44 ++++---- README.md | 23 ++++- docs/architecture.md | 87 ---------------- docs/providers/claude.md | 58 +++++------ docs/providers/codex.md | 57 +++++------ tools/check.js | 15 +-- 15 files changed, 103 insertions(+), 422 deletions(-) delete mode 100644 .AGENTS/README.md delete mode 100644 .AGENTS/plans/roadmap.md delete mode 100644 .AGENTS/references/claude-usage.jpeg delete mode 100644 .AGENTS/references/codex-usage.png delete mode 100644 .AGENTS/rules/architecture.md delete mode 100644 .AGENTS/rules/quality.md delete mode 100644 .AGENTS/rules/releases.md delete mode 100644 AGENTS.md delete mode 100644 docs/architecture.md diff --git a/.AGENTS/README.md b/.AGENTS/README.md deleted file mode 100644 index f5c8da4..0000000 --- a/.AGENTS/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# Agent workspace - -This directory contains the repository's durable agent guidance, active roadmap, -and visual product references. The root `AGENTS.md` is the discovery entrypoint. - -Read these rules before changing the repository: - -1. [Product and architecture](rules/architecture.md) -2. [Quality, privacy, and verification](rules/quality.md) -3. [Version control and releases](rules/releases.md) - -Use [plans/roadmap.md](plans/roadmap.md) for current product scope and milestone -gates. Images under [references/](references/) inform the visual language but are -never runtime or release assets. diff --git a/.AGENTS/plans/roadmap.md b/.AGENTS/plans/roadmap.md deleted file mode 100644 index 45fcacf..0000000 --- a/.AGENTS/plans/roadmap.md +++ /dev/null @@ -1,144 +0,0 @@ -# UsageBeam product roadmap - -## Product contract - -UsageBeam is a native GNOME Shell monitor for AI coding usage. It reports only -provider-backed account limits and derived local activity, keeps those scopes -visibly separate, and remains useful when one source is temporarily unavailable. - -The stable product identity is: - -- Name: UsageBeam -- Extension UUID: `usagebeam@oo7kc.github.io` -- Settings schema: `org.gnome.shell.extensions.usagebeam` -- Repository owner: `oo7kc` - -Cost, billing, budgets, and a new icon remain outside the usage-monitoring -release. A provider adapter does not ship until its authentication, units, -unsupported states, and failure behavior have been validated. - -## Current product surface - -- [x] Codex account limits through its installed CLI app-server. -- [x] Codex seven-day local token and model activity. -- [x] Claude Code supported account limits through its saved OAuth sign-in. -- [x] Claude Code seven-day local token and model activity. -- [x] Independent live, local, cached, syncing, and setup states. -- [x] Compact active-provider indicator and naturally expanding detail view. -- [x] Fixed left/right areas plus exact left/right calendar placement. -- [x] Theme-derived light/dark surfaces, accessible controls, and quota severity. -- [x] Private, bounded, cancellable collection with strict record validation. -- [x] Deterministic packaging and isolated GNOME lifecycle coverage. - -Released foundations: - -- [`v2.0.0-alpha.1`](https://github.com/oo7kc/freeby/releases/tag/v2.0.0-alpha.1): - Codex and the provider-neutral platform. -- [`v2.0.0-alpha.2`](https://github.com/oo7kc/freeby/releases/tag/v2.0.0-alpha.2): - Claude Code and provider switching. - -## Milestone 3 — Cursor - -- [ ] Revalidate authentication and usage sources on current free and paid - accounts. -- [ ] Preserve reported units and distinguish paid allowance, bonus usage, - missing subscription, unknown values, and exhaustion. -- [ ] Add only verified capabilities to the shared contract and interface. -- [ ] Cover empty payloads, expired authentication, unavailable endpoints, - changed upstream shapes, and quota exhaustion. -- [ ] Complete the release gate and publish `v2.0.0-alpha.3`. - -## Milestone 4 — GitHub Copilot - -- [ ] Revalidate authentication, entitlement data, and usage units for supported - account types. -- [ ] Keep request counts, tokens, allowances, unknown values, and unlimited - states distinct. -- [ ] Implement only verified limit and activity capabilities. -- [ ] Verify all supported providers together under partial outages and missing - integrations. -- [ ] Complete the release gate and publish `v2.0.0-alpha.4`. - -## Milestone 5 — Preferences and diagnostics - -- [ ] Add provider enablement, order, and default-provider controls. -- [ ] Add connection diagnostics and actionable recovery guidance. -- [ ] Add notification threshold, history retention, and clear-data controls. -- [ ] Preserve existing settings through schema changes and document defaults. -- [ ] Complete keyboard, screen-reader, scaling, long-content, and theme QA. -- [ ] Complete the release gate and publish `v2.0.0-alpha.5`. - -## Stabilization - -- [ ] Validate every advertised GNOME Shell version. -- [ ] Verify activity accuracy, bounded collection, responsiveness, migration, - clean install, upgrade, disable/re-enable, uninstall, and downgrade. -- [ ] Complete provider compatibility notes and product screenshots. -- [ ] Publish and verify `v2.0.0-beta.1`; resolve release-blocking feedback. -- [ ] Publish the first stable usage-monitoring release. - -### Portability stress checkpoint — 2026-09-08 - -The initial `6e6cef4` diagnostic reproduced six release blockers. Hardened -candidate `27934c0` was then verified on GNOME Shell 50.1 using archive SHA-256 -`edef685e21b69c34c816df0325397435a95e015147222214d190a6e6348e1958` -(46,530 bytes). - -- Standard checks: 35 unit tests and the GJS collector integration suite passed. -- Six private desktops exercised 1024×600 through 1920×1080 logical sizes, - 1×/2× monitor scale, 100%/125%/150% text, SF Pro and verified Noto Sans fallback. - All 396 UI assertions passed against the exact archive. -- All 600 refresh/provider-switch/expand cycles and 60 verified disable/re-enable - cycles completed without an extension JavaScript error or Shell crash. -- Fifteen synthetic stress cases passed, including 50,000-event history, cache - rewrites, partial and oversized records, 10,000 notification observations, - concurrent subprocess/RPC work, bounded output, Unicode boundaries, and - in-flight cancellation. - -Resolved blockers: - -- [x] Constrained popup layouts paginate limits and activity before exceeding - the work area; every one of 32 quota windows and 12 model rows remains keyboard - reachable without nested scrolling. -- [x] Calendar-gap balancing tracks visible center-panel neighbors, additions, - removals, visibility, and width changes on both calendar sides. -- [x] History cache version 5 verifies complete committed prefixes, rebuilding - same-length and growing in-place rewrites instead of retaining stale totals. -- [x] Oversized complete and incomplete history lines are skipped safely while - later valid records remain available and totals remain explicitly partial. -- [x] Notification milestone state uses bounded LRU eviction and remains at or - below 200 entries across many windows and reset periods. -- [x] Subprocess and RPC limits are enforced during bounded asynchronous reads; - queued writes, pending calls, cancellation, and child reaping are also bounded. - -Reproduce with `npm run test:stress` and -`python3 tools/smoke-shell.py --archive dist/usagebeam@oo7kc.github.io-2.0.0-alpha.3-dev.0.zip --matrix`. -The optional suites do not replace `npm run check`. Test output belongs in -temporary directories, not the release archive. Real GPU drivers, mixed-monitor -hotplug, fractional scaling, non-English/RTL desktops and live provider failures -remain unverified. GNOME versions other than the advertised 50 are not covered. - -## Release gate - -Every milestone must satisfy all applicable gates before its checkbox is closed: - -1. Strict syntax, import-boundary, schema, repository-layout, unit, and GJS - integration checks. -2. Deterministic packaging from the exact candidate revision. -3. Installation and disable/re-enable testing of that exact archive in an - isolated GNOME Shell session. -4. Live provider verification that records the tested client version and account - scope; unavailable live access remains an explicit limitation. -5. A conventional commit, green remote CI, annotated prerelease tag, downloadable - archive, checksum, upgrade notes, and downgrade path. - -Synthetic tests must remain network-free and contain no credentials, prompts, -responses, raw transcripts, account identities, or raw session identifiers. - -## After usage monitoring - -- Create a distinctive product icon without changing the established UUID. -- Design cost breakdowns separately, distinguishing provider-reported charges - from estimates and retaining currency, period, and pricing provenance. -- Scope budgets, multiple accounts, cross-device history, and a larger dashboard - only after the usage-monitoring release is reliable. diff --git a/.AGENTS/references/claude-usage.jpeg b/.AGENTS/references/claude-usage.jpeg deleted file mode 100644 index 65a16234f2a3f74ade20b31e8074f2c5994b6cbc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 47094 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 SiRkxHS2x>HarGtofB)HTqjJdr diff --git a/.AGENTS/references/codex-usage.png b/.AGENTS/references/codex-usage.png deleted file mode 100644 index 0aa5ef8125f269345623087741327c4fbf59025d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 82644 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⋙H2(rkB@#bx^yiacJ(rMX{Gag^n^UyS|p} z-cUpURZ-cvFbK&(=7dnM3J=3z@(Av6^|bK1y6m8O_LWk8uqALgDOB3ipWj@4e0Aas zsL-^;{Jkf}RUpb6W|*<^EnofWPWO|l20(cE?A3SAzAL3zv|4C# z>GBA)^wvO^li-89b!>aT9EpICuUu#mTll5SDt<|J7n0HMh}V=pK&2^DTEZhZZ4 z`tDdOORC;Rh4hw<*_@|@E~8^J3A#Qz43EoT)t6Azf`NygAYd(?e&$~`h{$qNRza6U zQ4+*xO!Yn(b1g&G|8DF@7=xc~hx)V6o(!c%A5fhof+TY+ISokk$6FjmQgr1B`x-4> zXyAC#19bh>>*;S^-W}^G8ssfC32kMFrnuLF>s;I~jB&mo5yMD(uOIo?95EV7n+Q2Z z^Fj5g*>yqj>PldME%hOo91W-|&L~|>_gxU1v>Hdq;^e__|Jy5a+aj$l~qvq*>hk?@9^F0xU^Qe2C$xA;Q0ERWE zwgXTBn0!~FtM1ueb_TjIpW9g3UP(%`N8){^^L0?GsqOc$dw%RARou0+mivE=L`oHd(g|z-yFZ6298dbdnPW% zhI+I}c^_wT@$v!95Q^1`ujkVb!bF)Os4o$mD7Z%*@f52uO6rLq(t0LUB&q(cjx@dFGj^ZfDQ@MJ7wbw;^& z6H=b+fzC|E?1Hmo%|viEW8N{}OMl>OiP|(*yZRoPxcXCgq>-;!pFA!<`smS)0*3{V z2hSeBvvb#VFpMb^g=pN1zy}BrlTKCpUp#;F#Sc}{S(ad!Q!Br&k5-bwx>#RNb4`(R z+nmG9L5hs5h7V;dJkyyxxI$0G+cJz^XUrmDiP$03EaZQM>v6BPIq5F9PgdZ;&2YB2EtBUM@840js`HqlO2nH z)m{JfeEPm;n((D6#t`MJO)@Rg#hCX^Q93&}FlCTWko)1=GapV3<5k`&yWw^yT3>1=54~^s z&(2t!Mu^aL8LoEw>axtOiBX&Q-XDHsZDL293cQ>DUfK497B?`!yUHrS|qgu zT9eoHemOOOSK|=IPXl8^d0Lj6tCh#0`-;qOFD$6=P_I6^ee&~bT8T7lrVOo36Sm5Vk4bsb}cdF@bP*cDOT7 z4hRgczqq-&-bZ0{#7;Cv5U5+R1j*LBYUv(RY0GY^LX6s!SJU+T;pmx;EU_$kzGUq& z$PC5hpE7aW>&I7zkB36f6lI5-7OBUKLgkNfpP)cT&TolLrnS5H!7Ef|1v?>KxxN={ zr>L($?;hDzIpyT^@yEBHT#aUM)C50s120Pl(SitUgqH9cN-bIhFXG+o&7J<^^EWRi z80jb$t%0qwIm++IZK&k~^uzwHcw8&6i~&l_Yb4r80VY6EN8kWHGF}tX?1n)ktx68J zXvZQ^vb-=7>A&RFJ0vVFA53I~aRR!{!4=^8UX}U#X_=HobV_VaSp5D7`|bSj<-^0< z8I=YTPz?;sy*aIC`fKe=X~R15ke(C`uAM~&tPr8H zHX@_KSXLHGNpQD0BMa?75iTpI)9fh4=n3USa40SfU1al`MUu0Lv6Q!O@2Gvg^aTK@7E8EBWQltA zw#y=}cf&5aODJo_*l>n0ztbfUk#2!!vL`L8FhL2$)Y^i(z>p@X;#~&R`6SIzV;|h$ z⪙>XugZ*F1CbL#yE<*kApuNchO6o41+Hq^Ejy9670cM+t7uveEOb>@>HdOs+9G(^vJZ^QOG&EVX%&5tPrkjGR zyq5Nseo;MD6~0xWh>9|SoD*RKf2wUqg~arPLO6n z%inCCS)P^lLQikeP*mJ!*+9{~TCoYkS1<2=`D%J_(#!?4(sp_~CyS^{iNxjci|}kt zd_V7PDTQ=4WMN@|q}MdzL1U5Xf&94erp};|j5Iby&2n=zC`5?_qJ>0juaP&cnKEUi z9ez2ohZ&+KL9MX~qy>D!%ILYotX8Hr*GM;_TZWg&<&*$Sz_a=Eo8!aV$(_Y1TN)9o zC@=Qzavf{c((il~Tk2nliy7KRqeF=?u%4#csHSbN<#U1$ohkdw!}1y|CKhRt>YLpx zAgY6V0f08EFWm`IuD7RZHY)D?X{%EjH?K8ow`JnX!$6_VQ$XVvYkp=YoT=x6(KHOk zh6Ch@NMr!*H)&ft`;$Un#I*iLua7h9&Q@56t2eh0Ssg@ z^8=ylKe{NPL~byww&qom3g|w&SFRqr0v#^M`g;V?tkP_*JL9ccX?0(KU=0&#oR`5k zoy%{t#Z$e9vQhQ^(-`h!HDoI6y zyIBs)E0tc%r)SbE8oa7x)IB(aa#&>4s$moA4NelxjwqS4*zHF*HW(sQ9TliX!RuVV z{?*sV`y&J^JrhX_(C{I~YL@r}A7m~W=bznN{p7IYT+K-L4|UXI_iUOd_ja3tku$w! zc4PT&o}WFOfHGtoeYU&(fM$c1mU@)pHyu!5o{u+F6CkVLjJ|w-D}5OtDDgn<(^#D;r&6Sb?R z)9K@P!>zGchkKeny7;;bboYqvyNd($py9s3t8AB_{qALrvpZ7PyMa&?!uG%VDp@*G z?3DQnQnXs|?55Z?3{fIas(l7=G7Nyvb)M9EPLngL6*Kbw)&0v~f6r;86~q;Mu|+UjnxrSFo;~Bk z%xcfGT2V&VOtY7>Ubq~Cp`MD<>B?5UU;q!jfU`e2AkTKh!Xhw&1=4Tf}cGk|JPGO!U;P` zujl!T)BN)E;4~Rg3iGbp{7`@2T5Cp*X|#kqv)L9)=FF72pvC@ZiGlr3-Z4S|OON7i z&9xtQrNF01dFp7!&4!`g@l7*HdauuRQo=3C4I%p@qMG|wk^?M;DK6JIR%cu5mY3JV|@ zX$M`E;d(UpXiVQE7tYRe`Y&Q!l2l45rNo;+igc+o@M*=00MfM;obaenB&r6OzyLafBGj+uRhrc?#_Y6#3C=Hb1ZUz4fO}S{>2ZPj3yQL zs9($)stfZXEN(hvK(&^0-~zn51iW!QJJp#Y=gDZ*Dw$?O>|AD)w4@<$wb)Id3vRl8 za=kkYc}*xRvb+j8&(pVGzxe*e+i2U-A`fc?c0N%cB3>6#P^Y<=|H5yTl{>fqY z*>3PTi_C;8H&hvF>=S*$c$;AhXrN_C0a>;$9yAMXg=_j8&y1gz$ zo#lfSYY62cRhqTbhusIi^kyk)fT$!^%>&9k_P_l0?dPxWDtDvt=(V)s>8Pn&kGU^P zn?lNO;9ZViT=Df^)J4o{)&p2-eOz!$npd+zmf2?;5Uqa5Siop{t*rGtfBupOw6>BP z?6eF#VFJHo+?B1^m+oB~s2kaezh=xzC)g{WzkWD=J>8p+UdCp~0j3nMOUrcc7F&Px zYTYiA;Lvm;ejlB=0V{JYJW@dmP#ix>M=JxYnpI{}cHH!LE{qV(bI@;11XWuDBhd=V zL-i;xtqF6t;U)$P`Pl>v=9&pJ8%P|F55^+5DAib)j3usE_QvK)eWe|+FNKPWa7w$* zx7`A~UWHHto|jP6idSzRkQtTLXxR>c5k(f5U0O*vt%wyhvUHvvcE#(2!rH=_&5&GK zj&U+WoQA-O#>@SJx7Y#~i!exXX&i=GK9F^uTLE$0LecAj^S^8TipUN!Gs#@lK=<3x zcEcda%-TY>3>A{S{|-EZEN1RL+RnmUjHfTL-Zf4&xKwL+sFfgOhzwSt6zqqgNUj!` zGg);7YP0|!WQu$1u9HF$njF={YW49?pFH`|BXO#b(Y$q^ur_{S28XmBAEQhngLUo>Lt$anME>cx z|M6j{K3QCPnf-U_s;!5-n6)Y^hg_^Oz%7iN>JVV8@vWOnHcM_)D(vOz z90d9az|F>K`q9<+c-(Pvn5wLF@#F+p(-Q(RJ_^fQKg^8*!NUk-KN+;_v+h$a(rl#w z6!noIqdn?L*e*GU6*fZ!S>a}7U*?~E_R(&HR6!25>ZJu_b9}*!>-||t$nLZm3_JOx zu~I0b)zjN*|IgR2UY%&RD@2ucEHzELG^kcKcT;9iE@X!>*HcA{rZ?5whANP~wA83* zbXA0>)K%dKQ7*G!W@av!v0J*v$h~Ywa%J3eanG8)9Rst>m|k4k5N-=n+)e~iExcnZ zuq|kyy&;a~VJNi{UV-nY>5J3!?DTLd1GTT_=uHfd>Ro8t9*b>Yc$-t(%XGUnte(O+E$-S6Oe<#fnzpt1F7E=}E0K6({!#>VH-)^G zbQT7XT0_YTV?5m;4Hj#gVizmCsPh%i>lS8rl8^SgVvJJyl@M)8EqhJ}!!^?zg#74s za_2BCgF(YO$hD=9b`Z!S_|3pWDUqFIS?SPk7K$ z?K_1gVdGzGzJl54oZvM581}Q98`cvFQQZkO&6+8|Ac4}3bTDI8UM5>5d5sjP8BUZh z9`4_qYT@8$N~Ae2%;5I_SJ$=dL=3~gnWVdwKp-tb;{Sh(J3@#9;fzwTbr2lfbvd95#IlgO?|83 zbvO`pm=OS=H4#Du?{D?=c$%=}-vFl&wr4VTIfY1sri8OkOXIm5s#FQH_IlRy+vmj! zOYKw-at&SJH9Ti=`zel=7ar+xE>I9??pi-S8Xe0n6`~VXcHUg65S~~y$002ovPDHLk FV1j}4YPbLZ diff --git a/.AGENTS/rules/architecture.md b/.AGENTS/rules/architecture.md deleted file mode 100644 index 084830d..0000000 --- a/.AGENTS/rules/architecture.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -description: Product scope and architecture invariants for UsageBeam -alwaysApply: true ---- - -# Product and architecture - -- Build a reliable native GNOME usage monitor for Codex, Claude Code, Cursor, - and Copilot across free and paid plans. -- Keep the finalized UsageBeam identity stable. Finish usage monitoring before - cost, billing, budgets, or icon work. -- Keep GNOME entry points thin. Separate provider adapters, usage contracts, - services, UI actors, and preferences. -- Prefer GJS, Gio/GLib, Soup, and native GNOME APIs. Never download runtime - dependencies or invoke unpinned package runners. -- Keep expensive history scans in bounded, cancellable collector subprocesses. - Prevent overlapping jobs and ignore results after disable. -- Keep account-wide limits separate from local activity. Never derive quota or - subscription percentages from token totals. -- Store metrics, units, periods, source scope, capabilities, and freshness as - typed fields. Do not infer state from display strings. -- Treat unknown, unsupported, stale, unauthenticated, unlimited, and exhausted - as distinct states. Empty data is not zero usage. -- Preserve provider-specific token/cache semantics, deduplicate repeated events, - and handle cumulative counters, partial writes, rotation, and local dates. -- Adapt `.AGENTS/references/` to GNOME themes, keyboard access, screen readers, - work-area limits, and display scaling; do not copy another panel literally. diff --git a/.AGENTS/rules/quality.md b/.AGENTS/rules/quality.md deleted file mode 100644 index 1347a5c..0000000 --- a/.AGENTS/rules/quality.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -description: Verification and privacy requirements for every change -alwaysApply: true ---- - -# Quality, privacy, and verification - -- Add focused regression coverage for behavior changes. Keep automated fixtures - synthetic or sanitized and network-free. -- Run `npm run check` for JavaScript, schema, unit, and GJS integration checks. - Run `npm run pack` when packaging inputs or release metadata change. -- Use `python3 tools/smoke-shell.py` for GNOME lifecycle changes when a compatible - shell is available. Test the exact archive before release. -- Validate provider interfaces against current first-party documentation, - installed client versions, sanitized fixtures, and live accounts when safely - available. Record unavailable live checks honestly. -- Verify lifecycle, wake handling, accessibility, packaging, and every advertised - GNOME version before release. Do not claim checks that were not run. -- Keep credentials, prompts, responses, raw transcripts, account identities, and - raw session IDs out of logs, fixtures, caches, commits, and release archives. -- Store derived usage metadata only in private XDG state/cache locations. Hash - identifiers used solely for deduplication or account-change detection. -- Preserve unrelated worktree changes and user-owned assets. Stage explicit paths - and inspect the staged diff before committing. diff --git a/.AGENTS/rules/releases.md b/.AGENTS/rules/releases.md deleted file mode 100644 index 26213eb..0000000 --- a/.AGENTS/rules/releases.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -description: Branch, commit, tag, and prerelease workflow -alwaysApply: true ---- - -# Version control and releases - -- Develop on `dev`; reserve `main` for the later stable migration. -- Follow `.AGENTS/plans/roadmap.md` in order and keep its checkboxes and evidence - current. Planning setup alone is not a product milestone. -- Use conventional commits and separate implementation from release-documentation - checkpoints when that improves reviewability. -- A milestone requires applicable tests, strict schema validation, deterministic - packaging, an isolated install, and lifecycle checks for UI changes. -- Push the candidate to `dev` and require green remote CI before tagging. -- Create an annotated semantic prerelease tag from the exact tested commit. -- Publish the CI-built archive as a GitHub prerelease with install/upgrade, - verification, known limitations, and downgrade instructions. -- Verify the remote tag target, prerelease/draft status, downloadable asset, and - checksum. Record the commit, URL, size, and SHA-256 in the roadmap. -- Never mark a milestone complete while a required check or publication is - blocked. Keep stable unreleased until all provider and stabilization gates pass. diff --git a/.gitignore b/.gitignore index 41023e5..007f19f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,14 @@ /build/ /dist/ /.cache/ +/.ruff_cache/ meson-private/ +# Local agent workspace and visual research are not product source. +/.AGENTS/ +/AGENTS.md +/reference-images/ + __pycache__/ *.py[cod] *.swp diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 279670c..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,4 +0,0 @@ -# Agent entrypoint - -Before changing this repository, read and follow [.AGENTS/README.md](.AGENTS/README.md). -The active implementation roadmap is [.AGENTS/plans/roadmap.md](.AGENTS/plans/roadmap.md). diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a4872f..7059ae0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ All notable UsageBeam changes are documented here. - UsageBeam product identity with the permanent extension UUID `usagebeam@oo7kc.github.io` and settings schema `org.gnome.shell.extensions.usagebeam`. -- Active-provider panel readout with provider icon, highest current quota, and +- Active-provider panel readout with provider icon, shortest current quota, and reset countdown. - Left area, right area, left-of-calendar, and right-of-calendar placement. - Expandable seven-day activity chart and compact per-model token totals. @@ -21,11 +21,9 @@ All notable UsageBeam changes are documented here. - Rebuilt the popup as a compact, accent-aware GNOME surface with SF Pro typography when available, aligned quota metrics, and stable panel geometry. -- Account limits, local activity, and cached results now expose their scope and - freshness independently. +- Account limits, local activity, and saved results now remain available + independently when one source cannot refresh. - Codex discovery supports common user-local and Node version-manager layouts. -- Provider records use a strict versioned contract with bounded collection, - typed quota states, source attribution, and private derived storage. - Recognized Freeby alpha settings and derived data migrate without copying credentials or overwriting existing UsageBeam data. @@ -35,11 +33,9 @@ All notable UsageBeam changes are documented here. preserving access to all quotas and model totals without nested scrolling. - Calendar-gap centering now accounts for neighboring panel extensions and their size changes. -- History totals recover from same-length and growing in-place rewrites, malformed - caches, and oversized lines followed by valid usage records. -- Subprocess and RPC output limits are enforced during reads; queued RPC writes - are bounded and cancellation reaps child processes. -- Notification period tracking stays bounded while retaining active milestones. +- Local activity totals recover when provider history files are rewritten or + contain an unreadable record. +- Large or malformed provider responses no longer interrupt future refreshes. - Calendar-side readouts anchor toward the clock on both sides, keeping reserved width outside the visible gap when provider names and countdowns change. - Each quota now shows its reset countdown below the bar as `Resets in …`, with @@ -50,6 +46,9 @@ All notable UsageBeam changes are documented here. - Reset values, percentages, and separator dots retain consistent alignment. - The active-provider readout uses compact internal and calendar-side spacing, while retaining a fixed width across provider changes. +- Panel usage metrics share the provider label's visual baseline. +- The panel always prefers the shortest available quota period, even at 0% or + 100%, instead of switching to a longer window with higher usage. - Seven-day activity emphasizes its local/account scope without repeating a date range already represented by the chart. - The activity disclosure now shows only its title and chevron; period, totals, @@ -84,32 +83,27 @@ All notable UsageBeam changes are documented here. - Expired cached quota windows are discarded after their reset time. - Missing, absent, and expired Claude authentication remain distinct states. -### Verification scope +### Compatibility -- Claude Code 2.1.218 installation detection and missing-authentication behavior - were exercised locally. -- Quota payloads, expired authentication, duplicate messages, and cache-token - semantics were verified with deterministic fixtures. -- A signed-in Claude account was not available for a live quota probe. +- Tested with Claude Code 2.1.218. +- Supports standard and model-scoped limits, expired sign-ins, duplicate local + activity records, and separate cache-token categories. +- Live account-limit validation was not available for this prerelease. ## 2.0.0-alpha.1 - 2026-09-06 ### Added -- Provider-neutral usage contract with explicit capabilities, source scope, - freshness, quota state, reset timestamp, and token categories. -- Bounded Codex app-server collection for account rate limits. +- Codex account limits with distinct quota periods and reset times. - Incremental local Codex history with seven-day and model aggregates. -- Private XDG state/cache persistence, stale-data recovery, and refresh backoff. -- Deterministic packaging and isolated GNOME lifecycle verification. +- Private local storage and temporary saved-value recovery during refresh + failures. ### Fixed - Refresh occurs after resume instead of before suspend. -- Provider jobs do not overlap or update a destroyed interface. -- Notifications require a verified threshold crossing and deduplicate per - account, quota window, and reset period. -- Runtime package downloads and unbounded collection processes were removed. +- Repeated refreshes no longer overlap. +- Notifications appear once for each reached milestone and quota period. ## 1.0.2 - 2026-04-27 diff --git a/README.md b/README.md index 322a363..41eed12 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # UsageBeam UsageBeam puts Codex and Claude Code usage where it is easiest to see: in the -GNOME top panel. The compact indicator shows the active provider's highest quota +GNOME top panel. The compact indicator shows the active provider's shortest quota and reset countdown; its popup reveals every reported limit plus seven days of local token and model activity. @@ -30,6 +30,20 @@ converted into an account quota. Activity expands in full when space allows. On smaller screens, compact page controls keep every quota and model total reachable without a scrolling popup. +## Screenshots + +### Panel indicator + +_Screenshot placeholder — compact provider usage beside the calendar._ + +### Usage details + +_Screenshot placeholder — account limits and seven-day local activity._ + +### Preferences + +_Screenshot placeholder — provider, placement, refresh, and notification settings._ + ## Supported providers ### Codex @@ -84,7 +98,9 @@ gnome-extensions prefs usagebeam@oo7kc.github.io Preferences control panel placement, refresh frequency, and quota notifications. Calendar-side placement keeps the gap between the indicator and clock on the panel midpoint. The indicator reserves a stable width so switching providers or -loading new quota data does not move the calendar. +loading new quota data does not move the calendar. When several limits are +available, the panel shows the shortest reported window; the popup continues to +show every reported limit. ## Privacy and storage @@ -121,5 +137,4 @@ gnome-extensions disable usagebeam@oo7kc.github.io gnome-extensions uninstall usagebeam@oo7kc.github.io ``` -UsageBeam is licensed under the [MIT License](LICENSE). Its runtime design and -security boundaries are documented in [Architecture](docs/architecture.md). +UsageBeam is licensed under the [MIT License](LICENSE). diff --git a/docs/architecture.md b/docs/architecture.md deleted file mode 100644 index 027a055..0000000 --- a/docs/architecture.md +++ /dev/null @@ -1,87 +0,0 @@ -# Architecture - -UsageBeam is a GNOME Shell extension with a small trusted UI process and short-lived -collector subprocesses. Provider code never creates GNOME actors, and UI code -never reads provider credentials or transcript formats. - -## Runtime flow - -1. `extension.js` creates the indicator, places it beside the Shell calendar - according to preferences, and creates `UsageService`. -2. `UsageService` loads cached provider records, schedules independent refreshes, - and starts one bounded GJS collector per enabled provider. Command discovery - is isolated in `src/services/commands.js`, including user-local and supported - version-manager paths that may be absent from GNOME Shell's environment. -3. `src/collector/main.js` supplies shared file, HTTP, authentication, and process - capabilities to one adapter under `src/providers/`. -4. The adapter returns a versioned record from `src/core/usage.js` with limits and - history represented as independent sections. -5. The service validates and merges the record, stores derived metadata, evaluates - notification crossings, and asks the indicator to render typed data. - -## Boundaries - -- `src/core/` owns the provider-neutral contract, aggregation, formatting, and - threshold state. -- `src/providers/` owns one adapter per external provider. -- `src/services/` owns scheduling, process lifetimes, HTTP, history scanning, and - private XDG storage. -- `src/ui/` owns Shell actors and accessibility behavior; provider-status and - period presentation decisions remain pure and unit-testable. -- `prefs.js` is the native libadwaita preferences entry point for placement, - refresh, and notification controls. - -Collectors have deadlines, bounded output, and cancellation. Child processes -receive a scoped `PATH` without changing the Shell process environment. Refresh -jobs do not overlap, resume events refresh only after wake, and results are -ignored after extension disable. - -Subprocess output is capped during asynchronous reads, before decoding or parsing. -RPC reads, queued writes, and pending requests are bounded independently; cancellation -reaps the managed child. Notification tracking uses a bounded least-recently-used -set of quota-period milestones and accepts current partial quota responses. - -Popup layout measures native actor heights against the monitor work area before -painting. Normal displays retain the full expanded activity view; constrained -layouts progressively paginate limits, daily/model activity, and model totals. -Page sizes remain stable during navigation, all records remain reachable, and -keyboard focus survives page changes. No nested scroll view or allocation-driven -rebuild loop is used. Calendar balancing includes other visible center-panel actors. - -The provider-neutral record is currently schema version 2. A record claiming -current, partial, or cached data must include a source and successful-update -timestamp; ready limits must contain at least one typed quota window. Invalid or -oversized records are rejected before they reach the UI or persistent state. - -## Privacy and storage - -UsageBeam stores derived records in `$XDG_STATE_HOME/usagebeam` and versioned -incremental scan caches in `$XDG_CACHE_HOME/usagebeam`, with private directory -and file modes. Credentials are read only when needed and are never written by -UsageBeam. Prompt/response content is ignored, while session and account identifiers -used for deduplication are hashed once before persistence. - -History cache version 5 stores a digest of each committed file prefix. Changed -files must match that digest before an incremental read resumes; rewrites rebuild -their derived totals. Oversized lines are skipped within the scan deadline, with -continuation state retained until their newline. Later valid records remain readable, -and incomplete totals stay explicitly marked partial. Older caches rebuild from -source without modifying the provider's files. - -On the first start after the pre-alpha identity change, UsageBeam copies only -recognized provider records and incremental history caches from the former -directories when the corresponding destination is absent. Explicitly changed -preferences are imported from an installed former schema under the same -non-overwrite rule. Credentials and unrelated files are never migrated. - -Account quota and local token activity remain separate scopes. Local activity is -not billing data and never becomes a fabricated subscription percentage. - -## Distribution - -Meson installs only extension entry points, source modules, verified provider -adapters, provider SVG marks, metadata, styles, license, and an extension-local -schema. `tools/package.py` uses the same explicit runtime allowlist to produce -deterministic ZIP files. Inputs must be regular, repository-owned files, and the -completed archive is checked against that allowlist; repository guidance, docs, -tests, tools, and references cannot enter a release archive. diff --git a/docs/providers/claude.md b/docs/providers/claude.md index fc061bc..b4dbe6f 100644 --- a/docs/providers/claude.md +++ b/docs/providers/claude.md @@ -1,41 +1,37 @@ -# Claude Code provider +# Claude Code support -UsageBeam keeps Claude's account limits and local activity as separate sources. -Failure of one source does not erase valid data from the other. +UsageBeam can show supported Claude Code account limits alongside seven days of +activity recorded on the current device. -## Account limits +## Set up Claude Code -The collector reads only the `claudeAiOauth` fields needed from -`$CLAUDE_CONFIG_DIR/.credentials.json` (or `~/.claude/.credentials.json`) and -sends the access token only to `https://api.anthropic.com/api/oauth/usage`. -It supports the current 5-hour, weekly, and model-scoped response buckets. The -token itself is never cached or logged; only a SHA-256 account fingerprint may -be retained to prevent stale data crossing accounts. +1. Install Claude Code. +2. Open Claude Code and complete its normal sign-in flow. +3. Enable Claude in UsageBeam preferences. +4. Select **Refresh** from the UsageBeam menu. -This OAuth usage interface is used by current Claude Code integrations but is -not a stable public Anthropic API contract. UsageBeam treats unknown/empty payloads -as unavailable, retains only unexpired stale windows after transient failures, -and keeps local history visible when sign-in expires or the endpoint is offline. +## What UsageBeam shows -## Local activity +- The shortest available quota window in the top panel, including when it is + exhausted. +- The current 5-hour, weekly, and model-scoped limits reported for the signed-in + account in the menu. +- A seven-day token chart and per-model totals from local Claude Code activity. -Claude Code assistant-message usage is scanned incrementally from -`$CLAUDE_CONFIG_DIR/projects/**/*.jsonl` or `~/.claude/projects/**/*.jsonl`. -UsageBeam reads timestamps, model identifiers, message/session identifiers, and the -four usage counters only. Prompt and response content is ignored. +Account limits and local activity are independent. Local activity is not an +account-wide total, subscription quota, or billable cost. -`input_tokens`, `output_tokens`, `cache_read_input_tokens`, and -`cache_creation_input_tokens` are independent Anthropic counters, so UsageBeam adds -all four to activity totals without subtracting cache tokens from input. Repeated -message IDs are counted once. Session identifiers are hashed before derived data -is written to UsageBeam's XDG cache. +## Privacy -History is local to this device, covers the displayed seven-day period, and is -not a measure of subscription quota or billable cost. +UsageBeam reads only the sign-in and usage fields needed for these views. It +does not store access tokens, prompts, responses, credentials, or raw session +identifiers. -## Verification status +## If Claude shows Setup -- Claude Code 2.1.218 installation detection and missing-auth behavior: live. -- Standard/scoped limits, percentage normalization, expired auth, endpoint auth - errors, duplicate messages, and token categories: deterministic fixtures. -- Signed-in account quota probe: not yet available for live verification. +- Open Claude Code and confirm the same user is signed in. +- Refresh UsageBeam after completing sign-in. +- If limits remain unavailable, local activity may still continue to work. + +Claude Code's account-usage interface can change independently of UsageBeam. An +unsupported response is shown as unavailable rather than as zero usage. diff --git a/docs/providers/codex.md b/docs/providers/codex.md index 5c626be..dbb7cd7 100644 --- a/docs/providers/codex.md +++ b/docs/providers/codex.md @@ -1,41 +1,38 @@ -# Codex provider +# Codex support -UsageBeam keeps Codex account limits and local activity as separate sources. -Failure of either source does not turn missing data into zero usage. +UsageBeam can show live Codex account limits alongside seven days of activity +recorded on the current device. -## Account limits +## Set up Codex -The collector launches the installed Codex CLI app-server, completes its normal -initialization handshake, reads the current signed-in account, and requests -account rate limits. It supports the reported session, weekly, reserve, and -scoped quota windows without inventing windows that are absent from the response. +1. Install the Codex CLI. +2. Sign in with `codex login`. +3. Enable Codex in UsageBeam preferences. +4. Select **Refresh** from the UsageBeam menu. -UsageBeam does not persist the account email or account identifier. When an -identifier is available, it is reduced to a SHA-256 fingerprint used only to -prevent cached values from crossing accounts. +UsageBeam recognizes standard command locations as well as common fnm, nvm, +mise, asdf, and Volta installations. -The CLI is discovered from the Shell `PATH`, common user-local directories, and -fnm, nvm, mise, asdf, and Volta layouts. When a version-managed CLI requires -Node, its sibling runtime is added only to the collector process environment. +## What UsageBeam shows -## Local activity +- The shortest available quota window in the top panel, including when it is + exhausted. +- Every session, weekly, reserve, or scoped limit reported for the signed-in + account in the menu. +- A seven-day token chart and per-model totals from local Codex activity. -Codex JSONL records are scanned incrementally from `sessions/` and -`archived_sessions/` under `$CODEX_HOME` or `~/.codex`. UsageBeam reads timestamps, -models, session identifiers, and token counters. Prompt and response content is -ignored. +Account limits and local activity are independent. Local activity is not an +account-wide total, subscription quota, or billable cost. -Cumulative counters are converted into individual deltas, including counter -reset handling. Cached-input and cache-write counters retain their own -categories. Session identifiers are hashed before derived events are persisted. +## Privacy -History covers the displayed period on this device. It is not account-wide -subscription usage or billable cost. +UsageBeam reads only the account and usage fields needed for these views. It +does not store account email addresses, prompts, responses, credentials, or raw +session identifiers. -## Verification status +## If Codex shows Setup -- Codex CLI 0.145.0: live account, quota, and local-history verification. -- Session/weekly/reserve parsing, cumulative resets, rotated/replaced records, - duplicate events, and version-managed runtime discovery: deterministic tests. -- GNOME Shell 50 restricted-environment discovery: isolated lifecycle and - sanitized live verification. +- Confirm `codex` runs from a terminal for the same user. +- Confirm the CLI is signed in. +- Refresh UsageBeam after signing in. +- If the CLI was just installed, log out of GNOME and back in once. diff --git a/tools/check.js b/tools/check.js index 3068785..e0d92b9 100644 --- a/tools/check.js +++ b/tools/check.js @@ -47,12 +47,11 @@ const version = JSON.parse(readFileSync('package.json', 'utf8')).version; if (!readFileSync('meson.build', 'utf8').includes(`version: '${version}'`)) throw new Error('Meson and package versions differ'); -const required = ['AGENTS.md', '.AGENTS/README.md', '.AGENTS/plans/roadmap.md', - '.AGENTS/rules/architecture.md', '.AGENTS/rules/quality.md', '.AGENTS/rules/releases.md', - 'docs/architecture.md', 'docs/providers/codex.md', 'docs/providers/claude.md']; +const required = ['README.md', 'CHANGELOG.md', + 'docs/providers/codex.md', 'docs/providers/claude.md']; for (const file of required) { if (!existsSync(file)) - throw new Error(`Missing repository guidance: ${file}`); + throw new Error(`Missing product documentation: ${file}`); } const schemas = walk('schemas').filter(file => file.endsWith('.xml')); if (schemas.length !== 1 || schemas[0] !== 'schemas/org.gnome.shell.extensions.usagebeam.gschema.xml') @@ -67,16 +66,12 @@ if (providerFiles.join(',') !== 'src/providers/claude.js,src/providers/codex.js' const obsolete = ['plan.md', 'indicator.js', 'src/providers/legacy.js', 'scripts/freeby.sh', 'src/providers/cursor.js', 'src/providers/copilot.js', 'scripts/copilot-setup.sh', 'tests/freeby.bats', 'docs/README.md', 'docs/assets', 'docs/providers/README.md', - 'docs/s1.png', 'docs/s2.png', 'reference-images', 'CONTRIBUTING.md']; + 'docs/s1.png', 'docs/s2.png', 'CONTRIBUTING.md']; for (const file of obsolete) { if (existsSync(file)) throw new Error(`Obsolete repository path returned: ${file}`); } -for (const file of walk('.AGENTS/rules')) { - if (readFileSync(file, 'utf8').split('\n').length > 50) - throw new Error(`${file}: agent rules must stay focused and under 50 lines`); -} -const markdown = ['README.md', 'CHANGELOG.md', 'AGENTS.md', ...walk('.AGENTS'), ...walk('docs')] +const markdown = ['README.md', 'CHANGELOG.md', ...walk('docs')] .filter(file => file.endsWith('.md')); for (const file of markdown) { const text = readFileSync(file, 'utf8'); From 5fd37236b8de9cb479acedfd310c9285f8e4a6e3 Mon Sep 17 00:00:00 2001 From: kcnewman Date: Tue, 8 Sep 2026 23:32:10 +0000 Subject: [PATCH 41/44] style: format Python release tooling --- tools/package.py | 109 +++++++--- tools/smoke-shell.py | 481 ++++++++++++++++++++++++++++++------------- 2 files changed, 416 insertions(+), 174 deletions(-) diff --git a/tools/package.py b/tools/package.py index ec2cfb5..81388c8 100644 --- a/tools/package.py +++ b/tools/package.py @@ -1,56 +1,90 @@ #!/usr/bin/env python3 """Create a deterministic GNOME extension archive from an explicit allowlist.""" + import argparse import json -from pathlib import Path import re import subprocess import tempfile import zipfile +from pathlib import Path def checked_file(source, path): """Return a regular, repository-owned packaging input.""" if path.is_symlink() or not path.is_file(): - raise RuntimeError(f'Invalid packaging input: {path}') + raise RuntimeError(f"Invalid packaging input: {path}") try: path.resolve().relative_to(source) except ValueError as error: - raise RuntimeError(f'Packaging input escapes the source tree: {path}') from error + raise RuntimeError( + f"Packaging input escapes the source tree: {path}" + ) from error return path def package(source, output): source = source.resolve() - metadata_file = checked_file(source, source / 'metadata.json') - package_file = checked_file(source, source / 'package.json') + metadata_file = checked_file(source, source / "metadata.json") + package_file = checked_file(source, source / "package.json") metadata = json.loads(metadata_file.read_text()) - version = json.loads(package_file.read_text())['version'] - uuid = metadata.get('uuid') - if not isinstance(uuid, str) or not re.fullmatch(r'[A-Za-z0-9._-]+@[A-Za-z0-9._-]+', uuid): - raise RuntimeError('metadata.json contains an invalid extension UUID') - if not isinstance(version, str) or not re.fullmatch(r'[0-9A-Za-z][0-9A-Za-z.+-]*', version): - raise RuntimeError('package.json contains an invalid release version') - files = [source / name for name in ('extension.js', 'prefs.js', 'metadata.json', 'stylesheet.css', 'LICENSE')] - files.extend(sorted((source / 'src').rglob('*.js'))) - files.extend(sorted((source / 'icons').glob('*.svg'))) - schema = metadata.get('settings-schema') - if schema != 'org.gnome.shell.extensions.usagebeam': - raise RuntimeError('metadata.json contains an unexpected settings schema') - files.append(source / 'schemas' / f'{schema}.gschema.xml') + version = json.loads(package_file.read_text())["version"] + uuid = metadata.get("uuid") + if not isinstance(uuid, str) or not re.fullmatch( + r"[A-Za-z0-9._-]+@[A-Za-z0-9._-]+", uuid + ): + raise RuntimeError("metadata.json contains an invalid extension UUID") + if not isinstance(version, str) or not re.fullmatch( + r"[0-9A-Za-z][0-9A-Za-z.+-]*", version + ): + raise RuntimeError("package.json contains an invalid release version") + files = [ + source / name + for name in ( + "extension.js", + "prefs.js", + "metadata.json", + "stylesheet.css", + "LICENSE", + ) + ] + files.extend(sorted((source / "src").rglob("*.js"))) + files.extend(sorted((source / "icons").glob("*.svg"))) + schema = metadata.get("settings-schema") + if schema != "org.gnome.shell.extensions.usagebeam": + raise RuntimeError("metadata.json contains an unexpected settings schema") + files.append(source / "schemas" / f"{schema}.gschema.xml") files = [checked_file(source, file) for file in files] output.mkdir(parents=True, exist_ok=True) - archive = output / f'{uuid}-{version}.zip' + archive = output / f"{uuid}-{version}.zip" if archive.is_symlink(): - raise RuntimeError(f'Refusing to overwrite archive symlink: {archive}') - with tempfile.TemporaryDirectory(prefix='usagebeam-schemas-') as scratch: - subprocess.run(['glib-compile-schemas', '--strict', '--targetdir', scratch, str(source / 'schemas')], check=True) - payloads = [(file.relative_to(source).as_posix(), file.read_bytes()) for file in files] - payloads.append(('schemas/gschemas.compiled', (Path(scratch) / 'gschemas.compiled').read_bytes())) + raise RuntimeError(f"Refusing to overwrite archive symlink: {archive}") + with tempfile.TemporaryDirectory(prefix="usagebeam-schemas-") as scratch: + subprocess.run( + [ + "glib-compile-schemas", + "--strict", + "--targetdir", + scratch, + str(source / "schemas"), + ], + check=True, + ) + payloads = [ + (file.relative_to(source).as_posix(), file.read_bytes()) for file in files + ] + payloads.append( + ( + "schemas/gschemas.compiled", + (Path(scratch) / "gschemas.compiled").read_bytes(), + ) + ) expected = {name for name, _payload in payloads} if len(expected) != len(payloads): - raise RuntimeError('Duplicate paths in extension package') - with zipfile.ZipFile(archive, 'w', compression=zipfile.ZIP_DEFLATED, compresslevel=9) as target: + raise RuntimeError("Duplicate paths in extension package") + with zipfile.ZipFile( + archive, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=9 + ) as target: for name, payload in sorted(payloads): info = zipfile.ZipInfo(name, date_time=(2020, 1, 1, 0, 0, 0)) info.create_system = 3 @@ -60,18 +94,27 @@ def package(source, output): with zipfile.ZipFile(archive) as check: names = check.namelist() if check.testzip() is not None: - raise RuntimeError('Extension archive failed its integrity check') + raise RuntimeError("Extension archive failed its integrity check") if set(names) != expected: - raise RuntimeError('Extension archive contents differ from the runtime allowlist') - if any(name.startswith(('.AGENTS/', '.github/', 'docs/', 'tests/', 'tools/')) for name in names): - raise RuntimeError('Repository-only content entered the extension archive') + raise RuntimeError( + "Extension archive contents differ from the runtime allowlist" + ) + if any( + name.startswith((".AGENTS/", ".github/", "docs/", "tests/", "tools/")) + for name in names + ): + raise RuntimeError( + "Repository-only content entered the extension archive" + ) print(archive.resolve()) return archive -if __name__ == '__main__': +if __name__ == "__main__": parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('--source', type=Path, default=Path(__file__).resolve().parent.parent) - parser.add_argument('--output', type=Path, default=Path('dist')) + parser.add_argument( + "--source", type=Path, default=Path(__file__).resolve().parent.parent + ) + parser.add_argument("--output", type=Path, default=Path("dist")) args = parser.parse_args() package(args.source.resolve(), args.output.resolve()) diff --git a/tools/smoke-shell.py b/tools/smoke-shell.py index ee1d9dc..cbe0aaf 100644 --- a/tools/smoke-shell.py +++ b/tools/smoke-shell.py @@ -1,186 +1,358 @@ #!/usr/bin/env python3 """Exercise the packaged extension in a private headless GNOME session.""" + import argparse -from datetime import date, timedelta import json import os -from pathlib import Path -import signal import shutil +import signal import subprocess import tempfile import time import zipfile +from datetime import date, timedelta +from pathlib import Path from xml.sax.saxutils import escape -UUID = 'usagebeam@oo7kc.github.io' -SCHEMA = 'org.gnome.shell.extensions.usagebeam' -PRODUCT_DIRECTORY = 'usagebeam' -TEST_UUID = 'usagebeam-ui-test@tests.invalid' +UUID = "usagebeam@oo7kc.github.io" +SCHEMA = "org.gnome.shell.extensions.usagebeam" +PRODUCT_DIRECTORY = "usagebeam" +TEST_UUID = "usagebeam-ui-test@tests.invalid" def run(command, env, **options): - return subprocess.run(command, env=env, text=True, capture_output=True, timeout=15, **options) + return subprocess.run( + command, env=env, text=True, capture_output=True, timeout=15, **options + ) def install(source, archive, prefix, destination): - extension = prefix / 'share' / 'gnome-shell' / 'extensions' / UUID + extension = prefix / "share" / "gnome-shell" / "extensions" / UUID if archive: with zipfile.ZipFile(archive) as payload: for entry in payload.infolist(): parts = Path(entry.filename).parts if entry.is_dir(): continue - if Path(entry.filename).is_absolute() or '..' in parts: - raise RuntimeError(f'Unsafe archive entry: {entry.filename}') + if Path(entry.filename).is_absolute() or ".." in parts: + raise RuntimeError(f"Unsafe archive entry: {entry.filename}") target = extension.joinpath(*parts) target.parent.mkdir(parents=True, exist_ok=True) target.write_bytes(payload.read(entry)) return - build = destination / 'build' - subprocess.run(['meson', 'setup', str(build), str(source), f'--prefix={prefix}'], check=True, stdout=subprocess.DEVNULL) - subprocess.run(['meson', 'install', '-C', str(build)], check=True, stdout=subprocess.DEVNULL) + build = destination / "build" + subprocess.run( + ["meson", "setup", str(build), str(source), f"--prefix={prefix}"], + check=True, + stdout=subprocess.DEVNULL, + ) + subprocess.run( + ["meson", "install", "-C", str(build)], check=True, stdout=subprocess.DEVNULL + ) def seed_usage(destination): """Populate non-personal records so every primary popup widget is constructed.""" now = int(time.time() * 1000) - dates = [(date.today() - timedelta(days=offset)).isoformat() for offset in range(6, -1, -1)] + dates = [ + (date.today() - timedelta(days=offset)).isoformat() + for offset in range(6, -1, -1) + ] def record(provider, name, plan, percentages, models): - days = [{'date': day, 'total': [0, 0, 0, 0, 0, 70_700_000, 38_500_000][index], - 'sessions': index + 1, 'events': index + 2} - for index, day in enumerate(dates)] + days = [ + { + "date": day, + "total": [0, 0, 0, 0, 0, 70_700_000, 38_500_000][index], + "sessions": index + 1, + "events": index + 2, + } + for index, day in enumerate(dates) + ] return { - 'schemaVersion': 2, 'id': provider, 'name': name, 'plan': plan, 'accountKey': '0' * 64, - 'capabilities': {'limits': True, 'history': True, 'models': True}, - 'limits': {'status': 'ready', 'message': '', 'updatedAt': now, 'scope': 'account', - 'source': 'Synthetic smoke fixture', - 'windows': [ - {'id': f'{provider}:session', 'label': 'Session · 5 hours', 'usedPercent': percentages[0], - 'used': None, 'limit': None, 'unit': 'percent', 'unlimited': False, - 'state': 'exhausted' if percentages[0] >= 100 else 'active', - 'durationMinutes': 300, 'resetsAt': now + 7_200_000}, - {'id': f'{provider}:weekly', 'label': 'Weekly', 'usedPercent': percentages[1], - 'used': None, 'limit': None, 'unit': 'percent', 'unlimited': False, - 'state': 'exhausted' if percentages[1] >= 100 else 'active', - 'durationMinutes': 10_080, 'resetsAt': now + 172_800_000}, - {'id': f'{provider}:reserve', 'label': 'Weekly reserve', 'usedPercent': percentages[2], - 'used': None, 'limit': None, 'unit': 'percent', 'unlimited': False, - 'state': 'exhausted' if percentages[2] >= 100 else 'active', - 'durationMinutes': 10_080, 'resetsAt': now + 604_800_000}, - ]}, - 'history': {'status': 'ready', 'message': '', 'updatedAt': now, 'scope': 'local', - 'period': {'start': dates[0], 'end': dates[-1]}, 'days': days, 'models': models, - 'source': 'Synthetic smoke fixture'}, + "schemaVersion": 2, + "id": provider, + "name": name, + "plan": plan, + "accountKey": "0" * 64, + "capabilities": {"limits": True, "history": True, "models": True}, + "limits": { + "status": "ready", + "message": "", + "updatedAt": now, + "scope": "account", + "source": "Synthetic smoke fixture", + "windows": [ + { + "id": f"{provider}:session", + "label": "Session · 5 hours", + "usedPercent": percentages[0], + "used": None, + "limit": None, + "unit": "percent", + "unlimited": False, + "state": "exhausted" if percentages[0] >= 100 else "active", + "durationMinutes": 300, + "resetsAt": now + 7_200_000, + }, + { + "id": f"{provider}:weekly", + "label": "Weekly", + "usedPercent": percentages[1], + "used": None, + "limit": None, + "unit": "percent", + "unlimited": False, + "state": "exhausted" if percentages[1] >= 100 else "active", + "durationMinutes": 10_080, + "resetsAt": now + 172_800_000, + }, + { + "id": f"{provider}:reserve", + "label": "Weekly reserve", + "usedPercent": percentages[2], + "used": None, + "limit": None, + "unit": "percent", + "unlimited": False, + "state": "exhausted" if percentages[2] >= 100 else "active", + "durationMinutes": 10_080, + "resetsAt": now + 604_800_000, + }, + ], + }, + "history": { + "status": "ready", + "message": "", + "updatedAt": now, + "scope": "local", + "period": {"start": dates[0], "end": dates[-1]}, + "days": days, + "models": models, + "source": "Synthetic smoke fixture", + }, } models = [ - {'model': 'gpt-5.6-sol', 'total': 77_200_000, 'input': 190_000, 'output': 68_000, - 'cacheRead': 76_942_000, 'cacheWrite': 0}, - {'model': 'codex-auto-review', 'total': 24_100_000, 'input': 97_000, 'output': 8_000, - 'cacheRead': 23_995_000, 'cacheWrite': 0}, - {'model': 'gpt-6-astra', 'total': 7_900_000, 'input': 42_000, 'output': 62_000, - 'cacheRead': 7_796_000, 'cacheWrite': 0}, + { + "model": "gpt-5.6-sol", + "total": 77_200_000, + "input": 190_000, + "output": 68_000, + "cacheRead": 76_942_000, + "cacheWrite": 0, + }, + { + "model": "codex-auto-review", + "total": 24_100_000, + "input": 97_000, + "output": 8_000, + "cacheRead": 23_995_000, + "cacheWrite": 0, + }, + { + "model": "gpt-6-astra", + "total": 7_900_000, + "input": 42_000, + "output": 62_000, + "cacheRead": 7_796_000, + "cacheWrite": 0, + }, ] - target = destination / 'state' / PRODUCT_DIRECTORY + target = destination / "state" / PRODUCT_DIRECTORY target.mkdir(parents=True, exist_ok=True) - records = {'codex': record('codex', 'Codex', 'Plus', (100, 94, 83), models), - 'claude': record('claude', 'Claude Code', 'Pro', (46, 7, 0), models)} + records = { + "codex": record("codex", "Codex", "Plus", (100, 94, 83), models), + "claude": record("claude", "Claude Code", "Pro", (46, 7, 0), models), + } for provider, data in records.items(): - (target / f'{provider}.json').write_text(json.dumps(data)) + (target / f"{provider}.json").write_text(json.dumps(data)) # Separate fixtures survive asynchronous collector updates to the cache. - (destination / 'ui-fixtures.json').write_text(json.dumps(records)) + (destination / "ui-fixtures.json").write_text(json.dumps(records)) def smoke(source, archive, destination, scale=1, text_scale=1.0, *, width=1280, height=1024, system_fonts=False, stress=False): destination.mkdir(parents=True, exist_ok=True) - prefix = destination / 'install' + prefix = destination / "install" install(source, archive, prefix, destination) - shutil.copytree(source / 'tests/shell', prefix / 'share/gnome-shell/extensions' / TEST_UUID) - isolated_home = destination / 'home' + shutil.copytree( + source / "tests/shell", prefix / "share/gnome-shell/extensions" / TEST_UUID + ) + isolated_home = destination / "home" isolated_home.mkdir(parents=True, exist_ok=True) - runtime = destination / 'runtime' + runtime = destination / "runtime" runtime.mkdir(mode=0o700) seed_usage(destination) - env = {**os.environ, 'HOME': str(isolated_home), 'PATH': '/usr/bin:/bin', - 'XDG_CONFIG_HOME': str(destination / 'config'), - 'XDG_DATA_HOME': str(prefix / 'share'), 'XDG_CACHE_HOME': str(destination / 'cache'), - 'XDG_STATE_HOME': str(destination / 'state'), 'GSETTINGS_BACKEND': 'keyfile', - 'XDG_RUNTIME_DIR': str(runtime), - 'GSETTINGS_SCHEMA_DIR': str(prefix / 'share/gnome-shell/extensions' / UUID / 'schemas'), - 'LIBGL_ALWAYS_SOFTWARE': '1', 'USAGEBEAM_TEST_OUTPUT': str(destination), - 'USAGEBEAM_TEST_SCALE': str(scale), - 'USAGEBEAM_STRESS': '1' if stress else '0'} + env = { + **os.environ, + "HOME": str(isolated_home), + "PATH": "/usr/bin:/bin", + "XDG_CONFIG_HOME": str(destination / "config"), + "XDG_DATA_HOME": str(prefix / "share"), + "XDG_CACHE_HOME": str(destination / "cache"), + "XDG_STATE_HOME": str(destination / "state"), + "GSETTINGS_BACKEND": "keyfile", + "XDG_RUNTIME_DIR": str(runtime), + "GSETTINGS_SCHEMA_DIR": str( + prefix / "share/gnome-shell/extensions" / UUID / "schemas" + ), + "LIBGL_ALWAYS_SOFTWARE": "1", + "USAGEBEAM_TEST_OUTPUT": str(destination), + "USAGEBEAM_TEST_SCALE": str(scale), + "USAGEBEAM_STRESS": "1" if stress else "0", + } # Never let inherited provider paths expose the real account to a test shell. - for name in ('CODEX_HOME', 'CLAUDE_CONFIG_DIR'): + for name in ("CODEX_HOME", "CLAUDE_CONFIG_DIR"): env.pop(name, None) # Use locally installed fonts without copying or distributing licensed files. - font = run(['fc-match', '-f', '%{file}', 'SF Pro Text'], os.environ, check=True).stdout - font_config = destination / 'fonts.conf' - extra_fonts = '' if system_fonts else f'{escape(str(Path(font).parent))}' - font_config.write_text('/etc/fonts/fonts.conf' - f'{extra_fonts}') - env['FONTCONFIG_FILE'] = str(font_config) - resolved_font = run(['fc-match', '-f', '%{family}', 'SF Pro Text'], env, check=True).stdout - if system_fonts and 'SF Pro' in resolved_font: - raise RuntimeError('System-font case still resolves SF Pro; font fallback was not isolated') - run(['gsettings', 'set', 'org.gnome.desktop.interface', 'scaling-factor', str(scale)], env, check=True) - run(['gsettings', 'set', 'org.gnome.desktop.interface', 'text-scaling-factor', str(text_scale)], env, check=True) - run(['gsettings', 'set', SCHEMA, 'enabled-providers', "['codex', 'claude']"], env, check=True) - run(['gsettings', 'set', SCHEMA, 'panel-position', "'left-of-calendar'"], env, check=True) - run(['gsettings', 'set', 'org.gnome.shell', 'enabled-extensions', f"['{UUID}', '{TEST_UUID}']"], env, check=True) - run(['gsettings', 'set', 'org.gnome.shell', 'welcome-dialog-last-shown-version', '999'], env) - bus = subprocess.Popen(['dbus-daemon', '--session', '--nofork', '--print-address=1'], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True) - env['DBUS_SESSION_BUS_ADDRESS'] = bus.stdout.readline().strip() - log_path = destination / 'shell.log' + font = run( + ["fc-match", "-f", "%{file}", "SF Pro Text"], os.environ, check=True + ).stdout + font_config = destination / "fonts.conf" + extra_fonts = "" if system_fonts else f"{escape(str(Path(font).parent))}" + font_config.write_text( + "/etc/fonts/fonts.conf" + f"{extra_fonts}" + ) + env["FONTCONFIG_FILE"] = str(font_config) + resolved_font = run(["fc-match", "-f", "%{family}", "SF Pro Text"], env, check=True).stdout + if system_fonts and "SF Pro" in resolved_font: + raise RuntimeError("System-font case still resolves SF Pro; font fallback was not isolated") + run( + [ + "gsettings", + "set", + "org.gnome.desktop.interface", + "scaling-factor", + str(scale), + ], + env, + check=True, + ) + run( + [ + "gsettings", + "set", + "org.gnome.desktop.interface", + "text-scaling-factor", + str(text_scale), + ], + env, + check=True, + ) + run( + ["gsettings", "set", SCHEMA, "enabled-providers", "['codex', 'claude']"], + env, + check=True, + ) + run( + ["gsettings", "set", SCHEMA, "panel-position", "'left-of-calendar'"], + env, + check=True, + ) + run( + [ + "gsettings", + "set", + "org.gnome.shell", + "enabled-extensions", + f"['{UUID}', '{TEST_UUID}']", + ], + env, + check=True, + ) + run( + [ + "gsettings", + "set", + "org.gnome.shell", + "welcome-dialog-last-shown-version", + "999", + ], + env, + ) + bus = subprocess.Popen( + ["dbus-daemon", "--session", "--nofork", "--print-address=1"], + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + text=True, + ) + env["DBUS_SESSION_BUS_ADDRESS"] = bus.stdout.readline().strip() + log_path = destination / "shell.log" shell = None try: - with log_path.open('w') as log: - shell = subprocess.Popen(['gnome-shell', '--headless', '--no-x11', '--virtual-monitor', f'{width * scale}x{height * scale}', - '--wayland-display', 'usagebeam-test'], env=env, stdout=log, stderr=log, start_new_session=True) - command = ['gdbus', 'call', '--session', '--dest', 'org.gnome.Shell.Extensions', '--object-path', '/org/gnome/Shell/Extensions', - '--method', 'org.gnome.Shell.Extensions.GetExtensionInfo', UUID] + with log_path.open("w") as log: + shell = subprocess.Popen( + [ + "gnome-shell", + "--headless", + "--no-x11", + "--virtual-monitor", + f"{width * scale}x{height * scale}", + "--wayland-display", + "usagebeam-test", + ], + env=env, + stdout=log, + stderr=log, + start_new_session=True, + ) + command = [ + "gdbus", + "call", + "--session", + "--dest", + "org.gnome.Shell.Extensions", + "--object-path", + "/org/gnome/Shell/Extensions", + "--method", + "org.gnome.Shell.Extensions.GetExtensionInfo", + UUID, + ] deadline = time.monotonic() + 30 - info = '' + info = "" while time.monotonic() < deadline and shell.poll() is None: result = run(command, env) info = result.stdout if "'state': <1.0>" in info: break if "'state': <3.0>" in info: - raise RuntimeError(f'Extension failed: {info}') + raise RuntimeError(f"Extension failed: {info}") time.sleep(0.4) else: - raise RuntimeError(f'Extension did not become active: {info}') - print('PASS: packaged extension loads in a private GNOME Shell session') - results_path = destination / 'ui-results.json' + raise RuntimeError(f"Extension did not become active: {info}") + print("PASS: packaged extension loads in a private GNOME Shell session") + results_path = destination / "ui-results.json" deadline = time.monotonic() + (90 if stress else 25) - while time.monotonic() < deadline and not results_path.exists() and shell.poll() is None: + while ( + time.monotonic() < deadline + and not results_path.exists() + and shell.poll() is None + ): time.sleep(0.2) if not results_path.exists(): - raise RuntimeError(f'UI tests did not complete; inspect {log_path}') + raise RuntimeError(f"UI tests did not complete; inspect {log_path}") results = json.loads(results_path.read_text()) - if not results['ok'] and not stress: + if not results["ok"] and not stress: raise RuntimeError(f"UI check failed: {results['error']}") - if results['ok']: - print('PASS: private-shell UI assertions') - print(f'UI geometry and screenshots: {destination}') - run(['gnome-extensions', 'disable', TEST_UUID], env, check=True) + if results["ok"]: + print("PASS: private-shell UI assertions") + print(f"UI geometry and screenshots: {destination}") + run(["gnome-extensions", "disable", TEST_UUID], env, check=True) cycles = 10 if stress else 1 for cycle in range(cycles): - run(['gnome-extensions', 'disable', UUID], env, check=True) + run(["gnome-extensions", "disable", UUID], env, check=True) disabled = run(command, env, check=True).stdout if "'state': <2.0>" not in disabled: - raise RuntimeError(f'Disable cycle {cycle + 1} did not reach INACTIVE') - run(['gnome-extensions', 'enable', UUID], env, check=True) + raise RuntimeError(f"Disable cycle {cycle + 1} did not reach INACTIVE") + run(["gnome-extensions", "enable", UUID], env, check=True) enabled = run(command, env, check=True).stdout if "'state': <1.0>" not in enabled: - raise RuntimeError(f'Enable cycle {cycle + 1} did not reach ACTIVE') - print(f'PASS: {cycles} verified disable/re-enable cycles') + raise RuntimeError(f"Enable cycle {cycle + 1} did not reach ACTIVE") + print(f"PASS: {cycles} verified disable/re-enable cycles") # Leave no collector running while the private shell itself shuts down. - run(['gnome-extensions', 'disable', UUID], env, check=True) + run(["gnome-extensions", "disable", UUID], env, check=True) finally: if shell and shell.poll() is None: os.killpg(shell.pid, signal.SIGTERM) @@ -191,60 +363,87 @@ def smoke(source, archive, destination, scale=1, text_scale=1.0, *, shell.wait() bus.terminate() bus.wait(timeout=5) - print(f'GNOME log: {log_path}') - contents = log_path.read_text(errors='replace') - if 'Gjs-CRITICAL' in contents and UUID in contents: - raise RuntimeError(f'Extension emitted a GJS critical; inspect {log_path}') - if 'St-WARNING' in contents or 'UsageBeam could not' in contents or 'JS ERROR' in contents: - raise RuntimeError(f'UI, stylesheet, or JavaScript warning; inspect {log_path}') + print(f"GNOME log: {log_path}") + contents = log_path.read_text(errors="replace") + if "Gjs-CRITICAL" in contents and UUID in contents: + raise RuntimeError(f"Extension emitted a GJS critical; inspect {log_path}") + if ( + "St-WARNING" in contents + or "UsageBeam could not" in contents + or "JS ERROR" in contents + ): + raise RuntimeError(f"UI, stylesheet, or JavaScript warning; inspect {log_path}") results.update(resolvedFont=resolved_font, lifecycleCycles=cycles) - (destination / 'ui-results.json').write_text(json.dumps(results, indent=2)) - if not results['ok']: + (destination / "ui-results.json").write_text(json.dumps(results, indent=2)) + if not results["ok"]: raise RuntimeError(f"UI stress failures; inspect {destination / 'ui-results.json'}") def matrix(source, archive, destination, selected=None): cases = [ - ('desktop-sf', 1280, 1024, 1, 1.0, False), - ('laptop-fallback', 1366, 768, 1, 1.0, True), - ('small-fallback', 1024, 600, 1, 1.0, True), - ('large-text', 1920, 1080, 1, 1.5, True), - ('hidpi-fallback', 1920, 1080, 2, 1.0, True), - ('hidpi-large-text', 1280, 1024, 2, 1.25, False), + ("desktop-sf", 1280, 1024, 1, 1.0, False), + ("laptop-fallback", 1366, 768, 1, 1.0, True), + ("small-fallback", 1024, 600, 1, 1.0, True), + ("large-text", 1920, 1080, 1, 1.5, True), + ("hidpi-fallback", 1920, 1080, 2, 1.0, True), + ("hidpi-large-text", 1280, 1024, 2, 1.25, False), ] outcomes = [] for name, width, height, scale, text_scale, fallback in cases: if selected and name != selected: continue - print(f'CASE: {name} ({width}x{height} logical, scale {scale}, text {text_scale})', flush=True) + print(f"CASE: {name} ({width}x{height} logical, scale {scale}, text {text_scale})", flush=True) try: smoke(source, archive, destination / name, scale, text_scale, width=width, height=height, system_fonts=fallback, stress=True) - outcomes.append({'case': name, 'ok': True}) + outcomes.append({"case": name, "ok": True}) except (RuntimeError, subprocess.SubprocessError) as error: - outcomes.append({'case': name, 'ok': False, 'error': str(error)}) - print(f'FAIL: {name}: {error}', flush=True) - (destination / 'matrix-results.json').write_text(json.dumps(outcomes, indent=2)) - return all(result['ok'] for result in outcomes) + outcomes.append({"case": name, "ok": False, "error": str(error)}) + print(f"FAIL: {name}: {error}", flush=True) + (destination / "matrix-results.json").write_text(json.dumps(outcomes, indent=2)) + return all(result["ok"] for result in outcomes) -if __name__ == '__main__': +if __name__ == "__main__": parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('--source', type=Path, default=Path(__file__).resolve().parent.parent) - parser.add_argument('--archive', type=Path, help='Test an already-built release archive instead of a Meson install') - parser.add_argument('--output', type=Path) - parser.add_argument('--matrix', action='store_true', help='Run isolated portability stress cases') - parser.add_argument('--case', choices=['desktop-sf', 'laptop-fallback', 'small-fallback', - 'large-text', 'hidpi-fallback', 'hidpi-large-text'], help='Select one matrix case') - parser.add_argument('--scale', type=int, choices=[1, 2], default=1, help='Private virtual-monitor scale') - parser.add_argument('--text-scale', type=float, choices=[1.0, 1.25], default=1.0, help='Accessibility text scale') + parser.add_argument( + "--source", type=Path, default=Path(__file__).resolve().parent.parent + ) + parser.add_argument( + "--archive", + type=Path, + help="Test an already-built release archive instead of a Meson install", + ) + parser.add_argument("--output", type=Path) + parser.add_argument("--matrix", action="store_true", help="Run isolated portability stress cases") + parser.add_argument("--case", choices=["desktop-sf", "laptop-fallback", "small-fallback", + "large-text", "hidpi-fallback", "hidpi-large-text"], help="Select one matrix case") + parser.add_argument( + "--scale", + type=int, + choices=[1, 2], + default=1, + help="Private virtual-monitor scale", + ) + parser.add_argument( + "--text-scale", + type=float, + choices=[1.0, 1.25], + default=1.0, + help="Accessibility text scale", + ) args = parser.parse_args() if args.case and not args.matrix: - parser.error('--case requires --matrix') + parser.error("--case requires --matrix") if args.matrix: - output = args.output.resolve() if args.output else Path(tempfile.mkdtemp(prefix='usagebeam-stress-')) - raise SystemExit(0 if matrix(args.source.resolve(), args.archive.resolve() if args.archive else None, - output, args.case) else 1) - smoke(args.source.resolve(), args.archive.resolve() if args.archive else None, - args.output.resolve() if args.output else Path(tempfile.mkdtemp(prefix='usagebeam-shell-')), - args.scale, args.text_scale) + output = args.output.resolve() if args.output else Path(tempfile.mkdtemp(prefix="usagebeam-stress-")) + raise SystemExit(0 if matrix(args.source.resolve(), args.archive.resolve() if args.archive else None, output, args.case) else 1) + smoke( + args.source.resolve(), + args.archive.resolve() if args.archive else None, + args.output.resolve() + if args.output + else Path(tempfile.mkdtemp(prefix="usagebeam-shell-")), + args.scale, + args.text_scale, + ) From 6d042989a32e98989750bdb2f747c6d4d2dfec72 Mon Sep 17 00:00:00 2001 From: kcnewman Date: Wed, 9 Sep 2026 00:26:12 +0000 Subject: [PATCH 42/44] docs: add product screenshots --- CHANGELOG.md | 6 ++++-- README.md | 23 ++++++++--------------- docs/images/account-limits.png | Bin 0 -> 74351 bytes docs/images/local-activity.png | Bin 0 -> 139952 bytes docs/images/panel-indicator.png | Bin 0 -> 8343 bytes 5 files changed, 12 insertions(+), 17 deletions(-) create mode 100644 docs/images/account-limits.png create mode 100644 docs/images/local-activity.png create mode 100644 docs/images/panel-indicator.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 7059ae0..1863de9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,11 +16,13 @@ All notable UsageBeam changes are documented here. - Native preferences for placement, refresh interval, and notifications. - Explicit quota severity states: caution at 80%, warning at 90%, and exhausted at 100%. +- Product screenshots for the panel indicator, account limits, and local + activity views. ### Changed -- Rebuilt the popup as a compact, accent-aware GNOME surface with SF Pro - typography when available, aligned quota metrics, and stable panel geometry. +- Rebuilt the popup as a compact, accent-aware GNOME surface with aligned quota + metrics and stable panel geometry. - Account limits, local activity, and saved results now remain available independently when one source cannot refresh. - Codex discovery supports common user-local and Node version-manager layouts. diff --git a/README.md b/README.md index 41eed12..ca87081 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,7 @@ preferences, accessibility, and lifecycle conventions. ## At a glance - Live account limits and reset windows for Codex and Claude Code. -- A stable, single-provider panel indicator that does not move the clock when - its content changes. +- A stable, single-provider panel indicator. - Four panel placements: left area, right area, left of calendar, or right of calendar. - Seven-day local activity chart and per-model token totals. @@ -27,18 +26,19 @@ UsageBeam displays only data reported by a provider or found in local usage records. Local activity covers this device; it is not billing data and is never converted into an account quota. -Activity expands in full when space allows. On smaller screens, compact page -controls keep every quota and model total reachable without a scrolling popup. - ## Screenshots ### Panel indicator -_Screenshot placeholder — compact provider usage beside the calendar._ +![UsageBeam showing the active Codex quota beside the GNOME calendar](docs/images/panel-indicator.png) + +### Account limits + +![UsageBeam showing Codex account limits and reset times](docs/images/account-limits.png) -### Usage details +### Local activity -_Screenshot placeholder — account limits and seven-day local activity._ +![UsageBeam showing a seven-day activity chart and token totals by model](docs/images/local-activity.png) ### Preferences @@ -69,8 +69,6 @@ compatibility details. - GNOME Shell 50. - GJS with Gio, GLib, and Soup 3 introspection data. - The Codex CLI and/or Claude Code, installed and signed in for account limits. -- SF Pro Text and SF Pro Display are used when installed; GNOME's system fallback - is used otherwise. UsageBeam does not bundle or download fonts. ## Install @@ -96,11 +94,6 @@ gnome-extensions prefs usagebeam@oo7kc.github.io ``` Preferences control panel placement, refresh frequency, and quota notifications. -Calendar-side placement keeps the gap between the indicator and clock on the -panel midpoint. The indicator reserves a stable width so switching providers or -loading new quota data does not move the calendar. When several limits are -available, the panel shows the shortest reported window; the popup continues to -show every reported limit. ## Privacy and storage diff --git a/docs/images/account-limits.png b/docs/images/account-limits.png new file mode 100644 index 0000000000000000000000000000000000000000..4bb6724d7ca015f2d806233e985a0a498278ba79 GIT binary patch literal 74351 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 z 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 NfoSNf*QnY@{2x#;fc5|Y literal 0 HcmV?d00001 diff --git a/docs/images/local-activity.png b/docs/images/local-activity.png new file mode 100644 index 0000000000000000000000000000000000000000..69bea8cbd502b3e61d4ae650c950f0aef2d8e36e GIT binary patch literal 139952 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%0AxBcEz_ta#7O0m2AiLz0(P?FJ;xvya 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 literal 0 HcmV?d00001 From cc39e9cec7974bba50a5a40b42a86368b5295db1 Mon Sep 17 00:00:00 2001 From: oo7kc Date: Wed, 9 Sep 2026 00:36:16 +0000 Subject: [PATCH 43/44] chore: align repository identity with UsageBeam --- README.md | 2 +- metadata.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ca87081..a830a12 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ compatibility details. ## Install -Download the UsageBeam ZIP from [GitHub Releases](https://github.com/oo7kc/freeby/releases), +Download the UsageBeam ZIP from [GitHub Releases](https://github.com/oo7kc/usagebeam/releases), then run: ```bash diff --git a/metadata.json b/metadata.json index 7ea9310..19b94a8 100644 --- a/metadata.json +++ b/metadata.json @@ -4,5 +4,5 @@ "description": "Private, at-a-glance usage monitoring for Codex and Claude Code, with account limits, reset windows, local token activity and model breakdowns.", "shell-version": ["50"], "settings-schema": "org.gnome.shell.extensions.usagebeam", - "url": "https://github.com/oo7kc/freeby" + "url": "https://github.com/oo7kc/usagebeam" } From 76db4fff4308e2e2ae0cbbb68d108f93cd3bb0c7 Mon Sep 17 00:00:00 2001 From: oo7kc Date: Wed, 9 Sep 2026 09:24:48 +0000 Subject: [PATCH 44/44] chore: prepare v2.0.0-alpha.3 release --- CHANGELOG.md | 2 ++ meson.build | 2 +- package.json | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1863de9..2729b9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable UsageBeam changes are documented here. ## Unreleased +## 2.0.0-alpha.3 - 2026-09-09 + ### Added - UsageBeam product identity with the permanent extension UUID diff --git a/meson.build b/meson.build index 5116c41..3b75146 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('usagebeam', version: '2.0.0-alpha.3-dev.0', license: 'MIT', +project('usagebeam', version: '2.0.0-alpha.3', license: 'MIT', meson_version: '>= 0.56.0', default_options: ['warning_level=0']) diff --git a/package.json b/package.json index 992f777..227ae6b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "usagebeam", - "version": "2.0.0-alpha.3-dev.0", + "version": "2.0.0-alpha.3", "private": true, "type": "module", "scripts": {

nvZ#V%yNVtwh5nvjZBg}X39exEmxvXF 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