Skip to content

Replace osascript with native Swift helper for stable TCC permissions#84

Merged
priyanshujain merged 26 commits intomasterfrom
macos-native-integration
Mar 16, 2026
Merged

Replace osascript with native Swift helper for stable TCC permissions#84
priyanshujain merged 26 commits intomasterfrom
macos-native-integration

Conversation

@priyanshujain
Copy link
Copy Markdown
Collaborator

Summary

  • Adds obkmacos Swift helper binary that uses CNContactStore (contacts) and NSAppleScript (notes) for stable macOS TCC permission attribution instead of fragile osascript automation permissions
  • Fixes linking bug where setupAppleContacts() called LinkSource("contacts") but daemon checked IsSourceLinked("applecontacts"), so Apple Contacts never synced in the background
  • Adds auto-migration for existing users who had the wrong source linked

Changes

New files:

  • swift/obkmacos.swift — Native Swift binary with contacts, notes, check subcommands
  • internal/obkmacos/ — Go bridge package wrapping exec calls with typed JSON parsing

Modified files:

  • source/contacts/sync_applecontacts.go — Uses obkmacos.FetchContacts() instead of osascript
  • source/applenotes/applescript.go — Uses obkmacos.FetchNotes() instead of osascript, consolidated into single FetchAll() call
  • internal/cli/setup.goLinkSource("contacts")LinkSource("applecontacts")
  • internal/cli/contacts/contacts.go — Removed erroneous LinkSource("contacts") from sync command
  • daemon/contacts.go — Added migrateContactsLinking() for existing users
  • Makefile — Added build-obkmacos target, wired into install
  • .github/workflows/ci.yml — Added Swift compilation check on macOS
  • .github/workflows/release.yml — Added macOS runner to build Swift binary for releases

Test plan

  • make build-obkmacos compiles Swift binary on macOS
  • ~/.obk/bin/obkmacos contacts outputs valid JSON with real contacts
  • ~/.obk/bin/obkmacos check shows permission status
  • go test ./internal/obkmacos/... passes (JSON parsing tests)
  • go test ./daemon/... passes (migration + linking tests)
  • go test ./source/applenotes/... passes
  • go test ./source/contacts/... passes
  • obk setup with Apple Contacts links "applecontacts" (not "contacts")
  • Daemon syncs Apple Contacts without repeated TCC popups after initial grant
  • Daemon syncs Apple Notes without repeated TCC popups after initial grant
  • Existing users with "contacts" linked get auto-migrated to "applecontacts"

Uses CNContactStore for contacts (kTCCServiceAddressBook) and NSAppleScript
for notes, giving stable TCC attribution from a single binary instead of
fragile osascript automation permissions.
Wraps exec calls to obkmacos binary with typed JSON parsing for
contacts, notes, and permission checks.
Uses CNContactStore via the Swift helper binary for stable TCC
attribution instead of fragile osascript automation permissions.
Consolidates FetchAllNotes+FetchFolders into single FetchAll() call
that uses the Swift helper binary. Date parsing now handled in Swift
with RFC 3339 output.
…setup

The daemon checks IsSourceLinked("applecontacts") but setup was linking
"contacts", so Apple Contacts never synced in the background.
The contacts aggregator is a unified store, not a source to be linked.
Individual providers (applecontacts, whatsapp, etc.) are linked separately.
Existing users who ran setup before the fix had "contacts" linked
instead of "applecontacts". This auto-migrates on daemon startup.
Compiles swift/obkmacos.swift to ~/.obk/bin/obkmacos on Darwin.
Wired into the install target as a prerequisite.
CI: verifies obkmacos compiles on macos-latest.
Release: builds obkmacos on macOS runner and includes it as artifact
for the goreleaser step.
Spec tests hit real LLM APIs and are slow. Gate them behind
OBK_TEST_PROVIDER so `go test ./...` stays fast. Use "all" to
run all available providers.
Tests convertContacts filtering (empty contacts, nickname-only, nil input)
separately from the obkmacos binary call.
Tests convertNotesResponse including Recently Deleted filtering,
noteToFolder mapping, password-protected notes, and empty responses.
The ISO date values were mapped to the wrong JSON keys — createdAt
had the modification date and vice versa.
…dialog

CheckPermissions only reads status without requesting access, so
first-time users would never see the macOS permission prompt.
FetchContacts calls CNContactStore.requestAccess which triggers it.
…ase assets

Builds on macos-latest (arm64) and macos-13 (amd64), uploads binaries
directly as release assets via gh release upload.
Users can now set up individual sources without re-running the full
setup wizard: obk setup applecontacts, obk setup applenotes.
…access

requestAccess triggers the TCC dialog on every call for unsigned
binaries. Check authorizationStatus first and only request when
notDetermined (first-time setup). Prevents repeated permission
popups on daemon restart.
Single curl-pipe-sh installer that handles binary download from
releases, source builds via Go, and macOS Swift helper compilation.
- Add set -eu, need_cmd/check_cmd helpers for proper dep checking
- Add Rosetta 2 detection via sysctl hw.optional.arm64
- Create env script (~/.obk/env) sourced from shell RCs instead of
  writing PATH directly — matches uv/Rustup pattern
- Add GITHUB_PATH support for CI environments
- Clear error messages with install links instead of auto-installing deps
- Initialize all variables to avoid set -u failures
Opens the macOS system dialog to install Xcode Command Line Tools,
matching what Homebrew does. User re-runs the script after completion.
@priyanshujain priyanshujain merged commit 51a4d18 into master Mar 16, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant