CLI reference
Everything sshelf does without opening the TUI.
Commands
| Command | What it does |
|---|---|
sshelf | Launch the interactive TUI. |
sshelf <host> | Connect straight to a saved host by name or id, skipping the TUI, on the same connect path as Enter (frecency recorded, stored secret auto-supplied or asked for and saved on the first connect, 2FA code prompted on the terminal). A miss suggests the closest names; a host named like a subcommand (list, import, …) is reached via the TUI instead. |
sshelf - | Reconnect to the most recently used host. Errors (without connecting) if there’s no history yet. |
sshelf add [NAME ...] | Bare: open the TUI add form. With arguments: add a host non-interactively; see below. With --from-ssh: build the host from an ssh command line; see below. |
sshelf list [query] [--json] | List hosts (with a ·site· column). query filters with the TUI’s syntax: fuzzy text and/or tag:NAME / site:NAME (e.g. sshelf list site:prod-dc). The user@host:port column shows site defaults resolved. |
sshelf print-command <host> | Print the generated, shell-quoted ssh ... command (site defaults included) without connecting or touching frecency, the CLI twin of Ctrl-y. |
sshelf sites [--json] | List defined sites with member counts + their shared defaults. |
sshelf sites add NAME [-u/-p/-J/-i] | Define a site (settings optional; edit later with F3). |
sshelf import [--dry-run] | Read-only import from ~/.ssh/config. |
sshelf import --tailscale [--dry-run] | Import your Tailscale tailnet: runs your own tailscale status --json and adds every eligible peer (MagicDNS name → host, tailnet → site, ACL tags → tags). Add-only; re-running adds 0. |
sshelf export [--stdout] | Export the database as an ssh_config Include fragment, written next to sshelf’s config (--stdout prints it instead). Once the file exists, it refreshes on every hosts change. |
sshelf set-password <host> | Store a password / key passphrase for a host, read from stdin. |
sshelf doctor | Check this machine and your host database: the OpenSSH version, the secret backend, dangling site references, a stale export, a missing ssh-agent. Local and read-only; it never contacts a host. Exits 1 if any check failed (warnings don’t). |
sshelf completions <shell> | Print static shell completions. |
sshelf man | Print the man page. |
Global flags work before or after a subcommand (sshelf --config F list and
sshelf list --config F are the same command). --config FILE uses a specific config file
(also $SSHELF_CONFIG); see Configuration. --transfer-log FILE appends
transfer diagnostics, no secrets, to FILE (also $SSHELF_TRANSFER_LOG); see
Transferring files.
A host name and a subcommand can’t be combined: sshelf prod-web list is refused rather than
quietly running list and forgetting the host.
sshelf <host> can ask up to two things on the terminal before it hands over to ssh. A host with
nothing stored asks for its password or key passphrase and saves it once a throwaway login proves
it works (details); Enter skips. A
2FA host then asks for its code. Both read with echo
off. When stdin is a pipe they read plain lines instead, one per question and in that order, so
a script connecting to a password host with nothing stored needs one extra line on stdin, the
same way a 2FA code does.
Plain output replaces terminal control characters. Host names, users, hostnames, tags and site
names come out of hosts.toml, an imported ~/.ssh/config or a tailnet, and any of those can
be crafted: an escape sequence in one of them could move the cursor or forge the lines after
it. Every human-readable field sshelf list, import, add, sites, doctor and the shell
completions print goes through a filter first, which turns control characters, C1 codes and
bidirectional overrides into U+FFFD. The add form and the importers refuse such a name outright,
and an existing hosts.toml still loads: those values are cleaned up on display, not rejected.
Environment: $SSHELF_TAILSCALE_BIN names the tailscale binary --tailscale should run,
for installs that aren’t on PATH (sshelf otherwise tries PATH, then the macOS app bundle
at /Applications/Tailscale.app/Contents/MacOS/Tailscale). $SSHELF_VAULT_PASSPHRASE uses
the age vault instead of the OS keyring; see
Passwords, keys & 2FA.
Adding hosts from the CLI
sshelf add with arguments adds a host non-interactively (handy for scripts and dotfiles).
NAME and --hostname are required:
# key auth (a -i identity implies --auth key)
sshelf add prod-web -H 10.25.25.10 -u deploy -i ~/.ssh/id_ed25519 -t prod,web
# jump host + custom port
sshelf add prod-db -H 10.25.25.25 -u mike -p 5432 -J bastion.example.net -i ~/.ssh/db
# agent auth (the default) with extra raw ssh flags
sshelf add edge -H edge.example.net --extra "-o ServerAliveInterval=30"
# password auth: pipe the secret in (kept out of argv/history; stored in the keyring or vault)
echo "$PASS" | sshelf add legacy -H 10.0.0.9 -u root --password-stdin
| Flag | Meaning |
|---|---|
NAME (positional) | Alias to search/connect by. Required. |
-H, --hostname <HOST> | IP or DNS name. Required. |
-u, --user <USER> | Login user (defaults to $USER at connect time). |
-p, --port <PORT> | SSH port (default 22). |
-a, --auth <agent|key|password> | Auth method. Inferred as key with --identity, password with --password-stdin, else agent. |
-i, --identity <PATH> | Identity file for key auth (repeatable). |
-J, --jump <HOST> | ProxyJump host (repeatable or comma-separated). Key/agent auth only. |
-t, --tag <TAG> | Tag (repeatable or comma-separated). |
-s, --site <NAME> | Assign the host to a site. |
--2fa | Mark the host as needing a verification code on connect. |
--extra "<ARGS>" | Extra raw ssh flags, appended verbatim. |
--password-stdin | Read a password / key passphrase from stdin and store it. |
A duplicate name is refused. To store a secret after the fact, use
sshelf set-password <name>, or let the first connect ask for it.
From an ssh command line
--from-ssh builds the host from a working ssh command instead of flags:
sshelf add --from-ssh 'ssh -i ~/Downloads/dev.pem -p 2222 ubuntu@203.0.113.10'
sshelf add prod-web --from-ssh "$(fc -ln -1)" # the ssh command you just ran
echo 'ssh -J bastion deploy@10.0.0.5' | sshelf add --from-ssh
It opens the add form with the fields filled in, so what’s left to type is the secret, if the
host has one. -q/--quiet saves the host without the form and prints the same added line a
flag-built add does, after one note: line for anything the parser dropped. What maps to which
field, and what gets dropped, is in
Adding & editing hosts.
| Flag | Meaning |
|---|---|
--from-ssh [CMD] | The ssh command as one quoted string. No value, or -, reads one line from stdin. Can’t be combined with --hostname, --user, --port, --auth, --identity, --jump or --extra, which the line already supplies. |
-q, --quiet | With --from-ssh: add without opening the form. Refused without --from-ssh. |
NAME, --tag, --site, --2fa and --password-stdin still apply. Put NAME before
--from-ssh, since the flag takes the next word as its value. With no value and stdin on a
terminal, sshelf stops and shows both ways to pass the command instead of waiting for input.
When --from-ssh and --password-stdin both read stdin, the command line is the first line and
the secret is the second:
printf '%s\n%s\n' 'ssh -p 2222 ops@legacy.example' "$PASS" |
sshelf add legacy --from-ssh --password-stdin --quiet
Without --quiet, a secret read that way is put in the form’s secret field. If there is no
terminal to open the form on at all, the host is added as if --quiet were given, with a note
saying so.
Shell completions
Static completion (subcommands + flags) ships with every package. Open a new shell after
installing so it loads. It’s also printed by sshelf completions <shell>.
Dynamic completion (sshelf prod<Tab> completing your saved host names) takes one
line in your shell rc:
source <(COMPLETE=bash sshelf) # bash: add to ~/.bashrc
source <(COMPLETE=zsh sshelf) # zsh: add to ~/.zshrc (after compinit)
COMPLETE=fish sshelf | source # fish: add to ~/.config/fish/config.fish
Host-name completion works for direct connect, print-command, and set-password.
JSON output
sshelf list --json [query] emits each selected host’s fields plus its generated ssh
command, and is always valid JSON even when the selection is empty, the stable surface for
scripts and integrations. sshelf sites --json does the same for sites.
The host fields are the record as stored in hosts.toml, so a user, port, jump host or
identity file inherited from a site stays null or empty there. The resolved
values are in command.
JSON output is not filtered the way the plain output is: the serializer already escapes
control characters, and a script needs the value exactly as it is stored. command is built as
if no secret were stored for the host, so a listing never reads your keyring once per host;
sshelf print-command and Ctrl-y do resolve the secret and can therefore differ for a host
that has both a stored secret and a jump host. See
SSH command generation.
sshelf doctor has no --json: its exit code (0 healthy, 1 something failed) is the
scriptable part, and a machine-readable report has no users yet (D-027).