Files
ccs/macos-bar/Sources/CCSBarCore/BarDiscovery.swift
T
Tam Nhu Tran 23abf6a635 feat(macos-bar): add CCS Bar core (client, models, discovery) + tests
Pure-Foundation CCSBarCore: thin CCS web-server client (summary force-fresh,
pause/resume/default/solo/tier-lock), bar.json discovery with an offline state,
summary models, status-title formatting, and a force-refresh debouncer. Tested
via a runnable assert harness (ccs-bar-check) so it builds without full Xcode.
2026-06-07 15:52:03 -04:00

62 lines
2.0 KiB
Swift

import Foundation
/// Connection handshake the app reads to find the running CCS web-server.
///
/// Written by `ccs bar launch` to `~/.ccs/bar.json`. v1 only supports
/// `authMode == "loopback"` (dashboard auth disabled, localhost).
public struct BarDiscovery: Codable, Sendable, Equatable {
public let baseUrl: String
public let port: Int
public let authMode: String
public init(baseUrl: String, port: Int, authMode: String) {
self.baseUrl = baseUrl
self.port = port
self.authMode = authMode
}
enum CodingKeys: String, CodingKey {
case baseUrl
case port
case authMode
}
/// Resolved base URL, falling back to a localhost URL built from `port`
/// when `baseUrl` is empty or unparseable.
public var resolvedURL: URL? {
if let url = URL(string: baseUrl), url.scheme != nil { return url }
return URL(string: "http://127.0.0.1:\(port)")
}
public enum LoadError: Error, Equatable {
case missing(path: String)
case unreadable(path: String)
case malformed
}
/// Default discovery file path under the given home directory.
public static func defaultPath(home: String = NSHomeDirectory()) -> String {
URL(fileURLWithPath: home)
.appendingPathComponent(".ccs")
.appendingPathComponent("bar.json")
.path
}
/// Load discovery from `~/.ccs/bar.json`. Returns a typed error when the
/// file is absent (CCS not launched) or malformed so the UI can show a
/// clear "CCS offline" state instead of crashing.
public static func load(home: String = NSHomeDirectory()) -> Result<BarDiscovery, LoadError> {
let path = defaultPath(home: home)
guard FileManager.default.fileExists(atPath: path) else {
return .failure(.missing(path: path))
}
guard let data = FileManager.default.contents(atPath: path) else {
return .failure(.unreadable(path: path))
}
guard let discovery = try? JSONDecoder().decode(BarDiscovery.self, from: data) else {
return .failure(.malformed)
}
return .success(discovery)
}
}