diff --git a/CHANGELOG.md b/CHANGELOG.md index ea738227f7..59802fc1ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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-` 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 diff --git a/README.md b/README.md index 173d0a7a6c..093d6a0fd2 100644 --- a/README.md +++ b/README.md @@ -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 --- diff --git a/docs/architecture/REPOSITORY_MAP.md b/docs/architecture/REPOSITORY_MAP.md index 205396b9b0..8d3ada80fc 100644 --- a/docs/architecture/REPOSITORY_MAP.md +++ b/docs/architecture/REPOSITORY_MAP.md @@ -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 | diff --git a/docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md b/docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md new file mode 100644 index 0000000000..6576766857 --- /dev/null +++ b/docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md @@ -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._ diff --git a/docs/marketing/TIERS.md b/docs/marketing/TIERS.md new file mode 100644 index 0000000000..b63bca8284 --- /dev/null +++ b/docs/marketing/TIERS.md @@ -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. diff --git a/docs/ops/RELEASE_CHECKLIST.md b/docs/ops/RELEASE_CHECKLIST.md index 9756c01ac2..0cabf18f87 100644 --- a/docs/ops/RELEASE_CHECKLIST.md +++ b/docs/ops/RELEASE_CHECKLIST.md @@ -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@` 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: diff --git a/docs/releases/v3.8.0.md b/docs/releases/v3.8.0.md new file mode 100644 index 0000000000..6b9d0a8e46 --- /dev/null +++ b/docs/releases/v3.8.0.md @@ -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 ` +- 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. diff --git a/docs/routing/AUTO-COMBO.md b/docs/routing/AUTO-COMBO.md index eb12811af1..1a9ea95bc0 100644 --- a/docs/routing/AUTO-COMBO.md +++ b/docs/routing/AUTO-COMBO.md @@ -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 | diff --git a/images/tier-flow-dark.svg b/images/tier-flow-dark.svg new file mode 100644 index 0000000000..a8c43093a5 --- /dev/null +++ b/images/tier-flow-dark.svg @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + OmniRoute 3-tier fallback + Never stop building — automatic zero-config failover across 207+ providers + + + + Your IDE / CLI / App + Claude Code · Cursor · Cline · Copilot · JetBrains AI + + + + localhost:20128/v1 + + + + OmniRoute Smart Router + RTK compression · Caveman terse-mode · Auto-Combo (14 strategies) + Circuit breaker · MCP server (37 tools) · A2A · Memory · Guardrails + + + + + + + + + Tier 1 — SUBSCRIPTION + Pay flat-rate, use every drop + Claude Code · Codex · Copilot + Cursor · Antigravity · Windsurf + quota exhausted → Tier 2 + + + + + Tier 2 — CHEAP + Pay-per-token, under $1/1M + DeepSeek $0.27 · GLM $0.60 + MiniMax $0.20 · Qwen $0.30 + budget hit → Tier 3 + + + + + Tier 3 — FREE + Free tiers & credit programs + Kiro · OpenCode · Qoder + Gemini CLI · Vertex $300cr + always available + + + Fallback happens in milliseconds — transparent to the calling tool + diff --git a/images/tier-flow-light.svg b/images/tier-flow-light.svg new file mode 100644 index 0000000000..c05dd86406 --- /dev/null +++ b/images/tier-flow-light.svg @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + OmniRoute 3-tier fallback + Never stop building — automatic zero-config failover across 207+ providers + + + + Your IDE / CLI / App + Claude Code · Cursor · Cline · Copilot · JetBrains AI + + + + localhost:20128/v1 + + + + OmniRoute Smart Router + RTK compression · Caveman terse-mode · Auto-Combo (14 strategies) + Circuit breaker · MCP server (37 tools) · A2A · Memory · Guardrails + + + + + + + + + Tier 1 — SUBSCRIPTION + Pay flat-rate, use every drop + Claude Code · Codex · Copilot + Cursor · Antigravity · Windsurf + quota exhausted → Tier 2 + + + + + Tier 2 — CHEAP + Pay-per-token, under $1/1M + DeepSeek $0.27 · GLM $0.60 + MiniMax $0.20 · Qwen $0.30 + budget hit → Tier 3 + + + + + Tier 3 — FREE + Free tiers & credit programs + Kiro · OpenCode · Qoder + Gemini CLI · Vertex $300cr + always available + + + Fallback happens in milliseconds — transparent to the calling tool + diff --git a/src/app/(dashboard)/dashboard/HomePageClient.tsx b/src/app/(dashboard)/dashboard/HomePageClient.tsx index 2011948e6e..c9b4d06e34 100644 --- a/src/app/(dashboard)/dashboard/HomePageClient.tsx +++ b/src/app/(dashboard)/dashboard/HomePageClient.tsx @@ -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) { + {/* Tier Coverage */} + + {/* Providers Overview */}
diff --git a/src/app/(dashboard)/dashboard/TierCoverageWidget.tsx b/src/app/(dashboard)/dashboard/TierCoverageWidget.tsx new file mode 100644 index 0000000000..6d08ae16b5 --- /dev/null +++ b/src/app/(dashboard)/dashboard/TierCoverageWidget.tsx @@ -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 = { + tier1: "Subscription", + tier2: "Cheap", + tier3: "Free", +}; +const TIER_COLORS: Record = { + tier1: "text-amber-500", + tier2: "text-green-500", + tier3: "text-indigo-400", +}; + +export function TierCoverageWidget() { + const t = useTranslations("common"); + const [coverage, setCoverage] = useState(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 ( +
+
+
+

Tier coverage

+

Providers configured per fallback tier

+
+ + Manage → + +
+ +
+ {tiers.map(({ key, label, colorClass, configured, active }) => ( +
+
{active}
+
{label}
+ {configured > 0 && active < configured && ( +
{configured - active} inactive
+ )} + {configured === 0 && ( + + {t("add")} + + )} +
+ ))} +
+
+ ); +} diff --git a/src/app/(dashboard)/dashboard/onboarding/components/TierFlowDiagram.tsx b/src/app/(dashboard)/dashboard/onboarding/components/TierFlowDiagram.tsx new file mode 100644 index 0000000000..74bfbff6d6 --- /dev/null +++ b/src/app/(dashboard)/dashboard/onboarding/components/TierFlowDiagram.tsx @@ -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 ( +
+ OmniRoute 3-tier fallback diagram +

+ Requests flow through your subscription quotas first, then pay-per-token cheap providers, + then free-tier providers — automatic, zero-config. +

+
+ ); +} diff --git a/src/app/(dashboard)/dashboard/onboarding/page.tsx b/src/app/(dashboard)/dashboard/onboarding/page.tsx index df80edcaa5..fc99f0a89a 100644 --- a/src/app/(dashboard)/dashboard/onboarding/page.tsx +++ b/src/app/(dashboard)/dashboard/onboarding/page.tsx @@ -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() {
)} + {/* Tiers */} + {currentStep.id === "tiers" && } + {/* Security */} {currentStep.id === "security" && (
@@ -465,6 +469,14 @@ export default function OnboardingWizard() { {t("getStarted")} )} + {currentStep.id === "tiers" && ( + + )} {currentStep.id === "security" && (
+ ); +} + +export function TierTour() { + const t = useTranslations("onboarding.tier"); + + return ( +
+
+

{t("subtitle")}

+
+ + + +
+ + + +
+ +

+ + {t("configure")} + {" "} + after setup. +

+
+ ); +} diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 7955339c41..429bdfac29 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -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",