say-hi and the alternatives
This project beside its neighbours: some may suit you better, and some were instrumental in this one.
Contents
- The problem being solved
- The direct alternatives, side by side
- Tool by tool
- Adjacent tools, and how they compose
- What actually makes say-hi different
The problem being solved
You have a shell you have spent years tuning, and you spend your day on machines that are not yours: production boxes, a colleague’s server, a jump host, a container that will not exist in an hour. There you get sh-4.4$ and no ll.
Install your config there. Dotfile managers — chezmoi, yadm, GNU Stow, dotbot, rcm, homeshick. Excellent, and say-hi does not compete with them: they assume the machine is yours, that you’ll be back, and that leaving files behind is fine — wrong for a shared production host, a box you touch once, or a container. The edge blurs (chezmoi’s --one-shot, devcontainers cloning a dotfiles repo), but both need the target to reach your repo over the network, both leave files behind, and neither does anything per-session.
Carry your config with you, per session. Ship it over the connection, use it for that session, get out. That is say-hi’s family, and everything below is a member of it.
Not the same thing: terminal emulators that help with ssh, like kitty’s ssh kitten, which solve the adjacent terminfo / shell-integration problem. If your pain is “backspace is broken over ssh”, that is the fix, and it composes with say-hi — which handles the terminfo half itself, swapping a TERM the target has no entry for to xterm-256color (HI.22).
The direct alternatives, side by side
| say-hi | sshrc | xxh | kyrat | sshdot | |
|---|---|---|---|---|---|
| Written in | POSIX/bash shell | shell | Python | bash | shell |
| Client needs | bash 3.2+, base64 (or openssl) | bash, ssh | a Python install (pip/pipx/conda) or the portable binary | bash ≥ 4.0, GNU coreutils | shell, ssh |
| Target needs | base64 (or openssl); bash for the full session | openssl (its base64), tar, bash | Linux x86_64 only | shell | shell |
| Target OS | Linux (glibc + musl), macOS/BSD, Windows via WSL/Git Bash | broad | Linux x86_64 | Linux, macOS | broad |
| Installs on target | nothing | nothing | a portable shell + plugins under ~/.xxh | nothing | nothing |
| Cleans up on exit | yes, automatically | yes, on exit (a hard kill leaves it, as with hi) | not by default (+hhr removes it on disconnect) | yes, automatically | yes, on exit |
| Size ceiling | no argv cap (stdin); CI holds a 64KB gzipped budget | ~64KB and the server may block you | large — it uploads whole shells | small | none (that is its point) |
| Non-ssh targets | docker, podman, nerdctl, finch, nomad, k8s | no | no | no | no |
| Can give you a shell the host lacks | no | no | yes | no | no |
| Maturity | pre-1.0, packaged | original gone from GitHub; cdown’s fork maintained | mature, active | quiet since 2020 | quiet since 2015 |
Tool by tool
sshrc — the ancestor
say-hi is a fork of sshrc (via cdown’s and danrabinowitz’s lines), and the core idea is unchanged: tar your config, base64 it, hand it to the login shell, source it on the far side. Links here point at cdown’s fork, the maintained continuation, which keeps the design (64KB argv ceiling included).
Where sshrc still wins: smaller and simpler, which counts in something that runs on every host you touch. If you just want your .bashrc and .vimrc over there, sshrc does it in a fraction of the code.
Where say-hi went further, beyond the table’s rows:
- Cleanup, proven for the dropped link.
tests/targets/ssh_disconnect_test.shfreezes a live session until sshd reaps it and checks the tree is gone, not only after a cleanexit. - A designed session, not copied files. Header, hashed per-host colors, a git prompt, aliases, editor configs — degrading in defined tiers when the target cannot support all of it.
xxh — the one that solves a harder problem
xxh uploads a portable build of the shell itself, so you can use fish or zsh on a host that has neither.
Where xxh wins outright: that capability. say-hi cannot give you a shell the target lacks — its no-bash ladder (fish > zsh > dash > ash > sh) picks the best of what is installed and says so. xxh’s plugins can also ship whole frameworks, where hi’s extensions/ (HI.59) is shell lines.
Where say-hi wins, beyond the table’s reach and weight rows: no architecture or libc tie. Anything with sh and base64 is in reach, and CI lands sessions on musl, macOS, the BSDs, and Windows (COMPATIBILITY.md) where xxh’s shells are x86_64-Linux builds.
kyrat — closest in spirit
kyrat is the nearest neighbour: a bash ssh wrapper, base64+gzip through the command line, cleanup on exit, KYRAT_SHELL to pick bash/zsh/sh. If you don’t use fish, kyrat is a lighter alternative — ssh only, and bash ≥ 4.0 on the client rules out macOS’s stock bash, but simpler.
sshdot
sshdot is sshrc without the size limit, achieved by not squeezing through the command line. Narrower than say-hi: it solves the one problem it names.
homeshick — the same constraints, the opposite answer
homeshick is a git dotfiles synchronizer in bash whose constraints look most like say-hi’s: “provided that at least Bash 3 and Git 1.5 are available you can use homeshick” — no Ruby, no Python, no root. It answers the other half of the problem, symlinking a cloned repo’s home/ into $HOME and keeping the two in step.
So it is not a competitor and is not in the table. It is the tool for a machine you own and will come back to: the checkout and symlinks stay, and the next login is configured with no client involved. The failure modes are mirror images: homeshick on a production box you touch once leaves a ~/.homesick and an edited rc file for the next person; say-hi on your own laptop re-sends a payload every session for what a symlink gives for free. The two compose — install say-hi permanently on that box (scripts/install.sh) and let homeshick manage everything else.
Adjacent tools, and how they compose
None of these are alternatives — they touch the same session from a different side.
- mosh / Eternal Terminal replace ssh as the transport. hi’s ssh path is two calls multiplexed on one OpenSSH connection, which neither of them is, so
hicannot ride them. What works: install say-hi permanently on the target, then mosh in. - Warp’s SSH extension and “Warpify” attack the same pain from the terminal side: a persistent remote component under
~/.warp*plus a hook line in the remote’s rc files. It ships Warp’s features, not your config. The two coexist — say-hi writes nothing into the remote’s rc files. - chezmoi/yadm/GNU Stow as the overlay’s keeper. Keep
~/.config/say-hi/in your dotfile manager: the manager versions it, hi ships it to every target per session. SETTINGS.md covers symlinks and who ownssettings.sh.
What actually makes say-hi different
Two things; the rest is degree, not kind.
1. It is not an ssh tool. Every alternative above is an ssh wrapper. hi resolves a name through a ladder — ssh host, container (docker, podman, nerdctl, finch), nomad allocation, kubernetes pod — and gives the same session on whichever it finds. hi web-1 is your shell whether web-1 is a Host in ~/.ssh/config or a pod in the namespace your kubectl points at. Nothing else in this space does it.
2. It degrades in stated tiers rather than failing or lying. The compatibility tables answer two questions — can hi land a session here at all, and what shell do you end up in — and mark every cell exercised by a suite, expected, reduced, or decided against. A target with no bash gets aliases, a colored prompt, and a warning; a Windows OpenSSH host with no POSIX shell gets a plain PowerShell session rather than an error.