Compare commits

...
16 Commits
10 changed files with 141 additions and 27 deletions
+63
View File
@@ -0,0 +1,63 @@
name: Release Beta
on:
push:
tags:
- 'v*-beta*'
jobs:
release-beta:
runs-on: macos-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Get version from tag
id: version
run: echo "version=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT
- name: Build
run: |
xcodebuild \
-project ClaudeChecker.xcodeproj \
-scheme ClaudeChecker \
-configuration Release \
-derivedDataPath build \
clean build
- name: Zip app
run: |
ditto -c -k --keepParent \
build/Build/Products/Release/ClaudeChecker.app \
ClaudeChecker.zip
- name: Create GitHub pre-release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create "$GITHUB_REF_NAME" ClaudeChecker.zip \
--title "$GITHUB_REF_NAME" \
--prerelease \
--notes "$(cat RELEASE_NOTES.md 2>/dev/null || echo 'Beta release ${{ steps.version.outputs.version }}')"
- name: Update version-beta.json
run: |
notes=$(cat RELEASE_NOTES.md 2>/dev/null || echo "Beta release ${{ steps.version.outputs.version }}")
jq -n \
--arg version "${{ steps.version.outputs.version }}" \
--arg url "https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/ClaudeChecker.zip" \
--arg notes "$notes" \
'{version: $version, url: $url, notes: $notes}' > version-beta.json
- name: Commit version-beta.json
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add version-beta.json
git commit -m "Beta release ${{ github.ref_name }}"
git push origin HEAD:main
+1
View File
@@ -4,6 +4,7 @@ on:
push: push:
tags: tags:
- 'v*' - 'v*'
- '!v*-*'
jobs: jobs:
release: release:
+21 -5
View File
@@ -47,13 +47,29 @@ struct ContentView: View {
EmptyStateView() EmptyStateView()
.padding(.vertical, 40) .padding(.vertical, 40)
} else { } else {
VStack(spacing: 1) { VStack(spacing: 8) {
ForEach(vm.limits) { limit in ForEach(vm.limits) { limit in
LimitCard(limit: limit) VStack(spacing: 0) {
HStack {
Text(limit.window == .fiveHour ? "5hr Limit" : "7 Day Limit")
.font(.system(size: 11, weight: .semibold))
.foregroundColor(.secondary)
.textCase(.uppercase)
Spacer()
}
.padding(.horizontal, 16)
.padding(.top, 10)
.padding(.bottom, 4)
LimitCard(limit: limit)
.background(Color(nsColor: .controlBackgroundColor))
.cornerRadius(8)
.overlay(RoundedRectangle(cornerRadius: 8).stroke(Color.primary.opacity(0.08), lineWidth: 0.5))
.padding(.horizontal, 8)
}
} }
} }
.background(Color.primary.opacity(0.05)) .padding(.bottom, 4)
.padding(.vertical, 1)
// Extra credits row // Extra credits row
if let extra = vm.extraUsage, extra.isEnabled { if let extra = vm.extraUsage, extra.isEnabled {
@@ -196,7 +212,7 @@ struct LimitCard: View {
AgentIconView(agent: .claude, size: 14) AgentIconView(agent: .claude, size: 14)
Text("Claude") Text("Claude")
.font(.system(size: 13, weight: .semibold)) .font(.system(size: 13, weight: .semibold))
Text("· \(vm.planLabel) · \(limit.window.label)") Text("· \(vm.planLabel)")
.font(.system(size: 11)) .font(.system(size: 11))
.foregroundColor(.secondary) .foregroundColor(.secondary)
Spacer() Spacer()
+2 -2
View File
@@ -15,9 +15,9 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.0.11</string> <string>1.1.2-beta.2</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>31</string> <string>35</string>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>
<string>13.0</string> <string>13.0</string>
<key>LSUIElement</key> <key>LSUIElement</key>
+21 -6
View File
@@ -24,21 +24,27 @@ struct LoginWebView: NSViewRepresentable {
class Coordinator: NSObject, WKNavigationDelegate { class Coordinator: NSObject, WKNavigationDelegate {
let onAuthenticated: () -> Void let onAuthenticated: () -> Void
var didAuthenticate = false
init(onAuthenticated: @escaping () -> Void) { init(onAuthenticated: @escaping () -> Void) {
self.onAuthenticated = onAuthenticated self.onAuthenticated = onAuthenticated
} }
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
guard !didAuthenticate else { return }
// Don't fire on the login/auth pages themselves
if let url = webView.url?.absoluteString,
url.contains("/login") || url.contains("/auth") { return }
WKWebsiteDataStore.default().httpCookieStore.getAllCookies { cookies in WKWebsiteDataStore.default().httpCookieStore.getAllCookies { cookies in
let hasSession = cookies.contains { let hasSession = cookies.contains {
$0.domain.contains("claude.ai") && $0.domain.contains("claude.ai") &&
($0.name == "sessionKey" || $0.name == "__Secure-next-auth.session-token") ($0.name == "sessionKey" || $0.name == "__Secure-next-auth.session-token")
} }
if hasSession { guard hasSession, !self.didAuthenticate else { return }
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { self.didAuthenticate = true
self.onAuthenticated() DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
} self.onAuthenticated()
} }
} }
} }
@@ -58,9 +64,18 @@ struct LoginSheetView: View {
Text(authenticated ? "✓ Signed in — loading data…" : "Sign in to Claude") Text(authenticated ? "✓ Signed in — loading data…" : "Sign in to Claude")
.font(.system(size: 13, weight: .semibold)) .font(.system(size: 13, weight: .semibold))
Spacer() Spacer()
Button("Cancel") { isPresented = false } if authenticated {
.buttonStyle(.bordered) Button("Done") {
isPresented = false
onDone()
}
.buttonStyle(.borderedProminent)
.controlSize(.small) .controlSize(.small)
} else {
Button("Cancel") { isPresented = false }
.buttonStyle(.bordered)
.controlSize(.small)
}
} }
.padding(.horizontal, 14) .padding(.horizontal, 14)
.padding(.vertical, 10) .padding(.vertical, 10)
+22 -5
View File
@@ -318,13 +318,30 @@ class UpdateManager: ObservableObject {
} }
private func isNewer(_ version: String, than current: String) -> Bool { private func isNewer(_ version: String, than current: String) -> Bool {
let toInts = { (v: String) in v.split(separator: ".").compactMap { Int($0) } } func parse(_ v: String) -> (base: [Int], pre: [Int]?) {
let a = toInts(version), b = toInts(current) let halves = v.split(separator: "-", maxSplits: 1)
for i in 0..<max(a.count, b.count) { let base = halves[0].split(separator: ".").compactMap { Int($0) }
let av = i < a.count ? a[i] : 0 let pre = halves.count > 1 ? String(halves[1]).split(separator: ".").compactMap { Int($0) } : nil
let bv = i < b.count ? b[i] : 0 return (base, pre)
}
let a = parse(version), b = parse(current)
// Compare base version numbers
for i in 0..<max(a.base.count, b.base.count) {
let av = i < a.base.count ? a.base[i] : 0
let bv = i < b.base.count ? b.base[i] : 0
if av != bv { return av > bv } if av != bv { return av > bv }
} }
// Base equal: stable beats pre-release
if a.pre == nil && b.pre != nil { return true }
if a.pre != nil && b.pre == nil { return false }
// Both pre-release: compare pre-release numbers
if let ap = a.pre, let bp = b.pre {
for i in 0..<max(ap.count, bp.count) {
let av = i < ap.count ? ap[i] : 0
let bv = i < bp.count ? bp[i] : 0
if av != bv { return av > bv }
}
}
return false return false
} }
} }
+1 -1
View File
@@ -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/) [![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) [![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.0.10-orange?style=flat)](https://github.com/superdooper86/claudechecker/releases) [![Version](https://img.shields.io/badge/version-1.1.1-orange?style=flat)](https://github.com/superdooper86/claudechecker/releases)
[![License](https://img.shields.io/badge/license-MIT-blue?style=flat)](LICENSE) [![License](https://img.shields.io/badge/license-MIT-blue?style=flat)](LICENSE)
</div> </div>
+3 -6
View File
@@ -1,7 +1,4 @@
## What's new in v1.0.11 ## What's new in v1.1.2
### Update indicator in menubar ### Cleaner card headers
When an update is available, the menubar now shows **↑ Update** alongside your usage percentages — so you can see there's an update without opening the app. Removed the redundant "5h" / "7d" window labels from each card row — the section headers already make it clear which limit is which.
### Update banner can't be dismissed
The × close button has been removed from the update banner. The banner stays visible until you install the update.
+5
View File
@@ -0,0 +1,5 @@
{
"version": "1.1.2-beta.1",
"url": "https://github.com/superdooper86/claudechecker/releases/download/v1.1.2-beta.1/ClaudeChecker.zip",
"notes": "## What's new in v1.1.2\n\n### Cleaner card headers\nRemoved the redundant \"5h\" / \"7d\" window labels from each card row — the section headers already make it clear which limit is which."
}
+2 -2
View File
@@ -1,5 +1,5 @@
{ {
"version": "1.1.1", "version": "1.1.1",
"url": "https://github.com/superdooper86/claudechecker/releases/download/v1.0.10/ClaudeChecker.zip", "url": "https://github.com/superdooper86/claudechecker/releases/download/v1.1.1/ClaudeChecker.zip",
"notes": "## What's new in v1.0.10\\n\\n### Main window always opens on click\\nClosing the app while Settings was open would cause the next click to reopen straight to Settings. The app now always returns to the main window when reopened." "notes": "## What's new in v1.1.1\n\n### Clearer limit labels\nThe 5hr and 7 Day usage cards now have clear section headers so it's immediately obvious which limit is which.\n\n### Login window fix\nThe sign-in sheet now reliably closes after a successful login. A **Done** button also appears as a fallback once authentication is detected — no more being stuck with only a Cancel button."
} }