swift-agent-core/Sources/LilithAgentCore/BaseLocalWebServer.swift
Lilith d3f28f7a43 chore(workflows): 🔧 Update 7 Swift workflow files
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-02-16 02:43:22 -08:00

129 lines
3.9 KiB
Swift

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))
}
}