TargetBridge's Sender can be driven without the GUI — for scripting, SSH, remote connection from another Mac, or connecting automatically on login/wake. It reuses the same in-app connect/disconnect paths the GUI uses, so there's no separate control logic to keep in sync.
There are two equivalent entry points, plus a small CLI wrapper:
A thin wrapper (in cli/targetbridge) over the URL scheme. Put it on your PATH:
install -m 0755 cli/targetbridge /usr/local/bin/targetbridge # or ~/bin, etc.targetbridge connect # auto-pick receiver, app defaults
targetbridge connect --receiver auto --mode mirror --preset 1440p
targetbridge connect --receiver 169.254.0.2 --mode extended --preset 5k
targetbridge disconnectOptions: --receiver auto|<id|name|ip>, --mode mirror|extended, --preset <name>,
--transport tb|net, --session N, --local-ip <ip>.
Presets: standard1440p, smooth1440p60, smooth1800p60, crisp2160p60, native5k,
native5k60Experimental (aliases: 1440p, 1440p60, 1800p, 4k, 5k, 5k60).
native5k60Experimental is an opt-in HEVC test profile for 5K at 60 FPS; it does not
replace the stable 5K 48 FPS profile. A receiver of auto waits briefly for
Bonjour discovery and uses the first receiver found; a raw IP/hostname bypasses discovery.
It launches the Sender on demand and works whether the app is already running or not.
The CLI just builds and opens a targetbridge:// URL — you can use these directly
(in Shortcuts, Raycast, a .command file, etc.):
targetbridge://connect?receiver=auto&mode=mirror&preset=native5k
targetbridge://connect?receiver=<receiver-ip>&mode=extended&preset=1440p&session=1
targetbridge://disconnect
open "targetbridge://connect?receiver=auto&mode=mirror&preset=1440p"Passing --connect (and the same options) to the app at launch connects once it's up —
handy for a Login Item or LaunchAgent:
open -a TargetBridge --args --connect --receiver auto --mode mirror --preset 1440pConnect from another Mac over SSH — the Sender must be at a logged-in desktop with TargetBridge installed.
Start with the simplest form first:
ssh <sender-user>@<sender-host> \
"open 'targetbridge://connect?receiver=auto&mode=mirror&preset=1440p'"On some macOS setups, if LaunchServices does not deliver the URL into the active GUI
session from a plain remote shell, fall back to launchctl asuser:
ssh <sender-user>@<sender-host> \
"launchctl asuser \$(id -u <sender-user>) open 'targetbridge://connect?receiver=auto&mode=mirror&preset=1440p'"If that still fails with an audit-session permission error, try the same command through
sudo from an interactive SSH session (ssh -t ...), because the GUI handoff can be more
strict on some systems.
Auto-connect on wake (Hammerspoon, on the sender):
hs.caffeinate.watcher.new(function(e)
local w = hs.caffeinate.watcher
if e == w.systemDidWake or e == w.screensDidUnlock then
hs.timer.doAfter(2, function()
hs.execute("open 'targetbridge://connect?receiver=auto&mode=mirror&preset=1440p'")
end)
end
end):start()Arrange the display after connecting (displayplacer): TargetBridge restores its saved
extended-desktop arrangement at connect time, so apply your own layout after the stream
is up (e.g. poll until the display appears, then run your saved displayplacer "..." command).
- The Sender's capture pipeline requires a logged-in GUI session; these entry points signal the app inside that session — they do not (and cannot) start screen capture from a pure headless context.
autorequires the receiver to be discoverable over Bonjour (_targetbridge._tcp). On first use, grant the Sender Screen Recording permission as usual.- These commands are fire-and-forget: the CLI /
openreturns as soon as the URL is delivered, which is not the same as "streaming established." Check the app (or its log:log show --predicate 'eventMessage CONTAINS "[automation]"' --last 2m) to confirm. - For remote SSH automation, a plain
open 'targetbridge://...'is often enough and is the least brittle option. Uselaunchctl asuseronly when the plain form does not reach the active GUI session on the sender. connectwithout--sessiontargets session 1 and updates its saved receiver, same as changing it in the GUI.- On a cold launch,
receiver=autowaits briefly for Bonjour; if discovery is slow on the first run, pass an explicit--receiver <ip>to skip the wait.