Skip to content

Keeping track of daily word counts from the Bear app into Beeminder [work-in-progress]

License

Notifications You must be signed in to change notification settings

brennanbrown/bearminder

Repository files navigation

๐Ÿป BearMinder

Bear โ†’ Beeminder word tracker for macOS

macOS Swift License: MIT GitHub issues GitHub stars

Features โ€ข Quick Start โ€ข Developer Guide โ€ข Support


BearMinder screenshot showing menubar status and settings

๐Ÿ“– About

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.

๐Ÿ’ก Why This Exists

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.

๐Ÿ”— What About URLminder?

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.

๐Ÿ“š References

๐Ÿ”— Quick Links

Documentation Project
Spec Sheet Changelog
App Setup Roadmap/TODO
Build Write-up Issues

โœจ Features

  • ๐Ÿป 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

๐Ÿš€ Quick Start (nonโ€‘technical users)

1๏ธโƒฃ Install and Open the App

  • Build from source (ask a friend) or download a signed build when available
  • Launch the app โ€” a ๐Ÿป icon appears in your macOS menu bar

2๏ธโƒฃ Open Settings

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.

3๏ธโƒฃ Run Your First Sync

  • Click ๐Ÿป โ†’ Sync Now
  • Your datapoint's comment shows a concise oneโ€‘line summary (words, notes, tags)

4๏ธโƒฃ Daily Use

  • โœ๏ธ 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)

๐Ÿ”ง Troubleshooting

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

๐Ÿ“Š What Gets Posted to Beeminder

  • 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).


๐Ÿ‘จโ€๐Ÿ’ป Developer Guide

๐Ÿ“ Project Structure

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

๐Ÿ”จ Build & Run

Generate Xcode Project

cd Apps/BearMinder
xcodegen generate
open BearMinder.xcodeproj

Select the BearMinder scheme โ†’ Destination My Mac โ†’ Build & Run.

Commandโ€‘Line Build

# 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.app

๐Ÿ” Local Credentials (Optional for Dev)

Create 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.

๐Ÿงฎ How Today's Words Are Computed

BearIntegrationManager Flow

  1. Triggers a Bear search xโ€‘callback with an empty term (broad search)
  2. Bear returns a JSON notes array
  3. Filters to notes whose modificationDate is today (UTC)
  4. For each note, calls open-note to fetch metadata and body text
  5. Counts words from the text or note callback param

AppDelegate Sync Flow

  1. Baseline = yesterday's endโ€‘ofโ€‘day count per note (UTC); if none, baseline = 0
  2. Today's delta = sum(max(0, current - baseline)) for all notes modified today
  3. Stores NoteTracking for today with previousWordCount = baseline, currentWordCount = current
  4. Posts only today's delta to Beeminder, skipping if delta is 0

๐Ÿ”ง Key Technical Details

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

๐Ÿ—บ๏ธ Roadmap

  • 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

๐Ÿค Contributing

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

๐Ÿ’– Support

If this project helps you, consider supporting:

GitHub Sponsors Ko-fi

Have ideas or found a bug? Open an issue ๐Ÿ›


๐Ÿ“„ License

MIT โ€” See LICENSE for details


Made with โค๏ธ by Brennan Brown

About

Keeping track of daily word counts from the Bear app into Beeminder [work-in-progress]

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published