on master help sign in
Replace provider emojis: Claude ๐Ÿ…ฒโ†’โ•, z.ai ๐Ÿ…‰โ†’Z
commit93d0b8354740d7b0001199e98505af823b5874fd authorLancelot <montelot-dev@typeable.io> dateFri 17 Jul 2026, 17:15 (3 months ago) parentb2c392e
5 files changed+35โˆ’35
unifiedsplit
CLAUDE.md+2โˆ’2 README.md+6โˆ’6 UsageMenubar/MenuView.swift+2โˆ’2 UsageMenubar/ViewModel.swift+3โˆ’3 UsageMenubarTests/StatusBarTextTests.swift+22โˆ’22
CLAUDE.md+2โˆ’2
@@ -20,8 +20,8 @@ click-to-copy on the headline, and threshold-crossing notifications.
2020- `KeychainHelper.swift` โ€” Keychain wrapper (KeychainStore + KeychainHelper + ZaiKeychainHelper)
2121- `VersionFormatter.swift` โ€” Version string formatting
2222
23โˆ’The status bar title shows `โšก{balance}` (Hyper), `๐Ÿ•{percent}%` (Claude 5-hour),
24โˆ’`๐Ÿ“…{percent}%` (Claude 7-day), `๐Ÿค–{percent}%` (z.ai 5-hour), and `๐Ÿ“†{percent}%`
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}%`
2525(z.ai weekly) โ€” each omitted when absent or at 0%, and entirely absent when the
2626service is not configured (no API key or no credentials). An unconfigured service
2727produces 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
99## Features
1010
1111- โšก **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
13โˆ’- ๐Ÿค– **z.ai Coding Plan** โ€” 5-hour and weekly token quota percentages, with reset countdowns. Enter your API key in settings
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
1414- ๐Ÿ”„ **Auto-refresh** โ€” configurable interval (1m / 5m / 15m / 30m), plus refresh on wake from sleep
1515- ๐Ÿ–ฑ๏ธ **Right-click menu** โ€” quick access to Refresh and Quit
1616- ๐Ÿ”’ **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%:
2424| Service | Emoji | Example |
2525|---------|-------|---------|
2626| Hyper balance | โšก | `โšก42` |
27โˆ’| Claude 5-hour | ๐Ÿ• | `๐Ÿ•62%` |
28โˆ’| Claude 7-day | ๐Ÿ“… | `๐Ÿ“…8%` |
29โˆ’| z.ai 5-hour | ๐Ÿค– | `๐Ÿค–12%` |
30โˆ’| z.ai weekly | ๐Ÿ“† | `๐Ÿ“†3%` |
27+| Claude 5-hour | โ• | `โ•62%` |
28+| Claude 7-day | โ• | `โ•8%` |
29+| z.ai 5-hour | Z | `Z12%` |
30+| z.ai weekly | Z | `Z3%` |
3131
3232Unconfigured services don't appear at all โ€” no placeholder, no icon.
3333
UsageMenubar/MenuView.swift+2โˆ’2
@@ -259,7 +259,7 @@ struct MenuView: View {
259259 VStack(spacing: 8) {
260260 HStack(alignment: .firstTextBaseline, spacing: 8) {
261261 headlineNumber(
262โˆ’ text: "๐Ÿ…ฒ \(percent)%",
262+ text: "โ• \\(percent)%",
263263 color: Self.usageColor(percent),
264264 trend: viewModel.claudeTrend,
265265 copiedConfirmation: viewModel.copiedClaudeConfirmation,
@@ -379,7 +379,7 @@ struct MenuView: View {
379379 VStack(spacing: 8) {
380380 HStack(alignment: .firstTextBaseline, spacing: 8) {
381381 headlineNumber(
382โˆ’ text: "๐Ÿ…‰ \(fiveHour)%",
382+ text: "Z \\(fiveHour)%",
383383 color: Self.usageColor(fiveHour),
384384 trend: viewModel.zaiTrend,
385385 copiedConfirmation: viewModel.copiedZaiConfirmation,
UsageMenubar/ViewModel.swift+3โˆ’3
@@ -243,7 +243,7 @@ final class ViewModel: ObservableObject {
243243 /// separated by spaces. An unconfigured service produces no segment.
244244 /// When z.ai is in peak hours, a ๐Ÿ”ถ marker is appended to the z.ai segment.
245245 ///
246โˆ’ /// Example: `โšก42 ยท ๐Ÿ…ฒ62% 8% ยท ๐Ÿ…‰12% 3%๐Ÿ”ถ`
246+ /// Example: `โšก42 ยท โ•62% 8% ยท Z12% 3%๐Ÿ”ถ`
247247 ///
248248 /// Takes its values as parameters rather than reading the properties: `@Published`
249249 /// publishes in `willSet`, so a Combine subscriber that called back into the view
@@ -274,7 +274,7 @@ final class ViewModel: ObservableObject {
274274 claudeWindows.append("\(sevenDay)%")
275275 }
276276 if !claudeWindows.isEmpty {
277โˆ’ segments.append("๐Ÿ…ฒ" + claudeWindows.joined(separator: " "))
277+ segments.append("โ•" + claudeWindows.joined(separator: " "))
278278 }
279279
280280 // z.ai โ€” provider emoji + bare percentages
@@ -286,7 +286,7 @@ final class ViewModel: ObservableObject {
286286 zaiWindows.append("\(zaiWeekly)%")
287287 }
288288 if !zaiWindows.isEmpty {
289โˆ’ var segment = "๐Ÿ…‰" + zaiWindows.joined(separator: " ")
289+ var segment = "Z" + zaiWindows.joined(separator: " ")
290290 if zaiInPeakHours {
291291 segment += "๐Ÿ”ถ"
292292 }
UsageMenubarTests/StatusBarTextTests.swift+22โˆ’22
@@ -18,7 +18,7 @@ final class StatusBarTextTests: XCTestCase {
1818 zaiFiveHourPercent: nil, zaiWeeklyPercent: nil,
1919 zaiInPeakHours: false
2020 )
21โˆ’ XCTAssertEqual(title, "โšก42 ยท \u{1F172}62% 8%")
21+ XCTAssertEqual(title, "โšก42 ยท \u{2055}62% 8%")
2222 }
2323
2424 func testHyperOnly() {
@@ -40,7 +40,7 @@ final class StatusBarTextTests: XCTestCase {
4040 zaiFiveHourPercent: nil, zaiWeeklyPercent: nil,
4141 zaiInPeakHours: false
4242 )
43โˆ’ XCTAssertEqual(title, "\u{1F172}62% 8%")
43+ XCTAssertEqual(title, "\u{2055}62% 8%")
4444 }
4545
4646 func testLoadingWithNothingToShow() {
@@ -76,7 +76,7 @@ final class StatusBarTextTests: XCTestCase {
7676 zaiFiveHourPercent: nil, zaiWeeklyPercent: nil,
7777 zaiInPeakHours: false
7878 )
79โˆ’ XCTAssertEqual(title, "\u{1F172}62%")
79+ XCTAssertEqual(title, "\u{2055}62%")
8080 }
8181
8282 func testLoadingKeepsAStaleBalanceOnScreen() {
@@ -87,7 +87,7 @@ final class StatusBarTextTests: XCTestCase {
8787 zaiFiveHourPercent: nil, zaiWeeklyPercent: nil,
8888 zaiInPeakHours: false
8989 )
90โˆ’ XCTAssertEqual(title, "โšก42 ยท \u{1F172}62% 8%")
90+ XCTAssertEqual(title, "โšก42 ยท \u{2055}62% 8%")
9191 }
9292
9393 func testZeroPercentIsShown() {
@@ -98,7 +98,7 @@ final class StatusBarTextTests: XCTestCase {
9898 zaiFiveHourPercent: nil, zaiWeeklyPercent: nil,
9999 zaiInPeakHours: false
100100 )
101โˆ’ XCTAssertEqual(title, "โšก42 ยท \u{1F172}0% 8%")
101+ XCTAssertEqual(title, "โšก42 ยท \u{2055}0% 8%")
102102 }
103103
104104 func testBothClaudeWindowsZero() {
@@ -109,7 +109,7 @@ final class StatusBarTextTests: XCTestCase {
109109 zaiFiveHourPercent: nil, zaiWeeklyPercent: nil,
110110 zaiInPeakHours: false
111111 )
112โˆ’ XCTAssertEqual(title, "โšก42 ยท \u{1F172}0% 0%")
112+ XCTAssertEqual(title, "โšก42 ยท \u{2055}0% 0%")
113113 }
114114
115115 func testFiveHourOnly() {
@@ -120,7 +120,7 @@ final class StatusBarTextTests: XCTestCase {
120120 zaiFiveHourPercent: nil, zaiWeeklyPercent: nil,
121121 zaiInPeakHours: false
122122 )
123โˆ’ XCTAssertEqual(title, "\u{1F172}62%")
123+ XCTAssertEqual(title, "\u{2055}62%")
124124 }
125125
126126 func testSevenDayOnly() {
@@ -131,7 +131,7 @@ final class StatusBarTextTests: XCTestCase {
131131 zaiFiveHourPercent: nil, zaiWeeklyPercent: nil,
132132 zaiInPeakHours: false
133133 )
134โˆ’ XCTAssertEqual(title, "\u{1F172}8%")
134+ XCTAssertEqual(title, "\u{2055}8%")
135135 }
136136
137137 // MARK: - Hyper not configured
@@ -168,7 +168,7 @@ final class StatusBarTextTests: XCTestCase {
168168 zaiFiveHourPercent: nil, zaiWeeklyPercent: nil,
169169 zaiInPeakHours: false
170170 )
171โˆ’ XCTAssertEqual(title, "\u{1F172}62% 8%")
171+ XCTAssertEqual(title, "\u{2055}62% 8%")
172172 }
173173
174174 // MARK: - z.ai in Title
@@ -181,7 +181,7 @@ final class StatusBarTextTests: XCTestCase {
181181 zaiFiveHourPercent: 12, zaiWeeklyPercent: nil,
182182 zaiInPeakHours: false
183183 )
184โˆ’ XCTAssertEqual(title, "\u{1F149}12%")
184+ XCTAssertEqual(title, "\u{005A}12%")
185185 }
186186
187187 func testZaiWithHyper() {
@@ -192,7 +192,7 @@ final class StatusBarTextTests: XCTestCase {
192192 zaiFiveHourPercent: 12, zaiWeeklyPercent: nil,
193193 zaiInPeakHours: false
194194 )
195โˆ’ XCTAssertEqual(title, "โšก42 ยท \u{1F149}12%")
195+ XCTAssertEqual(title, "โšก42 ยท \u{005A}12%")
196196 }
197197
198198 func testZaiWithClaude() {
@@ -203,7 +203,7 @@ final class StatusBarTextTests: XCTestCase {
203203 zaiFiveHourPercent: 12, zaiWeeklyPercent: nil,
204204 zaiInPeakHours: false
205205 )
206โˆ’ XCTAssertEqual(title, "\u{1F172}62% 8% ยท \u{1F149}12%")
206+ XCTAssertEqual(title, "\u{2055}62% 8% ยท \u{005A}12%")
207207 }
208208
209209 func testAllThreeServices() {
@@ -214,7 +214,7 @@ final class StatusBarTextTests: XCTestCase {
214214 zaiFiveHourPercent: 12, zaiWeeklyPercent: 3,
215215 zaiInPeakHours: false
216216 )
217โˆ’ XCTAssertEqual(title, "โšก42 ยท \u{1F172}62% 8% ยท \u{1F149}12% 3%")
217+ XCTAssertEqual(title, "โšก42 ยท \u{2055}62% 8% ยท \u{005A}12% 3%")
218218 }
219219
220220 func testZaiZeroIsShown() {
@@ -225,7 +225,7 @@ final class StatusBarTextTests: XCTestCase {
225225 zaiFiveHourPercent: 0, zaiWeeklyPercent: 0,
226226 zaiInPeakHours: false
227227 )
228โˆ’ XCTAssertEqual(title, "โšก42 ยท \u{1F149}0% 0%")
228+ XCTAssertEqual(title, "โšก42 ยท \u{005A}0% 0%")
229229 }
230230
231231 func testLoadingKeepsZaiPercentOnScreen() {
@@ -236,7 +236,7 @@ final class StatusBarTextTests: XCTestCase {
236236 zaiFiveHourPercent: 12, zaiWeeklyPercent: nil,
237237 zaiInPeakHours: false
238238 )
239โˆ’ XCTAssertEqual(title, "\u{1F149}12%")
239+ XCTAssertEqual(title, "\u{005A}12%")
240240 }
241241
242242 // MARK: - z.ai Weekly
@@ -249,7 +249,7 @@ final class StatusBarTextTests: XCTestCase {
249249 zaiFiveHourPercent: nil, zaiWeeklyPercent: 3,
250250 zaiInPeakHours: false
251251 )
252โˆ’ XCTAssertEqual(title, "\u{1F149}3%")
252+ XCTAssertEqual(title, "\u{005A}3%")
253253 }
254254
255255 func testZaiWeeklyWithFiveHour() {
@@ -260,7 +260,7 @@ final class StatusBarTextTests: XCTestCase {
260260 zaiFiveHourPercent: 12, zaiWeeklyPercent: 3,
261261 zaiInPeakHours: false
262262 )
263โˆ’ XCTAssertEqual(title, "\u{1F149}12% 3%")
263+ XCTAssertEqual(title, "\u{005A}12% 3%")
264264 }
265265
266266 func testZaiWeeklyZeroIsShown() {
@@ -271,7 +271,7 @@ final class StatusBarTextTests: XCTestCase {
271271 zaiFiveHourPercent: 12, zaiWeeklyPercent: 0,
272272 zaiInPeakHours: false
273273 )
274โˆ’ XCTAssertEqual(title, "\u{1F149}12% 0%")
274+ XCTAssertEqual(title, "\u{005A}12% 0%")
275275 }
276276
277277 func testLoadingKeepsZaiWeeklyOnScreen() {
@@ -282,7 +282,7 @@ final class StatusBarTextTests: XCTestCase {
282282 zaiFiveHourPercent: nil, zaiWeeklyPercent: 3,
283283 zaiInPeakHours: false
284284 )
285โˆ’ XCTAssertEqual(title, "\u{1F149}3%")
285+ XCTAssertEqual(title, "\u{005A}3%")
286286 }
287287
288288 // MARK: - z.ai Peak Hours
@@ -295,7 +295,7 @@ final class StatusBarTextTests: XCTestCase {
295295 zaiFiveHourPercent: 12, zaiWeeklyPercent: 3,
296296 zaiInPeakHours: true
297297 )
298โˆ’ XCTAssertEqual(title, "โšก42 ยท \u{1F172}62% 8% ยท \u{1F149}12% 3%\u{1F536}")
298+ XCTAssertEqual(title, "โšก42 ยท \u{2055}62% 8% ยท \u{005A}12% 3%\u{1F536}")
299299 }
300300
301301 func testPeakHoursWithZaiOnly() {
@@ -306,7 +306,7 @@ final class StatusBarTextTests: XCTestCase {
306306 zaiFiveHourPercent: 12, zaiWeeklyPercent: nil,
307307 zaiInPeakHours: true
308308 )
309โˆ’ XCTAssertEqual(title, "\u{1F149}12%\u{1F536}")
309+ XCTAssertEqual(title, "\u{005A}12%\u{1F536}")
310310 }
311311
312312 func testPeakHoursIgnoredWhenZaiNotConfigured() {
@@ -318,7 +318,7 @@ final class StatusBarTextTests: XCTestCase {
318318 zaiInPeakHours: true
319319 )
320320 // No z.ai segment, so peak marker has nothing to attach to.
321โˆ’ XCTAssertEqual(title, "โšก42 ยท \u{1F172}62% 8%")
321+ XCTAssertEqual(title, "โšก42 ยท \u{2055}62% 8%")
322322 }
323323
324324 // MARK: - Claude Window Percentages