Read your Find My people, device, and item locations from the macOS FindMy.app via UI scraping. Apple does not expose a public API for Find My locations and the on-disk caches are encrypted with keychain-bound keys, so this tool drives the GUI: it activates FindMy.app, switches to the People, Devices, or Items tab, screenshots the window, and runs Vision OCR on the result.
Privacy & consent. This is read-only and consent-bounded. It can only see the people who have already opted in to share their location with this Mac's Apple ID in Apple's Find My, plus your own devices and items. It returns coarse location only (city/state, staleness, distance), bypasses no Apple access control, and initiates no network traffic — everything stays on-device. Use it to locate consenting friends and family, not to monitor or track anyone without their knowledge and consent.
The macOS APIs we need (Vision, CoreGraphics window list, CGEvent click) have no
Go binding. We bundle a tiny Swift binary findmy-helper that exposes them as
JSON-emitting subcommands, and a Go CLI findmy that orchestrates.
Pick the channel that matches how you'll use it:
| Goal | Channel | Command |
|---|---|---|
Use findmy CLI from terminal |
Homebrew | brew install omarshahine/tap/findmy-cli |
| Use as OpenClaw plugin (chat tools) | ClawHub | clawhub install findmy-cli |
| Use as Claude Code plugin | OpenClaw | openclaw plugins install --link ~/GitHub/findmy-cli |
| Use as a Node library | NPM | npm install findmy-cli |
| Hack on the code | Source | git clone … && make |
brew install omarshahine/tap/findmy-cliInstalls findmy and findmy-helper to /opt/homebrew/bin/. macOS only.
Tap source: omarshahine/homebrew-tap.
First run will prompt for Screen Recording permission.
clawhub install findmy-cliRegisters findmy_people and findmy_person as OpenClaw tools. Shells out
to the findmy binary — install that via Homebrew first.
Listing: clawhub.com/p/findmy-cli ·
NPM package: findmy-cli.
makeOutputs bin/findmy and bin/findmy-helper.
Requirements:
- macOS (tested on 15+; FindMy.app is a Catalyst app)
- Go 1.22+
- Xcode Command Line Tools (
swiftc)
# List people in the sidebar with coarse location, staleness, distance.
findmy people
findmy people --json
findmy people --no-log
# Read one matching person. `--zoom` clicks the row and OCRs the detail pane
# for a street address — see the note on macOS 26+ under Limitations.
findmy person "Omar Shahine"
findmy person "Omar Shahine" --json
# List devices in the sidebar.
findmy devices
findmy devices --json
# Read one matching device.
findmy device "Omar's iPhone"
findmy device "Omar's iPhone" --json
# List items in the sidebar.
findmy items
findmy items --json
# Read one matching item.
findmy item "AirPods Pro"
findmy item "AirPods Pro" --json
# Query the SQLite history ledger populated by people/devices runs.
findmy log "Omar Shahine" --since=24h
findmy log "Omar's iPhone" --kind=devices --limit=10 --json
### Ring a device
`ring` finds the device by scrolling the Devices sidebar, opens its card on the
map, and clicks **Play Sound**. It is a dry run unless you pass `--confirm`, so
you can check it found the right device before anything makes noise.
```bash
findmy ring "Omar's iPhone" # locates the button, does not click
findmy ring "Omar's iPhone" --confirm # actually plays the sound
Ringing needs Accessibility as well as Screen Recording, since it synthesizes clicks. It activates Find My and switches back to the app you were in when it finishes.
Aliases save typing the exact device name, and live in
~/.config/findmy-cli/aliases.json.
findmy alias phone "Omar's iPhone" # set
findmy alias # list
findmy alias --delete phone # remove
findmy ring phone --confirm # any command that takes a device accepts onefindmy phone is the shorthand: with no argument it rings whatever the phone
alias points at.
findmy phone --confirm
Successful `findmy people`, `findmy devices`, and `findmy items` runs append parsed sidebar
records to a local SQLite ledger before printing output. Pass `--no-log` to
skip a write for one invocation. The default database path is
`$XDG_DATA_HOME/findmy-cli/history.sqlite`, or
`~/.local/share/findmy-cli/history.sqlite` when `XDG_DATA_HOME` is unset.
Set `FINDMY_HISTORY_DB=/path/to/history.sqlite` to override it.
FindMy.app menu, tab, and sidebar labels are localized on non-English macOS
installs. The CLI auto-detects the current macOS language for the supported
FindMy.app strings, and `FINDMY_LANG=fr` can be used to force a locale for
testing.
## Required macOS permissions
Grant to the terminal emulator (or to `findmy` once installed system-wide):
- **Screen Recording** — for `screencapture`
- **Accessibility** — for `osascript` menu clicks
Settings → Privacy & Security → Screen Recording / Accessibility.
After granting, **fully quit and relaunch the host process** — TCC is read once
at process start.
## Running on a headless Mac
FindMy.app needs WindowServer compositing to render its window. WindowServer
only runs when macOS sees a display, so a Mac mini / Studio / Pro with nothing
plugged into HDMI or USB-C will return a 99 KB all-black PNG every time you
call `screencapture`, even though the process itself runs fine.
To make findmy work headless:
1. **Plug in a dummy display.** A 4K HDMI/USB-C "headless adapter" (~$10 on
Amazon, search "4K HDMI dummy plug" or "USB-C dummy display"). macOS sees
it as a real 4K@60Hz monitor and starts WindowServer normally.
2. **Disable display sleep** so WindowServer stays compositing:
```bash
sudo pmset -a displaysleep 0
sudo pmset -a sleep 0 # optional: also disable system sleep
Or, for a per-session keep-awake without changing global power settings:
caffeinate -d &Run caffeinate as a LaunchAgent if you want it to start at login.
-
Verify WindowServer can see FindMy.app:
open -a FindMy findmy-helper window --owner FindMy
Should return JSON with non-zero
width/heightandonScreen: true. Ifwidth/heightare 0 or the array is empty, WindowServer isn't compositing — re-check display sleep and that the dummy plug is seated. -
Grant Screen Recording to the host process that will call
findmy(your SSH session's shell, the LaunchAgent, the OpenClaw gateway, etc.). TCC is per-binary path; the brew-installed/opt/homebrew/bin/findmy-helperis what needs the grant.
If you're hitting black screenshots even with a dummy plug, the CLI's
findmy-helper permissions output (screenRecording: false) is the
diagnostic — TCC denied is more common than missing display.
--zoomyields no street address on macOS 26 and later. macOS 26 replaced FindMy's split view with a floating sidebar over a full-window map. A row click no longer opens a detail pane; it opens a callout pinned to the map carrying the same coarse location and staleness the sidebar already showed. There is nothing more precise on screen to OCR, so--zoomprints a warning to stderr and leavesprecise_addressunset rather than guessing. Everything else —people,devices,items,watch,log— is unaffected. Tracked in #13.- The display must be awake and unlocked. WindowServer stops compositing
when the display sleeps, so
screencapturereturns a 99 KB all-black PNG. The CLI detects this and tells you to wake the keyboard. There is no software-only path to wake a sleeping display from userland — Apple gatesIODisplayWranglerWakeupbehind real HID hardware. See Running on a headless Mac above for the dummy-plug fix. - The MapKit map area does not always render into the captured bitmap (Catalyst quirk). This tool only reads the sidebar and detail pane text; map pins are not extracted.
- The FindMy.app window must be openable on this Mac (you must be signed in to iCloud and have at least one friend sharing).
- Window position is re-queried on every run; the app does not need to be at a fixed location.
- This brings FindMy.app to the foreground and steals focus during a click.
- Apple's TOS may consider GUI scraping out of scope. Use at your own risk.
cmd/findmy/ Go CLI
internal/findmy/ Orchestration + sidebar parser
helpers/findmy-helper/main.swift window + ocr + click subcommands
bin/ Build outputs
.claude-plugin/plugin.json Claude Code / OpenClaw plugin manifest
commands/findmy.md /findmy slash command
skills/findmy/SKILL.md Auto-triggering skill
scripts/findmy.sh Plugin wrapper (auto-builds on first use)
All four distribution channels in one repo:
| Surface | Source of truth | Auto-published |
|---|---|---|
| Homebrew formula | omarshahine/homebrew-tap Formula/findmy-cli.rb |
manual on tag |
| NPM package | openclaw/package.json |
GH Actions on tag push |
| ClawHub package | same as NPM, source-linked to commit | GH Actions on tag push |
| Claude Code plugin | .claude-plugin/plugin.json (bundle format) |
manual linked install |
CI workflows under .github/workflows/ handle NPM and ClawHub on every
v* tag push (OIDC trusted publishing for NPM, CLAWHUB_TOKEN for
ClawHub). Homebrew formula bump is still manual.
The Claude Code wrapper (scripts/findmy.sh) builds bin/findmy and
bin/findmy-helper on first invocation via make. No binaries are
committed.