Skip to content

Commit e8c1925

Browse files
committed
Add native macOS WidgetKit support
1 parent 17439b0 commit e8c1925

14 files changed

Lines changed: 1302 additions & 30 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ jobs:
3131
- name: Compile application
3232
run: python -m py_compile src/ai_usage_widget.py
3333

34+
- name: Compile macOS application
35+
if: runner.os == 'macOS'
36+
run: python -m py_compile src/ai_usage_widget_macos.py
37+
3438
- name: Run tests
3539
run: python -m unittest discover -s tests -v
3640

@@ -41,3 +45,4 @@ jobs:
4145
sh -n scripts/macos/uninstall.sh
4246
sh -n packaging/macos/ai-usage-widget
4347
plutil -lint packaging/macos/Info.plist
48+
plutil -lint packaging/macos/widget/Info.plist

‎README.md‎

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,37 @@
11
# AI Usage Widget
22

3-
![Version](https://img.shields.io/badge/version-1.0.0-blue)
3+
![Windows version](https://img.shields.io/badge/Windows-1.0.0-blue)
4+
![macOS version](https://img.shields.io/badge/macOS-1.1.4-blue)
45
![Platforms](https://img.shields.io/badge/platform-Windows%20%7C%20macOS-lightgrey)
56
![License](https://img.shields.io/badge/license-MIT-green)
67

7-
A compact Windows system-tray and macOS menu-bar widget for monitoring AI subscription and agent usage across:
8+
A compact Windows system-tray app and native macOS app with a WidgetKit desktop widget for monitoring AI subscription and agent usage across:
89

910
- **Claude / Claude Code**
1011
- **ChatGPT / Codex**
1112
- **Cursor**
1213
- **Cursor per-model usage breakdown**
1314

14-
The widget is designed to stay out of the way: it lives in the Windows system tray or macOS menu bar and opens on demand.
15+
The Windows app lives in the system tray. On macOS, the app behaves like a normal Dock application and can supply an optional desktop widget.
1516

1617
## Preview
1718

18-
![AI Usage Widget showing Claude, ChatGPT/Codex, and Cursor usage](assets/ai-usage-widget-preview.png)
19+
<table>
20+
<tr>
21+
<th>Windows</th>
22+
<th>macOS</th>
23+
</tr>
24+
<tr>
25+
<td align="center"><img src="assets/ai-usage-widget-preview.png" alt="AI Usage Widget on Windows" height="500"></td>
26+
<td align="center"><img src="assets/ai-usage-widget-macos-app.jpg" alt="AI Usage Widget app on macOS" height="500"></td>
27+
</tr>
28+
</table>
29+
30+
### Widget
31+
32+
<p align="center">
33+
<img src="assets/ai-usage-widget-macos-widget.jpg" alt="AI Usage desktop widget on macOS" width="560">
34+
</p>
1935

2036
## Features
2137

@@ -26,17 +42,18 @@ The widget is designed to stay out of the way: it lives in the Windows system tr
2642
- Separate Cursor **Cursor Models** and **Other Models** pools
2743
- Expandable Cursor model table with requests, weighted usage and costs
2844
- Expandable **Model consumption** view with one bar per Cursor model
29-
- `AI` system-tray icon
45+
- `AI` system-tray icon on Windows
46+
- Native Dock application and WidgetKit widget on macOS
3047
- Manual refresh and 5-minute auto-refresh
3148
- Optional always-on-top mode
3249
- No API keys need to be pasted into the app
3350

3451
## Platform support
3552

3653
- **Windows 10/11:** stable
37-
- **macOS 12 or newer:** beta; the shared application code and installer are available, but the package still needs validation on real Mac hardware
54+
- **macOS 14 or newer:** native app bundle and WidgetKit extension built locally by the installer
3855

39-
Both platforms use the same application core. Only installation, menu-bar integration, icons, and local provider paths are platform-specific.
56+
The established Windows entry point remains unchanged. macOS has a separate native build entry point so its Dock, WidgetKit, Keychain, and Claude Desktop integrations do not change Windows behavior.
4057

4158
## Installation
4259

@@ -69,7 +86,7 @@ You can also run the repository copy directly:
6986
start_ai_usage_widget.bat
7087
```
7188

72-
### macOS (beta)
89+
### macOS
7390

7491
Clone or download the repository, open Terminal in the repository folder, and run:
7592

@@ -78,33 +95,50 @@ chmod +x scripts/macos/install.sh
7895
./scripts/macos/install.sh
7996
```
8097

98+
Before installation, install full Xcode, sign in under **Xcode > Settings > Accounts**, and create an **Apple Development** certificate.
99+
81100
The installer:
82101

83-
1. checks for Python 3.10+ and Tkinter,
84-
2. creates an isolated Python environment in `~/Library/Application Support/AIUsageWidget`,
85-
3. installs the required Python dependencies,
86-
4. creates `~/Applications/AI Usage Widget.app`,
87-
5. opens the widget in the macOS menu bar.
102+
1. checks for Python 3.10+, Tkinter, full Xcode, and a signing certificate,
103+
2. creates an isolated build environment inside the repository,
104+
3. builds a self-contained native macOS application with its own Dock icon,
105+
4. builds and embeds the signed WidgetKit extension,
106+
5. creates `~/Applications/AI Usage Widget.app`, registers it with macOS, and opens it.
107+
108+
The **AI Usage** widget can then be added from macOS **Edit Widgets** in small
109+
or medium size. The desktop app must be running for fresh usage data; WidgetKit
110+
keeps the most recent timeline entry between refreshes.
111+
112+
Closing the macOS app window quits the app. Launch it again from Finder,
113+
Spotlight, Launchpad, or the Dock. Use macOS **Keep in Dock** if you want its
114+
icon to remain there while the app is closed.
88115

89116
If Tkinter is missing, install Python from [python.org](https://www.python.org/downloads/macos/) or install the matching `python-tk` package through Homebrew.
90117

91118
## Requirements
92119

93-
- Windows 10/11 or macOS 12+
120+
- Windows 10/11 or macOS 14+
94121
- Python 3.10+
122+
- Full Xcode and an Apple Development certificate for the macOS WidgetKit build
95123
- For each provider you want to monitor, its corresponding local app/CLI must already be logged in.
96124

97125
### Claude
98126

99-
The app uses the existing Claude Code OAuth credentials stored by Claude Code.
127+
On macOS, the app first uses the existing Claude Code OAuth credentials from the
128+
macOS Keychain. If Claude Code is not installed, it can fall back to the current,
129+
non-sensitive usage cache written by the signed-in Claude Desktop app.
100130

101131
Typical location:
102132

103133
```text
104134
%USERPROFILE%\.claude\.credentials.json
105135
```
106136

107-
It can show normal 5h/7d limits when exposed by Anthropic. For accounts using **Extra Usage**, it displays used/remaining budget. Some Extra Usage account types do not expose a reset timestamp; in that case the widget explicitly says that the reset is not reported.
137+
Claude Desktop-only users should open Claude Desktop periodically so its local
138+
usage cache remains current. The app can show normal 5h/7d limits when exposed
139+
by Anthropic. For accounts using **Extra Usage**, it displays used and remaining
140+
budget. When Anthropic does not expose a reset timestamp, the widget says that
141+
the provider did not report one instead of estimating a date.
108142

109143
### ChatGPT / Codex
110144

@@ -150,7 +184,7 @@ means model A accounts for approximately 41% of the captured weighted usage.
150184

151185
AI Usage Widget does not ask you to paste API keys into the UI.
152186

153-
It reads the local authentication state already created by the installed provider tools and queries their usage endpoints.
187+
It reads the local authentication state already created by the installed provider tools and queries their usage endpoints. The macOS desktop-widget bridge exposes only usage percentages on `127.0.0.1`; it does not expose authentication tokens.
154188

155189
No telemetry is intentionally collected by this project.
156190

@@ -181,7 +215,8 @@ chmod +x scripts/macos/uninstall.sh
181215

182216
## Version
183217

184-
Current release: **1.0.0**
218+
- Windows: **1.0.0**
219+
- macOS: **1.1.4**
185220

186221
## License
187222

116 KB
Loading
25.1 KB
Loading

‎packaging/macos/generate_icon.py‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#!/usr/bin/env python3
2+
3+
"""Generate the macOS .icns asset used by the native app bundle."""
4+
5+
import sys
6+
from pathlib import Path
7+
8+
from PIL import Image, ImageDraw, ImageFont
9+
10+
11+
def font(size):
12+
candidates = (
13+
"/System/Library/Fonts/SFNSRounded.ttf",
14+
"/System/Library/Fonts/SFNS.ttf",
15+
"/System/Library/Fonts/Helvetica.ttc",
16+
"/System/Library/Fonts/Supplemental/Arial Bold.ttf",
17+
)
18+
for candidate in candidates:
19+
try:
20+
return ImageFont.truetype(candidate, size=size)
21+
except OSError:
22+
pass
23+
return ImageFont.load_default()
24+
25+
26+
def main():
27+
if len(sys.argv) != 2:
28+
raise SystemExit("usage: generate_icon.py OUTPUT.icns")
29+
30+
size = 1024
31+
image = Image.new("RGBA", (size, size), (0, 0, 0, 0))
32+
draw = ImageDraw.Draw(image)
33+
draw.rounded_rectangle(
34+
(64, 64, 960, 960),
35+
radius=220,
36+
fill=(20, 22, 28, 255),
37+
outline=(255, 255, 255, 255),
38+
width=34,
39+
)
40+
label_font = font(400)
41+
bounds = draw.textbbox((0, 0), "AI", font=label_font)
42+
text_width = bounds[2] - bounds[0]
43+
text_height = bounds[3] - bounds[1]
44+
position = ((size - text_width) / 2, (size - text_height) / 2 - bounds[1] - 12)
45+
draw.text(position, "AI", fill=(255, 255, 255, 255), font=label_font)
46+
47+
output = Path(sys.argv[1])
48+
output.parent.mkdir(parents=True, exist_ok=True)
49+
image.save(output, format="ICNS")
50+
51+
52+
if __name__ == "__main__":
53+
main()

0 commit comments

Comments
 (0)