Features โข Quick Start โข Developer Guide โข Support
BearMinder is a tiny macOS menubar app that totals the words you wrote in Bear today and posts them to your Beeminder goal. It stays out of the way, runs onโdemand or hourly, and keeps your tokens securely in the Keychain.
I used to rely on Draft for daily writing with an autoโsync to Beeminder. Since Draft shut down, there hasn't been an enjoyable replacement. BearMinder fills that gap by letting me keep writing in Bear and still feed my Beeminder goal automatically.
Beeminder's official URLminder integration is still active and works great if you write in Google Docs, Dropbox, or any publicly accessible URL. However, if you prefer writing in Bear (a native macOS notes app with excellent Markdown support), URLminder won't work since Bear notes aren't web-accessible. BearMinder bridges that gap, letting Bear users enjoy the same automatic word-count tracking that URLminder provides for cloud documents.
- Archive of Draft
- Beeminder discussion on Draft's shutdown
- URLminder help docs
- URLminder announcement
| Documentation | Project |
|---|---|
| Spec Sheet | Changelog |
| App Setup | Roadmap/TODO |
| Build Write-up | Issues |
- ๐ป Seamless Bear Integration โ Automatically tracks words from your Bear notes
- ๐ Beeminder Sync โ Posts daily word counts to your Beeminder goal
- โฐ Automatic Hourly Sync โ Configurable intervals (30/60/120 minutes)
- ๐ Secure Token Storage โ Uses macOS Keychain for API tokens
- ๐ฏ Smart Delta Tracking โ Only posts today's new words (idempotent)
- ๐ซ Unobtrusive โ Tiny menubar app that stays out of your way
- ๐ Rich Comments โ Datapoints include word count, notes modified, and tags
- Build from source (ask a friend) or download a signed build when available
- Launch the app โ a ๐ป icon appears in your macOS menu bar
Click ๐ป โ Settings and fill in:
- Beeminder Username
- Beeminder API Token โ Get it from beeminder.com/api/v1/auth_token.json
- Beeminder Goal โ For example:
writing - Bear API Token โ In Bear: Help โ Advanced โ API Token โ Copy
๐พ Save. On first access, macOS may ask you to allow Keychain access. Choose "Always Allow" so you aren't asked again on each launch.
- Click ๐ป โ Sync Now
- Your datapoint's comment shows a concise oneโline summary (words, notes, tags)
- โ๏ธ Just write in Bear โ nothing else to do!
- โฐ Automatic hourly sync runs in the background (frequency is configurable in Settings: 30/60/120m)
- ๐ป The menubar shows ๐ป when everything is working and ๐ด when there's an error
- ๐ The menu shows "Last sync" and "Next sync" details
- ๐ Click ๐ป โ Sync Now anytime (BearMinder won't post a zero if you didn't write since the last sync)
| Issue | Solution |
|---|---|
| Bear pops up during sync | Enable "Use AppleScript mode" in Settings to prevent Bear from coming to the foreground |
| No ๐ป in the menu bar | Make sure the app launched. If needed, quit and relaunch |
| Keychain permissions every launch | Choose "Always Allow" when prompted, or open Keychain Access and add BearMinder to Access Control for items with Service "beeminder" and "bear" |
| No words posted despite writing | Click Sync Now and check the log. BearMinder counts only words written today (UTC) across notes modified today |
- Value โ Today's delta only (idempotent): words added today since yesterday's final counts
- Comment โ A short oneโline summary:
๐ {today_words}w | ๐ {notes_modified} notes | ๐ท๏ธ {unique_tags} tags โข ๐ป via Bear โ Beeminder
๐ก Tip: We only post today's delta. If there's no new writing since the last sync, BearMinder won't post a 0 (to avoid clobbering a positive datapoint).
Apps/BearMinder/ # XcodeGen app project (AppKit menubar app)
AppTemplate/ # App layer (AppDelegate, StatusItemController, Settings window)
Sources/ # Swift Package modules
โโโ Models/ # Data models (snapshots, tracking, settings, datapoints)
โโโ Logging/ # Simple logging helper
โโโ KeychainSupport/ # Keychain read/write wrappers
โโโ BeeminderClient/ # Beeminder datapoint POST client
โโโ BearClient/ # Bear client and types
โโโ Persistence/ # Persistence protocol + Core Data implementation
โโโ SyncManager/ # Polling/trigger logic
docs/ # Spec and setup notes
cd Apps/BearMinder
xcodegen generate
open BearMinder.xcodeprojSelect the BearMinder scheme โ Destination My Mac โ Build & Run.
# Build into ./build without code signing
xcodebuild -project Apps/BearMinder/BearMinder.xcodeproj \
-scheme BearMinder -configuration Debug \
-derivedDataPath build CODE_SIGNING_ALLOWED=NO build
# Launch the built app
open build/Build/Products/Debug/BearMinder.appCreate local/credentials.json (gitignored) using local.sample/credentials.json:
{
"beeminderUsername": "yourname",
"beeminderGoal": "writing",
"beeminderToken": "...",
"bearToken": "..."
}On launch, AppTemplate/LocalConfigLoader.swift seeds UserDefaults + Keychain if empty.
- Triggers a Bear
searchxโcallback with an empty term (broad search) - Bear returns a JSON
notesarray - Filters to notes whose
modificationDateis today (UTC) - For each note, calls
open-noteto fetch metadata and body text - Counts words from the
textornotecallback param
- Baseline = yesterday's endโofโday count per note (UTC); if none, baseline = 0
- Today's delta = sum(max(0, current - baseline)) for all notes modified today
- Stores
NoteTrackingfor today withpreviousWordCount = baseline,currentWordCount = current - Posts only today's delta to Beeminder, skipping if delta is 0
| Component | Implementation |
|---|---|
| Menu bar icon | AppTemplate/StatusItemController.swift (emoji title fallback for reliability) |
| App entrypoint | Explicit @main class in AppTemplate/Main.swift (avoids lifecycle ambiguity) |
| URL callbacks | AppTemplate/AppDelegate+URLHandling.swift registers for kAEGetURL |
| Token storage | Sources/KeychainSupport/KeychainSupport.swift with kSecAttrAccessibleAfterFirstUnlock |
| Beeminder POST | Sources/BeeminderClient/BeeminderClient.swift using application/x-www-form-urlencoded |
| SyncManager | Background hourly timer with exponential backoff retry (3 attempts, 5s base delay) |
| Offline queue | Failed datapoints queued on disk and sent on next successful sync |
| Error handling | Distinguishes retryable errors from permanent failures; respects Retry-After headers |
| Persistence | Core Data with lightweight migrations for automatic schema updates |
- Sparkle autoโupdater and signed builds
- Code signing and hardened runtime
- Performance optimizations (target <10MB idle, <15MB during sync)
- Tag-based filtering UI and logic
Issues and PRs welcome! Please:
- โ Keep UI tiny and unobtrusive
- ๐ Guard tokens โ never log secrets
- ๐ Prefer UTC for dates and day boundaries
- ๐ Add concise logs around network calls and callbacks
If this project helps you, consider supporting:
Have ideas or found a bug? Open an issue ๐
MIT โ See LICENSE for details
Made with โค๏ธ by Brennan Brown
