OMI Desktop App for macOS (Swift)
- App log file:
/private/tmp/omi.log(production) or/private/tmp/omi-dev.log(dev builds)
Check errors in the latest (or specific) release using the sentry-release skill:
./scripts/sentry-release.sh # new issues in latest version (default)
./scripts/sentry-release.sh --version X # specific version
./scripts/sentry-release.sh --all # include carryover issues
./scripts/sentry-release.sh --quota # billing/quota statusSee .claude/skills/sentry-release/SKILL.md for full documentation.
When debugging issues for a specific user (crashes, errors, behavior), use the user-logs skill:
# Sentry (crashes, errors, breadcrumbs)
./scripts/sentry-logs.sh <email>
# PostHog (events, feature usage, app version)
./scripts/posthog_query.py <email>See .claude/skills/user-logs/SKILL.md for full documentation and API queries.
- This is the
desktop/subfolder of the OMI monorepo (BasedHardware/omi) - macOS Swift app + Rust backend live here
Merging desktop/** changes to main triggers a fully automated release:
- GitHub Actions (
desktop_auto_release.yml) — auto-increments version, pushes av*-macostag - Codemagic (
codemagic.yaml, workflowomi-desktop-swift-release) — triggered by the tag, runs on Mac mini M2:- Builds universal binary (arm64 + x86_64)
- Signs with Developer ID, notarizes with Apple
- Creates DMG + Sparkle ZIP
- Publishes GitHub release, uploads to GCS, registers in Firestore
- Deploys Rust backend to Cloud Run
- Sparkle auto-update delivers the new version to users
Codemagic CLI & API:
- Token:
$CODEMAGIC_API_TOKEN(set in~/.zshrc) - App ID:
66c95e6ec76853c447b8bcbb - List builds:
curl -s -H "x-auth-token: $CODEMAGIC_API_TOKEN" "https://api.codemagic.io/builds?appId=66c95e6ec76853c447b8bcbb" | python3 -c "import json,sys; [print(f\"{b.get('status','?'):12} tag={b.get('tag','-'):30} start={(b.get('startedAt') or '-')[:19]}\") for b in json.load(sys.stdin).get('builds',[])[:5]]"
To promote: ./scripts/promote_release.sh <tag> (staging → beta → stable).
Use /firebase command or see .claude/skills/firebase/SKILL.md
Quick connect:
cd ../backend && source venv/bin/activate && python3 -c "
import firebase_admin
from firebase_admin import credentials, firestore, auth
cred = credentials.Certificate('google-credentials.json')
try: firebase_admin.initialize_app(cred)
except ValueError: pass
db = firestore.client()
print('Connected to Firebase: based-hardware')
"- Firebase Auth with Apple/Google Sign-In
- Desktop apps should use backend OAuth flow:
/v1/auth/authorize - Apple Services ID:
me.omi.web(shared across all apps) - iOS apps use native Sign-In, Desktop uses backend OAuth + custom token
- Firestore (
based-hardware): User data, conversations, action items - Redis: Caching
- Typesense: Search
users/{uid}/conversations- Hassourcefield (omi, desktop, phone, etc.)users/{uid}/action_items- Tasks (no platform tracking)users/{uid}/fcm_tokens- Token ID prefix = platform (ios_, android_, macos_)users/{uid}/memories- Extracted memories
- FCM tokens: Document ID prefix (e.g.,
macos_abc123) - Conversations:
sourcefield - Action items: No platform tracking
- Firestore has no collection group indexes for
sourcefield - Counting users by platform requires iterating all users (slow)
- Apple Sign-In: Only one Services ID per Firebase project
- Production:
https://api.omi.me - Local:
http://localhost:8080
See .claude/settings.json for connection details.
-
No Xcode project — this is a Swift Package Manager project
-
Build command:
xcrun swift build -c debug --package-path Desktop(thexcrunprefix is required to match the SDK version) -
Full dev run:
./run.sh— builds Swift app, starts Rust backend, starts Cloudflare tunnel, launches app -
Build only:
./build.sh— release build without running -
DO NOT use bare
swift build— it will fail with SDK version mismatch -
DO NOT use
xcodebuild— there is no.xcodeproj -
DO NOT launch the app directly from
build/— always use./run.shor./reset-and-run.sh. These scripts install to/Applications/Omi Dev.appand launch from there, which is required for macOS "Quit & Reopen" (after granting permissions) to find the correct binary. Launching frombuild/causes stale binaries to run after permission restarts. -
DO NOT manually copy binaries into app bundles and launch them — this bypasses signing,
/Applications/installation, and LaunchServices registration -
DO NOT kill, delete, or interfere with running "Omi", "omi", or "Omi Beta" app bundles — these are production/release installs the user relies on
./run.shbuilds "Omi Dev" → installs to/Applications/Omi Dev.app(bundle ID:com.omi.desktop-dev)./build.shbuilds "Omi Beta" →build/Omi Beta.app(bundle ID:com.omi.computer-macos)- Different bundle IDs, different app names, but same source code
- When updating resources (icons, assets, etc.) in built app bundles, update BOTH
- To check which app is currently running:
ps aux | grep "Omi"
When the user asks to test a feature or bug fix, always create a separate named bundle so it can run side-by-side with the existing dev/prod apps:
OMI_APP_NAME="fix-rewind-delay" ./run.shThis creates /Applications/fix-rewind-delay.app with bundle ID com.omi.fix-rewind-delay, completely independent of "Omi Dev" and "Omi Beta". Name it after the feature/bug being tested (e.g., OMI_APP_NAME="onboarding-capture" ./run.sh). The user can then run multiple test builds simultaneously without interfering with each other or the production app.
Rules:
- NEVER use the default
./run.sh(which overwrites "Omi Dev") when testing a specific feature — always setOMI_APP_NAME - Keep the name short and descriptive (it becomes both the app name and bundle ID suffix)
- The named bundle gets its own permissions, database, and auth state — the user may need to re-grant permissions and sign in
- To connect agent-swift:
agent-swift connect --bundle-id com.omi.fix-rewind-delay
xcrun swift buildis for compile checks only — it does NOT start the backend- To actually test, ALWAYS use
./run.shwithOMI_APP_NAME— it starts Rust backend + Cloudflare tunnel + Swift app together - When the user says "test it", use the
test-localskill to build, run, and verify via macOS automation
After editing Swift UI code, verify the change programmatically using agent-swift — a CLI that controls any macOS app via the Accessibility API.
One-time setup: brew install beastoin/tap/agent-swift + grant Accessibility permission to Terminal.app.
# After ./run.sh launches the app:
agent-swift doctor # verify Accessibility permission
agent-swift connect --bundle-id com.omi.desktop-dev # connect to running app
agent-swift snapshot -i # see interactive elements
agent-swift click @e3 # CGEvent click (SwiftUI)
agent-swift press @e3 # AXPress (AppKit buttons)
agent-swift fill @e5 "search text" # type into a text field
agent-swift find role button click # find + chained action
agent-swift is exists @e3 # assert element exists (exit 0/1)
agent-swift wait text "Settings" # wait for text to appear
agent-swift screenshot /tmp/evidence.png # capture app windowKey rules:
- Always use
snapshot -i(interactive only) — full snapshot of a complex SwiftUI app is extremely verbose. - Prefer
clickoverpressfor SwiftUI —clicksends CGEvent clicks (triggers NavigationLink),presssends AXPress (AppKit only). - Refs go stale after
click/press/fill/scroll— re-snapshot before the next interaction. - Argument order:
get <property> <ref>,is <condition> <ref>,wait <condition> [<target>],find <locator> <value>. - 15 commands:
doctor,connect,disconnect,status,snapshot,press,click,fill,get,find,screenshot,is,wait,scroll,schema. - No app-side instrumentation needed — works via macOS Accessibility API on any Cocoa/SwiftUI app.
- Dev bundle ID:
com.omi.desktop-dev. Prod:com.omi.computer-macos(never automate prod).
After completing a desktop task with user-visible impact, append a one-liner to unreleased in desktop/CHANGELOG.json:
python3 -c "
import json
with open('CHANGELOG.json', 'r') as f:
data = json.load(f)
data.setdefault('unreleased', []).append('Your user-facing change description')
with open('CHANGELOG.json', 'w') as f:
json.dump(data, f, indent=2)
f.write('\n')
"Guidelines:
- Write from the user's perspective: "Fixed X", "Added Y", "Improved Z"
- One sentence, no period at the end
- Skip internal-only changes (refactors, CI config, code cleanup)
- HTML is allowed for links:
<a href='...'>text</a> - Commit CHANGELOG.json with your other changes (same commit is fine)
When completing a task that was triggered by an app user request (bug report, feature request, support inquiry, etc.) and you have the user's email address, send them an email about the results using the omi-email skill:
node ../omi-analytics/scripts/send-email.js \
--to "<user-email>" \
--subject "<brief result summary>" \
--body "<what was done, what they should expect, any next steps>"- Write as Matt (first person "I", not "we") — the user already has an ongoing email thread with us, so treat this as a casual continuation of that conversation, not a fresh introduction
- Be concise and direct — they know the context, just share what was done and any next steps (e.g. "update the app")
- Only send when there are meaningful results to share (don't email for internal-only changes)