spb/zyquo-atlas Public License
The AI-native macOS web browser — every surface, intelligent.
Swift 75.2%
JavaScript 22%
Shell 2%
Makefile 0.9%
1//2// SelfTest.swift3// Zyquo Atlas4//5// Author: Simon-Pierre Boucher6// Mail: contact@spboucher.ai7//8// Headless end-to-end check of the Phase 3 gate: load a real page in an9// offscreen WKWebView, extract a PageContext, and stream a grounded summary10// with a live Cloud model. Proves ContentExtractor + Summarizer + AIService +11// the ported provider client work together. Invoked via12// `ZyquoAtlas --selftest-summarize <url> [--provider <id>] [--model <id>]`;13// the API key comes from the environment (never logged).14//1516import Foundation17import WebKit18import AppKit1920@MainActor21enum SelfTest {22 private static let envKeys: [ProviderID: String] = [23 .openai: "OPENAI_API_KEY", .anthropic: "ANTHROPIC_API_KEY", .xai: "XAI_API_KEY",24 .mistral: "MISTRAL_API_KEY", .gemini: "GEMINI_API_KEY", .qwen: "DASHSCOPE_API_KEY",25 .deepseek: "DEEPSEEK_API_KEY", .kimi: "MOONSHOT_API_KEY", .perplexity: "PERPLEXITY_API_KEY",26 .together: "TOGETHER_API_KEY", .deepinfra: "DEEPINFRA_API_KEY", .cerebras: "CEREBRAS_API_KEY",27 ]2829 static func summarize(arguments: [String]) async -> Int32 {30 guard let urlString = value(after: "--selftest-summarize", in: arguments),31 let url = URL(string: urlString) else {32 printErr("usage: --selftest-summarize <url> [--provider <id>] [--model <id>]")33 return 234 }3536 let catalog = ModelCatalog()37 let providerArg = value(after: "--provider", in: arguments).flatMap { ProviderID(rawValue: $0) }38 let modelArg = value(after: "--model", in: arguments)3940 let model: AIModel41 if let id = modelArg, let provider = providerArg, let m = catalog.model(id: id, provider: provider) {42 model = m43 } else if let provider = providerArg, let m = catalog.cheapestModel(for: provider) {44 model = m45 } else if let m = catalog.defaultModel {46 model = m47 } else {48 printErr("no model available in catalog"); return 149 }5051 guard let keyName = envKeys[model.provider],52 let apiKey = ProcessInfo.processInfo.environment[keyName], !apiKey.isEmpty else {53 printErr("no API key in env for \(model.provider.rawValue) (expected \(envKeys[model.provider] ?? "?"))")54 return 155 }5657 print("→ loading \(url.absoluteString)")58 let config = ProfileStore.shared.makeConfiguration(for: .defaultProfile)59 let loader = PageLoader(configuration: config)60 do {61 try await loader.load(url)62 } catch {63 printErr("page load failed: \(error.localizedDescription)"); return 164 }65 // Let late/lazy content settle before extracting.66 try? await Task.sleep(nanoseconds: 1_500_000_000)6768 let context: PageContext69 do {70 context = try await ContentExtractor.extract(from: loader.webView)71 } catch {72 printErr("extraction failed: \(error.localizedDescription)"); return 173 }7475 print("✓ extracted: \"\(context.title)\" — quality=\(context.quality.rawValue), "76 + "words=\(context.wordCount), ~\(context.estimatedTokens) tokens, "77 + "headings=\(context.headings.count), truncated=\(context.truncated)")78 let plan = Summarizer.plan(for: context, model: model)79 print("✓ model=\(model.id) (\(model.provider.rawValue)), plan=\(plan)")80 print("---- extracted markdown (first 400 chars) ----")81 print(String(context.markdown.prefix(400)))82 print("---- streaming summary ----")8384 let service = AIService()85 service.run(.summarize, on: context, model: model, apiKey: apiKey)8687 // Drain the stream, printing new text as it arrives.88 var printed = 089 while service.isStreaming || printed < service.output.count {90 let current = service.output91 if current.count > printed {92 let start = current.index(current.startIndex, offsetBy: printed)93 FileHandle.standardOutput.write(Data(String(current[start...]).utf8))94 printed = current.count95 }96 if let err = service.errorText { printErr("\n\nAI error: \(err)"); return 1 }97 if !service.isStreaming && printed >= service.output.count { break }98 try? await Task.sleep(nanoseconds: 80_000_000)99 }100 print("\n---- end ----")101 print("✓ summary chars=\(service.output.count)")102 return service.output.isEmpty ? 1 : 0103 }104105 private static func value(after flag: String, in args: [String]) -> String? {106 guard let i = args.firstIndex(of: flag), i + 1 < args.count else { return nil }107 return args[i + 1]108 }109110 private static func printErr(_ s: String) {111 FileHandle.standardError.write(Data((s + "\n").utf8))112 }113}114115/// Loads a URL in an offscreen, windowed WKWebView (a window ensures WebKit116/// runs layout/timers/JS the same as onscreen) and resolves when navigation117/// finishes.118@MainActor119final class PageLoader: NSObject, WKNavigationDelegate {120 let webView: WKWebView121 private let window: NSWindow122 private var continuation: CheckedContinuation<Void, Error>?123124 init(configuration: WKWebViewConfiguration) {125 webView = WKWebView(frame: NSRect(x: 0, y: 0, width: 1200, height: 900),126 configuration: configuration)127 window = NSWindow(contentRect: NSRect(x: 0, y: 0, width: 1200, height: 900),128 styleMask: [.borderless], backing: .buffered, defer: false)129 super.init()130 window.contentView = webView131 window.orderOut(nil)132 webView.navigationDelegate = self133 }134135 func load(_ url: URL) async throws {136 try await withCheckedThrowingContinuation { (c: CheckedContinuation<Void, Error>) in137 continuation = c138 webView.load(URLRequest(url: url))139 }140 }141142 func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {143 continuation?.resume(); continuation = nil144 }145 func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {146 continuation?.resume(throwing: error); continuation = nil147 }148 func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {149 continuation?.resume(throwing: error); continuation = nil150 }151}152