// // ZyquoRouterApp.swift // Zyquo Router // // Author: Simon-Pierre Boucher // Mail: contact@spboucher.ai // import SwiftUI struct ZyquoRouterApp: App { @NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate @StateObject private var environment = AppEnvironment() @AppStorage("autoStartServer") private var autoStart = false @AppStorage("menuBarExtraEnabled") private var menuBarExtraEnabled = true var body: some Scene { WindowGroup("Zyquo Router") { MainWindowView() .environmentObject(environment.server) .environmentObject(environment.catalog) .environmentObject(environment.appearance) .environmentObject(environment.vault) .environmentObject(environment.localKeys) .environmentObject(environment.routerConfig) .onAppear { if autoStart, !environment.server.isRunning { environment.server.start() } } } .defaultSize( width: ZyquoMetrics.windowDefaultWidth, height: ZyquoMetrics.windowDefaultHeight ) .commands { AppCommands(server: environment.server) } Settings { SettingsView() .environmentObject(environment.server) .environmentObject(environment.catalog) .environmentObject(environment.appearance) .environmentObject(environment.vault) .environmentObject(environment.localKeys) .environmentObject(environment.routerConfig) } MenuBarExtra(isInserted: $menuBarExtraEnabled) { MenuBarContent() .environmentObject(environment.server) } label: { Image(nsImage: Self.menuBarImage) } } /// Template glyph rendered by scripts/generate-icon.sh; falls back to an /// SF Symbol during `swift run` (no bundle resources). private static let menuBarImage: NSImage = { if let url = Bundle.main.url(forResource: "MenuBarIcon", withExtension: "png"), let image = NSImage(contentsOf: url) { image.isTemplate = true image.size = NSSize(width: 18, height: 18) return image } let fallback = NSImage( systemSymbolName: "point.3.connected.trianglepath.dotted", accessibilityDescription: "Zyquo Router" ) ?? NSImage() fallback.isTemplate = true return fallback }() } /// Menu bar extra: status, Start/Stop, live numbers, copy endpoint. private struct MenuBarContent: View { @EnvironmentObject private var server: ServerController @State private var summary = "" var body: some View { Group { Text(statusLine) if !summary.isEmpty { Text(summary) } Divider() Button(server.isRunning ? "Stop Server" : "Start Server") { server.toggle() } .keyboardShortcut("r") Button("Copy Endpoint URL") { NSPasteboard.general.clearContents() NSPasteboard.general.setString(server.endpointURL, forType: .string) } Divider() Button("Open Zyquo Router") { NSApp.activate(ignoringOtherApps: true) } Button("Quit") { NSApp.terminate(nil) } } .task { while !Task.isCancelled { await refreshSummary() try? await Task.sleep(nanoseconds: 5_000_000_000) } } } private var statusLine: String { switch server.state { case .running(let port): return "● Running on :\(port)" case .starting: return "◐ Starting…" case .failed: return "○ Failed to start" case .stopped: return "○ Stopped" } } private func refreshSummary() async { let meter = server.usageMeter let today = await meter.totals(since: Calendar.current.startOfDay(for: Date())) let lastMinute = await meter.requestsPerMinute(minutes: 1).first ?? 0 summary = String(format: "%d req/min · $%.2f today", lastMinute, today.cost) } } /// App-level menu commands: ⌘1–6 sections, ⌘⇧C copy endpoint. /// (⌘R start/stop lives on the dashboard's Start button.) struct AppCommands: Commands { let server: ServerController var body: some Commands { CommandMenu("Server") { Button(server.isRunning ? "Stop Server" : "Start Server") { server.toggle() } .keyboardShortcut("r", modifiers: .command) } CommandMenu("Go") { ForEach(Array(AppSection.allCases.enumerated()), id: \.element.id) { index, section in Button(section.rawValue) { UserDefaults.standard.set(section.rawValue, forKey: "selectedSection") } .keyboardShortcut(KeyEquivalent(Character("\(index + 1)")), modifiers: .command) } } CommandGroup(after: .pasteboard) { Button("Copy Endpoint URL") { NSPasteboard.general.clearContents() NSPasteboard.general.setString(server.endpointURL, forType: .string) } .keyboardShortcut("c", modifiers: [.command, .shift]) } } } final class AppDelegate: NSObject, NSApplicationDelegate { func applicationDidFinishLaunching(_ notification: Notification) { // Ensure the app fronts correctly when launched outside Finder // (e.g. `swift run` or `open` from a terminal during development). NSApplication.shared.setActivationPolicy(.regular) NSApplication.shared.activate(ignoringOtherApps: true) } func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { // "Keep serving when window closed" — the server survives by default. !UserDefaults.standard.bool(forKey: "keepServingWhenClosed") } }