Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
abf05b04fc | ||
|
|
849e94c942 | ||
|
|
d76c7496bb | ||
|
|
45a3925f65 | ||
|
|
113e91583d | ||
|
|
c60fb93e1a | ||
|
|
b16c24cb44 | ||
|
|
89a79c6f13 | ||
|
|
aa529835f7 | ||
|
|
b5c3cc6379 | ||
|
|
c8adaf1baf | ||
|
|
3e99e1417e | ||
|
|
e41d3d0c0f | ||
|
|
1078951c43 | ||
|
|
ed6a5e1b51 | ||
|
|
8701fb1326 | ||
|
|
247b5fc202 | ||
|
|
255de797f8 | ||
|
|
0046c53283 | ||
|
|
8d222d63f0 | ||
|
|
5720370649 | ||
|
|
0843730ae1 | ||
|
|
9c030fcdd5 | ||
|
|
dd68ff39d6 | ||
|
|
b76bdf3bc1 | ||
|
|
c31950fd76 | ||
|
|
4e6ae21825 | ||
|
|
c0bf2e4873 | ||
|
|
081d4804ab | ||
|
|
be3899e188 | ||
|
|
286761e51a | ||
|
|
8e0d66468c | ||
|
|
ed64bcc9e5 | ||
|
|
b2275a74d3 | ||
|
|
3b05188a88 | ||
|
|
ea77454c10 |
@@ -0,0 +1,95 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
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: Compute SHA256
|
||||
id: sha
|
||||
run: echo "sha256=$(shasum -a 256 ClaudeChecker.zip | awk '{print $1}')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create GitHub release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh release create "$GITHUB_REF_NAME" ClaudeChecker.zip \
|
||||
--title "$GITHUB_REF_NAME" \
|
||||
--notes "$(cat RELEASE_NOTES.md 2>/dev/null || echo 'Release ${{ steps.version.outputs.version }}')"
|
||||
|
||||
- name: Update version.json
|
||||
run: |
|
||||
notes=$(cat RELEASE_NOTES.md 2>/dev/null || echo "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.json
|
||||
|
||||
- name: Update Homebrew cask
|
||||
run: |
|
||||
sed -i '' \
|
||||
-e 's/version "[^"]*"/version "${{ steps.version.outputs.version }}"/' \
|
||||
-e 's/sha256 "[^"]*"/sha256 "${{ steps.sha.outputs.sha256 }}"/' \
|
||||
../homebrew-tap/Casks/claudechecker.rb 2>/dev/null || true
|
||||
|
||||
- name: Update README badge
|
||||
run: |
|
||||
sed -i '' \
|
||||
's/version-[0-9][^-]*-orange/version-${{ steps.version.outputs.version }}-orange/' \
|
||||
README.md
|
||||
|
||||
- name: Commit version.json and README
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add version.json README.md
|
||||
git commit -m "Release ${{ github.ref_name }}"
|
||||
git push origin HEAD:main
|
||||
|
||||
- name: Update Homebrew tap
|
||||
env:
|
||||
TAP_TOKEN: ${{ secrets.TAP_TOKEN }}
|
||||
run: |
|
||||
git clone https://x-access-token:${{ secrets.TAP_TOKEN }}@github.com/${{ github.repository_owner }}/homebrew-tap /tmp/homebrew-tap
|
||||
sed -i '' \
|
||||
-e 's/version "[^"]*"/version "${{ steps.version.outputs.version }}"/' \
|
||||
-e 's/sha256 "[^"]*"/sha256 "${{ steps.sha.outputs.sha256 }}"/' \
|
||||
/tmp/homebrew-tap/Casks/claudechecker.rb
|
||||
cd /tmp/homebrew-tap
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add Casks/claudechecker.rb
|
||||
git commit -m "Update claudechecker to ${{ github.ref_name }}"
|
||||
git push
|
||||
@@ -60,6 +60,11 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
.sink { [weak self] _ in self?.updateStatusItem() }
|
||||
.store(in: &cancellables)
|
||||
|
||||
updateManager.$updateAvailable
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] _ in self?.updateStatusItem() }
|
||||
.store(in: &cancellables)
|
||||
|
||||
// Refresh timer
|
||||
scheduleTimer(interval: usageViewModel.refreshInterval)
|
||||
|
||||
@@ -166,7 +171,13 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
let sdStr = sd.map { "\(Int($0.usedPercent.rounded()))%" } ?? "—"
|
||||
button.image = nil
|
||||
button.attributedTitle = NSAttributedString(string: "")
|
||||
button.title = "◔ \(fhStr) \(sdStr)"
|
||||
let updatePrefix = updateManager.updateAvailable ? "↑ Update " : "◔ "
|
||||
button.title = "\(updatePrefix)\(fhStr) \(sdStr)"
|
||||
button.font = NSFont.monospacedDigitSystemFont(ofSize: 12, weight: .medium)
|
||||
} else if updateManager.updateAvailable {
|
||||
button.image = nil
|
||||
button.attributedTitle = NSAttributedString(string: "")
|
||||
button.title = "↑ Update"
|
||||
button.font = NSFont.monospacedDigitSystemFont(ofSize: 12, weight: .medium)
|
||||
} else {
|
||||
button.title = ""
|
||||
@@ -199,6 +210,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
@objc func showPopover() {
|
||||
guard let button = statusItem?.button, let popover else { return }
|
||||
if !popover.isShown {
|
||||
NotificationCenter.default.post(name: .popoverWillOpen, object: nil)
|
||||
popover.show(relativeTo: button.bounds, of: button, preferredEdge: .minY)
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
}
|
||||
@@ -209,6 +221,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
if popover.isShown {
|
||||
popover.performClose(nil)
|
||||
} else {
|
||||
NotificationCenter.default.post(name: .popoverWillOpen, object: nil)
|
||||
popover.show(relativeTo: button.bounds, of: button, preferredEdge: .minY)
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
}
|
||||
|
||||
@@ -47,13 +47,29 @@ struct ContentView: View {
|
||||
EmptyStateView()
|
||||
.padding(.vertical, 40)
|
||||
} else {
|
||||
VStack(spacing: 1) {
|
||||
VStack(spacing: 8) {
|
||||
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(.vertical, 1)
|
||||
.padding(.bottom, 4)
|
||||
|
||||
// Extra credits row
|
||||
if let extra = vm.extraUsage, extra.isEnabled {
|
||||
@@ -94,6 +110,13 @@ struct ContentView: View {
|
||||
}
|
||||
.frame(width: 480)
|
||||
.animation(.easeInOut(duration: 0.2), value: showSettings)
|
||||
.onReceive(NotificationCenter.default.publisher(for: .popoverWillOpen)) { _ in
|
||||
showSettings = false
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: .openUpdateSheet)) { _ in
|
||||
showSettings = false
|
||||
showUpdateSheet = true
|
||||
}
|
||||
.sheet(isPresented: $showLogin) {
|
||||
LoginSheetView(isPresented: $showLogin) {
|
||||
Task { await vm.refresh() }
|
||||
@@ -973,6 +996,36 @@ struct UpdateSuccessBanner: View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Markdown notes renderer
|
||||
|
||||
struct ReleaseNotesText: View {
|
||||
let text: String
|
||||
var font: Font = .system(size: 12)
|
||||
|
||||
var body: some View {
|
||||
let processed = text
|
||||
.split(separator: "\n", omittingEmptySubsequences: false)
|
||||
.map { line -> String in
|
||||
let s = String(line)
|
||||
if s.hasPrefix("### ") { return "**\(s.dropFirst(4))**" }
|
||||
if s.hasPrefix("## ") { return "**\(s.dropFirst(3))**" }
|
||||
if s.hasPrefix("# ") { return "**\(s.dropFirst(2))**" }
|
||||
if s.hasPrefix("- ") { return "• \(s.dropFirst(2))" }
|
||||
return s
|
||||
}
|
||||
.joined(separator: "\n")
|
||||
|
||||
if let attr = try? AttributedString(markdown: processed,
|
||||
options: .init(interpretedSyntax: .inlineOnlyPreservingWhitespace)) {
|
||||
Text(attr).font(font).foregroundColor(.secondary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
} else {
|
||||
Text(text).font(font).foregroundColor(.secondary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Update Banner
|
||||
|
||||
struct UpdateBanner: View {
|
||||
@@ -988,10 +1041,7 @@ struct UpdateBanner: View {
|
||||
Text("Update available — v\(updater.latestVersion)")
|
||||
.font(.system(size: 12, weight: .semibold))
|
||||
if !updater.releaseNotes.isEmpty {
|
||||
Text(updater.releaseNotes)
|
||||
.font(.system(size: 11))
|
||||
.foregroundColor(.secondary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
ReleaseNotesText(text: updater.releaseNotes, font: .system(size: 11))
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
@@ -1051,9 +1101,7 @@ struct UpdateSheet: View {
|
||||
.foregroundColor(.blue)
|
||||
}
|
||||
if !updater.releaseNotes.isEmpty {
|
||||
Text(updater.releaseNotes)
|
||||
.font(.system(size: 12))
|
||||
.foregroundColor(.secondary)
|
||||
ReleaseNotesText(text: updater.releaseNotes)
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.0</string>
|
||||
<string>1.1.1</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>19</string>
|
||||
<string>34</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>13.0</string>
|
||||
<key>LSUIElement</key>
|
||||
|
||||
@@ -24,21 +24,27 @@ struct LoginWebView: NSViewRepresentable {
|
||||
|
||||
class Coordinator: NSObject, WKNavigationDelegate {
|
||||
let onAuthenticated: () -> Void
|
||||
var didAuthenticate = false
|
||||
|
||||
init(onAuthenticated: @escaping () -> Void) {
|
||||
self.onAuthenticated = onAuthenticated
|
||||
}
|
||||
|
||||
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
|
||||
let hasSession = cookies.contains {
|
||||
$0.domain.contains("claude.ai") &&
|
||||
($0.name == "sessionKey" || $0.name == "__Secure-next-auth.session-token")
|
||||
}
|
||||
if hasSession {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
self.onAuthenticated()
|
||||
}
|
||||
guard hasSession, !self.didAuthenticate else { return }
|
||||
self.didAuthenticate = true
|
||||
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")
|
||||
.font(.system(size: 13, weight: .semibold))
|
||||
Spacer()
|
||||
Button("Cancel") { isPresented = false }
|
||||
.buttonStyle(.bordered)
|
||||
if authenticated {
|
||||
Button("Done") {
|
||||
isPresented = false
|
||||
onDone()
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.controlSize(.small)
|
||||
} else {
|
||||
Button("Cancel") { isPresented = false }
|
||||
.buttonStyle(.bordered)
|
||||
.controlSize(.small)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 14)
|
||||
.padding(.vertical, 10)
|
||||
|
||||
@@ -87,6 +87,8 @@ class UpdateManager: ObservableObject {
|
||||
}
|
||||
} else {
|
||||
print("[UpdateManager] Stable fetch returned nil")
|
||||
updateAvailable = false
|
||||
latestVersion = ""
|
||||
}
|
||||
|
||||
if let beta = await betaResult {
|
||||
|
||||
@@ -14,7 +14,7 @@ class UpdateNotificationWindowController: NSWindowController {
|
||||
|
||||
// Called when a new update is detected while running
|
||||
static func showUpdate(version: String, notes: String, near statusItem: NSStatusItem?) {
|
||||
showWindow(version: version, subtitle: notes.isEmpty ? "Tap to update now." : notes, near: statusItem, isUpdate: true)
|
||||
showWindow(version: version, subtitle: "Tap to view release notes & update now!", near: statusItem, isUpdate: true)
|
||||
}
|
||||
|
||||
static func showLimitWarning(windowName: String, percent: Int, near statusItem: NSStatusItem?) {
|
||||
@@ -39,7 +39,12 @@ class UpdateNotificationWindowController: NSWindowController {
|
||||
sharedLimit?.close()
|
||||
let content = UpdateNotificationView(
|
||||
version: "", subtitle: subtitle, isUpdate: false,
|
||||
titleText: title, badgeIcon: badgeIcon, badgeColor: badgeColor
|
||||
titleText: title, badgeIcon: badgeIcon, badgeColor: badgeColor,
|
||||
onTap: {
|
||||
sharedLimit?.close()
|
||||
sharedLimit = nil
|
||||
NotificationCenter.default.post(name: .showPopover, object: nil)
|
||||
}
|
||||
) {
|
||||
sharedLimit?.close()
|
||||
sharedLimit = nil
|
||||
@@ -53,7 +58,16 @@ class UpdateNotificationWindowController: NSWindowController {
|
||||
|
||||
private static func showWindow(version: String, subtitle: String, near statusItem: NSStatusItem?, isUpdate: Bool) {
|
||||
shared?.close()
|
||||
let content = UpdateNotificationView(version: version, subtitle: subtitle, isUpdate: isUpdate) {
|
||||
let content = UpdateNotificationView(version: version, subtitle: subtitle, isUpdate: isUpdate,
|
||||
onTap: {
|
||||
shared?.close()
|
||||
shared = nil
|
||||
NotificationCenter.default.post(name: .showPopover, object: nil)
|
||||
if isUpdate {
|
||||
NotificationCenter.default.post(name: .openUpdateSheet, object: nil)
|
||||
}
|
||||
}
|
||||
) {
|
||||
shared?.close()
|
||||
shared = nil
|
||||
}
|
||||
@@ -129,6 +143,7 @@ struct UpdateNotificationView: View {
|
||||
var titleText: String? = nil
|
||||
var badgeIcon: String? = nil
|
||||
var badgeColor: Color? = nil
|
||||
var onTap: (() -> Void)? = nil
|
||||
let onDismiss: () -> Void
|
||||
|
||||
private var effectiveTitle: String {
|
||||
@@ -142,30 +157,40 @@ struct UpdateNotificationView: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 12) {
|
||||
ZStack(alignment: .bottomTrailing) {
|
||||
Image(nsImage: NSImage(named: "AppIcon") ?? NSImage())
|
||||
.resizable()
|
||||
.frame(width: 44, height: 44)
|
||||
.cornerRadius(10)
|
||||
ZStack(alignment: .topTrailing) {
|
||||
Button(action: { onTap?() }) {
|
||||
HStack(spacing: 12) {
|
||||
ZStack(alignment: .bottomTrailing) {
|
||||
Image(nsImage: NSImage(named: "AppIcon") ?? NSImage())
|
||||
.resizable()
|
||||
.frame(width: 44, height: 44)
|
||||
.cornerRadius(10)
|
||||
|
||||
Image(systemName: effectiveBadgeIcon)
|
||||
.font(.system(size: 16, weight: .semibold))
|
||||
.foregroundColor(effectiveBadgeColor)
|
||||
.background(Color(nsColor: .windowBackgroundColor).clipShape(Circle()))
|
||||
.offset(x: 4, y: 4)
|
||||
Image(systemName: effectiveBadgeIcon)
|
||||
.font(.system(size: 16, weight: .semibold))
|
||||
.foregroundColor(effectiveBadgeColor)
|
||||
.background(Color(nsColor: .windowBackgroundColor).clipShape(Circle()))
|
||||
.offset(x: 4, y: 4)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 3) {
|
||||
Text(effectiveTitle)
|
||||
.font(.system(size: 13, weight: .semibold))
|
||||
.foregroundColor(.primary)
|
||||
Text(subtitle)
|
||||
.font(.system(size: 11))
|
||||
.foregroundColor(.secondary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
|
||||
Spacer(minLength: 24)
|
||||
}
|
||||
.padding(.horizontal, 14)
|
||||
.padding(.vertical, 14)
|
||||
.frame(width: 340, alignment: .leading)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 3) {
|
||||
Text(effectiveTitle)
|
||||
.font(.system(size: 13, weight: .semibold))
|
||||
Text(subtitle)
|
||||
.font(.system(size: 11))
|
||||
.foregroundColor(.secondary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
|
||||
Spacer(minLength: 8)
|
||||
.buttonStyle(.plain)
|
||||
|
||||
Button(action: onDismiss) {
|
||||
Image(systemName: "xmark")
|
||||
@@ -173,10 +198,8 @@ struct UpdateNotificationView: View {
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.padding(14)
|
||||
}
|
||||
.padding(.horizontal, 14)
|
||||
.padding(.vertical, 14)
|
||||
.frame(width: 340, alignment: .leading)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 13)
|
||||
.fill(Color(nsColor: .windowBackgroundColor))
|
||||
|
||||
@@ -307,7 +307,9 @@ extension Notification.Name {
|
||||
static let refreshIntervalChanged = Notification.Name("refreshIntervalChanged")
|
||||
static let closePopover = Notification.Name("closePopover")
|
||||
static let showPopover = Notification.Name("showPopover")
|
||||
static let popoverWillOpen = Notification.Name("popoverWillOpen")
|
||||
static let updateDetected = Notification.Name("updateDetected")
|
||||
static let limitWarning = Notification.Name("limitWarning")
|
||||
static let limitReset = Notification.Name("limitReset")
|
||||
static let openUpdateSheet = Notification.Name("openUpdateSheet")
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
</div>
|
||||
@@ -113,25 +113,29 @@ No API key required. It reads directly from your `claude.ai` session using a bui
|
||||
## Requirements
|
||||
|
||||
- macOS 13 Ventura or later
|
||||
- Xcode 15+ (to build from source)
|
||||
- A claude.ai account
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
### Download (recommended)
|
||||
### Homebrew (easiest)
|
||||
```bash
|
||||
brew tap superdooper86/tap
|
||||
brew install --cask claudechecker
|
||||
```
|
||||
|
||||
### Download
|
||||
1. Go to [Releases](https://github.com/superdooper86/claudechecker/releases)
|
||||
2. Download `ClaudeChecker.zip` from the latest release
|
||||
3. Unzip and drag `ClaudeChecker.app` to `/Applications`
|
||||
4. Open from Applications — it will appear in your menubar
|
||||
|
||||
### Build from source
|
||||
```bash
|
||||
git clone https://github.com/superdooper86/claudechecker.git
|
||||
open ClaudeChecker/ClaudeChecker.xcodeproj
|
||||
```
|
||||
Then in Xcode: set your Team under Signing & Capabilities, then **⌘R** to run.
|
||||
> **macOS security prompt?** If macOS blocks the app on first launch, run this in Terminal:
|
||||
> ```bash
|
||||
> xattr -cr /Applications/ClaudeChecker.app
|
||||
> ```
|
||||
> This is not needed when installing via Homebrew.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
## What's new in v1.1.1
|
||||
|
||||
### Clearer limit labels
|
||||
The 5hr and 7 Day usage cards now have clear section headers so it's immediately obvious which limit is which.
|
||||
|
||||
### Login window fix
|
||||
The 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.
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"url": "https://github.com/superdooper86/claudechecker/releases/download/v1.0.0/ClaudeChecker.zip",
|
||||
"notes": "Initial release."
|
||||
"version": "1.1.0",
|
||||
"url": "https://github.com/superdooper86/claudechecker/releases/download/v1.1.0/ClaudeChecker.zip",
|
||||
"notes": "## What's new in v1.1.0\n\n### Clickable notifications\nTapping a limit warning or reset notification opens the main window. Tapping an update notification opens the update sheet directly — no need to manually click the menubar icon.\n\n### Update indicator in menubar\nWhen an update is available, the menubar shows **↑ Update** alongside your usage percentages so you know at a glance without opening the app.\n\n### Release notes render correctly\nThe update banner and update sheet now render markdown formatting — headers, **bold text**, and bullet points display properly.\n\n### Update notification improved\nThe update popup now reads **\"Tap to view release notes & update now!\"** making it clear you can read what changed before installing.\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."
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user