A native macOS clipboard history manager — a personal toy project inspired by the clipboard panels in Alfred and Raycast. Swift + SwiftUI with AppKit where SwiftUI falls short (nonactivating panels, global hotkeys, focus capture).
- Background app (no Dock icon, status-bar only).
- Configurable global hotkey (default ⌘⇧V) summons a floating panel over any app, including fullscreen apps.
- Captures text, RTF, images, and file references; remembers which app produced each entry.
- fzf-style fuzzy search with word-boundary, consecutive-match, and camelCase bonuses.
- ⏎ pastes with original formatting; ⇧⏎ pastes as plain text; ⌘P pins; ⌘⌫ deletes; Esc dismisses.
- Reorder pinned entries — with a pinned row selected, ⌥↑ / ⌥↓ swaps it with the adjacent pinned neighbour in the visible list. Lets a long-lived pin keep its spot at the top instead of slowly sinking under newer pins.
- Multi-paste — ⇧Space (or ⌘-click) gathers rows into a batch, ⇧↑/⇧↓
extends contiguously, ⏎ pastes them joined by a configurable delimiter
(default newline; configurable via Settings → General → Multi-select with
\n/\t/\\escapes). Selected rows show a numbered chip in insertion order. ⌘1–⌘9 still pastes that single row directly, ignoring an active batch. - Recently used floats to the top — pasting an entry from history bumps it to the top of the unpinned list (under pins), so the rows you actually use stay closest to home. Mirrors the existing "re-copy bumps an entry" behaviour. Multi-paste bumps every constituent; transforms / snippets / actions leave the source entry's position alone.
- ⌘O obfuscates a sensitive entry (passwords you paste daily) — the row
collapses to
🔒 nickname ••••••••and the preview / Quick Look refuse to render the payload, but ⏎ still pastes the real value. ⌘R re-edits the nickname. Search hits the nickname only, so typing the underlying text never reveals it. Obfuscated rows are exempt from retention sweeps. - Hidden from screen capture — the panel is excluded from screenshots, screen recordings, and screen-sharing sessions (it stays visible to you). On by default; the window-level complement to ⌘O. Toggle in Settings → Privacy → Screen capture if you need to record the panel itself.
- ⌘1–⌘9 quick-pastes the Nth visible entry without arrow-keying. ⇧ combined with the digit pastes as plain text.
- ⌘Y toggles a full-panel Quick Look preview of the selected entry (full-size images, larger monospaced text, file paths with icons, or — for JSON / YAML / XML — a collapsible tree view with typed colouring). Arrow keys still navigate while the overlay is open.
- Code-aware panel: each text entry is sniffed for its language (JSON, YAML, XML, HTML, Swift, Java, JS/TS, Python, Go, Rust, Ruby, SQL, Shell, Dockerfile, Markdown, CSS). Detected rows show a small language chip and the preview + Quick Look panes render with syntax highlighting.
- Type-keyword search: typing
imagefinds image entries,filefinds file-reference entries, and image dimensions (400x300/400×300) match too — images and files aren't just "no text, unsearchable." - ⌘T opens a transform picker over the selected text entry — pretty/minify
JSON, JSON ↔ YAML, decode JWT, Unix timestamp ↔ ISO 8601, SHA-256 / SHA-1 /
MD5, number-base conversions (dec / hex / bin), query string ↔ JSON,
Base64 / URL en-decode, case conversions (UPPER / lower / Title / camel /
snake / kebab), trim whitespace (whole-string, per-line trailing, per-line
both ends, or per-line borders for
│ … │table dumps), strip ANSI / HTML / SQL continuation arrows (Trino / mysql->prompts), extract URLs / emails. Fuzzy-search transforms by name, ⏎ applies and pastes. - ⌘S opens a snippet picker of user-authored canned text, managed in
Settings → Snippets. Supports placeholders:
{clipboard},{date[:FMT]},{time[:FMT]},{uuid},{newline},{tab}, plus{{/}}for literal braces. ⏎ expands placeholders and pastes. - ⌘K opens a type-aware action picker for the selected entry. Available actions depend on the entry type: URL → Open in Browser / Paste as Markdown link; files → Reveal in Finder / Open; email → Compose Mail; phone → Call with FaceTime; hex color → Paste as rgb() / hsl(). Inapplicable actions are hidden; ⏎ runs, Esc cancels.
- SQLite-backed persistence (GRDB), with image blobs on disk addressed by SHA-256.
- Configurable retention (max count, max age) with automatic hourly sweeps.
- Respects the
nspasteboard.orgconcealed/transient convention — password managers marking their entries this way are ignored. - Ignored-apps list (Settings → Privacy): capture is skipped entirely
while the frontmost app matches. Ships with sensible defaults
(1Password 7/8, Bitwarden, Keychain Access, Apple Passwords, LastPass,
KeePassXC); user-editable via a file picker that reads the bundle ID
straight from the chosen
.app. - Export / Import history (Settings → Privacy → Backup): dump every entry (including image bytes) into a single JSON file; import the same shape back. Dedup by content hash means re-importing is a no-op.
- Optional "restore previous clipboard after paste" so history isn't disturbed by its own use.
- Adjustable panel opacity (slider, 30–100%) and launch at login in Settings → General.
- Auto-updates via Sparkle — background check every 24 hours, manual "Check for Updates…" in the menu-bar, feed hosted on GitHub Pages. Updates are EdDSA-signed and notarized.
- Predictive Paste (off by default, easter egg) — a separate global hotkey (default ⌃⌥⌘P) pastes a random silly quote into the frontmost app. Nothing about it is actually predictive. Toggle in Settings → General → Easter Eggs; rebind in Settings → Hotkey. Quotes do not enter history.
Requirements:
- macOS 14 (Sonoma) or newer
- Xcode 15 or newer
- xcodegen —
brew install xcodegen - An Apple Developer Team ID (free tier is fine for local use) — without this you get an ad-hoc signature that macOS treats as a new app on every rebuild, which makes TCC (Accessibility, etc.) drift out of sync.
cd Birchboard
# Put your Team ID into the signing config (first time only).
$EDITOR Config/Signing.xcconfig # fill DEVELOPMENT_TEAM = ABCD123456
xcodegen generate
open Birchboard.xcodeprojYour Team ID is the 10-character string shown at Xcode → Settings → Accounts, or at developer.apple.com/account → Membership.
Config/Signing.xcconfig is gitignored — it's your local override of
Signing.xcconfig.example. Once filled in it persists across xcodegen
regenerations.
Hit ⌘R in Xcode. Swift Package Manager will resolve the two dependencies
(KeyboardShortcuts, GRDB.swift) on the first build.
Building from the command line:
cd Birchboard
xcodegen generate
xcodebuild -project Birchboard.xcodeproj -scheme Birchboard \
-configuration Debug -destination 'platform=macOS' buildIf you'd been running an ad-hoc-signed build before setting up a real team ID, macOS's TCC database still has the old binary hash on file. After your first properly-signed build:
- Open System Settings → Privacy & Security → Accessibility.
- Remove any existing Birchboard entry (minus button).
- Run the new build and trigger a paste; macOS will re-add it.
- Toggle it on.
You only need to do this once — subsequent signed rebuilds reuse the same identity and TCC remembers the grant.
To send a DMG to another Mac and have it open without Gatekeeper complaints, the build has to be signed with a Developer ID Application cert and notarized by Apple. Apple Development certs (what the Debug config uses) only work on your own Mac.
-
Get a Developer ID Application cert. developer.apple.com/account → Certificates →
+→ Developer ID Application. Follow the CSR prompts (Keychain Access → Certificate Assistant → Request a Certificate…). Double-click the downloaded.certo install. Verify withsecurity find-identity -v -p codesigning; you should see a "Developer ID Application: …" line. -
Generate an app-specific password at appleid.apple.com → Sign-In and Security → App-Specific Passwords.
-
Store notarytool credentials in the keychain:
xcrun notarytool store-credentials "notarytool-birchboard" \ --apple-id "you@example.com" \ --team-id "YOUR_TEAM_ID" \ --password "xxxx-xxxx-xxxx-xxxx"
The profile name is your label;
make-dmg.shreads it from$NOTARY_PROFILE.
cd Birchboard
NOTARY_PROFILE=notarytool-birchboard ./scripts/make-dmg.shmake-dmg.sh runs xcodegen generate, archives the Release config
(manual Developer ID signing + hardened runtime), packages the .app
into a DMG, codesigns the DMG, submits to Apple's notary service with
--wait, staples the returned ticket, and runs spctl --assess to
verify Gatekeeper accepts it. The DMG lands at
Birchboard/build/Birchboard.dmg (around 3 MB).
If notarization fails, fetch the log with the submission ID from the output:
xcrun notarytool log <submission-id> \
--keychain-profile "notarytool-birchboard"Most common causes: signing with Apple Development instead of
Developer ID (check project.yml Release override), hardened runtime
not enabled (ENABLE_HARDENED_RUNTIME must be YES, already set),
or a stale archive (delete build/ and rerun).
Bump MARKETING_VERSION and CURRENT_PROJECT_VERSION in
Birchboard/project.yml, then run make-dmg.sh again. The one-time
setup above is a one-time-ever affair.
make-dmg.sh is for local builds. For public releases a tag push is
all it takes — .github/workflows/release.yml builds a signed +
notarized DMG, appends an entry to docs/appcast.xml, and publishes
a GitHub Release with the DMG attached. Installed users pick up the
new version automatically via Sparkle.
Full instructions — required repo secrets, per-release checklist,
troubleshooting — live in RELEASE_PROCESS.md.
- Accessibility — required so the app can post a synthetic ⌘V into the previously focused app after you pick an entry. On first launch the system will prompt; if you dismiss it, grant access in System Settings → Privacy & Security → Accessibility.
The app is not sandboxed. A clipboard manager needs to observe the pasteboard, post HID events globally, and access arbitrary file URLs in paste entries — none of which play nicely with the App Sandbox for a personal tool.
- Database:
~/Library/Application Support/Birchboard/history.sqlite - Image blobs:
~/Library/Application Support/Birchboard/blobs/<sha256>.png
Delete both to fully reset the app.
Birchboard/
├── project.yml # xcodegen spec
├── Config/Signing.xcconfig* # local Team ID override
├── scripts/make-dmg.sh # Release build + optional notarization
├── Birchboard.xcodeproj # generated
└── Birchboard/
├── App/ # @main, AppDelegate, status bar
├── Panel/ # NSPanel shell + SwiftUI content
├── Clipboard/ # watcher / reader / writer / filter
├── Model/ # ClipEntry, EntryKind, SourceApp
├── Storage/ # GRDB database + repository + archive
├── Search/ # FuzzyMatcher
├── Transforms/ # ⌘T: TextTransform + registry + built-ins
├── Snippets/ # ⌘S: Snippet + store + placeholders
├── Actions/ # ⌘K: EntryAction + registry + built-ins
├── Settings/ # SwiftUI settings window + Preferences
├── Util/ # AX permission, SHA-256, hotkey names
├── Info.plist # generated (LSUIElement=true)
└── Birchboard.entitlements # generated (sandbox off)
Birchboard is released under the MIT License.
Third-party dependencies — KeyboardShortcuts, GRDB.swift, Splash, and Yams — are also MIT-licensed.