AUR -Ss with fzf

AUR/Pacman has basic search with pipelining and grep; the user can filter the returned hits to narrow the results. However, why do AUR or pacman not have a built-in fzf support with an optional select list to select package hits for installation?

Probably when those tools were first designed, fzf didn’t exist or it wasn’t an important design decision to use fzf to select packages.

Here is the yay-find script vibe-coded by Gemini Pro 3.1
Features:

  1. fzf keyword search
  2. Tabulated selection list for the “hits” packages
  3. The user selects the package and is offered a list of optional packages to select and install.
  4. Options for the script
    4.1) Help
    4.2) Version
    4.3) Install the selected package with -noconfirn
#!/usr/bin/env bash

# Script Metadata
VERSION="1.1.0"
TIMESTAMP="2026-07-21"

# Ensure standard English output for reliable parsing
export LANG=C

# --- Color Definitions ---
C_RESET='\e[0m'
C_CYAN='\e[1;36m'
C_GREEN='\e[1;32m'
C_YELLOW='\e[1;33m'
C_RED='\e[1;31m'
C_MAGENTA='\e[1;35m'
C_BOLD='\e[1m'

# Function to display help information
show_help() {
    echo -e "${C_BOLD}==================================================${C_RESET}"
    echo -e " ${C_CYAN}yay-find${C_RESET} - Interactive Search & Install with Opt-Deps"
    echo -e "${C_BOLD}==================================================${C_RESET}"
    echo -e "${C_BOLD}Usage:${C_RESET} $(basename "$0") [OPTIONS] <keyword...>"
    echo ""
    echo -e "${C_BOLD}Options:${C_RESET}"
    echo -e "  -y, --noconfirm Bypass any and all confirmation messages"
    echo -e "  -h, --help      Show this help message and exit"
    echo -e "  -v, --version   Show version and timestamp information and exit"
    echo ""
    echo -e "${C_BOLD}Description:${C_RESET}"
    echo "  Searches pacman, Chaotic-AUR, and AUR for packages matching the keyword(s)."
    echo "  Displays a tabulated fzf list of matching packages."
    echo "  If packages are selected, a second panel presents their optional"
    echo "  dependencies for selection. Installs selected packages and optionally"
    echo "  selected dependencies as clean dependencies (--asdeps)."
}

# Function to display version information
show_version() {
    echo -e "${C_CYAN}yay-find${C_RESET} version ${C_GREEN}$VERSION${C_RESET}"
    echo "Last updated: $TIMESTAMP"
}

# Check if no arguments were provided
if [ -z "$1" ]; then
    show_help
    exit 1
fi

NOCONFIRM_FLAG=""
KEYWORDS=""

# Argument parsing loop
while [[ "$#" -gt 0 ]]; do
    case $1 in
        -h|--help)
            show_help
            exit 0
            ;;
        -v|--version)
            show_version
            exit 0
            ;;
        -y|--noconfirm)
            NOCONFIRM_FLAG="--noconfirm"
            shift
            ;;
        -*)
            echo -e "${C_RED}Error:${C_RESET} Unknown parameter passed: $1"
            echo "Run '$(basename "$0") --help' for usage."
            exit 1
            ;;
        *)
            # Append keywords
            KEYWORDS="$KEYWORDS $1"
            shift
            ;;
    esac
done

# Trim leading/trailing whitespace from keywords
KEYWORDS=$(echo "$KEYWORDS" | xargs)

if [ -z "$KEYWORDS" ]; then
    echo -e "${C_RED}Error:${C_RESET} Search keyword(s) required."
    exit 1
fi

echo -e "${C_BOLD}Searching for: ${C_CYAN}$KEYWORDS${C_RESET}..."

# Fetch search results from yay
SEARCH_RAW=$(yay -Ss $KEYWORDS 2>/dev/null)

if [ -z "$SEARCH_RAW" ]; then
    echo -e "${C_YELLOW}No packages found matching '$KEYWORDS'.${C_RESET}"
    exit 0
fi

# Parse yay -Ss output into a strictly tabulated format for fzf
SEARCH_FZF=$(echo "$SEARCH_RAW" | awk -v c_mag="\033[1;35m" -v c_grn="\033[1;32m" -v c_yel="\033[1;33m" -v c_rst="\033[0m" '
    /^[^ \t]/ {
        pkg = $1
        ver = $2
        installed = match($0, /\(Installed\)/) ? "[Installed]" : ""
        getline desc
        sub(/^[ \t]+/, "", desc)

        # Formatted with fixed-width columns:
        # 45 chars for Package, 20 chars for Version, 12 chars for Status
        printf "%s%-45s%s %s%-20s%s %s%-12s%s │ %s\n", c_mag, pkg, c_rst, c_grn, ver, c_rst, c_yel, installed, c_rst, desc
    }
')

# --- Panel 1: Package Selection ---
echo -e "\n${C_BOLD}[Package Selection]${C_RESET} Use ${C_BOLD}TAB${C_RESET} to select multiple, ${C_BOLD}ENTER${C_RESET} to confirm."
SELECTED_PKGS_RAW=$(echo -e "$SEARCH_FZF" | fzf -m --ansi --header="Select packages to install (Tab to mark multiple)")

# Extract just the "repo/pkgname" or "pkgname" from the selection
SELECTED_PKGS=$(echo "$SELECTED_PKGS_RAW" | sed $'s/\033\\[[0-9;]*m//g' | awk '{print $1}')

if [ -z "$SELECTED_PKGS" ]; then
    echo -e "${C_YELLOW}No packages selected. Exiting.${C_RESET}"
    exit 0
fi

# Clean package names (remove 'repo/' prefix for installation and dependency checking)
SELECTED_PKGS_CLEAN=$(echo "$SELECTED_PKGS" | awk -F'/' '{print $NF}' | tr '\n' ' ')

echo -e "\n${C_BOLD}Selected Packages:${C_RESET} ${C_CYAN}$SELECTED_PKGS_CLEAN${C_RESET}"
echo -e "${C_BOLD}Gathering optional dependencies...${C_RESET}"

# --- Gather Optional Dependencies ---
ALL_OPT_DEPS_FZF=""

for PKG in $SELECTED_PKGS_CLEAN; do
    INFO_CMD=""
    if yay -Qi "$PKG" &>/dev/null; then
        INFO_CMD="yay -Qi"
    elif yay -Si "$PKG" &>/dev/null; then
        INFO_CMD="yay -Si"
    else
        continue
    fi

    RAW_OPT=$($INFO_CMD "$PKG" 2>/dev/null | awk -F':' '
        /^Optional Deps/ {
            flag=1
            sub(/^Optional Deps[ \t]*:[ \t]*/, "")
            if ($0 != "") print $0
            next
        }
        /^[A-Z]/ { flag=0 }
        flag {
            sub(/^[ \t]+/, "")
            print $0
        }
    ' | grep -v '^$')

    if [ -n "$RAW_OPT" ] && [[ "$RAW_OPT" != *"None"* ]]; then
        while IFS= read -r line; do
            opt_pkg_name=$(echo "$line" | cut -d':' -f1 | xargs)
            desc=$(echo "$line" | cut -d':' -f2- | xargs | sed -e 's/ \[installed\]//i' -e 's/ \[pending\]//i')

            clean_pkg=$(echo "$opt_pkg_name" | sed -E 's/[<>=].*//')

            # Determine text and color separately to prevent ANSI-code padding issues
            if pacman -Qq "$clean_pkg" &>/dev/null; then
                status_text="[Installed]"
                status_color="${C_GREEN}"
            else
                status_text="[Pending]"
                status_color="${C_YELLOW}"
            fi

            # Create perfectly sized columns without colors messing up the character count
            padded_pkg=$(printf "%-35s" "$opt_pkg_name")
            padded_status=$(printf "%-12s" "$status_text")
            padded_parent=$(printf "%-25s" "(for $PKG)")

            # Assemble the final string, injecting colors around the evenly padded blocks
            ALL_OPT_DEPS_FZF+="${C_CYAN}${padded_pkg}${C_RESET} ${status_color}${padded_status}${C_RESET} ${C_MAGENTA}${padded_parent}${C_RESET} │ ${desc}\n"
        done <<< "$RAW_OPT"
    fi
done

# --- Panel 2: Optional Dependency Selection ---
SELECTED_OPT_DEPS_CLEAN=""

if [ -n "$ALL_OPT_DEPS_FZF" ]; then
    echo -e "\n${C_BOLD}[Optional Dependencies]${C_RESET} Use ${C_BOLD}TAB${C_RESET} to select, ${C_BOLD}ENTER${C_RESET} to confirm, or ${C_BOLD}ESC${C_RESET} to skip."

    SELECTED_OPT_RAW=$(echo -e "$ALL_OPT_DEPS_FZF" | fzf -m --ansi --header="Select optional dependencies (Tab to mark, ESC to skip)")
    SELECTED_OPT_DEPS_CLEAN=$(echo "$SELECTED_OPT_RAW" | sed $'s/\033\\[[0-9;]*m//g' | awk '{print $1}' | tr '\n' ' ')
else
    echo -e "  ${C_YELLOW}No optional dependencies found for selected packages.${C_RESET}"
fi

# --- Execution Phase ---
echo -e "\n${C_BOLD}==================================================${C_RESET}"
echo -e "${C_BOLD}Summary of Installations:${C_RESET}"
echo -e "Packages:      ${C_CYAN}${SELECTED_PKGS_CLEAN:-None}${C_RESET}"
echo -e "Opt-Depends:   ${C_CYAN}${SELECTED_OPT_DEPS_CLEAN:-None}${C_RESET}"
echo -e "${C_BOLD}==================================================${C_RESET}\n"

# Install main packages
if [ -n "$SELECTED_PKGS_CLEAN" ]; then
    echo -e "${C_BOLD}Installing main packages...${C_RESET}"
    yay -S $NOCONFIRM_FLAG $SELECTED_PKGS_CLEAN
fi

# Install optional dependencies as dependencies (--asdeps)
if [ -n "$SELECTED_OPT_DEPS_CLEAN" ]; then
    echo -e "\n${C_BOLD}Installing optional dependencies (--asdeps)...${C_RESET}"
    yay -S --asdeps $NOCONFIRM_FLAG $SELECTED_OPT_DEPS_CLEAN
fi

echo -e "\n${C_GREEN}Finished!${C_RESET}"

pacseek exists, by the way, OP.

I tried pacseek and it did not work well with my zshell. So, I asked Gemini to improve on yay-opt (install optional dependencies of the package) and generate a yay-find bash script

What didn’t work exactly?

Packseek does not respond to the keystrokes; latency is too long, I do not know why. Also, I had a few crashes and corrupted output. I was forced to close the terminal (Konsole) since it was not usable.

Pacseek has a nice side panel for the package info; all the info is displayed. Nice touch,
Also, I noticed I cannot use keywords, like bash language, when I am searching for the bash language server, LSP.

I’ve noticed that you have to click the list with a mouse before you can use the keyboard. I’m hoping this is something that gets fixed in the near future. However seems to have been a while since any development has been done on pacseek. (maybe finished in the eyes of the dev :man_shrugging:)

Just ask @moson. He is somewhere around here

Here is a shorter, not vide-coded script from my personal zsh config. Used it for years, never felt the need for more.

# fzf aided package installer;
# originally from https://github.com/IBArbitrary/dotfiles/blob/master/.config/fish/config.fish#L72-L78
function pacs {
    cmd=$(yay -Slq | fzf --prompt 'yay> ' \
        -q "$1" \
        --header 'Install packages. CTRL+(Pacman/Aur/Installed)' \
        --bind 'ctrl-p:change-prompt(pacman> )+reload(pacman -Slq)' \
        --bind 'ctrl-a:change-prompt(yay> )+reload(yay -Slq)' \
        --bind 'ctrl-i:change-prompt(inst> )+reload(yay -Qq)' \
        --multi --height=80% --preview 'yay -Si {1}' \
        --preview-window bottom) #| xargs -ro yay -S
    cmd=${cmd//$'\n'/ } # newline -> space
    if ! [ -z "$cmd" ];
    then print -z yay -S $cmd ;
    fi
}