Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
96ff685
feat: add macOS Electron migration candidate
pekth Oct 6, 2026
b36158f
fix: preserve Electron account and notch behavior
pekth Oct 6, 2026
c93c3b2
fix: bind Electron reads to current account sources
pekth Oct 6, 2026
1e410f6
fix: restore independent service health and notch ETA
pekth Oct 6, 2026
7792d83
fix: preserve provider metadata and macOS status parity
pekth Oct 6, 2026
5a9f78f
fix: preserve history privacy and macOS usage contracts
pekth Oct 6, 2026
2562e38
test: preserve macOS candidate path and filename checks
pekth Oct 6, 2026
193c2af
fix: reject unsupported provider and cache dates
pekth Oct 6, 2026
8bffa0b
docs: record verified macOS candidate package and CLI
pekth Oct 6, 2026
2cf8cce
docs: record native source scanner comparison
pekth Oct 7, 2026
5153a6d
feat: add opt-in macOS desktop account connections
pekth Oct 7, 2026
ad090cf
fix: bound demo notch scrolling and guard destroyed tray
pekth Oct 8, 2026
fffb9e5
fix: simplify settings and repair desktop helper launches
pekth Oct 9, 2026
559ef4e
feat: isolate live Codex candidate profiles
pekth Oct 9, 2026
06e5c89
fix: stop display updates during app shutdown
pekth Oct 10, 2026
07cb9d0
feat: add panel sizes and handle candidate reopens
pekth Oct 10, 2026
8a7e7c7
fix: keep panel sizing in sync and refine settings
pekth Oct 10, 2026
605f1c4
fix: move onboarding into settings
pekth Oct 10, 2026
df30011
feat: prepare Electron public beta with compact usage panels
pekth Oct 15, 2026
c3731fb
feat(ui): organize settings and refine heatmap interaction
pekth Oct 15, 2026
d08af00
docs: align hover feedback descriptions with design A
pekth Oct 15, 2026
8c72eeb
fix: fit Settings content and match compact notch rail
pekth Oct 15, 2026
06c791b
Compact usage cards and keep service status first
pekth Oct 15, 2026
299a883
fix: compact controls and align heatmap menu
pekth Oct 15, 2026
e2fe7dc
feat: restore diagnostic reports and simplify notch controls
pekth Oct 15, 2026
6ce6953
fix: persist diagnostic report cooldown across relaunches
pekth Oct 15, 2026
c507864
docs: publish beta guide and replace legacy screenshots
pekth Oct 16, 2026
baff989
fix: compact heatmap token and session totals (#62)
pekth Oct 16, 2026
d94c0fc
fix: show the installed bundle version in Settings
pekth Oct 16, 2026
5b79382
Merge pull request #63 from pekth/fix/beta-followups-20261016
pekth Oct 16, 2026
69407d8
feat: connect Grok Bot Desktop and polish provider setup
pekth Oct 16, 2026
cbceb9b
fix: connect Grok Bot and add separate API billing
pekth Oct 17, 2026
f883a67
feat: measure Grok quota burn and clarify API billing setup
pekth Oct 17, 2026
c2d092c
feat: simplify API billing setup and preload tray usage
pekth Oct 17, 2026
ec39199
feat: add opt-in Muse Code allowance tracking
pekth Oct 17, 2026
7523244
fix: map Claude plans and handle duplicate Desktop tokens
pekth Oct 20, 2026
3c56bd0
feat: add per-provider quota activity heatmaps
Oct 22, 2026
2eb12bf
docs: document public beta 4, build 73
Oct 22, 2026
321dd8a
feat: offer newer betas through the in-app updater
Oct 22, 2026
023b753
docs: document public beta 5, build 74
Oct 22, 2026
20fe299
docs: prepare beta 6 and record stable cutover requirements
pekth Oct 22, 2026
af6f790
Merge main and reconcile the Electron source stack
pekth Oct 22, 2026
58ea110
Fix quota activity isolation when connections change
pekth Oct 22, 2026
6b028e3
Preserve Muse quota history while collection is paused
pekth Oct 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,24 @@ concurrency:
cancel-in-progress: true

jobs:
typescript:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
with:
node-version: '24'
- name: Install pnpm
run: npm install --global pnpm@12.9.1 --ignore-scripts
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Typecheck
run: pnpm typecheck
- name: Test
run: pnpm test
- name: Build
run: pnpm build
test:
runs-on: macos-15
timeout-minutes: 15
Expand Down
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,12 @@ NO-LOOPS.md
.handoff/
build/
.automation/

# TypeScript / Electron candidate output
node_modules/
.vite-plus/
release/
coverage/

# Generated from the existing macOS SF Symbol stand-ins.
Resources/*-symbol.png
462 changes: 446 additions & 16 deletions CHANGELOG.md

Large diffs are not rendered by default.

145 changes: 123 additions & 22 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,40 @@
# Contributing to meterusage

New MeterUsage features target Electron. The default branch currently retains
stable Swift source; use the [beta source tag](https://github.com/pekth/meterusage/tree/v0.3.0-beta.1)
and its [Electron contributor instructions](https://github.com/pekth/meterusage/blob/v0.3.0-beta.1/CONTRIBUTING.md#typescript-electron-candidate)
for that implementation. Swift receives critical maintenance and compatibility
fixes. The stable Swift guidance follows.
New MeterUsage features target Electron in `src/` and `tests/`. Retain Swift
source for critical maintenance, compatibility and rollback. Both implementations
remain in this checkout; use the matching validation commands below.


> ### ⚡ TL;DR
> ### Quick start
> 1. Fork & clone the repo.
> 2. Enable pre-commit hook: `git config core.hooksPath .githooks`
> 3. Verify: `swift test` & `./Scripts/make-app.sh --build-from-source`
> 3. For Electron, use the declared Node/pnpm versions, then run
> `pnpm install --frozen-lockfile`, `pnpm typecheck`, `pnpm test` and `pnpm build`.
> 4. Keep diffs focused, zero secrets or credentials, synthetic fixtures only.


Fork the repository, make one focused change, run the checks below, and open a
pull request.

## Edition routing

New features target the [Electron implementation](docs/electron/README.md).
The [legacy Swift app](docs/swift/README.md) is frozen for new features and kept
for critical maintenance, transition compatibility and rollback. Both source
trees remain until the [retirement conditions](docs/adr/0024-electron-primary-swift-retirement.md#retirement-conditions)
pass. Validate both affected paths when a shared contract changes.
`Scripts/make-app.sh` prepares the published Swift app by default and never
builds Electron. Isolated demo/test profiles are required for candidate QA;
shared normal profiles do not support safe side-by-side use.

## Before you start

meterusage is a macOS 13+ Swift menu-bar app. The package uses Swift tools
version 5.9 and has no third-party package dependencies. Install Xcode Command
Line Tools before building:
Electron development uses the Node and pnpm versions declared in `package.json`.
Follow [the Electron commands](#typescript-electron-candidate). Electron's macOS
packaging still needs Swift for its Keychain helper and asset exporter.

The published app is a macOS 13+ Swift menu-bar app. A TypeScript/Electron
candidate is under development; see the candidate commands below. The package uses Swift tools
version 5.9 and has no third-party package dependencies. Swift source builds need Xcode Command Line Tools:

```sh
xcode-select --install
Expand Down Expand Up @@ -70,7 +83,8 @@ git fetch upstream
git rebase upstream/main
```

Run the test commands again after a rebase.
After a rebase, rerun checks affected by changed inputs and refresh independent
review. Reuse matching checks for unchanged artifacts and inputs.

## Commit messages

Expand All @@ -82,13 +96,15 @@ Add OpenRouter balance display

Do not mix unrelated fixes in the same commit.

## Build and test
<a id="build-and-test"></a>
## Legacy Swift build and test

GitHub Actions runs `swift build` and `swift test` on macOS 15 for pushes to
GitHub Actions runs `swift build` and `swift test` on macOS 15, plus Electron
typecheck, synthetic tests and production bundling on Linux, for pushes to
`main` and pull requests. Repository owners must enable Actions under
Settings > Actions > General for these checks to run.

Run these checks before a pull request:
For Swift source changes, run these checks before a pull request:

```sh
swift build
Expand Down Expand Up @@ -123,19 +139,103 @@ not move, and include the captures in the pull request. See

See [`docs/DEMO.md`](docs/DEMO.md) for the data and privacy rules.

## TypeScript Electron candidate

The source manifest retains 0.2.41. The public Electron beta is 0.3.0-beta.6,
build 75, with the version explicitly set during packaging. See the
[beta guide](docs/BETA.md) for the signed artifact and incomplete native
acceptance, and the [Electron guide](docs/electron/README.md) for source choices
and parity gaps.


Use the Node and pnpm versions declared in `package.json`. Provider accounts
are optional; synthetic tests and demo mode require no login. Shared checks
can run on Linux. Packaging, system symbol export and native acceptance require
macOS with the existing Swift and signing tools.

`pnpm package:mac` also compiles the fixed Claude Desktop Keychain helper and
bundles it as an app resource. `pnpm build:desktop-helper` targets arm64 and
macOS 13 explicitly, matching the candidate bundle. Verify the packaged helper's
architecture and deployment target with `file` and `vtool -show-build` on macOS.
The same build command prepares an unpackaged native candidate. Do not invoke the helper during synthetic
QA; it reads an actual Keychain item. Desktop connections are disabled in demo
mode. Live connection acceptance remains separate from source checks.

```sh
pnpm install --frozen-lockfile
pnpm typecheck
pnpm test
pnpm build
meterusage_demo_root="$(node -p 'require("node:fs").realpathSync(require("node:os").tmpdir())')"
node dist/main/cli.cjs json --force --demo --candidate-profile "$meterusage_demo_root/meterusage-cli-demo"
```

The candidate profile must be an empty test directory on first use. It may be
reused for relaunch checks after the candidate creates its marker. All demo
preferences, caches, history and Electron state stay in that profile.
Use the canonical temporary root above. On macOS, `/tmp` and `/var` can be
symlink aliases; candidate mode rejects paths that follow a symlink.

On macOS, build and launch a separate candidate without installing it:

```sh
pnpm package:mac
./release/mac-arm64/MeterUsage.app/Contents/MacOS/meterusage --demo --candidate-profile /private/tmp/meterusage-native-demo
```

`package:mac` exports the existing SF Symbol stand-ins, builds the app and
packages an Apple silicon ZIP with publication disabled. It does not install
or replace an app. Locally packaged output is not the verified public beta
artifact. Keep the stable release and beta download routes separate.
Windows packaging, CI, native testing and release remain paused.

electron-builder resolves `pnpm` from PATH when collecting production
dependencies. If an existing launcher incorrectly loads a standalone pnpm
executable as JavaScript, verify the executable directly, then put a `pnpm`
symlink to it in a separate candidate-local directory at the start of PATH.
Keep host launchers and installed dependencies unchanged. This PATH correction
still requires a new native packaging check.

Changes live in `src/domain/` (shared calculations), `src/main/` (provider
readers, persistence, coordination and native shell), `src/shared/` (IPC DTOs),
`src/renderer/` (React UI) and `tests/` (synthetic fixtures). Provider paths,
credentials and raw responses must stay out of renderer imports. Settings may
show a reduced directory label chosen by the user.

TypeScript tests and builds do not prove native parity. Before cutover, record
candidate identity/digest/signature, headless JSON, launch and relaunch,
settings/account isolation, offline failures, notification/login behavior,
sharing and updater paths. Capture two different-height provider cards on
macOS and confirm the top edge and strip stay fixed. Use only synthetic reset
credits. Keep the published app and its data untouched during candidate QA.
Retained Swift sources are the behavior oracle and rollback path. Run affected
Swift checks when those inputs change; reuse matching receipts otherwise.

See [ADR 0010](docs/adr/0010-macos-typescript-electron.md) and
[the migration plan](docs/plans/macos-electron-migration-plan.md).

## Where to make changes

New providers and data sources belong in Electron. Use the `src/` and `tests/`
map in [Electron development](#typescript-electron-candidate).

### Legacy Swift maintenance

Use these paths only for critical fixes, transition compatibility and rollback:

- `Sources/MeterUsage/Services/` contains provider readers, pricing, and
service-status sources.
- `Sources/MeterUsage/Core/` contains coordination and saved preferences.
- `Sources/MeterUsage/Views/` contains the SwiftUI menu-bar interface.
- `Tests/MeterUsageTests/` contains unit tests and synthetic fixtures.

When adding a provider or data source, use the existing `QuotaSource`,
When maintaining a legacy provider or data source, use the existing `QuotaSource`,
`UsageSource`, `LocalActivitySource`, or `StatusSource` contract in
`Sources/MeterUsage/Services/DataSource.swift`. Keep provider failures isolated
so one unavailable provider does not hide the others. Add or update the model,
coordinator, settings, and tests only when the feature needs them.
coordinator, settings, and tests only when the maintenance change needs them.

### Shared contribution rules

New providers must have synthetic demo data and tests. Normal tests and
screenshots must not require a live login.
Expand All @@ -162,18 +262,19 @@ positive.

## Generated assets

Run `./Scripts/make-icon.sh` only when the icon design changes. The app bundle
is assembled from source by `./Scripts/make-app.sh --build-from-source`.
Output under `dist/` is ignored
and should not be force-added.
Run `./Scripts/make-icon.sh` only when the icon design changes. Build the legacy
Swift app bundle with `./Scripts/make-app.sh --build-from-source`. For Electron
packaging, follow [Electron development](#typescript-electron-candidate).
Generated output under `dist/` and `release/` is ignored and should not be
force-added.

## Pull requests

A pull request should include:

- the user-visible change and why it is needed;
- the files or provider paths affected;
- the exact checks you ran, including `swift test`;
- the exact checks you ran, including the affected Swift or TypeScript checks;
- any privacy, network, or credential-handling impact; and
- demo-mode screenshots when a UI change needs visual review, plus captured
side notch frames when the change touches panel layout or motion.
Expand Down
29 changes: 15 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ Swift app remains the stable download and rollback option during migration.

| Edition | Current status | Setup and behavior |
| --- | --- | --- |
| Electron 0.3.0-beta.3, build 61 | Public beta for Apple silicon, macOS 13+; signed and notarized | [Download beta](https://github.com/pekth/meterusage/releases/tag/v0.3.0-beta.3) · [Beta guide](docs/BETA.md) |
| Electron 0.3.0-beta.6, build 75 | Public beta for Apple silicon, macOS 13+; signed and notarized | [Download beta](https://github.com/pekth/meterusage/releases/tag/v0.3.0-beta.6) · [Beta guide](docs/BETA.md) |
| Legacy Swift 0.2.41 | Frozen for new features; published stable download and migration rollback | [Swift guide](docs/swift/README.md) |

Electron is the active app for new features. Swift remains the stable download
and rollback reference. The beta is installed manually; the stable updater does
not offer it. Quit the existing MeterUsage before replacing it, and keep a copy
and rollback reference. Install the beta manually once. From beta 5 it checks
GitHub for newer betas and offers to install them; the stable updater does
not offer betas. Quit the existing MeterUsage before replacing it, and keep a copy
for rollback. Both apps share their identity and saved data, so do not run them
together against a normal profile. See [beta installation and limits](docs/BETA.md)
and the [version strategy](docs/VERSIONS.md).
Expand Down Expand Up @@ -71,14 +72,14 @@ All nine provider adapters remain in the retained Swift and candidate source:
Codex, Claude Code, Antigravity, OpenRouter, Grok, OpenCode Go, Cursor, Copilot
CLI and Gemini CLI. Their inputs and available measurements differ. The
[Swift provider matrix](docs/swift/README.md#provider-matrix) describes existing
CLI/local collection. The [Electron source choices](https://github.com/pekth/meterusage/blob/v0.3.0-beta.3/docs/electron/README.md#source-choices)
CLI/local collection. The [Electron source choices](https://github.com/pekth/meterusage/blob/v0.3.0-beta.6/docs/electron/README.md#source-choices)
also describe optional Codex browser sign-in and Claude Desktop allowance.

Local activity covers this Mac. Provider-reported account data has its own
coverage limits. Missing tokens remain unknown. Local token costs are estimates; optional API
billing cards label provider-reported amounts separately. Provider dashboards
remain the billing source of record. Read the [beta privacy contract](https://github.com/pekth/meterusage/blob/v0.3.0-beta.3/docs/PRIVACY.md)
for Electron or the [Swift privacy contract](docs/PRIVACY.md) before changing a
remain the billing source of record. Read the [beta privacy contract](https://github.com/pekth/meterusage/blob/v0.3.0-beta.6/docs/PRIVACY.md)
for Electron or the [retained Swift reporting privacy contract](docs/PRIVACY.md#legacy-swift-source-diagnostic-reports) before changing a
data source or sharing screenshots.

## Can both editions coexist?
Expand All @@ -87,20 +88,20 @@ The beta tag contains the Electron source and retained Swift tree. The default
branch retains stable Swift source during migration; the intended end state is one
Electron app. Normal-profile side-by-side installation is unsafe:
the app name, bundle ID and default preferences/data locations are shared.
Use only the documented [isolated candidate QA profiles](https://github.com/pekth/meterusage/blob/v0.3.0-beta.3/docs/electron/README.md#isolated-qa)
Use only the documented [isolated candidate QA profiles](https://github.com/pekth/meterusage/blob/v0.3.0-beta.6/docs/electron/README.md#isolated-qa)
for testing. A separate app identity and data root require a later decision;
there is no automatic cutover. See [ADR 0010](https://github.com/pekth/meterusage/blob/v0.3.0-beta.3/docs/adr/0010-macos-typescript-electron.md).
there is no automatic cutover. See [ADR 0010](https://github.com/pekth/meterusage/blob/v0.3.0-beta.6/docs/adr/0010-macos-typescript-electron.md).

## Documentation

- [Beta download, verification and limits](docs/BETA.md)
- [Current beta screenshots](docs/SCREENSHOTS.md)
- [Swift guide](docs/swift/README.md)
- [Electron guide and known gaps](https://github.com/pekth/meterusage/blob/v0.3.0-beta.3/docs/electron/README.md)
- [Electron guide and known gaps](https://github.com/pekth/meterusage/blob/v0.3.0-beta.6/docs/electron/README.md)
- [Versions and staged Swift retirement](docs/VERSIONS.md)
- [Contributing](CONTRIBUTING.md)
- [Project knowledge](docs/KB.md) and [decision index](docs/adr/README.md)
- [Desktop connections](https://github.com/pekth/meterusage/blob/v0.3.0-beta.3/docs/DESKTOP-CONNECTIONS.md), [demo mode](docs/DEMO.md)
- [Desktop connections](https://github.com/pekth/meterusage/blob/v0.3.0-beta.6/docs/DESKTOP-CONNECTIONS.md), [demo mode](docs/DEMO.md)
and [side notch](docs/SIDE-NOTCH.md)
- [Concept mockups](docs/mockups/README.md) and [changelog](CHANGELOG.md)

Expand All @@ -113,11 +114,11 @@ the Swift guide; product screenshots now show the Electron beta.
See [downloads](#download).

<a id="macos-electron-migration-candidate"></a>
See the [Electron candidate guide](https://github.com/pekth/meterusage/blob/v0.3.0-beta.3/docs/electron/README.md).
See the [Electron candidate guide](https://github.com/pekth/meterusage/blob/v0.3.0-beta.6/docs/electron/README.md).

<a id="second-accounts"></a>
See [Swift additional local accounts](docs/swift/README.md#second-accounts) and
[Electron advanced local accounts](https://github.com/pekth/meterusage/blob/v0.3.0-beta.3/docs/electron/README.md#advanced-local-accounts).
[Electron advanced local accounts](https://github.com/pekth/meterusage/blob/v0.3.0-beta.6/docs/electron/README.md#advanced-local-accounts).

<a id="requirements"></a>
See [Swift requirements](docs/swift/README.md#requirements).
Expand All @@ -127,7 +128,7 @@ See [contributor setup](CONTRIBUTING.md).

<a id="agent-budget-cli"></a>
See the [Swift Agent Budget CLI](docs/swift/README.md#agent-budget-cli) and
[Electron commands](https://github.com/pekth/meterusage/blob/v0.3.0-beta.3/docs/electron/README.md#build-and-test).
[Electron commands](https://github.com/pekth/meterusage/blob/v0.3.0-beta.6/docs/electron/README.md#build-and-test).

<a id="showcase"></a><a id="-showcase"></a>
<a id="ambient-time-to-empty--side-notch-hud"></a>
Expand All @@ -150,7 +151,7 @@ See [installation details](docs/swift/README.md#download-and-installation).

<a id="report-a-problem"></a>
See [beta diagnostics and bug reports](docs/BETA.md#known-limits) and the
[beta reporting guide](https://github.com/pekth/meterusage/blob/v0.3.0-beta.3/docs/REPORTING.md).
[beta reporting guide](https://github.com/pekth/meterusage/blob/v0.3.0-beta.6/docs/REPORTING.md).

<a id="-tldr"></a><a id="tldr"></a>
See [downloads](#download).
Expand Down
15 changes: 15 additions & 0 deletions Scripts/electron/after-pack.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const { renameSync } = require("node:fs");
const { join } = require("node:path");
const { execFileSync } = require("node:child_process");

// executableName also changes electron-builder's bundle filename. Keep the
// product bundle name, then rename only its main executable before signing.
function renameExecutable(bundle, command = execFileSync) {
renameSync(join(bundle, "Contents/MacOS/MeterUsage"), join(bundle, "Contents/MacOS/meterusage"));
command("/usr/libexec/PlistBuddy", ["-c", "Set :CFBundleExecutable meterusage", join(bundle, "Contents/Info.plist")]);
}
module.exports = async context => {
if (context.electronPlatformName !== "darwin") throw new Error("Only macOS candidate packaging is enabled");
renameExecutable(join(context.appOutDir, "MeterUsage.app"));
};
module.exports.renameExecutable = renameExecutable;
Loading
Loading