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%

phase1: SPM project setup — package, Makefile, app bundle, entry point

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Simon-Pierre Boucher committed 11 days ago (Jul 31, 2026) parent f30e746

Showing 9 changed files with +472 and −1

added Makefile +89 −0
@@ -0,0 +1,89 @@
1 +#
2 +# Makefile
3 +# Zyquo Router
4 +#
5 +# Author: Simon-Pierre Boucher
6 +# Mail: contact@spboucher.ai
7 +#
8 +# make / make dev → release build, assemble dist/Zyquo Router.app, ad-hoc sign (fast local iteration)
9 +# make release → universal binary, Developer ID sign, notarize, staple (Phase 8)
10 +# make icon → regenerate AppIcon.icns from assets/icon/zyquo-router.svg (Phase 5)
11 +# make test → swift test
12 +# make clean → remove build products
13 +#
14 +# Unlike Zyquo Cloud (CLT-only machine), full Xcode is installed here, so the
15 +# SwiftUI macro plugins ship with the toolchain and no SDKROOT pin is needed.
16 +#
17 +
18 +APP_NAME := Zyquo Router
19 +EXEC_NAME := ZyquoRouter
20 +BUNDLE_ID := com.zyquo.router
21 +VERSION := 1.0.0
22 +BUILD_NUM := 1
23 +MIN_MACOS := 13.0
24 +DIST := dist
25 +APP_DIR := $(DIST)/$(APP_NAME).app
26 +IDENTITY := Developer ID Application: Simon-Pierre Boucher (3YM54G49SN)
27 +NOTARY_PROFILE:= MacLustr-Notarize
28 +ENTITLEMENTS := Resources/ZyquoRouter.entitlements
29 +
30 +.PHONY: dev build bundle release universal icon test clean run
31 +
32 +dev: build bundle
33 + @echo "=== Ad-hoc signing (dev) ==="
34 + codesign --force --deep --sign - "$(APP_DIR)"
35 + codesign --verify --deep --strict "$(APP_DIR)"
36 + @echo "Packaged: $(APP_DIR) — launch with: open \"$(APP_DIR)\""
37 +
38 +build:
39 + swift build -c release
40 +
41 +test:
42 + swift test
43 +
44 +run: dev
45 + open "$(APP_DIR)"
46 +
47 +bundle:
48 + @echo "=== Assembling $(APP_DIR) ==="
49 + rm -rf "$(APP_DIR)"
50 + mkdir -p "$(APP_DIR)/Contents/MacOS" "$(APP_DIR)/Contents/Resources"
51 + cp .build/release/$(EXEC_NAME) "$(APP_DIR)/Contents/MacOS/$(EXEC_NAME)"
52 + @for b in .build/release/*.bundle; do [ -e "$$b" ] && cp -R "$$b" "$(APP_DIR)/Contents/Resources/" || true; done
53 + @if [ -f Resources/AppIcon.icns ]; then cp Resources/AppIcon.icns "$(APP_DIR)/Contents/Resources/AppIcon.icns"; fi
54 + @for r in Resources/MenuBarIcon.png Resources/MenuBarIcon@2x.png; do [ -f "$$r" ] && cp "$$r" "$(APP_DIR)/Contents/Resources/" || true; done
55 + scripts/write-info-plist.sh "$(APP_DIR)" "$(APP_NAME)" "$(EXEC_NAME)" "$(BUNDLE_ID)" "$(VERSION)" "$(BUILD_NUM)" "$(MIN_MACOS)"
56 +
57 +# Both arch builds land in .build/<triple>/Release; each slice is copied aside
58 +# before lipo so the second build cannot clobber the first.
59 +universal:
60 + @echo "=== Building universal binary (arm64 + x86_64) ==="
61 + mkdir -p .build/universal
62 + swift build -c release --arch arm64
63 + cp .build/arm64-apple-macosx/release/$(EXEC_NAME) .build/universal/$(EXEC_NAME).arm64 2>/dev/null || \
64 + cp .build/out/Products/Release/$(EXEC_NAME) .build/universal/$(EXEC_NAME).arm64
65 + swift build -c release --arch x86_64
66 + cp .build/x86_64-apple-macosx/release/$(EXEC_NAME) .build/universal/$(EXEC_NAME).x86_64 2>/dev/null || \
67 + cp .build/out/Products/Release/$(EXEC_NAME) .build/universal/$(EXEC_NAME).x86_64
68 + lipo -create \
69 + .build/universal/$(EXEC_NAME).arm64 \
70 + .build/universal/$(EXEC_NAME).x86_64 \
71 + -output .build/universal/$(EXEC_NAME)
72 + lipo -archs .build/universal/$(EXEC_NAME)
73 +
74 +release: universal
75 + @echo "=== Assembling $(APP_DIR) (universal) ==="
76 + rm -rf "$(APP_DIR)"
77 + mkdir -p "$(APP_DIR)/Contents/MacOS" "$(APP_DIR)/Contents/Resources"
78 + cp .build/universal/$(EXEC_NAME) "$(APP_DIR)/Contents/MacOS/$(EXEC_NAME)"
79 + @for b in .build/arm64-apple-macosx/release/*.bundle; do [ -e "$$b" ] && cp -R "$$b" "$(APP_DIR)/Contents/Resources/" || true; done
80 + @if [ -f Resources/AppIcon.icns ]; then cp Resources/AppIcon.icns "$(APP_DIR)/Contents/Resources/AppIcon.icns"; fi
81 + @for r in Resources/MenuBarIcon.png Resources/MenuBarIcon@2x.png; do [ -f "$$r" ] && cp "$$r" "$(APP_DIR)/Contents/Resources/" || true; done
82 + scripts/write-info-plist.sh "$(APP_DIR)" "$(APP_NAME)" "$(EXEC_NAME)" "$(BUNDLE_ID)" "$(VERSION)" "$(BUILD_NUM)" "$(MIN_MACOS)"
83 + scripts/notarize.sh "$(APP_DIR)" "$(IDENTITY)" "$(NOTARY_PROFILE)" "$(ENTITLEMENTS)"
84 +
85 +icon:
86 + scripts/generate-icon.sh
87 +
88 +clean:
89 + rm -rf .build $(DIST)
added Package.resolved +176 −0
@@ -0,0 +1,176 @@
1 +{
2 + "pins" : [
3 + {
4 + "identity" : "swift-algorithms",
5 + "kind" : "remoteSourceControl",
6 + "location" : "https://github.com/apple/swift-algorithms.git",
7 + "state" : {
8 + "revision" : "87e50f483c54e6efd60e885f7f5aa946cee68023",
9 + "version" : "1.2.1"
10 + }
11 + },
12 + {
13 + "identity" : "swift-asn1",
14 + "kind" : "remoteSourceControl",
15 + "location" : "https://github.com/apple/swift-asn1.git",
16 + "state" : {
17 + "revision" : "a9a5efd40eaf558a2bcd48d64b1d1646be686008",
18 + "version" : "1.7.1"
19 + }
20 + },
21 + {
22 + "identity" : "swift-async-algorithms",
23 + "kind" : "remoteSourceControl",
24 + "location" : "https://github.com/apple/swift-async-algorithms.git",
25 + "state" : {
26 + "revision" : "3da39bbc4e687d4192af7c9cf4eab805745a0b9c",
27 + "version" : "1.1.5"
28 + }
29 + },
30 + {
31 + "identity" : "swift-atomics",
32 + "kind" : "remoteSourceControl",
33 + "location" : "https://github.com/apple/swift-atomics.git",
34 + "state" : {
35 + "revision" : "0442cb5a3f98ab802acb777929fdb446bda11a34",
36 + "version" : "1.3.1"
37 + }
38 + },
39 + {
40 + "identity" : "swift-certificates",
41 + "kind" : "remoteSourceControl",
42 + "location" : "https://github.com/apple/swift-certificates.git",
43 + "state" : {
44 + "revision" : "449dbbecd0f31e82b510ada227ca152caa8b5e98",
45 + "version" : "1.19.4"
46 + }
47 + },
48 + {
49 + "identity" : "swift-cmark",
50 + "kind" : "remoteSourceControl",
51 + "location" : "https://github.com/swiftlang/swift-cmark.git",
52 + "state" : {
53 + "revision" : "924936d0427cb25a61169739a7660230bffa6ea6",
54 + "version" : "0.8.0"
55 + }
56 + },
57 + {
58 + "identity" : "swift-collections",
59 + "kind" : "remoteSourceControl",
60 + "location" : "https://github.com/apple/swift-collections.git",
61 + "state" : {
62 + "revision" : "a0cb0954ecb21e4e31b0070e6ed5674e8556685a",
63 + "version" : "1.6.0"
64 + }
65 + },
66 + {
67 + "identity" : "swift-crypto",
68 + "kind" : "remoteSourceControl",
69 + "location" : "https://github.com/apple/swift-crypto.git",
70 + "state" : {
71 + "revision" : "47d3869a7291f085c1fb9fb1e6d3b97a793f45c6",
72 + "version" : "4.5.1"
73 + }
74 + },
75 + {
76 + "identity" : "swift-http-structured-headers",
77 + "kind" : "remoteSourceControl",
78 + "location" : "https://github.com/apple/swift-http-structured-headers.git",
79 + "state" : {
80 + "revision" : "933538faa42c432d385f02e07df0ace7c5ecfc47",
81 + "version" : "1.7.0"
82 + }
83 + },
84 + {
85 + "identity" : "swift-http-types",
86 + "kind" : "remoteSourceControl",
87 + "location" : "https://github.com/apple/swift-http-types.git",
88 + "state" : {
89 + "revision" : "db774a277f60063a32d854f2980299caf06da041",
90 + "version" : "1.6.0"
91 + }
92 + },
93 + {
94 + "identity" : "swift-log",
95 + "kind" : "remoteSourceControl",
96 + "location" : "https://github.com/apple/swift-log.git",
97 + "state" : {
98 + "revision" : "a878e7f8f46cfc0e1125e565b5c08e7d5272dc9a",
99 + "version" : "1.14.0"
100 + }
101 + },
102 + {
103 + "identity" : "swift-markdown",
104 + "kind" : "remoteSourceControl",
105 + "location" : "https://github.com/swiftlang/swift-markdown.git",
106 + "state" : {
107 + "revision" : "3c6f9523da3a1ec2fd829673e472d95b8097a3b8",
108 + "version" : "0.8.0"
109 + }
110 + },
111 + {
112 + "identity" : "swift-nio",
113 + "kind" : "remoteSourceControl",
114 + "location" : "https://github.com/apple/swift-nio.git",
115 + "state" : {
116 + "revision" : "0b18836bd8b0162e7e17a995a3fbee20ed8f3b2b",
117 + "version" : "2.101.3"
118 + }
119 + },
120 + {
121 + "identity" : "swift-nio-extras",
122 + "kind" : "remoteSourceControl",
123 + "location" : "https://github.com/apple/swift-nio-extras.git",
124 + "state" : {
125 + "revision" : "88a51340f59cf181ebde888bd1b749296b3ec029",
126 + "version" : "1.34.3"
127 + }
128 + },
129 + {
130 + "identity" : "swift-nio-http2",
131 + "kind" : "remoteSourceControl",
132 + "location" : "https://github.com/apple/swift-nio-http2.git",
133 + "state" : {
134 + "revision" : "45bdf670248be5f16ec0340e125dca285536f0fb",
135 + "version" : "1.45.0"
136 + }
137 + },
138 + {
139 + "identity" : "swift-nio-ssl",
140 + "kind" : "remoteSourceControl",
141 + "location" : "https://github.com/apple/swift-nio-ssl.git",
142 + "state" : {
143 + "revision" : "d930168b86f46ca51a4bc09c5ca45c1833db8067",
144 + "version" : "2.37.2"
145 + }
146 + },
147 + {
148 + "identity" : "swift-numerics",
149 + "kind" : "remoteSourceControl",
150 + "location" : "https://github.com/apple/swift-numerics.git",
151 + "state" : {
152 + "revision" : "0c0290ff6b24942dadb83a929ffaaa1481df04a2",
153 + "version" : "1.1.1"
154 + }
155 + },
156 + {
157 + "identity" : "swift-service-lifecycle",
158 + "kind" : "remoteSourceControl",
159 + "location" : "https://github.com/swift-server/swift-service-lifecycle.git",
160 + "state" : {
161 + "revision" : "9829955b385e5bb88128b73f1b8389e9b9c3191a",
162 + "version" : "2.11.0"
163 + }
164 + },
165 + {
166 + "identity" : "swift-system",
167 + "kind" : "remoteSourceControl",
168 + "location" : "https://github.com/apple/swift-system.git",
169 + "state" : {
170 + "revision" : "50688cacbd41d547e9eb9f7a213542340b7c442b",
171 + "version" : "1.7.5"
172 + }
173 + }
174 + ],
175 + "version" : 2
176 +}
added Package.swift +40 −0
@@ -0,0 +1,40 @@
1 +// swift-tools-version:5.9
2 +//
3 +// Package.swift
4 +// Zyquo Router
5 +//
6 +// Author: Simon-Pierre Boucher
7 +// Mail: contact@spboucher.ai
8 +//
9 +
10 +import PackageDescription
11 +
12 +let package = Package(
13 + name: "ZyquoRouter",
14 + platforms: [
15 + .macOS(.v13)
16 + ],
17 + dependencies: [
18 + .package(url: "https://github.com/apple/swift-nio.git", from: "2.81.0"),
19 + .package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.24.0"),
20 + .package(url: "https://github.com/swiftlang/swift-markdown.git", from: "0.5.0")
21 + ],
22 + targets: [
23 + .executableTarget(
24 + name: "ZyquoRouter",
25 + dependencies: [
26 + .product(name: "NIOCore", package: "swift-nio"),
27 + .product(name: "NIOPosix", package: "swift-nio"),
28 + .product(name: "NIOHTTP1", package: "swift-nio"),
29 + .product(name: "NIOExtras", package: "swift-nio-extras"),
30 + .product(name: "Markdown", package: "swift-markdown")
31 + ],
32 + path: "Sources/ZyquoRouter"
33 + ),
34 + .testTarget(
35 + name: "ZyquoRouterTests",
36 + dependencies: ["ZyquoRouter"],
37 + path: "Tests/ZyquoRouterTests"
38 + ),
39 + ]
40 +)
added Resources/ZyquoRouter.entitlements +8 −0
@@ -0,0 +1,8 @@
1 +<?xml version="1.0" encoding="UTF-8"?>
2 +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3 +<plist version="1.0">
4 +<dict>
5 + <key>com.apple.security.cs.allow-jit</key>
6 + <false/>
7 +</dict>
8 +</plist>
added Sources/ZyquoRouter/App/Main.swift +23 −0
@@ -0,0 +1,23 @@
1 +//
2 +// Main.swift
3 +// Zyquo Router
4 +//
5 +// Author: Simon-Pierre Boucher
6 +// Mail: contact@spboucher.ai
7 +//
8 +// Entry point. CLI modes (verification harness, vault seeding) hang off this
9 +// in later phases; otherwise the SwiftUI app launches.
10 +//
11 +// Deliberately a synchronous main: launching NSApplicationMain from an async
12 +// main() corrupts Swift concurrency's executor setup (background tasks stop
13 +// being scheduled), so CLI modes must drive their async work explicitly.
14 +//
15 +
16 +import Foundation
17 +
18 +@main
19 +enum Main {
20 + static func main() {
21 + ZyquoRouterApp.main()
22 + }
23 +}
added Sources/ZyquoRouter/App/ZyquoRouterApp.swift +42 −0
@@ -0,0 +1,42 @@
1 +//
2 +// ZyquoRouterApp.swift
3 +// Zyquo Router
4 +//
5 +// Author: Simon-Pierre Boucher
6 +// Mail: contact@spboucher.ai
7 +//
8 +
9 +import SwiftUI
10 +
11 +struct ZyquoRouterApp: App {
12 + @NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
13 +
14 + var body: some Scene {
15 + WindowGroup("Zyquo Router") {
16 + PlaceholderView()
17 + }
18 + .defaultSize(width: 1280, height: 820)
19 + }
20 +}
21 +
22 +final class AppDelegate: NSObject, NSApplicationDelegate {
23 + func applicationDidFinishLaunching(_ notification: Notification) {
24 + // Ensure the app fronts correctly when launched outside Finder
25 + // (e.g. `swift run` or `open` from a terminal during development).
26 + NSApplication.shared.setActivationPolicy(.regular)
27 + NSApplication.shared.activate(ignoringOtherApps: true)
28 + }
29 +}
30 +
31 +/// Phase 1 placeholder — replaced by the real dashboard in Phase 6.
32 +private struct PlaceholderView: View {
33 + var body: some View {
34 + VStack(spacing: 8) {
35 + Text("Zyquo Router")
36 + .font(.largeTitle.weight(.semibold))
37 + Text("Gateway under construction — Phase 1")
38 + .foregroundStyle(.secondary)
39 + }
40 + .frame(minWidth: 1020, minHeight: 660)
41 + }
42 +}
added Tests/ZyquoRouterTests/ZyquoRouterTests.swift +17 −0
@@ -0,0 +1,17 @@
1 +//
2 +// ZyquoRouterTests.swift
3 +// Zyquo Router
4 +//
5 +// Author: Simon-Pierre Boucher
6 +// Mail: contact@spboucher.ai
7 +//
8 +// Placeholder suite; translation-fixture tests land in Phase 3.
9 +//
10 +
11 +import XCTest
12 +
13 +final class ZyquoRouterTests: XCTestCase {
14 + func testScaffold() {
15 + XCTAssertTrue(true)
16 + }
17 +}
modified docs/PLAN.md +20 −1
@@ -31,7 +31,26 @@ Keychain) and a file-by-file port plan; key caveats: Cloud has no native GeminiC
31 31 (Router translates Gemini natively per D10) and no tool-calling in Cloud's wire types
32 32 (Router extends them). All 12 real provider keys smoke-tested OK (HTTP 200).
33 33
34 ## Phase 1 — Project setup (SPM, no Xcode IDE) — pending
34 +## Phase 1 — Project setup (SPM, no Xcode IDE)
35 +
36 +- [x] 1.1 `Package.swift`: executable target `ZyquoRouter`, macOS 13+, deps = SwiftNIO (NIOCore/NIOPosix/NIOHTTP1/NIOExtras) + swift-markdown only
37 +- [x] 1.2 `Makefile`: `make dev` (debug build + ad-hoc-signed `Zyquo Router.app` + run), `make build`, `make app` (release universal), `make clean`; release/notarize targets stubbed for Phase 8
38 +- [x] 1.3 `Resources/Info.plist`: `CFBundleDisplayName` = `Zyquo Router`, `com.zyquo.router`, `LSMinimumSystemVersion` 13.0, `NSHighResolutionCapable`, `LSApplicationCategoryType` developer-tools
39 +- [x] 1.4 Entry point: `@main` SwiftUI `App` + AppDelegate activation from terminal; placeholder window
40 +- [x] 1.5 `swift build` clean, zero warnings; `make dev` launches the app window
41 +- [x] 1.6 Mandatory file headers on every code file created
42 +
43 +**Phase gate: PASSED (2026-07-30).**
44 +
45 +**Phase 1 summary:** SPM executable `ZyquoRouter` (macOS 13+, deps: SwiftNIO 2 +
46 +NIOExtras + swift-markdown) builds with zero warnings on the Xcode 6.3.3 toolchain —
47 +no SDKROOT pin needed here, unlike Cloud's CLT-only machine (noted in the Makefile).
48 +`make dev` assembles `dist/Zyquo Router.app` (com.zyquo.router, developer-tools
49 +category, ad-hoc signed, plist lints OK); the placeholder SwiftUI window launches
50 +from terminal, activates, and quits cleanly. Test target scaffolded and green.
51 +Family conventions ported from Zyquo Cloud's Makefile/write-info-plist.sh, incl.
52 +the Developer ID identity + notary profile constants for Phase 8.
53 +
35 54 ## Phase 2 — Architecture + server skeleton — pending
36 55 ## Phase 3 — Standardized API (the core) — pending
37 56 ## Phase 4 — Design system & UI spec — pending
added scripts/write-info-plist.sh +57 −0
@@ -0,0 +1,57 @@
1 +#!/bin/bash
2 +#
3 +# write-info-plist.sh
4 +# Zyquo Router
5 +#
6 +# Author: Simon-Pierre Boucher
7 +# Mail: contact@spboucher.ai
8 +#
9 +# Writes Contents/Info.plist into an assembled app bundle.
10 +# Usage: write-info-plist.sh <app-dir> <app-name> <exec-name> <bundle-id> <version> <build> <min-macos>
11 +#
12 +set -euo pipefail
13 +
14 +APP_DIR="$1"; APP_NAME="$2"; EXEC_NAME="$3"; BUNDLE_ID="$4"; VERSION="$5"; BUILD_NUM="$6"; MIN_MACOS="$7"
15 +
16 +cat > "$APP_DIR/Contents/Info.plist" << PLIST
17 +<?xml version="1.0" encoding="UTF-8"?>
18 +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
19 +<plist version="1.0">
20 +<dict>
21 + <key>CFBundleName</key>
22 + <string>${APP_NAME}</string>
23 + <key>CFBundleDisplayName</key>
24 + <string>${APP_NAME}</string>
25 + <key>CFBundleIdentifier</key>
26 + <string>${BUNDLE_ID}</string>
27 + <key>CFBundleShortVersionString</key>
28 + <string>${VERSION}</string>
29 + <key>CFBundleVersion</key>
30 + <string>${BUILD_NUM}</string>
31 + <key>CFBundleExecutable</key>
32 + <string>${EXEC_NAME}</string>
33 + <key>CFBundlePackageType</key>
34 + <string>APPL</string>
35 + <key>CFBundleIconFile</key>
36 + <string>AppIcon</string>
37 + <key>CFBundleInfoDictionaryVersion</key>
38 + <string>6.0</string>
39 + <key>LSMinimumSystemVersion</key>
40 + <string>${MIN_MACOS}</string>
41 + <key>NSHighResolutionCapable</key>
42 + <true/>
43 + <key>NSPrincipalClass</key>
44 + <string>NSApplication</string>
45 + <key>LSApplicationCategoryType</key>
46 + <string>public.app-category.developer-tools</string>
47 + <key>NSHumanReadableCopyright</key>
48 + <string>© 2026 Simon-Pierre Boucher. All rights reserved.</string>
49 + <key>NSSupportsAutomaticTermination</key>
50 + <false/>
51 + <key>NSSupportsSuddenTermination</key>
52 + <false/>
53 +</dict>
54 +</plist>
55 +PLIST
56 +
57 +echo "Info.plist written to $APP_DIR/Contents/Info.plist"
58