docs/ux(release): tier marketing, onboarding tour, comparison, and v3.8.0 changelog

Task 8 — Tier 1/2/3 marketing & onboarding UX:
  - README "Why OmniRoute?" enhanced with ASCII 3-tier fallback diagram
    and comparison table vs 9router/LiteLLM/OpenRouter/Portkey
  - docs/marketing/TIERS.md: user-facing tier guide with provider
    classification, strategy notes, and common patterns
  - images/tier-flow-{light,dark}.svg: SVG tier flow diagrams
  - TierFlowDiagram.tsx: responsive SVG diagram (light/dark via next-themes)
  - TierTour.tsx: onboarding step showing tier flow + 3 tier cards
  - onboarding/page.tsx: inserts "How It Works" tier step after Welcome
  - TierCoverageWidget.tsx: home dashboard card showing active provider
    counts per tier with "Add" CTA for empty tiers
  - HomePageClient.tsx: renders TierCoverageWidget before providers section
  - en.json: onboarding.tier namespace (tier1/2/3 labels, subtitle, CTAs)
  - docs/routing/AUTO-COMBO.md: tier weight table and override example

Task 9 — Docs, CHANGELOG, comparison page:
  - CHANGELOG.md: [3.8.0] section documenting all Tasks 1-8
  - docs/releases/v3.8.0.md: detailed release notes with migration guide
  - docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md: 9router/LiteLLM/
    OpenRouter/Portkey comparison matrix
  - docs/architecture/REPOSITORY_MAP.md: new bin/cli/tray/, bin/cli/runtime/,
    skills/ directories
  - docs/ops/RELEASE_CHECKLIST.md: v3.8.0+ checks (tray, SQLite, MCP filter,
    route guard)
This commit is contained in:
diegosouzapw
2026-05-15 01:04:15 -03:00
parent 8f915b18b0
commit 302ea853d5
16 changed files with 877 additions and 42 deletions

View File

@@ -6,46 +6,66 @@
- **refactor(@omniroute/opencode-provider):** complete rewrite of the npm helper. The `1.0.0` artifact was non-functional — `index.js` re-exported from `.ts` (unrunnable at install time) and the emitted shape didn't match the OpenCode `https://opencode.ai/config.json` schema. The new release ships a real `tsup` build (CJS + ESM + `.d.ts`), schema-correct output (`npm: "@ai-sdk/openai-compatible"`, with `models` catalog), `baseURL` deduplication (no more `/v1/v1`), input validation, 13 unit tests, and full documentation in [`docs/frameworks/OPENCODE.md`](docs/frameworks/OPENCODE.md). Versioned as `0.1.0` to signal the pre-1.0 reset.
- **chore(npm):** [`@omniroute/opencode-provider@0.1.0`](https://www.npmjs.com/package/@omniroute/opencode-provider) published to npmjs.com under the new `@omniroute` org. Install with `npm install --save-dev @omniroute/opencode-provider`.
- **BREAKING**: dropped Node 20.x support. Minimum Node version is now 22.22.2 (or 24.0.0+). Required because http-proxy-middleware 4.x requires `node >=22.15.0`. Users on Node 20 must upgrade — see [`package.json` engines field](package.json) and the README Node badge.
### Security
- **fix(oauth/windsurf):** Windsurf Firebase token refresh now reads `WINDSURF_CONFIG.firebaseApiKey` instead of `process.env.WINDSURF_FIREBASE_API_KEY` directly. The literal was removed from `.env.example` in this release, so the previous direct read would have silently skipped refresh for browser-flow Windsurf/Devin sessions (forcing re-auth instead of renewing). Operators with a legacy `WINDSURF_FIREBASE_API_KEY` value in their `.env` keep working — the env override path is preserved by `resolvePublicCred()`. See [`docs/security/PUBLIC_CREDS.md`](docs/security/PUBLIC_CREDS.md).
- **fix(kiro/translator):** assistant-first conversations no longer collide on a single `conversationId`. The synthetic "(empty)" user turn injected to satisfy Kiro's "first message must be user" rule is now marked non-enumerable `__synthetic` and excluded from the `uuidv5` conversationId derivation, so unrelated chats no longer share the same upstream AWS Builder ID context. Prevents leaking prior session state across unrelated chats.
- **fix(utils/publicCreds):** `decodePublicCred()` no longer silently mangles raw credential overrides that don't match `RAW_VALUE_PATTERN`. The previous path always base64-decoded + XOR-unmasked (and `Buffer.from(v, "base64")` is lenient, accepting many non-base64 inputs without throwing). Now: strict-base64 alphabet check + printable-plain check on the decoded result; failing either, the original value is returned untouched.
- **fix(auth/extractApiKey):** `x-api-key` fallback now only triggers when the request also carries an `anthropic-version` header. Without this scoping, non-Anthropic clients in local mode (placeholder `x-api-key`) would get `401 Invalid API key` from per-route gates even with `REQUIRE_API_KEY` off.
- **fix(providers/qoder):** the OAuth+PAT disambiguation message now actually surfaces. The `getProviderRuntimeStatus()` early-return on `qoder + !apikey` was masking the new branch added in #2247.
- **fix(oauth/windsurf):** Windsurf Firebase token refresh now reads `WINDSURF_CONFIG.firebaseApiKey` instead of `process.env.WINDSURF_FIREBASE_API_KEY` directly.
- **fix(kiro/translator):** assistant-first conversations no longer collide on a single `conversationId`.
- **fix(utils/publicCreds):** `decodePublicCred()` no longer silently mangles raw credential overrides that don't match `RAW_VALUE_PATTERN`.
- **fix(auth/extractApiKey):** `x-api-key` fallback now only triggers when the request also carries an `anthropic-version` header.
- **fix(providers/qoder):** the OAuth+PAT disambiguation message now actually surfaces.
### Fixed
- **fix(executor/claude-code):** store tool-name round-trip metadata in non-enumerable `_toolNameMap` so it survives in-memory but is stripped by `JSON.stringify()` — prevents internal OmniRoute metadata from leaking to upstream providers. ([#2254](https://github.com/diegosouzapw/OmniRoute/pull/2254) — thanks @Rikonorus)
- **fix(streaming):** strip upstream `Content-Encoding`, `Content-Length`, and `Transfer-Encoding` headers from SSE responses — prevents client-side decompression corruption when the proxy serves plain-text event streams through nginx/caddy. ([#2253](https://github.com/diegosouzapw/OmniRoute/pull/2253) — thanks @Rikonorus)
- **fix(kiro):** harden OpenAI-to-Kiro translator for API compliance: recursively strip `additionalProperties` and empty `required: []` from tool schemas; merge consecutive assistant messages; prepend synthetic user for assistant-first conversations; convert orphaned tool results to inline text; enforce `origin: "AI_EDITOR"` on all history user messages; deterministic `uuidv5` session caching. Closes #2213. ([#2251](https://github.com/diegosouzapw/OmniRoute/pull/2251) — thanks @8mbe)
- **fix(models):** sync managed model aliases with provider model visibility — remove aliases when models are hidden/deleted, skip alias creation for hidden models during sync, restore aliases when unhidden, cross-connection safety guard prevents pruning aliases still valid from another connection. ([#2250](https://github.com/diegosouzapw/OmniRoute/pull/2250) — thanks @InkshadeWoods)
- **fix(models/cleanup):** align managed model cleanup for imported models — provider-level "Delete All" now also removes synced available model storage; delete-alias button only shown for alias-source rows; compatible models section uses proper 3-way source-aware delete logic. ([#2261](https://github.com/diegosouzapw/OmniRoute/pull/2261) — thanks @InkshadeWoods)
- **fix(auth):** accept `x-api-key` header in `extractApiKey` so Anthropic-native clients (Claude Code, `@anthropic-ai/sdk`) hit the same per-key policy enforcement as Bearer clients. Previously these requests were treated as anonymous, bypassing model/budget/rate-limit policies and showing up as `NULL` in `usage_history.api_key_id` (~50% of traffic invisible in Costs/Analytics). `Authorization: Bearer` still wins when both are present (back-compat). (#2225)
- **fix(translator/claude-to-openai):** stop including `cache_creation_input_tokens` in `prompt_tokens`. Anthropic pads short prompts up to a 1024-token minimum on cache creation, so a 2-token `"hi"` could be reported as ~2008 `prompt_tokens` and inflate downstream billing (Sub2API/NewAPI/OneAPI) ~250x. `prompt_tokens` now matches the dashboard "Total In" (`input + cache_read`); `cache_creation_tokens` is exposed separately in `prompt_tokens_details.cache_creation_tokens` for auditing. (#2215)
- **fix(ui/claude-extra-usage):** clarify the toggle-success notification text to spell out the toggle→effect relationship ("Claude extra-usage blocking enabled/disabled" instead of the ambiguous "blocked/allowed"). (#2157)
- **fix(providers/qoder):** disambiguate the "Local CLI runtime is not installed" error when a user pastes a Personal Access Token but the connection is in OAuth/CLI-flavored mode. The test route now surfaces a single actionable message ("switch this connection to API Key auth") instead of cascading CLI + 401 errors. (#2247)
- **fix(dashboard/api-manager):** route custom OpenAI-/Anthropic-compatible provider IDs through `getProviderDisplayName` so the model grouping label shows `Compatible (openai)` instead of leaking the raw synthetic `openai-compatible-chat-<uuid>` value. (#2021)
- **fix(providers/blackbox-web):** add `BLACKBOX_WEB_VALIDATED_TOKEN` env override and 403 token-error disambiguation. Blackbox `/api/chat` started rejecting requests whose `validated` field didn't match the frontend `tk` token, even with a valid cookie + active subscription. Operators with the real token can now set the env var; otherwise the previous random-UUID fallback still ships, and a 403 with a token-specific body now surfaces a one-line "set BLACKBOX_WEB_VALIDATED_TOKEN" hint instead of the generic "cookie expired" message. (#2252)
- **docs(security):** document the ToS-violation hot spot of `ANTIGRAVITY_CREDITS=always` in `STEALTH_GUIDE.md`, including why it draws Google abuse detection more aggressively than free-tier-only usage and the recommended posture (`=retry`, Auto-Combo spread, per-connection RPM caps). (#2246)
- **fix(executor/claude-code):** store tool-name round-trip metadata in non-enumerable `_toolNameMap`. ([#2254](https://github.com/diegosouzapw/OmniRoute/pull/2254))
- **fix(streaming):** strip upstream `Content-Encoding`, `Content-Length`, and `Transfer-Encoding` headers from SSE responses. ([#2253](https://github.com/diegosouzapw/OmniRoute/pull/2253))
- **fix(kiro):** harden OpenAI-to-Kiro translator for API compliance. ([#2251](https://github.com/diegosouzapw/OmniRoute/pull/2251))
- **fix(models):** sync managed model aliases with provider model visibility. ([#2250](https://github.com/diegosouzapw/OmniRoute/pull/2250))
- **fix(auth):** accept `x-api-key` header in `extractApiKey`. (#2225)
- **fix(translator/claude-to-openai):** stop including `cache_creation_input_tokens` in `prompt_tokens`. (#2215)
- **Docs:** 270 broken internal markdown links repaired.
---
## [3.8.0] - 2026-05-15
### Added
- `feat(mcp): MCP accessibility-tree smart filter engine` — collapses ≥30 repeated sibling lines, preserves `[ref=eXX]` anchors, 60-80% savings on browser snapshot outputs (Task 1)
- `docs(skills): publish 10 SKILL.md manifests for external AI agents` — zero-friction onboarding for Claude Desktop, ChatGPT, Cursor, Cline (Task 2)
- `feat(cli): standalone system tray with PowerShell fallback on Windows` — no Electron required; `omniroute --tray`; autostart via LaunchAgent/.desktop/registry (Task 3)
- `feat(auth): CLI machine-ID HMAC-SHA256 token` — zero-friction local auth without JWT/password; loopback-only; constant-time compare (Task 4)
- `feat(security): route protection tiers` — 5 tiers: public/read-only/protected/always/local-only; spawn-capable routes enforce loopback even with valid JWT (Task 5)
- `feat(compression): Caveman SHARED_BOUNDARIES` — all 6 languages × 3 intensities embed boundary clause; `alreadyApplied` check order fixed (Task 6)
- `feat(runtime): dynamic SQLite 5-step fallback chain` — bundled → runtime-installed → lazy-install → node:sqlite → sql.js; magic-byte validation (ELF/Mach-O/PE) (Task 7)
- `docs/ux: tier 1/2/3 marketing, onboarding tour, dashboard widget` — README tier diagram, `docs/marketing/TIERS.md`, TierTour onboarding step, Tier Coverage widget (Task 8)
- `docs(comparison): OMNIROUTE_VS_ALTERNATIVES.md` — objective comparison vs 9router, LiteLLM, OpenRouter, Portkey
### Changed
- **BREAKING**: dropped Node 20.x support. Minimum Node version is now 22.22.2 (or 24.0.0+). Required because http-proxy-middleware 4.x requires `node >=22.15.0`. Users on Node 20 must upgrade — see [`package.json` engines field](package.json) and the README Node badge.
- **Platform overhaul (FASES 1-9):** scripts cleanup, `.env` audit, `/docs` restructure, diagrams folder, i18n pipelines (docs + UI), `/src/app/docs` sync, CI gates.
- **Scripts:** `scripts/` reorganized into 6 subfolders (`build/`, `dev/`, `check/`, `docs/`, `i18n/`, `ad-hoc/`); 23 one-shot scripts archived to the `archive/scripts-scratch-pre-3.8` branch.
- **Environment:** `.env.example` cleaned (-11 orphan vars, +63 missing vars, 11 hardcoded URLs/timeouts promoted to env); new strict `scripts/check/check-env-doc-sync.mjs` validates code ↔ `.env.example``docs/reference/ENVIRONMENT.md` parity.
- **Docs:** `/docs` restructured into 8 functional subfolders (`architecture/`, `guides/`, `reference/`, `frameworks/`, `routing/`, `security/`, `compression/`, `ops/`); ~899 cross-references rewritten.
- **Diagrams:** new `docs/diagrams/` with 8 canonical Mermaid sources + SVG exports, linked into 9 docs.
- **i18n (docs):** hash-based incremental pipeline (`config/i18n.json`, `scripts/i18n/run-translation.mjs`, `.i18n-state.json`); old `i18n_autotranslate.py` and `generate-multilang.mjs` deprecated.
- **i18n (UI):** `scripts/i18n/sync-ui-keys.mjs` propagates `en.json` keys to all 40 locales (no missing keys; coverage ≥ 85.8%); cosmetic `DocsI18n.tsx` removed (locale handling unified via `next-intl`).
- **`/src/app/docs`:** drift fixes (179 → 177 providers, 13 → 14 strategies, 36 → 37 MCP tools); YAML frontmatter added to all docs; `ApiExplorer` consumes `docs/reference/openapi.yaml` (19 endpoints); `content.ts` updated (37 MCP tool groups, 7 internal deployment guide hrefs).
- **CI gates:** strict env-doc-sync in pre-commit; `check:doc-links` validates internal markdown refs; new `docs-sync-strict` and `i18n-ui-coverage` jobs in `.github/workflows/ci.yml`.
- `getDbInstance()` requires prior `ensureDbInitialized()` call — server startup awaits it automatically (see release notes for migration)
- Caveman prompts embed `SHARED_BOUNDARIES` verbatim (LITE/FULL/ULTRA × 6 languages)
- README "Why OmniRoute?" enhanced with 3-tier ASCII diagram and comparison table
- Onboarding wizard gains "How It Works" tier tour step (after Welcome, before Security)
- Home dashboard shows "Tier coverage" widget (configured + active counts per tier)
### Fixed
### Security
- **Docs:** 270 broken internal markdown links repaired (consequence of `/docs` subfolder restructure not relativizing all paths). Categories: 241 `i18n-relative`, 14 `parent-relative`, 9 `screenshots`, 2 deleted-RFC, 4 misc. Now `npm run check:doc-links` PASS with 0 broken links.
- Hard Rule #15: spawn-capable routes must call `assertRouteAllowed(req)` (CLAUDE.md)
- CLI token rejected on non-loopback hosts even when the HMAC is correct
- `always`-protected routes (shutdown, db export) reject CLI tokens unconditionally
### Documentation
- `docs/security/CLI_TOKEN.md`
- `docs/security/ROUTE_GUARD_TIERS.md`
- `docs/ops/SQLITE_RUNTIME.md`
- `docs/marketing/TIERS.md`
- `docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md`
- `docs/releases/v3.8.0.md`
---
## [3.8.0] — 2026-05-06

View File

@@ -211,26 +211,80 @@ _Connect any AI-powered IDE or CLI tool through OmniRoute — free API gateway f
## 🤔 Why OmniRoute?
**Stop wasting money, tokens and hitting limits:**
**One endpoint. 207+ providers. Never stop building.**
❌ Subscription quota expires unused every month
❌ Rate limits stop you mid-coding
❌ Tool outputs (`git diff`, `grep`, `ls`...) burn tokens fast
❌ Expensive APIs ($20-50/month per provider)
❌ Manual switching between providers
❌ Each provider has a different API format
❌ AI providers blocked in your country
Stop juggling 10 dashboards, dead API keys, and surprise bills. OmniRoute
routes every request through the cheapest viable provider — automatically.
**OmniRoute solves all of this:**
### The 3-tier fallback (zero-downtime AI)
```
┌──────────────────────────────────────────────────────────┐
│ Your IDE / CLI / App │
│ (Claude Code, Cursor, Cline, Copilot, …) │
└─────────────────────┬────────────────────────────────────┘
│ http://localhost:20128/v1
┌──────────────────────────────────────────────────────────┐
│ OmniRoute (Smart Router) │
│ • RTK token saver (47 specialized filters) │
│ • Caveman terse-mode (3 levels + SHARED_BOUNDARIES) │
│ • Auto-fallback combos (14 strategies) │
│ • Circuit breaker · TLS fingerprint stealth (JA3/JA4) │
│ • Memory · MCP server · A2A · Guardrails · Evals │
└─────────────────────┬────────────────────────────────────┘
┌──────────────────┼─────────────────┐
▼ Tier 1 ▼ Tier 2 ▼ Tier 3
SUBSCRIPTION CHEAP FREE
(Claude Code, (DeepSeek $0.27, (Kiro, OpenCode,
Codex, Copilot, GLM $0.60, Gemini CLI,
Cursor, Antigravity) MiniMax $0.20) Vertex $300cr)
quota exhausted? budget hit? always available
→ falls to Tier 2 → falls to Tier 3
```
### Why this matters
-**Subscription quota wasted** every month? OmniRoute uses every token before expiry.
-**Rate limits stop you mid-flow?** Auto-fallback to the next provider in milliseconds.
-**Tool outputs burn tokens?** RTK compresses `git diff`, logs, and grep results 30-50%.
-**Paying $50/mo across 5 providers?** Route to the cheapest viable model automatically.
-**Each AI tool wants its own setup?** One endpoint, every tool, one dashboard.
### What sets OmniRoute apart
| Feature | OmniRoute | Other routers |
| ----------------------------------- | ------------------------------------------------------------- | ------------- |
| Providers | **207+** | 20-100 |
| Combo strategies | **14** (priority, weighted, cost-optimized, context-relay, …) | 1-3 |
| Token compression (RTK) | **47 specialized filters** | None |
| Built-in MCP server | **37 tools, 3 transports, 13 scopes** | Rare |
| A2A agent protocol | **5 skills, JSON-RPC 2.0** | None |
| Memory (FTS5 + vector) | **Yes** | Rare |
| Guardrails (PII, injection, vision) | **Yes** | Rare |
| Cloud agent integrations | Codex, Devin, Jules | None |
| Circuit breaker per provider | **3-state, lazy recovery** | Rare |
| TLS fingerprint stealth | **JA3/JA4 via wreq-js** | None |
| Eval framework | **Built-in** | Rare |
| CLI (no Electron required) | **Yes** + system tray | Varies |
| i18n | **40+ locales** | 0-4 |
See [`docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md`](docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md) for a detailed comparison vs 9router, LiteLLM, OpenRouter, and Portkey.
---
**Also solves:**
**Prompt Compression** — auto-compress prompts & tool outputs, save 15-95% eligible tokens per request with RTK+Caveman stacked mode
**Maximize subscriptions** — track quota, use every bit before reset
**Auto fallback** — Subscription → API Key → Cheap → Free, zero downtime
**Auto fallback** — Subscription → Cheap → Free, zero downtime
**Multi-account** — round-robin between accounts per provider
**Format translation** — OpenAI ↔ Claude ↔ Gemini ↔ Responses API, any tool works
**3-level proxy** — bypass geo-blocks with global, per-provider, and per-key proxies
**10 multi-modal APIs** — chat, images, video, music, audio, search in one endpoint
**MCP + A2A**29 MCP tools + agent-to-agent protocol, production-ready
**MCP + A2A**37 MCP tools + agent-to-agent protocol, production-ready
**Universal** — works with Claude Code, Codex, Gemini CLI, Cursor, Cline, OpenClaw, any CLI tool
---

View File

@@ -278,10 +278,24 @@ open-sse/
| `cli/commands/doctor.mjs` | System health diagnostics (8+ checks) |
| `cli/commands/providers.mjs` | Provider list/test/validate |
| `cli/{args,data-dir,encryption,io,provider-catalog,provider-store,provider-test,settings-store,sqlite}.mjs` | CLI helper modules |
| `cli/tray/tray.ts` | System tray integration (cross-platform: NotifyIcon on Windows, systray2 on macOS/Linux) |
| `cli/tray/tray.ps1` | PowerShell NotifyIcon backend (Windows, zero new binaries) |
| `cli/tray/autostart.ts` | Cross-platform autostart (LaunchAgent / .desktop / registry) |
| `cli/runtime/sqliteRuntime.mjs` | 5-step SQLite driver resolution chain (bundled → runtime → lazy-install → node:sqlite → sql.js) |
| `cli/runtime/magicBytes.mjs` | Binary magic-byte validation (ELF / Mach-O / Mach-O fat / PE) |
| `cli/runtime/index.mjs` | `warmUpRuntimes()` — pre-resolves drivers at postinstall / first startup |
| `nodeRuntimeSupport.mjs` | Validate supported Node.js version on install |
---
## `skills/` — Public Agent Skills
| File | Purpose |
| ---------------------------- | ---------------------------------------------------------------------------------- |
| `skills/omniroute*/SKILL.md` | 10 skill manifests for external AI agents (Claude Desktop, ChatGPT, Cursor, Cline) |
---
## `scripts/` — Build & Check Scripts
| Script | Purpose |

View File

@@ -0,0 +1,73 @@
---
title: "OmniRoute vs Alternatives"
version: 3.8.0
lastUpdated: 2026-05-15
---
# OmniRoute vs Alternatives
Objective feature comparison vs popular open-source AI routers.
> **Methodology**: Public repos audited 2026-Q2. Versions as listed.
> Submit corrections via PR — we want this to be accurate.
| Feature | OmniRoute 3.8 | 9router 0.4 | LiteLLM 1.x | OpenRouter (SaaS) | Portkey |
| -------------------------------------------------- | :------------------------: | :------------------------: | :------------: | :---------------: | :---------: |
| **Providers** | **207+** | ~40 | ~100 | ~50 | ~30 |
| **Self-hostable** | ✅ | ✅ | ✅ | ❌ | ⚠ paid |
| **OAuth providers (Claude, Codex, Copilot, etc.)** | **15+** | 8 | partial | ❌ | ❌ |
| **Auto-fallback combos** | **14 strategies** | round-robin only | priority-based | tier-based | weighted |
| **Tier 1/2/3 fallback (subscription→cheap→free)** | ✅ + UI | ✅ + UI | manual | n/a | manual |
| **Token compression** | RTK (47 filters) + Caveman | RTK (10 filters) + Caveman | none | none | none |
| **Built-in MCP server** | ✅ 37 tools, 13 scopes | stdio bridge only | ❌ | ❌ | ❌ |
| **A2A protocol** | ✅ 5 skills | ❌ | ❌ | ❌ | ❌ |
| **Memory (FTS5 + vector)** | ✅ | ❌ | ❌ | ❌ | ❌ |
| **Guardrails (PII, injection, vision)** | ✅ | ❌ | partial | ❌ | ✅ paid |
| **Cloud agent integrations** | Codex, Devin, Jules | ❌ | ❌ | ❌ | ❌ |
| **Circuit breaker per provider** | ✅ 3-state, lazy recovery | ❌ | basic | ❌ | ✅ |
| **TLS fingerprint stealth (JA3/JA4)** | ✅ wreq-js | ❌ | ❌ | ❌ | ❌ |
| **Eval framework** | ✅ built-in | ❌ | ❌ | ❌ | ⚠ paid |
| **MITM proxy (intercepts Cursor/Antigravity)** | ✅ cross-platform | ✅ cross-platform | ❌ | ❌ | ❌ |
| **CLI with system tray (no Electron)** | ✅ | ✅ | ❌ | n/a | n/a |
| **CLI machine-ID auto-auth** | ✅ | ✅ | ❌ | n/a | n/a |
| **Dashboard** | Next.js 16 | Next.js | basic | proprietary | proprietary |
| **i18n** | **40+ locales** | 4 locales | ❌ | ❌ | ⚠ |
| **Public agent skills (SKILL.md)** | ✅ 10 | ✅ 8 | ❌ | ❌ | ❌ |
| **Tunnel support (Cloudflared, Tailscale, Ngrok)** | ✅ | partial (Tailscale) | ❌ | n/a | n/a |
| **License** | MIT | MIT | MIT | proprietary | proprietary |
## When to choose OmniRoute
- You self-host and want **maximum provider coverage** (207+)
- You need a **built-in MCP server** (LLM tools, memory, skills exposed as tools)
- You need **A2A protocol** for agent-to-agent workflows
- You want **fingerprint stealth** (JA3/JA4) to avoid detection by upstream CAPTCHAs
- You need **enterprise features** (guardrails, evals, audit trail) without a SaaS bill
## When to choose 9router
- You prefer a **lightweight** router (~5× smaller codebase)
- You want the same tier strategy but with less surface area to maintain
- You don't need MCP / A2A / Memory / Guardrails / Cloud agents
## When to choose LiteLLM
- You're **Python-first** and need tight integration with `litellm.completion()`
- You need **mature production deployment recipes** (k8s, Helm charts)
- Your team already runs Python microservices
## When to choose OpenRouter (SaaS)
- You don't want to self-host
- You're fine paying per-token at SaaS markup
- You need a **single payment method** across all providers
## When to choose Portkey
- You need a **commercial SLA** with uptime guarantees
- You prefer a **managed dashboard** without ops overhead
- You need **enterprise compliance** features out of the box
---
_Last updated: 2026-05-15. Submit corrections via PR to keep this table accurate._

107
docs/marketing/TIERS.md Normal file
View File

@@ -0,0 +1,107 @@
---
title: "OmniRoute Tiers — User Guide"
version: 3.8.0
lastUpdated: 2026-05-15
---
# OmniRoute Tiers — User Guide
OmniRoute organizes the 207+ supported providers into 3 economic tiers. Each
request travels through them in order until one returns successfully — you
get the cheapest viable response without ever writing fallback code.
## Tier 1 — Subscription
**Providers you already pay for.** OmniRoute uses every drop of quota before
it expires.
| Provider | Why Tier 1 |
| ----------------------------------- | -------------------------------------------- |
| Claude Code OAuth | Anthropic Pro/Team — flat-rate, often unused |
| OpenAI Codex (ChatGPT subscription) | Plus/Team includes Codex quota |
| GitHub Copilot | Per-seat — quota resets monthly |
| Cursor IDE | Pro plan quota |
| Antigravity / Windsurf | Built-in quotas |
**Strategy**: route here first for every request that fits the model's
strengths. Quota tracker monitors approaching reset; combo strategies
`reset-aware` and `subscription` prioritize accordingly.
## Tier 2 — Cheap
**Pay-per-token providers under $1/1M tokens.** Reserved for high-volume work
or after Tier 1 quotas hit limits.
| Provider | Price (input/output) | Strengths |
| ---------------------------- | -------------------- | -------------------- |
| DeepSeek V4 Pro | $0.27 / $1.10 per 1M | Code, reasoning |
| GLM-4.5 | $0.60 / $2.20 per 1M | Long context |
| MiniMax M1 | $0.20 / $1.10 per 1M | Speed |
| Qwen Coder | $0.30 / $1.20 per 1M | Code |
| OpenRouter (price-optimized) | varies | 100+ models, dynamic |
**Strategy**: combo `cost-optimized` picks lowest $/token model that meets
the task's capability filter (vision, JSON mode, tools, max-context).
## Tier 3 — Free
**Zero-cost providers** — free tiers, credit programs, OAuth daily quotas.
| Provider | Free quota / credits |
| ---------------- | ------------------------------------ |
| Kiro AI | Free Claude tier (generous fair-use) |
| OpenCode Free | No auth, generous rate limits |
| Qoder | Free OAuth |
| Gemini CLI OAuth | Generous daily quota |
| Google Vertex AI | $300 new-account credits |
| Amazon Q | Free tier for AWS users |
| Pollinations | Open public API |
| Cloudflare AI | Workers AI free tier |
**Strategy**: combo `auto` with budget cap routes here when Tier 1+2 fail
or when `useFreeOnly=true` is set. Free providers often have weaker
rate limits — circuit breaker recovers them on backoff.
## Configuring tiers
Dashboard → **Tiers** → assign your providers. Defaults (from `tierDefaults.json`) are
sensible; edit when you have specific subscriptions to prioritize or providers to exclude.
Auto-Combo's 9-factor scoring also considers tier. See
[`docs/routing/AUTO-COMBO.md`](../routing/AUTO-COMBO.md).
## Telemetry
Dashboard → **Usage** shows tokens spent per tier per day. Use this to:
- Confirm Tier 1 is utilized fully (otherwise you're wasting subscription value)
- Identify which Tier 2 models are picked most (consolidate to 1-2)
- Verify Tier 3 saves money on test/exploration workloads
## Common patterns
### Pure-free workload
```json
{
"strategy": "auto",
"config": { "auto": { "weights": { "costInv": 0.5, "tierPriority": 0.3 } } }
}
```
Forces strongly towards Tier 3; only uses Tier 2 if Tier 3 is unavailable.
### Subscription-first with cheap fallback
```json
{
"strategy": "priority",
"targets": [
{ "provider": "claude-code-oauth", "weight": 1 },
{ "provider": "deepseek", "weight": 1 },
{ "provider": "kiro", "weight": 1 }
]
}
```
Explicit ordered list matching Tier 1 → Tier 2 → Tier 3.

View File

@@ -188,6 +188,27 @@ If `electron/` changed:
- [ ] Open milestone for next version
- [ ] If critical: pin discussion or post in `news.json` for in-app banner
## v3.8.0+ checks
Before shipping any v3.8.x release, verify these additional items:
- [ ] `omniroute --tray` boots on macOS (systray2 installed into `~/.omniroute/runtime/`)
- [ ] `omniroute --tray` boots on Linux (requires DISPLAY; graceful error if not set)
- [ ] `omniroute --tray` boots on Windows (PowerShell NotifyIcon, no extra binaries)
- [ ] `omniroute config tray enable` creates autostart entry; disable removes it
- [ ] `npm install -g omniroute@<this-version>` runs postinstall without fatal exit
- [ ] `omniroute status` works with no `.env` (CLI token path, loopback only)
- [ ] `curl http://localhost:20128/api/shutdown` returns 401 (always-protected route)
- [ ] `curl -H "host: evil.com" http://localhost:20128/api/mcp/sse` returns 401 (loopback guard)
- [ ] SQLite runtime resolves to `bundled` on first run (bundled binary valid for platform)
- [ ] SQLite runtime falls back to `runtime` when `node_modules/better-sqlite3` is deleted
- [ ] Smart MCP filter compresses real `playwright-mcp browser_snapshot` output (≥50% reduction)
- [ ] All 10 `skills/omniroute*/SKILL.md` files are publicly fetchable via raw GitHub URL
- [ ] Onboarding wizard shows "How It Works" tier tour step on fresh setup
- [ ] Home dashboard tier coverage widget shows configured/active counts
---
## Rollback
If release has critical issue:

146
docs/releases/v3.8.0.md Normal file
View File

@@ -0,0 +1,146 @@
---
title: "OmniRoute v3.8.0 Release Notes"
version: 3.8.0
releaseDate: 2026-05-15
---
# OmniRoute v3.8.0 — Release Notes
**Release date**: 2026-05-15
**Highlights**: 7 features inspired by 9router analysis + tier UX overhaul + 10 public AI agent skills.
## New features
### MCP smart text filter (Task 1)
Compression engine specifically for verbose MCP outputs (browser snapshots,
accessibility trees). Collapses ≥30 repeated sibling lines into head + summary
- tail, preserves `[ref=eXX]` anchors required by Playwright/computer-use,
hard-truncates oversized text with a navigation hint.
* New engine: `open-sse/services/compression/engines/mcpAccessibility/`
* Settings: `compression.mcpAccessibility` (migration 056)
* Expected savings: 60-80% on browser snapshot tool results
### Public AI agent skills (Task 2)
Ten `SKILL.md` manifests under `skills/omniroute*/` published for external AI
agents (Claude Desktop, ChatGPT, Cursor, Cline) to consume via raw GitHub URL.
Zero-friction onboarding: tell your agent to fetch one URL and start routing.
### Standalone system tray (Task 3)
CLI users now get a system tray icon without needing Electron. Windows uses
PowerShell `NotifyIcon` (zero new binaries, AV-friendly). macOS/Linux use the
`systray2` fork installed lazily into `~/.omniroute/runtime/`. Includes
cross-platform autostart (LaunchAgent / `.desktop` / registry).
- New flag: `omniroute --tray`
- New command: `omniroute config tray <enable|disable>`
- New modules: `bin/cli/tray/`
### CLI machine-ID token (Task 4)
`HMAC-SHA256(machine-id, salt)` → constant-time-compared token sent via
`x-omniroute-cli-token` header. Lets CLI commands authenticate locally
without JWT/password.
- Accepted only on loopback host + whitelisted read-only routes
- Salt rotation via `OMNIROUTE_CLI_SALT` env var
- Falls back to `~/.omniroute/machine-id` (0600) if `node-machine-id` fails
- Docs: `docs/security/CLI_TOKEN.md`
### Route protection tiers (Task 5)
All API routes classified into 5 tiers (`public`, `read-only`, `protected`,
`always`, `local-only`). Spawn-capable routes (MCP, CLI tools, OAuth
callbacks, MITM, skills) enforce loopback host+origin even with valid JWT —
leaked JWT via tunnel can no longer trigger child process spawn.
- New module: `src/lib/auth/routeGuard.ts`
- New Hard Rule #15 in `CLAUDE.md`
- Docs: `docs/security/ROUTE_GUARD_TIERS.md`
### Caveman SHARED_BOUNDARIES (Task 6)
Caveman prompts (LITE/FULL/ULTRA) now embed a shared boundary clause
instructing the LLM to keep code blocks, file paths, commands, errors, and
URLs exact, and to write security warnings and irreversible-action
confirmations in normal prose.
- New constant: `SHARED_BOUNDARIES` in `open-sse/services/compression/outputMode.ts`
- All 6 languages × 3 intensities updated
- `alreadyApplied` check order fixed (must precede `shouldBypassCavemanOutputMode`)
### Dynamic SQLite runtime installer (Task 7)
Five-step driver resolution chain:
`bundled better-sqlite3``runtime-installed better-sqlite3``lazy install`
`node:sqlite (Node ≥22.5)``bundled sql.js (WASM)`. Each native binary is
validated against expected platform magic bytes (ELF / Mach-O / PE) before
load. Solves Windows EBUSY error on global updates while OmniRoute is running.
- New scripts: `scripts/postinstall.mjs`, `bin/cli/runtime/`
- Runtime dir: `~/.omniroute/runtime/`
- New helper: `src/lib/db/core.ts::ensureDbInitialized()`
- Docs: `docs/ops/SQLITE_RUNTIME.md`
### Tier 1/2/3 marketing & onboarding (Task 8)
README "Why OmniRoute?" section enhanced with ASCII tier diagram and comparison
table. New `/dashboard/onboarding/steps/TierTour.tsx` step shows tier flow on
first run with light/dark SVG assets. Home dashboard gains "Tier coverage"
widget with empty-state CTA.
- New assets: `images/tier-flow-light.svg`, `images/tier-flow-dark.svg`
- New docs: `docs/marketing/TIERS.md`
## Improvements
- New i18n strings for tier UI (en first; translations in v3.8.1)
- Compression analytics dashboard now reports per-engine savings
- 47 RTK filters documented in provider reference
## Security
- Route protection tiers (Task 5) — spawn-capable routes blocked off loopback
- Hard Rule #15 in `CLAUDE.md``assertRouteAllowed(req)` mandatory for spawn routes
- CLI machine-ID token does NOT bypass `always`-protected routes (shutdown, db export)
## Documentation
- `docs/security/CLI_TOKEN.md`
- `docs/security/ROUTE_GUARD_TIERS.md`
- `docs/ops/SQLITE_RUNTIME.md`
- `docs/marketing/TIERS.md`
- `docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md`
## Breaking changes
`getDbInstance()` now requires `ensureDbInitialized()` to have run first.
The server startup orchestrator awaits it automatically. In tests or custom
embeddings, call `await ensureDbInitialized()` before any `src/lib/db/*`
import that triggers instance access.
## Migration guide
For existing installations:
```bash
npm install -g omniroute@3.8.0
omniroute setup # optional — re-runs onboarding to show new tier tour
```
CLI tokens are derived automatically; no migration needed.
To enable tray: `omniroute --tray` or `omniroute config tray enable`.
For developers embedding OmniRoute:
- Replace `getDbInstance()` direct calls with `await ensureDbInitialized(); getDbInstance()`
## Contributors
This release was informed by deep analysis of the `9router` project
(decolua/9router). Their tray, SHARED_BOUNDARIES, and tier marketing
patterns were adapted to OmniRoute's TypeScript infrastructure.

View File

@@ -205,6 +205,37 @@ Including the bare `auto` (default) plus the 6 `AutoVariant` values declared in
(`AutoVariant` itself enumerates 6 values; the 7th option is "no variant" — bare `auto` — handled by `parseAutoPrefix()` as `variant: undefined`.)
## How tiers fit Auto-Combo
The 9-factor scoring function (`open-sse/services/autoCombo/scoring.ts`) treats tier
membership as one signal via the `tierPriority` weight. Default weights (from `DEFAULT_WEIGHTS`):
| Factor | Default weight | Notes |
| ------------------------ | -------------- | --------------------------------- |
| Tier priority | 0.05 | Tier 1 premium → higher score |
| Latency (p50 inverse) | 0.35 | Fastest wins |
| Cost ($/1M inverse) | 0.20 | Cheapest wins |
| Recent health/error rate | 0.15 | Unhealthy deprioritized |
| Quota remaining | 0.10 | Near-exhausted deprioritized |
| Context window match | 0.08 | Penalizes short windows |
| Task fitness | 0.10 | Coding → coding-specialist models |
| Stability | 0.00 | Disabled by default |
Tier alone does **not** force Tier 1 first — if Tier 1 latency is bad or
cost-vs-quality is suboptimal, Tier 2 wins. To force tier ordering, use combo
strategy `priority` and arrange providers by tier.
To strongly favor Tier 1 (subscription), increase `tierPriority` weight:
```json
{
"strategy": "auto",
"config": { "auto": { "weights": { "tierPriority": 0.3, "costInv": 0.05 } } }
}
```
See `docs/marketing/TIERS.md` for tier definitions and provider classification.
## Files
| File | Purpose |

66
images/tier-flow-dark.svg Normal file
View File

@@ -0,0 +1,66 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 420" font-family="system-ui, -apple-system, sans-serif">
<defs>
<marker id="arrow-dark" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
<path d="M0,0 L0,6 L9,3 z" fill="#9ca3af"/>
</marker>
<marker id="arrow-fallback-dark" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
<path d="M0,0 L0,6 L9,3 z" fill="#f87171"/>
</marker>
</defs>
<!-- Background -->
<rect width="800" height="420" fill="#111827" rx="12"/>
<!-- Title -->
<text x="400" y="32" text-anchor="middle" font-size="18" font-weight="700" fill="#f9fafb">OmniRoute 3-tier fallback</text>
<text x="400" y="52" text-anchor="middle" font-size="12" fill="#6b7280">Never stop building — automatic zero-config failover across 207+ providers</text>
<!-- Client box -->
<rect x="275" y="70" width="250" height="52" rx="8" fill="#1f2937" stroke="#374151" stroke-width="1.5"/>
<text x="400" y="97" text-anchor="middle" font-size="13" font-weight="600" fill="#f3f4f6">Your IDE / CLI / App</text>
<text x="400" y="114" text-anchor="middle" font-size="10" fill="#6b7280">Claude Code · Cursor · Cline · Copilot · JetBrains AI</text>
<!-- Arrow down to router -->
<line x1="400" y1="122" x2="400" y2="150" stroke="#9ca3af" stroke-width="1.5" marker-end="url(#arrow-dark)"/>
<text x="420" y="140" font-size="9" fill="#6b7280">localhost:20128/v1</text>
<!-- Router box -->
<rect x="200" y="152" width="400" height="72" rx="8" fill="#1e3a5f" stroke="#3b82f6" stroke-width="1.5"/>
<text x="400" y="177" text-anchor="middle" font-size="14" font-weight="700" fill="#93c5fd">OmniRoute Smart Router</text>
<text x="400" y="196" text-anchor="middle" font-size="10" fill="#60a5fa">RTK compression · Caveman terse-mode · Auto-Combo (14 strategies)</text>
<text x="400" y="213" text-anchor="middle" font-size="10" fill="#60a5fa">Circuit breaker · MCP server (37 tools) · A2A · Memory · Guardrails</text>
<!-- Arrows from router to tiers -->
<line x1="310" y1="224" x2="150" y2="285" stroke="#9ca3af" stroke-width="1.5" marker-end="url(#arrow-dark)"/>
<line x1="400" y1="224" x2="400" y2="285" stroke="#9ca3af" stroke-width="1.5" marker-end="url(#arrow-dark)"/>
<line x1="490" y1="224" x2="650" y2="285" stroke="#9ca3af" stroke-width="1.5" marker-end="url(#arrow-dark)"/>
<!-- Tier 1: Subscription -->
<rect x="30" y="287" width="220" height="110" rx="8" fill="#1c1a00" stroke="#d97706" stroke-width="1.5"/>
<text x="140" y="310" text-anchor="middle" font-size="11" font-weight="700" fill="#fbbf24">Tier 1 — SUBSCRIPTION</text>
<text x="140" y="328" text-anchor="middle" font-size="10" fill="#d97706">Pay flat-rate, use every drop</text>
<text x="140" y="346" text-anchor="middle" font-size="9.5" fill="#fbbf24">Claude Code · Codex · Copilot</text>
<text x="140" y="362" text-anchor="middle" font-size="9.5" fill="#fbbf24">Cursor · Antigravity · Windsurf</text>
<text x="140" y="387" text-anchor="middle" font-size="9" fill="#f59e0b">quota exhausted → Tier 2</text>
<line x1="140" y1="378" x2="300" y2="378" stroke="#f87171" stroke-width="1" stroke-dasharray="4,2" marker-end="url(#arrow-fallback-dark)"/>
<!-- Tier 2: Cheap -->
<rect x="290" y="287" width="220" height="110" rx="8" fill="#052e16" stroke="#16a34a" stroke-width="1.5"/>
<text x="400" y="310" text-anchor="middle" font-size="11" font-weight="700" fill="#4ade80">Tier 2 — CHEAP</text>
<text x="400" y="328" text-anchor="middle" font-size="10" fill="#22c55e">Pay-per-token, under $1/1M</text>
<text x="400" y="346" text-anchor="middle" font-size="9.5" fill="#4ade80">DeepSeek $0.27 · GLM $0.60</text>
<text x="400" y="362" text-anchor="middle" font-size="9.5" fill="#4ade80">MiniMax $0.20 · Qwen $0.30</text>
<text x="400" y="387" text-anchor="middle" font-size="9" fill="#16a34a">budget hit → Tier 3</text>
<line x1="400" y1="378" x2="560" y2="378" stroke="#f87171" stroke-width="1" stroke-dasharray="4,2" marker-end="url(#arrow-fallback-dark)"/>
<!-- Tier 3: Free -->
<rect x="550" y="287" width="220" height="110" rx="8" fill="#1e1b4b" stroke="#6366f1" stroke-width="1.5"/>
<text x="660" y="310" text-anchor="middle" font-size="11" font-weight="700" fill="#a5b4fc">Tier 3 — FREE</text>
<text x="660" y="328" text-anchor="middle" font-size="10" fill="#818cf8">Free tiers &amp; credit programs</text>
<text x="660" y="346" text-anchor="middle" font-size="9.5" fill="#a5b4fc">Kiro · OpenCode · Qoder</text>
<text x="660" y="362" text-anchor="middle" font-size="9.5" fill="#a5b4fc">Gemini CLI · Vertex $300cr</text>
<text x="660" y="387" text-anchor="middle" font-size="9" fill="#6366f1">always available</text>
<!-- Footer note -->
<text x="400" y="413" text-anchor="middle" font-size="9" fill="#4b5563">Fallback happens in milliseconds — transparent to the calling tool</text>
</svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@@ -0,0 +1,66 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 420" font-family="system-ui, -apple-system, sans-serif">
<defs>
<marker id="arrow-light" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
<path d="M0,0 L0,6 L9,3 z" fill="#6b7280"/>
</marker>
<marker id="arrow-fallback-light" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
<path d="M0,0 L0,6 L9,3 z" fill="#ef4444"/>
</marker>
</defs>
<!-- Background -->
<rect width="800" height="420" fill="#f9fafb" rx="12"/>
<!-- Title -->
<text x="400" y="32" text-anchor="middle" font-size="18" font-weight="700" fill="#111827">OmniRoute 3-tier fallback</text>
<text x="400" y="52" text-anchor="middle" font-size="12" fill="#6b7280">Never stop building — automatic zero-config failover across 207+ providers</text>
<!-- Client box -->
<rect x="275" y="70" width="250" height="52" rx="8" fill="#ffffff" stroke="#d1d5db" stroke-width="1.5"/>
<text x="400" y="97" text-anchor="middle" font-size="13" font-weight="600" fill="#374151">Your IDE / CLI / App</text>
<text x="400" y="114" text-anchor="middle" font-size="10" fill="#9ca3af">Claude Code · Cursor · Cline · Copilot · JetBrains AI</text>
<!-- Arrow down to router -->
<line x1="400" y1="122" x2="400" y2="150" stroke="#6b7280" stroke-width="1.5" marker-end="url(#arrow-light)"/>
<text x="420" y="140" font-size="9" fill="#9ca3af">localhost:20128/v1</text>
<!-- Router box -->
<rect x="200" y="152" width="400" height="72" rx="8" fill="#dbeafe" stroke="#3b82f6" stroke-width="1.5"/>
<text x="400" y="177" text-anchor="middle" font-size="14" font-weight="700" fill="#1d4ed8">OmniRoute Smart Router</text>
<text x="400" y="196" text-anchor="middle" font-size="10" fill="#3b82f6">RTK compression · Caveman terse-mode · Auto-Combo (14 strategies)</text>
<text x="400" y="213" text-anchor="middle" font-size="10" fill="#3b82f6">Circuit breaker · MCP server (37 tools) · A2A · Memory · Guardrails</text>
<!-- Arrows from router to tiers -->
<line x1="310" y1="224" x2="150" y2="285" stroke="#6b7280" stroke-width="1.5" marker-end="url(#arrow-light)"/>
<line x1="400" y1="224" x2="400" y2="285" stroke="#6b7280" stroke-width="1.5" marker-end="url(#arrow-light)"/>
<line x1="490" y1="224" x2="650" y2="285" stroke="#6b7280" stroke-width="1.5" marker-end="url(#arrow-light)"/>
<!-- Tier 1: Subscription -->
<rect x="30" y="287" width="220" height="110" rx="8" fill="#fffbeb" stroke="#f59e0b" stroke-width="1.5"/>
<text x="140" y="310" text-anchor="middle" font-size="11" font-weight="700" fill="#92400e">Tier 1 — SUBSCRIPTION</text>
<text x="140" y="328" text-anchor="middle" font-size="10" fill="#78350f">Pay flat-rate, use every drop</text>
<text x="140" y="346" text-anchor="middle" font-size="9.5" fill="#92400e">Claude Code · Codex · Copilot</text>
<text x="140" y="362" text-anchor="middle" font-size="9.5" fill="#92400e">Cursor · Antigravity · Windsurf</text>
<text x="140" y="387" text-anchor="middle" font-size="9" fill="#b45309">quota exhausted → Tier 2</text>
<line x1="140" y1="378" x2="300" y2="378" stroke="#ef4444" stroke-width="1" stroke-dasharray="4,2" marker-end="url(#arrow-fallback-light)"/>
<!-- Tier 2: Cheap -->
<rect x="290" y="287" width="220" height="110" rx="8" fill="#f0fdf4" stroke="#22c55e" stroke-width="1.5"/>
<text x="400" y="310" text-anchor="middle" font-size="11" font-weight="700" fill="#14532d">Tier 2 — CHEAP</text>
<text x="400" y="328" text-anchor="middle" font-size="10" fill="#166534">Pay-per-token, under $1/1M</text>
<text x="400" y="346" text-anchor="middle" font-size="9.5" fill="#14532d">DeepSeek $0.27 · GLM $0.60</text>
<text x="400" y="362" text-anchor="middle" font-size="9.5" fill="#14532d">MiniMax $0.20 · Qwen $0.30</text>
<text x="400" y="387" text-anchor="middle" font-size="9" fill="#15803d">budget hit → Tier 3</text>
<line x1="400" y1="378" x2="560" y2="378" stroke="#ef4444" stroke-width="1" stroke-dasharray="4,2" marker-end="url(#arrow-fallback-light)"/>
<!-- Tier 3: Free -->
<rect x="550" y="287" width="220" height="110" rx="8" fill="#eef2ff" stroke="#6366f1" stroke-width="1.5"/>
<text x="660" y="310" text-anchor="middle" font-size="11" font-weight="700" fill="#312e81">Tier 3 — FREE</text>
<text x="660" y="328" text-anchor="middle" font-size="10" fill="#3730a3">Free tiers &amp; credit programs</text>
<text x="660" y="346" text-anchor="middle" font-size="9.5" fill="#312e81">Kiro · OpenCode · Qoder</text>
<text x="660" y="362" text-anchor="middle" font-size="9.5" fill="#312e81">Gemini CLI · Vertex $300cr</text>
<text x="660" y="387" text-anchor="middle" font-size="9" fill="#4338ca">always available</text>
<!-- Footer note -->
<text x="400" y="413" text-anchor="middle" font-size="9" fill="#9ca3af">Fallback happens in milliseconds — transparent to the calling tool</text>
</svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@@ -12,6 +12,7 @@ import { AI_PROVIDERS, FREE_PROVIDERS, OAUTH_PROVIDERS } from "@/shared/constant
import { useNotificationStore } from "@/store/notificationStore";
import { copyToClipboard } from "@/shared/utils/clipboard";
import type { NewsAnnouncement } from "@/shared/utils/releaseNotes";
import { TierCoverageWidget } from "./TierCoverageWidget";
type UpdateStep = {
step: string;
@@ -748,6 +749,9 @@ export default function HomePageClient({ machineId }: HomePageClientProps) {
</div>
</Card>
{/* Tier Coverage */}
<TierCoverageWidget />
{/* Providers Overview */}
<Card>
<div className="flex items-center justify-between mb-4">

View File

@@ -0,0 +1,100 @@
"use client";
import { useEffect, useState } from "react";
import { useTranslations } from "next-intl";
import Link from "next/link";
import { FREE_PROVIDERS, OAUTH_PROVIDERS } from "@/shared/constants/providers";
type TierCount = { configured: number; active: number };
type Coverage = { tier1: TierCount; tier2: TierCount; tier3: TierCount };
const FREE_IDS = new Set(Object.keys(FREE_PROVIDERS));
const OAUTH_IDS = new Set(Object.keys(OAUTH_PROVIDERS));
function classifyConnection(providerId: string): "tier1" | "tier2" | "tier3" {
if (FREE_IDS.has(providerId)) return "tier3";
if (OAUTH_IDS.has(providerId)) return "tier1";
return "tier2";
}
const TIER_LABELS: Record<string, string> = {
tier1: "Subscription",
tier2: "Cheap",
tier3: "Free",
};
const TIER_COLORS: Record<string, string> = {
tier1: "text-amber-500",
tier2: "text-green-500",
tier3: "text-indigo-400",
};
export function TierCoverageWidget() {
const t = useTranslations("common");
const [coverage, setCoverage] = useState<Coverage | null>(null);
useEffect(() => {
fetch("/api/providers")
.then((r) => r.json())
.then((data) => {
const connections: { provider: string; isActive: boolean }[] = data.connections ?? [];
const counts: Coverage = {
tier1: { configured: 0, active: 0 },
tier2: { configured: 0, active: 0 },
tier3: { configured: 0, active: 0 },
};
for (const conn of connections) {
const tier = classifyConnection(conn.provider);
counts[tier].configured++;
if (conn.isActive) counts[tier].active++;
}
setCoverage(counts);
})
.catch(() => {});
}, []);
if (!coverage) return null;
const tiers = (["tier1", "tier2", "tier3"] as const).map((k) => ({
key: k,
label: TIER_LABELS[k],
colorClass: TIER_COLORS[k],
...coverage[k],
}));
return (
<div className="rounded-xl border border-white/[0.06] bg-surface p-5">
<div className="flex items-center justify-between mb-4">
<div>
<h3 className="font-semibold text-sm">Tier coverage</h3>
<p className="text-xs text-text-muted mt-0.5">Providers configured per fallback tier</p>
</div>
<Link
href="/dashboard/providers"
className="text-xs text-text-muted hover:text-text-main transition-colors"
>
Manage
</Link>
</div>
<div className="grid grid-cols-3 gap-3">
{tiers.map(({ key, label, colorClass, configured, active }) => (
<div key={key} className="text-center">
<div className={`text-2xl font-bold ${colorClass}`}>{active}</div>
<div className="text-xs text-text-muted mt-0.5">{label}</div>
{configured > 0 && active < configured && (
<div className="text-xs text-amber-500 mt-0.5">{configured - active} inactive</div>
)}
{configured === 0 && (
<Link
href="/dashboard/providers/new"
className="text-xs text-blue-400 underline mt-0.5 block"
>
{t("add")}
</Link>
)}
</div>
))}
</div>
</div>
);
}

View File

@@ -0,0 +1,27 @@
"use client";
import { useTheme } from "next-themes";
import Image from "next/image";
export function TierFlowDiagram() {
const { resolvedTheme } = useTheme();
const src =
resolvedTheme === "dark" ? "/images/tier-flow-dark.svg" : "/images/tier-flow-light.svg";
return (
<div className="flex flex-col items-center gap-3 my-4">
<Image
src={src}
alt="OmniRoute 3-tier fallback diagram"
width={800}
height={420}
priority
className="w-full max-w-2xl rounded-lg border border-white/[0.06]"
/>
<p className="text-xs text-text-muted max-w-xl text-center">
Requests flow through your subscription quotas first, then pay-per-token cheap providers,
then free-tier providers automatic, zero-config.
</p>
</div>
);
}

View File

@@ -4,9 +4,10 @@ import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import { useTranslations } from "next-intl";
import { useDisplayBaseUrl } from "@/shared/hooks";
import { TierTour } from "./steps/TierTour";
const STEP_IDS = ["welcome", "security", "provider", "test", "done"];
const STEP_ICONS = ["waving_hand", "lock", "dns", "play_circle", "check_circle"];
const STEP_IDS = ["welcome", "tiers", "security", "provider", "test", "done"];
const STEP_ICONS = ["waving_hand", "layers", "lock", "dns", "play_circle", "check_circle"];
const COMMON_PROVIDERS = [
{ id: "openai", name: "OpenAI", color: "#10A37F" },
@@ -301,6 +302,9 @@ export default function OnboardingWizard() {
</div>
)}
{/* Tiers */}
{currentStep.id === "tiers" && <TierTour />}
{/* Security */}
{currentStep.id === "security" && (
<div className="space-y-4">
@@ -465,6 +469,14 @@ export default function OnboardingWizard() {
{t("getStarted")}
</button>
)}
{currentStep.id === "tiers" && (
<button
onClick={handleNext}
className="px-6 py-2.5 bg-primary rounded-lg text-white font-medium text-sm hover:bg-primary/90 transition-colors cursor-pointer"
>
{t("continue")}
</button>
)}
{currentStep.id === "security" && (
<button
onClick={handleSetPassword}

View File

@@ -0,0 +1,75 @@
"use client";
import { useTranslations } from "next-intl";
import Link from "next/link";
import { TierFlowDiagram } from "../components/TierFlowDiagram";
type TierCardProps = {
number: number;
colorClass: string;
label: string;
description: string;
examples: string[];
};
function TierCard({ number, colorClass, label, description, examples }: TierCardProps) {
return (
<div className={`p-4 rounded-xl border-2 ${colorClass}`}>
<div className="flex items-baseline gap-2 mb-2">
<span className="text-2xl font-bold">{number}</span>
<span className="font-semibold text-sm">{label}</span>
</div>
<p className="text-xs text-text-muted mb-3">{description}</p>
<ul className="text-xs space-y-0.5 text-text-muted">
{examples.map((e) => (
<li key={e}>· {e}</li>
))}
</ul>
</div>
);
}
export function TierTour() {
const t = useTranslations("onboarding.tier");
return (
<div className="space-y-5">
<div className="text-center">
<p className="text-sm text-text-muted">{t("subtitle")}</p>
</div>
<TierFlowDiagram />
<div className="grid grid-cols-1 gap-3 sm:grid-cols-3">
<TierCard
number={1}
colorClass="border-amber-500/60 bg-amber-500/[0.06]"
label={t("tier1.label")}
description={t("tier1.description")}
examples={["Claude Code", "Codex", "Copilot", "Cursor"]}
/>
<TierCard
number={2}
colorClass="border-green-500/60 bg-green-500/[0.06]"
label={t("tier2.label")}
description={t("tier2.description")}
examples={["DeepSeek", "GLM", "MiniMax", "Qwen"]}
/>
<TierCard
number={3}
colorClass="border-indigo-500/60 bg-indigo-500/[0.06]"
label={t("tier3.label")}
description={t("tier3.description")}
examples={["Kiro", "OpenCode", "Gemini CLI", "Vertex"]}
/>
</div>
<p className="text-xs text-text-muted/60 text-center">
<Link href="/dashboard/providers/new" className="underline hover:text-text-muted">
{t("configure")}
</Link>{" "}
after setup.
</p>
</div>
);
}

View File

@@ -2585,8 +2585,27 @@
"onboarding": {
"welcome": "Welcome",
"security": "Security",
"tiers": "How It Works",
"test": "Test",
"ready": "Ready!",
"tier": {
"title": "How OmniRoute saves you money",
"subtitle": "Three tiers, automatic fallback — never stop building.",
"tier1": {
"label": "Subscription",
"description": "Use what you already pay for — every drop of quota before it resets."
},
"tier2": {
"label": "Cheap",
"description": "Pay-per-token under $1/1M — kicks in when subscription quotas hit."
},
"tier3": {
"label": "Free",
"description": "Generous free tiers and credit programs — final safety net."
},
"continue": "Continue",
"configure": "Configure tiers now"
},
"setPassword": "Set Password",
"addProvider": "Add your first provider",
"getStarted": "Get Started",