Skip to content

v6.15.0

Choose a tag to compare

@ndycode ndycode released this 31 Aug 13:10
· 133 commits to main since this release

https://www.npmjs.com/package/oc-codex-multi-auth/v/6.15.0

npx oc-codex-multi-auth@6.15.0 install

Added

Desktop quota notifications for macOS (#239). Turn it on with quotaNotifications.enabled: true, or CODEX_AUTH_QUOTA_NOTIFICATIONS=1:

{
  "quotaNotifications": {
    "enabled": true,
    "intervalMs": 1800000,
    "notifyEveryCheck": false,
    "thresholds": [25, 10, 0]
  }
}

The plugin polls every enabled account on that interval and alerts through Notification Center when the pool's 5-hour or weekly quota crosses a threshold:

5h: 10% | resets 22:30
Weekly: 72% | resets 22:30 on Aug 30

Each line reports the account with the most headroom in that window together with that same account's reset, so the percentage and the reset always describe one real account. A window the plan has switched off is skipped rather than counted as a full quota. Account identities are never shown or persisted, so nothing identifying appears on a lock screen.

notifyEveryCheck: true delivers the summary after every successful poll instead of only on a crossing. thresholds: [] turns threshold alerts off. intervalMs defaults to 30 minutes with a 30-second floor. Delivery state is shared across processes working in the same account scope, so concurrent instances produce one routine alert per interval.

Delivery uses the built-in osascript, with no extra dependency. The feature is off by default and is unavailable on Windows and Linux.

Fixed

An unattended quota refresh could kill an account (#239, #240). The monitor refreshes any account whose access token is near expiry, and a refresh rotates a single-use refresh token durably to disk. The in-process AccountManager cache kept the old token, and its debounced save overwrote the rotation; the next refresh then returned refresh_token_reused and the account stayed dead until a fresh login. The monitor now drops that cache at the moment a rotation persists, before that account's usage call. Invalidating at the end of a check was not enough: the save debounce is 500 ms and a check over several accounts takes far longer.

A malformed usage document threw instead of reading as empty. fetchCodexUsage casts response.json() straight to its declared type, so a 200 carrying the body null, a non-array additional_rate_limits, or a non-string limit_name reached dereferences that assumed the declared shape. The gateway in front of /wham/usage is user-configurable through OPENAI_BASE_URL, so those are reachable responses. A non-object payload now reads as an empty document and renders as unavailable.

A disabled quota window was scored as a full quota (#239). A window the plan has switched off still reports used_percent: 0. Without the hasUsageWindow guard it counted as 100% remaining and masked every other account's low quota, so the alert never fired at all for an affected pool.

The alert could describe a quota no account had (#239). The aggregate paired the highest remaining percentage with the earliest reset across different accounts, so a pool could be told it had 60% left and would recover at a time that restores nothing.

Concurrent processes dropped whole quota checks (#239). Notification delivery was awaited inside the cross-process state lease, whose retry budget is roughly 660 ms against a 10-second delivery timeout. A second process exhausted its budget and abandoned a check whose network fetches it had already paid for. Delivery now claims the slot under the lease, delivers outside it, and releases the claim if delivery failed.

The quota monitor outlived shutdown (#239, #240). Its only teardown was the server.instance.disposed event, so the timer survived SIGINT/SIGTERM and any host that does not emit it. It now registers with the shared shutdown drain, and it stops polling once it observes the feature switched off, an unsupported platform, or a configuration that can never deliver.

quotaNotifications.thresholds: [] was ignored (#239). An explicitly empty list was replaced by the [25, 10, 0] default, so no configuration could run notifyEveryCheck on its own.

Quota state could leak between projects (#239, #240). The in-memory threshold state was not keyed by state path, and the path was resolved after the network fetches rather than beside the account load that produced the aggregate, so switching projects mid-check could write one project's thresholds into another's file.

Changed

Quota reset times stay on the 24-hour clock (#239). Moving to 12-hour formatting would have changed every quota display in the product, left the TUI status line rendering 22:30 where codex-limits rendered 10:30 PM, and forced a 12-hour clock on locales that do not use one.

Internal

test/quota-notifications-fetch.test.ts covers the monitor's default fetch path, which previously had none because every test injected a fake (#240). That gap is how the token-cache ordering defect above reached main.

quotaNotifications is documented in docs/development/CONFIG_FIELDS.md, the notification state file in AGENTS.md, and the three new modules in lib/AGENTS.md, which the repo treats as a documented contract (#239).


#239 by @lubshad.