Integrations
hi installs and ships none of the tools below. Where a target has one, a session wires it in the way the tool’s own README wires it into an rc; where it does not, the session goes on without it and says nothing. The switches are rows in SETTINGS.md; _HI_DISABLE_LOCAL=1 sets every _HI_DISABLE_* one, and turns the two alias opt-ins off, on your own machine only (On your own machine).
Contents
- At a glance
- Prompt programs
- Shell hooks of your own
- Header cells of your own
- The environment segment
- bat and eza
- Terminal multiplexers
- Debian chroots
- readline
- Shell frameworks
- Which side is asked
- A tool with no variable and no flag
- Config sizes
At a glance
| tool | what hi does with it | on by default | switch |
|---|---|---|---|
| starship, oh-my-posh, powerline-go, powerlevel10k, oh-my-zsh themes, oh-my-bash themes, bash-it themes, tide | draws the prompt in hi’s place, with your config from home | yes, where installed here | _HI_PROMPT_TOOL (hi for hi’s own) |
| mise, asdf, pyenv, rbenv, nodenv, nix, guix, devbox, devenv, direnv, conda, venv | names the active ones in the prompt’s leading (myproj) segment | yes | _HI_DISABLE_ENV_STATUS |
| bat, eza, exa | cat, bat, and one ls/eza/exa alias with hi’s flags, your theme from home | no - opt-in, where installed | _HI_TOOL_ALIASES, the _HI_*_OPTS and _BINs |
| tmux, zellij, screen | hi --mux runs the connect inside one, on the client, and hi --keep the session inside tmux or screen on the target; a tmux on a target reads your tmux/tmux.conf | no - per connect | --mux, --no-mux, --keep, --no-keep, --end |
| vim/neovim, nano, emacs, micro, helix, kakoune | opened with hi’s config, or yours, through an alias - neovim reads nvim/init.lua, vim vim/vimrc, micro your micro directory’s files, helix helix/config.toml and languages.toml, kakoune kak/kakrc and its colors/ through $KAKOUNE_CONFIG_DIR on a target | yes | hi --plugin-off editors, or one by name; the files are SETTINGS.md’s overlay table |
| oh-my-zsh, powerlevel10k, bash-it, fzf | loads after them and leaves their hooks working | - | Shell frameworks |
Prompt programs
A prompt program you already use draws the prompt in hi’s place, on this machine and on every target that has it: hi keeps the header, aliases, and editors, and hands over only the prompt line. Out of the box _HI_PROMPT_TOOL is unset, which means every program installed here, in this order - the first that fits the shell and that the target has wins:
| program | shells | counts as installed here | started on a target | your config from home |
|---|---|---|---|---|
| powerlevel10k | zsh | your zsh rc loads the theme (ZSH_THEME=powerlevel10k/powerlevel10k, a source of powerlevel10k.zsh-theme, or a manager naming romkatv/powerlevel10k) and ${ZDOTDIR:-~}/.p10k.zsh (or $POWERLEVEL9K_CONFIG_FILE) exists | as the rc loaded it, else from ~/powerlevel10k, oh-my-zsh’s custom themes, the distro or Homebrew path | that file, sourced after powerlevel10k |
| oh-my-zsh | zsh | ZSH_THEME in ${ZDOTDIR:-~}/.zshrc names a theme file | as the rc loaded it, else only the libraries themes call | the theme file, found the way oh-my-zsh finds it - so a custom theme works on a box without it |
| oh-my-bash | bash | OSH_THEME in ~/.bashrc names a theme file | as the rc loaded it, else without its plugins, aliases, or completions | the theme file, likewise |
| bash-it | bash | BASH_IT_THEME in ~/.bashrc names a theme file | as the rc loaded it, else BASH_IT_THEME pointed straight at the home theme file and the whole framework sourced | the theme file, found the way bash-it finds it |
| tide | fish | its functions autoload in fish (as fisher installs it) | fish loads it; hi only leaves its prompt alone | the tide_* universal variables - never the rest of fish_variables |
| starship | bash, zsh, fish | on $PATH | starship init <shell> | $STARSHIP_CONFIG, else ~/.config/starship.toml |
| oh-my-posh | bash, zsh, fish | on $PATH | oh-my-posh init <shell> | $POSH_CONFIG (or the older $POSH_THEME), else the file your rc’s oh-my-posh init --config names (json, yaml, or toml) |
| powerline-go | bash, zsh, fish | on $PATH | once per prompt, as its README wires it, with _HI_POWERLINE_GO_OPTS | the flags in _HI_POWERLINE_GO_OPTS |
A framework theme from home is sourced on a target only once hi has found that framework’s tree there ($ZSH/lib/git.zsh, $OSH/oh-my-bash.sh, $BASH_IT/bash_it.sh), so a theme’s source "$ZSH/lib/..." rides as written: on a target without oh-my-zsh the theme never runs, and hi’s prompt draws instead. Only the theme gets that pass, and only for its own framework - the same line in an extension or a shell rc, which run on every target, is disabled like any include hi cannot carry (# hi-allow above it keeps one you have guarded yourself). A theme that sources a library its target’s framework version lacks fails there as it would at home.
So a powerlevel10k-in-zsh, tide-in-fish user gets both prompts on every box that has them, and hi’s where it has neither. A prompt hi has no hand-over for stays yours rather than being drawn over: liquidprompt or bash-git-prompt in bash; spaceship, pure, or a promptinit theme (prezto’s included) in zsh; and in fish any fish_prompt that is not fish’s own - your functions/ directory’s, a theme such as pure, hydro, or bobthefish, or one your config defines. A PS1 or PROMPT you wrote in your rc stays as well, in bash and zsh at home: hi draws over the ones nobody wrote - the shell’s built-in default and the one a stock rc sets on Debian and Ubuntu, Raspberry Pi OS, Kali, Fedora and the RHEL family, Arch, Alpine, openSUSE, Gentoo, macOS, Git Bash, MSYS2, Cygwin, and Termux - and leaves any other alone. A target’s own rc is not asked, so a session’s prompt is hi’s whatever the box sets; a prompt of yours for targets goes in the overlay’s bashrc or zshrc with _HI_DISABLE_PROMPT=1. hi in _HI_PROMPT_TOOL takes the prompt anyway. Under powerlevel10k’s instant prompt, hi calls p10k clear-instant-prompt before drawing the header, the call p10k provides for an rc that prints, so it does not warn about console output on every start. The list is worked out on this machine and handed to the target, which never looks for programs of its own - a shared box with powerlevel10k installed does not change your prompt unless you use it too.
_HI_PROMPT_TOOL=hi keeps hi’s prompt everywhere: it starts no program on any target, and takes the prompt back from one the target’s own rc started, unhooking its prompt hook. Only the name hi does that - an unset list, or one no entry of which fits, leaves the target’s own choice drawing. To choose, name them: "tide starship" is tide in fish and starship in bash and zsh, "tide hi" tide in fish and hi’s prompt elsewhere. An entry written <shell>:<program> is tried first, by that shell alone: "bash:starship hi" is starship in bash and hi’s prompt in zsh and fish, and "bash:starship" alone leaves zsh and fish to the programs installed here, as unset does. hi --configure’s Prompt page asks shell by shell and writes these entries. _HI_DISABLE_PROMPT=1 beats all of it: no prompt from hi at all, its own or a program’s.
The configs from home ride the overlay - a copy in ~/.config/say-hi/ rides in its place, which is how targets get a different one - and apply on a target only, over whatever the target has; at home each program’s own config is already in force. Why hi hands over the prompt and nothing else, and how each program is started, is HI.32.
Shell hooks of your own
zoxide’s and atuin’s init, direnv hook, and mise activate are plugins of the hooks group, off by default: hi --plugin-on zoxide has a target that has zoxide run its init after the aliases and extensions, and hi --plugin-on hooks turns on all four (SETTINGS.md). A tool of your own is one hi --add-plugin hooks <name> 'init=<command> {shell}'. Once started, a tool keeps state of its own under the target’s $HOME - zoxide’s directory database, atuin’s history - which hi neither writes nor cleans up, and which is why none is on until you say so. The per-shell files the overlay carries (~/.config/say-hi/bashrc, zshrc, config.fish) still take a line of your own for anything else:
# ~/.config/say-hi/bashrc
command -v fzf >/dev/null && eval "$(fzf --bash)"
Header cells of your own
A header item hi does not have is a file under ~/.config/say-hi/header/, named for its word, that defines _hi_cell_<word>: the function writes the cell’s text, a color first, into the variable its first argument names. The file rides to every target with the rest of the overlay, and _HI_HEADER_ORDER puts the word where it prints:
# ~/.config/say-hi/header/load
_hi_cell_load() {
local _load_l
read -r _load_l _ 2>/dev/null </proc/loadavg || _load_l="?"
printf -v "$1" '%s' "${YELLOW}Load: $_load_l"
}
# ~/.config/say-hi/settings.sh
export _HI_HEADER_ORDER="utc version localtime os load check"
The header is bash on every side, so the file is bash whatever shell the session runs. The word is the name up to its first . (load.sh is load too), and a file named for a built-in word replaces that item. Prefix the function’s locals: the variable it writes is its caller’s. One of hi’s color variables leads the text - $RED, $GREEN, $YELLOW, $BLUE, $PURPLE, $CYAN, or a $BR one of them - and a cell that would repeat its neighbor’s color takes the next hue round from its own (HI.48). The function runs on every header hi draws, a local shell’s greeting included, so a command it starts costs that fork each time; the built-in items’ probes are not shared with it. A file bash cannot parse is skipped with a yellow line saying so, and hi --doctor lists the cells in the order they load and flags that one. hi --configure lists the words that loaded beside hi’s own, and a line sourcing a file outside ~/.config/say-hi goes out disabled, as in every overlay file (SETTINGS.md).
The environment segment
The prompt’s leading (myproj) names every environment manager that is active, outermost first: (mise|direnv:proj|myproj) is mise activated, a direnv-loaded proj, and a venv inside it. It reads $MISE_SHELL, $ASDF_DIR, $PYENV_VERSION/$RBENV_VERSION/$NODENV_VERSION, $IN_NIX_SHELL, $GUIX_ENVIRONMENT, $DEVBOX_SHELL_ENABLED, $DEVENV_ROOT, $DIRENV_DIR, $CONDA_DEFAULT_ENV, and $VIRTUAL_ENV_PROMPT/$VIRTUAL_ENV - variables the tools export, so a draw costs no probe and no fork. mise is named only where a config file between the directory and ~ overrides the global one, so an activated mise with nothing but ~/.tool-versions stays off the prompt. A .venv is named for the directory holding it, not for itself. _HI_DISABLE_ENV_STATUS=1 turns the whole segment off. The segment is part of hi’s prompt, so a prompt program replaces it along with the rest.
Tools that draw their own prefix
hi stands down for a tool already drawing its own prefix, so nothing appears twice: a source .venv/bin/activate keeps its own (myproj) in zsh and fish, where the shell keeps the prompt the activate script edited. bash is the exception - hi rebuilds $PS1 on every draw, so that prefix cannot survive and hi draws the segment itself. A venv is named in all three shells, then, in its own styling or hi’s; direnv, nix, and the rest have no prefix of their own and are always hi’s.
To get hi’s styling and naming everywhere, silence the tool’s own prefix the way the tool documents: VIRTUAL_ENV_DISABLE_PROMPT=1 for a venv (export it before you activate) and conda config --set changeps1 false. hi then draws the segment in every shell - which is also how a .venv stops reading as (.venv). hi never sets those two for you: every other shell and prompt you open reads them too (HI.54).
bat and eza
With _HI_TOOL_ALIASES=1, common/aliases.sh builds the styled tool aliases from whatever the target has, first installed wins:
catandcatnrun bat (Debian’sbatcat, where that is its name) with_HI_BAT_OPTS- no pager, two-space tabs, and thechanges,gridstyle, with the theme left to your bat config - andcatnadds line numbers. Without bat,catfalls through toccat, then plaincat, andcatnis not defined.ls,eza, andexaare one alias under three names, running the first of eza, exa, andlsthe target has (_HI_LS_BIN);ezaandexaanswer only where that binary is installed. The flags follow the rung that answered, since the three share almost no syntax:_HI_EZA_OPTS,_HI_EXA_OPTS, or a plain-F -lforls, with--color=autowhere thatlstakes it (coreutils, busybox, newer BSD), since it colors only when asked._HI_LS_OPTSis whichever of those the ladder picked, and setting it yourself wins outright.
Off, the default, none of these exists and the binary lookups behind them are skipped: cat, ls, and bat are the commands themselves. The tmux, screen, and zellij config aliases are not among them: they follow the overlay’s files alone (Terminal multiplexers). No alias names a tool the target lacks: an editor, tmux, bat, eza, or sudo that is not installed leaves its name to the shell’s own not-found. The flags and the binary each alias runs are rows in Every setting, set in your settings.sh; to add one flag to hi’s instead, redefine the alias in your aliases.sh.
Shipping your bat theme
Every target gets the bat config you already keep: hi ships the file bat reads here - $BAT_CONFIG_PATH, else $BAT_CONFIG_DIR/config, else ~/.config/bat/config (under $XDG_CONFIG_HOME when set) - or, when there is one, the bat/config in ~/.config/say-hi/ instead. On a target the file becomes $BAT_CONFIG_PATH, and the default _HI_BAT_OPTS carry no --theme, so the file’s theme is the one you see through cat and bat; a _HI_BAT_OPTS of your own always wins outright.
Shipping your eza theme
eza reads its colors from $EZA_CONFIG_DIR/theme.yml, and only under that name. hi ships the one eza reads here - $EZA_CONFIG_DIR/theme.yml, else ~/.config/eza/theme.yml (under $XDG_CONFIG_HOME when set) - or the eza/theme.yml in ~/.config/say-hi/ when there is one. On a target, EZA_CONFIG_DIR points at the directory holding the shipped copy (HI.62); at home the variable is left alone. Like BAT_CONFIG_PATH, it is exported whatever _HI_TOOL_ALIASES says, so a bare command eza matches too.
Terminal multiplexers
hi --mux <target> starts the connect inside a session of the first of tmux, zellij, and screen on your PATH, named hi-<target>, and a second hi --mux <target> joins the one already running - so a dropped link leaves a session to reattach to, on your side. Already inside tmux, hi switches the client to that session rather than nesting; inside screen or zellij it opens a new window or tab. _HI_MUX=1 (hi --configure’s advanced item) makes it the default and --no-mux skips it once. The target sees an ordinary session; HI.52 is how the wrap works.
hi --keep <target> puts the multiplexer on the target instead: the session runs in tmux, zellij, or screen there, the first of the three it has, as hi-<target>, and outlives the connection, your machine sleeping, or a move to another one. Every later hi <target> reattaches rather than starting over; --no-keep asks for an ordinary session beside it. It ends three ways:
exitin the first pane, which asks first (close the kept session? [y/N]); anything butydetaches you and leaves a fresh shell in the pane (zellij cannot be told to detach, so there you stay attached to the fresh shell);hi --end <target>, from your side, without attaching;- nobody attached for
_HI_KEEP_TIMEOUT(24h).
Each removes the session directory, as any session’s end does (SECURITY.md). Detach with the multiplexer’s own key. It needs an ssh target with bash and one of the three, and a terminal on your side; anywhere else hi says so and connects as usual. A zellij session starts under zellij’s own two bars, kept off the disk (nothing to resurrect) and with its startup popups off. A pane or window you open in a kept session is hi’s session shell too, not the host’s bare one, and closing the first pane (a y) closes them all. Typed with no target inside a session you already have, hi --keep keeps that one: a fresh shell in a multiplexer there, under the same name, sharing the session’s directory. Detaching lands you back in the shell you typed it in, and whichever of the two ends last removes the directory. A kept session that dies with its target - a reboot that keeps /tmp - leaves its directory behind; your next hi <target> removes it, and says the kept session is gone if this machine had seen it. When the link to a kept session drops and hi is itself running in a pane of a local tmux, zellij, or screen, it retries for _HI_KEEP_RETRY (5m) and lands back in the session; anywhere else it ends as ssh does, and hi <target> reattaches. _HI_KEEP=1 makes it the default. The two flags combine: under hi --mux --keep both ends hold a session, and the inner multiplexer’s prefix key has to be sent through the outer one. HI.65 is how it works.
A tmux you start on a target reads the config you use here: ~/.tmux.conf (else $XDG_CONFIG_HOME/tmux/tmux.conf, and an overlay tmux/tmux.conf over both) rides along and the session’s tmux alias is tmux -f it. screen the same, ${SCREENRC:-~/.screenrc} under screen -c; and zellij’s config directory ($ZELLIJ_CONFIG_DIR, else $XDG_CONFIG_HOME/zellij) - config.kdl and every file of layouts/ and themes/, an overlay zellij/ copy of each name first - under the alias’s --config-dir. A source-file of another file, TPM’s @plugin list and its run, screen’s source, zellij’s layout_dir/theme_dir and file: plugins name something the target does not have, so they go out disabled and hi --doctor names the line.
Debian chroots
A chroot’s /etc/debian_chroot leads the prompt, as the distro’s own ~/.bashrc sets it up: (name) in bash and zsh, (chroot:name) in fish. hi sets no $LESSOPEN, $GCC_COLORS, $CLICOLOR, or $LSCOLORS: those stay your rc’s.
readline
Every target gets the inputrc you already keep: hi ships the file readline reads here - $INPUTRC, else ~/.inputrc - or the inputrc in ~/.config/say-hi/ when there is one. On a target, INPUTRC points at the shipped copy, so bash’s line editing and every readline program started from the session take your bindings; zsh and fish have line editors of their own and ignore it. At home the variable is left alone. Nothing is asked about first: readline is a library, not a command on PATH.
A set INPUTRC replaces /etc/inputrc rather than adding to it, so an inputrc that relies on the system one says $include /etc/inputrc, which rides as written. An $include of any other file is dropped on the way out (SETTINGS.md).
Shell frameworks
A framework on a target loads normally. hi lands you in your own login shell when hi styles it, else the best of fish zsh bash the target has, and hi’s setup runs after that shell’s own rc - so hi is the one positioned to break a framework, and the one tested for it. tests/targets/framework_test.sh installs twelve per their own READMEs - oh-my-zsh, powerlevel10k, starship, bash-it, oh-my-bash, tide, powerline-go, fzf, zoxide, direnv, atuin, and mise - plus a tmux under a ~/.tmux.conf of the target’s own, connects for real, and asserts no shell errors and each one’s hook, Ctrl-R binding, array base, or prompt intact; the prompt programs are handed the prompt with a marker config from home, and tmux must read the client’s config over the target’s.
On your own machine
A prompt program your rc loads keeps drawing here without asking (Prompt programs). _HI_DISABLE_LOCAL=1 goes further: every _HI_DISABLE_* switch on and both alias opt-ins off, so everything on this page stays as your own rc set it up on this machine, while every target still gets hi’s. How hi tells home from a target is SETTINGS.md’s Others.
Which side is asked
Two machines could answer “is this tool installed”, and hi asks each about a different thing:
- The client, about what rides. A config from home - your
~/.vimrc,~/.tmux.conf, micro’s directory, bat’s and eza’s files - is “the one in force here” only with its tool here to read it, so it ships only then:vim/vimrcwith vim,nvim/init.luawith nvim,helix/’s files with hx,kak/’s with kak,nano/nanorcwith nano,emacs/init.elwith emacs,tmux/tmux.confwith tmux,screenrcwith screen,micro/with micro,zellij/with zellij,bat/configwith bat (orbatcat),eza/theme.ymlwith eza,ripgreprcwith rg,fzfrcwith fzf,lazygit/config.ymlwith lazygit;inputrcalways, since readline is a library, not a command. A dotfile left behind by a tool you removed neither ships nor gets ahi --doctorrow. It is the client because only the client can be asked before a connect, which is when the overlay is packed - the reason the prompt programs are a list worked out here too. - Nobody, about an overlay copy. A file you put in
~/.config/say-hi/is you saying “targets get this”, and it rides whatever this machine has - the way to carry avim/vimrcfrom a laptop that only has neovim. - Nobody, about a plugin that is off.
hi --plugin-offkeeps every file of that plugin home, overlay copy included (SETTINGS.md). - The target, about what is used. Each alias is made from what the target has, and only for a config that rode (HI.62), so a config that rode to a box without its tool is a few idle bytes, and a tool whose config stayed home keeps its own - never an alias to a missing binary or file.
A tool with no variable and no flag
A carried config reaches its tool on a target the way the tool lets it: a variable naming the file ($RIPGREP_CONFIG_PATH) or its directory ($KAKOUNE_CONFIG_DIR), or a flag an alias adds (tmux -f, hx -c). Some files have neither - helix’s languages.toml is read from its config directory alone - and for those a row’s wire is xdg:<command>: the alias sets $XDG_CONFIG_HOME to the overlay, where each <tool>/<file> member sits as it would under ~/.config (SETTINGS.md).
It is the fallback, not the first choice. The variable reaches everything the command starts, not just the tool: a language server under helix, or a git under lazygit, looks in the overlay for its own config and not in the target’s ~/.config. Use it only for a file nothing else points at; hi’s own helix row keeps hx -c for config.toml and switches to xdg: only when a languages.toml rides too.
Config sizes
Theoretical. None of these rows is a measurement of a real user’s setup or a promise about a connect: each pairs a plausible configuration with the size of public sample files like it, run through hi’s comment strip and
gzip -9nby hand. Real configs vary widely;hi --doctorand the size hi prints on connect are the numbers for yours.
Everything in the overlay rides every connect beside the payload (README’s badge measures it), so what a heavy config costs on the wire is the gzipped size after hi strips comments and blank lines (HI.35). Prose-heavy files shrink the most: powerlevel10k’s wizard output is three-quarters comments.
| user | what rides the overlay | on disk | stripped | on the wire (gzip) |
|---|---|---|---|---|
| defaults, nothing configured | nothing | 0 | 0 | 0 |
| a few settings and aliases | settings.sh, aliases.sh | ~2 KB | ~1 KB | ~0.5 KB |
| starship with a preset | starship.toml (the nerd-font-symbols preset) | ~3.4 KB | ~3.4 KB | ~1.4 KB |
| oh-my-posh with a stock theme | oh-my-posh.json (jandedobbeleer) | ~7 KB | ~7 KB | ~1.4 KB |
| oh-my-zsh, robbyrussell | oh-my-zsh.zsh-theme | ~0.4 KB | ~0.4 KB | ~0.2 KB |
| oh-my-zsh, agnoster | oh-my-zsh.zsh-theme | ~13 KB | ~8 KB | ~2.5 KB |
| oh-my-bash, font or agnoster | oh-my-bash.theme.sh | 2-20 KB | 1-9 KB | 0.5-2.6 KB |
| tide, configured by its wizard | tide.vars (its ~160 variables) | ~6 KB | ~6 KB | ~1.5 KB |
| powerlevel10k from its wizard | p10k.zsh (lean or rainbow) | 90-95 KB | 24-28 KB | ~5.5 KB |
| a tuned vim | vim/vimrc (like amix/vimrc’s basic.vim) | ~9.5 KB | ~4 KB | ~1.8 KB |
| a neovim starter config | nvim/init.lua (like kickstart.nvim, single file) | ~44 KB | ~19 KB | ~6 KB |
| a long-lived bash setup | bashrc, aliases.sh, a few extensions | 10-30 KB | 5-15 KB | 2-6 KB |
| all of it: powerlevel10k, tide, neovim, vim, bash, starship | everything above that ships at once | ~200 KB | ~75 KB | ~20 KB |
A neovim config spread over many files under ~/.config/nvim/lua/ does not ride at all - only nvim/init.lua does - so its size here is the single file, and hi --doctor says how many files stay home.