import Foundation import Swifter /// Base local web server for Lilith macOS agents /// /// Provides common HTTP routes for agent status, settings, and control. /// Subclass to add agent-specific routes. /// /// Example: /// ```swift /// class SyncAgentServer: BaseLocalWebServer { /// override func registerRoutes() { /// super.registerRoutes() /// server["/api/sync/trigger"] = { request in /// self.triggerSync() /// return .ok(.json(["triggered": true])) /// } /// } /// } /// ``` open class BaseLocalWebServer { public let server: HttpServer public let port: UInt16 public let agentName: String private var isRunning = false /// Create a new local web server /// - Parameters: /// - port: Port to listen on /// - agentName: Name of the agent (for status responses) public init(port: UInt16, agentName: String) { self.server = HttpServer() self.port = port self.agentName = agentName } /// Start the web server public func start() throws { registerRoutes() try server.start(port, forceIPv4: true) isRunning = true } /// Stop the web server public func stop() { server.stop() isRunning = false } /// Register HTTP routes. Override to add custom routes. /// Always call super.registerRoutes() to include base routes. open func registerRoutes() { // GET /status - Agent health check server["/status"] = { [weak self] _ in guard let self else { return .internalServerError } let status = self.getStatus() return .ok(.json(status)) } // GET /settings - Current agent settings server["/settings"] = { [weak self] _ in guard let self else { return .internalServerError } let settings = self.getSettings() return .ok(.json(settings)) } // POST /settings - Update agent settings server["/settings"] = { [weak self] request in guard let self else { return .internalServerError } return self.handleUpdateSettings(request) } // POST /restart - Restart the agent server["/restart"] = { [weak self] _ in guard let self else { return .internalServerError } self.handleRestart() return .ok(.json(["restarting": true])) } // GET /health - Simple health probe server["/health"] = { _ in return .ok(.text("ok")) } } // MARK: - Overridable Handlers /// Get the current agent status. Override to add custom status fields. open func getStatus() -> [String: Any] { return [ "agent": agentName, "running": isRunning, "port": port, "uptime": ProcessInfo.processInfo.systemUptime, "version": Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "unknown", ] } /// Get the current agent settings. Override to return actual settings. open func getSettings() -> [String: Any] { return [:] } /// Handle a settings update request. Override to apply settings. open func handleUpdateSettings(_ request: HttpRequest) -> HttpResponse { return .ok(.json(["updated": true])) } /// Handle a restart request. Override to implement restart logic. open func handleRestart() { DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) { // Default: exit and let launchd/launchctl restart us exit(0) } } } // MARK: - HttpResponse JSON Helper extension HttpResponse { static func json(_ dict: [String: Any]) -> HttpResponse { guard let data = try? JSONSerialization.data(withJSONObject: dict), let jsonString = String(data: data, encoding: .utf8) else { return .internalServerError } return .ok(.json(jsonString)) } }