on b2c392e help sign in
CLAUDE.md+2−2
since splitsnapshots
unifiedsplit
@@ -20,8 +20,8 @@ click-to-copy on the headline, and threshold-crossing notifications.
20- `KeychainHelper.swift` — Keychain wrapper (KeychainStore + KeychainHelper + ZaiKeychainHelper)
20- `KeychainHelper.swift` — Keychain wrapper (KeychainStore + KeychainHelper + ZaiKeychainHelper)
21- `VersionFormatter.swift` — Version string formatting
21- `VersionFormatter.swift` — Version string formatting
22
22
23−The status bar title shows `⚡{balance}` (Hyper), `⁕{percent}%` (Claude 5-hour),
23+The status bar title shows `⚡{balance}` (Hyper), `🕐{percent}%` (Claude 5-hour),
24−`⁕{percent}%` (Claude 7-day), `Z{percent}%` (z.ai 5-hour), and `Z{percent}%`
24+`📅{percent}%` (Claude 7-day), `🤖{percent}%` (z.ai 5-hour), and `📆{percent}%`
25(z.ai weekly) — each omitted when absent or at 0%, and entirely absent when the
25(z.ai weekly) — each omitted when absent or at 0%, and entirely absent when the
26service is not configured (no API key or no credentials). An unconfigured service
26service is not configured (no API key or no credentials). An unconfigured service
27produces no placeholder in the menu bar. The three fetches are independent —
27produces no placeholder in the menu bar. The three fetches are independent —
README.md+6−6
@@ -9,8 +9,8 @@ A macOS menu bar application that displays usage limits and credit balances for
9## Features
9## Features
10
10
11- ⚡ **Hyper credits** — balance from `hyper.charm.land`, with sparkline trend, click-to-copy, and low-balance notifications
11- ⚡ **Hyper credits** — balance from `hyper.charm.land`, with sparkline trend, click-to-copy, and low-balance notifications
12−- ⁕ **Claude Code limits** — Pro/Max 5-hour and 7-day usage windows, with countdown to next reset. No setup: if you've signed in with the Claude Code CLI, it just appears
12+- 🤖 **Claude Code limits** — Pro/Max 5-hour and 7-day usage windows, with countdown to next reset. No setup: if you've signed in with the Claude Code CLI, it just appears
13−- Z **z.ai Coding Plan** — 5-hour and weekly token quota percentages, with reset countdowns. Enter your API key in settings
13+- 🤖 **z.ai Coding Plan** — 5-hour and weekly token quota percentages, with reset countdowns. Enter your API key in settings
14- 🔄 **Auto-refresh** — configurable interval (1m / 5m / 15m / 30m), plus refresh on wake from sleep
14- 🔄 **Auto-refresh** — configurable interval (1m / 5m / 15m / 30m), plus refresh on wake from sleep
15- 🖱️ **Right-click menu** — quick access to Refresh and Quit
15- 🖱️ **Right-click menu** — quick access to Refresh and Quit
16- 🔒 **Keychain storage** — API keys stored securely in macOS Keychain
16- 🔒 **Keychain storage** — API keys stored securely in macOS Keychain
@@ -24,10 +24,10 @@ Each service shows an emoji-prefixed segment when configured and above 0%:
24| Service | Emoji | Example |
24| Service | Emoji | Example |
25|---------|-------|---------|
25|---------|-------|---------|
26| Hyper balance | ⚡ | `⚡42` |
26| Hyper balance | ⚡ | `⚡42` |
27−| Claude 5-hour | ⁕ | `⁕62%` |
27+| Claude 5-hour | 🕐 | `🕐62%` |
28−| Claude 7-day | ⁕ | `⁕8%` |
28+| Claude 7-day | 📅 | `📅8%` |
29−| z.ai 5-hour | Z | `Z12%` |
29+| z.ai 5-hour | 🤖 | `🤖12%` |
30−| z.ai weekly | Z | `Z3%` |
30+| z.ai weekly | 📆 | `📆3%` |
31
31
32Unconfigured services don't appear at all — no placeholder, no icon.
32Unconfigured services don't appear at all — no placeholder, no icon.
33
33
UsageMenubar/MenuView.swift+2−2
@@ -259,7 +259,7 @@ struct MenuView: View {
259 VStack(spacing: 8) {
259 VStack(spacing: 8) {
260 HStack(alignment: .firstTextBaseline, spacing: 8) {
260 HStack(alignment: .firstTextBaseline, spacing: 8) {
261 headlineNumber(
261 headlineNumber(
262− text: "⁕ \\(percent)%",
262+ text: "🅲 \(percent)%",
263 color: Self.usageColor(percent),
263 color: Self.usageColor(percent),
264 trend: viewModel.claudeTrend,
264 trend: viewModel.claudeTrend,
265 copiedConfirmation: viewModel.copiedClaudeConfirmation,
265 copiedConfirmation: viewModel.copiedClaudeConfirmation,
@@ -379,7 +379,7 @@ struct MenuView: View {
379 VStack(spacing: 8) {
379 VStack(spacing: 8) {
380 HStack(alignment: .firstTextBaseline, spacing: 8) {
380 HStack(alignment: .firstTextBaseline, spacing: 8) {
381 headlineNumber(
381 headlineNumber(
382− text: "Z \\(fiveHour)%",
382+ text: "🅉 \(fiveHour)%",
383 color: Self.usageColor(fiveHour),
383 color: Self.usageColor(fiveHour),
384 trend: viewModel.zaiTrend,
384 trend: viewModel.zaiTrend,
385 copiedConfirmation: viewModel.copiedZaiConfirmation,
385 copiedConfirmation: viewModel.copiedZaiConfirmation,
UsageMenubar/ViewModel.swift+3−3
@@ -243,7 +243,7 @@ final class ViewModel: ObservableObject {
243 /// separated by spaces. An unconfigured service produces no segment.
243 /// separated by spaces. An unconfigured service produces no segment.
244 /// When z.ai is in peak hours, a 🔶 marker is appended to the z.ai segment.
244 /// When z.ai is in peak hours, a 🔶 marker is appended to the z.ai segment.
245 ///
245 ///
246− /// Example: `⚡42 · ⁕62% 8% · Z12% 3%🔶`
246+ /// Example: `⚡42 · 🅲62% 8% · 🅉12% 3%🔶`
247 ///
247 ///
248 /// Takes its values as parameters rather than reading the properties: `@Published`
248 /// Takes its values as parameters rather than reading the properties: `@Published`
249 /// publishes in `willSet`, so a Combine subscriber that called back into the view
249 /// publishes in `willSet`, so a Combine subscriber that called back into the view
@@ -274,7 +274,7 @@ final class ViewModel: ObservableObject {
274 claudeWindows.append("\(sevenDay)%")
274 claudeWindows.append("\(sevenDay)%")
275 }
275 }
276 if !claudeWindows.isEmpty {
276 if !claudeWindows.isEmpty {
277− segments.append("⁕" + claudeWindows.joined(separator: " "))
277+ segments.append("🅲" + claudeWindows.joined(separator: " "))
278 }
278 }
279
279
280 // z.ai — provider emoji + bare percentages
280 // z.ai — provider emoji + bare percentages
@@ -286,7 +286,7 @@ final class ViewModel: ObservableObject {
286 zaiWindows.append("\(zaiWeekly)%")
286 zaiWindows.append("\(zaiWeekly)%")
287 }
287 }
288 if !zaiWindows.isEmpty {
288 if !zaiWindows.isEmpty {
289− var segment = "Z" + zaiWindows.joined(separator: " ")
289+ var segment = "🅉" + zaiWindows.joined(separator: " ")
290 if zaiInPeakHours {
290 if zaiInPeakHours {
291 segment += "🔶"
291 segment += "🔶"
292 }
292 }
UsageMenubarTests/StatusBarTextTests.swift+22−22
@@ -18,7 +18,7 @@ final class StatusBarTextTests: XCTestCase {
18 zaiFiveHourPercent: nil, zaiWeeklyPercent: nil,
18 zaiFiveHourPercent: nil, zaiWeeklyPercent: nil,
19 zaiInPeakHours: false
19 zaiInPeakHours: false
20 )
20 )
21− XCTAssertEqual(title, "⚡42 · \u{2055}62% 8%")
21+ XCTAssertEqual(title, "⚡42 · \u{1F172}62% 8%")
22 }
22 }
23
23
24 func testHyperOnly() {
24 func testHyperOnly() {
@@ -40,7 +40,7 @@ final class StatusBarTextTests: XCTestCase {
40 zaiFiveHourPercent: nil, zaiWeeklyPercent: nil,
40 zaiFiveHourPercent: nil, zaiWeeklyPercent: nil,
41 zaiInPeakHours: false
41 zaiInPeakHours: false
42 )
42 )
43− XCTAssertEqual(title, "\u{2055}62% 8%")
43+ XCTAssertEqual(title, "\u{1F172}62% 8%")
44 }
44 }
45
45
46 func testLoadingWithNothingToShow() {
46 func testLoadingWithNothingToShow() {
@@ -76,7 +76,7 @@ final class StatusBarTextTests: XCTestCase {
76 zaiFiveHourPercent: nil, zaiWeeklyPercent: nil,
76 zaiFiveHourPercent: nil, zaiWeeklyPercent: nil,
77 zaiInPeakHours: false
77 zaiInPeakHours: false
78 )
78 )
79− XCTAssertEqual(title, "\u{2055}62%")
79+ XCTAssertEqual(title, "\u{1F172}62%")
80 }
80 }
81
81
82 func testLoadingKeepsAStaleBalanceOnScreen() {
82 func testLoadingKeepsAStaleBalanceOnScreen() {
@@ -87,7 +87,7 @@ final class StatusBarTextTests: XCTestCase {
87 zaiFiveHourPercent: nil, zaiWeeklyPercent: nil,
87 zaiFiveHourPercent: nil, zaiWeeklyPercent: nil,
88 zaiInPeakHours: false
88 zaiInPeakHours: false
89 )
89 )
90− XCTAssertEqual(title, "⚡42 · \u{2055}62% 8%")
90+ XCTAssertEqual(title, "⚡42 · \u{1F172}62% 8%")
91 }
91 }
92
92
93 func testZeroPercentIsShown() {
93 func testZeroPercentIsShown() {
@@ -98,7 +98,7 @@ final class StatusBarTextTests: XCTestCase {
98 zaiFiveHourPercent: nil, zaiWeeklyPercent: nil,
98 zaiFiveHourPercent: nil, zaiWeeklyPercent: nil,
99 zaiInPeakHours: false
99 zaiInPeakHours: false
100 )
100 )
101− XCTAssertEqual(title, "⚡42 · \u{2055}0% 8%")
101+ XCTAssertEqual(title, "⚡42 · \u{1F172}0% 8%")
102 }
102 }
103
103
104 func testBothClaudeWindowsZero() {
104 func testBothClaudeWindowsZero() {
@@ -109,7 +109,7 @@ final class StatusBarTextTests: XCTestCase {
109 zaiFiveHourPercent: nil, zaiWeeklyPercent: nil,
109 zaiFiveHourPercent: nil, zaiWeeklyPercent: nil,
110 zaiInPeakHours: false
110 zaiInPeakHours: false
111 )
111 )
112− XCTAssertEqual(title, "⚡42 · \u{2055}0% 0%")
112+ XCTAssertEqual(title, "⚡42 · \u{1F172}0% 0%")
113 }
113 }
114
114
115 func testFiveHourOnly() {
115 func testFiveHourOnly() {
@@ -120,7 +120,7 @@ final class StatusBarTextTests: XCTestCase {
120 zaiFiveHourPercent: nil, zaiWeeklyPercent: nil,
120 zaiFiveHourPercent: nil, zaiWeeklyPercent: nil,
121 zaiInPeakHours: false
121 zaiInPeakHours: false
122 )
122 )
123− XCTAssertEqual(title, "\u{2055}62%")
123+ XCTAssertEqual(title, "\u{1F172}62%")
124 }
124 }
125
125
126 func testSevenDayOnly() {
126 func testSevenDayOnly() {
@@ -131,7 +131,7 @@ final class StatusBarTextTests: XCTestCase {
131 zaiFiveHourPercent: nil, zaiWeeklyPercent: nil,
131 zaiFiveHourPercent: nil, zaiWeeklyPercent: nil,
132 zaiInPeakHours: false
132 zaiInPeakHours: false
133 )
133 )
134− XCTAssertEqual(title, "\u{2055}8%")
134+ XCTAssertEqual(title, "\u{1F172}8%")
135 }
135 }
136
136
137 // MARK: - Hyper not configured
137 // MARK: - Hyper not configured
@@ -168,7 +168,7 @@ final class StatusBarTextTests: XCTestCase {
168 zaiFiveHourPercent: nil, zaiWeeklyPercent: nil,
168 zaiFiveHourPercent: nil, zaiWeeklyPercent: nil,
169 zaiInPeakHours: false
169 zaiInPeakHours: false
170 )
170 )
171− XCTAssertEqual(title, "\u{2055}62% 8%")
171+ XCTAssertEqual(title, "\u{1F172}62% 8%")
172 }
172 }
173
173
174 // MARK: - z.ai in Title
174 // MARK: - z.ai in Title
@@ -181,7 +181,7 @@ final class StatusBarTextTests: XCTestCase {
181 zaiFiveHourPercent: 12, zaiWeeklyPercent: nil,
181 zaiFiveHourPercent: 12, zaiWeeklyPercent: nil,
182 zaiInPeakHours: false
182 zaiInPeakHours: false
183 )
183 )
184− XCTAssertEqual(title, "\u{005A}12%")
184+ XCTAssertEqual(title, "\u{1F149}12%")
185 }
185 }
186
186
187 func testZaiWithHyper() {
187 func testZaiWithHyper() {
@@ -192,7 +192,7 @@ final class StatusBarTextTests: XCTestCase {
192 zaiFiveHourPercent: 12, zaiWeeklyPercent: nil,
192 zaiFiveHourPercent: 12, zaiWeeklyPercent: nil,
193 zaiInPeakHours: false
193 zaiInPeakHours: false
194 )
194 )
195− XCTAssertEqual(title, "⚡42 · \u{005A}12%")
195+ XCTAssertEqual(title, "⚡42 · \u{1F149}12%")
196 }
196 }
197
197
198 func testZaiWithClaude() {
198 func testZaiWithClaude() {
@@ -203,7 +203,7 @@ final class StatusBarTextTests: XCTestCase {
203 zaiFiveHourPercent: 12, zaiWeeklyPercent: nil,
203 zaiFiveHourPercent: 12, zaiWeeklyPercent: nil,
204 zaiInPeakHours: false
204 zaiInPeakHours: false
205 )
205 )
206− XCTAssertEqual(title, "\u{2055}62% 8% · \u{005A}12%")
206+ XCTAssertEqual(title, "\u{1F172}62% 8% · \u{1F149}12%")
207 }
207 }
208
208
209 func testAllThreeServices() {
209 func testAllThreeServices() {
@@ -214,7 +214,7 @@ final class StatusBarTextTests: XCTestCase {
214 zaiFiveHourPercent: 12, zaiWeeklyPercent: 3,
214 zaiFiveHourPercent: 12, zaiWeeklyPercent: 3,
215 zaiInPeakHours: false
215 zaiInPeakHours: false
216 )
216 )
217− XCTAssertEqual(title, "⚡42 · \u{2055}62% 8% · \u{005A}12% 3%")
217+ XCTAssertEqual(title, "⚡42 · \u{1F172}62% 8% · \u{1F149}12% 3%")
218 }
218 }
219
219
220 func testZaiZeroIsShown() {
220 func testZaiZeroIsShown() {
@@ -225,7 +225,7 @@ final class StatusBarTextTests: XCTestCase {
225 zaiFiveHourPercent: 0, zaiWeeklyPercent: 0,
225 zaiFiveHourPercent: 0, zaiWeeklyPercent: 0,
226 zaiInPeakHours: false
226 zaiInPeakHours: false
227 )
227 )
228− XCTAssertEqual(title, "⚡42 · \u{005A}0% 0%")
228+ XCTAssertEqual(title, "⚡42 · \u{1F149}0% 0%")
229 }
229 }
230
230
231 func testLoadingKeepsZaiPercentOnScreen() {
231 func testLoadingKeepsZaiPercentOnScreen() {
@@ -236,7 +236,7 @@ final class StatusBarTextTests: XCTestCase {
236 zaiFiveHourPercent: 12, zaiWeeklyPercent: nil,
236 zaiFiveHourPercent: 12, zaiWeeklyPercent: nil,
237 zaiInPeakHours: false
237 zaiInPeakHours: false
238 )
238 )
239− XCTAssertEqual(title, "\u{005A}12%")
239+ XCTAssertEqual(title, "\u{1F149}12%")
240 }
240 }
241
241
242 // MARK: - z.ai Weekly
242 // MARK: - z.ai Weekly
@@ -249,7 +249,7 @@ final class StatusBarTextTests: XCTestCase {
249 zaiFiveHourPercent: nil, zaiWeeklyPercent: 3,
249 zaiFiveHourPercent: nil, zaiWeeklyPercent: 3,
250 zaiInPeakHours: false
250 zaiInPeakHours: false
251 )
251 )
252− XCTAssertEqual(title, "\u{005A}3%")
252+ XCTAssertEqual(title, "\u{1F149}3%")
253 }
253 }
254
254
255 func testZaiWeeklyWithFiveHour() {
255 func testZaiWeeklyWithFiveHour() {
@@ -260,7 +260,7 @@ final class StatusBarTextTests: XCTestCase {
260 zaiFiveHourPercent: 12, zaiWeeklyPercent: 3,
260 zaiFiveHourPercent: 12, zaiWeeklyPercent: 3,
261 zaiInPeakHours: false
261 zaiInPeakHours: false
262 )
262 )
263− XCTAssertEqual(title, "\u{005A}12% 3%")
263+ XCTAssertEqual(title, "\u{1F149}12% 3%")
264 }
264 }
265
265
266 func testZaiWeeklyZeroIsShown() {
266 func testZaiWeeklyZeroIsShown() {
@@ -271,7 +271,7 @@ final class StatusBarTextTests: XCTestCase {
271 zaiFiveHourPercent: 12, zaiWeeklyPercent: 0,
271 zaiFiveHourPercent: 12, zaiWeeklyPercent: 0,
272 zaiInPeakHours: false
272 zaiInPeakHours: false
273 )
273 )
274− XCTAssertEqual(title, "\u{005A}12% 0%")
274+ XCTAssertEqual(title, "\u{1F149}12% 0%")
275 }
275 }
276
276
277 func testLoadingKeepsZaiWeeklyOnScreen() {
277 func testLoadingKeepsZaiWeeklyOnScreen() {
@@ -282,7 +282,7 @@ final class StatusBarTextTests: XCTestCase {
282 zaiFiveHourPercent: nil, zaiWeeklyPercent: 3,
282 zaiFiveHourPercent: nil, zaiWeeklyPercent: 3,
283 zaiInPeakHours: false
283 zaiInPeakHours: false
284 )
284 )
285− XCTAssertEqual(title, "\u{005A}3%")
285+ XCTAssertEqual(title, "\u{1F149}3%")
286 }
286 }
287
287
288 // MARK: - z.ai Peak Hours
288 // MARK: - z.ai Peak Hours
@@ -295,7 +295,7 @@ final class StatusBarTextTests: XCTestCase {
295 zaiFiveHourPercent: 12, zaiWeeklyPercent: 3,
295 zaiFiveHourPercent: 12, zaiWeeklyPercent: 3,
296 zaiInPeakHours: true
296 zaiInPeakHours: true
297 )
297 )
298− XCTAssertEqual(title, "⚡42 · \u{2055}62% 8% · \u{005A}12% 3%\u{1F536}")
298+ XCTAssertEqual(title, "⚡42 · \u{1F172}62% 8% · \u{1F149}12% 3%\u{1F536}")
299 }
299 }
300
300
301 func testPeakHoursWithZaiOnly() {
301 func testPeakHoursWithZaiOnly() {
@@ -306,7 +306,7 @@ final class StatusBarTextTests: XCTestCase {
306 zaiFiveHourPercent: 12, zaiWeeklyPercent: nil,
306 zaiFiveHourPercent: 12, zaiWeeklyPercent: nil,
307 zaiInPeakHours: true
307 zaiInPeakHours: true
308 )
308 )
309− XCTAssertEqual(title, "\u{005A}12%\u{1F536}")
309+ XCTAssertEqual(title, "\u{1F149}12%\u{1F536}")
310 }
310 }
311
311
312 func testPeakHoursIgnoredWhenZaiNotConfigured() {
312 func testPeakHoursIgnoredWhenZaiNotConfigured() {
@@ -318,7 +318,7 @@ final class StatusBarTextTests: XCTestCase {
318 zaiInPeakHours: true
318 zaiInPeakHours: true
319 )
319 )
320 // No z.ai segment, so peak marker has nothing to attach to.
320 // No z.ai segment, so peak marker has nothing to attach to.
321− XCTAssertEqual(title, "⚡42 · \u{2055}62% 8%")
321+ XCTAssertEqual(title, "⚡42 · \u{1F172}62% 8%")
322 }
322 }
323
323
324 // MARK: - Claude Window Percentages
324 // MARK: - Claude Window Percentages