SPB Git

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%
33.3 KB

# 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:<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.

Any 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."

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
//
//  <FileName>.swift
//  Zyquo Router
//
//  Author: Simon-Pierre Boucher
//  Mail: contact@spboucher.ai
//

For shell scripts / Makefiles:

bash
#
#  <filename>
#  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

text
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:<port>/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., fastcerebras/...; bestanthropic/...). 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.

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.iconseticonutil -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:<port>/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:

text
/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: <identity from zyquo-term>" "Zyquo Router.app" — sign nested code first.
  4. ditto -c -k --keepParentxcrun notarytool submit "Zyquo Router.zip" --keychain-profile "<profile from zyquo-term>" --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 APIchat/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