SPB Git

spb/zyquo-local Public MIT

Native macOS AI chat that runs LLMs 100% locally on Apple Silicon with MLX — no cloud, no API keys.

Swift 97.2% Shell 1.8% Makefile 1%

phase1: SPM project, Makefile bundle pipeline, app entry with Silicon gate

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
simon-pierre boucher committed 11 days ago (Jul 30, 2026) parent 0c28822

Showing 11 changed files with +367 and −1

added Makefile +85 −0
@@ -0,0 +1,85 @@
1 +#
2 +# Makefile
3 +# Zyquo Local
4 +#
5 +# Author: Simon-Pierre Boucher
6 +# Mail: contact@spboucher.ai
7 +#
8 +
9 +# ── Build environment (see docs/BUILD.md) ────────────────────────────────────
10 +# 1. SDK pin: macOS 27 CLT SDKs ship SwiftUI @State/@Bindable as Xcode-only
11 +# macro plugins; 26.5 is the last SDK where plain `swift build` works.
12 +# 2. Metal: mlx-swift compiles .metal kernels at build time; the compiler is
13 +# resolved via PATH from the standalone Metal toolchain.
14 +export SDKROOT := /Library/Developer/CommandLineTools/SDKs/MacOSX26.5.sdk
15 +export PATH := $(HOME)/Developer/Metal.xctoolchain/usr/bin:$(PATH)
16 +
17 +APP_NAME := Zyquo Local
18 +EXEC_NAME := ZyquoLocal
19 +BUNDLE_ID := com.zyquo.local
20 +VERSION := 0.1.0
21 +BUILD_DIR := .build
22 +DIST_DIR := dist
23 +APP_BUNDLE := $(DIST_DIR)/$(APP_NAME).app
24 +RELEASE_BIN := $(BUILD_DIR)/release/$(EXEC_NAME)
25 +DEBUG_BIN := $(BUILD_DIR)/debug/$(EXEC_NAME)
26 +SIGN_IDENTITY := Developer ID Application: Simon-Pierre Boucher (3YM54G49SN)
27 +NOTARY_PROFILE := MacLustr-Notarize
28 +
29 +.PHONY: build release-build test clean dev bundle-debug bundle-release icon run poc release headers-check
30 +
31 +build:
32 + swift build
33 +
34 +release-build:
35 + swift build -c release
36 +
37 +test:
38 + swift test
39 +
40 +clean:
41 + swift package clean
42 + rm -rf $(DIST_DIR)
43 +
44 +# ── App bundle assembly ──────────────────────────────────────────────────────
45 +# $(1) = built products dir (.build/debug or .build/release)
46 +define assemble_bundle
47 + rm -rf "$(APP_BUNDLE)"
48 + mkdir -p "$(APP_BUNDLE)/Contents/MacOS" "$(APP_BUNDLE)/Contents/Resources"
49 + cp "$(1)/$(EXEC_NAME)" "$(APP_BUNDLE)/Contents/MacOS/$(EXEC_NAME)"
50 + cp Support/Info.plist "$(APP_BUNDLE)/Contents/Info.plist"
51 + printf 'APPL????' > "$(APP_BUNDLE)/Contents/PkgInfo"
52 + @# MLX metallib + any other SPM resource bundles must ship in Resources
53 + for b in "$(1)"/*.bundle; do \
54 + [ -e "$$b" ] && cp -R "$$b" "$(APP_BUNDLE)/Contents/Resources/" || true; \
55 + done
56 + [ -f assets/icon/AppIcon.icns ] && cp assets/icon/AppIcon.icns "$(APP_BUNDLE)/Contents/Resources/AppIcon.icns" || true
57 +endef
58 +
59 +bundle-debug: build
60 + $(call assemble_bundle,$(BUILD_DIR)/debug)
61 + codesign --force --deep --sign - "$(APP_BUNDLE)"
62 +
63 +bundle-release: release-build
64 + $(call assemble_bundle,$(BUILD_DIR)/release)
65 +
66 +# Debug bundle, ad-hoc signed, launched.
67 +dev: bundle-debug
68 + open "$(APP_BUNDLE)"
69 +
70 +run: dev
71 +
72 +# Inference proof-of-concept: make poc MODEL=<dir> PROMPT="..."
73 +poc: build
74 + "$(DEBUG_BIN)" --poc "$(MODEL)" "$(PROMPT)"
75 +
76 +# Icon pipeline (Phase 5): SVG → PNGs → icns via scripts/make-icon.sh
77 +icon:
78 + scripts/make-icon.sh
79 +
80 +# Signed, notarized, stapled release (Phase 8): scripts/release.sh
81 +release:
82 + scripts/release.sh
83 +
84 +headers-check:
85 + scripts/check-headers.sh
added Package.swift +35 −0
@@ -0,0 +1,35 @@
1 +// swift-tools-version: 6.1
2 +//
3 +// Package.swift
4 +// Zyquo Local
5 +//
6 +// Author: Simon-Pierre Boucher
7 +// Mail: contact@spboucher.ai
8 +//
9 +
10 +import PackageDescription
11 +
12 +let package = Package(
13 + name: "ZyquoLocal",
14 + platforms: [.macOS(.v14)],
15 + dependencies: [
16 + .package(url: "https://github.com/ml-explore/mlx-swift-lm", .upToNextMajor(from: "3.31.4")),
17 + .package(url: "https://github.com/huggingface/swift-huggingface", from: "0.9.0"),
18 + .package(url: "https://github.com/huggingface/swift-transformers", from: "1.3.0"),
19 + .package(url: "https://github.com/swiftlang/swift-markdown", from: "0.5.0"),
20 + ],
21 + targets: [
22 + .executableTarget(
23 + name: "ZyquoLocal",
24 + dependencies: [
25 + .product(name: "MLXLLM", package: "mlx-swift-lm"),
26 + .product(name: "MLXLMCommon", package: "mlx-swift-lm"),
27 + .product(name: "MLXHuggingFace", package: "mlx-swift-lm"),
28 + .product(name: "HuggingFace", package: "swift-huggingface"),
29 + .product(name: "Tokenizers", package: "swift-transformers"),
30 + .product(name: "Markdown", package: "swift-markdown"),
31 + ],
32 + path: "Sources/ZyquoLocal"
33 + )
34 + ]
35 +)
added Sources/ZyquoLocal/App/ContentView.swift +22 −0
@@ -0,0 +1,22 @@
1 +//
2 +// ContentView.swift
3 +// Zyquo Local
4 +//
5 +// Author: Simon-Pierre Boucher
6 +// Mail: contact@spboucher.ai
7 +//
8 +
9 +import SwiftUI
10 +
11 +/// Placeholder root view — replaced by the full NavigationSplitView in Phase 6.
12 +struct ContentView: View {
13 + var body: some View {
14 + VStack(spacing: 12) {
15 + Text("Zyquo Local")
16 + .font(.system(size: 20, weight: .semibold))
17 + Text("Phase 1 skeleton — inference engine and UI arrive in later phases.")
18 + .foregroundStyle(.secondary)
19 + }
20 + .frame(maxWidth: .infinity, maxHeight: .infinity)
21 + }
22 +}
added Sources/ZyquoLocal/App/HardwareGate.swift +29 −0
@@ -0,0 +1,29 @@
1 +//
2 +// HardwareGate.swift
3 +// Zyquo Local
4 +//
5 +// Author: Simon-Pierre Boucher
6 +// Mail: contact@spboucher.ai
7 +//
8 +
9 +import Foundation
10 +
11 +/// MLX requires Apple Silicon. The binary is arm64-only, so this gate is a
12 +/// defensive runtime check (e.g. against a future universal build).
13 +enum HardwareGate {
14 + static var isAppleSilicon: Bool {
15 + #if arch(arm64)
16 + return true
17 + #else
18 + return false
19 + #endif
20 + }
21 +
22 + /// Physical unified memory in bytes (`sysctl hw.memsize`).
23 + static var physicalMemory: UInt64 {
24 + var size: UInt64 = 0
25 + var len = MemoryLayout<UInt64>.size
26 + sysctlbyname("hw.memsize", &size, &len, nil, 0)
27 + return size
28 + }
29 +}
added Sources/ZyquoLocal/App/Main.swift +24 −0
@@ -0,0 +1,24 @@
1 +//
2 +// Main.swift
3 +// Zyquo Local
4 +//
5 +// Author: Simon-Pierre Boucher
6 +// Mail: contact@spboucher.ai
7 +//
8 +
9 +import AppKit
10 +import SwiftUI
11 +
12 +/// Entry point. Dispatches to the CLI proof-of-concept / verification harness
13 +/// when invoked with flags, otherwise boots the SwiftUI application.
14 +@main
15 +enum Main {
16 + static func main() async {
17 + let args = CommandLine.arguments
18 + if args.contains("--poc") {
19 + await PoCRunner.run(arguments: args)
20 + return
21 + }
22 + ZyquoLocalApp.main()
23 + }
24 +}
added Sources/ZyquoLocal/App/PoCRunner.swift +18 −0
@@ -0,0 +1,18 @@
1 +//
2 +// PoCRunner.swift
3 +// Zyquo Local
4 +//
5 +// Author: Simon-Pierre Boucher
6 +// Mail: contact@spboucher.ai
7 +//
8 +
9 +import Foundation
10 +
11 +/// CLI proof-of-concept mode: `ZyquoLocal --poc <model-dir> "<prompt>"`.
12 +/// Fully implemented in Phase 2 alongside the inference engine.
13 +enum PoCRunner {
14 + static func run(arguments: [String]) async {
15 + FileHandle.standardError.write(Data("--poc arrives with the Phase 2 inference engine.\n".utf8))
16 + exit(64)
17 + }
18 +}
added Sources/ZyquoLocal/App/UnsupportedHardwareView.swift +28 −0
@@ -0,0 +1,28 @@
1 +//
2 +// UnsupportedHardwareView.swift
3 +// Zyquo Local
4 +//
5 +// Author: Simon-Pierre Boucher
6 +// Mail: contact@spboucher.ai
7 +//
8 +
9 +import SwiftUI
10 +
11 +/// Polite full-window notice shown on non-Apple-Silicon hardware.
12 +struct UnsupportedHardwareView: View {
13 + var body: some View {
14 + VStack(spacing: 16) {
15 + Image(systemName: "cpu")
16 + .font(.system(size: 56, weight: .light))
17 + .foregroundStyle(.secondary)
18 + Text("Apple Silicon Required")
19 + .font(.title2.weight(.semibold))
20 + Text("Zyquo Local runs language models on-device with MLX, which requires an Apple Silicon Mac (M1 or later). This Mac's processor is not supported.")
21 + .multilineTextAlignment(.center)
22 + .foregroundStyle(.secondary)
23 + .frame(maxWidth: 420)
24 + }
25 + .frame(minWidth: 640, minHeight: 420)
26 + .padding(40)
27 + }
28 +}
added Sources/ZyquoLocal/App/ZyquoLocalApp.swift +33 −0
@@ -0,0 +1,33 @@
1 +//
2 +// ZyquoLocalApp.swift
3 +// Zyquo Local
4 +//
5 +// Author: Simon-Pierre Boucher
6 +// Mail: contact@spboucher.ai
7 +//
8 +
9 +import SwiftUI
10 +
11 +struct ZyquoLocalApp: App {
12 + @NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
13 +
14 + var body: some Scene {
15 + WindowGroup {
16 + if HardwareGate.isAppleSilicon {
17 + ContentView()
18 + .frame(minWidth: 980, minHeight: 640)
19 + } else {
20 + UnsupportedHardwareView()
21 + }
22 + }
23 + .defaultSize(width: 1240, height: 800)
24 + }
25 +}
26 +
27 +final class AppDelegate: NSObject, NSApplicationDelegate {
28 + func applicationDidFinishLaunching(_ notification: Notification) {
29 + // Proper foreground activation when launched from a terminal.
30 + NSApp.setActivationPolicy(.regular)
31 + NSApp.activate(ignoringOtherApps: true)
32 + }
33 +}
added Support/Info.plist +49 −0
@@ -0,0 +1,49 @@
1 +<?xml version="1.0" encoding="UTF-8"?>
2 +<!--
3 + Info.plist
4 + Zyquo Local
5 +
6 + Author: Simon-Pierre Boucher
7 + Mail: contact@spboucher.ai
8 +-->
9 +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
10 +<plist version="1.0">
11 +<dict>
12 + <key>CFBundleDevelopmentRegion</key>
13 + <string>en</string>
14 + <key>CFBundleDisplayName</key>
15 + <string>Zyquo Local</string>
16 + <key>CFBundleName</key>
17 + <string>Zyquo Local</string>
18 + <key>CFBundleExecutable</key>
19 + <string>ZyquoLocal</string>
20 + <key>CFBundleIdentifier</key>
21 + <string>com.zyquo.local</string>
22 + <key>CFBundleInfoDictionaryVersion</key>
23 + <string>6.0</string>
24 + <key>CFBundlePackageType</key>
25 + <string>APPL</string>
26 + <key>CFBundleShortVersionString</key>
27 + <string>0.1.0</string>
28 + <key>CFBundleVersion</key>
29 + <string>1</string>
30 + <key>CFBundleIconFile</key>
31 + <string>AppIcon</string>
32 + <key>LSMinimumSystemVersion</key>
33 + <string>14.0</string>
34 + <key>LSApplicationCategoryType</key>
35 + <string>public.app-category.productivity</string>
36 + <key>LSArchitecturePriority</key>
37 + <array>
38 + <string>arm64</string>
39 + </array>
40 + <key>NSHighResolutionCapable</key>
41 + <true/>
42 + <key>NSHumanReadableCopyright</key>
43 + <string>© 2026 Simon-Pierre Boucher</string>
44 + <key>NSMainNibFile</key>
45 + <string></string>
46 + <key>NSPrincipalClass</key>
47 + <string>NSApplication</string>
48 +</dict>
49 +</plist>
modified docs/PLAN.md +14 −1
@@ -34,7 +34,20 @@ needs `SDKROOT` pinned to MacOSX26.5.sdk (macro-plugin issue on 27.x SDKs).
34 34 Downloads: custom URLSession manager (Range-resume verified 206); tokenization
35 35 via swift-transformers. 30-model catalog live-verified with real sizes.
36 36
37 ## Phase 1 — Project setup — pending
37 +## Phase 1 — Project setup — ✅ DONE (2026-07-30)
38 +
39 +- [x] Package.swift (tools 6.1, mlx-swift-lm 3.31.4 + swift-huggingface + swift-transformers + swift-markdown)
40 +- [x] Makefile (SDK pin + Metal PATH, bundle assembly incl. *.bundle resources, dev/release targets)
41 +- [x] Support/Info.plist per spec (com.zyquo.local, LSMinimumSystemVersion 14.0, arm64)
42 +- [x] @main entry with CLI dispatch (--poc) + SwiftUI app + Apple Silicon gate
43 +- [x] scripts/check-headers.sh sweep (green)
44 +- [x] `make build` green, zero warnings from our sources (68 s full graph)
45 +- [x] `make bundle-debug` produces an ad-hoc-signed arm64 bundle; CLI dispatch smoke-tested
46 +
47 +**Checkpoint:** Full dependency graph (2 040 tasks incl. Cmlx Metal kernels)
48 +compiles under the pinned recipe. Bundle carries mlx-swift_Cmlx.bundle (metallib),
49 +Hub + Crypto resource bundles. `codesign -dv`: com.zyquo.local, arm64, adhoc.
50 +Executable dispatches --poc and exits 64 as designed.
38 51 ## Phase 2 — Architecture + inference PoC — pending
39 52 ## Phase 3 — Hub browse & download — pending
40 53 ## Phase 4 — Design system & UI spec — pending
added scripts/check-headers.sh +30 −0
@@ -0,0 +1,30 @@
1 +#!/bin/bash
2 +#
3 +# check-headers.sh
4 +# Zyquo Local
5 +#
6 +# Author: Simon-Pierre Boucher
7 +# Mail: contact@spboucher.ai
8 +#
9 +# Verifies that every code file carries the mandatory Author/Mail header.
10 +
11 +set -euo pipefail
12 +cd "$(dirname "$0")/.."
13 +
14 +fail=0
15 +while IFS= read -r f; do
16 + if ! head -8 "$f" | grep -q "Author: Simon-Pierre Boucher"; then
17 + echo "MISSING HEADER: $f"
18 + fail=1
19 + fi
20 + if ! head -8 "$f" | grep -q "Mail: contact@spboucher.ai"; then
21 + echo "MISSING MAIL: $f"
22 + fail=1
23 + fi
24 +done < <(find Sources Support scripts assets -type f \( -name '*.swift' -o -name '*.sh' -o -name '*.plist' \) 2>/dev/null; ls Package.swift Makefile)
25 +
26 +if [ "$fail" -eq 0 ]; then
27 + echo "All code files carry the mandatory header."
28 +else
29 + exit 1
30 +fi
31