{"owner":"decolua","repo":"9router","hasSkills":true,"hasMcp":false,"mcpConfig":null,"found":["CLAUDE.md"],"skills":{"CLAUDE.md":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## What this is\n\n9Router (`9router-app`) — a local AI routing gateway + Next.js dashboard. It exposes one OpenAI-compatible endpoint (`/v1/*`) and routes traffic across 40+ upstream providers with format translation, model-combo fallback, multi-account fallback, OAuth/API-key credential management, token refresh, quota/usage tracking, and optional cloud sync.\n\nTwo published artifacts live in this one repo:\n- The **dashboard + gateway** (root `package.json`, `9router-app`) — the Next.js server that does the actual routing.\n- The **CLI launcher** (`cli/`, published to npm as `9router`) — a separate package that installs/starts the server and manages the tray. It has its own `package.json`, version, and build.\n\nThe code lives in `src/` (Next.js app + dashboard/compat APIs), `open-sse/` (the provider-agnostic routing/translation engine), `cli/` (the launcher package), and `tests/`.\n\n## Commands\n\nDashboard/gateway (run from repo root):\n```bash\ncp .env.example .env\nnpm install\nPORT=20128 NEXT_PUBLIC_BASE_URL=http://localhost:20128 npm run dev   # dev (webpack, port 20127 by default via next dev)\nnpm run build && PORT=20128 HOSTNAME=0.0.0.0 npm run start           # production\n```\n- Bun variants: `npm run dev:bun` / `build:bun` / `start:bun`.\n- Default runtime port is **20128** (dashboard at `/dashboard`, API at `/v1`).\n- Lint: `npx eslint .` (config `eslint.config.mjs`, extends `eslint-config-next`).\n\nCLI package (`cli/`):\n```bash\nnpm run cli:pack       # build + npm pack from root\ncd cli && npm run dev  # nodemon watch\n```\n\nTests (vitest, in `tests/`, an **independent** ESM package — not wired into root `npm test`):\n```bash\nnpm install                             # ROOT deps first — tests import from src/ which needs `open`, `undici`, etc.\ncd tests && npm install                 # then tests' own deps (vitest) → tests/node_modules (allowed by tests/.gitignore)\nnpx vitest run                          # all tests; auto-discovers tests/vitest.config.js\nnpx vitest run unit/capabilities.test.js   # single file (path relative to tests/)\n```\n> The committed `tests/package.json` `test` script hardcodes Unix paths (`NODE_PATH=/tmp/node_modules …`) — a shared-install workaround from upstream. On Windows (or anywhere), ignore it and use the `npx vitest` form above; `vitest.config.js` resolves the `open-sse`/`@/` aliases from the repo root regardless of where vitest lives.\n>\n> **The suite is NOT expected to be all-green on a plain checkout.** ~938 pass, ~64 fail. Judge regressions with `tests/__baseline__/verify-no-regression.mjs`, not a raw run. Expected red:\n> - 26 catalogued in `tests/__baseline__/known-fails.txt` (rtk, oauth-cursor-auto-import, translator-request-normalization, …).\n> - `unit/embeddings.cloud.test.js` imports `cloud/src/handlers/embeddings.js` — the `cloud/` worker dir is **not in this repo**, so it always fails here.\n> - `unit/xai-oauth-service.test.js` times out (5s) when the xAI endpoint-discovery fetch isn't reachable/mocked.\n> - `real/*.real.test.js` make live provider calls — need credentials, skip otherwise.\n- `*.real.test.js` under `tests/translator/real/` make live provider calls — skip unless credentials are set.\n- Regression baselines: `tests/__baseline__/verify-*.mjs` compare against committed snapshots (providers, aliases, OAuth URLs). Run these after touching provider registry / alias logic.\n\n## Architecture\n\nTwo authoritative docs already exist — read them before working in these areas rather than re-deriving:\n- `docs/ARCHITECTURE.md` — full system: request lifecycle, combo/account fallback, OAuth + token refresh, cloud sync, data model.\n- `open-sse/AGENTS.md` — the routing/translation engine's own conventions and \"how to add a provider/executor/translator\". **Read this before editing anything under `open-sse/`.**\n\n### Request flow (the thing to understand first)\n`src/app/api/v1/*` route (Next rewrite maps `/v1/*` → `/api/v1/*` in `next.config.mjs`)\n→ `src/sse/handlers/chat.js` (parse, combo expansion, account-selection loop)\n→ `open-sse/handlers/chatCore.js` (detect source format, translate request, dispatch to executor, retry/refresh, stream setup)\n→ `open-sse/executors/*` (per-provider upstream call; `default.js` handles any OpenAI-compatible provider)\n→ `open-sse/translator/*` (client format ↔ provider format)\n→ SSE back to client.\n\n`src/sse/` is the app-side entry glue; `open-sse/` is the provider-agnostic engine (also usable standalone). Cross that boundary consciously.\n\n### Translator engine (`open-sse/translator/`)\n- Pivots through **OpenAI as the intermediate format**. A translator registered on an exact `source:target` pair (e.g. `claude:kiro`) runs as a **direct route**, skipping the lossy double-hop. Prefer a direct route for fragile pairs (thinking blocks, tool ids, non-base64 images, `is_error`).\n- Translators **self-register** via `register(from, to, reqFn, resFn)` as an import side effect — a new translator file MUST be imported in `open-sse/translator/index.js` or it never runs.\n- Never hardcode role/block/model strings — use `open-sse/translator/schema/` and `open-sse/config/` constants. Config-driven and DRY is enforced by convention here.\n\n### Provider registry (`open-sse/providers/registry/*`)\n- One file per provider. `providers/registry/index.js` is an **auto-generated** static import list — regenerate it with `scripts/migrate-registry.mjs` / `injectDisplayToRegistry.mjs`, don't hand-edit.\n- Add a provider: copy `providers/REGISTRY_TEMPLATE.js`, add models to `config/providerModels.js`. Only add an executor for non-OpenAI-compatible upstreams.\n\n### Persistence — IMPORTANT (ARCHITECTURE.md is stale here)\nState is **no longer `db.json`**. It's a SQLite layer under `src/lib/db/` with an adapter fallback chain (`driver.js`): `bun:sqlite` → `better-sqlite3` (optional native dep) → `node:sqlite` (Node ≥22.5) → `sql.js` (pure-JS fallback, always works). `better-sqlite3` is deliberately in `optionalDependencies` so install never fails without build tools.\n- `src/lib/localDb.js` is a **backward-compat shim** re-exporting `src/lib/db/index.js`. New code should import from `@/lib/db/index.js`; per-entity logic lives in `src/lib/db/repos/*`. Schema/migrations in `src/lib/db/migrations/`.\n- DB file location resolves via `src/lib/db/paths.js` (`DATA_DIR`, else `~/.9router/`).\n- Usage/logs (`src/lib/usageDb.js`, `usage.json` + `log.txt`) still live under `~/.9router` and do **not** follow `DATA_DIR`.\n\n### RTK token saver (`open-sse/rtk/`)\nPre-translate hooks that compress `tool_result` content in-place to cut tokens. **Fail-open**: any error returns null and leaves the body untouched — never throw out of them. Skips `is_error`/`status:\"error\"` results to preserve traces.\n\n## Conventions & gotchas\n\n- Plain JavaScript (ESM), no TypeScript. `@/*` path alias → `src/*` (`jsconfig.json`).\n- `custom-server.js` wraps the Next standalone server to derive client IP from the TCP socket and strip attacker-controlled `X-Forwarded-For` — trusting forwarding headers only from a loopback reverse proxy. Preserve this when touching request/IP/rate-limit code.\n- Security-sensitive env: `JWT_SECRET` (session cookie), `INITIAL_PASSWORD` (default `123456` — must override), `API_KEY_SECRET`, `MACHINE_ID_SALT`. Full env contract in `.env.example` and ARCHITECTURE.md's env matrix.\n- Binary/protobuf upstreams (kiro EventStream, cursor protobuf, commandcode NDJSON) don't round-trip through OpenAI — they're handled inside their own executor, not the translator.\n- Versioning: root and `cli/` are versioned independently; changes are logged in `CHANGELOG.md`. Commit style is Conventional Commits (`fix(translator): …`, `feat(...)`).\n"},"files":{"CLAUDE.md":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## What this is\n\n9Router (`9router-app`) — a local AI routing gateway + Next.js dashboard. It exposes one OpenAI-compatible endpoint (`/v1/*`) and routes traffic across 40+ upstream providers with format translation, model-combo fallback, multi-account fallback, OAuth/API-key credential management, token refresh, quota/usage tracking, and optional cloud sync.\n\nTwo published artifacts live in this one repo:\n- The **dashboard + gateway** (root `package.json`, `9router-app`) — the Next.js server that does the actual routing.\n- The **CLI launcher** (`cli/`, published to npm as `9router`) — a separate package that installs/starts the server and manages the tray. It has its own `package.json`, version, and build.\n\nThe code lives in `src/` (Next.js app + dashboard/compat APIs), `open-sse/` (the provider-agnostic routing/translation engine), `cli/` (the launcher package), and `tests/`.\n\n## Commands\n\nDashboard/gateway (run from repo root):\n```bash\ncp .env.example .env\nnpm install\nPORT=20128 NEXT_PUBLIC_BASE_URL=http://localhost:20128 npm run dev   # dev (webpack, port 20127 by default via next dev)\nnpm run build && PORT=20128 HOSTNAME=0.0.0.0 npm run start           # production\n```\n- Bun variants: `npm run dev:bun` / `build:bun` / `start:bun`.\n- Default runtime port is **20128** (dashboard at `/dashboard`, API at `/v1`).\n- Lint: `npx eslint .` (config `eslint.config.mjs`, extends `eslint-config-next`).\n\nCLI package (`cli/`):\n```bash\nnpm run cli:pack       # build + npm pack from root\ncd cli && npm run dev  # nodemon watch\n```\n\nTests (vitest, in `tests/`, an **independent** ESM package — not wired into root `npm test`):\n```bash\nnpm install                             # ROOT deps first — tests import from src/ which needs `open`, `undici`, etc.\ncd tests && npm install                 # then tests' own deps (vitest) → tests/node_modules (allowed by tests/.gitignore)\nnpx vitest run                          # all tests; auto-discovers tests/vitest.config.js\nnpx vitest run unit/capabilities.test.js   # single file (path relative to tests/)\n```\n> The committed `tests/package.json` `test` script hardcodes Unix paths (`NODE_PATH=/tmp/node_modules …`) — a shared-install workaround from upstream. On Windows (or anywhere), ignore it and use the `npx vitest` form above; `vitest.config.js` resolves the `open-sse`/`@/` aliases from the repo root regardless of where vitest lives.\n>\n> **The suite is NOT expected to be all-green on a plain checkout.** ~938 pass, ~64 fail. Judge regressions with `tests/__baseline__/verify-no-regression.mjs`, not a raw run. Expected red:\n> - 26 catalogued in `tests/__baseline__/known-fails.txt` (rtk, oauth-cursor-auto-import, translator-request-normalization, …).\n> - `unit/embeddings.cloud.test.js` imports `cloud/src/handlers/embeddings.js` — the `cloud/` worker dir is **not in this repo**, so it always fails here.\n> - `unit/xai-oauth-service.test.js` times out (5s) when the xAI endpoint-discovery fetch isn't reachable/mocked.\n> - `real/*.real.test.js` make live provider calls — need credentials, skip otherwise.\n- `*.real.test.js` under `tests/translator/real/` make live provider calls — skip unless credentials are set.\n- Regression baselines: `tests/__baseline__/verify-*.mjs` compare against committed snapshots (providers, aliases, OAuth URLs). Run these after touching provider registry / alias logic.\n\n## Architecture\n\nTwo authoritative docs already exist — read them before working in these areas rather than re-deriving:\n- `docs/ARCHITECTURE.md` — full system: request lifecycle, combo/account fallback, OAuth + token refresh, cloud sync, data model.\n- `open-sse/AGENTS.md` — the routing/translation engine's own conventions and \"how to add a provider/executor/translator\". **Read this before editing anything under `open-sse/`.**\n\n### Request flow (the thing to understand first)\n`src/app/api/v1/*` route (Next rewrite maps `/v1/*` → `/api/v1/*` in `next.config.mjs`)\n→ `src/sse/handlers/chat.js` (parse, combo expansion, account-selection loop)\n→ `open-sse/handlers/chatCore.js` (detect source format, translate request, dispatch to executor, retry/refresh, stream setup)\n→ `open-sse/executors/*` (per-provider upstream call; `default.js` handles any OpenAI-compatible provider)\n→ `open-sse/translator/*` (client format ↔ provider format)\n→ SSE back to client.\n\n`src/sse/` is the app-side entry glue; `open-sse/` is the provider-agnostic engine (also usable standalone). Cross that boundary consciously.\n\n### Translator engine (`open-sse/translator/`)\n- Pivots through **OpenAI as the intermediate format**. A translator registered on an exact `source:target` pair (e.g. `claude:kiro`) runs as a **direct route**, skipping the lossy double-hop. Prefer a direct route for fragile pairs (thinking blocks, tool ids, non-base64 images, `is_error`).\n- Translators **self-register** via `register(from, to, reqFn, resFn)` as an import side effect — a new translator file MUST be imported in `open-sse/translator/index.js` or it never runs.\n- Never hardcode role/block/model strings — use `open-sse/translator/schema/` and `open-sse/config/` constants. Config-driven and DRY is enforced by convention here.\n\n### Provider registry (`open-sse/providers/registry/*`)\n- One file per provider. `providers/registry/index.js` is an **auto-generated** static import list — regenerate it with `scripts/migrate-registry.mjs` / `injectDisplayToRegistry.mjs`, don't hand-edit.\n- Add a provider: copy `providers/REGISTRY_TEMPLATE.js`, add models to `config/providerModels.js`. Only add an executor for non-OpenAI-compatible upstreams.\n\n### Persistence — IMPORTANT (ARCHITECTURE.md is stale here)\nState is **no longer `db.json`**. It's a SQLite layer under `src/lib/db/` with an adapter fallback chain (`driver.js`): `bun:sqlite` → `better-sqlite3` (optional native dep) → `node:sqlite` (Node ≥22.5) → `sql.js` (pure-JS fallback, always works). `better-sqlite3` is deliberately in `optionalDependencies` so install never fails without build tools.\n- `src/lib/localDb.js` is a **backward-compat shim** re-exporting `src/lib/db/index.js`. New code should import from `@/lib/db/index.js`; per-entity logic lives in `src/lib/db/repos/*`. Schema/migrations in `src/lib/db/migrations/`.\n- DB file location resolves via `src/lib/db/paths.js` (`DATA_DIR`, else `~/.9router/`).\n- Usage/logs (`src/lib/usageDb.js`, `usage.json` + `log.txt`) still live under `~/.9router` and do **not** follow `DATA_DIR`.\n\n### RTK token saver (`open-sse/rtk/`)\nPre-translate hooks that compress `tool_result` content in-place to cut tokens. **Fail-open**: any error returns null and leaves the body untouched — never throw out of them. Skips `is_error`/`status:\"error\"` results to preserve traces.\n\n## Conventions & gotchas\n\n- Plain JavaScript (ESM), no TypeScript. `@/*` path alias → `src/*` (`jsconfig.json`).\n- `custom-server.js` wraps the Next standalone server to derive client IP from the TCP socket and strip attacker-controlled `X-Forwarded-For` — trusting forwarding headers only from a loopback reverse proxy. Preserve this when touching request/IP/rate-limit code.\n- Security-sensitive env: `JWT_SECRET` (session cookie), `INITIAL_PASSWORD` (default `123456` — must override), `API_KEY_SECRET`, `MACHINE_ID_SALT`. Full env contract in `.env.example` and ARCHITECTURE.md's env matrix.\n- Binary/protobuf upstreams (kiro EventStream, cursor protobuf, commandcode NDJSON) don't round-trip through OpenAI — they're handled inside their own executor, not the translator.\n- Versioning: root and `cli/` are versioned independently; changes are logged in `CHANGELOG.md`. Commit style is Conventional Commits (`fix(translator): …`, `feat(...)`).\n"},"items":[{"name":"CLAUDE.md","path":"CLAUDE.md","title":"CLAUDE.md","content":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## What this is\n\n9Router (`9router-app`) — a local AI routing gateway + Next.js dashboard. It exposes one OpenAI-compatible endpoint (`/v1/*`) and routes traffic across 40+ upstream providers with format translation, model-combo fallback, multi-account fallback, OAuth/API-key credential management, token refresh, quota/usage tracking, and optional cloud sync.\n\nTwo published artifacts live in this one repo:\n- The **dashboard + gateway** (root `package.json`, `9router-app`) — the Next.js server that does the actual routing.\n- The **CLI launcher** (`cli/`, published to npm as `9router`) — a separate package that installs/starts the server and manages the tray. It has its own `package.json`, version, and build.\n\nThe code lives in `src/` (Next.js app + dashboard/compat APIs), `open-sse/` (the provider-agnostic routing/translation engine), `cli/` (the launcher package), and `tests/`.\n\n## Commands\n\nDashboard/gateway (run from repo root):\n```bash\ncp .env.example .env\nnpm install\nPORT=20128 NEXT_PUBLIC_BASE_URL=http://localhost:20128 npm run dev   # dev (webpack, port 20127 by default via next dev)\nnpm run build && PORT=20128 HOSTNAME=0.0.0.0 npm run start           # production\n```\n- Bun variants: `npm run dev:bun` / `build:bun` / `start:bun`.\n- Default runtime port is **20128** (dashboard at `/dashboard`, API at `/v1`).\n- Lint: `npx eslint .` (config `eslint.config.mjs`, extends `eslint-config-next`).\n\nCLI package (`cli/`):\n```bash\nnpm run cli:pack       # build + npm pack from root\ncd cli && npm run dev  # nodemon watch\n```\n\nTests (vitest, in `tests/`, an **independent** ESM package — not wired into root `npm test`):\n```bash\nnpm install                             # ROOT deps first — tests import from src/ which needs `open`, `undici`, etc.\ncd tests && npm install                 # then tests' own deps (vitest) → tests/node_modules (allowed by tests/.gitignore)\nnpx vitest run                          # all tests; auto-discovers tests/vitest.config.js\nnpx vitest run unit/capabilities.test.js   # single file (path relative to tests/)\n```\n> The committed `tests/package.json` `test` script hardcodes Unix paths (`NODE_PATH=/tmp/node_modules …`) — a shared-install workaround from upstream. On Windows (or anywhere), ignore it and use the `npx vitest` form above; `vitest.config.js` resolves the `open-sse`/`@/` aliases from the repo root regardless of where vitest lives.\n>\n> **The suite is NOT expected to be all-green on a plain checkout.** ~938 pass, ~64 fail. Judge regressions with `tests/__baseline__/verify-no-regression.mjs`, not a raw run. Expected red:\n> - 26 catalogued in `tests/__baseline__/known-fails.txt` (rtk, oauth-cursor-auto-import, translator-request-normalization, …).\n> - `unit/embeddings.cloud.test.js` imports `cloud/src/handlers/embeddings.js` — the `cloud/` worker dir is **not in this repo**, so it always fails here.\n> - `unit/xai-oauth-service.test.js` times out (5s) when the xAI endpoint-discovery fetch isn't reachable/mocked.\n> - `real/*.real.test.js` make live provider calls — need credentials, skip otherwise.\n- `*.real.test.js` under `tests/translator/real/` make live provider calls — skip unless credentials are set.\n- Regression baselines: `tests/__baseline__/verify-*.mjs` compare against committed snapshots (providers, aliases, OAuth URLs). Run these after touching provider registry / alias logic.\n\n## Architecture\n\nTwo authoritative docs already exist — read them before working in these areas rather than re-deriving:\n- `docs/ARCHITECTURE.md` — full system: request lifecycle, combo/account fallback, OAuth + token refresh, cloud sync, data model.\n- `open-sse/AGENTS.md` — the routing/translation engine's own conventions and \"how to add a provider/executor/translator\". **Read this before editing anything under `open-sse/`.**\n\n### Request flow (the thing to understand first)\n`src/app/api/v1/*` route (Next rewrite maps `/v1/*` → `/api/v1/*` in `next.config.mjs`)\n→ `src/sse/handlers/chat.js` (parse, combo expansion, account-selection loop)\n→ `open-sse/handlers/chatCore.js` (detect source format, translate request, dispatch to executor, retry/refresh, stream setup)\n→ `open-sse/executors/*` (per-provider upstream call; `default.js` handles any OpenAI-compatible provider)\n→ `open-sse/translator/*` (client format ↔ provider format)\n→ SSE back to client.\n\n`src/sse/` is the app-side entry glue; `open-sse/` is the provider-agnostic engine (also usable standalone). Cross that boundary consciously.\n\n### Translator engine (`open-sse/translator/`)\n- Pivots through **OpenAI as the intermediate format**. A translator registered on an exact `source:target` pair (e.g. `claude:kiro`) runs as a **direct route**, skipping the lossy double-hop. Prefer a direct route for fragile pairs (thinking blocks, tool ids, non-base64 images, `is_error`).\n- Translators **self-register** via `register(from, to, reqFn, resFn)` as an import side effect — a new translator file MUST be imported in `open-sse/translator/index.js` or it never runs.\n- Never hardcode role/block/model strings — use `open-sse/translator/schema/` and `open-sse/config/` constants. Config-driven and DRY is enforced by convention here.\n\n### Provider registry (`open-sse/providers/registry/*`)\n- One file per provider. `providers/registry/index.js` is an **auto-generated** static import list — regenerate it with `scripts/migrate-registry.mjs` / `injectDisplayToRegistry.mjs`, don't hand-edit.\n- Add a provider: copy `providers/REGISTRY_TEMPLATE.js`, add models to `config/providerModels.js`. Only add an executor for non-OpenAI-compatible upstreams.\n\n### Persistence — IMPORTANT (ARCHITECTURE.md is stale here)\nState is **no longer `db.json`**. It's a SQLite layer under `src/lib/db/` with an adapter fallback chain (`driver.js`): `bun:sqlite` → `better-sqlite3` (optional native dep) → `node:sqlite` (Node ≥22.5) → `sql.js` (pure-JS fallback, always works). `better-sqlite3` is deliberately in `optionalDependencies` so install never fails without build tools.\n- `src/lib/localDb.js` is a **backward-compat shim** re-exporting `src/lib/db/index.js`. New code should import from `@/lib/db/index.js`; per-entity logic lives in `src/lib/db/repos/*`. Schema/migrations in `src/lib/db/migrations/`.\n- DB file location resolves via `src/lib/db/paths.js` (`DATA_DIR`, else `~/.9router/`).\n- Usage/logs (`src/lib/usageDb.js`, `usage.json` + `log.txt`) still live under `~/.9router` and do **not** follow `DATA_DIR`.\n\n### RTK token saver (`open-sse/rtk/`)\nPre-translate hooks that compress `tool_result` content in-place to cut tokens. **Fail-open**: any error returns null and leaves the body untouched — never throw out of them. Skips `is_error`/`status:\"error\"` results to preserve traces.\n\n## Conventions & gotchas\n\n- Plain JavaScript (ESM), no TypeScript. `@/*` path alias → `src/*` (`jsconfig.json`).\n- `custom-server.js` wraps the Next standalone server to derive client IP from the TCP socket and strip attacker-controlled `X-Forwarded-For` — trusting forwarding headers only from a loopback reverse proxy. Preserve this when touching request/IP/rate-limit code.\n- Security-sensitive env: `JWT_SECRET` (session cookie), `INITIAL_PASSWORD` (default `123456` — must override), `API_KEY_SECRET`, `MACHINE_ID_SALT`. Full env contract in `.env.example` and ARCHITECTURE.md's env matrix.\n- Binary/protobuf upstreams (kiro EventStream, cursor protobuf, commandcode NDJSON) don't round-trip through OpenAI — they're handled inside their own executor, not the translator.\n- Versioning: root and `cli/` are versioned independently; changes are logged in `CHANGELOG.md`. Commit style is Conventional Commits (`fix(translator): …`, `feat(...)`).\n","category":"root","tokens":1948}]}