Imported from zhiyozhao/bongocat-menubar (
AGENTS.md). Install upstream withnpx skills add zhiyozhao/bongocat-menubar. Copyright stays with the author.
AGENTS.md — BongoCat Menubar
What this is
macOS menu bar app (no dock icon) that animates a cat typing along with your keystrokes. Pure Swift, built with SwiftPM (Package.swift, executable target) and packaged by Scripts/build-app.sh.
Build and run
Scripts/build-app.sh # universal (arm64+x86_64) release build -> .build/release/BongoCat Menubar.app (DEV X signed)
swift build # debug compile only
swift Scripts/generate-icon.swift # regenerate Resources/AppIcon.icns from the cat SVG
- Requires macOS 13+ frameworks (AppKit, ApplicationServices, CoreGraphics, ServiceManagement).
- Build output lives in
.build/<config>/(gitignored). GeneratedAppIcon.icnsis also gitignored. - Version is stamped from the latest git tag (
v1.2.3→1.2.3), falling back toInfo.plist; build number is the commit count. The sourceInfo.plistis never mutated — stamping happens on the bundled copy via PlistBuddy.
Repository layout
Sources/ 5 Swift files (see below)
Resources/
Icons/ idle / typing_a / typing_b / typing_both SVGs
*.lproj/ en + zh-Hans Localizable.strings
AppIcon.icns generated (gitignored, rebuilt by `make build` when missing)
Info.plist LSUIElement app, bundle id com.zhiyozhao.bongocat-menubar
Scripts/build-app.sh build/package entry point
Scripts/generate-icon.swift renders AppIcon.icns (gradient squircle + cat, iconutil)
.github/workflows/release.yml
There are no tests, linter, formatter, or typecheck steps.
Architecture
5 source files in Sources/:
| File | Role |
|---|---|
main.swift |
Entry point. Creates NSApplication with .accessory activation policy (menu-bar-only, no dock icon). |
AppDelegate.swift |
Wires everything together: status item, menu, permission handling, mode switching. |
KeyboardMonitor.swift |
CGEvent tap for global key-down/key-up/flags-changed. Requires Accessibility permission. Auto-re-enables on tap timeout. |
KeystrokeAnimator.swift |
Maps keycodes to animation frames. Two modes: Normal (alternating toggle) and Follow Hands (left/right hand tracking). |
IconManager.swift |
Loads SVG/PNG icons from app bundle. Falls back to SF Symbols (cat/cat.fill). |
Entry point flow: main.swift → AppDelegate.applicationDidFinishLaunching → sets up icon manager, animator, keyboard monitor, menu, and permission polling.
Key domain details
- Animation frames:
idle,typing_a,typing_b,typing_both— corresponding SVGs inResources/Icons/. - Left/right hand split:
KeystrokeAnimator.swiftdefines which macOS keycodes belong to each hand. Modifiers (Shift, Ctrl, Opt, Cmd) are split left/right. - Follow Hands mode: tracks independent left/right state →
typing_bothframe when both hands are down. Normal mode just alternatestyping_a/typing_b. - Icon loading order:
<name>.svg→<name>_color.png→<name>.png→ SF Symbol fallback. Files with_colorin the name are not set as template images.
Signing
Two modes, auto-selected by the build script:
- Unified self-signed cert
DEV X(preferred): one certificate shared by all projects, managed in~/Codes/dev-x-signing(p12 + password there). The designated requirement anchors on the certificate, so macOS TCC keeps the Accessibility grant across rebuilds and upgrades. - Ad-hoc (
-): fallback when the cert is absent. The code hash changes every build, so Accessibility permission is re-requested after each update.
In CI, the release workflow imports the cert from secrets SIGNING_P12_BASE64 / SIGNING_P12_PASSWORD, so release builds share the same stable identity. To set up: gh secret set SIGNING_P12_BASE64 < <(base64 -i ~/Codes/dev-x-signing/dev-x.p12) and echo dev-x-p12 | gh secret set SIGNING_P12_PASSWORD.
This is NOT Developer ID signing: Gatekeeper still warns for direct DMG downloads (the Homebrew cask strips quarantine via xattr -cr in postflight). To remove the warning entirely, use a paid Developer ID certificate + notarization.
Permissions
The app needs Accessibility permission (System Settings → Privacy & Security → Accessibility) to use CGEvent taps. AppDelegate polls AXIsProcessTrusted() every 2 seconds and auto-starts monitoring once granted. Without permission, only the menu bar icon is shown. With a stable signing identity (see above), the grant persists across rebuilds and upgrades; with ad-hoc signing, run tccutil reset Accessibility com.zhiyozhao.bongocat-menubar to re-prompt.
CI / Release
.github/workflows/release.yml triggers on v* tags (same template as the other repos):
- Import signing identity →
Scripts/build-app.sh(swift build -c release --arch arm64 --arch x86_64+ assemble .app) → verifyAuthority=DEV X→ smoke launch → UDZO DMG viahdiutil(with/Applicationssymlink) - Publish GitHub release with auto-generated notes (asset:
BongoCat-Menubar-v<version>.dmg; the tap cask is bumped by homebrew-tap's scheduledsync-casks)
To release: push a tag like git tag v1.0.0 && git push --tags.
Homebrew distribution is pull-based and lives entirely in the zhiyozhao/homebrew-tap repo: its sync-casks.yml workflow polls this repo's latest published release on an hourly schedule, recomputes the DMG sha256, and bumps Casks/bongocat-menubar.rb. No token sharing between repos. The DMG asset name must stay BongoCat-Menubar-v<version>.dmg — the cask URL interpolates #{version} into that exact pattern.
Gotchas
- The app bundle name has a space:
BongoCat Menubar.app. Quoting matters in shell commands, and because GNU Make splits target names on whitespace, incremental builds are tracked via.build/<config>/.build-stampinstead of the binary path. - Universal binaries are built by
swift build --arch arm64 --arch x86_64(SwiftPM lipo 自动合并;产物在.build/apple/Products/Release/). swift buildcompiles everything underSources/BongoCatMenubar/— adding a new file requires no manifest changes.- CGEvent taps can be silently disabled by the system (timeout or permission revocation).
KeyboardMonitorhandles.tapDisabledByTimeoutand.tapDisabledByUserInputby re-enabling. - macOS renders SVGs in
NSImagenatively (used both at runtime for menu icons and by the icon generator script).
