MagicHop coordinates Bluetooth handoff across your Macs using a small Go/NATS daemon
1.7K
MagicHop lets several Macs share Apple Magic peripherals, such as Magic Keyboard, Magic Mouse, and Magic Trackpad, without talking to each other directly. A coordinator runs embedded NATS, each Mac runs a local daemon, and magichop claim asks another Mac to release a peripheral before acquiring it locally.
Release means unpairing the peripheral from the current Mac. Acquire means clearing stale local pairing state, pairing, connecting, and verifying the connected state.
Run the coordinator on an always-on host:
docker run -d \
--name magichop \
--restart unless-stopped \
-e MAGICHOP_AUTH_TOKEN=<shared-token> \
-e MAGICHOP_HOST=0.0.0.0 \
-e MAGICHOP_PORT=4222 \
-p 4222:4222 \
ghcr.io/yegor-usoltsev/magichop:latest
Or run the binary directly:
magichop server --host 0.0.0.0 --port 4222 --auth-token <shared-token>
Put the release binary somewhere stable:
mkdir -p ~/.local/bin
cp magichop ~/.local/bin/magichop
Install blueutil and find paired Bluetooth addresses:
brew install blueutil
blueutil --paired
Create ~/.config/magichop/config.json:
{
"node_name": "macbook-a",
"coordinator_url": "nats://coordinator.local:4222",
"auth_token": "CHANGE_ME",
"default_device": "trackpad",
"devices": {
"keyboard": "aa:bb:cc:dd:ee:01",
"trackpad": "aa:bb:cc:dd:ee:02"
}
}
The config file contains the shared token and should be owner-only:
chmod 600 ~/.config/magichop/config.json
Install and start the macOS LaunchAgent:
magichop install mac
Run a daemon manually during development:
magichop daemon --config ~/.config/magichop/config.json
Claim the default peripheral:
magichop claim
Claim an alias or a raw Bluetooth address:
magichop claim keyboard
magichop claim trackpad
magichop claim aa-bb-cc-dd-ee-ff
Release locally without telling another Mac to acquire:
magichop release trackpad
Inspect local state:
magichop status
magichop devices
magichop devices --scan
magichop doctor
JSON output is available for user-facing daemon commands:
magichop claim --json
magichop release trackpad --json
magichop status --json
magichop devices --json
magichop doctor --json
Generate Raycast Script Commands:
magichop install raycast
Add ~/.local/raycast-scripts in Raycast under Settings -> Extensions -> Script Commands -> Add Script Directory.
Generated scripts call magichop claim <device>. Raycast and other local clients never run blueutil directly.
Coordinator:
| Key | Type | Default | Required |
|---|---|---|---|
MAGICHOP_HOST | string | 0.0.0.0 | No |
MAGICHOP_PORT | int | 4222 | No |
MAGICHOP_AUTH_TOKEN | string | Yes |
Mac clients use MAGICHOP_CONFIG when it is set. Otherwise they read ~/.config/magichop/config.json.
magichop server --host 0.0.0.0 --port 4222 --auth-token TOKEN
magichop daemon --config ~/.config/magichop/config.json
magichop claim [device-or-address] [--json] [--timeout 11s]
magichop release [device-or-address] [--json]
magichop status [device-or-address] [--json]
magichop devices [--scan] [--json]
magichop doctor [--json]
magichop install mac
magichop install raycast [--dir DIR]
magichop uninstall mac
magichop edit
magichop upgrade [--check] [--version VERSION] [--yes]
magichop version
| Path | Purpose |
|---|---|
~/.config/magichop/config.json | Config and shared token |
~/Library/Application Support/MagicHop/state.jsonl | Accepted and final claim records |
~/Library/Logs/MagicHop/daemon.jsonl | Daemon logs |
$TMPDIR/magichop-$UID/daemon.sock | Local CLI socket |
~/Library/LaunchAgents/dev.magichop.daemon.plist | macOS LaunchAgent |
Check for the latest release:
magichop upgrade --check
Upgrade from GitHub Releases:
magichop upgrade --yes
MagicHop downloads the platform archive and checksums.sha256.txt, verifies SHA-256, replaces the current binary, and restarts the LaunchAgent when installed.
This project uses Semantic Versioning.
Content type
Image
Digest
sha256:f84acb8c0…
Size
11.9 MB
Last updated
3 months ago
docker pull yusoltsev/magichop