Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Shell integration

Three ways to reach a host without opening the full TUI: tab completion, a picker bound to a key, and sshelf pick inside your own scripts.

Tab completion

sshelf prod<Tab> completes your saved host names once dynamic completion is set up, which is one line in your shell rc. See Shell completions.

A picker on a key

contrib/sshelf.plugin.zsh (zsh) and contrib/sshelf.bash (bash) bind Alt-s to the host picker and drop what you choose onto the command line you are already typing.

Where you press itWhat you get
empty linesshelf web-01, ready to run
mid-line, e.g. after scp file the host name inserted at the cursor
with rows marked (^s in the picker)every marked name, space separated

Backing out of the picker with Esc leaves your line exactly as it was.

Install it with whatever you already use:

# Oh My Zsh: copy contrib/ in as a plugin directory, then add sshelf to plugins=()
cp -r contrib "$ZSH_CUSTOM/plugins/sshelf"

# zinit
zinit ice as'plugin' pick'contrib/sshelf.plugin.zsh'; zinit load max-rh/sshelf

# antidote (.zsh_plugins.txt)
max-rh/sshelf path:contrib

# or plainly, in ~/.zshrc or ~/.bashrc
source /path/to/sshelf/contrib/sshelf.plugin.zsh

Settings, set before sourcing: SSHELF_BIN is the binary to call, SSHELF_PICK_KEY the binding. ^s is free in zsh only after stty -ixon, since the terminal claims it for flow control, and Alt needs Option-as-Meta turned on in Terminal.app. The bash key binding needs bash 4.0 or newer; on the 3.2 Apple ships, the file defines sshelf-pick as a plain command instead.

sshelf pick on its own

sshelf pick opens the list in the rows under your prompt, prints the chosen host’s name on stdout, and connects to nothing. Your prompt is still there when it closes, and nothing it drew is left behind:

host=$(sshelf pick)      # the list draws on your terminal, the name lands in $host
sshelf "$host"           # connect when you are ready
sshelf pick tag:prod     # start with the search box filled

The streams are the contract. The list is drawn on stderr, so stdout carries the name and nothing else. Quitting without choosing prints nothing and exits 1, which is how a script tells “backed out” from “chose a host”. Mark rows with ^s and every marked name is printed, one per line.

It draws 15 rows by default. --height ROWS changes that for one run, pick_height in config.toml changes it for good, and either set to 0 (or --full-screen) takes the whole terminal the way the TUI does. A height taller than the window is clamped so your prompt keeps a row.

Pick mode answers only the keys a chooser needs: type to filter, ↑/↓ or ^p/^n to move, ^s to mark, Enter to print, Esc to back out, ^c to quit. The keys that add, edit, delete or open another screen do nothing, partly because the add form and the managers are full-screen designs that will not fit in fifteen rows, and partly because something your shell fires off on a keypress should not be able to edit the database on the way past.

Nothing is recorded in the frecency state by a pick, because the connect that normally follows records its own use.

A few things that fall out of it:

# run something on every host you marked
for h in $(sshelf pick); do sshelf "$h" -- uptime; done

# the full ssh command for a host you point at
eval "$(sshelf print-command "$(sshelf pick)")"

# pick, then open the transfer screen on that host
sshelf "$(sshelf pick)"

sshelf pick prints the name sshelf knows the host by, which ssh and scp do not resolve on their own. Either go through sshelf as above, or export an ssh_config fragment once and then plain scp file web-01:/tmp/ works too.

With fzf instead

If you would rather filter with fzf, list --json is the stable surface and needs no picker at all:

sshelf list --json | jq -r '.[].name' | fzf

That loses the site sections, the match highlighting and the frecency order, and it reads the whole database up front. It is the right tool if you are already composing fzf pipelines.

Writing your own integration

sshelf pick is meant to be called from a widget, so two details matter.

Give it a terminal on stderr. A zsh widget keeps all three streams for itself, so the command needs 2>/dev/tty, which is what the shipped plugin does. Without a terminal there, pick refuses rather than drawing into whatever stderr happens to be.

It will find its own keyboard. The picker reads keys from the controlling terminal even when stdin is a pipe or the shell has taken it away, so you do not need < /dev/tty. On macOS that alias is not enough on its own (its event reader cannot watch it), so sshelf resolves the real device for itself.

It borrows fd 1 while the list is up. Placing the inline viewport means asking the terminal where the cursor is, and that question goes out on stdout, which here is your pipe. So pick points fd 1 at the terminal for as long as the list is drawn and hands the pipe back before it prints the name. Nothing else writes to stdout in between.

Background for all three: Decision log, D-039.