# CLAUDE.md β€” Zyquo Router ## Project Identity **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:` that fronts **every model of every provider from Zyquo Cloud** β€” one unified endpoint, one request format, **streaming and non-streaming**, for all of them. Any tool that speaks the OpenAI API (SDKs, CLIs, IDE plugins, scripts, other apps) can point at `http://localhost:/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." **Naming conventions (use consistently everywhere):** - Display name / product name: `Zyquo Router` - App bundle: `Zyquo Router.app` - Bundle identifier: `com.zyquo.router` - Executable / SPM target: `ZyquoRouter` (no space) - Data folder: `~/Library/Application Support/ZyquoRouter/` - Repo module prefix in file headers: `Zyquo Router` --- ## πŸ“‹ MANDATORY FILE HEADER β€” EVERY CODE FILE **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: ```swift // // .swift // Zyquo Router // // Author: Simon-Pierre Boucher // Mail: contact@spboucher.ai // ``` For shell scripts / Makefiles: ```bash # # # Zyquo Router # # Author: Simon-Pierre Boucher # Mail: contact@spboucher.ai # ``` No 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. --- ## 🧭 METHODOLOGY β€” WORK METHODICALLY, KEEP EVERYTHING COHERENT You 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. **Working rules:** 1. **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. 2. **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". 3. **Single source of truth, everywhere:** - 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.** - 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. - Colors, fonts, spacing, radii β†’ only from `ZyquoTheme` design tokens. Zero raw hex or magic numbers in views. - HTTP serving, translation, and routing β†’ only in the `Server/`, `Translate/`, `Router/` layers; never leak into Views/ViewModels. - Product naming β†’ per the conventions above. Never `Zyquo` alone, never `ZyquoRouter` in user-facing text. 4. **Coherence sweeps:** after Phases 3, 6, and 8 (uniform naming β€” always `RouteRequest`, `UpstreamCall`, `ProviderClient`, `APIKeyRecord`; no dead code; headers present; folders match Phase 2). 5. **Compile early, compile often.** Never accumulate more than one file of unbuilt changes. 6. **Commit discipline:** one logical unit per commit, phase-prefixed. Never commit secrets or logged request bodies. 7. **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. --- ## ⚠️ PHASE 0 β€” MANDATORY RESEARCH + ZYQUO CLOUD STUDY (DO THIS FIRST, BEFORE ANY CODE) Two mandatory research tracks, each producing a document. No Swift until both are done. ### 0.A β€” `docs/ROUTER-RESEARCH.md` β€” how to build an LLM gateway/router, correctly (INTENSIVE WEB RESEARCH) Do 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: 1. **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). 2. **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). 3. **Translation matrices β€” the hard core.** For each NON-OpenAI-shaped upstream, document the exact bidirectional mapping: - **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. - **Gemini `generateContent` / `streamGenerateContent`:** `contents`/`parts`, `systemInstruction`, `generationConfig` param names, function calling ↔ tools, inline image data, candidate/finishReason mapping, and its streaming format β†’ OpenAI chunks. - 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). 4. **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). 5. **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. ### 0.B β€” `docs/PROVIDER-REUSE.md` β€” study the Zyquo Cloud repo and reuse its providers **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: 1. **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. 2. **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. 3. **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. 4. Provider streaming quirks so the translation layer normalizes them all into byte-perfect OpenAI chunks. --- ## PHASE 1 β€” Project Setup (No Xcode IDE) - **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. - **App bundle:** `Makefile` builds release, assembles `Zyquo Router.app`, signs (Phase 8; ad-hoc for `make dev`). - **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). - **Entry point:** `@main` SwiftUI `App`; proper activation from terminal; the server lifecycle is owned by the app (start/stop survives window close if enabled). --- ## PHASE 2 β€” Architecture + Server Skeleton ``` Sources/ZyquoRouter/ β”œβ”€β”€ App/ # @main, windows, menu bar extra, server lifecycle β”œβ”€β”€ DesignSystem/ # ZyquoTheme β€” family tokens, Router palette β”œβ”€β”€ Models/ # AIModel, ProviderConfig, RouteRule, Alias, RequestLogEntry, UsageRecord, LocalAPIKey… β”œβ”€β”€ Server/ β”‚ β”œβ”€β”€ HTTPServer.swift # NIO bootstrap, bind host/port, lifecycle, graceful shutdown β”‚ β”œβ”€β”€ Routes.swift # /v1/chat/completions, /v1/models, /health, (optional /v1/embeddings, /v1/responses) β”‚ β”œβ”€β”€ SSEWriter.swift # spec-exact chunked SSE emission, [DONE], client-disconnect handling β”‚ β”œβ”€β”€ AuthMiddleware.swift # optional local API keys (Bearer), per-key limits β”‚ └── CORS.swift β”œβ”€β”€ Router/ β”‚ β”œβ”€β”€ RequestRouter.swift # model-id β†’ provider resolution, aliases, fallback chains β”‚ β”œβ”€β”€ RetryPolicy.swift # backoff on 429/5xx, timeout handling β”‚ └── UsageMeter.swift # tokens + cost per request/key/model/provider β”œβ”€β”€ Translate/ β”‚ β”œβ”€β”€ OpenAINormalizer.swift # canonical internal request/response ⇄ OpenAI wire format β”‚ β”œβ”€β”€ AnthropicTranslator.swift β”‚ β”œβ”€β”€ GeminiTranslator.swift β”‚ └── CompatAdjuster.swift # per-provider param strip/translate table for OpenAI-compatible upstreams β”œβ”€β”€ Providers/ # PORTED FROM ZYQUO CLOUD (all models) β”‚ β”œβ”€β”€ ProviderProtocol.swift β”‚ β”œβ”€β”€ OpenAICompatibleClient.swift β”‚ β”œβ”€β”€ AnthropicClient.swift β”‚ └── GeminiClient.swift β”œβ”€β”€ Services/ β”‚ β”œβ”€β”€ SecureKeyStore.swift # reused from Zyquo Cloud (no Keychain) β”‚ β”œβ”€β”€ RequestLogStore.swift # ring-buffer + persisted log, redaction β”‚ └── PersistenceService.swift β”œβ”€β”€ ViewModels/ └── Views/ ``` - **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. - **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). --- ## PHASE 3 β€” THE STANDARDIZED API (THE CORE) Implement 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:/v1`, against one OpenAI-compatible provider, Anthropic, and Gemini. ### 3.A β€” `POST /v1/chat/completions` (mandatory, spec-exact) - 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. - **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. - **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. - **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. - **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. - Provider-specific extras (e.g., Perplexity search options) accepted via pass-through extra body keys, documented in `docs/API.md`. - **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. - **Retries & fallbacks:** RetryPolicy on transient upstream errors; optional user-configured fallback chains (model list tried in order), surfaced honestly in the response `model` field. ### 3.B β€” Supporting endpoints - `GET /v1/models` β€” full catalog with namespaced IDs (+ aliases), OpenAI list shape; optionally enriched metadata (context, pricing) under an `x-zyquo` extension key. - `GET /health` β€” status, uptime, version. - **Optional (nice, after the mandatory works):** `POST /v1/embeddings` routed to providers offering embeddings; `POST /v1/responses` if Phase 0 deemed it worthwhile. ### 3.C β€” Access control & network posture - Default bind `127.0.0.1`; **LAN exposure (`0.0.0.0`) is explicit opt-in** and forces at least one **local API key**. - 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. - CORS configurable (default permissive for localhost tooling). - Provider keys live only in the reused encrypted vault; no endpoint ever returns them. --- ## PHASE 4 β€” DESIGN SYSTEM & UI (LIGHT THEME, PIXEL-PERFECT, "CONTROL ROOM" IDENTITY) Same 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. ### 4.1 β€” Light theme | Token | Value (light) | Usage | |---|---|---| | `background` | `#FAFBFC` (crisp cool off-white) | Canvas | | `surface` | `#FFFFFF` | Cards, panels | | `surfaceSecondary` | `#F1F4F6` | Hover, log rows, code blocks | | `accent` | `#0891B2` (signal cyan) paired with graphite `#374151` | Start button, active states, links, charts | | `accentSubtle` | `#E5F5F9` | Selected rows, active-server tint | | `textPrimary` `#191C1F` Β· `textSecondary` `#697077` Β· `textTertiary` `#9BA3AA` Β· `border` `#E4E8EB` | | | | `success`/`warning`/`danger` | `#2FA36B`/`#D9822B`/`#D64545` | Server running / degraded / stopped & errors | | chart tokens | cyan (requests), graphite (latency), per-provider hues | Dashboard charts | Family 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. ### 4.2 β€” Layout & screens (exact spec) Left 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. - **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. - **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). - **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). - **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. - **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. - **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. - **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. **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). ### 4.3 β€” Motion & 4.4 quality gate Family 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. ### Menu bar extra (first-class here) A 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. --- ## PHASE 5 β€” APP ICON: ULTRA-LEGENDARY "ROUTER" ICON, DESIGNED IN SVG Designed 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. **Signature motif β€” the Z that routes.** Two directions (render both, keep the best): 1. *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. 2. *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. - 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. - **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. **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. --- ## PHASE 6 β€” Features (This is where Zyquo Router becomes LEGENDARY) ### The gateway - One OpenAI-compatible endpoint on a user-chosen port fronting **every provider/model from Zyquo Cloud**, streaming and non-streaming, spec-exact - Namespaced model IDs + user aliases + fallback chains; enable/disable models; multimodal (images) and tool calling translated per provider; reasoning content preserved - Retries with backoff; honest error mapping in OpenAI format; per-request cancellation propagated upstream on client disconnect ### Control & security - Localhost by default; LAN opt-in gated behind mandatory local API keys (`zyquo-sk-…`, hashed at rest, per-key limits and model allow-lists) - Provider keys in the reused encrypted vault (NO Keychain), never logged, never served - Redacted-by-default request logging with explicit reveal; export logs ### Observability - Live dashboard: req/min, tokens, **cost estimates** from the catalog's pricing, error rate, latency, per-provider/per-model/per-key breakdowns, uptime - Full request inspector with timing waterfall and pretty JSON - Usage history persisted; daily/weekly summaries ### Developer experience - One-click copy: endpoint URL, curl, OpenAI Python/JS snippets pre-filled with the port and a local key - In-app **Docs** rendered from `docs/API.md`; built-in **Playground** that calls the router's own endpoint - Health endpoint; auto-start server at launch; launch-at-login; keep-serving with window closed; first-class **menu bar extra** - Shortcuts: ⌘R start/stop server, ⌘K command palette, ⌘1–6 sections, ⌘F filter logs, βŒ˜β‡§C copy endpoint URL --- ## PHASE 7 β€” VERIFICATION (MANDATORY) The user will provide **real API keys** (same providers as Zyquo Cloud). You MUST: 1. **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.** 2. **Client-compatibility tests:** point the official **OpenAI Python SDK** and **OpenAI JS SDK** (and `curl`) at `http://localhost:/v1` with a local key and run scripted checks for both modes incl. streamed tool calls. The SDKs must work unmodified. 3. **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. 4. **Security checks:** confirm provider keys never appear in any response, log file, or error; logs are redacted by default; vault round-trips. 5. Never commit, log, or embed the user's keys anywhere; keys live only in the encrypted vault or env vars during testing. --- ## PHASE 8 β€” SIGNING & NOTARIZATION (REAL, NOT AD-HOC) The user has an existing, working signing/notarization setup for another project. **Before doing anything, read and inspect the folder:** ``` /Users/simon-pierreboucher/Desktop/other/OTHER/zyquo-term ``` Locate 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. Then implement `make release`: 1. Build universal release (arm64 + x86_64, `lipo`), assemble `Zyquo Router.app`. 2. `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. 3. `codesign --force --options runtime --timestamp --entitlements entitlements.plist --sign "Developer ID Application: " "Zyquo Router.app"` β€” sign nested code first. 4. `ditto -c -k --keepParent` β†’ `xcrun notarytool submit "Zyquo Router.zip" --keychain-profile "" --wait`. 5. `xcrun stapler staple "Zyquo Router.app"`; verify `spctl -a -vv` = "accepted, source=Notarized Developer ID" and `stapler validate`. 6. Optional signed+stapled DMG (`hdiutil`). 7. On failure: `notarytool log`, fix, resubmit until it passes. Keep `make dev` (ad-hoc) for iteration. --- ## Engineering Standards - Swift 5.9+ (Swift 6 mode if the toolchain allows); zero warnings - Server built on structured concurrency; every request a cancellable task; upstream cancellation on client disconnect guaranteed; backpressure-aware SSE writing - 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) - Robust, human-readable errors everywhere (port in use β†’ suggest next free port; provider key invalid β†’ name the provider; upstream down β†’ 502 with detail) - 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) - `README.md` (build + quickstart incl. pointing the OpenAI SDK at the router) + `docs/` (ROUTER-RESEARCH, PROVIDER-REUSE, API, PLAN) - Commit in logical, phase-prefixed increments; never commit secrets or unredacted logs ## Definition of Done - `make release` produces a **Developer ID–signed, notarized, stapled** `Zyquo Router.app` (verified by `spctl`), built without the Xcode IDE - 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 chains - The official OpenAI Python and JS SDKs work against the router unmodified; the Phase 7 compatibility matrix is fully green with real keys - 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 leaks - Live dashboard, request inspector, usage/cost tracking, Playground, in-app Docs, and a first-class menu bar extra all work beautifully - 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 glyph - The cyan-graphite light theme matches the Phase 4 spec and passes the design quality gate; dark theme derived and correct - Naming coherent everywhere: `Zyquo Router` user-facing, `com.zyquo.router`, `ZyquoRouter` target/data folder - **Every code file starts with the mandatory Author/Mail header** (verified by a repo-wide sweep) - `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 implementation - Zyquo Router feels like a polished, legendary native Mac gateway β€” the definitive local LLM router