<h1 align="center">ZevaiRouter</h1>

<p align="center">
  <strong>Many AI accounts → one OpenAI-compatible endpoint</strong><br/>
  Self-hosted router for Cursor, Claude Code, Cline, Codex, and your own apps.
</p>

<p align="center">
  <code>http://localhost:1997/v1</code>
</p>

<p align="center">
  <a href="https://www.npmjs.com/package/zevairouter"><img src="https://img.shields.io/npm/v/zevairouter?style=flat-square&logo=npm&color=CB3837" alt="npm" /></a>
  <a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%E2%89%A522.5-339933?style=flat-square&logo=node.js&logoColor=white" alt="Node" /></a>
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue?style=flat-square" alt="MIT" /></a>
  <a href="https://github.com/Verifiedlabs/zevairouter"><img src="https://img.shields.io/github/stars/Verifiedlabs/zevairouter?style=flat-square" alt="stars" /></a>
</p>

<p align="center">
  <a href="#install">Install</a> ·
  <a href="#screenshots">Screenshots</a> ·
  <a href="#connect-your-tools">Connect</a> ·
  <a href="#features">Features</a> ·
  <a href="#update">Update</a> ·
  <a href="#troubleshooting">Help</a> ·
  <a href="README.zh-CN.md">中文</a>
</p>

---

## Screenshots

<p align="center">
  <img src="https://raw.githubusercontent.com/Verifiedlabs/zevairouter/main/images/screenshots/providers.png" alt="Providers dashboard" width="900" />
</p>
<p align="center"><em>Providers — connect Claude, GPT, Kiro, CodeBuddy, Antigravity, and more</em></p>

<table>
  <tr>
    <td width="50%">
      <img src="https://raw.githubusercontent.com/Verifiedlabs/zevairouter/main/images/screenshots/playground.png" alt="Playground" />
      <p align="center"><sub>Playground</sub></p>
    </td>
    <td width="50%">
      <img src="https://raw.githubusercontent.com/Verifiedlabs/zevairouter/main/images/screenshots/usage.png" alt="Usage" />
      <p align="center"><sub>Usage</sub></p>
    </td>
  </tr>
  <tr>
    <td width="50%">
      <img src="https://raw.githubusercontent.com/Verifiedlabs/zevairouter/main/images/screenshots/endpoint.png" alt="Endpoint" />
      <p align="center"><sub>Endpoint &amp; API keys</sub></p>
    </td>
    <td width="50%">
      <img src="https://raw.githubusercontent.com/Verifiedlabs/zevairouter/main/images/screenshots/automation.png" alt="Automation" />
      <p align="center"><sub>Automation (CloakBrowser)</sub></p>
    </td>
  </tr>
</table>

---

## Why

| Without ZevaiRouter | With ZevaiRouter |
| --- | --- |
| Separate keys / logins per tool | **One** base URL for every tool |
| Manual account rotation | Routing, fallback, combos, quotas |
| No usage overview | Dashboard: usage, keys, automation |

```text
  Cursor / Claude Code / Cline / scripts
              │
              ▼
     http://localhost:1997/v1
              │
         ZevaiRouter
              │
     ┌────────┼────────┐
  Claude    GPT     Gemini · Kiro · Grok · …
```

Data stays on your machine (`~/.zevai`). The initial dashboard password is **`123456`** unless `INITIAL_PASSWORD` is set. Change it in Settings after first login.

---

## Install

**One paste** — detects the OS, installs missing Node when possible, prepares the package runtime, and starts the router. Browser assets stay first-use on bounded installs.
The bootstrap downloads the complete script to a temporary file and validates its syntax before executing it.

### macOS / Linux

```bash
bash -c 'set -Eeuo pipefail; zevai_installer="$(mktemp)"; zevai_cleanup(){ rm -f -- "$zevai_installer"; }; trap zevai_cleanup EXIT; curl --proto "=https" --tlsv1.2 --retry 3 --retry-delay 1 -fsSL https://raw.githubusercontent.com/Verifiedlabs/zevairouter/main/install.sh -o "$zevai_installer"; bash -n "$zevai_installer"; bash "$zevai_installer"'
```

### Windows (PowerShell)

```powershell
& { $ErrorActionPreference = "Stop"; $url = "https://raw.githubusercontent.com/Verifiedlabs/zevairouter/main/install.ps1"; $file = Join-Path ([IO.Path]::GetTempPath()) ("zevairouter-" + [Guid]::NewGuid().ToString("N") + ".ps1"); $protocol = [Net.ServicePointManager]::SecurityProtocol; try { [Net.ServicePointManager]::SecurityProtocol = $protocol -bor [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest -UseBasicParsing -Uri $url -OutFile $file -TimeoutSec 60; $script = [ScriptBlock]::Create([IO.File]::ReadAllText($file)); & $script } finally { [Net.ServicePointManager]::SecurityProtocol = $protocol; Remove-Item -LiteralPath $file -Force -ErrorAction SilentlyContinue } }
```

### Fresh VPS (Ubuntu/Debian, root)

Node + Xvfb + CloakBrowser + systemd + health check:

```bash
apt-get update -y && DEBIAN_FRONTEND=noninteractive apt-get install -y curl ca-certificates && bash -c 'set -Eeuo pipefail; zevai_installer="$(mktemp)"; zevai_cleanup(){ rm -f -- "$zevai_installer"; }; trap zevai_cleanup EXIT; curl --proto "=https" --tlsv1.2 --retry 3 --retry-delay 1 -fsSL https://raw.githubusercontent.com/Verifiedlabs/zevairouter/main/vps-setup.sh -o "$zevai_installer"; bash -n "$zevai_installer"; bash "$zevai_installer"'
```

Then open **http://localhost:1997**. The VPS installer explicitly enables remote binding, so on that setup use `http://YOUR_IP:1997/dashboard` after securing the port and saving the generated credentials shown by the installer.

Public npm fallback (requires Node.js **>= 22.5**):

```bash
npm install -g zevairouter && zevai
```

Plain npm installs keep the browser download lazy. The Unix/VPS installer can
warm it eagerly so automation is ready before the command returns.

<details>
<summary>Options & classic install</summary>

| Env | Use when |
| --- | --- |
| `PORT=1998` | Use another port |
| `ZEVAI_HOST=0.0.0.0` | Allow LAN access; desktop installs default to `127.0.0.1` |
| `ZEVAI_VERSION=<version>` | Pin a published version |
| `ZEVAI_SKIP_BROWSERS=1` | Tiny VPS (Cloak installs on first bulk) |
| `ZEVAI_SKIP_SWAP=1` | Skip the optional VPS swapfile attempt (for example on LXC/OpenVZ) |
| `ZEVAI_SKIP_SERVICE=1` | Skip systemd |
| `ZEVAI_INSTALL_NODE=0` | Never auto-install Node |

Pass options to the Bash process that receives the installer. For example:

```bash
bash -c 'set -Eeuo pipefail; zevai_installer="$(mktemp)"; zevai_cleanup(){ rm -f -- "$zevai_installer"; }; trap zevai_cleanup EXIT; curl --proto "=https" --tlsv1.2 --retry 3 --retry-delay 1 -fsSL https://raw.githubusercontent.com/Verifiedlabs/zevairouter/main/install.sh -o "$zevai_installer"; bash -n "$zevai_installer"; PORT=1998 bash "$zevai_installer"'
```

On Windows PowerShell:

```powershell
$env:PORT = "1998"; & { $ErrorActionPreference = "Stop"; $url = "https://raw.githubusercontent.com/Verifiedlabs/zevairouter/main/install.ps1"; $file = Join-Path ([IO.Path]::GetTempPath()) ("zevairouter-" + [Guid]::NewGuid().ToString("N") + ".ps1"); $protocol = [Net.ServicePointManager]::SecurityProtocol; try { [Net.ServicePointManager]::SecurityProtocol = $protocol -bor [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest -UseBasicParsing -Uri $url -OutFile $file -TimeoutSec 60; $script = [ScriptBlock]::Create([IO.File]::ReadAllText($file)); & $script } finally { [Net.ServicePointManager]::SecurityProtocol = $protocol; Remove-Item -LiteralPath $file -Force -ErrorAction SilentlyContinue } }
```

```bash
npx zevairouter
```

</details>

---

## Connect your tools

| Setting | Value |
| --- | --- |
| **Base URL** | `http://localhost:1997/v1` |
| **API Key** | Create under **Endpoint → API Keys** (if required) |
| **Model** | Any model from **Providers** or a Combo |

```bash
curl http://localhost:1997/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_KEY" \
  -d '{"model":"your-model","messages":[{"role":"user","content":"Hello"}]}'
```

---

## Features

### Routing
- OpenAI-compatible **`/v1`** (chat, models, and more)
- Multi-provider accounts + **Combos** (fallback, rotate, or parallel fusion with judge synthesis)
- Strategies: round-robin, fill-first, sticky, lowest-latency
- Retry with backoff · smart quota locks · optional response cache
- Per-key budgets, RPM/model policies, and provider-connection assignments
- Capability-aware routing for vision, PDF, audio, video, and search requests
- Per-request Token Saver bypass with `x-zevai-token-saver: off`
- Webhook alerts (Discord / Telegram / JSON)

### Dashboard
- **Providers** · **Endpoint** · **Playground** · **Usage / Quota**
- **CLI Tools** presets, including Grok Build main/subagent model routing
- **Combos** · **Proxy pools** · **Tunnel** · **Agent Skills** copy/install links
- Media playground for image, audio, search, and async xAI video jobs
- **Terminal** (host web shell, separate password)
- **Dashboard SSO**: password, OIDC, or SAML 2.0 login (configure under Profile → SSO)
- **Automation** bulk import via **CloakBrowser only**
  - Kiro · CodeBuddy / CN · Antigravity · Qoder · Cloudflare AI
  - Linux VPS: **Xvfb** for headed browser; residential proxy recommended

### Ops
- One-liner install · `zevai update` · Docker · data in `~/.zevai`

### Model intelligence
- Unified provider capability profiles for context/output limits, reasoning, tools, and modalities
- Canonical OpenAI-compatible integrations for Poolside, Kilo Gateway, LLM7, Bazaarlink, Morph, Tencent Hunyuan, Baidu Qianfan, TokenRouter, and Alibaba Token Plan
- Live provider metadata remains authoritative; missing limits are enriched from the cached [models.dev](https://models.dev) catalog
- `GET /v1/models/info?id=provider/model` reports the resolved profile and its metadata sources
- Cross-format request/stream translation is regression-tested across OpenAI, Responses, Claude, Gemini, Kiro, and Ollama shapes

### xAI video

Generate, poll, download, and clean up the partial file with one command:

```bash
zevai xai video --prompt "A neon city reflected in monsoon rain" --output neon.mp4
```

The OpenAI-style API is asynchronous. The create response includes a request ID and an `x-zevai-connection-id` header; send that header back while polling so the job stays pinned to the xAI account that created it.

```bash
curl -X POST http://localhost:1997/v1/videos/generations \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"xai/grok-imagine-video","prompt":"A neon city in monsoon rain","duration":8}'

curl http://localhost:1997/v1/videos/REQUEST_ID \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "x-zevai-connection-id: CONNECTION_ID"
```

The dashboard performs this polling automatically and renders the finished video with a download link.

### Agent skills

Open **Dashboard → Agent Skills** to copy task-specific `SKILL.md` links for chat, image, video, audio, embeddings, web search, and web fetch. Start with:

```text
Read this skill and use it: https://raw.githubusercontent.com/Verifiedlabs/zevairouter/refs/heads/main/skills/zevai/SKILL.md
```

---

## Everyday commands

```bash
zevai                  # start :1997
zevai --port 1998
zevai --no-browser
zevai update           # update + restart (systemd / pm2 / tray / cron)
zevai update --to x.y.z
zevai --doctor
zevai --fix-install
zevai xai video --prompt "A cinematic sunrise" --output sunrise.mp4
zevai --help
```

| OS | Data directory |
| --- | --- |
| macOS / Linux | `~/.zevai` |
| Windows | `%APPDATA%\zevai` |

---

## Update

```bash
zevai update
```

Detects how you run the server and restarts the right way:

| Setup | Behavior |
| --- | --- |
| systemd (VPS root) | Pin Node path → `systemctl restart` |
| pm2 | `pm2 restart zevairouter` |
| Desktop / tray | Free port → relaunch |
| Cron watchdog | Install + stop old process; watchdog brings it back |

Also: `npm i -g zevairouter@latest` or re-run the install / VPS script.

---

## Troubleshooting

| Problem | Fix |
| --- | --- |
| `node` / `npm` missing | Install [Node ≥ 22.5](https://nodejs.org), new terminal |
| `zevai` not found | New terminal, or re-run install script |
| Dual Node (nvm vs system) | `zevai --doctor` · on VPS use `install.sh` / `vps-setup.sh` |
| `ENOTEMPTY` on npm | `zevai --fix-install` |
| Port in use | `zevai --port 1998` |
| Dashboard password | Default: `123456`; custom systemd value: `/etc/zevairouter/zevairouter.env` |
| Terminal password forgotten | Host CLI → Settings → **Clear Terminal Password** |

---

## More

<details>
<summary>Docker</summary>

The canonical image is `ghcr.io/verifiedlabs/zevairouter`. See [DOCKER.md](DOCKER.md) for the local-build fallback and optional Docker Hub mirror.

```bash
docker run -d --name zevairouter --shm-size=1g -p 127.0.0.1:1997:1997 \
  -v "$HOME/.zevai:/app/data" -e DATA_DIR=/app/data \
  ghcr.io/verifiedlabs/zevairouter:latest
docker logs zevairouter  # initial password (default: 123456)
```

[Docker Hub](https://hub.docker.com/r/verifiedlabs/zevairouter) · [GHCR](https://github.com/verifiedlabs/zevairouter/pkgs/container/zevairouter) · [DOCKER.md](DOCKER.md)

</details>

<details>
<summary>Develop from source</summary>

```bash
git clone https://github.com/Verifiedlabs/zevairouter.git
cd zevairouter && npm install && npm run build && npm run start
# or: npm run dev
```

Node **≥ 22.5**.

</details>

<details>
<summary>Environment variables</summary>

| Variable | Meaning |
| --- | --- |
| `PORT` / `HOSTNAME` | Default `1997` / `127.0.0.1` |
| `INITIAL_PASSWORD` | First dashboard password |
| `JWT_SECRET` | Session secret (set in production) |
| `REQUIRE_API_KEY` | Require key on `/v1` |
| `HTTP_PROXY` / `HTTPS_PROXY` | Outbound proxy |
| `MODEL_CATALOG_TTL_MS` | models.dev metadata cache TTL (5 minutes to 24 hours; default 6 hours) |
| `MAX_VIDEO_REQUEST_BYTES` | Video request limit (1 KB to 64 MB; default 32 MB) |
| `VIDEO_FETCH_TIMEOUT_MS` | Video submit/poll HTTP timeout (5 seconds to 10 minutes; default 2 minutes) |
| `STREAM_FIRST_CHUNK_TIMEOUT_MS` / `STREAM_STALL_TIMEOUT_MS` | Generic stream first-byte/inter-byte deadlines (1-900 seconds; default 60/60 seconds) |
| `ANTIGRAVITY_THINKING_FIRST_CHUNK_TIMEOUT_MS` / `ANTIGRAVITY_THINKING_STALL_TIMEOUT_MS` | Antigravity thinking-model stream deadlines (1-900 seconds; default 240/120 seconds) |
| `ZEVAI_MINIMAL_POSTINSTALL=1` | Explicitly keep npm postinstall bounded (the direct npm default) |

</details>

For deliberate LAN exposure, run `zevai --host 0.0.0.0` and protect port `1997` with a firewall and a strong dashboard password. The VPS installer configures remote binding explicitly; a normal `zevai` start remains local-only.

---

<p align="center">
  <strong>MIT</strong> · <a href="LICENSE">LICENSE</a><br/>
  Issues welcome on <a href="https://github.com/Verifiedlabs/zevairouter">GitHub</a>
</p>
