Files
reyna-cli/native/ReynaCLIHost/Sources/ReynaCLIHostCore/AppEntry.swift
T
2026-08-16 08:24:45 -04:00

89 lines
3.2 KiB
Swift

import Foundation
// Public entry point used by both SwiftPM executable and Xcode app target.
// Preserves AF_UNIX privacy-host protocol exactly as before.
// Headless design: socket-server or stdin JSON-lines mode only.
public func runReynaCLIHost(arguments: [String] = CommandLine.arguments) -> Never {
let hasSocket = arguments.contains("--socket")
let hasPython = arguments.contains("--python")
if hasSocket && hasPython {
fputs("error: --python cannot be combined with --socket\n", stderr)
Darwin.exit(2)
}
if let idx = arguments.firstIndex(of: "--python") {
let forwardedArguments = Array(arguments.dropFirst(idx + 1))
Darwin.exit(runPythonCLI(forwardedArguments: forwardedArguments))
}
if let idx = arguments.firstIndex(of: "--socket") {
let nextIdx = idx + 1
guard nextIdx < arguments.count else {
fputs("error: --socket requires a path argument\n", stderr)
Darwin.exit(2)
}
let socketPath = arguments[nextIdx]
runSocketServer(socketPath: socketPath)
} else {
runStdinLoop()
}
}
func extractRecoverableId(from data: Data) -> String? {
if let obj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
let id = obj["id"] as? String {
return id
}
guard let str = String(data: data, encoding: .utf8) else { return nil }
let pattern = "\"id\"\\s*:\\s*\"([^\"]*)\""
guard let regex = try? NSRegularExpression(pattern: pattern, options: []),
let match = regex.firstMatch(in: str, options: [], range: NSRange(str.startIndex..., in: str)),
match.numberOfRanges >= 2,
let r = Range(match.range(at: 1), in: str) else {
return nil
}
return String(str[r])
}
func writeResponse(_ response: Response) {
guard let jsonData = try? JSONEncoder().encode(response),
let jsonString = String(data: jsonData, encoding: .utf8),
let outData = (jsonString + "\n").data(using: .utf8) else {
return
}
FileHandle.standardOutput.write(outData)
}
func handleLine(_ lineData: Data) {
if lineData.isEmpty { return }
if let s = String(data: lineData, encoding: .utf8),
s.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
return
}
if let request = try? JSONDecoder().decode(Request.self, from: lineData) {
let response = dispatch(request: request)
writeResponse(response)
} else {
let recoveredId = extractRecoverableId(from: lineData)
let err = ErrorPayload(code: "invalid_request", message: "Invalid request JSON")
let resp = Response(id: recoveredId ?? "", ok: false, result: nil, error: err)
writeResponse(resp)
}
}
func runStdinLoop() -> Never {
while let line = readLine() {
if line.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { continue }
if let data = line.data(using: .utf8) {
handleLine(data)
} else {
let err = ErrorPayload(code: "invalid_request", message: "Invalid request encoding")
let resp = Response(id: "", ok: false, result: nil, error: err)
writeResponse(resp)
}
}
Darwin.exit(0)
}