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%
7.9 KB · 232 lines swift
Raw Blame History
1//2//  ZyquoAgentApp.swift3//  Zyquo Agent4//5//  Author: Simon-Pierre Boucher6//  Mail: contact@spboucher.ai7//8//  The SwiftUI app shell: builds the shared environment (task store, run9//  hub, model catalog, key vault, appearance, agent settings, templates,10//  personas, UI signals), presents the command-center window (1320×86011//  default, 1040×680 min), the Settings scene (760×560, 7 tabs), the12//  toggleable menu bar extra with running-task status, the ⌥Space Quick Task13//  panel, and the full Phase 6 command set (⌘N, ⌘K, ⌘⇧A, ⌘⇧E, ⌥Space;14//  ⌘⏎/⌘./⌘F live in the detail/sidebar views).15//1617import SwiftUI1819/// Shared object graph for the app session.20@MainActor21final class AppEnvironment: ObservableObject {22    let tasks: TaskStore23    let hub: RunHub24    let catalog: ModelCatalog25    let vault: KeyVaultStore26    let appearance: AppearanceStore27    let settings: AgentSettingsStore28    let templates: TemplateStore29    let personas: PersonaStore30    let uiState: AppUIState3132    init() {33        let tasks = TaskStore()34        let settings = AgentSettingsStore()35        self.tasks = tasks36        self.settings = settings37        self.hub = RunHub(store: tasks, settings: settings)38        self.catalog = ModelCatalog()39        self.vault = KeyVaultStore()40        self.appearance = AppearanceStore()41        self.templates = TemplateStore()42        self.personas = PersonaStore()43        self.uiState = AppUIState()44    }4546    /// The model new tasks start with (persisted default, catalog fallback).47    var newTaskModel: AIModel? {48        settings.defaultAgentModel(in: catalog)49    }50}5152struct ZyquoAgentApp: App {53    @NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate54    @StateObject private var environment = AppEnvironment()55    @State private var quickTask: QuickTaskController?56    @AppStorage("menuBarExtraEnabled") private var menuBarExtraEnabled = true5758    var body: some Scene {59        WindowGroup("Zyquo Agent") {60            MainWindowView()61                .modifier(EnvironmentInjector(environment: environment))62                .frame(63                    minWidth: ZyquoMetrics.windowMinWidth,64                    minHeight: ZyquoMetrics.windowMinHeight65                )66                .onAppear {67                    if quickTask == nil {68                        quickTask = QuickTaskController(environment: environment)69                    }70                }71        }72        .defaultSize(73            width: ZyquoMetrics.windowDefaultWidth,74            height: ZyquoMetrics.windowDefaultHeight75        )76        .commands {77            AppCommands(environment: environment, quickTask: { quickTask })78        }7980        Settings {81            SettingsView()82                .modifier(EnvironmentInjector(environment: environment))83        }8485        MenuBarExtra(isInserted: $menuBarExtraEnabled) {86            MenuBarContent(store: environment.tasks, environment: environment, quickTask: { quickTask })87        } label: {88            MenuBarIconView()89        }90    }91}9293/// Injects the full shared object graph (all scenes get the same set).94private struct EnvironmentInjector: ViewModifier {95    let environment: AppEnvironment9697    func body(content: Content) -> some View {98        content99            .environmentObject(environment.tasks)100            .environmentObject(environment.hub)101            .environmentObject(environment.catalog)102            .environmentObject(environment.vault)103            .environmentObject(environment.appearance)104            .environmentObject(environment.settings)105            .environmentObject(environment.templates)106            .environmentObject(environment.personas)107            .environmentObject(environment.uiState)108    }109}110111// MARK: - Commands112113/// App-level menu commands and shortcuts (⌘N, ⌘K, ⌘⇧A, ⌘⇧E, Quick Task).114struct AppCommands: Commands {115    let environment: AppEnvironment116    var quickTask: () -> QuickTaskController?117118    var body: some Commands {119        CommandGroup(replacing: .newItem) {120            Button("New Task") {121                environment.tasks.newTask(122                    model: environment.newTaskModel,123                    safetyMode: environment.settings.settings.defaultSafetyMode124                )125            }126            .keyboardShortcut("n", modifiers: .command)127        }128        CommandMenu("Task") {129            Button("Command Palette") {130                environment.uiState.showCommandPalette.toggle()131            }132            .keyboardShortcut("k", modifiers: .command)133            Button("Quick Task") {134                quickTask()?.show()135            }136            Button("Browse Templates…") {137                environment.uiState.showTemplateBrowser = true138            }139            Divider()140            Button("Open Audit Log") {141                environment.uiState.requestAuditLog()142            }143            .keyboardShortcut("a", modifiers: [.command, .shift])144            Divider()145            Button("Export Transcript as Markdown…") {146                if let id = environment.tasks.selectedID, let task = environment.tasks.task(id: id) {147                    TaskTranscriptExporter.presentSavePanel(for: task, format: .markdown)148                }149            }150            .keyboardShortcut("e", modifiers: [.command, .shift])151            .disabled(environment.tasks.selectedID == nil)152            Button("Export Transcript as PDF…") {153                if let id = environment.tasks.selectedID, let task = environment.tasks.task(id: id) {154                    TaskTranscriptExporter.presentSavePanel(for: task, format: .pdf)155                }156            }157            .disabled(environment.tasks.selectedID == nil)158        }159    }160}161162// MARK: - Menu bar extra163164/// Menu bar glyph: the shipped template PNG when bundled, SF Symbol fallback165/// during `swift run` (no bundle resources).166struct MenuBarIconView: View {167    var body: some View {168        if let image = Self.templateImage() {169            Image(nsImage: image)170        } else {171            Image(systemName: "bolt.circle")172        }173    }174175    private static func templateImage() -> NSImage? {176        guard let path = Bundle.main.path(forResource: "MenuBarIcon", ofType: "png"),177              let image = NSImage(contentsOfFile: path) else { return nil }178        image.isTemplate = true179        image.size = NSSize(width: 18, height: 18)180        return image181    }182}183184/// Menu content: running-task status, New Task, Quick Task, open, quit.185struct MenuBarContent: View {186    @ObservedObject var store: TaskStore187    let environment: AppEnvironment188    var quickTask: () -> QuickTaskController?189190    var body: some View {191        let running = store.tasks.filter { $0.status.isActive }192        if running.isEmpty {193            Text("No running tasks")194        } else {195            Text("\(running.count) running task\(running.count == 1 ? "" : "s")")196            ForEach(running) { task in197                Button("\(task.title)\(task.status.displayName)") {198                    store.selectedID = task.id199                    NSApp.activate(ignoringOtherApps: true)200                }201            }202        }203        Divider()204        Button("New Task") {205            store.newTask(206                model: environment.newTaskModel,207                safetyMode: environment.settings.settings.defaultSafetyMode208            )209            NSApp.activate(ignoringOtherApps: true)210        }211        Button("Quick Task  ⌥Space") {212            quickTask()?.show()213        }214        Divider()215        Button("Open Zyquo Agent") {216            NSApp.activate(ignoringOtherApps: true)217        }218        Button("Quit") {219            NSApp.terminate(nil)220        }221    }222}223224final class AppDelegate: NSObject, NSApplicationDelegate {225    func applicationDidFinishLaunching(_ notification: Notification) {226        // Ensure the app fronts correctly when launched outside Finder227        // (e.g. `swift run` or `open` from a terminal during development).228        NSApp.setActivationPolicy(.regular)229        NSApp.activate(ignoringOtherApps: true)230    }231}232