spb/zyquo-router Public MIT
One local endpoint, every AI provider — a private OpenAI-compatible LLM gateway for your Mac (170 models, 12 providers).
Swift 95.7%
Python 2.3%
Shell 1.2%
Makefile 0.9%
1# CLAUDE.md — Zyquo Router23## Project Identity45**Zyquo Router** is the **gateway** member of the **Zyquo** family: a legendary, native macOS app written in **Swift + SwiftUI**, built **without the Xcode IDE** (Swift Package Manager + command-line toolchain). It turns the user's Mac into a **local LLM gateway**: the user stores their provider API keys once (same encrypted vault as the rest of the family — NO Keychain), picks a **port**, hits Start, and Zyquo Router serves a **standardized, OpenAI-compatible HTTP API** on `http://localhost:<port>` that fronts **every model of every provider from Zyquo Cloud** — one unified endpoint, one request format, **streaming and non-streaming**, for all of them.67Any tool that speaks the OpenAI API (SDKs, CLIs, IDE plugins, scripts, other apps) can point at `http://localhost:<port>/v1` and instantly use Anthropic, OpenAI, xAI, Mistral, Gemini, Qwen/DashScope, DeepSeek, Kimi, Perplexity, Together, DeepInfra, and Cerebras through a single normalized interface — with per-request model routing, live traffic logs, usage/cost tracking, and access control. Think "OpenRouter / LiteLLM, but native, local, private, and gorgeous."89**Naming conventions (use consistently everywhere):**10- Display name / product name: `Zyquo Router`11- App bundle: `Zyquo Router.app`12- Bundle identifier: `com.zyquo.router`13- Executable / SPM target: `ZyquoRouter` (no space)14- Data folder: `~/Library/Application Support/ZyquoRouter/`15- Repo module prefix in file headers: `Zyquo Router`1617---1819## 📋 MANDATORY FILE HEADER — EVERY CODE FILE2021**Every single code file you write** (all `.swift` files, plus `Makefile`, shell scripts, `Package.swift`, verification scripts — anything containing code) **MUST begin with this header comment**, adapted to the file's comment syntax:2223```swift24//25// <FileName>.swift26// Zyquo Router27//28// Author: Simon-Pierre Boucher29// Mail: contact@spboucher.ai30//31```3233For shell scripts / Makefiles:3435```bash36#37# <filename>38# Zyquo Router39#40# Author: Simon-Pierre Boucher41# Mail: contact@spboucher.ai42#43```4445No exceptions. If you ever create or refactor a file and the header is missing, add it. Before declaring the project done, run a sweep over the repository to verify every code file carries the header.4647---4849## 🧭 METHODOLOGY — WORK METHODICALLY, KEEP EVERYTHING COHERENT5051You must execute this project **strictly in phase order (0 → 8)**. Do not jump ahead, do not interleave phases, do not build the dashboard UI before the HTTP server routes a real request end-to-end, and do not write any code before Phase 0 research + Zyquo Cloud study are complete.5253**Working rules:**54551. **One phase at a time.** At the start of each phase, write a checklist into `docs/PLAN.md`; check items off as you go. At the end of each phase, run a **phase checkpoint**: build (`swift build`), run what's runnable, fix all warnings/errors, write a 3–5 line phase summary in `docs/PLAN.md` before moving on.562. **Phase gates:** Phase 0 is complete only when `docs/ROUTER-RESEARCH.md` and `docs/PROVIDER-REUSE.md` are complete. Phase 2 is complete only when the embedded HTTP server answers `GET /v1/models` on the chosen port. Phase 3 is complete only when `POST /v1/chat/completions` works end-to-end — streaming AND non-streaming — against at least one model of at least three structurally different providers (one OpenAI-compatible, Anthropic, Gemini), verified with `curl` and the official OpenAI SDK. Phase 4 spec is the contract for all UI in Phase 6. Phase 7 is complete only when the full compatibility matrix is green with real keys. Phase 8 is complete only when `spctl` says "Notarized Developer ID".573. **Single source of truth, everywhere:**58 - Provider/model behavior → ported from Zyquo Cloud's client layer (Phase 0.B); never re-invent upstream request formats. **All Zyquo Cloud models are exposed by the router.**59 - The public API surface → defined once in `docs/API.md` (Phase 3) and implemented exactly; the served behavior, the docs, and the in-app API reference must never drift apart.60 - Colors, fonts, spacing, radii → only from `ZyquoTheme` design tokens. Zero raw hex or magic numbers in views.61 - HTTP serving, translation, and routing → only in the `Server/`, `Translate/`, `Router/` layers; never leak into Views/ViewModels.62 - Product naming → per the conventions above. Never `Zyquo` alone, never `ZyquoRouter` in user-facing text.634. **Coherence sweeps:** after Phases 3, 6, and 8 (uniform naming — always `RouteRequest`, `UpstreamCall`, `ProviderClient`, `APIKeyRecord`; no dead code; headers present; folders match Phase 2).645. **Compile early, compile often.** Never accumulate more than one file of unbuilt changes.656. **Commit discipline:** one logical unit per commit, phase-prefixed. Never commit secrets or logged request bodies.667. **Security is a design constraint from the first line of server code:** the server binds to `127.0.0.1` (localhost) by default; exposing on the LAN (`0.0.0.0`) is an explicit opt-in with a mandatory local access token; provider keys are NEVER returned by any endpoint, never logged, and requests/responses in logs are redacted by default.6768---6970## ⚠️ PHASE 0 — MANDATORY RESEARCH + ZYQUO CLOUD STUDY (DO THIS FIRST, BEFORE ANY CODE)7172Two mandatory research tracks, each producing a document. No Swift until both are done.7374### 0.A — `docs/ROUTER-RESEARCH.md` — how to build an LLM gateway/router, correctly (INTENSIVE WEB RESEARCH)7576Do NOT rely on training data. Perform **several intensive web research sessions** to nail every detail of building a production-quality LLM router. Research and document at minimum:77781. **The OpenAI API specification — the standard you are implementing.** Document the CURRENT `POST /v1/chat/completions` contract exhaustively: request schema (`model`, `messages` with roles and multimodal content parts incl. `image_url`/base64, `temperature`, `top_p`, `max_tokens`/`max_completion_tokens`, `stop`, `n`, `frequency_penalty`, `presence_penalty`, `seed`, `response_format` / JSON mode, `tools` + `tool_choice` function calling, `stream`, `stream_options` incl. `include_usage`, `user`), the non-streaming response schema (`id`, `object: "chat.completion"`, `created`, `model`, `choices[].message`, `finish_reason` values, `usage` with prompt/completion/total tokens), and the **SSE streaming format** exactly (`data: {chat.completion.chunk}` events, delta structure for content AND for tool_calls arguments, role delta on first chunk, `finish_reason` on last content chunk, optional final usage chunk, terminating `data: [DONE]`). Also document `GET /v1/models` (list) and the **OpenAI error response format** (`{"error": {"message", "type", "param", "code"}}`) with correct HTTP status codes (400/401/403/404/429/500/502/503). Check whether the newer OpenAI `/v1/responses` API is worth also exposing; decide and document (chat/completions is mandatory; responses optional).792. **How existing gateways do it — study the references:** **LiteLLM** (proxy config, model naming `provider/model`, param translation tables, error mapping), **OpenRouter** (unified model IDs `vendor/model`, extra headers, streaming normalization, usage/cost accounting), and local servers that expose OpenAI-compatible endpoints (**Ollama**'s OpenAI compatibility layer, **LM Studio** server, **vLLM**). Extract concrete patterns: model namespacing, how they handle provider-specific params (pass-through via extra body), how they normalize finish reasons and usage, how they surface upstream errors, retry/fallback policies, and what compatibility pitfalls trip up real clients (SDKs are strict about chunk shapes).803. **Translation matrices — the hard core.** For each NON-OpenAI-shaped upstream, document the exact bidirectional mapping:81 - **Anthropic Messages API:** system prompt extraction (top-level `system` vs. messages), content blocks, `max_tokens` required, tool_use/tool_result ↔ OpenAI `tool_calls`/`tool` role messages, image blocks, stop_reason ↔ finish_reason mapping, and the **SSE event model** (`message_start`, `content_block_start/delta/stop`, `message_delta`, `message_stop`, `input_json_delta` for streamed tool arguments) → how each maps onto OpenAI chunk deltas.82 - **Gemini `generateContent` / `streamGenerateContent`:** `contents`/`parts`, `systemInstruction`, `generationConfig` param names, function calling ↔ tools, inline image data, candidate/finishReason mapping, and its streaming format → OpenAI chunks.83 - Confirm which of the remaining providers (xAI, Mistral, DashScope/Qwen, DeepSeek, Kimi, Perplexity, Together, DeepInfra, Cerebras) are OpenAI-compatible enough for near-pass-through, and document every known deviation per provider (unsupported params to strip, extra params to allow, usage/finish quirks, Perplexity citations, DeepSeek/Qwen reasoning content fields — decide how reasoning is surfaced, e.g., a `reasoning_content` field preserved in the normalized response).844. **HTTP server in Swift without heavyweight deps:** evaluate and choose the serving approach — **SwiftNIO** directly (preferred: Apple-maintained, SPM-clean, full control over SSE/chunked responses, graceful shutdown, backpressure) vs. `Network.framework` listener vs. embedding a small framework (Hummingbird/Vapor — heavier). Document the choice with rationale, plus: binding localhost vs. 0.0.0.0, port-in-use detection, concurrent connection handling with structured concurrency, request body size limits, timeouts (long ones for streaming), keep-alive, CORS headers (so browser-based tools can call the router), and clean cancel when a client disconnects mid-stream (must cancel the upstream call too).855. **Gateway concerns:** request logging with redaction, token usage extraction per provider, **cost calculation** from per-model pricing, rate limiting per API key, retries with exponential backoff on upstream 429/5xx, optional **fallback chains** (if model A fails → try model B), and health checks.8687### 0.B — `docs/PROVIDER-REUSE.md` — study the Zyquo Cloud repo and reuse its providers8889**Before writing provider code, read and study the Zyquo Cloud repository** (sibling project). Locate it on disk (check the user's projects folder; if not found, ask the user for its path). Document and reuse:90911. **Exactly how each provider's API is called** in Zyquo Cloud: base URLs, auth headers, request/response `Codable` models, the shared `OpenAICompatibleClient`, native `AnthropicClient`/`GeminiClient`, SSE parsing. The router calls upstreams the **exact same way** — port or factor this code to be identical to Cloud's.922. **The complete model catalog** (`ModelCatalog` / `docs/PROVIDERS.md`) with capabilities (vision, tools, reasoning, context, max output) and pricing. **The router exposes ALL of these models** via `GET /v1/models`, using stable namespaced IDs: **`provider/model-id`** (e.g., `anthropic/claude-...`, `openai/gpt-...`, `deepseek/deepseek-chat`), while also accepting the bare upstream model ID when it is unambiguous. Aliases (user-defined friendly names, e.g., `fast` → a chosen model) supported.933. **The secure key vault** from Cloud (custom AES-256-GCM, **NO Keychain**): reuse the same `SecureKeyStore` design and vault format so users manage keys identically across the family.944. Provider streaming quirks so the translation layer normalizes them all into byte-perfect OpenAI chunks.9596---9798## PHASE 1 — Project Setup (No Xcode IDE)99100- **Toolchain:** SPM. `Package.swift`, executable target `ZyquoRouter`. Dependencies: Foundation + SwiftUI + CryptoKit + **SwiftNIO** (or the serving choice justified in Phase 0.A.4) + optionally Apple `swift-markdown` for the in-app docs. Nothing else.101- **App bundle:** `Makefile` builds release, assembles `Zyquo Router.app`, signs (Phase 8; ad-hoc for `make dev`).102- **Info.plist:** `CFBundleDisplayName` = `Zyquo Router`, bundle ID `com.zyquo.router`, `LSMinimumSystemVersion` (macOS 13.0+), `NSHighResolutionCapable`, `LSApplicationCategoryType` (`public.app-category.developer-tools`). Universal (arm64 + x86_64) for release. Optional `LSUIElement`-style behavior later via a "run in menu bar only" setting (still a regular app by default).103- **Entry point:** `@main` SwiftUI `App`; proper activation from terminal; the server lifecycle is owned by the app (start/stop survives window close if enabled).104105---106107## PHASE 2 — Architecture + Server Skeleton108109```110Sources/ZyquoRouter/111├── App/ # @main, windows, menu bar extra, server lifecycle112├── DesignSystem/ # ZyquoTheme — family tokens, Router palette113├── Models/ # AIModel, ProviderConfig, RouteRule, Alias, RequestLogEntry, UsageRecord, LocalAPIKey…114├── Server/115│ ├── HTTPServer.swift # NIO bootstrap, bind host/port, lifecycle, graceful shutdown116│ ├── Routes.swift # /v1/chat/completions, /v1/models, /health, (optional /v1/embeddings, /v1/responses)117│ ├── SSEWriter.swift # spec-exact chunked SSE emission, [DONE], client-disconnect handling118│ ├── AuthMiddleware.swift # optional local API keys (Bearer), per-key limits119│ └── CORS.swift120├── Router/121│ ├── RequestRouter.swift # model-id → provider resolution, aliases, fallback chains122│ ├── RetryPolicy.swift # backoff on 429/5xx, timeout handling123│ └── UsageMeter.swift # tokens + cost per request/key/model/provider124├── Translate/125│ ├── OpenAINormalizer.swift # canonical internal request/response ⇄ OpenAI wire format126│ ├── AnthropicTranslator.swift127│ ├── GeminiTranslator.swift128│ └── CompatAdjuster.swift # per-provider param strip/translate table for OpenAI-compatible upstreams129├── Providers/ # PORTED FROM ZYQUO CLOUD (all models)130│ ├── ProviderProtocol.swift131│ ├── OpenAICompatibleClient.swift132│ ├── AnthropicClient.swift133│ └── GeminiClient.swift134├── Services/135│ ├── SecureKeyStore.swift # reused from Zyquo Cloud (no Keychain)136│ ├── RequestLogStore.swift # ring-buffer + persisted log, redaction137│ └── PersistenceService.swift138├── ViewModels/139└── Views/140```141142- **Structured concurrency end-to-end:** each inbound request is a task; upstream streaming is an `AsyncSequence` piped into `SSEWriter`; client disconnect cancels the upstream task immediately.143- **PHASE GATE:** server starts on a user-chosen port, `GET /health` returns ok, `GET /v1/models` returns the full namespaced catalog as spec-shaped JSON, port-in-use is detected with a clean error, and Stop shuts down gracefully (in-flight requests drain or cancel cleanly).144145---146147## PHASE 3 — THE STANDARDIZED API (THE CORE)148149Implement the unified endpoint per `docs/ROUTER-RESEARCH.md`, and write **`docs/API.md`** — the exact public contract — as you build. **PHASE GATE:** streaming + non-streaming chat completions verified with `curl` AND the official OpenAI Python/JS SDK pointed at `http://localhost:<port>/v1`, against one OpenAI-compatible provider, Anthropic, and Gemini.150151### 3.A — `POST /v1/chat/completions` (mandatory, spec-exact)152- Accept the full OpenAI request schema (0.A.1) including multimodal image content and `tools`/`tool_choice`. Resolve `model` (namespaced `provider/model`, unambiguous bare ID, or alias) via `RequestRouter`; return the proper OpenAI 404-style error for unknown models.153- **Non-streaming:** call the upstream via the ported clients, translate the response into a spec-exact `chat.completion` object — correct `choices`, mapped `finish_reason`, real `usage` (from upstream when provided; estimated and flagged otherwise), and the namespaced model echoed back.154- **Streaming:** translate every upstream's stream (OpenAI-style SSE, Anthropic event blocks, Gemini chunks) into **byte-perfect OpenAI `chat.completion.chunk` SSE** — role delta first, content deltas, streamed `tool_calls` argument deltas, `finish_reason` chunk, optional usage chunk when `stream_options.include_usage` is set, then `data: [DONE]`. Strict SDKs must parse it without complaint.155- **Tool calling** and **JSON mode / response_format** translated per provider where supported; cleanly rejected with a helpful OpenAI-format error where the upstream can't do it.156- **Reasoning models:** preserve reasoning output in a consistent field (per the Phase 0 decision, e.g., `reasoning_content` on the message/delta) so DeepSeek-R1-style models work through the router.157- Provider-specific extras (e.g., Perplexity search options) accepted via pass-through extra body keys, documented in `docs/API.md`.158- **Error mapping:** upstream failures become OpenAI-format errors with correct status codes (401 upstream auth → 401 with clear "provider key invalid" message; 429 → 429 with retry-after if given; timeouts → 504-style; unknown → 502) — never leak raw provider payload shapes or any key material.159- **Retries & fallbacks:** RetryPolicy on transient upstream errors; optional user-configured fallback chains (model list tried in order), surfaced honestly in the response `model` field.160161### 3.B — Supporting endpoints162- `GET /v1/models` — full catalog with namespaced IDs (+ aliases), OpenAI list shape; optionally enriched metadata (context, pricing) under an `x-zyquo` extension key.163- `GET /health` — status, uptime, version.164- **Optional (nice, after the mandatory works):** `POST /v1/embeddings` routed to providers offering embeddings; `POST /v1/responses` if Phase 0 deemed it worthwhile.165166### 3.C — Access control & network posture167- Default bind `127.0.0.1`; **LAN exposure (`0.0.0.0`) is explicit opt-in** and forces at least one **local API key**.168- Local API keys: user-generated bearer tokens (`zyquo-sk-…`) with per-key enable/disable, optional per-key rate limits and model allow-lists; keys hashed at rest; shown once at creation.169- CORS configurable (default permissive for localhost tooling).170- Provider keys live only in the reused encrypted vault; no endpoint ever returns them.171172---173174## PHASE 4 — DESIGN SYSTEM & UI (LIGHT THEME, PIXEL-PERFECT, "CONTROL ROOM" IDENTITY)175176Same design DNA and `ZyquoTheme` token system as the family, with a **"Router / signal" identity**: a **graphite-cyan** story — switching, signal, uptime. The app is a **dashboard/control room**, not a chat app.177178### 4.1 — Light theme179180| Token | Value (light) | Usage |181|---|---|---|182| `background` | `#FAFBFC` (crisp cool off-white) | Canvas |183| `surface` | `#FFFFFF` | Cards, panels |184| `surfaceSecondary` | `#F1F4F6` | Hover, log rows, code blocks |185| `accent` | `#0891B2` (signal cyan) paired with graphite `#374151` | Start button, active states, links, charts |186| `accentSubtle` | `#E5F5F9` | Selected rows, active-server tint |187| `textPrimary` `#191C1F` · `textSecondary` `#697077` · `textTertiary` `#9BA3AA` · `border` `#E4E8EB` | | |188| `success`/`warning`/`danger` | `#2FA36B`/`#D9822B`/`#D64545` | Server running / degraded / stopped & errors |189| chart tokens | cyan (requests), graphite (latency), per-provider hues | Dashboard charts |190191Family rules apply (no pure black on white, 0.5pt hairlines, ultra-soft shadows on floating panels only, dark theme derived — a deep graphite "ops room" feel; **light theme is flagship**). Typography/spacing/radii identical to the family; **SF Mono everywhere data lives**: endpoints, model IDs, logs, JSON, keys.192193### 4.2 — Layout & screens (exact spec)194195Left navigator (240pt, translucent) + detail. Default 1280×820, min 1020×660. Sections: **Dashboard**, **Models**, **Requests**, **Keys** (Provider Keys + Local API Keys), **Playground**, **Docs**, plus footer settings gear and a permanent **server status pill** (● Running on :8787 / ○ Stopped) with a Start/Stop control — visible from every screen.196197- **Dashboard:** the hero. A large **server card**: status, big Start/Stop button (accent), editable **port field**, bind selector (Localhost only / LAN with warning), the endpoint URL `http://localhost:8787/v1` with a one-click **Copy** and copy-as `curl` / OpenAI-Python / OpenAI-JS snippets. Below: live tiles — requests/min sparkline, tokens in/out today, **estimated cost today**, error rate, active streams count, uptime; a per-provider breakdown bar. Everything updates live and smoothly.198- **Models:** the full namespaced catalog (search, filter by provider/capability): each row = `provider/model-id` (mono, one-click copy), capability badges (vision/tools/reasoning), context, pricing, enable/disable toggle (disabled models 404 through the API), favorite. **Aliases** editor (e.g., `fast` → `cerebras/...`; `best` → `anthropic/...`). **Fallback chains** editor (ordered model lists with drag-reorder).199- **Requests (live traffic):** a streaming log table — time, method/path, model, provider, status chip, latency, tokens in/out, cost, stream badge; click a row → detail pane with redacted request/response JSON (pretty-printed, mono), timing waterfall (queue → upstream TTFB → stream duration), and error detail when failed. Filters (provider, model, status, key), pause/clear, and a **redaction toggle** (bodies hidden by default; revealing requires an explicit per-session switch).200- **Keys:** two tabs. *Provider Keys* — identical UX to Zyquo Cloud (masked fields, per-provider Test button with status dot + latency, reused encrypted vault). *Local API Keys* — create/name/revoke `zyquo-sk-…` tokens (shown once, copy button), per-key rate limit and model allow-list, per-key usage mini-chart.201- **Playground:** a built-in tester that calls **the router's own local endpoint** (not the upstreams directly — it must eat its own dogfood): model picker (namespaced IDs), message composer, params, streaming toggle; response pane shows streamed output AND the raw request/response JSON side-by-side with copy-as-code.202- **Docs:** a beautiful in-app rendering of `docs/API.md` — endpoint reference, schemas, streaming format, error codes, and ready-to-paste snippets (curl, Python `OpenAI(base_url=...)`, JS, LangChain) each with copy buttons. Generated from the same source of truth as the implementation.203- **Empty/first-run state:** a stunning onboarding card — "Add a provider key → pick a port → Start" three-step, with the endpoint revealed on start. Must look App-Store-feature quality.204205**Settings** (native tabs, 720×540): 1. **Server** (default port, bind, auto-start server at app launch, launch app at login, keep serving when window closed, request body size limit, timeouts) 2. **Logging** (retention, redaction default, export logs) 3. **Usage & Pricing** (pricing table view/override, cost currency, reset counters) 4. **Appearance** (Light/Dark/System; accent: cyan default + graphite, sky, emerald, violet, copper; font size) 5. **Shortcuts** 6. **Advanced** (reveal data folder, export/import config incl. aliases/chains — never keys in plaintext).206207### 4.3 — Motion & 4.4 quality gate208Family motion standard; router-specific: the live request table streams new rows without jank, sparkline/tiles update smoothly (≤4Hz), the status pill transitions cleanly (stopped→starting→running), Start/Stop feels instant. **Quality gate:** review every state — stopped, starting, running, port conflict, LAN warning, no provider keys yet, request streaming live, upstream failing (429/500), key revoked, logs empty/full, redaction on/off. Consistent tokens, aligned mono columns, no clipped IDs. If it looks "developer-made", iterate.209210### Menu bar extra (first-class here)211A toggleable menu bar item: status dot + port, Start/Stop, requests/min, today's cost, and "Copy endpoint URL". The router is exactly the kind of app that lives in the menu bar; make this excellent.212213---214215## PHASE 5 — APP ICON: ULTRA-LEGENDARY "ROUTER" ICON, DESIGNED IN SVG216217Designed in SVG first (`assets/icon/zyquo-router.svg`) → `.icns`. Sibling of the family, in the established style: **Big Sur squircle with a clean near-white soft-3D background (#FCFCFD, faint inner-edge shadow), gentle top lighting, soft realistic drop shadows; the dominant hero is the very bold, thick, chunky geometric charcoal-black "Z" (#0B0B14 → #1C1C2A gradient, subtle top-face sheen, fully opaque, ~50% of the icon) whose bottom stroke is a vivid electric-blue-to-indigo parallelogram accent (#2B6BFF → #4C2BE0)** — identical Z treatment to the other Zyquo icons.218219**Signature motif — the Z that routes.** Two directions (render both, keep the best):2201. *Z-hub:* from the Z's right side, **three thin gradient connection lines fan out** to three small soft-3D node endpoints (tiny rounded squares or spheres, cyan-to-indigo gradients) at the icon's right edge — one Z in, many providers out; a small glowing cyan "port" dot sits at the line origin. Lines kept thin and secondary.2212. *Z-switch:* the Z centered over a subtle backdrop of **crossing signal paths** (two thin curved lines swapping positions, like a rail switch/interchange, light cyan-gray, low contrast) with small direction chevrons — routing made visual.222- Accent color leaning **cyan** (#22B8D4 blended into the family blue-indigo) in the connection lines/nodes so Router is distinguishable beside Cloud (cloud), Local (server stack), Agent (robot), Atlas (globe), MLX (forge) while remaining unmistakably the same set.223- **Precision & iteration:** clean paths, `viewBox="0 0 1024 1024"`, optical centering; render 16→1024, inspect, refine; simplified small-size variant (drop lines/nodes, keep the black Z with blue base) for 16/32px.224225**Pipeline (Makefile):** SVG → PNGs (16→1024 incl. `@2x`) via `rsvg-convert` or CoreGraphics rasterizer → `AppIcon.iconset` → `iconutil -c icns`. SVG is the source of truth. Derive the monochrome **menu bar template icon** (18×18pt — critical for this app; a mini Z-with-signal-dot glyph) and in-app wordmark from the same SVG.226227---228229## PHASE 6 — Features (This is where Zyquo Router becomes LEGENDARY)230231### The gateway232- One OpenAI-compatible endpoint on a user-chosen port fronting **every provider/model from Zyquo Cloud**, streaming and non-streaming, spec-exact233- Namespaced model IDs + user aliases + fallback chains; enable/disable models; multimodal (images) and tool calling translated per provider; reasoning content preserved234- Retries with backoff; honest error mapping in OpenAI format; per-request cancellation propagated upstream on client disconnect235236### Control & security237- Localhost by default; LAN opt-in gated behind mandatory local API keys (`zyquo-sk-…`, hashed at rest, per-key limits and model allow-lists)238- Provider keys in the reused encrypted vault (NO Keychain), never logged, never served239- Redacted-by-default request logging with explicit reveal; export logs240241### Observability242- Live dashboard: req/min, tokens, **cost estimates** from the catalog's pricing, error rate, latency, per-provider/per-model/per-key breakdowns, uptime243- Full request inspector with timing waterfall and pretty JSON244- Usage history persisted; daily/weekly summaries245246### Developer experience247- One-click copy: endpoint URL, curl, OpenAI Python/JS snippets pre-filled with the port and a local key248- In-app **Docs** rendered from `docs/API.md`; built-in **Playground** that calls the router's own endpoint249- Health endpoint; auto-start server at launch; launch-at-login; keep-serving with window closed; first-class **menu bar extra**250- Shortcuts: ⌘R start/stop server, ⌘K command palette, ⌘1–6 sections, ⌘F filter logs, ⌘⇧C copy endpoint URL251252---253254## PHASE 7 — VERIFICATION (MANDATORY)255256The user will provide **real API keys** (same providers as Zyquo Cloud). You MUST:2572581. **Full compatibility matrix:** for **every provider and every chat model** in the catalog, drive requests through the local endpoint (never directly at upstreams) and verify: non-streaming completion correct; **streaming chunk-shape spec-exact** (validated by parsing with the official OpenAI SDK, not just eyeballing); usage present/estimated correctly; finish_reason mapped; errors in OpenAI format. Additionally verify **tool calling** on tool-capable models, **vision** on vision-capable models, and **reasoning content** on reasoning models. Produce the table: provider → model → non-stream ✅/❌ → stream ✅/❌ → tools/vision/reasoning ✅/❌/n-a → notes. **Fix every failure until green.**2592. **Client-compatibility tests:** point the official **OpenAI Python SDK** and **OpenAI JS SDK** (and `curl`) at `http://localhost:<port>/v1` with a local key and run scripted checks for both modes incl. streamed tool calls. The SDKs must work unmodified.2603. **Gateway behavior:** port conflict handling; graceful shutdown with an active stream; client-disconnect cancels upstream (verify no orphaned upstream usage); retry on injected 429; fallback chain triggers correctly and reports the actually-used model; local-key auth (valid/invalid/revoked/rate-limited paths); LAN mode refuses to start without a key.2614. **Security checks:** confirm provider keys never appear in any response, log file, or error; logs are redacted by default; vault round-trips.2625. Never commit, log, or embed the user's keys anywhere; keys live only in the encrypted vault or env vars during testing.263264---265266## PHASE 8 — SIGNING & NOTARIZATION (REAL, NOT AD-HOC)267268The user has an existing, working signing/notarization setup for another project. **Before doing anything, read and inspect the folder:**269270```271/Users/simon-pierreboucher/Desktop/other/OTHER/zyquo-term272```273274Locate the **Developer ID Application identity name**, **Team ID**, **notarytool keychain profile (or Apple ID + app-specific password)**, entitlements, and any config there. **Reuse the exact same identity, Team ID, and notarytool credentials/profile for Zyquo Router.** Never invent placeholders, never print secrets, never commit them.275276Then implement `make release`:2771. Build universal release (arm64 + x86_64, `lipo`), assemble `Zyquo Router.app`.2782. `entitlements.plist` with **Hardened Runtime**; minimal set — network **client** (upstream calls) and network **server** behavior (verify: for a non-sandboxed Developer ID app, listening on localhost needs no special entitlement; if you adopt App Sandbox instead, you'd need `com.apple.security.network.server` + `.client` — prefer NOT sandboxing this developer tool, document the choice). Note that macOS may show the local-network permission prompt when binding non-localhost; handle and explain it in-app.2793. `codesign --force --options runtime --timestamp --entitlements entitlements.plist --sign "Developer ID Application: <identity from zyquo-term>" "Zyquo Router.app"` — sign nested code first.2804. `ditto -c -k --keepParent` → `xcrun notarytool submit "Zyquo Router.zip" --keychain-profile "<profile from zyquo-term>" --wait`.2815. `xcrun stapler staple "Zyquo Router.app"`; verify `spctl -a -vv` = "accepted, source=Notarized Developer ID" and `stapler validate`.2826. Optional signed+stapled DMG (`hdiutil`).2837. On failure: `notarytool log`, fix, resubmit until it passes. Keep `make dev` (ad-hoc) for iteration.284285---286287## Engineering Standards288289- Swift 5.9+ (Swift 6 mode if the toolchain allows); zero warnings290- Server built on structured concurrency; every request a cancellable task; upstream cancellation on client disconnect guaranteed; backpressure-aware SSE writing291- All wire types `Codable` and spec-exact; the translation layer fully unit-testable with recorded fixtures per provider (build fixture tests for chunk translation — Anthropic events → OpenAI chunks, Gemini → OpenAI chunks)292- Robust, human-readable errors everywhere (port in use → suggest next free port; provider key invalid → name the provider; upstream down → 502 with detail)293- Provider layer identical to Zyquo Cloud with ALL models; design tokens only; UI strings centralized; `docs/API.md` always in sync with behavior (add a CI-style check script that exercises the running server against the documented schemas)294- `README.md` (build + quickstart incl. pointing the OpenAI SDK at the router) + `docs/` (ROUTER-RESEARCH, PROVIDER-REUSE, API, PLAN)295- Commit in logical, phase-prefixed increments; never commit secrets or unredacted logs296297## Definition of Done298299- `make release` produces a **Developer ID–signed, notarized, stapled** `Zyquo Router.app` (verified by `spctl`), built without the Xcode IDE300- The server starts on any user-chosen port and serves a **spec-exact OpenAI-compatible API** — `chat/completions` (streaming + non-streaming, tools, vision, reasoning) and `models` — fronting **every provider/model from Zyquo Cloud** with namespaced IDs, aliases, and fallback chains301- The official OpenAI Python and JS SDKs work against the router unmodified; the Phase 7 compatibility matrix is fully green with real keys302- Security posture holds: localhost by default, LAN only with mandatory local keys, provider keys only in the reused encrypted vault (NO Keychain), redacted logs, nothing leaks303- Live dashboard, request inspector, usage/cost tracking, Playground, in-app Docs, and a first-class menu bar extra all work beautifully304- The cyan-signal SVG icon exists in the family style (chunky black Z with electric-blue base on the white soft-3D squircle, routing-lines motif), striking at all sizes, embedded as `.icns` + menu bar template glyph305- The cyan-graphite light theme matches the Phase 4 spec and passes the design quality gate; dark theme derived and correct306- Naming coherent everywhere: `Zyquo Router` user-facing, `com.zyquo.router`, `ZyquoRouter` target/data folder307- **Every code file starts with the mandatory Author/Mail header** (verified by a repo-wide sweep)308- `docs/PLAN.md` shows every phase completed; `docs/ROUTER-RESEARCH.md`, `docs/PROVIDER-REUSE.md`, and `docs/API.md` are complete and traceable to the implementation309- Zyquo Router feels like a polished, legendary native Mac gateway — the definitive local LLM router310