Architecture and pitfalls met

The data flow, the components, cache rules and the engineering pitfalls met.

AIUsageUpdated 23 Oct 2026
kiến trúc

This page is for readers who want to understand or change the code: the data flow, the components and the pitfalls met along the way.

Data flow

claude.ai · Anthropic Console · chatgpt.com · agy · Google Cloud · ~/.claude · ~/.codex · remote host (SSH)
                              |
             fetch_one.sh <source>   (each source runs independently, in parallel)
                              |  atomic write, file lock
                       ~/.ai_usage_cache.json
                        |                     |
              AIUsage.app (SwiftUI)    AIUsageWidget (WidgetKit)

The app and the widget only read the cache. The refresh button only writes a trigger file and a LaunchAgent does the rest.

Components

  • fetch_one.sh takes one source (claude, codex, codexhist, agy, gemini, insights) and merges the result into the cache. Parser errors go to a log file instead of being swallowed.
  • ClaudeHelper is a small WKWebView app that keeps the sign-in session and prints the page content for the scripts to parse. Its modes are login, fetch, login-aistudio, fetch-aistudio, login-console and fetch-console.
  • remote_insights.py runs on the remote host, reads the session logs incrementally and returns compressed JSON.
  • Shared.swift is compiled into both the app and the widget: the logo parser, the data model and the shared forecast logic.

Data rules

  • Each source writes to its own key in the cache with a fetched_at. The _fresh flag marks a genuinely successful fetch; only then does the shared clock advance.
  • A transient fetch failure keeps the previous numbers and never overwrites them with empty values.
  • When a session expires the old numbers are kept and needs_login is set so the card shows a sign-in button.

Pitfalls met

  • The helper's background window must be on screen. An off-screen window makes WebKit report visibilityState=hidden, parks requestAnimationFrame, and Next.js pages such as the Anthropic Console never render. The helper uses a 99% transparent window placed on screen that ignores the mouse.
  • Cookies are tied to the helper's bundle id. A loose executable run outside the .app bundle does not see the sign-in session.
  • The claude.ai usage page has a button whose label ends in an icon glyph, so a regular expression must not anchor on $; the Console text also contains icon glyphs on their own lines between a label and its number.
  • The widget extension process survives for a long time across deploys. After every deploy that changes the widget, terminate the AIUsageWidgetExtension process so the system starts the new build.
  • A broken build copied by mistake. Never pipe xcodebuild through grep and skip checking for BUILD SUCCEEDED.
  • Ad-hoc signing inside a synced folder fails with "detritus not allowed"; sign a copy in a temporary directory.
  • The widget has no real NSHomeDirectory() because it runs in a sandbox; the cache path is built from the user name.

Illustrations

The app has two environment variables for producing promotional screenshots: AIUSAGE_HOME points the cache and scripts at a folder of sample data, and AIUSAGE_APPEARANCE (light or dark) pins the appearance. This way public images never contain real figures.