phase1: SPM project, app shell, Makefile, Info.plist pipeline
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Showing 8 changed files with +329 and −0
added
Makefile
+91 −0
@@ -0,0 +1,91 @@ | ||
| 1 | +# | |
| 2 | +# Makefile | |
| 3 | +# Zyquo Agent | |
| 4 | +# | |
| 5 | +# Author: Simon-Pierre Boucher | |
| 6 | +# Mail: contact@spboucher.ai | |
| 7 | +# | |
| 8 | +# make / make dev → release build, assemble dist/Zyquo Agent.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-agent.svg (Phase 5) | |
| 11 | +# make test → swift test | |
| 12 | +# make clean → remove build products | |
| 13 | +# | |
| 14 | + | |
| 15 | +# CLT-only toolchain: SDK 27's SwiftUI declares @State & co. as macros whose | |
| 16 | +# plugin (libSwiftUIMacros.dylib) ships only with Xcode. SDK 26 keeps the | |
| 17 | +# property-wrapper forms, so all builds pin to it. | |
| 18 | +export SDKROOT := /Library/Developer/CommandLineTools/SDKs/MacOSX26.sdk | |
| 19 | + | |
| 20 | +APP_NAME := Zyquo Agent | |
| 21 | +EXEC_NAME := ZyquoAgent | |
| 22 | +BUNDLE_ID := com.zyquo.agent | |
| 23 | +VERSION := 1.0.0 | |
| 24 | +BUILD_NUM := 1 | |
| 25 | +MIN_MACOS := 13.0 | |
| 26 | +DIST := dist | |
| 27 | +APP_DIR := $(DIST)/$(APP_NAME).app | |
| 28 | +IDENTITY := Developer ID Application: Simon-Pierre Boucher (3YM54G49SN) | |
| 29 | +NOTARY_PROFILE:= MacLustr-Notarize | |
| 30 | +ENTITLEMENTS := Resources/ZyquoAgent.entitlements | |
| 31 | + | |
| 32 | +.PHONY: dev build bundle release universal icon test clean run | |
| 33 | + | |
| 34 | +dev: build bundle | |
| 35 | + @echo "=== Ad-hoc signing (dev) ===" | |
| 36 | + codesign --force --deep --sign - "$(APP_DIR)" | |
| 37 | + codesign --verify --deep --strict "$(APP_DIR)" | |
| 38 | + @echo "Packaged: $(APP_DIR) — launch with: open \"$(APP_DIR)\"" | |
| 39 | + | |
| 40 | +build: | |
| 41 | + swift build -c release | |
| 42 | + | |
| 43 | +test: | |
| 44 | + swift test | |
| 45 | + | |
| 46 | +run: dev | |
| 47 | + open "$(APP_DIR)" | |
| 48 | + | |
| 49 | +bundle: | |
| 50 | + @echo "=== Assembling $(APP_DIR) ===" | |
| 51 | + rm -rf "$(APP_DIR)" | |
| 52 | + mkdir -p "$(APP_DIR)/Contents/MacOS" "$(APP_DIR)/Contents/Resources" | |
| 53 | + cp .build/release/$(EXEC_NAME) "$(APP_DIR)/Contents/MacOS/$(EXEC_NAME)" | |
| 54 | + @for b in .build/release/*.bundle; do [ -e "$$b" ] && cp -R "$$b" "$(APP_DIR)/Contents/Resources/" || true; done | |
| 55 | + @if [ -f Resources/AppIcon.icns ]; then cp Resources/AppIcon.icns "$(APP_DIR)/Contents/Resources/AppIcon.icns"; fi | |
| 56 | + @for r in Resources/MenuBarIcon.png Resources/MenuBarIcon@2x.png; do [ -f "$$r" ] && cp "$$r" "$(APP_DIR)/Contents/Resources/" || true; done | |
| 57 | + scripts/write-info-plist.sh "$(APP_DIR)" "$(APP_NAME)" "$(EXEC_NAME)" "$(BUNDLE_ID)" "$(VERSION)" "$(BUILD_NUM)" "$(MIN_MACOS)" | |
| 58 | + | |
| 59 | +# Both arch builds land in .build/out/Products/Release with this build system, | |
| 60 | +# so each slice is copied aside before lipo. The -U flag allows the | |
| 61 | +# swiftCompatibility56 force-load symbol (CLT ships it arm64-only; it is never | |
| 62 | +# referenced at runtime on macOS 13+). | |
| 63 | +universal: | |
| 64 | + @echo "=== Building universal binary (arm64 + x86_64) ===" | |
| 65 | + mkdir -p .build/universal | |
| 66 | + swift build -c release --arch arm64 | |
| 67 | + cp .build/out/Products/Release/$(EXEC_NAME) .build/universal/$(EXEC_NAME).arm64 | |
| 68 | + swift build -c release --arch x86_64 -Xlinker -U -Xlinker '__swift_FORCE_LOAD_$$_swiftCompatibility56' | |
| 69 | + cp .build/out/Products/Release/$(EXEC_NAME) .build/universal/$(EXEC_NAME).x86_64 | |
| 70 | + lipo -create \ | |
| 71 | + .build/universal/$(EXEC_NAME).arm64 \ | |
| 72 | + .build/universal/$(EXEC_NAME).x86_64 \ | |
| 73 | + -output .build/universal/$(EXEC_NAME) | |
| 74 | + lipo -archs .build/universal/$(EXEC_NAME) | |
| 75 | + | |
| 76 | +release: universal | |
| 77 | + @echo "=== Assembling $(APP_DIR) (universal) ===" | |
| 78 | + rm -rf "$(APP_DIR)" | |
| 79 | + mkdir -p "$(APP_DIR)/Contents/MacOS" "$(APP_DIR)/Contents/Resources" | |
| 80 | + cp .build/universal/$(EXEC_NAME) "$(APP_DIR)/Contents/MacOS/$(EXEC_NAME)" | |
| 81 | + @for b in .build/out/Products/Release/*.bundle; do [ -e "$$b" ] && cp -R "$$b" "$(APP_DIR)/Contents/Resources/" || true; done | |
| 82 | + @if [ -f Resources/AppIcon.icns ]; then cp Resources/AppIcon.icns "$(APP_DIR)/Contents/Resources/AppIcon.icns"; fi | |
| 83 | + @for r in Resources/MenuBarIcon.png Resources/MenuBarIcon@2x.png; do [ -f "$$r" ] && cp "$$r" "$(APP_DIR)/Contents/Resources/" || true; done | |
| 84 | + scripts/write-info-plist.sh "$(APP_DIR)" "$(APP_NAME)" "$(EXEC_NAME)" "$(BUNDLE_ID)" "$(VERSION)" "$(BUILD_NUM)" "$(MIN_MACOS)" | |
| 85 | + scripts/notarize.sh "$(APP_DIR)" "$(IDENTITY)" "$(NOTARY_PROFILE)" "$(ENTITLEMENTS)" | |
| 86 | + | |
| 87 | +icon: | |
| 88 | + scripts/generate-icon.sh | |
| 89 | + | |
| 90 | +clean: | |
| 91 | + 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
+34 −0
@@ -0,0 +1,34 @@ | ||
| 1 | +// swift-tools-version:5.9 | |
| 2 | +// | |
| 3 | +// Package.swift | |
| 4 | +// Zyquo Agent | |
| 5 | +// | |
| 6 | +// Author: Simon-Pierre Boucher | |
| 7 | +// Mail: contact@spboucher.ai | |
| 8 | +// | |
| 9 | + | |
| 10 | +import PackageDescription | |
| 11 | + | |
| 12 | +let package = Package( | |
| 13 | + name: "ZyquoAgent", | |
| 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: "ZyquoAgent", | |
| 23 | + dependencies: [ | |
| 24 | + .product(name: "Markdown", package: "swift-markdown") | |
| 25 | + ], | |
| 26 | + path: "Sources/ZyquoAgent" | |
| 27 | + ), | |
| 28 | + .testTarget( | |
| 29 | + name: "ZyquoAgentTests", | |
| 30 | + dependencies: ["ZyquoAgent"], | |
| 31 | + path: "Tests/ZyquoAgentTests" | |
| 32 | + ), | |
| 33 | + ] | |
| 34 | +) | |
added
Sources/ZyquoAgent/App/AgentCLI.swift
+24 −0
@@ -0,0 +1,24 @@ | ||
| 1 | +// | |
| 2 | +// AgentCLI.swift | |
| 3 | +// Zyquo Agent | |
| 4 | +// | |
| 5 | +// Author: Simon-Pierre Boucher | |
| 6 | +// Mail: contact@spboucher.ai | |
| 7 | +// | |
| 8 | +// Headless command-line modes: `--run "<task>"` (agent loop POC, Phase 3) | |
| 9 | +// and `--verify` (provider tool-calling harness, Phase 7). Fleshed out in | |
| 10 | +// their respective phases; Phase 1 ships the scaffold so `Main` compiles. | |
| 11 | +// | |
| 12 | + | |
| 13 | +import Foundation | |
| 14 | + | |
| 15 | +enum AgentCLI { | |
| 16 | + static func run(arguments: [String]) async -> Int32 { | |
| 17 | + FileHandle.standardError.write(Data("Zyquo Agent CLI: engine not built yet (arrives in Phase 3/7). Arguments: \(arguments.dropFirst().joined(separator: " "))\n".utf8)) | |
| 18 | + return 64 | |
| 19 | + } | |
| 20 | + | |
| 21 | + static func loadVault() { | |
| 22 | + FileHandle.standardError.write(Data("Zyquo Agent CLI: vault loading arrives with the ported SecureKeyStore.\n".utf8)) | |
| 23 | + } | |
| 24 | +} | |
added
Sources/ZyquoAgent/App/Main.swift
+39 −0
@@ -0,0 +1,39 @@ | ||
| 1 | +// | |
| 2 | +// Main.swift | |
| 3 | +// Zyquo Agent | |
| 4 | +// | |
| 5 | +// Author: Simon-Pierre Boucher | |
| 6 | +// Mail: contact@spboucher.ai | |
| 7 | +// | |
| 8 | +// Entry point. `--run "<task>"` executes a headless agent task (Phase 3 CLI | |
| 9 | +// POC), `--verify` runs the provider tool-calling harness (Phase 7), | |
| 10 | +// `--load-vault` seeds the encrypted vault from environment keys; otherwise | |
| 11 | +// the SwiftUI app launches. | |
| 12 | +// | |
| 13 | +// Deliberately a synchronous main: launching NSApplicationMain from an async | |
| 14 | +// main() corrupts Swift concurrency's executor setup (background tasks stop | |
| 15 | +// being scheduled), so the CLI modes drive their async work explicitly. | |
| 16 | +// | |
| 17 | + | |
| 18 | +import Foundation | |
| 19 | + | |
| 20 | +@main | |
| 21 | +enum Main { | |
| 22 | + static func main() { | |
| 23 | + let arguments = CommandLine.arguments | |
| 24 | + if arguments.contains("--run") || arguments.contains("--verify") { | |
| 25 | + Task.detached { | |
| 26 | + let status = await AgentCLI.run(arguments: arguments) | |
| 27 | + exit(status) | |
| 28 | + } | |
| 29 | + // Park the main thread servicing the main queue so MainActor work | |
| 30 | + // can run (a blocking semaphore here deadlocks the harness). | |
| 31 | + dispatchMain() | |
| 32 | + } | |
| 33 | + if arguments.contains("--load-vault") { | |
| 34 | + AgentCLI.loadVault() | |
| 35 | + exit(0) | |
| 36 | + } | |
| 37 | + ZyquoAgentApp.main() | |
| 38 | + } | |
| 39 | +} | |
added
Sources/ZyquoAgent/App/ZyquoAgentApp.swift
+44 −0
@@ -0,0 +1,44 @@ | ||
| 1 | +// | |
| 2 | +// ZyquoAgentApp.swift | |
| 3 | +// Zyquo Agent | |
| 4 | +// | |
| 5 | +// Author: Simon-Pierre Boucher | |
| 6 | +// Mail: contact@spboucher.ai | |
| 7 | +// | |
| 8 | + | |
| 9 | +import SwiftUI | |
| 10 | + | |
| 11 | +struct ZyquoAgentApp: App { | |
| 12 | + @NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate | |
| 13 | + | |
| 14 | + var body: some Scene { | |
| 15 | + WindowGroup("Zyquo Agent") { | |
| 16 | + PlaceholderRootView() | |
| 17 | + .frame(minWidth: 1040, minHeight: 680) | |
| 18 | + } | |
| 19 | + .defaultSize(width: 1320, height: 860) | |
| 20 | + } | |
| 21 | +} | |
| 22 | + | |
| 23 | +/// Phase 1 stand-in for the command-center window; replaced by the real | |
| 24 | +/// MainWindowView in Phase 6. | |
| 25 | +struct PlaceholderRootView: View { | |
| 26 | + var body: some View { | |
| 27 | + VStack(spacing: 12) { | |
| 28 | + Text("Zyquo Agent") | |
| 29 | + .font(.largeTitle.weight(.semibold)) | |
| 30 | + Text("The agent engine is under construction (Phase 3).") | |
| 31 | + .foregroundStyle(.secondary) | |
| 32 | + } | |
| 33 | + .frame(maxWidth: .infinity, maxHeight: .infinity) | |
| 34 | + } | |
| 35 | +} | |
| 36 | + | |
| 37 | +final class AppDelegate: NSObject, NSApplicationDelegate { | |
| 38 | + func applicationDidFinishLaunching(_ notification: Notification) { | |
| 39 | + // Ensure the app fronts correctly when launched outside Finder | |
| 40 | + // (e.g. `swift run` or `open` from a terminal during development). | |
| 41 | + NSApp.setActivationPolicy(.regular) | |
| 42 | + NSApp.activate(ignoringOtherApps: true) | |
| 43 | + } | |
| 44 | +} | |
added
Tests/ZyquoAgentTests/ZyquoAgentTests.swift
+15 −0
@@ -0,0 +1,15 @@ | ||
| 1 | +// | |
| 2 | +// ZyquoAgentTests.swift | |
| 3 | +// Zyquo Agent | |
| 4 | +// | |
| 5 | +// Author: Simon-Pierre Boucher | |
| 6 | +// Mail: contact@spboucher.ai | |
| 7 | +// | |
| 8 | + | |
| 9 | +import XCTest | |
| 10 | + | |
| 11 | +final class ZyquoAgentTests: XCTestCase { | |
| 12 | + func testScaffold() { | |
| 13 | + XCTAssertTrue(true) | |
| 14 | + } | |
| 15 | +} | |
added
scripts/write-info-plist.sh
+59 −0
@@ -0,0 +1,59 @@ | ||
| 1 | +#!/bin/bash | |
| 2 | +# | |
| 3 | +# write-info-plist.sh | |
| 4 | +# Zyquo Agent | |
| 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>NSAppleEventsUsageDescription</key> | |
| 48 | + <string>Zyquo Agent runs AppleScript on your behalf to automate Mac apps (Finder, Notes, Mail, Calendar…). Every script is shown to you and passes the safety policy before it runs.</string> | |
| 49 | + <key>NSHumanReadableCopyright</key> | |
| 50 | + <string>© 2026 Simon-Pierre Boucher. All rights reserved.</string> | |
| 51 | + <key>NSSupportsAutomaticTermination</key> | |
| 52 | + <false/> | |
| 53 | + <key>NSSupportsSuddenTermination</key> | |
| 54 | + <false/> | |
| 55 | +</dict> | |
| 56 | +</plist> | |
| 57 | +PLIST | |
| 58 | + | |
| 59 | +echo "Info.plist written to $APP_DIR/Contents/Info.plist" | |
| 60 | ||