diff --git a/ClaudeChecker/ContentView.swift b/ClaudeChecker/ContentView.swift index fd75fae..fc5feb6 100644 --- a/ClaudeChecker/ContentView.swift +++ b/ClaudeChecker/ContentView.swift @@ -596,13 +596,31 @@ struct SettingsView: View { .font(.system(size: 12, weight: .medium)) .foregroundColor(.secondary) - HStack(spacing: 6) { - Circle() - .fill(vm.isSignedIn ? Color.green : Color.orange) - .frame(width: 7, height: 7) - Text(vm.isSignedIn ? "Signed in to claude.ai" : "Not signed in") - .font(.system(size: 12, weight: .medium)) - .foregroundColor(vm.isSignedIn ? .primary : .orange) + if vm.isSignedIn { + VStack(alignment: .leading, spacing: 2) { + HStack(spacing: 6) { + Circle() + .fill(Color.green) + .frame(width: 7, height: 7) + Text("Signed in to claude.ai") + .font(.system(size: 13, weight: .semibold)) + } + if !vm.userEmail.isEmpty { + Text(vm.userEmail) + .font(.system(size: 11.5)) + .foregroundColor(.secondary) + .padding(.leading, 13) + } + } + } else { + HStack(spacing: 6) { + Circle() + .fill(Color.orange) + .frame(width: 7, height: 7) + Text("Not signed in") + .font(.system(size: 13, weight: .semibold)) + .foregroundColor(.orange) + } } Text("Your session is stored locally and persists across restarts.") diff --git a/ClaudeChecker/Info.plist b/ClaudeChecker/Info.plist index ba1ad04..292b022 100644 --- a/ClaudeChecker/Info.plist +++ b/ClaudeChecker/Info.plist @@ -15,9 +15,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2.0.6 + 2.0.7 CFBundleVersion - 13 + 14 LSMinimumSystemVersion 13.0 LSUIElement diff --git a/ClaudeChecker/UsageViewModel.swift b/ClaudeChecker/UsageViewModel.swift index 3f1cea4..e20ce9b 100644 --- a/ClaudeChecker/UsageViewModel.swift +++ b/ClaudeChecker/UsageViewModel.swift @@ -14,6 +14,7 @@ class UsageViewModel: ObservableObject { @Published var planLabel: String = "Claude" @Published var isNotAuthenticated: Bool = false @Published var isSignedIn: Bool = false + @Published var userEmail: String = "" @Published var triggerLogin: Bool = false @Published var showInMenuBar: Bool = true { didSet { UserDefaults.standard.set(showInMenuBar, forKey: "show_in_menubar") } @@ -46,6 +47,7 @@ class UsageViewModel: ObservableObject { isSignedIn = false isNotAuthenticated = true lastUpdated = nil + userEmail = "" limits = [] extraUsage = nil prepaidCredits = nil @@ -63,7 +65,9 @@ class UsageViewModel: ObservableObject { 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) + async let emailFetch = fetchUserEmail() + let (usage, prepaid, overage, email) = try await (usageFetch, prepaidFetch, overageFetch, emailFetch) + if let email { userEmail = email } limits = buildLimits(from: usage) extraUsage = usage.extraUsage prepaidCredits = prepaid @@ -121,6 +125,23 @@ class UsageViewModel: ObservableObject { return try JSONDecoder().decode(UsageResponse.self, from: data) } + private func fetchUserEmail() async throws -> String? { + let url = URL(string: "https://claude.ai/api/bootstrap")! + 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 } + guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any], + let account = json["account"] as? [String: Any], + let email = account["email_address"] as? String else { return nil } + return email + } + 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) diff --git a/README.md b/README.md index ea6b01a..0e89829 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ [![macOS](https://img.shields.io/badge/macOS-13.0+-000000?style=flat&logo=apple&logoColor=white)](https://www.apple.com/macos/) [![Swift](https://img.shields.io/badge/Swift-5.9-F05138?style=flat&logo=swift&logoColor=white)](https://swift.org) -[![Version](https://img.shields.io/badge/version-2.0.6-orange?style=flat)](https://github.com/superdooper86/claudechecker/releases) +[![Version](https://img.shields.io/badge/version-2.0.7-orange?style=flat)](https://github.com/superdooper86/claudechecker/releases) [![License](https://img.shields.io/badge/license-MIT-blue?style=flat)](LICENSE) @@ -138,7 +138,7 @@ ClaudeChecker reads from the following `claude.ai` API endpoints (authenticated | Endpoint | Used for | |---|---| -| `/api/bootstrap` | Organisation UUID | +| `/api/bootstrap` | Organisation UUID and account email | | `/api/organizations/{id}/usage` | 5-hour and 7-day quota windows | | `/api/organizations/{id}/overage_spend_limit` | Extra usage spent and monthly limit | | `/api/organizations/{id}/prepaid/credits` | Current credit balance | diff --git a/version.json b/version.json index 702782c..b35739a 100644 --- a/version.json +++ b/version.json @@ -1,5 +1,5 @@ { - "version": "2.0.6", - "url": "https://github.com/superdooper86/claudechecker/releases/download/v2.0.6/ClaudeChecker.zip", - "notes": "Authentication section shows sign-in status with re-authenticate and sign out options." + "version": "2.0.7", + "url": "https://github.com/superdooper86/claudechecker/releases/download/v2.0.7/ClaudeChecker.zip", + "notes": "Show account email in settings auth section." }