Skip to content

About

No description, website, or topics provided.

Resources

Stars

5 stars

Watchers

0 watching

Forks

Repository files navigation

AIusageBar

How much of your Claude Code and Codex subscription is left, at a glance, on every screen you own: a GNOME tray, a Wear OS watch, Android and iPhone home screens. Several Claude accounts pool together.

One Go program, aiusage-server, reads the usage on the desktop, owns pairing and alerts, and serves a small JSON API. Everything else is a client of that API: the Linux tray (aiusagebar), the Wear OS app, the Android app and widgets and the iPhone app and widgets. Closing any client leaves the server running.

Local provider CLIs and usage caches feed the Go usage server, which serves the Linux tray, Android, Wear OS and iOS clients

The orbit

Every client draws your accounts the same way: each account's week is a thread, bright for what's left and faint where it's been used, with a bead where it runs out. Claude's accounts twist round the outer ring (orange, tan and further shades), Codex's round the inner one (greens).

Wear OS: the orbit tile Tap a ring: Claude Tap a ring: Codex
Watch tile: two Claude accounts twisted round the outer ring, one about two thirds through its week and one spent; the Codex ring inside Watch: the Claude ring softened, with 5H 71% and WEEK 32% circling it and the Claude mark in the middle Watch: the Codex ring softened, its figures circling it and the Codex mark in the middle

On the watch the orbit is a tile and the app's first page. Tap a ring and it blurs slightly, the family's session and week left circle over it, slowly turning, and its mark sits in the middle; tap again to put it back. The blur and lettering are drawn once per data change and cached, so a tap costs nothing but a redraw.

Android: orbit Android: threads Android app
Android 2x2 orbit widget: three Claude and three Codex accounts as threads in two rings Android 4x1 threads widget: every account's week twisted into one rope Android app: Claude's Session (42% left, +33% in 41m) and Weekly (56% left) cards with a bar segment and a row per account
Android: dash Session out Tap card
Dash widget: 10% of the session left, 38% of the week, with pace estimates Dash widget: session out, 2:51 until the 5h reset Tap card: Claude across two accounts and Codex, a 12-hour graph of the pooled session with reset steps, and every account's week as twisted threads
iPhone: orbit and threads iPhone app App icon (the ring)
iPhone Home Screen: the orbit widget, and the threads widget with 5H LEFT 42%, BURN 1.6%/H and USABLE NOW 39% iPhone app: Claude's Session and Weekly cards with a row per account The app icon: the ring face, session as the ring and the week as the pie

Screenshots of the phones use made-up accounts (the debug fixture); the watch's are live.

What each client shows

  • Wear OS (wearos/): the orbit as a tile and as the app, with the tap-a-ring figures; a Setup page (provider, pairing, the API-cost estimate); two complications (% left and time to the reset that matters) and the server's alerts, including "Claude's back" when a spent limit resets.
  • Android (android/): five widgets in Nothing OS's style: the pooled 1x1 ring, the 2x2 ring, the 2x2 orbit, the 4x2 dash and the 4x1 threads. The app shows each family's Session and Weekly cards (the share of the whole family left, what the next reset brings back, pace, a bar split per account, a row per account). Tapping a widget opens a compact card: both families' limits, a five-hour history graph of the pooled session with upcoming reset steps, and every account's week as threads.
  • iPhone (ios/): the same app and tap card, and Android's widgets where iOS has a size for them: the orbit (small), the dash (medium), the threads (medium, with the selected family's session left, burn rate and what's usable now under them; also on the Lock Screen) and the pooled ring (Lock Screen). iOS has no 1x1 Home Screen widget, so the ring is the app's icon.
  • Linux tray (aiusagebar):
    • Tray icon = two small meters (green/orange/red, grey when stale) for the label's two numbers. Tray text = one provider's windows (C1 5h 42% · 7d 61%) or, with two Claude accounts, each account's tighter limit (Claude 42 · 17%).
    • Click menu shows:
      • Session (5h) and Week (7d) limit utilization and when each resets, for every provider the server serves (Claude, Claude 2, Codex)
        • read straight from Anthropic's own cached usage data in ~/.claude.json (the same numbers Claude Code itself shows you), so the percentages and reset times are exact, not estimated.
      • API-cost equivalent for today / this session / this week - what the same token usage would have cost on pay-as-you-go API pricing, computed from the token counts logged in ~/.claude/projects/**/*.jsonl using current per-model Anthropic pricing (updated 2026-09). Each reply is counted once: Claude Code writes a transcript line per content block, all repeating the reply's usage, so lines are deduplicated by message id + request id. Cache reads use each model's own rate (Opus 5.5 and Fable 5.1 aren't the usual 0.1x), and 1-hour cache writes are 2x input. T3 Code prices every cache write at the 5-minute 1.25x rate, so it shows a little less for the same usage.
    • Refreshes every 30s, or on demand via the "Refresh" menu item.
    • The independent server serves these numbers to every client over the same API. The tray shows connection/source freshness and retains visibly stale values when offline.

Subscription plans (Pro/Max) don't expose a dollar limit or a metered balance - only a percentage of your session/weekly allowance. The cost figure here is a separate, independent estimate of value, not what you're being billed.

Build

CGO_ENABLED=0 go build -o aiusage-server ./cmd/aiusage-server  # Go only; no GTK
go build -o aiusagebar .                                    # Linux tray client

Run go test -race ./... for the Go checks, and python3 scripts/check-split.py ./aiusage-server ./aiusagebar for isolated pairing and restart checks with fake provider sources.

The server needs Go 1.22+ only. The tray additionally needs a C compiler (gcc) and GTK/AppIndicator development headers:

sudo apt install build-essential libgtk-3-dev libayatana-appindicator3-dev

The watch and phone apps are separate Gradle builds (Android SDK, JDK 17+), installed by sideloading. Both pair with the server (see Pairing a device):

cd wearos  && ./gradlew :wear:assembleRelease   # wear/build/outputs/apk/release/
cd android && ./gradlew :app:assembleRelease    # app/build/outputs/apk/release/

Or skip the build: each widget-v* release on the Releases page has the phone APK, and from 0.12.0 the watch APK too. Both are signed with a local debug key, so a release built on a different machine than the one you installed from won't install over it: uninstall first (and pair again).

The iPhone app is an Xcode project generated with XcodeGen, built on a Mac (Xcode 26+, iOS 17+) and installed from Xcode with your own signing team:

cd ios && xcodegen && open AIusageWidget.xcodeproj

On the Home Screen, choose the Clear appearance for WidgetKit's Liquid Glass widget background; tinted and Lock Screen appearances work too. Pick Claude, Claude 2 or Codex under the app's "•••": the dash follows it, the pool and the threads' figures pool that pick's family, and the orbit and the threads always show every account.

Run the server and tray

Start the server on the machine where the provider CLIs are signed in:

./aiusage-server -serve :8765

Pair the Linux tray just like another device. On the server machine, mint a code:

./aiusage-server -pair

Then configure the tray using that code (replace the example):

./aiusagebar -server http://127.0.0.1:8765 -pair 123456
./aiusagebar

For a server on another machine, use its private LAN IP or public HTTPS URL. The tray stores its own device token and chosen provider in $XDG_CONFIG_HOME/aiusagebar/client.json (default ~/.config/aiusagebar/client.json, mode 0600). It never reads provider credentials, transcripts, or server state. Changing -server clears the old token; pair with the new server before using it. The menu lists every provider the server serves: Claude (or Claude 1 and Claude 2, each with its account email, when the server runs a CLIProxyAPI hub with two subscriptions) and Codex, each with its 5h and weekly meters, reset times and any source error. Claude 2 only appears once the server has delivered limits for it. -provider pool|claude|claude2|codex chooses what the top-bar label shows, and the menu's "Top bar shows" (or a click on a section heading) changes it. pool, the default for new configs, shows both Claude accounts as Claude 42 · 17%: each number is that account's tighter limit (the higher of its 5h and weekly use), and the icon's two bars match the two numbers. A single provider shows C1 5h 42% · 7d 61%. Saved configs naming claude or codex keep working. -check fetches one JSON snapshot without opening the tray (an array of both Claude accounts for pool), useful for diagnostics. -config /path/to/client.json selects a different client configuration.

Serve usage over the LAN

The standalone server supports the existing phone/watch API and flags:

./aiusage-server -serve :8765
./aiusage-server -serve :8765 -token s3cr3t  # optional shared secret

The server advertises _aiusage._tcp over mDNS through avahi-publish-service when available. -mdns=false disables LAN discovery.

The server retains $XDG_STATE_HOME/aiusagebar/state.json and the local aiusagebar.sock control socket, so existing mobile tokens, alert history and pairing behavior survive the split. The tray's token is a separate device entry.

To upgrade from the combined binary, stop that process, start aiusage-server with its previous server flags, then pair and start the new aiusagebar client. The tray no longer accepts -serve, -headless, server refresh settings, or server pairing-code creation; those responsibilities belong to aiusage-server.

Codex

Install the official Codex CLI on the server machine and sign in with your ChatGPT account using codex login. The bridge uses that CLI's existing login via account/rateLimits/read, without opening a thread or making a model request. API-key-only accounts do not supply subscription quota windows through this method.

./aiusage-server -serve :8765 -codex /path/to/codex  # optional; PATH or ~/.local/bin/codex by default

The desktop menu switches between Claude and Codex. On Android, the widget's tap card and the app show both side by side; pick which one the widgets draw with its "Show on widgets" button. On iOS, the app and the card show both too; pick the widgets' provider under the app's "•••". Each phone's ring and dash widgets follow that selection. On the watch, choose the provider on the Setup page; its complications and notifications follow that selection, while the orbit always shows every account. Pairing works for both providers with the same device token.

Codex is sampled at most once a minute, including requests from multiple clients. If the CLI is missing, signed out or offline, the bridge reports source_error and retains the last successful limits and their original cache_fetched_at. Clients dim stale data and retry. An absent quota window is shown as unknown. Codex cost estimates are unavailable (cost_available: false); the numeric cost fields are compatibility placeholders.

Endpoints

Endpoint What it does
GET /usage current snapshot for ?provider=claude (default), claude2 (second hub account, see Several Claude accounts) or codex, refreshed first if it's stale (see Freshness). Sends an ETag; send it back as If-None-Match to get 304 when nothing has changed
GET /events?since=<id> alert events for the selected provider (default claude; append &provider=claude2 or &provider=codex), including threshold crossings and window resets newer than <id>, plus the new high_water mark
GET /pool?family=claude|codex the pooled limit across a family's accounts, with recorded history and a projection (see Pooled limits). Same auth as /usage; ETag/If-None-Match work too
POST /pair {"code":"123456"} redeems a pairing code for a per-device token (plus public_url, if set)
GET /healthz liveness, no auth, no usage data. Says whether a token is needed
GET /share off unless -share is set. The selected provider's limits only (?provider=codex, claude2), never the account email, no token, readable from web pages (see Show it on a website)
{
  "schema": 1, "provider": "claude", "cost_available": true,
  "five_hour": {"utilization": 53, "remaining": 47, "resets_at": "2026-09-21T18:20:00Z", "resets_in_sec": 16631},
  "seven_day": {"utilization": 41, "remaining": 59, "resets_at": "2026-09-27T00:00:00Z", "resets_in_sec": 469031},
  "cost_today": 251.02, "cost_session": 25.06, "cost_week": 351.44,
  "generated_at": "2026-09-21T13:43:29Z", "cache_fetched_at": "2026-09-21T13:43:12Z", "host": "pop-os"
}

The ETag deliberately ignores generated_at and resets_in_sec: they change on every poll but carry no news, and a 304 over a Bluetooth proxy is nearly free next to a full body. A 304 means "your copy is still current now", so use cache_fetched_at for the age of the source data when it is present, and otherwise use the last successful fetch time. ETags include the source fetch time so a fresh quota read updates that age even if usage has not changed. ETags also include the provider, source errors and window lengths.

five_hour and seven_day retain their schema-1 names for both providers. For Codex these carry the primary and secondary windows, respectively, and include window_minutes: use that duration for labels, pace and countdown fractions. Neither window has a fixed duration; an hourly primary and daily secondary are also supported. Reset times always come from resets_at. Optional source_error means the upstream source is unavailable; any attached limits are stale. Older bridges that omit provider and cost_available are treated as Claude. An optional account (the subscription's email) is sent in hub mode; clients that don't know it ignore it.

Freshness

Claude limits come from Claude Code's cache in ~/.claude.json, which only moves when Claude Code fetches usage, normally during a session. So asking is refreshing: when a client calls GET /usage and that cache is more than a minute old, the bridge first runs the official CLI's local /usage command (claude -p /usage; no model call, a couple of seconds), which makes Claude Code fetch current numbers, and then answers. That's at most one CLI run a minute however many clients ask, and concurrent requests share it. A client's refresh button and its background poll are the same request; there's no separate refresh endpoint.

cache_fetched_at is when Claude Code last fetched the limits, so the true age of the numbers. The bridge never talks to Anthropic itself.

./aiusage-server -serve :8765 -refresh-min-age 5m     # refresh less eagerly
./aiusage-server -serve :8765 -refresh-min-age 0      # never; serve the cache as Claude Code leaves it
./aiusage-server -serve :8765 -claude /opt/bin/claude # if claude isn't on PATH or in ~/.local/bin

Pooled limits (/pool)

GET /pool?family=claude|codex (default claude; anything else is 400; optional days=1..14 sets how much history to return, default 7) answers "how much can I still use across all my accounts?". Claude's family is every hub account, Codex's is a pool of one. Numbers are in account-percent: one account is 100, so two accounts have capacity_pct 200. The wire types are in internal/protocol/pool.go.

{
  "family": "claude", "accounts": 2, "capacity_pct": 200,
  "remaining_pct": 131.0, "five_hour_remaining_pct": 103.0, "effective_remaining_pct": 103.0,
  "burn_pct_per_hour": 1.63,
  "next_reset": {"at": "2026-11-01T02:48:00Z", "gain_pct": 65.4},
  "first_empty_at": "2026-11-06T05:29:57Z", "pool_empty_at": null,
  "members": [{"provider": "claude", "account": "a@x.com", "five_hour": {"...": "as /usage"}, "seven_day": {"...": "as /usage"}, "source_error": ""}],
  "history": [{"t": 1793393280, "remaining_pct": 131.0, "five_hour_remaining_pct": 103.0, "effective_remaining_pct": 103.0}],
  "projection": [{"t": 1793393280, "remaining_pct": 131.0, "reset": false}],
  "generated_at": "2026-10-31T02:48:00Z", "source_error": ""
}
  • remaining_pct is the weekly pool (Σ 100 - seven_day.utilization); five_hour_remaining_pct the same over the five-hour windows; effective_remaining_pct sums, per account, the smaller of the two, which is what is usable right now. A window an account doesn't report stands in for the other one.
  • burn_pct_per_hour is how fast remaining_pct dropped over the last 3 hours of history (resets inside the window don't offset it); null until there's a minute of history.
  • projection plays the pool forward 7 days: one point per hour plus, at each weekly reset, two points with the same t (value just before, then the value after with reset: true). It drains at the burn rate split evenly across the accounts that still have weekly capacity, never below 0, and an account jumps back to 100 at its seven_day.resets_at (and every window length after: 7 days, or Codex's window_minutes). With no burn it is flat apart from the step-ups.
  • first_empty_at / pool_empty_at come from that projection (null if not within 7 days; pool_empty_at is "now" if every account is already at 0). next_reset is the soonest reset and what it adds back.
  • No account with data (Claude without a signed-in hub account, say) gives accounts: 0, empty arrays and a source_error; it is not an HTTP error.

History is sampled by the poll loop and on each /pool request whenever the pool's numbers change (at least 5 minutes apart, plus an hourly sample even if nothing changed), kept for 14 days and stored as JSON lines in $XDG_STATE_HOME/aiusagebar/pool-history-<family>.jsonl (mode 0600), so it survives restarts. The ETag covers the underlying snapshots and a 15-minute time bucket, because the projection is anchored at "now". New history samples also include five_hour_remaining (remaining percent, 0–100) and five_hour_resets_at (Unix seconds), maps keyed by provider such as claude, claude2 or codex. Only accounts reporting a five-hour window are included, and idle windows may have no reset. Older samples omit these maps; clients can fall back to the pooled five-hour sum.

Several Claude accounts (CLIProxyAPI hub)

With more than one Claude subscription pooled behind a local CLIProxyAPI hub, Claude Code no longer refreshes ~/.claude.json for any one of them. Point the server at the hub and it reads each account's limits from there instead:

./aiusage-server -serve :8765 -cliproxy-url http://127.0.0.1:8317 -cliproxy-key-file ~/.cli-proxy-api/keys.env

The key file is env-style and must hold a CPA_MGMT_KEY=... line (the hub's management key; it is re-read on every poll and never logged). In this mode:

  • The hub's enabled Claude accounts, sorted by email, become providers claude (first) and claude2 (second). A missing account is a source_error.
  • The hub is asked at most every 5 minutes (15s timeout): its management auth-files route lists accounts, and its api-call route fetches Anthropic's usage for each one on the account's own token. The hub calls Anthropic; the server only talks to the hub. The claude -p /usage refresher is not used. If the hub fails, the last limits are kept and source_error says so.
  • Transcripts are pooled across accounts, so the cost estimate stays on claude (windowed by that account's resets); claude2 has cost_available: false. Alerts are armed separately for each provider.

Claude's resets_at is rounded to the minute: Anthropic's value wobbles by a second between fetches, which would otherwise look like the window rolling over. Codex reset times are preserved to the second.

Alerts

The bridge is the only thing that decides an alert; clients just render what they're told. Crossing 80%, 95% or 100% (exhausted) on either window raises an event, and so does a window resetting. One crossing produces exactly one event: it re-arms only when the window rolls over or utilization falls -hysteresis points back below the line, and the arming state is persisted, so restarting the bridge doesn't replay history.

./aiusage-server -serve :8765 -thresholds 80,95,100 -hysteresis 5

Cost is deliberately not alertable - it's an independent estimate of value, not a bill, and a "you've spent $X" notification would imply a charge that isn't happening.

Pairing a device

Typing a 32-character token on a watch is not a plan. Instead:

./aiusage-server -pair       # asks the running bridge for a code, prints it

The server prints a 6-digit code, good for one use and two minutes; the device posts it to /pair and gets back its own token. Ten wrong codes in a row burn every pending code, so the 6-digit space can't be walked.

Codes are minted only over a Unix socket ($XDG_RUNTIME_DIR/aiusagebar.sock, mode 0600), never over HTTP. A tunnel or reverse proxy on this machine delivers internet traffic from 127.0.0.1, so a "loopback only" HTTP endpoint would let anyone who can reach the tunnel mint a code. The socket is reachable only by your own user. Pairing one device closes the bridge: once any device is paired, unauthenticated requests get a 401, and each device's token can be revoked on its own by deleting it from the state file.

State - device tokens, the event ring, alert arming - lives in $XDG_STATE_HOME/aiusagebar/state.json (mode 0600), usually ~/.local/state/aiusagebar/state.json.

The bridge itself is plain HTTP with no transport security. On the LAN, keep it to a trusted network. To reach it from anywhere, put TLS in front with a tunnel:

Reach it from anywhere

Either tunnel works; the bridge doesn't care which one is in front of it. Run it with -public-url set to the tunnel's address either way.

Tailscale Funnel (no domain needed)

Free, with a stable https://<machine>.<tailnet>.ts.net address and TLS included. Funnel exposes only the port you give it, not the rest of the machine or your tailnet, and clients don't need Tailscale installed.

curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
sudo tailscale funnel --bg 8765      # prints the public URL; persists across reboots
./aiusage-server -serve :8765 -public-url https://<machine>.<tailnet>.ts.net

sudo tailscale funnel reset turns it off. Funnel forwards every path, which is fine: /pair/new doesn't exist on TCP, and everything else needs a token.

Cloudflare Tunnel (needs a domain)

A Cloudflare named tunnel gives the bridge a stable https:// hostname without opening a port on your router. Cloudflare terminates TLS; cloudflared on this machine forwards to the bridge on loopback. It needs a Cloudflare account and a domain on it. The free "quick tunnel" (trycloudflare.com) won't do: its hostname changes every restart, and the watch needs one that stays put.

The quick way, which does everything below (login, tunnel, DNS, a config that only forwards the bridge's routes, a user-level systemd service, and server service):

scripts/setup-tunnel.sh usage.example.com

Or by hand (usage.example.com is your hostname):

cloudflared tunnel login                      # opens a browser, picks the domain
cloudflared tunnel create aiusage             # prints the tunnel UUID
cloudflared tunnel route dns aiusage usage.example.com

~/.cloudflared/config.yml:

tunnel: <UUID>
credentials-file: /home/<you>/.cloudflared/<UUID>.json
ingress:
  # Only the bridge's public routes cross the tunnel; everything else 404s.
  - hostname: usage.example.com
    path: ^/(usage|events|pair|share|healthz)$
    service: http://127.0.0.1:8765
  - service: http_status:404

Then run both:

cloudflared tunnel run aiusage
./aiusage-server -serve :8765 -public-url https://usage.example.com

-public-url does two things:

  • Every request needs a credential from the start. Without it, an unpaired bridge answers anyone; with it, the internet can reach the port, so /usage and /events return 401 until a device has paired.
  • It's handed to devices when they pair. A watch pairs once on home Wi-Fi, where it finds the bridge over mDNS, and the /pair response tells it the public URL to use from then on. Nobody types a hostname on a watch.

Send the token as Authorization: Bearer …, not ?token=: query strings end up in proxy logs. Keep -serve :8765 (all interfaces) if you want LAN pairing; bind 127.0.0.1:8765 instead to make the tunnel the only way in, at the cost of pairing through the public URL.

cloudflared service install runs the tunnel at boot (see Cloudflare's docs; it reads /etc/cloudflared/config.yml).

Don't rely on an unguessable hostname

Every HTTPS certificate is published in Certificate Transparency logs, and scanners watch them, so a public hostname is found within minutes however random it is. The token is what protects the bridge. It's invisible in use, because a client pairs once and sends the token from then on.

Show it on a website

-share opens one read-only route for the public: your remaining session and weekly allowance, for a personal site, a status page, a README badge service.

./aiusage-server -serve :8765 -public-url https://usage.example.com -share https://you.example
./aiusage-server -serve :8765 -public-url https://usage.example.com -share '*'   # any site
curl https://usage.example.com/share
{
  "schema": 1,
  "five_hour": {"utilization": 53, "remaining": 47, "resets_at": "2026-09-21T18:20:00Z", "resets_in_sec": 16631},
  "seven_day": {"utilization": 41, "remaining": 59, "resets_at": "2026-09-27T00:00:00Z", "resets_in_sec": 469031},
  "cache_fetched_at": "2026-09-21T13:43:12Z",
  "generated_at": "2026-09-21T13:43:29Z"
}
  • It needs no token, even on a bridge that is closed by -public-url, and it is the only route that doesn't. Cost and hostname are left out.
  • Browsers can read it only from the origins you list; * allows any. Origins match exactly (https://you.example, no path).
  • It serves the last 30-second sample and never runs the CLI refresh, so page views can't make your machine spawn anything. The numbers move whenever Claude Code fetches its limits, which is often while you work. When resets_at is in the past, that window has reset: show it as full.
  • It's sent with Cache-Control: public, max-age=30, so a CDN in front can soak up traffic.

In the page:

const u = await (await fetch('https://usage.example.com/share')).json()
const left = (w) => (new Date(w.resets_at) < new Date() ? 100 : w.remaining)
console.log(`${left(u.five_hour)}% of this session left, ${left(u.seven_day)}% of the week`)

Writing another client

Anything can read the bridge, not just the watch. Here's the whole integration:

  1. Find it. On the LAN, browse mDNS for _aiusage._tcp, or use the host's address on port 8765. From anywhere, use the public URL.
  2. Pair once. Run aiusage-server -pair on the server machine to get a 6-digit code, then:
    curl -X POST https://<public-url>/pair -d '{"code":"123456","name":"my-thing"}'
    # → {"token":"…","device_id":"…","host":"pop-os","public_url":"https://…"}
    Store the token. If you paired on the LAN, switch to public_url for later requests.
  3. Read. Send Authorization: Bearer <token> on every request, never ?token=. Poll GET /usage with If-None-Match: <last ETag>: a 304 means nothing changed. For notifications, poll GET /events?since=<high_water> and deduplicate by dedupe_key. The bridge is the only thing that decides an alert, so don't re-derive thresholds in your client.
  4. Handle 401 as "pair again"; your device was revoked or the state was reset. Use GET /healthz (no auth) to check the bridge is up.

Rules of thumb:

  • Date your data from when you fetched it, not from generated_at; or better, from cache_fetched_at when it's there.
  • Don't add a refresh button that does anything but GET /usage: that request already refreshes.
  • Take reset times only from resets_at.
  • Never label the cost numbers as a bill.

Each client gets its own token. Revoke one by deleting its entry from state.json and restarting the bridge.

License

PolyForm Noncommercial 1.0.0. It covers everything here, including the Wear OS app in wearos/, the Android widgets in android/ and the iOS widgets in ios/. You may use, change and share it for any noncommercial purpose: personal use, research, hobby projects, and charitable, educational or public-interest organisations. Commercial use needs permission from the copyright holder.

Run independently at login

Install the server and enable its user service:

mkdir -p ~/.local/bin ~/.config/systemd/user
install -m 755 aiusage-server ~/.local/bin/aiusage-server
cp deploy/aiusage-server.service ~/.config/systemd/user/
# Before starting, edit ExecStart for any public-tunnel/provider-path flags.
systemctl --user daemon-reload
systemctl --user enable --now aiusage-server.service

Set -public-url before starting the service if a public tunnel forwards to it. Keep deployment URLs and tokens in your local configuration, outside this repository. journalctl --user -u aiusage-server.service shows server logs; restarting the tray has no effect on that service. To keep the server running after logout, enable user lingering with loginctl enable-linger "$USER" if desired.

After pairing the tray, install its autostart entry:

mkdir -p ~/.config/autostart
cp aiusagebar.desktop ~/.config/autostart/

Edit the desktop entry's Exec= to the installed tray binary's absolute path. It should launch only aiusagebar, with no old server flags.

About

No description, website, or topics provided.

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages