Compare commits

...
12 Commits
8 changed files with 43 additions and 24 deletions
+3 -3
View File
@@ -911,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.2.0</string>
<string>1.2.1-beta.3</string>
<key>CFBundleVersion</key>
<string>48</string>
<string>51</string>
<key>LSMinimumSystemVersion</key>
<string>13.0</string>
<key>LSUIElement</key>
+1 -1
View File
@@ -12,7 +12,7 @@ struct LoginWebView: NSViewRepresentable {
let webView = WKWebView(frame: .zero, configuration: config)
webView.navigationDelegate = context.coordinator
webView.load(URLRequest(url: URL(string: "https://claude.ai/login")!))
webView.load(URLRequest(url: URL(string: "https://claude.ai")!))
return webView
}
+22 -1
View File
@@ -38,6 +38,16 @@ class UsageViewModel: ObservableObject {
burnHistoryStore = saved
}
loadPlaceholderData()
Task { await checkInitialSignInState() }
}
private func checkInitialSignInState() async {
let cookies = await WKWebsiteDataStore.default().httpCookieStore.allCookies()
let hasSession = cookies.contains {
$0.domain.contains("claude.ai") &&
($0.name == "sessionKey" || $0.name == "__Secure-next-auth.session-token")
}
if hasSession { isSignedIn = true }
}
func signOut() async {
@@ -140,11 +150,22 @@ class UsageViewModel: ObservableObject {
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
// 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, let cookie = await claudeCookieHeader() {
// Final fallback: fetch /api/organizations directly
var orgsReq = URLRequest(url: URL(string: "https://claude.ai/api/organizations")!)
orgsReq.setValue("application/json", forHTTPHeaderField: "accept")
orgsReq.setValue(cookie, forHTTPHeaderField: "Cookie")
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
+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.7-orange?style=flat)](https://github.com/superdooper86/claudechecker/releases/tag/v1.1.4-beta.7) <!-- BETA_BADGE -->
[![Beta](https://img.shields.io/badge/beta-1.2.1--beta.2-orange?style=flat)](https://github.com/superdooper86/claudechecker/releases/tag/v1.2.1-beta.2) <!-- BETA_BADGE -->
</div>
+7 -9
View File
@@ -1,11 +1,9 @@
## What's new in v1.2.0
## What's new in v1.2.1
### Bug fixes
- App now works for all users — org ID is fetched dynamically from the API instead of being hardcoded
- Plan name (e.g. Pro, Max) now updates correctly on every refresh
### Improvements
- Plan name is read from the API rather than hardcoded
- Bootstrap API call consolidated — org ID, email, and plan name fetched in a single request per refresh
- Main panel no longer scrolls — popover auto-sizes to fit content
- Session Diary card redesigned — sample count and avg burn rate shown left/right above the sparkline, Claude header removed
- 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 Re-authenticate (and Sign In) not detecting an existing session — the login window now loads `claude.ai` directly so already-signed-in users are detected correctly and the window auto-dismisses
- 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
- Session cookie detection now checks both `sessionKey` and `__Secure-next-auth.session-token` cookie names
+3 -3
View File
@@ -1,5 +1,5 @@
{
"version": "1.1.4-beta.7",
"url": "https://github.com/superdooper86/claudechecker/releases/download/v1.1.4-beta.7/ClaudeChecker.zip",
"notes": "## What's new in v1.1.4-beta.7\n\n### Bug fix\n- Main panel no longer scrolls — popover now auto-sizes to fit content"
"version": "1.2.1-beta.2",
"url": "https://github.com/superdooper86/claudechecker/releases/download/v1.2.1-beta.2/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 Re-authenticate (and Sign In) not detecting an existing session — the login window now loads `claude.ai` directly so already-signed-in users are detected correctly and the window auto-dismisses\n- Added `/api/organizations` as a final fallback for org ID resolution when the bootstrap API response doesn't include it\n- Session cookie detection now checks both `sessionKey` and `__Secure-next-auth.session-token` cookie names"
}
+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"
}