Move the focused macOS window to another Space and follow it. ferry adapts the
code behind yabai -m window --space N --focus into a standalone command.
You do not need yabai installed.
ferry 3- Runs once and exits, with no background service or configuration file.
- Requires no scripting addition or changes to System Integrity Protection.
- Includes Raycast Script Commands for Spaces 1 to 5.
I only used yabai to move a window and follow it to another Space. I wrote ferry so I could keep that command without running the yabai service.
ferry supports macOS 26 Tahoe on Apple silicon only.
brew install softmaxe/tap/ferryTo install without Homebrew, use a release archive or build from source.
-
Open System Settings > Desktop & Dock > Mission Control and turn off Automatically rearrange Spaces based on most recent use. This keeps Space numbers stable while you switch between them.
-
Open System Settings > Privacy & Security > Accessibility and turn it on for the app that runs ferry, such as Raycast or your terminal. ferry does not prompt for permission. If it cannot read the focused window through Accessibility, it uses the frontmost app's topmost visible normal window. That may be a different window when the app has several open.
-
Make sure Space 2 exists and is not a native fullscreen Space, then run:
ferry 2
The terminal window moves to Space 2 and the screen follows it. The terminal has focus when you press Return. To move another window, bind ferry to a hotkey or pass
--window <id>.
Homebrew does not install the Raycast scripts. Clone this repository to a folder
you will keep, then add its raycast/ directory to Raycast:
git clone https://github.com/softmaxe/ferry.git ~/ferry- In Raycast Settings > Extensions, choose + > Add Script Directory
and pick
~/ferry/raycast. If you already have a script directory, copy the five files into it instead. - Find Ferry Window to Space 1 and assign a hotkey. Repeat for each Space you use, up to 5.
For Space 6 and up, copy a script and update space=, @raycast.title, and
@raycast.description to match the destination.
The scripts use FERRY_BINARY if set. Otherwise, they look for ferry on
PATH, then in /opt/homebrew/bin, /usr/local/bin, $HOME/.local/bin, and
build/ next to the raycast/ directory.
For a custom binary location, add this line to each script before the binary lookup. Exporting it in a terminal only affects commands launched from that shell:
export FERRY_BINARY="/path/to/ferry"ferry 3 # move the focused window to Space 3 and follow it
ferry --no-follow 3 # move it and stay on the current Space
ferry --verbose 3 # also print the window ID and timing
ferry --window 1234 3 # move window 1234 instead of the focused window
ferry --help # print usage; -h also works
ferry --version # print the versionSpace numbers start at 1 and count all Spaces across all displays in Mission
Control order, including native fullscreen Spaces. They match the index field
of yabai -m query --spaces. The destination must already exist and must not be
a native fullscreen Space. ferry does not create Spaces.
--window takes a macOS window ID, not an app's process ID. If the window is
already on the destination Space, ferry skips the move and still requests focus
unless you pass --no-follow.
ferry prints nothing after a successful move unless --verbose is set.
Verbose output includes the window ID, how ferry selected it, the destination
Space ID, and move and total timing. These times cover work inside ferry. They
exclude launcher startup and do not measure the full Space switch animation.
--verbose, --help, and --version print to stdout. Errors go to stderr.
The exit status is 0 on success, 1 for runtime errors, and 2 for invalid
arguments. ferry verifies that the window is on the destination Space, but does
not verify that the focus change succeeded. Verbose output says focus requested
to distinguish the request from a confirmed Space switch.
Follow from a background app needs an Accessibility reference to the selected window. ferry resolves explicit window IDs before moving them. Without Accessibility, or when a window starts on an inactive Space and macOS omits it from the app's Accessibility window list, the window may move without Follow.
| Problem | What to do |
|---|---|
space N does not exist |
Create more Spaces in Mission Control, or pick a lower number. |
space N is a native fullscreen space |
Choose a non-fullscreen Space. ferry rejects fullscreen destinations. |
no focused window |
Click the window you want to move, then retry. |
window N not found |
The ID passed to --window may be stale. Use the focused window or a current window ID. |
window ... did not move |
ferry could not confirm the move within one second. Some windows, such as system panels and fullscreen windows, may refuse the move. |
unsupported macOS version |
ferry could not find SkyLight's private window move interface, usually because a macOS update changed it. ferry supports macOS 26 only. |
| ferry moves the wrong window | Turn on Accessibility for the app that runs ferry. See Set up. |
| Space numbers don't match what you see | Count Spaces across all displays, including fullscreen Spaces, and turn off automatic rearranging. See Set up. |
| A new binary or script starts slowly | Compare the first run with later runs. --verbose measures work inside ferry, so launcher and macOS startup checks can add time outside that measurement. |
| macOS cannot verify the developer | Browser downloads may trigger this warning. After trying to run ferry, open System Settings > Privacy & Security and choose Open Anyway. See Apple's instructions. |
Each release has an archive for Apple silicon, its SHA-256 checksum, and GitHub build provenance. Run these commands in an empty directory with the GitHub CLI:
gh release download --repo softmaxe/ferry --pattern '*-aarch64-apple-darwin.tar.gz*'
shasum -a 256 -c ferry-*.tar.gz.sha256
gh attestation verify ferry-*.tar.gz --repo softmaxe/ferry
tar -xzf ferry-*.tar.gz ferry
mkdir -p ~/.local/bin && install -m 0755 ferry ~/.local/bin/ferryMake sure ~/.local/bin is on your PATH. After installation, you can delete the
downloaded archive, checksum, and extracted binary.
The binary has an ad hoc signature, without an Apple Developer ID signature or notarization. Browser downloads may trigger a warning on the first run. See Troubleshooting.
Building requires the Xcode Command Line Tools with a macOS 26 SDK or newer.
git clone https://github.com/softmaxe/ferry.git
cd ferry
make
make test
make install PREFIX="$HOME/.local"make test checks the command-line interface and the built binary's architecture,
deployment target, and that it links AppKit. It does not move windows.
make test-follow runs the live desktop regression for background --window
commands. It requires Accessibility and two normal Spaces on the active display,
creates temporary test windows, and temporarily switches Spaces. It closes the
test windows and restores the original app afterward.
With Homebrew:
brew upgrade ferry
brew uninstall ferryFor the other methods, repeat the install to upgrade, or delete the installed
binary to uninstall. The commands above install it at ~/.local/bin/ferry.
If you added Raycast scripts, remove them from your script directory too.
ferry creates no configuration or data files.
ferry adapts yabai's Space lookup, window move, and focus code for macOS 26:
- Maps the Space number to a Space ID with
SLSCopyManagedDisplaySpacesand rejects fullscreen destinations. - Uses
--windowif supplied. Otherwise, it reads the frontmost app's focused window through Accessibility, with a fallback to that app's topmost visible normal window. - If the window is not already there, moves it with SkyLight's private
SLSBridgedMoveWindowsToManagedSpaceOperationand sets the destination's front process withSLSSpaceSetFrontPSN. It waits up to one second for WindowServer to report the window on the destination Space. - Unless
--no-followis set, requests focus with_SLPSSetFrontProcessWithOptionsand synthesized key-window events. It also callsAXRaisewhen the Accessibility lookup returned a window reference. Focusing the window asks macOS to switch to its Space.
The binary links AppKit even though it calls no AppKit API. Without AppKit loaded, WindowServer ignores the move operation and reports no error.
These private macOS interfaces can change with an OS update. For upstream
changes, see space_manager_move_window_to_space in yabai's
src/space_manager.c
and window_manager_focus_window_with_raise in
src/window_manager.c.
MIT. The window management code comes from yabai by Åsmund Vikane, also MIT.
