SPB Git

spb/zyquo-agent Public MIT

The autonomous agent that actually operates your Mac — plans, runs real commands, verifies its own work.

Swift 94.7% Shell 4.1% Python 0.7% Makefile 0.5%

phase1: SPM project, app shell, Makefile, Info.plist pipeline

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

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