import Foundation /// Shape of `codeburn status --format menubar-json --period `. /// `current` is scoped to the requested period; the whole payload reflects that slice. struct MenubarPayload: Codable, Sendable { let generated: String let current: CurrentBlock let optimize: OptimizeBlock let history: HistoryBlock } struct HistoryBlock: Codable, Sendable { let daily: [DailyHistoryEntry] } struct DailyModelBreakdown: Codable, Sendable { let name: String let cost: Double let calls: Int let inputTokens: Int let outputTokens: Int var totalTokens: Int { inputTokens + outputTokens } } struct DailyHistoryEntry: Codable, Sendable { let date: String let cost: Double let calls: Int let inputTokens: Int let outputTokens: Int let cacheReadTokens: Int let cacheWriteTokens: Int let topModels: [DailyModelBreakdown] /// Pricing-ratio prior: input + 5x output + cache_creation + 0.1x cache_read. /// Matches Anthropic's published per-token pricing on Sonnet/Opus closely enough to be a useful proxy. var effectiveTokens: Double { Double(inputTokens) + 5.0 * Double(outputTokens) + Double(cacheWriteTokens) + 0.1 * Double(cacheReadTokens) } } extension DailyHistoryEntry { /// Required for legacy payloads (no topModels emitted yet). enum CodingKeys: String, CodingKey { case date, cost, calls, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, topModels } init(from decoder: Decoder) throws { let c = try decoder.container(keyedBy: CodingKeys.self) date = try c.decode(String.self, forKey: .date) cost = try c.decode(Double.self, forKey: .cost) calls = try c.decode(Int.self, forKey: .calls) inputTokens = try c.decode(Int.self, forKey: .inputTokens) outputTokens = try c.decode(Int.self, forKey: .outputTokens) cacheReadTokens = try c.decode(Int.self, forKey: .cacheReadTokens) cacheWriteTokens = try c.decode(Int.self, forKey: .cacheWriteTokens) topModels = try c.decodeIfPresent([DailyModelBreakdown].self, forKey: .topModels) ?? [] } } struct CurrentBlock: Codable, Sendable { let label: String let cost: Double let calls: Int let sessions: Int let oneShotRate: Double? let inputTokens: Int let outputTokens: Int let cacheHitPercent: Double let topActivities: [ActivityEntry] let topModels: [ModelEntry] let providers: [String: Double] } struct ActivityEntry: Codable, Sendable { let name: String let cost: Double let turns: Int let oneShotRate: Double? } struct ModelEntry: Codable, Sendable { let name: String let cost: Double let calls: Int } struct OptimizeBlock: Codable, Sendable { let findingCount: Int let savingsUSD: Double let topFindings: [FindingEntry] } struct FindingEntry: Codable, Sendable { let title: String let impact: String let savingsUSD: Double } // MARK: - Empty fallback extension MenubarPayload { /// Strictly-empty payload. Used as the fallback before real data arrives, so no /// plausible-looking fake numbers leak into the UI. static let empty = MenubarPayload( generated: "", current: CurrentBlock( label: "", cost: 0, calls: 0, sessions: 0, oneShotRate: nil, inputTokens: 0, outputTokens: 0, cacheHitPercent: 0, topActivities: [], topModels: [], providers: [:] ), optimize: OptimizeBlock(findingCount: 0, savingsUSD: 0, topFindings: []), history: HistoryBlock(daily: []) ) }