#!/usr/bin/env bash # ~/.config/kitty/ssh_picker.sh # Launched by kitty as an overlay window. # Reads servers from ~/.ssh/config (Host entries) and lets # you fuzzy-filter with arrow keys, then opens kitten ssh. # # Dependencies: none beyond bash + standard coreutils. # Optional: fzf — if present it takes over the picker UI. set -euo pipefail # ------------------------------------------------------------ # config — edit these or let them fall through to ~/.ssh/config # ------------------------------------------------------------ SSH_CONFIG="${HOME}/.ssh/config" TERM_FALLBACK="xterm-256color" # ------------------------------------------------------------ # gruvbox dark ANSI helpers # ------------------------------------------------------------ RESET=$'\e[0m' BOLD=$'\e[1m' DIM=$'\e[2m' GB_BG=$'\e[48;2;40;40;40m' # #282828 GB_BG1=$'\e[48;2;60;56;54m' # #3c3836 GB_BG2=$'\e[48;2;80;73;69m' # #504945 GB_YELLOW=$'\e[38;2;250;189;47m' # #fabd2f GB_BLUE=$'\e[38;2;131;165;152m' # #83a598 GB_GREEN=$'\e[38;2;184;187;38m' # #b8bb26 GB_RED=$'\e[38;2;251;73;52m' # #fb4934 GB_ORANGE=$'\e[38;2;254;128;25m' # #fe8019 GB_FG=$'\e[38;2;235;219;178m' # #ebdbb2 GB_DIM=$'\e[38;2;168;153;132m' # #a89984 # ------------------------------------------------------------ # parse servers # ------------------------------------------------------------ get_servers() { local servers=() # pull Host entries from ~/.ssh/config (skip wildcards) if [[ -f "$SSH_CONFIG" ]]; then while IFS= read -r line; do if [[ "$line" =~ ^[Hh]ost[[:space:]]+([^*?]+)$ ]]; then local host="${BASH_REMATCH[1]// /}" [[ -n "$host" ]] && servers+=("$host") fi done < "$SSH_CONFIG" fi # fallback examples if config is empty if [[ ${#servers[@]} -eq 0 ]]; then servers=( "contabo server" ) fi printf '%s\n' "${servers[@]}" } # ------------------------------------------------------------ # fzf path (preferred) # ------------------------------------------------------------ if command -v fzf &>/dev/null; then selected=$(get_servers | fzf \ --prompt=" ssh " \ --pointer=">" \ --marker="*" \ --height=40% \ --layout=reverse \ --border=rounded \ --info=inline \ --color="dark,\ bg:#282828,\ bg+:#3c3836,\ fg:#ebdbb2,\ fg+:#fbf1c7,\ hl:#fabd2f,\ hl+:#fabd2f,\ border:#504945,\ prompt:#83a598,\ pointer:#fe8019,\ marker:#b8bb26,\ spinner:#d3869b,\ header:#a89984,\ info:#a89984" \ --header=" ctrl+c to cancel" \ --preview="echo {} | xargs -I% sh -c 'grep -A10 \"^Host %$\" ~/.ssh/config 2>/dev/null || echo \"no config entry\"'" \ --preview-window="right:40%:wrap" \ 2>/dev/null) || exit 0 [[ -z "$selected" ]] && exit 0 TERM="$TERM_FALLBACK" kitten ssh "$selected" exit 0 fi # ------------------------------------------------------------ # built-in picker (no fzf) # ------------------------------------------------------------ mapfile -t SERVERS < <(get_servers) TOTAL=${#SERVERS[@]} SELECTED=0 QUERY="" FILTERED=("${SERVERS[@]}") draw() { clear printf '%s' "${GB_BG}" printf '\n' printf ' %s%s ssh picker%s\n' "${GB_YELLOW}${BOLD}" "" "${RESET}" printf ' %s%s%s\n\n' "${GB_DIM}" "arrow keys to navigate, enter to connect, esc to quit" "${RESET}" # search bar printf ' %ssearch:%s %s%s%s\n\n' \ "${GB_DIM}" "${RESET}" \ "${GB_FG}${BOLD}" "${QUERY}" "${RESET}" # server list local i=0 for srv in "${FILTERED[@]}"; do if [[ $i -eq $SELECTED ]]; then printf ' %s%s > %s%s\n' "${GB_BG1}${GB_ORANGE}${BOLD}" "" "$srv" "${RESET}" else printf ' %s %s%s\n' "${GB_DIM}" "$srv" "${RESET}" fi (( i++ )) done printf '\n %s%s hosts found • install fzf for preview support%s\n' \ "${GB_DIM}" "${#FILTERED[@]}" "${RESET}" } filter() { FILTERED=() SELECTED=0 for srv in "${SERVERS[@]}"; do if [[ -z "$QUERY" || "${srv,,}" == *"${QUERY,,}"* ]]; then FILTERED+=("$srv") fi done } # hide cursor tput civis trap 'tput cnorm; clear' EXIT while true; do draw # read a single keypress (handles escape sequences) IFS= read -r -s -N1 key # check for escape sequences (arrow keys) if [[ "$key" == $'\e' ]]; then IFS= read -r -s -N1 -t 0.1 k2 || true if [[ "$k2" == "[" ]]; then IFS= read -r -s -N1 -t 0.1 k3 || true case "$k3" in A) # up (( SELECTED > 0 )) && (( SELECTED-- )) ;; B) # down (( SELECTED < ${#FILTERED[@]} - 1 )) && (( SELECTED++ )) ;; esac else # plain escape — quit exit 0 fi continue fi case "$key" in $'\n'|$'\r') [[ ${#FILTERED[@]} -eq 0 ]] && continue tput cnorm clear host="${FILTERED[$SELECTED]}" printf '%sconnecting to %s%s%s...\n\n' \ "${GB_DIM}" "${GB_BLUE}${BOLD}" "$host" "${RESET}" TERM="$TERM_FALLBACK" kitten ssh "$host" exit 0 ;; $'\x7f'|$'\b') # backspace QUERY="${QUERY%?}" filter ;; $'\x03') # ctrl+c exit 0 ;; *) # printable character — append to query if [[ "$key" =~ ^[[:print:]]$ ]]; then QUERY+="$key" filter fi ;; esac done