macos-ai-menubar-usage/AIMenubarUsage/Services/UsageCache.swift
Felipe M. 00c4e33171
Initial commit: AI Menubar Usage — macOS menu bar app for Cursor and Claude.
Includes provider integrations, settings, NSStatusItem UI, tests, Makefile, and README with screenshot.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-27 20:41:40 +02:00

56 lines
1.9 KiB
Swift

import Foundation
struct UsageCacheEntry: Codable, Sendable {
let fetchedAt: Date
let payload: Data
}
actor UsageCache {
static let shared = UsageCache()
private let directory: URL
private let fileManager = FileManager.default
init() {
let base = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first!
directory = base.appendingPathComponent("ai-usage", isDirectory: true)
try? fileManager.createDirectory(at: directory, withIntermediateDirectories: true)
}
func load(provider: ProviderID, maxAge: TimeInterval) -> UsageCacheEntry? {
let url = cacheURL(for: provider)
guard fileManager.fileExists(atPath: url.path),
let data = try? Data(contentsOf: url),
let entry = try? JSONDecoder().decode(UsageCacheEntry.self, from: data)
else {
return nil
}
if Date().timeIntervalSince(entry.fetchedAt) > maxAge {
return nil
}
return entry
}
func loadStale(provider: ProviderID) -> UsageCacheEntry? {
let url = cacheURL(for: provider)
guard fileManager.fileExists(atPath: url.path),
let data = try? Data(contentsOf: url),
let entry = try? JSONDecoder().decode(UsageCacheEntry.self, from: data)
else {
return nil
}
return entry
}
func save(provider: ProviderID, payload: Data) {
let entry = UsageCacheEntry(fetchedAt: Date(), payload: payload)
guard let data = try? JSONEncoder().encode(entry) else { return }
let url = cacheURL(for: provider)
try? data.write(to: url, options: .atomic)
try? fileManager.setAttributes([.posixPermissions: 0o600], ofItemAtPath: url.path)
}
private func cacheURL(for provider: ProviderID) -> URL {
directory.appendingPathComponent("\(provider.rawValue)-usage.json")
}
}