DEV Community

Cover image for A 13MB Cursor Monitor: AppKit, Undocumented APIs, Zero Dependencies
Woojin Ahn
Woojin Ahn

Posted on

A 13MB Cursor Monitor: AppKit, Undocumented APIs, Zero Dependencies

My company recently started supporting Cursor, so I jumped in. But checking usage meant opening the dashboard every time — and if Max mode was on without me noticing, requests burned through fast.

I wanted a glanceable counter, always visible.

So I built one.

Menu bar showing CursorMeter with usage fraction

V1: SwiftUI — Done in a Weekend, Regretted in a Week

The first version took two days. SwiftUI's MenuBarExtra made it trivial to get something on screen. I was proud of it.

Then I checked Activity Monitor.

56 MB of memory. For a menu bar app that displays a single number.

I tried to optimize — removed animations, simplified views, cached aggressively. Nothing moved the needle. The SwiftUI runtime itself was the cost. MenuBarExtra allocates a full SwiftUI rendering pipeline whether you need it or not.

For a "set and forget" menu bar app that runs 24/7, 56MB felt like a tax I shouldn't be paying.

The Rewrite: Burning It All Down

I threw out the entire UI layer and rebuilt everything in pure AppKit. NSStatusItem, NSPopover, NSViewController — the APIs your framework tries to hide from you.

It was painful. SwiftUI's declarative bindings became manual NSTextField updates. Layout constraints replaced VStack. Every state change needed explicit UI synchronization.

But the result:

SwiftUI AppKit
Memory (RSS) ~56 MB ~13 MB
Lines of UI code ~200 ~660

3x more code, 4x less memory. For a menu bar app, I'd make that trade every time.

Other SwiftUI-based menu bar apps I've seen typically sit around 30–50MB. The framework overhead is real.

The API That Doesn't Exist

Cursor has no public API for usage data. But their dashboard has to get the numbers from somewhere.

Browser DevTools → Network tab → three requests:

  • /api/usage — per-model request counts
  • /api/usage-summary — billing cycle, plan usage in USD cents
  • /api/auth/me — user info

No documentation. Cookie-based auth. The response shape has changed at least once since I started.

This meant two things:

1. No hardcoding. The /api/usage response contains model names as dynamic keys. If Cursor adds claude-4-opus tomorrow, the parser picks it up automatically — no app update needed.

2. Expect failure. Both API calls run in parallel. If /api/usage-summary fails but /api/usage succeeds, the app shows what it can. If both fail, it shows the last known data. The app never crashes on an API change — it just degrades gracefully.

Zero Dependencies, on Purpose

The Package.swift imports nothing. No Alamofire, no KeychainAccess, no SwiftyJSON. Just macOS SDK frameworks.

This wasn't ideology. It was pragmatism:

  • The app does three HTTP requests. I don't need a networking library for that.
  • Keychain access is ~80 lines of Security framework calls. A dependency would be longer.
  • JSON decoding is Codable. It's built in.

The side benefit: no supply chain to audit, no version conflicts, no "this dependency requires macOS 15 but I target 14" surprises.

What I Actually Learned

AppKit isn't dead — it's just unfashionable. For long-running, low-footprint apps, the memory savings are real and measurable. SwiftUI is better for most things. But not this.

Swift 6 strict concurrency is worth the pain. The API client is an actor. The UI is @MainActor. All models are Sendable. It took effort to get it compiling, but an entire category of bugs simply can't happen now.

Build for the API to break. When you depend on undocumented endpoints, "it works today" is the only guarantee. Dynamic parsing + graceful degradation isn't a nice-to-have — it's the whole architecture.

The App

Popover showing usage details

CursorMeter is open source (MIT). macOS 14+, zero dependencies, ~13MB memory.

curl -sL https://raw.githubusercontent.com/WoojinAhn/CursorMeter/main/Scripts/install.sh | bash
Enter fullscreen mode Exit fullscreen mode

If you use Cursor daily and want to keep an eye on your usage without opening a browser, give it a try. Issues and feedback are always welcome.

Top comments (0)