Skip to content

Repository files navigation

TokenRunway 🛬

Swift macOS License

English · 简体中文

Volcano floating ball: monthly / weekly / 5-hour quotas DeepSeek floating ball and hover card: balance + last-5h spend

An always-on macOS desktop floating ball that shows how many minutes of AI you have left — at a glance, across all your providers, before you hit empty.

"Runway" is the startup word for "how long can you keep burning at this rate before you're out." TokenRunway puts that number on your desktop as a floating ball. The outer ring shows your monthly remaining; the core liquid shows your current-window remaining; the ball's breathing rate mirrors your actual burn rate. When it starts breathing hard and flashing red, your 5-hour quota is 10 minutes from empty. You knew before it happened.


Why

  • Glanceable: see consumption at a glance, without switching away from your work or opening a browser.
  • Unified multi-provider view: Volcano (5h / 7d / 30d rolling windows), DeepSeek (pure balance), Kimi Code (weekly quota + rolling rate windows + booster wallet), MiMo (monthly Token Plan), Zhipu (monthly / weekly token quotas), MiniMax (5h rolling window + weekly) — rendered homogeneously; the dashboard shows one card per selected provider - a searchable multi-select dropdown (status dot + headline metric per row) is the single place that decides who shows up, both on the ball and in the panel.
  • Prediction over reporting: computes ETA from burn rate ("at the current pace, your 5h quota has 12 minutes left"), attacking the pain of "my 5-hour quota burned out in 10 minutes before I noticed."
  • Low-overhead resident: native SwiftUI + AppKit, tiny resident memory and CPU; never steals focus.

Providers

Adapter-first architecture: every provider maps onto one unified Quota model.

Done (real-verified)

Provider What is shown
Volcano Engine (火山引擎) 5h / 7d / 30d rolling windows
DeepSeek Pure account balance (¥)
Kimi Code Weekly quota + rolling rate windows
MiMo Monthly Token Plan
Zhipu Monthly / weekly token quotas
MiniMax 5h rolling window + weekly
Custom metrics Your own HTTP API as a quota ball (see Custom metrics)

Not yet: OpenAI · Anthropic — planned next.

Custom metrics

Point TokenRunway at your own HTTP endpoint to show your internal usage — GPU quota, API budget, token consumption — as a ball next to the built-in providers. Each custom metric is one ball. The endpoint just needs to return the output-contract JSON below — an internal usage service, an API gateway, even a static JSON file on a CDN.

Add one: Dashboard toolbar → Custom Metrics (chart icon) → +.

Field Example Notes
Name GPU quota Ball label
Endpoint URL https://api.corp.com/v1/usage Full endpoint URL, may contain {userId}
User ID wyang Identifies the current user (placeholder or user_id param)
Semantics Used / Remaining Used (default): value = consumed; Remaining: value = remaining
Cap / Total 10000 Optional. Used → water = used/max (full = drained); remaining → water = remaining/max (full = healthy)
Unit None / CNY / USD / Tokens / Credits / Custom Optional
Token — Optional Bearer token; leave empty for open internal endpoints

Request: GET <url>, optional Authorization: Bearer <token>.

  • URL contains a {userId} placeholder → replaced with the user ID (RESTful style):https://api.corp.com/v1/users/{userId}/usage
  • No placeholder + non-empty user ID → user_id query param appended:https://api.corp.com/v1/usage?user_id=wyang

Output contract (HTTP 200 + JSON):

Field Required Notes
value ✅ current value; number or numeric string
max — cap; omitted → no water level
semantics — "used" (default) or "remaining"; overrides the config
unit — CNY / USD / any text; overrides the config, "none" clears it
label — Display name; overrides the configured name
updatedAt — ISO 8601 timestamp (accepted, not yet checked)
error — Business error; the ball shows a generic error state
{"value": 1234.5, "max": 5000, "semantics": "used", "unit": "CNY"}

Rules:

  • Output fields override the config; missing optional fields fall back to it.
  • Invalid output (missing/non-numeric value, bad semantics, malformed JSON, HTTP 4xx/5xx, error field) shows a generic error state — server free text is never rendered.
  • All four ball shapes apply (see below): semantics × max from either the config or the output.

Example — per-user GPU quota via a RESTful endpoint: name GPU quota, endpoint URL https://api.corp.com/v1/users/{userId}/usage, user ID wyang, semantics Used, cap 5000, unit CNY — the ball shows 1234 with water at 24.7%.

Ball shapes

Semantics Max Ball
used yes center = usage value, water = used/max (full = drained), sub = percent
used no center = usage value, no water level
remaining yes center = remaining percent, water = remaining/max (full = healthy)
remaining no balance ball (remaining / high-water, ¥ / $ badge)

Notes:

  • If your endpoint returns a remaining value (e.g. budget_remaining), pick Remaining semantics instead of Used.
  • Config lives in ~/.trwy/config.json (customMetrics), shared with trwyctl — trwyctl all includes custom metrics, trwyctl <custom-id> queries one.
  • Changes apply immediately (hot reload) — no restart needed.
  • Pre-Prometheus-removal configs (baseURL/metric keys) still load but report "missing url" — re-edit them with the new Endpoint URL field.

Requirements

  • macOS 14.0+ (Sonoma; uses PhaseAnimator / Canvas)
  • Xcode Command Line Tools (Swift 6.0 toolchain)

Installation

Homebrew

🚧 Coming soon - pending Apple notarization and a cask tap. For now, build from source.

Build from source

1. Build

git clone https://github.com/kungf/token-runway.git
cd token-runway
swift build

2. Package & run

./Scripts/make-app.sh         # produces build/TokenRunway.app (LSUIElement background agent)
open build/TokenRunway.app

The ball appears at the top-right of the main screen. Quit: pkill -x TokenRunway.

Preview without credentials - run the real UI with synthetic data, no API keys or network:

TRWY_MOCK=mixed open build/TokenRunway.app

Six scenarios to try: healthy · warning · critical · exhausted · mixed · balance-critical.

Distribution signing — by default the build is ad-hoc signed (no certificate needed; fine for local use and screenshots). To produce a Developer-ID-signed build for distribution:

TRWY_SIGNING=identity \
TRWY_SIGNING_IDENTITY="Developer ID Application: Your Name (TEAMID)" \
./Scripts/make-app.sh

This enables Hardened Runtime with a network-client entitlement. Notarization and stapling are a separate step (TODO: Scripts/notarize.sh) and require an Apple Developer account.

Interactions

Gesture Action
hover floating summary (provider + per-window percentages)
click open dashboard (cards for all selected providers)
scroll cycle core liquid between 5h ↔ 7d (outer monthly ring unchanged)
drag move the ball; snaps to nearest screen edge on release
right-click menu (refresh / pause / settings / hide)

Architecture

Adapter-first: all provider differences are contained in the adapter layer; the upper UI / scheduling / prediction only knows the unified Quota model.

UI           FloatingBall (NSPanel) · Dashboard (multi-select dropdown + cards) · Settings (per-provider sheet)
              │ subscribes @Published
Service      UsageStore (ObservableObject) · ForecastEngine (burn rate/ETA)
              │ scheduling                    │ credentials
Adapter      Provider protocol · Volcano(V4) · DeepSeek(bearer) · Kimi Code(localCLI) · MiMo(consoleSession) · Zhipu(bearer) · MiniMax(bearer) · Custom metrics(HTTP output contract) · [OpenAI/Anthropic WIP]
              │
Core         Quota model · VolcSigner · HTTPClient · CredentialStore

Four SPM targets:

  • TokenRunwayCore (Foundation-only, zero AppKit/SwiftUI) — model / auth / forecast / providers / networking
  • TokenRunwayApp — floating ball + dashboard UI
  • trwyctl — CLI (integration / debugging)
  • TokenRunwayCoreTests — unit tests (225/225)

Security

  • API keys live only in ~/.trwy/config.json (chmod 600, outside the repo); never committed, never logged, never sent to third parties (M2 will migrate to Keychain).
  • Credential implements a redacting CustomStringConvertible — any print() shows only the first 4 characters.
  • Built-in providers are HTTPS-only (ATS + certificate validation). Custom metrics allow plain http:// for internal endpoints (ATS exemption) — a known tradeoff for single-user LAN tools; use HTTPS when the endpoint is reachable off the local network.

Documentation

License

MIT — see LICENSE.

Releases

Packages

Contributors

Languages