Token Horizon documentation
Token Horizon tracks AI token usage, cost, plan limits and local model performance from provider-reported data. Use the native macOS app or the desktop dashboard for Windows and Linux. Local tracking works without a Token Horizon account; leaderboard and team sharing are optional.
Overview
On macOS, the app runs as a notch panel or menu-bar item. On Windows and Linux, it opens a desktop dashboard with its portable core included. Both expose the local HTTP API at 127.0.0.1:8765; GET /health identifies the running version and build. The notch, WidgetKit extension, and native Mac integrations described later on this page belong to the macOS app.
Install
Choose a macOS download, Windows download, or Linux download. The download selector shows packages available for each platform. Windows and Linux installers include the core and runtime, so destination machines do not need Go, Node.js, or a separate daemon installation.
If the selector reports no published Windows or Linux package, check the Desktop Linux and Windows workflow for a successful review build. When a completed run has downloadable artifacts, download the one matching your machine and extract its ZIP. GitHub requires sign-in to download workflow artifacts; this is separate from using Token Horizon locally. Review builds do not install automatic updates, and Windows review installers are unsigned.
macOS · Apple silicon
One line, no sudo:
curl -fsSL https://raw.githubusercontent.com/castlemilk/token-horizon/main/install.sh | bash
Homebrew:
brew tap castlemilk/tap
brew install --cask token-horizon
Releases are Developer ID signed and Apple-notarized (Gatekeeper reports source=Notarized Developer ID). Local builds via ./scripts/make-app.sh are ad-hoc signed.
Windows 10 or 11 · x64
- Choose Windows x64 in the download selector and download the
TokenHorizon-…-windows-x64-setup.exeinstaller. - Run the installer, choose your installation folder, and complete the setup wizard. Installation is for your Windows user and does not require administrator access.
- Open Token Horizon from the Start menu or desktop shortcut. Follow capture setup to select which local usage source to count.
The Windows installer supports x64 machines. Keep provider clients and their histories in the same Windows user profile used to run Token Horizon.
Linux · x64 or ARM64
Use a graphical Linux desktop session and choose the package matching your processor: x64 / amd64 for Intel or AMD, or ARM64 / aarch64 for ARM. Run uname -m if unsure. Download a Debian .deb package or portable .AppImage from the Linux selector.
Debian or Ubuntu: install the downloaded package, then open Token Horizon from your application launcher or run token-horizon. Replace VERSION with the version in the downloaded filename; use arm64 in place of amd64 for ARM:
sudo apt install ./TokenHorizon-VERSION-linux-amd64.deb
token-horizon
AppImage: make the downloaded file executable, then launch it. Replace VERSION with your downloaded version and x86_64 with arm64 when appropriate:
chmod +x TokenHorizon-VERSION-linux-x86_64.AppImage
./TokenHorizon-VERSION-linux-x86_64.AppImage
AppImage needs FUSE 2 support and Chromium user-namespace sandbox support. If FUSE 2 is missing on Ubuntu 24.04, install libfuse2t64; Ubuntu 22.04 uses libfuse2. See the AppImage FUSE guide for other distributions.
# Ubuntu 24.04, if FUSE 2 is missing
sudo apt install libfuse2t64
If your Debian or Ubuntu desktop restricts user namespaces and AppImage reports a sandbox error, install the .deb package. Its installer configures the bundled AppArmor profile and sandbox helper. Keep the app sandbox enabled.
First-run capture setup · Windows and Linux
A fresh desktop installation starts in point mode and counts requests routed through consented local meters. An empty activity view is expected until a meter is configured or you opt in to provider-file capture. The current desktop UI links to setup instructions; configure capture in the local files below.
Quit Token Horizon, open its configuration folder, and create or edit settings.json and consents.json. Preserve existing keys and restart the app after editing. Default folders:
- Windows: paste
%USERPROFILE%\.config\token-horizoninto File Explorer's address bar. - Linux: open
~/.config/token-horizon/. IfXDG_CONFIG_HOMEis set, use$XDG_CONFIG_HOME/token-horizon/.
TH_CONFIG_DIR overrides these locations if you configured it yourself.
Read existing provider history: in settings.json, select files mode:
{
"captureMethodology": "files"
}
In consents.json, grant only the file-reading scope:
{
"fileReading": { "granted": true, "version": 1 }
}
Restart Token Horizon and allow about 30 seconds for the first history import. Files mode counts provider-reported usage from supported CLI histories on this machine, then imports updates every 15 minutes while the core runs. It does not start request meters. The native macOS app discovers its supported histories directly and uses its own setup flow.
Meter live OpenAI requests: instead, select point mode and enable the OpenAI meter in settings.json:
{
"captureMethodology": "point",
"meterToggles": { "openai": true }
}
Grant the metering scope in consents.json:
{
"metering": { "granted": true, "version": 1 }
}
Restart the app and set your OpenAI client or SDK's base URL to http://127.0.0.1:9242/v1. Keep its normal provider credentials in that client. Only requests sent through the local meter are counted; other providers have separate meter ports listed at GET /meters. Meter traces are stored locally and can include request and response bodies; set "traceCapture": false in settings.json to disable body capture while retaining usage counts.
Opening the desktop app starts its core on 127.0.0.1:8765, or attaches to a compatible core already there. Closing the app stops the core it started and preserves your saved history. An independently running service remains available. Local tracking needs no account; enable cloud sharing separately if you want to publish usage.
The macOS panel
Hover the notch (or click the menu-bar rings) to open the panel. Tabs:
| Tab | Shows |
|---|---|
| TOKENS | Today's tokens/cost, per-model and per-tool breakdown, cache rates, plan-limit bars with reset countdowns, streaks and league standing. |
| ACTIVITY | CPU, memory, disk and network history, top processes by CPU/memory/disk/network, Docker containers, and MLX runner telemetry (measured tok/s). |
| MLX | Local model runners (MLX/Ollama) with per-process telemetry, bounded history, and measured throughput. |
| ENGINE | Local inference backends (Splash and TH Engine) — hardware fit, model catalog, serve/stop, live throughput, benchmark results. |
| Leaderboard | Your published standing, leagues/MMR, sync and share controls. |
| Shells | Recent shell execution events captured by the zsh hooks. |
| Settings | Surface mode (auto/notch/tray), sharing rules, history persistence, start-at-login, and the serving build stamp. |
macOS desktop widget
The app ships a native WidgetKit extension (macOS 14+), embedded at build time by scripts/make-widget.sh. Add it from the desktop's Edit Widgets gallery in small, medium or large. It has a 3-page carousel — usage, plan limits, plans & resets — and a segmented time-window picker h · d · w · m · y (24 hourly bars, 7 daily, 17 weekly, 30 daily, 12 monthly). The GitHub-style activity heatmap always matches the selected window, and the plans page spotlights what expires soonest with urgency colors (<24h red, <48h orange). Configure it in Settings → Desktop widget (enable, period, accent, show cost/limits/chart) with a live preview; every control writes the same app-owned preference the widget reads, so preview, widget and app never disagree. The extension consumes the versioned snapshot at GET /widget and falls back to its last cached copy when the app is down.
Usage tracking
Parsers read the tools' own session data on your machine:
- Claude Code — JSONL session logs under
~/.claude*(multi-profile aware). - Codex — stateful watermarks over
~/.codex*/sessions(multi-directory, truncation-safe). - OpenCode — SQLite (
READONLY | FULLMUTEX, stat-gated). - Generic JSONL providers — glm, qwen, grok, deepseek, gemini/agy and friends.
- Ollama — an in-process loopback telemetry relay measures real token counts and eval rates from completed responses.
- LLM gateway — the bundled Go sidecar proxies provider APIs and captures traces; totals still come from the file parsers, never double-counted.
—.Plan limits
Limit fetchers query each provider's own plan APIs (or parse their local credentials) for 5-hour, weekly and monthly windows. Auth sources, checked in order:
| Provider | Source |
|---|---|
| Alibaba | ALIBABA_TOKEN_PLAN_COOKIE / Settings cookie |
| Kimi | KIMI_CODE_HOME/KIMI_HOME, ~/.kimi*/credentials/kimi-code.json (OAuth refresh) |
| GLM / MiniMax / OpenCode Go | opencode auth.json keys |
| OpenAI / Codex | opencode auth.json OAuth + live usage API, JSONL fallback |
| Claude | ~/.claude* profiles → credentials file → Keychain |
| Gemini / AGY | ~/.gemini*/oauth_creds.json + Antigravity language-server telemetry |
| DeepSeek | DEEPSEEK_API_KEY or opencode auth |
Model catalog & plans
The catalog merges models.dev, OpenRouter, live provider APIs and curated benchmarks into one family per model. The web list at /models is a static export of the app's own merge pipeline — regenerated on a schedule so app and web can't drift.
Pricing evidence is explicit, in three states:
- Priced — published input/output/cache rates, with prompt-cache discounts applied to the blended net cost.
- Free — only with evidence: local models, or an explicit
freelisting. - Plan-covered / unpriced — shown as
—, sorted last, badgedPLANwhen a subscription covers it.
Subscription plans live in a curated artifact with verified usage tiers (Copilot, Kimi Code, MiniMax Token Plan, GLM Coding Plan, OpenCode Zen, and more). See the Plans view and the blog post Pricing evidence.
The Cheapest Inference view ranks model families by the best price across every provider listing we track (OpenRouter, models.dev gateways and resellers), with the discount against the direct rate and a price-leaders board — so "cheap inference" is answerable from the same catalog instead of a separate scrape.
Leaderboard & privacy
Publishing is opt-in. The leaderboard ranks token volume across periods, with leagues/MMR, streaks, efficiency, per-model breakdowns and share cards. What's published: totals, model/tool breakdowns, projects, hardware, sessions with titles only if you enable leaderboardSharePrompts (default off — rows publish with empty titles so activity timelines still work). Prompt history itself never leaves the individual profile. Profile links (/u/<handle>) show a dynamic preview with token totals, a 17-week heatmap, streak and provider mix. Shared reports (/s/<id>) respect their audience, privacy options and expiry. Restricted links require sign-in and show a generic preview; enable the public-link option to let anyone with the link view the report.
Open your usage on the web: in the desktop app, set your handle under Settings → Leaderboard (or Sharing & teams in newer builds), save it, and use Publish or Sync now. Open that handle’s public profile on the web and use Claim to link it to your signed-in Google or GitHub account. Keep the original claim token if one was issued. Workspace discovers profiles linked to that exact account; signing in alone does not upload activity. A claimed profile’s later uploads require an authorized app write token.
Workspace analyzes one profile’s published token totals, model cost estimates and shared activity. Cost estimates are not provider invoices. Public previews expose only published fields; live traces, device telemetry and plan limits remain in the desktop app.
HTTP API
Local daemon on 127.0.0.1:8765 (CORS enabled for the dashboard):
| Endpoint | Returns |
|---|---|
GET /health | Serving build stamp, gateway/proxy ports. |
GET /stats | Usage snapshot, models, tools, projects, system stats. |
GET /trends?window=1D|1W|1M|3M|1Y | Rolling token history with tool breakdown. |
GET /limits | Provider plan limits with reset times. |
GET /claude/accounts | Claude profiles, orgs, live quota. |
GET /models · /top-picks | Filtered catalog rows · ranked value picks. |
GET /models/catalog | Full web catalog export (models, plans, top picks). |
GET /history?days= | Daily history with per-provider breakdown. |
GET /activity/heatmap?days= · /projects · /achievements | Heatmap grid · project rollups · achievements + season. |
GET /processes · /process?pid= · /docker | Top processes, deep PID inspection, containers. |
GET /events?limit= · POST /event | Shell events (zsh hooks). |
GET /metrics | Prometheus text (OTel meter). |
GET /widget · POST /widget/window?value= | Desktop-widget snapshot (v5 stacks + heatmap + limits) · set the chart window. |
GET /proxy/config · GET /traces | Gateway sidecar config · captured traces. |
GET /engine · POST /engine/serve · POST /engine/stop | Local-engine state · serve a model {backend?, model, tokenizer?, max_memory_gb?, max_context_k?} · stop a backend. |
GET /engine/bench | Latest side-by-side engine benchmark (scripts/bench-engines.sh). |
POST /cache/reset · GET /cache | Reset / inspect the durable cache. |
Hosted edge API on https://token-horizon.dev: /api/leaderboard, /api/user/:handle, /api/providers, /api/teams, /api/season, /api/models/catalog, /api/models/usage, /api/config, /api/health, /api/share (SVG badges).
MCP server
Two MCP servers ship in mcp/:
mcp/token-horizon-mcp.mjs— zero-dependency stdio server for the local daemon and hosted APIs. Installed to~/.config/token-horizon/mcp/byinstall.sh.mcp/src/index.js— SDK-based server for the hosted leaderboard.
Catalog tools (both servers): search_models (token_horizon_catalog in the standalone server) and get_plans (token_horizon_plans). They search the unified catalog with pricing evidence, benchmark scores, context windows and plan linkage, and return plan tiers with included models. Local daemon first, hosted catalog fallback; set TH_CATALOG_URL to pin a source.
{
"mcpServers": {
"token-horizon": {
"command": "node",
"args": ["~/.config/token-horizon/mcp/token-horizon-mcp.mjs"]
}
}
}
LLM gateway
The app ships with a Go sidecar (token-horizon-gateway) that speaks OpenAI and Anthropic protocols on one local port and captures full conversation traces. Point a client at the gateway by swapping its base URL:
- Codex:
OPENAI_BASE_URL=http://127.0.0.1:<port>/th-openai/v1 - Claude Code:
ANTHROPIC_BASE_URL=http://127.0.0.1:<port>/th-anthropic - Ollama traffic can be routed through the telemetry proxy for measured tok/s.
Traces are stored locally (bounded: 256KB per side, 30 day-files, 256MB) and served at GET /traces. Gateway traffic never feeds usage totals — the file parsers already count it.
Local engines
Two supervised inference backends run as sidecars — attach-or-spawn, never in-process. A server you started yourself is adopted read-only and never killed; the app only stops processes it spawned.
- Splash (
127.0.0.1:8000, brewincoai/tap/splash) — the native Metal engine. The app probes your chip/RAM/macOS, scores each Splash package for fit, and suggests memory/context ceilings automatically. - TH Engine (
127.0.0.1:8001, bundled Rust/candle sidecar) — Token Horizon's own engine. Serves GGUF (qwen3/qwen2/llama, Metal), dense safetensors (qwen2/qwen3, CPU/F32) and the Qwen3.5 hybrid arch (Gated DeltaNet + periodic full attention, MLX 4-bit affine weights kept packed and dequantised inside fused Metal matvec/matmul kernels) from a local path or Hugging Face repo id, with a--tokenizeroverride for GGUF repos. OpenAI- and Anthropic-compatible endpoints plus hooks Splash doesn't expose:GET/POST /engine/config(live sampling/prefill/context tuning),/engine/requests(per-request latency ring),/engine/kv/clear,/engine/events(lifecycle SSE),/engine/metrics.
TH Engine extras for the qwen3_5 backend:
- N-gram speculative decoding (
--spec-tokens N, default 4, max 7, 0 disables;spec_tokensin/engine/config) — drafts continuations from prompt-lookup matches and verifies them in one batched forward pass through the fragment-direct quantised matmul (affine_q4_sg— Splash's sgmatrix design: nibble-into-bf16 MMA fragments, group-sum correction, fused silu·mul and residual+norm epilogues), so the packed weights are read once per verified run rather than once per token. Every emitted token is still sampled from the target model's own distribution; an adaptive acceptance gate (EMA of accepted draft tokens) pauses drafting when the text stops being repetitive. Per-request acceptance counts are reported underth_stats.spec_rounds/spec_accepted. - TurboQuant KV compression (
--kv-quant, orkv_quantin/engine/config— applied between requests) — ports 0xsero/turboquant (arXiv:2504.19874) to the 16 full-attention layers: a fixed random orthogonal rotation + 2-bit Lloyd-Max codebooks for keys (plus a 1-bit QJL residual-sign correction, "prod" mode ≈ 3 effective bits) and values. Roughly 6x less KV memory (~172B vs ~1KB per token per kv-head) — a long-context play: at short contexts the eager dequant ops cost a few tok/s. - DFlash neural speculative decoding (
--draft <dir>) — ports Splash's packaged DFlash-2 draft (incoai/Qwen3.8-27B-DFlash2; reads the MDFD packed format directly, verified against the upstream safetensors): a 5-layer block-diffusion drafter conditioned on the target's hidden states captured at layers 5/19/33/47/61, proposing a 7-token block per round via top-16 candidates + predecessor/successor codebook chaining. Verification is one batched target forward; partial accepts roll back by re-scanning only the committed rows (no re-forward), and an adaptive gate shrinks the verified chain when acceptance drops. Lossless semantics: every emitted token is sampled from the target's own distribution (greedy = argmax chain, sampled = Leviathan rejection against the draft's candidate distribution). Acceptance counters land inth_stats.spec_rounds/spec_accepted. The draft was trained against the stock Qwen3.8-27B — acceptance is best on matching-class checkpoints.
Route clients through the gateway prefixes /th-splash/ and /th-engine/ so local inference is traced like any other provider. Compare the two with scripts/bench-engines.sh — results land at GET /engine/bench and in the ENGINE tab.
Shell helper
The zsh integration installs a th command and preexec/precmd hooks:
th status # daemon + gateway status
th limits # provider plan windows
th leaderboard # standing and sync controls
th share # generate a share card
th reset-cache # clear and rebuild the durable cache
Troubleshooting
| Symptom | Fix |
|---|---|
| Model list looks stale / commit mismatch | curl -s localhost:8765/health and compare build.commit with git rev-parse --short HEAD. Rebuild with ./scripts/make-app.sh. |
| API missing on :8765 | Check the LaunchAgent: /Applications/TokenHorizon.app/Contents/MacOS/TokenHorizon --agent-status. The app never runs API-less — a bind failure exits loudly. |
llm_gateway_port null | Rebuild via make-app.sh; the gateway sidecar is bundled and health-gated at launch. |
| Provider limits silently missing | Credentials expired — re-auth with the provider (opencode keys rotate, Claude reads stored tokens, Alibaba needs a fresh cookie). |
| Catalog/plans stale on the web | The static export is refreshed on a schedule; run task models-refresh locally to regenerate docs/data/models.json. |