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:
//
// <FileName>.swift
// Zyquo Router
//
// Author: Simon-Pierre Boucher
// Mail: contact@spboucher.ai
//For shell scripts / Makefiles:
#
# <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:
- 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 indocs/PLAN.mdbefore moving on. - Phase gates: Phase 0 is complete only when
docs/ROUTER-RESEARCH.mdanddocs/PROVIDER-REUSE.mdare complete. Phase 2 is complete only when the embedded HTTP server answersGET /v1/modelson the chosen port. Phase 3 is complete only whenPOST /v1/chat/completionsworks 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 withcurland 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 whenspctlsays "Notarized Developer ID". - 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
ZyquoThemedesign 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
Zyquoalone, neverZyquoRouterin user-facing text.
- Coherence sweeps: after Phases 3, 6, and 8 (uniform naming — always
RouteRequest,UpstreamCall,ProviderClient,APIKeyRecord; no dead code; headers present; folders match Phase 2). - Compile early, compile often. Never accumulate more than one file of unbuilt changes.
- Commit discipline: one logical unit per commit, phase-prefixed. Never commit secrets or logged request bodies.
- 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:
- The OpenAI API specification — the standard you are implementing. Document the CURRENT
POST /v1/chat/completionscontract exhaustively: request schema (model,messageswith 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_choicefunction calling,stream,stream_optionsincl.include_usage,user), the non-streaming response schema (id,object: "chat.completion",created,model,choices[].message,finish_reasonvalues,usagewith 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_reasonon last content chunk, optional final usage chunk, terminatingdata: [DONE]). Also documentGET /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/responsesAPI is worth also exposing; decide and document (chat/completions is mandatory; responses optional). - How existing gateways do it — study the references: LiteLLM (proxy config, model naming
provider/model, param translation tables, error mapping), OpenRouter (unified model IDsvendor/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). - Translation matrices — the hard core. For each NON-OpenAI-shaped upstream, document the exact bidirectional mapping:
- Anthropic Messages API: system prompt extraction (top-level
systemvs. messages), content blocks,max_tokensrequired, tool_use/tool_result ↔ OpenAItool_calls/toolrole 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_deltafor streamed tool arguments) → how each maps onto OpenAI chunk deltas. - Gemini
generateContent/streamGenerateContent:contents/parts,systemInstruction,generationConfigparam 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_contentfield preserved in the normalized response).
- Anthropic Messages API: system prompt extraction (top-level
- 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.frameworklistener 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). - 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:
- Exactly how each provider's API is called in Zyquo Cloud: base URLs, auth headers, request/response
Codablemodels, the sharedOpenAICompatibleClient, nativeAnthropicClient/GeminiClient, SSE parsing. The router calls upstreams the exact same way — port or factor this code to be identical to Cloud's. - 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 viaGET /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. - The secure key vault from Cloud (custom AES-256-GCM, NO Keychain): reuse the same
SecureKeyStoredesign and vault format so users manage keys identically across the family. - 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 targetZyquoRouter. Dependencies: Foundation + SwiftUI + CryptoKit + SwiftNIO (or the serving choice justified in Phase 0.A.4) + optionally Appleswift-markdownfor the in-app docs. Nothing else. - App bundle:
Makefilebuilds release, assemblesZyquo Router.app, signs (Phase 8; ad-hoc formake dev). - Info.plist:
CFBundleDisplayName=Zyquo Router, bundle IDcom.zyquo.router,LSMinimumSystemVersion(macOS 13.0+),NSHighResolutionCapable,LSApplicationCategoryType(public.app-category.developer-tools). Universal (arm64 + x86_64) for release. OptionalLSUIElement-style behavior later via a "run in menu bar only" setting (still a regular app by default). - Entry point:
@mainSwiftUIApp; 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
AsyncSequencepiped intoSSEWriter; client disconnect cancels the upstream task immediately. - PHASE GATE: server starts on a user-chosen port,
GET /healthreturns ok,GET /v1/modelsreturns 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. Resolvemodel(namespacedprovider/model, unambiguous bare ID, or alias) viaRequestRouter; 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.completionobject — correctchoices, mappedfinish_reason, realusage(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.chunkSSE — role delta first, content deltas, streamedtool_callsargument deltas,finish_reasonchunk, optional usage chunk whenstream_options.include_usageis set, thendata: [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_contenton 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
modelfield.
3.B — Supporting endpoints
GET /v1/models— full catalog with namespaced IDs (+ aliases), OpenAI list shape; optionally enriched metadata (context, pricing) under anx-zyquoextension key.GET /health— status, uptime, version.- Optional (nice, after the mandatory works):
POST /v1/embeddingsrouted to providers offering embeddings;POST /v1/responsesif 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/v1with a one-click Copy and copy-ascurl/ 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, PythonOpenAI(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):
- 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.
- 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:
- 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.
- Client-compatibility tests: point the official OpenAI Python SDK and OpenAI JS SDK (and
curl) athttp://localhost:<port>/v1with a local key and run scripted checks for both modes incl. streamed tool calls. The SDKs must work unmodified. - 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.
- Security checks: confirm provider keys never appear in any response, log file, or error; logs are redacted by default; vault round-trips.
- 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-termLocate 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:
- Build universal release (arm64 + x86_64,
lipo), assembleZyquo Router.app. entitlements.plistwith 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 needcom.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.codesign --force --options runtime --timestamp --entitlements entitlements.plist --sign "Developer ID Application: <identity from zyquo-term>" "Zyquo Router.app"— sign nested code first.ditto -c -k --keepParent→xcrun notarytool submit "Zyquo Router.zip" --keychain-profile "<profile from zyquo-term>" --wait.xcrun stapler staple "Zyquo Router.app"; verifyspctl -a -vv= "accepted, source=Notarized Developer ID" andstapler validate.- Optional signed+stapled DMG (
hdiutil). - On failure:
notarytool log, fix, resubmit until it passes. Keepmake 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
Codableand 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.mdalways 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 releaseproduces a Developer ID–signed, notarized, stapledZyquo Router.app(verified byspctl), 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) andmodels— 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 Routeruser-facing,com.zyquo.router,ZyquoRoutertarget/data folder - Every code file starts with the mandatory Author/Mail header (verified by a repo-wide sweep)
docs/PLAN.mdshows every phase completed;docs/ROUTER-RESEARCH.md,docs/PROVIDER-REUSE.md, anddocs/API.mdare complete and traceable to the implementation- Zyquo Router feels like a polished, legendary native Mac gateway — the definitive local LLM router