phase1: SPM package, minimal SwiftUI app, Makefile + bundle assembly (make dev verified)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
8 changed files +277 −5
added
Makefile
+81 −0
@@ -0,0 +1,81 @@ | |||||
| 1 | +# | ||||
| 2 | +# Makefile | ||||
| 3 | +# Zyquo Cloud | ||||
| 4 | +# | ||||
| 5 | +# Author: Simon-Pierre Boucher | ||||
| 6 | +# Mail: contact@spboucher.ai | ||||
| 7 | +# | ||||
| 8 | +# make / make dev → release build, assemble dist/Zyquo Cloud.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-cloud.svg (Phase 5) | ||||
| 11 | +# make test → swift test | ||||
| 12 | +# make clean → remove build products | ||||
| 13 | +# | ||||
| 14 | + | ||||
| 15 | +APP_NAME := Zyquo Cloud | ||||
| 16 | +EXEC_NAME := ZyquoCloud | ||||
| 17 | +BUNDLE_ID := com.zyquo.cloud | ||||
| 18 | +VERSION := 1.0.0 | ||||
| 19 | +BUILD_NUM := 1 | ||||
| 20 | +MIN_MACOS := 13.0 | ||||
| 21 | +DIST := dist | ||||
| 22 | +APP_DIR := $(DIST)/$(APP_NAME).app | ||||
| 23 | +IDENTITY := Developer ID Application: Simon-Pierre Boucher (3YM54G49SN) | ||||
| 24 | +NOTARY_PROFILE:= MacLustr-Notarize | ||||
| 25 | +ENTITLEMENTS := Resources/ZyquoCloud.entitlements | ||||
| 26 | + | ||||
| 27 | +.PHONY: dev build bundle release universal icon test clean run verify | ||||
| 28 | + | ||||
| 29 | +dev: build bundle | ||||
| 30 | + @echo "=== Ad-hoc signing (dev) ===" | ||||
| 31 | + codesign --force --deep --sign - "$(APP_DIR)" | ||||
| 32 | + codesign --verify --deep --strict "$(APP_DIR)" | ||||
| 33 | + @echo "Packaged: $(APP_DIR) — launch with: open \"$(APP_DIR)\"" | ||||
| 34 | + | ||||
| 35 | +build: | ||||
| 36 | + swift build -c release | ||||
| 37 | + | ||||
| 38 | +test: | ||||
| 39 | + swift test | ||||
| 40 | + | ||||
| 41 | +run: dev | ||||
| 42 | + open "$(APP_DIR)" | ||||
| 43 | + | ||||
| 44 | +bundle: | ||||
| 45 | + @echo "=== Assembling $(APP_DIR) ===" | ||||
| 46 | + rm -rf "$(APP_DIR)" | ||||
| 47 | + mkdir -p "$(APP_DIR)/Contents/MacOS" "$(APP_DIR)/Contents/Resources" | ||||
| 48 | + cp .build/release/$(EXEC_NAME) "$(APP_DIR)/Contents/MacOS/$(EXEC_NAME)" | ||||
| 49 | + @for b in .build/release/*.bundle; do [ -e "$$b" ] && cp -R "$$b" "$(APP_DIR)/Contents/Resources/" || true; done | ||||
| 50 | + @if [ -f Resources/AppIcon.icns ]; then cp Resources/AppIcon.icns "$(APP_DIR)/Contents/Resources/AppIcon.icns"; fi | ||||
| 51 | + scripts/write-info-plist.sh "$(APP_DIR)" "$(APP_NAME)" "$(EXEC_NAME)" "$(BUNDLE_ID)" "$(VERSION)" "$(BUILD_NUM)" "$(MIN_MACOS)" | ||||
| 52 | + | ||||
| 53 | +universal: | ||||
| 54 | + @echo "=== Building universal binary (arm64 + x86_64) ===" | ||||
| 55 | + swift build -c release --arch arm64 | ||||
| 56 | + swift build -c release --arch x86_64 | ||||
| 57 | + mkdir -p .build/universal | ||||
| 58 | + lipo -create \ | ||||
| 59 | + .build/arm64-apple-macosx/release/$(EXEC_NAME) \ | ||||
| 60 | + .build/x86_64-apple-macosx/release/$(EXEC_NAME) \ | ||||
| 61 | + -output .build/universal/$(EXEC_NAME) | ||||
| 62 | + | ||||
| 63 | +release: universal | ||||
| 64 | + @echo "=== Assembling $(APP_DIR) (universal) ===" | ||||
| 65 | + rm -rf "$(APP_DIR)" | ||||
| 66 | + mkdir -p "$(APP_DIR)/Contents/MacOS" "$(APP_DIR)/Contents/Resources" | ||||
| 67 | + cp .build/universal/$(EXEC_NAME) "$(APP_DIR)/Contents/MacOS/$(EXEC_NAME)" | ||||
| 68 | + @for b in .build/arm64-apple-macosx/release/*.bundle; do [ -e "$$b" ] && cp -R "$$b" "$(APP_DIR)/Contents/Resources/" || true; done | ||||
| 69 | + @if [ -f Resources/AppIcon.icns ]; then cp Resources/AppIcon.icns "$(APP_DIR)/Contents/Resources/AppIcon.icns"; fi | ||||
| 70 | + scripts/write-info-plist.sh "$(APP_DIR)" "$(APP_NAME)" "$(EXEC_NAME)" "$(BUNDLE_ID)" "$(VERSION)" "$(BUILD_NUM)" "$(MIN_MACOS)" | ||||
| 71 | + scripts/notarize.sh "$(APP_DIR)" "$(IDENTITY)" "$(NOTARY_PROFILE)" "$(ENTITLEMENTS)" | ||||
| 72 | + | ||||
| 73 | +icon: | ||||
| 74 | + scripts/generate-icon.sh | ||||
| 75 | + | ||||
| 76 | +verify: | ||||
| 77 | + swift build -c release | ||||
| 78 | + .build/release/zyquo-verify | ||||
| 79 | + | ||||
| 80 | +clean: | ||||
| 81 | + rm -rf .build $(DIST) | ||||
added
Package.resolved
+23 −0
@@ -0,0 +1,23 @@ | |||||
| 1 | +{ | ||||
| 2 | + "pins" : [ | ||||
| 3 | + { | ||||
| 4 | + "identity" : "swift-cmark", | ||||
| 5 | + "kind" : "remoteSourceControl", | ||||
| 6 | + "location" : "https://github.com/swiftlang/swift-cmark.git", | ||||
| 7 | + "state" : { | ||||
| 8 | + "revision" : "924936d0427cb25a61169739a7660230bffa6ea6", | ||||
| 9 | + "version" : "0.8.0" | ||||
| 10 | + } | ||||
| 11 | + }, | ||||
| 12 | + { | ||||
| 13 | + "identity" : "swift-markdown", | ||||
| 14 | + "kind" : "remoteSourceControl", | ||||
| 15 | + "location" : "https://github.com/swiftlang/swift-markdown.git", | ||||
| 16 | + "state" : { | ||||
| 17 | + "revision" : "3c6f9523da3a1ec2fd829673e472d95b8097a3b8", | ||||
| 18 | + "version" : "0.8.0" | ||||
| 19 | + } | ||||
| 20 | + } | ||||
| 21 | + ], | ||||
| 22 | + "version" : 2 | ||||
| 23 | +} | ||||
added
Package.swift
+39 −0
@@ -0,0 +1,39 @@ | |||||
| 1 | +// swift-tools-version:5.9 | ||||
| 2 | +// | ||||
| 3 | +// Package.swift | ||||
| 4 | +// Zyquo Cloud | ||||
| 5 | +// | ||||
| 6 | +// Author: Simon-Pierre Boucher | ||||
| 7 | +// Mail: contact@spboucher.ai | ||||
| 8 | +// | ||||
| 9 | + | ||||
| 10 | +import PackageDescription | ||||
| 11 | + | ||||
| 12 | +let package = Package( | ||||
| 13 | + name: "ZyquoCloud", | ||||
| 14 | + platforms: [ | ||||
| 15 | + .macOS(.v13) | ||||
| 16 | + ], | ||||
| 17 | + dependencies: [ | ||||
| 18 | + .package(url: "https://github.com/swiftlang/swift-markdown.git", from: "0.5.0") | ||||
| 19 | + ], | ||||
| 20 | + targets: [ | ||||
| 21 | + .executableTarget( | ||||
| 22 | + name: "ZyquoCloud", | ||||
| 23 | + dependencies: [ | ||||
| 24 | + .product(name: "Markdown", package: "swift-markdown") | ||||
| 25 | + ], | ||||
| 26 | + path: "Sources/ZyquoCloud" | ||||
| 27 | + ), | ||||
| 28 | + .executableTarget( | ||||
| 29 | + name: "zyquo-verify", | ||||
| 30 | + dependencies: [], | ||||
| 31 | + path: "Sources/ZyquoVerify" | ||||
| 32 | + ), | ||||
| 33 | + .testTarget( | ||||
| 34 | + name: "ZyquoCloudTests", | ||||
| 35 | + dependencies: ["ZyquoCloud"], | ||||
| 36 | + path: "Tests/ZyquoCloudTests" | ||||
| 37 | + ), | ||||
| 38 | + ] | ||||
| 39 | +) | ||||
added
Sources/ZyquoCloud/App/ZyquoCloudApp.swift
+31 −0
@@ -0,0 +1,31 @@ | |||||
| 1 | +// | ||||
| 2 | +// ZyquoCloudApp.swift | ||||
| 3 | +// Zyquo Cloud | ||||
| 4 | +// | ||||
| 5 | +// Author: Simon-Pierre Boucher | ||||
| 6 | +// Mail: contact@spboucher.ai | ||||
| 7 | +// | ||||
| 8 | + | ||||
| 9 | +import SwiftUI | ||||
| 10 | + | ||||
| 11 | +@main | ||||
| 12 | +struct ZyquoCloudApp: App { | ||||
| 13 | + @NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate | ||||
| 14 | + | ||||
| 15 | + var body: some Scene { | ||||
| 16 | + WindowGroup("Zyquo Cloud") { | ||||
| 17 | + Text("Zyquo Cloud") | ||||
| 18 | + .frame(minWidth: 980, minHeight: 640) | ||||
| 19 | + } | ||||
| 20 | + .defaultSize(width: 1240, height: 800) | ||||
| 21 | + } | ||||
| 22 | +} | ||||
| 23 | + | ||||
| 24 | +final class AppDelegate: NSObject, NSApplicationDelegate { | ||||
| 25 | + func applicationDidFinishLaunching(_ notification: Notification) { | ||||
| 26 | + // Ensure the app fronts correctly when launched outside Finder | ||||
| 27 | + // (e.g. `swift run` or `open` from a terminal during development). | ||||
| 28 | + NSApplication.shared.setActivationPolicy(.regular) | ||||
| 29 | + NSApplication.shared.activate(ignoringOtherApps: true) | ||||
| 30 | + } | ||||
| 31 | +} | ||||
added
Sources/ZyquoVerify/main.swift
+11 −0
@@ -0,0 +1,11 @@ | |||||
| 1 | +// | ||||
| 2 | +// main.swift | ||||
| 3 | +// Zyquo Cloud | ||||
| 4 | +// | ||||
| 5 | +// Author: Simon-Pierre Boucher | ||||
| 6 | +// Mail: contact@spboucher.ai | ||||
| 7 | +// | ||||
| 8 | +// zyquo-verify — Phase 7 API verification harness (implemented in Phase 7). | ||||
| 9 | +// | ||||
| 10 | + | ||||
| 11 | +print("zyquo-verify: harness not yet implemented (Phase 7)") | ||||
added
Tests/ZyquoCloudTests/ZyquoCloudTests.swift
+15 −0
@@ -0,0 +1,15 @@ | |||||
| 1 | +// | ||||
| 2 | +// ZyquoCloudTests.swift | ||||
| 3 | +// Zyquo Cloud | ||||
| 4 | +// | ||||
| 5 | +// Author: Simon-Pierre Boucher | ||||
| 6 | +// Mail: contact@spboucher.ai | ||||
| 7 | +// | ||||
| 8 | + | ||||
| 9 | +import XCTest | ||||
| 10 | + | ||||
| 11 | +final class ZyquoCloudTests: XCTestCase { | ||||
| 12 | + func testPlaceholder() { | ||||
| 13 | + XCTAssertTrue(true) | ||||
| 14 | + } | ||||
| 15 | +} | ||||
modified
docs/PLAN.md
+20 −5
@@ -16,12 +16,27 @@ Nemo 2026-07-31 (reasoning via `reasoning_effort`); xAI catalog now grok-4.5/4.3 | |||||
| 16 | down to 3 models; Perplexity has no `/models` (built-in catalog: sonar, sonar-pro, sonar-reasoning-pro, | 16 | down to 3 models; Perplexity has no `/models` (built-in catalog: sonar, sonar-pro, sonar-reasoning-pro, | ||
| 17 | sonar-deep-research). Items unverifiable from docs are flagged inline and queued for Phase 7. | 17 | sonar-deep-research). Items unverifiable from docs are flagged inline and queued for Phase 7. | ||
| 18 | 18 | ||||
| 19 | −## Phase 1 — Project Setup (in progress) | 19 | +## Phase 1 — Project Setup ✅ (completed 2026-07-30) | ||
| 20 | 20 | ||||
| 21 | −- [ ] `Package.swift` — executable target `ZyquoCloud`, macOS 13+, swift-markdown dep | 21 | +- [x] `Package.swift` — executable targets `ZyquoCloud` + `zyquo-verify`, test target, macOS 13+, swift-markdown dep | ||
| 22 | −- [ ] Minimal `@main` SwiftUI app that builds and launches | 22 | +- [x] Minimal `@main` SwiftUI app that builds and launches (activation policy handled) | ||
| 23 | −- [ ] `Makefile`: `make dev` (build + assemble `Zyquo Cloud.app` + ad-hoc sign), Info.plist per spec | 23 | +- [x] `Makefile`: `make dev` (release build + assemble `Zyquo Cloud.app` + ad-hoc sign), `make release` | ||
| 24 | −- [ ] Phase checkpoint: `swift build` clean, app launches from Finder/`open` | 24 | + (universal + Developer ID + notarize skeleton, completed in Phase 8), `scripts/write-info-plist.sh` | ||
| 25 | +- [x] Phase checkpoint: `swift build` clean (0 warnings), `make dev` assembles bundle, app launched via | ||||
| 26 | + `open`, process verified, quit cleanly | ||||
| 27 | + | ||||
| 28 | +**Phase 1 checkpoint summary:** SPM-only toolchain working on Swift 6.4. `make dev` produces an | ||||
| 29 | +ad-hoc-signed `dist/Zyquo Cloud.app` with correct Info.plist (`com.zyquo.cloud`, macOS 13+, | ||||
| 30 | +productivity category). Release/notarization targets stubbed to `scripts/notarize.sh` using the | ||||
| 31 | +verified zyquo-term identity + `MacLustr-Notarize` profile (implemented fully in Phase 8). | ||||
| 32 | + | ||||
| 33 | +## Phase 2 — Architecture (in progress) | ||||
| 34 | + | ||||
| 35 | +- [ ] Models: `ProviderID`, `AIModel`, `ModelCapabilities`, `ModelPricing`, `ParameterSupport`, `Message`, `Conversation`, `Persona`, `ChatParameters`, `TokenUsage` | ||||
| 36 | +- [ ] Providers: `ProviderProtocol` (`ChatRequest` → `AsyncThrowingStream<ChatEvent>`), `OpenAICompatibleClient` (10+ providers incl. Gemini-compat), `AnthropicClient` | ||||
| 37 | +- [ ] Services: `StreamingService` (SSE parser), `ModelCatalog` (from PROVIDERS.md), `PersistenceService` (JSON in Application Support) | ||||
| 38 | +- [ ] ViewModels/Views skeleton folders | ||||
| 39 | +- [ ] Phase checkpoint: builds clean, unit tests for SSE parser + models | ||||
| 25 | ## Phase 2 — Architecture (pending) | 40 | ## Phase 2 — Architecture (pending) | ||
| 26 | ## Phase 3 — SecureKeyStore (pending) | 41 | ## Phase 3 — SecureKeyStore (pending) | ||
| 27 | ## Phase 4 — Design System (pending) | 42 | ## Phase 4 — Design System (pending) | ||
added
scripts/write-info-plist.sh
+57 −0
@@ -0,0 +1,57 @@ | |||||
| 1 | +#!/bin/bash | ||||
| 2 | +# | ||||
| 3 | +# write-info-plist.sh | ||||
| 4 | +# Zyquo Cloud | ||||
| 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.productivity</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 | |||||