Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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() }
|
.sink { [weak self] _ in self?.updateStatusItem() }
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
updateManager.$updateAvailable
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { [weak self] _ in self?.updateStatusItem() }
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
// Refresh timer
|
// Refresh timer
|
||||||
scheduleTimer(interval: usageViewModel.refreshInterval)
|
scheduleTimer(interval: usageViewModel.refreshInterval)
|
||||||
|
|
||||||
@@ -166,7 +171,13 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
let sdStr = sd.map { "\(Int($0.usedPercent.rounded()))%" } ?? "—"
|
let sdStr = sd.map { "\(Int($0.usedPercent.rounded()))%" } ?? "—"
|
||||||
button.image = nil
|
button.image = nil
|
||||||
button.attributedTitle = NSAttributedString(string: "")
|
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)
|
button.font = NSFont.monospacedDigitSystemFont(ofSize: 12, weight: .medium)
|
||||||
} else {
|
} else {
|
||||||
button.title = ""
|
button.title = ""
|
||||||
@@ -199,6 +210,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
@objc func showPopover() {
|
@objc func showPopover() {
|
||||||
guard let button = statusItem?.button, let popover else { return }
|
guard let button = statusItem?.button, let popover else { return }
|
||||||
if !popover.isShown {
|
if !popover.isShown {
|
||||||
|
NotificationCenter.default.post(name: .popoverWillOpen, object: nil)
|
||||||
popover.show(relativeTo: button.bounds, of: button, preferredEdge: .minY)
|
popover.show(relativeTo: button.bounds, of: button, preferredEdge: .minY)
|
||||||
NSApp.activate(ignoringOtherApps: true)
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
}
|
}
|
||||||
@@ -209,6 +221,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
if popover.isShown {
|
if popover.isShown {
|
||||||
popover.performClose(nil)
|
popover.performClose(nil)
|
||||||
} else {
|
} else {
|
||||||
|
NotificationCenter.default.post(name: .popoverWillOpen, object: nil)
|
||||||
popover.show(relativeTo: button.bounds, of: button, preferredEdge: .minY)
|
popover.show(relativeTo: button.bounds, of: button, preferredEdge: .minY)
|
||||||
NSApp.activate(ignoringOtherApps: true)
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,6 +94,13 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
.frame(width: 480)
|
.frame(width: 480)
|
||||||
.animation(.easeInOut(duration: 0.2), value: showSettings)
|
.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) {
|
.sheet(isPresented: $showLogin) {
|
||||||
LoginSheetView(isPresented: $showLogin) {
|
LoginSheetView(isPresented: $showLogin) {
|
||||||
Task { await vm.refresh() }
|
Task { await vm.refresh() }
|
||||||
@@ -774,14 +781,16 @@ struct SettingsView: View {
|
|||||||
.padding(.vertical, 2)
|
.padding(.vertical, 2)
|
||||||
.background(Color.blue)
|
.background(Color.blue)
|
||||||
.cornerRadius(4)
|
.cornerRadius(4)
|
||||||
|
.fixedSize()
|
||||||
} else if updater.betaAvailable {
|
} else if updater.betaAvailable {
|
||||||
Text("v\(updater.latestBetaVersion) beta")
|
Text("v\(updater.latestBetaVersion) available")
|
||||||
.font(.system(size: 10, weight: .medium))
|
.font(.system(size: 10, weight: .medium))
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
.padding(.horizontal, 6)
|
.padding(.horizontal, 6)
|
||||||
.padding(.vertical, 2)
|
.padding(.vertical, 2)
|
||||||
.background(Color.orange)
|
.background(Color.orange)
|
||||||
.cornerRadius(4)
|
.cornerRadius(4)
|
||||||
|
.fixedSize()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Text("Built by superdooper86 & claude")
|
Text("Built by superdooper86 & claude")
|
||||||
@@ -971,6 +980,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
|
// MARK: - Update Banner
|
||||||
|
|
||||||
struct UpdateBanner: View {
|
struct UpdateBanner: View {
|
||||||
@@ -986,10 +1025,7 @@ struct UpdateBanner: View {
|
|||||||
Text("Update available — v\(updater.latestVersion)")
|
Text("Update available — v\(updater.latestVersion)")
|
||||||
.font(.system(size: 12, weight: .semibold))
|
.font(.system(size: 12, weight: .semibold))
|
||||||
if !updater.releaseNotes.isEmpty {
|
if !updater.releaseNotes.isEmpty {
|
||||||
Text(updater.releaseNotes)
|
ReleaseNotesText(text: updater.releaseNotes, font: .system(size: 11))
|
||||||
.font(.system(size: 11))
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
@@ -1049,9 +1085,7 @@ struct UpdateSheet: View {
|
|||||||
.foregroundColor(.blue)
|
.foregroundColor(.blue)
|
||||||
}
|
}
|
||||||
if !updater.releaseNotes.isEmpty {
|
if !updater.releaseNotes.isEmpty {
|
||||||
Text(updater.releaseNotes)
|
ReleaseNotesText(text: updater.releaseNotes)
|
||||||
.font(.system(size: 12))
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|||||||
@@ -15,9 +15,9 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>3.0.0</string>
|
<string>1.0.12</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>18</string>
|
<string>32</string>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
<string>13.0</string>
|
<string>13.0</string>
|
||||||
<key>LSUIElement</key>
|
<key>LSUIElement</key>
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ class UpdateManager: ObservableObject {
|
|||||||
@Published var updateComplete = false
|
@Published var updateComplete = false
|
||||||
@Published var justUpdated = false
|
@Published var justUpdated = false
|
||||||
|
|
||||||
|
private var notifiedVersion: String = ""
|
||||||
|
|
||||||
@Published var betaChannel: Bool = false {
|
@Published var betaChannel: Bool = false {
|
||||||
didSet {
|
didSet {
|
||||||
UserDefaults.standard.set(betaChannel, forKey: "beta_channel")
|
UserDefaults.standard.set(betaChannel, forKey: "beta_channel")
|
||||||
@@ -70,14 +72,13 @@ class UpdateManager: ObservableObject {
|
|||||||
if let stable = await stableResult {
|
if let stable = await stableResult {
|
||||||
print("[UpdateManager] Stable remote=\(stable.version), isNewer=\(isNewer(stable.version, than: currentVersion))")
|
print("[UpdateManager] Stable remote=\(stable.version), isNewer=\(isNewer(stable.version, than: currentVersion))")
|
||||||
if isNewer(stable.version, than: currentVersion) {
|
if isNewer(stable.version, than: currentVersion) {
|
||||||
let wasAvailable = updateAvailable
|
|
||||||
latestVersion = stable.version
|
latestVersion = stable.version
|
||||||
releaseNotes = stable.notes ?? ""
|
releaseNotes = stable.notes ?? ""
|
||||||
downloadURL = stable.url
|
downloadURL = stable.url
|
||||||
updateAvailable = true
|
updateAvailable = true
|
||||||
print("[UpdateManager] updateAvailable set to true")
|
print("[UpdateManager] updateAvailable set to true")
|
||||||
// Only notify once — when first detected
|
if notifiedVersion != stable.version {
|
||||||
if !wasAvailable {
|
notifiedVersion = stable.version
|
||||||
NotificationCenter.default.post(name: .updateDetected, object: stable.version)
|
NotificationCenter.default.post(name: .updateDetected, object: stable.version)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -86,6 +87,8 @@ class UpdateManager: ObservableObject {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
print("[UpdateManager] Stable fetch returned nil")
|
print("[UpdateManager] Stable fetch returned nil")
|
||||||
|
updateAvailable = false
|
||||||
|
latestVersion = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
if let beta = await betaResult {
|
if let beta = await betaResult {
|
||||||
@@ -98,10 +101,19 @@ class UpdateManager: ObservableObject {
|
|||||||
releaseNotes = beta.notes ?? ""
|
releaseNotes = beta.notes ?? ""
|
||||||
downloadURL = beta.url
|
downloadURL = beta.url
|
||||||
updateAvailable = true
|
updateAvailable = true
|
||||||
|
if notifiedVersion != beta.version {
|
||||||
|
notifiedVersion = beta.version
|
||||||
|
NotificationCenter.default.post(name: .updateDetected, object: beta.version)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
betaAvailable = false
|
||||||
|
latestBetaVersion = ""
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
print("[UpdateManager] Beta fetch returned nil")
|
print("[UpdateManager] Beta fetch returned nil")
|
||||||
|
betaAvailable = false
|
||||||
|
latestBetaVersion = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class UpdateNotificationWindowController: NSWindowController {
|
|||||||
|
|
||||||
// Called when a new update is detected while running
|
// Called when a new update is detected while running
|
||||||
static func showUpdate(version: String, notes: String, near statusItem: NSStatusItem?) {
|
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?) {
|
static func showLimitWarning(windowName: String, percent: Int, near statusItem: NSStatusItem?) {
|
||||||
@@ -39,7 +39,12 @@ class UpdateNotificationWindowController: NSWindowController {
|
|||||||
sharedLimit?.close()
|
sharedLimit?.close()
|
||||||
let content = UpdateNotificationView(
|
let content = UpdateNotificationView(
|
||||||
version: "", subtitle: subtitle, isUpdate: false,
|
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?.close()
|
||||||
sharedLimit = nil
|
sharedLimit = nil
|
||||||
@@ -53,7 +58,16 @@ class UpdateNotificationWindowController: NSWindowController {
|
|||||||
|
|
||||||
private static func showWindow(version: String, subtitle: String, near statusItem: NSStatusItem?, isUpdate: Bool) {
|
private static func showWindow(version: String, subtitle: String, near statusItem: NSStatusItem?, isUpdate: Bool) {
|
||||||
shared?.close()
|
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?.close()
|
||||||
shared = nil
|
shared = nil
|
||||||
}
|
}
|
||||||
@@ -129,6 +143,7 @@ struct UpdateNotificationView: View {
|
|||||||
var titleText: String? = nil
|
var titleText: String? = nil
|
||||||
var badgeIcon: String? = nil
|
var badgeIcon: String? = nil
|
||||||
var badgeColor: Color? = nil
|
var badgeColor: Color? = nil
|
||||||
|
var onTap: (() -> Void)? = nil
|
||||||
let onDismiss: () -> Void
|
let onDismiss: () -> Void
|
||||||
|
|
||||||
private var effectiveTitle: String {
|
private var effectiveTitle: String {
|
||||||
@@ -142,6 +157,8 @@ struct UpdateNotificationView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
ZStack(alignment: .topTrailing) {
|
||||||
|
Button(action: { onTap?() }) {
|
||||||
HStack(spacing: 12) {
|
HStack(spacing: 12) {
|
||||||
ZStack(alignment: .bottomTrailing) {
|
ZStack(alignment: .bottomTrailing) {
|
||||||
Image(nsImage: NSImage(named: "AppIcon") ?? NSImage())
|
Image(nsImage: NSImage(named: "AppIcon") ?? NSImage())
|
||||||
@@ -159,13 +176,21 @@ struct UpdateNotificationView: View {
|
|||||||
VStack(alignment: .leading, spacing: 3) {
|
VStack(alignment: .leading, spacing: 3) {
|
||||||
Text(effectiveTitle)
|
Text(effectiveTitle)
|
||||||
.font(.system(size: 13, weight: .semibold))
|
.font(.system(size: 13, weight: .semibold))
|
||||||
|
.foregroundColor(.primary)
|
||||||
Text(subtitle)
|
Text(subtitle)
|
||||||
.font(.system(size: 11))
|
.font(.system(size: 11))
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(minLength: 8)
|
Spacer(minLength: 24)
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 14)
|
||||||
|
.padding(.vertical, 14)
|
||||||
|
.frame(width: 340, alignment: .leading)
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
|
||||||
Button(action: onDismiss) {
|
Button(action: onDismiss) {
|
||||||
Image(systemName: "xmark")
|
Image(systemName: "xmark")
|
||||||
@@ -173,10 +198,8 @@ struct UpdateNotificationView: View {
|
|||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
|
.padding(14)
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 14)
|
|
||||||
.padding(.vertical, 14)
|
|
||||||
.frame(width: 340, alignment: .leading)
|
|
||||||
.background(
|
.background(
|
||||||
RoundedRectangle(cornerRadius: 13)
|
RoundedRectangle(cornerRadius: 13)
|
||||||
.fill(Color(nsColor: .windowBackgroundColor))
|
.fill(Color(nsColor: .windowBackgroundColor))
|
||||||
|
|||||||
@@ -307,7 +307,9 @@ extension Notification.Name {
|
|||||||
static let refreshIntervalChanged = Notification.Name("refreshIntervalChanged")
|
static let refreshIntervalChanged = Notification.Name("refreshIntervalChanged")
|
||||||
static let closePopover = Notification.Name("closePopover")
|
static let closePopover = Notification.Name("closePopover")
|
||||||
static let showPopover = Notification.Name("showPopover")
|
static let showPopover = Notification.Name("showPopover")
|
||||||
|
static let popoverWillOpen = Notification.Name("popoverWillOpen")
|
||||||
static let updateDetected = Notification.Name("updateDetected")
|
static let updateDetected = Notification.Name("updateDetected")
|
||||||
static let limitWarning = Notification.Name("limitWarning")
|
static let limitWarning = Notification.Name("limitWarning")
|
||||||
static let limitReset = Notification.Name("limitReset")
|
static let limitReset = Notification.Name("limitReset")
|
||||||
|
static let openUpdateSheet = Notification.Name("openUpdateSheet")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
[](https://www.apple.com/macos/)
|
[](https://www.apple.com/macos/)
|
||||||
[](https://swift.org)
|
[](https://swift.org)
|
||||||
[](https://github.com/superdooper86/claudechecker/releases)
|
[](https://github.com/superdooper86/claudechecker/releases)
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -113,25 +113,29 @@ No API key required. It reads directly from your `claude.ai` session using a bui
|
|||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- macOS 13 Ventura or later
|
- macOS 13 Ventura or later
|
||||||
- Xcode 15+ (to build from source)
|
|
||||||
- A claude.ai account
|
- A claude.ai account
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Installation
|
## 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)
|
1. Go to [Releases](https://github.com/superdooper86/claudechecker/releases)
|
||||||
2. Download `ClaudeChecker.zip` from the latest release
|
2. Download `ClaudeChecker.zip` from the latest release
|
||||||
3. Unzip and drag `ClaudeChecker.app` to `/Applications`
|
3. Unzip and drag `ClaudeChecker.app` to `/Applications`
|
||||||
4. Open from Applications — it will appear in your menubar
|
4. Open from Applications — it will appear in your menubar
|
||||||
|
|
||||||
### Build from source
|
> **macOS security prompt?** If macOS blocks the app on first launch, run this in Terminal:
|
||||||
```bash
|
> ```bash
|
||||||
git clone https://github.com/superdooper86/claudechecker.git
|
> xattr -cr /Applications/ClaudeChecker.app
|
||||||
open ClaudeChecker/ClaudeChecker.xcodeproj
|
> ```
|
||||||
```
|
> This is not needed when installing via Homebrew.
|
||||||
Then in Xcode: set your Team under Signing & Capabilities, then **⌘R** to run.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
## What's new in v1.0.12
|
||||||
|
|
||||||
|
### Update indicator in menubar
|
||||||
|
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.
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "3.1.0-beta",
|
|
||||||
"url": "https://github.com/superdooper86/claudechecker/releases/download/v3.0.0/ClaudeChecker.zip",
|
|
||||||
"notes": "Beta test build."
|
|
||||||
}
|
|
||||||
+3
-3
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "3.0.0",
|
"version": "1.0.11",
|
||||||
"url": "https://github.com/superdooper86/claudechecker/releases/download/v3.0.0/ClaudeChecker.zip",
|
"url": "https://github.com/superdooper86/claudechecker/releases/download/v1.0.11/ClaudeChecker.zip",
|
||||||
"notes": "Limit notifications for 80% and 95% usage, and on quota reset."
|
"notes": "## What's new in v1.0.11\n\n### Update indicator in menubar\nWhen 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.\n\n### Update banner can't be dismissed\nThe × close button has been removed from the update banner. The banner stays visible until you install the update."
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user