import Foundation public struct CCUsageTokenSnapshot: Sendable, Equatable { public let sessionTokens: Int? public let sessionCostUSD: Double? public let last30DaysTokens: Int? public let last30DaysCostUSD: Double? public let daily: [CCUsageDailyReport.Entry] public let updatedAt: Date public init( sessionTokens: Int?, sessionCostUSD: Double?, last30DaysTokens: Int?, last30DaysCostUSD: Double?, daily: [CCUsageDailyReport.Entry], updatedAt: Date) { self.sessionTokens = sessionTokens self.sessionCostUSD = sessionCostUSD self.last30DaysTokens = last30DaysTokens self.last30DaysCostUSD = last30DaysCostUSD self.daily = daily self.updatedAt = updatedAt }} public struct CCUsageDailyReport: Sendable, Decodable { public struct ModelBreakdown: Sendable, Decodable, Equatable { public let modelName: String public let costUSD: Double? private enum CodingKeys: String, CodingKey { case modelName case costUSD case cost } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.modelName = try container.decode(String.self, forKey: .modelName) self.costUSD = try container.decodeIfPresent(Double.self, forKey: .costUSD) ?? container.decodeIfPresent(Double.self, forKey: .cost) } public init(modelName: String, costUSD: Double?) { self.modelName = modelName self.costUSD = costUSD } } public struct Entry: Sendable, Decodable, Equatable { public let date: String public let inputTokens: Int? public let outputTokens: Int? public let totalTokens: Int? public let costUSD: Double? public let modelsUsed: [String]? public let modelBreakdowns: [ModelBreakdown]? private enum CodingKeys: String, CodingKey { case date case inputTokens case outputTokens case totalTokens case costUSD case totalCost case modelsUsed case models case modelBreakdowns } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.date = try container.decode(String.self, forKey: .date) self.inputTokens = try container.decodeIfPresent(Int.self, forKey: .inputTokens) self.outputTokens = try container.decodeIfPresent(Int.self, forKey: .outputTokens) self.totalTokens = try container.decodeIfPresent(Int.self, forKey: .totalTokens) self.costUSD = try container.decodeIfPresent(Double.self, forKey: .costUSD) ?? container.decodeIfPresent(Double.self, forKey: .totalCost) self.modelsUsed = Self.decodeModelsUsed(from: container) self.modelBreakdowns = try container.decodeIfPresent([ModelBreakdown].self, forKey: .modelBreakdowns) } public init( date: String, inputTokens: Int?, outputTokens: Int?, totalTokens: Int?, costUSD: Double?, modelsUsed: [String]?, modelBreakdowns: [ModelBreakdown]?) { self.date = date self.inputTokens = inputTokens self.outputTokens = outputTokens self.totalTokens = totalTokens self.costUSD = costUSD self.modelsUsed = modelsUsed self.modelBreakdowns = modelBreakdowns } private static func decodeModelsUsed(from container: KeyedDecodingContainer<CodingKeys>) -> [String]? { func decodeStringList(_ key: CodingKeys) -> [String]? { (try? container.decodeIfPresent([String].self, forKey: key)).flatMap(\.self) } if let modelsUsed = decodeStringList(.modelsUsed) { return modelsUsed } if let models = decodeStringList(.models) { return models } guard container.contains(.models) else { return nil } guard let modelMap = try? container.nestedContainer(keyedBy: CCUsageAnyCodingKey.self, forKey: .models) else { return nil } let modelNames = modelMap.allKeys.map(\.stringValue).sorted() return modelNames.isEmpty ? nil : modelNames } } public struct Summary: Sendable, Decodable, Equatable { public let totalInputTokens: Int? public let totalOutputTokens: Int? public let totalTokens: Int? public let totalCostUSD: Double? private enum CodingKeys: String, CodingKey { case totalInputTokens case totalOutputTokens case totalTokens case totalCostUSD case totalCost } public init( totalInputTokens: Int?, totalOutputTokens: Int?, totalTokens: Int?, totalCostUSD: Double?) { self.totalInputTokens = totalInputTokens self.totalOutputTokens = totalOutputTokens self.totalTokens = totalTokens self.totalCostUSD = totalCostUSD } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.totalInputTokens = try container.decodeIfPresent(Int.self, forKey: .totalInputTokens) self.totalOutputTokens = try container.decodeIfPresent(Int.self, forKey: .totalOutputTokens) self.totalTokens = try container.decodeIfPresent(Int.self, forKey: .totalTokens) self.totalCostUSD = try container.decodeIfPresent(Double.self, forKey: .totalCostUSD) ?? container.decodeIfPresent(Double.self, forKey: .totalCost) } } public let data: [Entry] public let summary: Summary? private enum CodingKeys: String, CodingKey { case type case data case summary case daily case totals } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) if container.contains(.type) { _ = try container.decode(String.self, forKey: .type) self.data = try container.decode([Entry].self, forKey: .data) self.summary = try container.decodeIfPresent(Summary.self, forKey: .summary) return } self.data = try container.decode([Entry].self, forKey: .daily) if container.contains(.totals) { let totals = try container.decode(CCUsageLegacyTotals.self, forKey: .totals) self.summary = Summary( totalInputTokens: totals.totalInputTokens, totalOutputTokens: totals.totalOutputTokens, totalTokens: totals.totalTokens, totalCostUSD: totals.totalCost) } else { self.summary = nil } } public init(data: [Entry], summary: Summary?) { self.data = data self.summary = summary }} public struct CCUsageSessionReport: Sendable, Decodable { public struct Entry: Sendable, Decodable, Equatable { public let session: String public let inputTokens: Int? public let outputTokens: Int? public let totalTokens: Int? public let costUSD: Double? public let lastActivity: String? private enum CodingKeys: String, CodingKey { case session case sessionId case inputTokens case outputTokens case totalTokens case costUSD case totalCost case lastActivity } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.session = try container.decodeIfPresent(String.self, forKey: .session) ?? container.decode(String.self, forKey: .sessionId) self.inputTokens = try container.decodeIfPresent(Int.self, forKey: .inputTokens) self.outputTokens = try container.decodeIfPresent(Int.self, forKey: .outputTokens) self.totalTokens = try container.decodeIfPresent(Int.self, forKey: .totalTokens) self.costUSD = try container.decodeIfPresent(Double.self, forKey: .costUSD) ?? container.decodeIfPresent(Double.self, forKey: .totalCost) self.lastActivity = try container.decodeIfPresent(String.self, forKey: .lastActivity) } } public struct Summary: Sendable, Decodable, Equatable { public let totalCostUSD: Double? private enum CodingKeys: String, CodingKey { case totalCostUSD case totalCost } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.totalCostUSD = try container.decodeIfPresent(Double.self, forKey: .totalCostUSD) ?? container.decodeIfPresent(Double.self, forKey: .totalCost) } } public let data: [Entry] public let summary: Summary? private enum CodingKeys: String, CodingKey { case type case data case summary case sessions case totals } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) if container.contains(.type) { _ = try container.decode(String.self, forKey: .type) self.data = try container.decode([Entry].self, forKey: .data) self.summary = try container.decodeIfPresent(Summary.self, forKey: .summary) return } self.data = try container.decode([Entry].self, forKey: .sessions) self.summary = try container.decodeIfPresent(Summary.self, forKey: .totals) }} public struct CCUsageMonthlyReport: Sendable, Decodable { public struct Entry: Sendable, Decodable, Equatable { public let month: String public let totalTokens: Int? public let costUSD: Double? private enum CodingKeys: String, CodingKey { case month case totalTokens case costUSD case totalCost } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.month = try container.decode(String.self, forKey: .month) self.totalTokens = try container.decodeIfPresent(Int.self, forKey: .totalTokens) self.costUSD = try container.decodeIfPresent(Double.self, forKey: .costUSD) ?? container.decodeIfPresent(Double.self, forKey: .totalCost) } } public struct Summary: Sendable, Decodable, Equatable { public let totalTokens: Int? public let totalCostUSD: Double? private enum CodingKeys: String, CodingKey { case totalTokens case costUSD case totalCostUSD case totalCost } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.totalTokens = try container.decodeIfPresent(Int.self, forKey: .totalTokens) self.totalCostUSD = try container.decodeIfPresent(Double.self, forKey: .totalCostUSD) ?? container.decodeIfPresent(Double.self, forKey: .costUSD) ?? container.decodeIfPresent(Double.self, forKey: .totalCost) } } public let data: [Entry] public let summary: Summary? private enum CodingKeys: String, CodingKey { case type case data case summary case monthly case totals } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) if container.contains(.type) { _ = try container.decode(String.self, forKey: .type) self.data = try container.decode([Entry].self, forKey: .data) self.summary = try container.decodeIfPresent(Summary.self, forKey: .summary) return } self.data = try container.decode([Entry].self, forKey: .monthly) self.summary = try container.decodeIfPresent(Summary.self, forKey: .totals) }} private struct CCUsageLegacyTotals: Sendable, Decodable { let totalInputTokens: Int? let totalOutputTokens: Int? let totalTokens: Int? let totalCost: Double?} private struct CCUsageAnyCodingKey: CodingKey { var intValue: Int? var stringValue: String init?(intValue: Int) { self.intValue = intValue self.stringValue = "\(intValue)" } init?(stringValue: String) { self.stringValue = stringValue self.intValue = nil }} enum CCUsageDateParser { static func parse(_ text: String?) -> Date? { guard let text, !text.isEmpty else { return nil } let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines) let iso = ISO8601DateFormatter() iso.formatOptions = [.withInternetDateTime, .withFractionalSeconds] if let d = iso.date(from: trimmed) { return d } iso.formatOptions = [.withInternetDateTime] if let d = iso.date(from: trimmed) { return d } let day = DateFormatter() day.locale = Locale(identifier: "en_US_POSIX") day.timeZone = TimeZone.current day.dateFormat = "yyyy-MM-dd" if let d = day.date(from: trimmed) { return d } let monthDayYear = DateFormatter() monthDayYear.locale = Locale(identifier: "en_US_POSIX") monthDayYear.timeZone = TimeZone.current monthDayYear.dateFormat = "MMM d, yyyy" if let d = monthDayYear.date(from: trimmed) { return d } return nil } static func parseMonth(_ text: String?) -> Date? { guard let text, !text.isEmpty else { return nil } let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines) let monthYear = DateFormatter() monthYear.locale = Locale(identifier: "en_US_POSIX") monthYear.timeZone = TimeZone.current monthYear.dateFormat = "MMM yyyy" if let d = monthYear.date(from: trimmed) { return d } let fullMonthYear = DateFormatter() fullMonthYear.locale = Locale(identifier: "en_US_POSIX") fullMonthYear.timeZone = TimeZone.current fullMonthYear.dateFormat = "MMMM yyyy" if let d = fullMonthYear.date(from: trimmed) { return d } let ym = DateFormatter() ym.locale = Locale(identifier: "en_US_POSIX") ym.timeZone = TimeZone.current ym.dateFormat = "yyyy-MM" if let d = ym.date(from: trimmed) { return d } return nil }}
import Foundation
public struct CCUsageTokenSnapshot: Sendable, Equatable {
public let sessionTokens: Int?
public let sessionCostUSD: Double?
public let last30DaysTokens: Int?
public let last30DaysCostUSD: Double?
public let daily: [CCUsageDailyReport.Entry]
public let updatedAt: Date
public init(
sessionTokens: Int?,
sessionCostUSD: Double?,
last30DaysTokens: Int?,
last30DaysCostUSD: Double?,
daily: [CCUsageDailyReport.Entry],
updatedAt: Date)
{
self.sessionTokens = sessionTokens
self.sessionCostUSD = sessionCostUSD
self.last30DaysTokens = last30DaysTokens
self.last30DaysCostUSD = last30DaysCostUSD
self.daily = daily
self.updatedAt = updatedAt
}
}
public struct CCUsageDailyReport: Sendable, Decodable {
public struct ModelBreakdown: Sendable, Decodable, Equatable {
public let modelName: String
public let costUSD: Double?
private enum CodingKeys: String, CodingKey {
case modelName
case costUSD
case cost
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.modelName = try container.decode(String.self, forKey: .modelName)
self.costUSD =
try container.decodeIfPresent(Double.self, forKey: .costUSD)
?? container.decodeIfPresent(Double.self, forKey: .cost)
}
public init(modelName: String, costUSD: Double?) {
self.modelName = modelName
self.costUSD = costUSD
}
}
public struct Entry: Sendable, Decodable, Equatable {
public let date: String
public let inputTokens: Int?
public let outputTokens: Int?
public let totalTokens: Int?
public let costUSD: Double?
public let modelsUsed: [String]?
public let modelBreakdowns: [ModelBreakdown]?
private enum CodingKeys: String, CodingKey {
case date
case inputTokens
case outputTokens
case totalTokens
case costUSD
case totalCost
case modelsUsed
case models
case modelBreakdowns
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.date = try container.decode(String.self, forKey: .date)
self.inputTokens = try container.decodeIfPresent(Int.self, forKey: .inputTokens)
self.outputTokens = try container.decodeIfPresent(Int.self, forKey: .outputTokens)
self.totalTokens = try container.decodeIfPresent(Int.self, forKey: .totalTokens)
self.costUSD =
try container.decodeIfPresent(Double.self, forKey: .costUSD)
?? container.decodeIfPresent(Double.self, forKey: .totalCost)
self.modelsUsed = Self.decodeModelsUsed(from: container)
self.modelBreakdowns = try container.decodeIfPresent([ModelBreakdown].self, forKey: .modelBreakdowns)
}
public init(
date: String,
inputTokens: Int?,
outputTokens: Int?,
totalTokens: Int?,
costUSD: Double?,
modelsUsed: [String]?,
modelBreakdowns: [ModelBreakdown]?)
{
self.date = date
self.inputTokens = inputTokens
self.outputTokens = outputTokens
self.totalTokens = totalTokens
self.costUSD = costUSD
self.modelsUsed = modelsUsed
self.modelBreakdowns = modelBreakdowns
}
private static func decodeModelsUsed(from container: KeyedDecodingContainer<CodingKeys>) -> [String]? {
func decodeStringList(_ key: CodingKeys) -> [String]? {
(try? container.decodeIfPresent([String].self, forKey: key)).flatMap(\.self)
}
if let modelsUsed = decodeStringList(.modelsUsed) { return modelsUsed }
if let models = decodeStringList(.models) { return models }
guard container.contains(.models) else { return nil }
guard let modelMap = try? container.nestedContainer(keyedBy: CCUsageAnyCodingKey.self, forKey: .models)
else { return nil }
let modelNames = modelMap.allKeys.map(\.stringValue).sorted()
return modelNames.isEmpty ? nil : modelNames
}
}
public struct Summary: Sendable, Decodable, Equatable {
public let totalInputTokens: Int?
public let totalOutputTokens: Int?
public let totalTokens: Int?
public let totalCostUSD: Double?
private enum CodingKeys: String, CodingKey {
case totalInputTokens
case totalOutputTokens
case totalTokens
case totalCostUSD
case totalCost
}
public init(
totalInputTokens: Int?,
totalOutputTokens: Int?,
totalTokens: Int?,
totalCostUSD: Double?)
{
self.totalInputTokens = totalInputTokens
self.totalOutputTokens = totalOutputTokens
self.totalTokens = totalTokens
self.totalCostUSD = totalCostUSD
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.totalInputTokens = try container.decodeIfPresent(Int.self, forKey: .totalInputTokens)
self.totalOutputTokens = try container.decodeIfPresent(Int.self, forKey: .totalOutputTokens)
self.totalTokens = try container.decodeIfPresent(Int.self, forKey: .totalTokens)
self.totalCostUSD =
try container.decodeIfPresent(Double.self, forKey: .totalCostUSD)
?? container.decodeIfPresent(Double.self, forKey: .totalCost)
}
}
public let data: [Entry]
public let summary: Summary?
private enum CodingKeys: String, CodingKey {
case type
case data
case summary
case daily
case totals
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if container.contains(.type) {
_ = try container.decode(String.self, forKey: .type)
self.data = try container.decode([Entry].self, forKey: .data)
self.summary = try container.decodeIfPresent(Summary.self, forKey: .summary)
return
}
self.data = try container.decode([Entry].self, forKey: .daily)
if container.contains(.totals) {
let totals = try container.decode(CCUsageLegacyTotals.self, forKey: .totals)
self.summary = Summary(
totalInputTokens: totals.totalInputTokens,
totalOutputTokens: totals.totalOutputTokens,
totalTokens: totals.totalTokens,
totalCostUSD: totals.totalCost)
} else {
self.summary = nil
}
}
public init(data: [Entry], summary: Summary?) {
self.data = data
self.summary = summary
}
}
public struct CCUsageSessionReport: Sendable, Decodable {
public struct Entry: Sendable, Decodable, Equatable {
public let session: String
public let inputTokens: Int?
public let outputTokens: Int?
public let totalTokens: Int?
public let costUSD: Double?
public let lastActivity: String?
private enum CodingKeys: String, CodingKey {
case session
case sessionId
case inputTokens
case outputTokens
case totalTokens
case costUSD
case totalCost
case lastActivity
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.session =
try container.decodeIfPresent(String.self, forKey: .session)
?? container.decode(String.self, forKey: .sessionId)
self.inputTokens = try container.decodeIfPresent(Int.self, forKey: .inputTokens)
self.outputTokens = try container.decodeIfPresent(Int.self, forKey: .outputTokens)
self.totalTokens = try container.decodeIfPresent(Int.self, forKey: .totalTokens)
self.costUSD =
try container.decodeIfPresent(Double.self, forKey: .costUSD)
?? container.decodeIfPresent(Double.self, forKey: .totalCost)
self.lastActivity = try container.decodeIfPresent(String.self, forKey: .lastActivity)
}
}
public struct Summary: Sendable, Decodable, Equatable {
public let totalCostUSD: Double?
private enum CodingKeys: String, CodingKey {
case totalCostUSD
case totalCost
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.totalCostUSD =
try container.decodeIfPresent(Double.self, forKey: .totalCostUSD)
?? container.decodeIfPresent(Double.self, forKey: .totalCost)
}
}
public let data: [Entry]
public let summary: Summary?
private enum CodingKeys: String, CodingKey {
case type
case data
case summary
case sessions
case totals
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if container.contains(.type) {
_ = try container.decode(String.self, forKey: .type)
self.data = try container.decode([Entry].self, forKey: .data)
self.summary = try container.decodeIfPresent(Summary.self, forKey: .summary)
return
}
self.data = try container.decode([Entry].self, forKey: .sessions)
self.summary = try container.decodeIfPresent(Summary.self, forKey: .totals)
}
}
public struct CCUsageMonthlyReport: Sendable, Decodable {
public struct Entry: Sendable, Decodable, Equatable {
public let month: String
public let totalTokens: Int?
public let costUSD: Double?
private enum CodingKeys: String, CodingKey {
case month
case totalTokens
case costUSD
case totalCost
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.month = try container.decode(String.self, forKey: .month)
self.totalTokens = try container.decodeIfPresent(Int.self, forKey: .totalTokens)
self.costUSD =
try container.decodeIfPresent(Double.self, forKey: .costUSD)
?? container.decodeIfPresent(Double.self, forKey: .totalCost)
}
}
public struct Summary: Sendable, Decodable, Equatable {
public let totalTokens: Int?
public let totalCostUSD: Double?
private enum CodingKeys: String, CodingKey {
case totalTokens
case costUSD
case totalCostUSD
case totalCost
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.totalTokens = try container.decodeIfPresent(Int.self, forKey: .totalTokens)
self.totalCostUSD =
try container.decodeIfPresent(Double.self, forKey: .totalCostUSD)
?? container.decodeIfPresent(Double.self, forKey: .costUSD)
?? container.decodeIfPresent(Double.self, forKey: .totalCost)
}
}
public let data: [Entry]
public let summary: Summary?
private enum CodingKeys: String, CodingKey {
case type
case data
case summary
case monthly
case totals
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if container.contains(.type) {
_ = try container.decode(String.self, forKey: .type)
self.data = try container.decode([Entry].self, forKey: .data)
self.summary = try container.decodeIfPresent(Summary.self, forKey: .summary)
return
}
self.data = try container.decode([Entry].self, forKey: .monthly)
self.summary = try container.decodeIfPresent(Summary.self, forKey: .totals)
}
}
private struct CCUsageLegacyTotals: Sendable, Decodable {
let totalInputTokens: Int?
let totalOutputTokens: Int?
let totalTokens: Int?
let totalCost: Double?
}
private struct CCUsageAnyCodingKey: CodingKey {
var intValue: Int?
var stringValue: String
init?(intValue: Int) {
self.intValue = intValue
self.stringValue = "\(intValue)"
}
init?(stringValue: String) {
self.stringValue = stringValue
self.intValue = nil
}
}
enum CCUsageDateParser {
static func parse(_ text: String?) -> Date? {
guard let text, !text.isEmpty else { return nil }
let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
let iso = ISO8601DateFormatter()
iso.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
if let d = iso.date(from: trimmed) { return d }
iso.formatOptions = [.withInternetDateTime]
if let d = iso.date(from: trimmed) { return d }
let day = DateFormatter()
day.locale = Locale(identifier: "en_US_POSIX")
day.timeZone = TimeZone.current
day.dateFormat = "yyyy-MM-dd"
if let d = day.date(from: trimmed) { return d }
let monthDayYear = DateFormatter()
monthDayYear.locale = Locale(identifier: "en_US_POSIX")
monthDayYear.timeZone = TimeZone.current
monthDayYear.dateFormat = "MMM d, yyyy"
if let d = monthDayYear.date(from: trimmed) { return d }
return nil
}
static func parseMonth(_ text: String?) -> Date? {
guard let text, !text.isEmpty else { return nil }
let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
let monthYear = DateFormatter()
monthYear.locale = Locale(identifier: "en_US_POSIX")
monthYear.timeZone = TimeZone.current
monthYear.dateFormat = "MMM yyyy"
if let d = monthYear.date(from: trimmed) { return d }
let fullMonthYear = DateFormatter()
fullMonthYear.locale = Locale(identifier: "en_US_POSIX")
fullMonthYear.timeZone = TimeZone.current
fullMonthYear.dateFormat = "MMMM yyyy"
if let d = fullMonthYear.date(from: trimmed) { return d }
let ym = DateFormatter()
ym.locale = Locale(identifier: "en_US_POSIX")
ym.timeZone = TimeZone.current
ym.dateFormat = "yyyy-MM"
if let d = ym.date(from: trimmed) { return d }
return nil
}
}