diff --git a/ClaudeChecker/ContentView.swift b/ClaudeChecker/ContentView.swift
index f52dc82..2c7f764 100644
--- a/ClaudeChecker/ContentView.swift
+++ b/ClaudeChecker/ContentView.swift
@@ -57,7 +57,7 @@ struct ContentView: View {
// Extra credits row
if let extra = vm.extraUsage, extra.isEnabled {
- ExtraUsageRow(extra: extra)
+ ExtraUsageRow(extra: extra, prepaid: vm.prepaidCredits, overage: vm.overageSpendLimit)
.padding(.horizontal, 16)
.padding(.top, 12)
}
@@ -490,10 +490,20 @@ struct FooterBar: View {
struct ExtraUsageRow: View {
let extra: ExtraUsage
+ let prepaid: PrepaidCredits?
+ let overage: OverageSpendLimit?
- var pct: Double { min(100, max(0, extra.utilization ?? 0)) }
+ var cur: String { overage?.currency ?? extra.currency ?? "" }
+
+ var spentCents: Double? { overage?.usedCredits }
+ var limitCents: Double? { overage?.monthlyCreditLimit }
+ var balanceCents: Double? { prepaid?.amount }
+
+ var pct: Double {
+ guard let s = spentCents, let l = limitCents, l > 0 else { return 0 }
+ return min(100, max(0, (s / l) * 100))
+ }
var color: Color { pct > 80 ? .red : pct > 50 ? .orange : .green }
- var cur: String { extra.currency ?? "" }
func fmt(_ cents: Double?) -> String {
guard let cents else { return "—" }
@@ -511,10 +521,11 @@ struct ExtraUsageRow: View {
.foregroundColor(.secondary)
}
VStack(spacing: 4) {
- ExtraUsageLine(label: "Spent", value: "\(cur) \(fmt(extra.usedCredits))", color: color)
- ExtraUsageLine(label: "Limit", value: extra.monthlyLimit != nil ? "\(cur) \(fmt(extra.monthlyLimit))" : "Unlimited", color: .secondary)
+ ExtraUsageLine(label: "Spent", value: "\(cur) \(fmt(spentCents))", color: color)
+ ExtraUsageLine(label: "Limit", value: limitCents != nil ? "\(cur) \(fmt(limitCents))" : "Unlimited", color: .secondary)
+ ExtraUsageLine(label: "Balance", value: "\(cur) \(fmt(balanceCents))", color: .primary)
}
- if extra.monthlyLimit != nil {
+ if limitCents != nil {
ProgressBar(value: pct / 100, color: color)
.frame(height: 4)
}
diff --git a/ClaudeChecker/Info.plist b/ClaudeChecker/Info.plist
index 432a7c3..b5a9570 100644
--- a/ClaudeChecker/Info.plist
+++ b/ClaudeChecker/Info.plist
@@ -15,9 +15,9 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 2.0.3
+ 2.0.4
CFBundleVersion
- 10
+ 11
LSMinimumSystemVersion
13.0
LSUIElement
diff --git a/ClaudeChecker/Models.swift b/ClaudeChecker/Models.swift
index cfd381f..cd016f1 100644
--- a/ClaudeChecker/Models.swift
+++ b/ClaudeChecker/Models.swift
@@ -83,24 +83,46 @@ struct UsageWindow: Codable {
struct ExtraUsage: Codable {
let isEnabled: Bool
- let monthlyLimit: Double?
- let usedCredits: Double?
- let utilization: Double?
let currency: String?
enum CodingKeys: String, CodingKey {
case isEnabled = "is_enabled"
- case monthlyLimit = "monthly_limit"
- case usedCredits = "used_credits"
- case utilization
case currency
}
init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
- isEnabled = (try? c.decodeIfPresent(Bool.self, forKey: .isEnabled)) ?? false
- monthlyLimit = try? c.decodeIfPresent(Double.self, forKey: .monthlyLimit)
- usedCredits = try? c.decodeIfPresent(Double.self, forKey: .usedCredits)
- utilization = try? c.decodeIfPresent(Double.self, forKey: .utilization)
- currency = try? c.decodeIfPresent(String.self, forKey: .currency)
+ isEnabled = (try? c.decodeIfPresent(Bool.self, forKey: .isEnabled)) ?? false
+ currency = try? c.decodeIfPresent(String.self, forKey: .currency)
+ }
+}
+
+struct PrepaidCredits: Decodable {
+ let amount: Double?
+ let currency: String?
+ enum CodingKeys: String, CodingKey { case amount, currency }
+ init(from decoder: Decoder) throws {
+ let c = try decoder.container(keyedBy: CodingKeys.self)
+ amount = try? c.decodeIfPresent(Double.self, forKey: .amount)
+ currency = try? c.decodeIfPresent(String.self, forKey: .currency)
+ }
+}
+
+struct OverageSpendLimit: Decodable {
+ let isEnabled: Bool
+ let usedCredits: Double?
+ let monthlyCreditLimit: Double?
+ let currency: String?
+ enum CodingKeys: String, CodingKey {
+ case isEnabled = "is_enabled"
+ case usedCredits = "used_credits"
+ case monthlyCreditLimit = "monthly_credit_limit"
+ case currency
+ }
+ init(from decoder: Decoder) throws {
+ let c = try decoder.container(keyedBy: CodingKeys.self)
+ isEnabled = (try? c.decodeIfPresent(Bool.self, forKey: .isEnabled)) ?? false
+ usedCredits = try? c.decodeIfPresent(Double.self, forKey: .usedCredits)
+ monthlyCreditLimit = try? c.decodeIfPresent(Double.self, forKey: .monthlyCreditLimit)
+ currency = try? c.decodeIfPresent(String.self, forKey: .currency)
}
}
diff --git a/ClaudeChecker/UsageViewModel.swift b/ClaudeChecker/UsageViewModel.swift
index 92aa1fe..60c17f3 100644
--- a/ClaudeChecker/UsageViewModel.swift
+++ b/ClaudeChecker/UsageViewModel.swift
@@ -9,6 +9,8 @@ class UsageViewModel: ObservableObject {
@Published var lastUpdated: Date?
@Published var errorMessage: String?
@Published var extraUsage: ExtraUsage?
+ @Published var prepaidCredits: PrepaidCredits?
+ @Published var overageSpendLimit: OverageSpendLimit?
@Published var planLabel: String = "Claude"
@Published var isNotAuthenticated: Bool = false
@Published var triggerLogin: Bool = false
@@ -42,9 +44,14 @@ class UsageViewModel: ObservableObject {
do {
let orgId = cachedOrgId!
- let usage = try await fetchUsage(orgId: orgId)
+ async let usageFetch = fetchUsage(orgId: orgId)
+ async let prepaidFetch = fetchPrepaidCredits(orgId: orgId)
+ async let overageFetch = fetchOverageSpendLimit(orgId: orgId)
+ let (usage, prepaid, overage) = try await (usageFetch, prepaidFetch, overageFetch)
limits = buildLimits(from: usage)
extraUsage = usage.extraUsage
+ prepaidCredits = prepaid
+ overageSpendLimit = overage
planLabel = "Pro"
lastUpdated = Date()
@@ -97,6 +104,34 @@ class UsageViewModel: ObservableObject {
return try JSONDecoder().decode(UsageResponse.self, from: data)
}
+ private func fetchPrepaidCredits(orgId: String) async throws -> PrepaidCredits? {
+ let url = URL(string: "https://claude.ai/api/organizations/\(orgId)/prepaid/credits")!
+ var req = URLRequest(url: url)
+ req.setValue("application/json", forHTTPHeaderField: "accept")
+ let cookies = await WKWebsiteDataStore.default().httpCookieStore.allCookies()
+ let claudeCookies = cookies.filter { $0.domain.contains("claude.ai") }
+ if let header = HTTPCookie.requestHeaderFields(with: claudeCookies)["Cookie"] {
+ req.setValue(header, forHTTPHeaderField: "Cookie")
+ }
+ let (data, response) = try await URLSession.shared.data(for: req)
+ guard let http = response as? HTTPURLResponse, http.statusCode == 200 else { return nil }
+ return try? JSONDecoder().decode(PrepaidCredits.self, from: data)
+ }
+
+ private func fetchOverageSpendLimit(orgId: String) async throws -> OverageSpendLimit? {
+ let url = URL(string: "https://claude.ai/api/organizations/\(orgId)/overage_spend_limit")!
+ var req = URLRequest(url: url)
+ req.setValue("application/json", forHTTPHeaderField: "accept")
+ let cookies = await WKWebsiteDataStore.default().httpCookieStore.allCookies()
+ let claudeCookies = cookies.filter { $0.domain.contains("claude.ai") }
+ if let header = HTTPCookie.requestHeaderFields(with: claudeCookies)["Cookie"] {
+ req.setValue(header, forHTTPHeaderField: "Cookie")
+ }
+ let (data, response) = try await URLSession.shared.data(for: req)
+ guard let http = response as? HTTPURLResponse, http.statusCode == 200 else { return nil }
+ return try? JSONDecoder().decode(OverageSpendLimit.self, from: data)
+ }
+
// MARK: - Build limits
private func buildLimits(from usage: UsageResponse) -> [AgentLimit] {
diff --git a/README.md b/README.md
index ffc9434..0c8e25e 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
[](https://www.apple.com/macos/)
[](https://swift.org)
-[](https://github.com/superdooper86/claudechecker/releases)
+[](https://github.com/superdooper86/claudechecker/releases)
[](LICENSE)
diff --git a/version.json b/version.json
index 47a6075..54aec02 100644
--- a/version.json
+++ b/version.json
@@ -1,5 +1,5 @@
{
- "version": "2.0.3",
- "url": "https://github.com/superdooper86/claudechecker/releases/download/v2.0.3/ClaudeChecker.zip",
- "notes": "Fixed extra usage section."
+ "version": "2.0.4",
+ "url": "https://github.com/superdooper86/claudechecker/releases/download/v2.0.4/ClaudeChecker.zip",
+ "notes": "Extra usage now shows spent, limit, and current balance."
}