ai-dispatch 10.38.0

Multi-AI CLI team orchestrator
// Persisted connection preferences and launch env override for LiveSource.
// Exports: ConnectionConfig, DataSourceKind.

import Foundation

enum DataSourceKind: String, Sendable, CaseIterable {
    case demo
    case live
}

struct ConnectionConfig: Sendable, Equatable {
    var host: String
    var port: Int
    var source: DataSourceKind
    var token: String?

    var baseURL: URL? {
        var components = URLComponents()
        components.scheme = "http"
        components.host = host
        components.port = port
        return components.url
    }

    static func load() -> ConnectionConfig {
        if let override = fromEnvironment() { return override }
        let defaults = UserDefaults.standard
        let host = defaults.string(forKey: Keys.host) ?? "127.0.0.1"
        let port = defaults.object(forKey: Keys.port) as? Int ?? 8080
        let sourceRaw = defaults.string(forKey: Keys.source) ?? DataSourceKind.demo.rawValue
        let source = DataSourceKind(rawValue: sourceRaw) ?? .demo
        return ConnectionConfig(
            host: host,
            port: port,
            source: source,
            token: KeychainTokenStore.load()
        )
    }

    func persist(token: String?) throws {
        let defaults = UserDefaults.standard
        defaults.set(host, forKey: Keys.host)
        defaults.set(port, forKey: Keys.port)
        defaults.set(source.rawValue, forKey: Keys.source)
        if let token, !token.isEmpty {
            try KeychainTokenStore.save(token)
        } else if token != nil {
            KeychainTokenStore.clear()
        }
    }

    static func fromEnvironment() -> ConnectionConfig? {
        let env = ProcessInfo.processInfo.environment
        guard let base = env["AID_BASE_URL"], !base.isEmpty,
              let token = env["AID_TOKEN"], !token.isEmpty,
              let url = URL(string: base),
              let host = url.host else {
            return nil
        }
        let port = url.port ?? 8080
        return ConnectionConfig(host: host, port: port, source: .live, token: token)
    }

    private enum Keys {
        static let host = "aid.command.host"
        static let port = "aid.command.port"
        static let source = "aid.command.source"
    }
}