Compare commits

...
Author SHA1 Message Date
superdooper86 aaed64484c fix: include anthropic.com cookies in all auth checks and API requests
Claude session cookies are on anthropic.com, not claude.ai. The login
window was not detecting auth (Cancel stayed, no Done) and API calls
were sent without the actual session token.

- claudeCookieHeader: include anthropic.com cookies so the token is
  sent to the usage/bootstrap endpoints
- checkInitialSignInState: detect anthropic.com cookies on startup
- didFinish in LoginView: fire auth when anthropic.com cookies found
- signOut: clear anthropic.com data alongside claude.ai
- notAuthenticated catch: set isSignedIn = false so Settings stays
  in sync with the main panel
2026-05-11 09:49:57 +02:00
github-actions[bot] 2e3ee350ca Beta release v1.2.1-beta.6 2026-05-11 07:37:40 +00:00
superdooper86 e939bdb88b fix: add browser headers to all API requests to resolve 403 on usage endpoint
Claude's usage/prepaid/overage endpoints require Origin, Referer, and
User-Agent headers to pass CORS/auth checks. Without them, bootstrap
succeeds (more permissive) but usage returns 403 -> 'Not signed in'.

Added claudeAPIRequest(for:) helper that sets all required browser-like
headers on every request. Bootstrap, usage, prepaid, overage, and the
orgs fallback all go through it.
2026-05-11 09:36:35 +02:00
github-actions[bot] 6749c5f158 Beta release v1.2.1-beta.5 2026-05-11 07:27:17 +00:00
superdooper86 de71ec2794 fix: load /login instead of root so premature auth detection is prevented
Loading https://claude.ai as the start URL caused didFinish to fire on
the landing page while stale/tracking cookies were already in
WKWebsiteDataStore. The 'any claude.ai cookie' check then fired
immediately, closing the login sheet before the user could sign in.

Loading /login ensures the URL-guard catches the initial page load and
only checks cookies after the real post-login redirect.
2026-05-11 09:25:45 +02:00
github-actions[bot] ac17ce154f Beta release v1.2.1-beta.4 2026-05-11 06:59:20 +00:00
SuperDooper d2eac62897 chore: bump to v1.2.1-beta.4 2026-05-11 08:58:05 +02:00
SuperDooper 8c64fc50ad fix: checkInitialSignInState uses any claude.ai cookie, not specific names 2026-05-11 08:58:04 +02:00
SuperDooper b2c138f665 fix: detect auth by any claude.ai cookie, not specific cookie names 2026-05-11 08:58:02 +02:00
github-actions[bot] aa6c299e2a Beta release v1.2.1-beta.3 2026-05-10 21:41:28 +00:00
SuperDooper ecbae7d7ca chore: release notes for v1.2.1-beta.3 2026-05-10 23:40:31 +02:00
SuperDooper 37a039e1d3 chore: bump to v1.2.1-beta.3 2026-05-10 23:40:30 +02:00
SuperDooper 8f3ead2f65 fix: replace stale 'No API key configured' with correct signed-out message 2026-05-10 23:40:29 +02:00
github-actions[bot] 734670bb3f Beta release v1.2.1-beta.2 2026-05-10 21:29:37 +00:00
SuperDooper 94430e00ba chore: release notes for v1.2.1-beta.2 2026-05-10 23:28:40 +02:00
SuperDooper 7b0368b001 chore: bump to v1.2.1-beta.2 2026-05-10 23:28:39 +02:00
SuperDooper 4629aeffbc fix: load claude.ai instead of /login so already-signed-in users are detected 2026-05-10 23:28:38 +02:00
github-actions[bot] 2cb4ddfe97 Beta release v1.2.1-beta.1 2026-05-10 21:16:08 +00:00
SuperDooper 73b251a1e9 chore: release notes for v1.2.1-beta.1 2026-05-10 23:14:48 +02:00
SuperDooper 7bab6a0df0 chore: bump to v1.2.1-beta.1 2026-05-10 23:14:47 +02:00
SuperDooper db6380fa66 fix: detect sign-in state from cookies on startup, add orgs API fallback for org ID 2026-05-10 23:14:45 +02:00
github-actions[bot] 47a148fa67 Release v1.2.0 2026-05-08 21:20:18 +00:00
SuperDooper 85ac329d36 chore: release notes for v1.2.0 2026-05-08 23:19:24 +02:00
SuperDooper a0fb6eabcf chore: bump to v1.2.0 2026-05-08 23:19:23 +02:00
github-actions[bot] b778d03323 Beta release v1.1.4-beta.7 2026-05-08 21:11:36 +00:00
SuperDooper 13387888b2 chore: release notes for 1.1.4-beta.7 2026-05-08 23:10:35 +02:00
SuperDooper 44be1649a1 chore: bump to 1.1.4-beta.7 2026-05-08 23:10:34 +02:00
SuperDooper 1160712e1b fix: remove ScrollView from main panel so popover auto-sizes to content 2026-05-08 23:10:22 +02:00
github-actions[bot] e4f55c80ee Beta release v1.1.4-beta.6 2026-05-08 21:05:48 +00:00
SuperDooper 526752dc36 chore: release notes for 1.1.4-beta.6 2026-05-08 23:04:54 +02:00
SuperDooper a2d983b555 chore: bump to 1.1.4-beta.6 2026-05-08 23:04:53 +02:00
SuperDooper 0d807c2285 fix: Session Diary — remove Claude icon/header, split stats left/right 2026-05-08 23:04:52 +02:00
SuperDooper 1c42433145 Windows beta release win-v0.0.1-beta.49 2026-05-08 20:46:42 +00:00
SuperDooper c0ac2e5a66 Windows beta release win-v0.0.1-beta.48 2026-05-08 20:39:17 +00:00
SuperDooper 405e65f9f7 Windows beta release win-v0.0.1-beta.47 2026-05-08 20:28:41 +00:00
SuperDooper 1c7433ebea Windows beta release win-v0.0.1-beta.46 2026-05-08 20:19:52 +00:00
SuperDooper a39ef800e8 Windows beta release win-v0.0.1-beta.45 2026-05-08 20:05:58 +00:00
SuperDooper 76dec8b85d Windows beta release win-v0.0.1-beta.44 2026-05-08 20:00:55 +00:00
SuperDooper 2f04f8581c Windows beta release win-v0.0.1-beta.42 2026-05-08 19:41:45 +00:00
SuperDooper 788c066a1e Windows beta release win-v0.0.1-beta.41 2026-05-08 19:39:07 +00:00
SuperDooper 31280d4ab2 Windows beta release win-v0.0.1-beta.40 2026-05-08 19:33:28 +00:00
SuperDooper 7b1c5bbf8e Windows beta release win-v0.0.1-beta.39 2026-05-08 19:19:11 +00:00
SuperDooper f2ba44bbaa Windows beta release win-v0.0.1-beta.38 2026-05-08 19:07:16 +00:00
SuperDooper de1fffe0e3 Windows beta release win-v0.0.1-beta.37 2026-05-08 19:00:02 +00:00
SuperDooper e80f07d1b9 Windows beta release win-v0.0.1-beta.36 2026-05-08 17:40:07 +00:00
github-actions[bot] e9ca8a7efc Beta release v1.1.4-beta.5 2026-05-08 17:32:10 +00:00
SuperDooper 63ef2005a4 Update release notes for 1.1.4-beta.5 2026-05-08 19:31:20 +02:00
SuperDooper 5ad7bb26f0 Bump version to 1.1.4-beta.5 2026-05-08 19:31:18 +02:00
SuperDooper 132a2a8f8d Consolidate bootstrap into single fetchBootstrap() call 2026-05-08 19:31:06 +02:00
github-actions[bot] debd1c1850 Beta release v1.1.4-beta.4 2026-05-08 17:27:59 +00:00
9 changed files with 109 additions and 114 deletions
+8 -11
View File
@@ -40,8 +40,7 @@ struct ContentView: View {
.environmentObject(updater)
.transition(.move(edge: .trailing).combined(with: .opacity))
} else {
ScrollView(.vertical, showsIndicators: false) {
VStack(spacing: 0) {
VStack(spacing: 0) {
// Cards grid
if vm.limits.isEmpty {
EmptyStateView()
@@ -96,7 +95,6 @@ struct ContentView: View {
.padding(.bottom, 8)
}
}
}
}
.transition(.opacity)
@@ -398,12 +396,11 @@ struct DiaryRow: View {
var body: some View {
VStack(alignment: .leading, spacing: 5) {
HStack {
AgentIconView(agent: .claude, size: 12)
Text("Claude")
.font(.system(size: 12, weight: .medium))
.foregroundColor(color)
Text("\(totalSaved) samples")
.font(.system(size: 10.5))
.foregroundColor(.secondary)
Spacer()
Text("\(totalSaved) samples · Avg Burn Rate: +\(String(format: "%.1f", burnRate))%/h")
Text("Avg burn rate: \(String(format: "%.1f", burnRate))%/h")
.font(.system(size: 10.5))
.foregroundColor(.secondary)
}
@@ -914,12 +911,12 @@ struct ErrorBanner: View {
struct EmptyStateView: View {
var body: some View {
VStack(spacing: 10) {
Image(systemName: "key.slash")
Image(systemName: "person.crop.circle.badge.questionmark")
.font(.system(size: 28))
.foregroundColor(.secondary)
Text("No API key configured")
Text("Not signed in")
.font(.system(size: 13, weight: .medium))
Text("Open Settings to add your Anthropic API key.")
Text("Sign in to claude.ai to see your usage.")
.font(.system(size: 11.5))
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
+2 -2
View File
@@ -15,9 +15,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.1.4-beta.4</string>
<string>1.2.1-beta.7</string>
<key>CFBundleVersion</key>
<string>44</string>
<string>55</string>
<key>LSMinimumSystemVersion</key>
<string>13.0</string>
<key>LSUIElement</key>
+3 -5
View File
@@ -36,12 +36,10 @@ struct LoginWebView: NSViewRepresentable {
if let url = webView.url?.absoluteString,
url.contains("/login") || url.contains("/auth") { return }
// URL is not a login/auth page, so if any claude.ai cookie exists we're signed in
WKWebsiteDataStore.default().httpCookieStore.getAllCookies { cookies in
let hasSession = cookies.contains {
$0.domain.contains("claude.ai") &&
($0.name == "sessionKey" || $0.name == "__Secure-next-auth.session-token")
}
guard hasSession, !self.didAuthenticate else { return }
let hasAnyCookie = cookies.contains { $0.domain.contains("claude.ai") || $0.domain.contains("anthropic.com") }
guard hasAnyCookie, !self.didAuthenticate else { return }
self.didAuthenticate = true
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.onAuthenticated()
+75 -82
View File
@@ -25,14 +25,12 @@ class UsageViewModel: ObservableObject {
}
}
private var cachedOrgId: String?
private var burnHistoryStore: [String: [Double]] = [:]
private let maxHistorySamples = 24
private var previousPercents: [String: Double] = [:]
private var firedThresholds: [String: Set<Int>] = [:]
init() {
cachedOrgId = UserDefaults.standard.string(forKey: "claude_org_id")
let saved = UserDefaults.standard.double(forKey: "refresh_interval")
refreshInterval = saved > 0 ? saved : 60
showInMenuBar = UserDefaults.standard.object(forKey: "show_in_menubar") as? Bool ?? true
@@ -40,15 +38,21 @@ class UsageViewModel: ObservableObject {
burnHistoryStore = saved
}
loadPlaceholderData()
Task { await checkInitialSignInState() }
}
private func checkInitialSignInState() async {
let cookies = await WKWebsiteDataStore.default().httpCookieStore.allCookies()
let hasAnyCookie = cookies.contains { $0.domain.contains("claude.ai") || $0.domain.contains("anthropic.com") }
if hasAnyCookie { isSignedIn = true }
}
func signOut() async {
let store = WKWebsiteDataStore.default()
let types = WKWebsiteDataStore.allWebsiteDataTypes()
let records = await store.dataRecords(ofTypes: types)
let claudeRecords = records.filter { $0.displayName.contains("claude.ai") }
let claudeRecords = records.filter { $0.displayName.contains("claude.ai") || $0.displayName.contains("anthropic.com") }
await store.removeData(ofTypes: types, for: claudeRecords)
cachedOrgId = nil
UserDefaults.standard.removeObject(forKey: "claude_org_id")
isSignedIn = false
isNotAuthenticated = true
@@ -67,26 +71,25 @@ class UsageViewModel: ObservableObject {
defer { isLoading = false }
do {
// Fetch org ID dynamically if not cached
if cachedOrgId == nil {
let (fetchedOrgId, fetchedPlan) = try await fetchOrgId()
cachedOrgId = fetchedOrgId
if let id = cachedOrgId {
UserDefaults.standard.set(id, forKey: "claude_org_id")
}
if let plan = fetchedPlan { planLabel = plan }
}
guard let orgId = cachedOrgId else {
let (fetchedOrgId, fetchedEmail, fetchedPlan) = try await fetchBootstrap()
let orgId: String
if let id = fetchedOrgId {
UserDefaults.standard.set(id, forKey: "claude_org_id")
orgId = id
} else if let cached = UserDefaults.standard.string(forKey: "claude_org_id") {
orgId = cached
} else {
throw AppError.notAuthenticated
}
if let email = fetchedEmail { userEmail = email }
if let plan = fetchedPlan { planLabel = plan }
async let usageFetch = fetchUsage(orgId: orgId)
async let prepaidFetch = fetchPrepaidCredits(orgId: orgId)
async let overageFetch = fetchOverageSpendLimit(orgId: orgId)
async let emailFetch = fetchUserEmail()
let (usage, prepaid, overage, emailResult) = try await (usageFetch, prepaidFetch, overageFetch, emailFetch)
if let email = emailResult.email { userEmail = email }
if let plan = emailResult.planLabel { planLabel = plan }
let (usage, prepaid, overage) = try await (usageFetch, prepaidFetch, overageFetch)
limits = buildLimits(from: usage)
extraUsage = usage.extraUsage
prepaidCredits = prepaid
@@ -106,6 +109,7 @@ class UsageViewModel: ObservableObject {
checkLimitNotifications(for: limits)
} catch AppError.notAuthenticated {
isNotAuthenticated = true
isSignedIn = false
errorMessage = "Not signed in"
} catch let error as DecodingError {
switch error {
@@ -123,65 +127,79 @@ class UsageViewModel: ObservableObject {
}
}
// MARK: - Fetch org ID from bootstrap
// MARK: - Bootstrap (org ID + email + plan label in one call)
private func fetchOrgId() async throws -> (orgId: String?, planLabel: String?) {
let url = URL(string: "https://claude.ai/api/bootstrap")!
private func claudeAPIRequest(for url: URL) async -> URLRequest {
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 claudeCookies.isEmpty { throw AppError.notAuthenticated }
if let header = HTTPCookie.requestHeaderFields(with: claudeCookies)["Cookie"] {
req.setValue(header, forHTTPHeaderField: "Cookie")
req.setValue("application/json, text/plain, */*", forHTTPHeaderField: "accept")
req.setValue("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", forHTTPHeaderField: "User-Agent")
req.setValue("https://claude.ai", forHTTPHeaderField: "Origin")
req.setValue("https://claude.ai/", forHTTPHeaderField: "Referer")
req.setValue("same-origin", forHTTPHeaderField: "sec-fetch-site")
req.setValue("cors", forHTTPHeaderField: "sec-fetch-mode")
req.setValue("empty", forHTTPHeaderField: "sec-fetch-dest")
if let cookie = await claudeCookieHeader() {
req.setValue(cookie, forHTTPHeaderField: "Cookie")
}
return req
}
private func fetchBootstrap() async throws -> (orgId: String?, email: String?, planLabel: String?) {
let url = URL(string: "https://claude.ai/api/bootstrap")!
var req = await claudeAPIRequest(for: url)
guard req.value(forHTTPHeaderField: "Cookie") != nil else { throw AppError.notAuthenticated }
let (data, response) = try await URLSession.shared.data(for: req)
guard let http = response as? HTTPURLResponse else { throw AppError.networkError }
if http.statusCode == 401 || http.statusCode == 403 { throw AppError.notAuthenticated }
guard http.statusCode == 200 else { throw AppError.networkError }
guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { return (nil, nil) }
guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
return (nil, nil, nil)
}
func planFromOrg(_ org: [String: Any]?) -> String? {
guard let caps = org?["capabilities"] as? [String] else { return nil }
guard let cap = caps.first(where: { $0.hasPrefix("claude_") }) else { return nil }
let account = json["account"] as? [String: Any]
// memberships may live under account or at root (older API shape)
let memberships = (account?["memberships"] ?? json["memberships"]) as? [[String: Any]]
let firstOrg = memberships?.first?["organization"] as? [String: Any]
// org ID primary path then flat-list fallback then dedicated endpoint
var orgId: String? = firstOrg?["uuid"] as? String
if orgId == nil {
orgId = (json["organizations"] as? [[String: Any]])?.first?["uuid"] as? String
}
if orgId == nil {
// Final fallback: fetch /api/organizations directly
let orgsReq = await claudeAPIRequest(for: URL(string: "https://claude.ai/api/organizations")!)
if let (orgsData, orgsResp) = try? await URLSession.shared.data(for: orgsReq),
let orgsHttp = orgsResp as? HTTPURLResponse, orgsHttp.statusCode == 200,
let orgs = try? JSONSerialization.jsonObject(with: orgsData) as? [[String: Any]] {
orgId = orgs.first?["uuid"] as? String
}
}
let email = account?["email_address"] as? String
// plan label from capabilities e.g. "claude_pro" -> "Pro"
var planLabel: String? = nil
if let caps = firstOrg?["capabilities"] as? [String],
let cap = caps.first(where: { $0.hasPrefix("claude_") }) {
let name = String(cap.dropFirst("claude_".count))
return name.prefix(1).uppercased() + name.dropFirst().lowercased()
planLabel = name.prefix(1).uppercased() + name.dropFirst().lowercased()
}
// account.memberships[0].organization.uuid
if let account = json["account"] as? [String: Any],
let memberships = account["memberships"] as? [[String: Any]],
let org = memberships.first?["organization"] as? [String: Any],
let uuid = org["uuid"] as? String {
return (uuid, planFromOrg(org))
}
// root memberships (older API shape)
if let memberships = json["memberships"] as? [[String: Any]],
let org = memberships.first?["organization"] as? [String: Any],
let uuid = org["uuid"] as? String {
return (uuid, planFromOrg(org))
}
// root organizations array
if let orgs = json["organizations"] as? [[String: Any]],
let uuid = orgs.first?["uuid"] as? String {
return (uuid, planFromOrg(orgs.first))
}
return (nil, nil)
return (orgId, email, planLabel)
}
// MARK: - Fetch usage
private func claudeCookieHeader() async -> String? {
let cookies = await WKWebsiteDataStore.default().httpCookieStore.allCookies()
let claudeCookies = cookies.filter { $0.domain.contains("claude.ai") }
let claudeCookies = cookies.filter { $0.domain.contains("claude.ai") || $0.domain.contains("anthropic.com") }
return HTTPCookie.requestHeaderFields(with: claudeCookies)["Cookie"]
}
private func fetchUsage(orgId: String) async throws -> UsageResponse {
let url = URL(string: "https://claude.ai/api/organizations/\(orgId)/usage")!
var req = URLRequest(url: url)
req.setValue("application/json", forHTTPHeaderField: "accept")
if let cookie = await claudeCookieHeader() { req.setValue(cookie, forHTTPHeaderField: "Cookie") }
let req = await claudeAPIRequest(for: url)
let (data, response) = try await URLSession.shared.data(for: req)
guard let http = response as? HTTPURLResponse else { throw AppError.networkError }
if http.statusCode == 401 || http.statusCode == 403 { throw AppError.notAuthenticated }
@@ -189,32 +207,9 @@ class UsageViewModel: ObservableObject {
return try JSONDecoder().decode(UsageResponse.self, from: data)
}
private func fetchUserEmail() async throws -> (email: String?, planLabel: String?) {
let url = URL(string: "https://claude.ai/api/bootstrap")!
var req = URLRequest(url: url)
req.setValue("application/json", forHTTPHeaderField: "accept")
if let cookie = await claudeCookieHeader() { req.setValue(cookie, forHTTPHeaderField: "Cookie") }
let (data, response) = try await URLSession.shared.data(for: req)
guard let http = response as? HTTPURLResponse, http.statusCode == 200 else { return (nil, nil) }
guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let account = json["account"] as? [String: Any] else { return (nil, nil) }
let email = account["email_address"] as? String
var planLabel: String? = nil
if let memberships = account["memberships"] as? [[String: Any]],
let org = memberships.first?["organization"] as? [String: Any],
let caps = org["capabilities"] as? [String],
let cap = caps.first(where: { $0.hasPrefix("claude_") }) {
let name = String(cap.dropFirst("claude_".count))
planLabel = name.prefix(1).uppercased() + name.dropFirst().lowercased()
}
return (email, planLabel)
}
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")
if let cookie = await claudeCookieHeader() { req.setValue(cookie, forHTTPHeaderField: "Cookie") }
let req = await claudeAPIRequest(for: url)
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)
@@ -222,9 +217,7 @@ class UsageViewModel: ObservableObject {
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")
if let cookie = await claudeCookieHeader() { req.setValue(cookie, forHTTPHeaderField: "Cookie") }
let req = await claudeAPIRequest(for: url)
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)
+2 -2
View File
@@ -8,9 +8,9 @@
[![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-1.1.3-orange?style=flat)](https://github.com/superdooper86/claudechecker/releases)
[![Version](https://img.shields.io/badge/version-1.2.0-orange?style=flat)](https://github.com/superdooper86/claudechecker/releases)
[![License](https://img.shields.io/badge/license-MIT-blue?style=flat)](LICENSE)
[![Beta](https://img.shields.io/badge/beta-1.1.4--beta.2-orange?style=flat)](https://github.com/superdooper86/claudechecker/releases/tag/v1.1.4-beta.2) <!-- BETA_BADGE -->
[![Beta](https://img.shields.io/badge/beta-1.2.1--beta.6-orange?style=flat)](https://github.com/superdooper86/claudechecker/releases/tag/v1.2.1-beta.6) <!-- BETA_BADGE -->
</div>
+10 -3
View File
@@ -1,4 +1,11 @@
## What's new in v1.1.4-beta.4
## What's new in v1.2.1
### Bug fix
- Plan name now updates correctly on every refresh (was only set on first launch)
### Bug fixes
- Fixed "Not signed in" showing incorrectly on launch when the session was already active
- Sign-in state is now detected immediately from stored cookies on startup, before the first data refresh completes
- Fixed login window auto-closing before the user could sign in — the login window now correctly loads the `/login` page so it only detects auth after the actual sign-in redirect
- Fixed "No API key configured" showing after signing out — now correctly shows "Not signed in" with a prompt to sign in
- Added `/api/organizations` as a final fallback for org ID resolution when the bootstrap API response doesn't include it
- Fixed usage data not loading — API requests now include required browser-like headers (Origin, Referer, User-Agent)
- Fixed sign-in detection and cookie handling for accounts whose session cookies are on the `anthropic.com` domain rather than `claude.ai`
- Fixed Settings incorrectly showing "Signed in" after a failed refresh — sign-in state now resets when authentication fails
+3 -3
View File
@@ -1,5 +1,5 @@
{
"version": "1.1.4-beta.2",
"url": "https://github.com/superdooper86/claudechecker/releases/download/v1.1.4-beta.2/ClaudeChecker.zip",
"notes": "## What's new in v1.1.4-beta.2\n\n### Improvements\n- Plan name (e.g. Pro, Max) is now read from the API instead of being hardcoded"
"version": "1.2.1-beta.6",
"url": "https://github.com/superdooper86/claudechecker/releases/download/v1.2.1-beta.6/ClaudeChecker.zip",
"notes": "## What's new in v1.2.1\n\n### Bug fixes\n- Fixed \"Not signed in\" showing incorrectly on launch when the session was already active\n- Sign-in state is now detected immediately from stored cookies on startup, before the first data refresh completes\n- Fixed login window auto-closing before the user could sign in — the login window now correctly loads the `/login` page so it only detects auth after the actual sign-in redirect\n- Fixed \"No API key configured\" showing after signing out — now correctly shows \"Not signed in\" with a prompt to sign in\n- Added `/api/organizations` as a final fallback for org ID resolution when the bootstrap API response doesn't include it\n- Fixed usage data not loading after sign-in — API requests now include required browser-like headers (Origin, Referer, User-Agent) that Claude's usage endpoints require"
}
+3 -3
View File
@@ -1,5 +1,5 @@
{
"version": "0.0.1-beta.35",
"url": "https://github.com/superdooper86/claudechecker/releases/download/win-v0.0.1-beta.35/ClaudeChecker-Windows.zip",
"notes": "## What's new in beta.10\r\n\r\n### Bug fixes\r\n- Limits now update correctly on each refresh cycle\r\n- Session expiry is detected and prompts re-authentication instead of silently showing stale data\r\n- Plan name (e.g. Pro, Max) is now read from the API instead of being hardcoded"
"version": "0.0.1-beta.49",
"url": "https://github.com/superdooper86/claudechecker/releases/download/win-v0.0.1-beta.49/ClaudeChecker-Windows.zip",
"notes": "## What's new in beta.36\r\n\r\n### Bug fixes\r\n- Settings no longer incorrectly shows \"Not signed in\" when limits are working\r\n- Session Diary now shows correctly after the first successful refresh\r\n- Extra Usage Credits section now restored from cache on startup\r\n- Plan name, overage, and prepaid credits are now cached and shown immediately on launch\r\n- App now loads cached state instantly on startup before the background refresh completes"
}
+3 -3
View File
@@ -1,5 +1,5 @@
{
"version": "1.1.3",
"url": "https://github.com/superdooper86/claudechecker/releases/download/v1.1.3/ClaudeChecker.zip",
"notes": "## What's new in v1.1.3\n\n### UI polish\n- Limit headers now read \"5 Hour Limit\" and \"7 Day Limit\"\n- Tapping anywhere on the blue update banner opens the update window\n\n### Session Diary fix\n- Burn history is persisted across app launches — the sparkline populates immediately on first open instead of requiring a manual refresh\n\n### Window sizing fixes\n- The popover now resizes correctly when switching between the main view and settings\n- Background colour no longer shows through during the resize animation"
"version": "1.2.0",
"url": "https://github.com/superdooper86/claudechecker/releases/download/v1.2.0/ClaudeChecker.zip",
"notes": "## What's new in v1.2.0\n\n### Bug fixes\n- App now works for all users — org ID is fetched dynamically from the API instead of being hardcoded\n- Plan name (e.g. Pro, Max) now updates correctly on every refresh\n\n### Improvements\n- Plan name is read from the API rather than hardcoded\n- Bootstrap API call consolidated — org ID, email, and plan name fetched in a single request per refresh\n- Main panel no longer scrolls — popover auto-sizes to fit content\n- Session Diary card redesigned — sample count and avg burn rate shown left/right above the sparkline, Claude header removed"
}