#!/bin/bash # Get script directory for commands-db.json location SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DB_FILE="$SCRIPT_DIR/commands-db.json" # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' MAGENTA='\033[0;35m' BOLD='\033[1m' DIM='\033[2m' NC='\033[0m' # No Color # ASCII Banner show_banner() { echo -e "${CYAN} ____ __ ____ __ __" echo -e " / __ )____ ______/ /_ / __ )__ ______/ /___/ /_ __" echo -e " / __ / __ \`/ ___/ __ \\ / __ / / / / __ / __ / / / /" echo -e " / /_/ / /_/ (__ ) / / / / /_/ / /_/ / /_/ / /_/ / /_/ /" echo -e "/_____/\\__,_/____/_/ /_/ /_____/\\__,_/\\__,_/\\__,_/\\__, /" echo -e " /____/${NC}" echo -e "${YELLOW} Your Intelligent CLI Assistant for Bash${NC}" echo "" } show_help() { show_banner echo -e "${BOLD}${YELLOW}QUICK START${NC}" echo -e " ${GREEN}bash-helper ask \"find large files\"${NC} # Ask a question" echo -e " ${GREEN}bash-helper category files${NC} # Browse by category" echo -e " ${GREEN}bash-helper explain \"tar -czf\"${NC} # Explain a command" echo "" echo -e "${BOLD}${YELLOW}MODES${NC}" echo -e " ${CYAN}Natural Language Queries:${NC}" echo -e " ${GREEN}ask${NC} \"query\" Ask in natural language (database)" echo -e " ${GREEN}task${NC} \"description\" Describe what you want to do (database)" echo -e " ${GREEN}ai${NC} \"complex query\" AI-powered NL2SH model (advanced) 🤖" echo "" echo -e " ${CYAN}Browse & Explore:${NC}" echo -e " ${GREEN}category${NC} NAME Browse by category (files/text/network/system)" echo -e " ${GREEN}explain${NC} \"command\" Explain what a command does" echo "" echo -e " ${CYAN}Direct Lookup:${NC}" echo -e " ${GREEN}COMMAND [FILTER]${NC} Show flags for command (e.g., ls size)" echo -e " ${GREEN}-i${NC}, ${GREEN}--interactive${NC} Interactive fzf mode" echo -e " ${GREEN}-l${NC}, ${GREEN}--list${NC} List all commands" echo "" echo -e "${BOLD}${YELLOW}CATEGORIES${NC} (20+ tasks)" echo -e " ${CYAN}files${NC} File operations (compress, find, permissions, symlinks)" echo -e " ${CYAN}text${NC} Text processing (search, replace, compare, count)" echo -e " ${CYAN}network${NC} Network ops (download, ports, ping)" echo -e " ${CYAN}system${NC} System monitoring (cpu, memory, processes)" echo "" echo -e "${BOLD}${YELLOW}EXAMPLES${NC}" echo -e " ${DIM}# Natural language${NC}" echo " bash-helper ask \"compress folder\"" echo " bash-helper ask \"search text in files\"" echo " bash-helper task \"monitor cpu usage\"" echo "" echo -e " ${DIM}# Browse and learn${NC}" echo " bash-helper category network" echo " bash-helper explain \"find . -name '*.txt'\"" echo "" echo -e " ${DIM}# Original modes (still work)${NC}" echo " bash-helper ls size # Direct flag lookup" echo " bash-helper # Interactive fzf mode" echo "" echo -e "${BOLD}${YELLOW}REQUIREMENTS${NC}" echo -e " ${GREEN}✓${NC} jq (for natural language queries)" echo -e " ${GREEN}✓${NC} fzf (for interactive mode only)" echo "" echo -e "${BOLD}${YELLOW}AI MODE${NC} 🤖" echo -e " ${GREEN}✓ NL2SH model detected!${NC} Use AI for complex queries:" echo -e " ${GREEN}bash-helper ai \"find python files modified last week\"${NC}" echo -e " ${GREEN}bash-helper ai \"count lines in all js files\"${NC}" echo "" echo " Other models available: llama3.2, qwen2.5" echo " To switch models, edit mode_ai() function" echo "" echo -e "${YELLOW}Database:${NC} $DB_FILE" echo -e "${YELLOW}Version:${NC} 2.1.0 (90+ commands, AI-powered)" echo "" } # Check if jq is available check_jq() { if ! command -v jq &> /dev/null; then echo -e "${RED}Error: jq is not installed${NC}" echo "jq is required for database search features." echo "Install it with: sudo apt install jq" echo "" echo "You can still use the original modes:" echo " bash-helper COMMAND [FILTER]" echo " bash-helper --interactive" return 1 fi return 0 } # Check if database exists check_database() { if [ ! -f "$DB_FILE" ]; then echo -e "${RED}Error: Database file not found${NC}" echo "Expected location: $DB_FILE" echo "" echo "You can still use the original modes for direct command lookup." return 1 fi return 0 } # Search database by keywords search_by_keywords() { local query="$1" local max_results="${2:-3}" if ! check_jq || ! check_database; then return 1 fi # Convert query to lowercase for matching local query_lower=$(echo "$query" | tr '[:upper:]' '[:lower:]') # Search and score tasks based on keyword matches jq -r --arg query "$query_lower" --argjson max "$max_results" ' .tasks[] | # Calculate score based on keyword matches . as $task | ($query | split(" ") | map(select(length > 2))) as $query_words | ( [ $query_words[] as $word | ( (.keywords | map(select(. | ascii_downcase | contains($word))) | length) * 2 + (if (.description | ascii_downcase | contains($word)) then 1 else 0 end) + (if (.command | ascii_downcase | contains($word)) then 1 else 0 end) ) ] | add // 0 ) as $score | select($score > 0) | {score: $score, task: .} ' "$DB_FILE" | jq -s --argjson max "$max_results" 'sort_by(-.score) | .[0:$max] | .[] | .task' } # Display a task with full details show_task() { local task_json="$1" local id=$(echo "$task_json" | jq -r '.id') local desc=$(echo "$task_json" | jq -r '.description') local cmd=$(echo "$task_json" | jq -r '.command') local category=$(echo "$task_json" | jq -r '.category') echo "" echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}" echo -e "${GREEN}Task:${NC} $desc" echo -e "${CYAN}Category:${NC} $category" echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}" echo "" echo -e "${YELLOW}Command:${NC}" echo -e " ${MAGENTA}$cmd${NC}" echo "" # Show flags explanation if available local flags=$(echo "$task_json" | jq -r '.flags_explained // empty') if [ -n "$flags" ]; then echo -e "${YELLOW}Flags explained:${NC}" echo "$task_json" | jq -r '.flags_explained | to_entries[] | " \(.key) : \(.value)"' echo "" fi # Show examples local examples=$(echo "$task_json" | jq -r '.examples // empty') if [ -n "$examples" ]; then echo -e "${YELLOW}Examples:${NC}" echo "$task_json" | jq -r '.examples[] | " \(.desc)\n → \(.cmd)\n"' fi # Show related tasks local related=$(echo "$task_json" | jq -r '.related // empty') if [ -n "$related" ] && [ "$related" != "null" ]; then echo -e "${YELLOW}Related tasks:${NC}" echo "$task_json" | jq -r '.related[]' | while read -r rel_id; do local rel_desc=$(jq -r --arg id "$rel_id" '.tasks[] | select(.id == $id) | .description' "$DB_FILE") if [ -n "$rel_desc" ] && [ "$rel_desc" != "null" ]; then echo " • $rel_desc (id: $rel_id)" fi done echo "" fi echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}" } # Show compact task result (for multiple results) show_task_compact() { local task_json="$1" local number="$2" local desc=$(echo "$task_json" | jq -r '.description') local cmd=$(echo "$task_json" | jq -r '.command') echo -e "${CYAN}${number}.${NC} ${GREEN}$desc${NC}" echo -e " ${MAGENTA}$cmd${NC}" echo "" } # Ask mode - search database and show results mode_ask() { local query="$1" if [ -z "$query" ]; then echo -e "${RED}Error: No query provided${NC}" echo "Usage: bash-helper ask \"your question\"" return 1 fi echo -e "${CYAN}Searching for:${NC} \"$query\"" # Search database local results=$(search_by_keywords "$query" 5) if [ -z "$results" ]; then echo "" echo -e "${YELLOW}No matches found for: \"$query\"${NC}" echo "" echo "Try:" echo " • Using different keywords" echo " • Browse by category: bash-helper category files" echo " • List all commands: bash-helper --list" return 1 fi # Count results local count=$(echo "$results" | jq -s 'length') if [ "$count" -eq 1 ]; then # Single result - show full details show_task "$results" else # Multiple results - show compact list echo "" echo -e "${GREEN}Found $count matches:${NC}" echo "" local i=1 echo "$results" | jq -c '.' | while read -r task; do show_task_compact "$task" "$i" i=$((i+1)) done echo -e "${YELLOW}Tip:${NC} Use more specific keywords to narrow results" fi } # Category mode - browse tasks by category mode_category() { local cat_name="$1" if ! check_jq || ! check_database; then return 1 fi if [ -z "$cat_name" ]; then echo -e "${YELLOW}Available categories:${NC}" echo "" jq -r '.categories | to_entries[] | " \(.key) : \(.value.name)"' "$DB_FILE" echo "" echo "Usage: bash-helper category NAME" return 1 fi # Get category info local cat_data=$(jq --arg cat "$cat_name" '.categories[$cat]' "$DB_FILE") if [ "$cat_data" = "null" ]; then echo -e "${RED}Category not found:${NC} $cat_name" echo "" echo "Available categories:" jq -r '.categories | keys[]' "$DB_FILE" | sed 's/^/ /' return 1 fi local cat_title=$(echo "$cat_data" | jq -r '.name') local task_ids=$(echo "$cat_data" | jq -r '.tasks[]') echo "" echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}" echo -e "${CYAN}Category:${NC} ${GREEN}$cat_title${NC}" echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}" echo "" local i=1 for task_id in $task_ids; do local task=$(jq --arg id "$task_id" '.tasks[] | select(.id == $id)' "$DB_FILE") if [ -n "$task" ] && [ "$task" != "null" ]; then show_task_compact "$task" "$i" i=$((i+1)) fi done } # AI mode - use NL2SH model for complex queries mode_ai() { local user_query="$1" if [ -z "$user_query" ]; then echo -e "${RED}Error: No query provided${NC}" echo "Usage: bash-helper ai \"your complex query\"" echo "" echo "Example:" echo " bash-helper ai \"find all python files modified in last week\"" return 1 fi # Check if ollama is available if ! command -v ollama &> /dev/null; then echo -e "${RED}Error: Ollama is not installed${NC}" echo "" echo "Install Ollama:" echo " curl -fsSL https://ollama.com/install.sh | sh" echo "" echo "For now, try: bash-helper ask \"$user_query\"" return 1 fi # Check if NL2SH model is available if ! ollama list | grep -q "westenfelder/NL2SH"; then echo -e "${YELLOW}NL2SH model not found. Available models:${NC}" ollama list echo "" echo "Pull NL2SH model with:" echo " ollama pull westenfelder/NL2SH" return 1 fi echo "" echo -e "${CYAN}🤖 AI Assistant - Using NL2SH Model${NC}" echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}" echo -e "${YELLOW}Query:${NC} $user_query" echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}" echo "" echo -e "${DIM}Generating bash command...${NC}" echo "" # Query the NL2SH model local response=$(ollama run westenfelder/NL2SH "$user_query" 2>/dev/null) if [ -z "$response" ]; then echo -e "${RED}Error: No response from AI model${NC}" echo "" echo "Try a simpler query or use: bash-helper ask \"$user_query\"" return 1 fi # Display the generated command echo -e "${GREEN}Generated Command:${NC}" echo -e " ${MAGENTA}$response${NC}" echo "" # Extract the base command for explanation local base_cmd=$(echo "$response" | awk '{print $1}') # Try to provide explanation if command -v "$base_cmd" &> /dev/null || type "$base_cmd" 2>/dev/null | grep -q "shell builtin"; then echo -e "${YELLOW}Command Info:${NC}" if type "$base_cmd" 2>/dev/null | grep -q "shell builtin"; then help "$base_cmd" 2>/dev/null | head -3 else man "$base_cmd" 2>/dev/null | col -b | grep -A 2 "^NAME" | tail -2 fi echo "" fi echo -e "${DIM}Note: Always review AI-generated commands before running them!${NC}" echo "" echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}" } # Explain mode - parse and explain a command mode_explain() { local cmd_line="$1" if [ -z "$cmd_line" ]; then echo -e "${RED}Error: No command provided${NC}" echo "Usage: bash-helper explain \"command with flags\"" echo "" echo "Example:" echo " bash-helper explain \"tar -czf archive.tar.gz folder/\"" return 1 fi # Extract the base command local base_cmd=$(echo "$cmd_line" | awk '{print $1}') echo "" echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}" echo -e "${YELLOW}Command:${NC} ${MAGENTA}$cmd_line${NC}" echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}" echo "" # Get syntax from man page SYNOPSIS section if command -v "$base_cmd" &> /dev/null || type "$base_cmd" 2>/dev/null | grep -q "shell builtin"; then echo -e "${CYAN}${BOLD}SYNTAX:${NC}" local raw_syntax="" if type "$base_cmd" 2>/dev/null | grep -q "shell builtin"; then # For builtins, get the first line of help raw_syntax=$(help "$base_cmd" 2>/dev/null | head -1 | sed 's/^[[:space:]]*//') [ -z "$raw_syntax" ] && raw_syntax="$base_cmd [options]" else # For regular commands, extract SYNOPSIS section from man page local synopsis=$(man "$base_cmd" 2>/dev/null | col -b | sed -n '/^SYNOPSIS/,/^DESCRIPTION/p' | head -15 | tail -n +2 | head -n -1 | tr '\n' ' ' | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//') if [ -n "$synopsis" ]; then raw_syntax="$synopsis" else # Fallback: try to get usage from command itself local usage=$("$base_cmd" --help 2>&1 | grep -i "usage" | head -1 | sed 's/^[Uu]sage: //') if [ -n "$usage" ]; then raw_syntax="$usage" else raw_syntax="$base_cmd [options]" fi fi fi # Display colorized syntax if [ -n "$raw_syntax" ]; then colorize_syntax "$raw_syntax" | sed 's/^/ /' fi echo "" # Add practical example echo -e "${CYAN}${BOLD}EXAMPLE:${NC}" local example=$(get_practical_example "$base_cmd") if [ -n "$example" ]; then echo -e " ${GREEN}$example${NC}" fi echo "" fi # Try to find in database local found_in_db=false if check_jq && check_database; then local task=$(jq --arg cmd "$base_cmd" '.tasks[] | select(.command | contains($cmd))' "$DB_FILE" 2>/dev/null | jq -s '.[0]' 2>/dev/null) if [ -n "$task" ] && [ "$task" != "null" ] && [ "$task" != "" ]; then local desc=$(echo "$task" | jq -r '.description // empty' 2>/dev/null) if [ -n "$desc" ]; then echo -e "${GREEN}Description:${NC} $desc" echo "" found_in_db=true # Show flags if available local flags=$(echo "$task" | jq -r '.flags_explained // empty' 2>/dev/null) if [ -n "$flags" ] && [ "$flags" != "null" ]; then echo -e "${YELLOW}Flag explanations:${NC}" echo "$task" | jq -r '.flags_explained | to_entries[] | " \(.key) : \(.value)"' 2>/dev/null echo "" fi fi fi fi # Fall back to man page if not found in database if [ "$found_in_db" = false ]; then if command -v "$base_cmd" &> /dev/null || type "$base_cmd" 2>/dev/null | grep -q "shell builtin"; then echo -e "${YELLOW}Getting help from man page...${NC}" echo "" if type "$base_cmd" 2>/dev/null | grep -q "shell builtin"; then help "$base_cmd" 2>/dev/null | head -20 else man "$base_cmd" 2>/dev/null | col -b | head -30 fi else echo -e "${RED}Command not found:${NC} $base_cmd" fi fi echo "" echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}" } # Get quick syntax for a command get_quick_syntax() { local cmd="$1" local syntax="" if type "$cmd" 2>/dev/null | grep -q "shell builtin"; then syntax=$(help "$cmd" 2>/dev/null | head -1 | sed 's/^[[:space:]]*//' | cut -c1-60) else # Try to get first line from man page SYNOPSIS syntax=$(man "$cmd" 2>/dev/null | col -b | sed -n '/^SYNOPSIS/,/^DESCRIPTION/p' | sed -n '2p' | sed 's/^[[:space:]]*//' | cut -c1-60) if [ -z "$syntax" ]; then # Fallback to --help syntax=$("$cmd" --help 2>&1 | grep -i "usage" | head -1 | sed 's/^[Uu]sage: //' | cut -c1-60) fi if [ -z "$syntax" ]; then syntax="$cmd [options]" fi fi echo "$syntax" } # Get practical example for a command get_practical_example() { local cmd="$1" local example="" case "$cmd" in # File Operations ls) example="ls -lah # List all files with human-readable sizes";; cd) example="cd /path/to/directory # Change to a directory";; cp) example="cp file.txt backup.txt # Copy a file";; mv) example="mv old.txt new.txt # Rename or move a file";; rm) example="rm file.txt # Delete a file";; mkdir) example="mkdir new_folder # Create a new directory";; touch) example="touch newfile.txt # Create an empty file";; cat) example="cat file.txt # Display file contents";; chmod) example="chmod 755 script.sh # Make a script executable";; chown) example="chown user:group file # Change file owner";; ln) example="ln -s /path/to/file link # Create a symbolic link";; # Text Processing grep) example="grep 'pattern' file.txt # Search for text in a file";; sed) example="sed 's/old/new/g' file # Replace text in a file";; awk) example="awk '{print \$1}' file # Print first column";; cut) example="cut -d',' -f1 file.csv # Extract first column from CSV";; sort) example="sort file.txt # Sort lines alphabetically";; uniq) example="sort file.txt | uniq # Remove duplicate lines";; wc) example="wc -l file.txt # Count lines in a file";; diff) example="diff file1.txt file2.txt # Compare two files";; # File Search find) example="find . -name '*.txt' # Find all .txt files";; locate) example="locate filename # Quickly find a file";; which) example="which python # Show full path of command";; # Compression tar) example="tar -czf archive.tar.gz folder/ # Compress a folder";; gzip) example="gzip file.txt # Compress a file";; gunzip) example="gunzip file.txt.gz # Decompress a file";; zip) example="zip archive.zip file1.txt file2.txt # Create zip archive";; unzip) example="unzip archive.zip # Extract zip archive";; # Network ping) example="ping google.com # Test network connectivity";; curl) example="curl https://example.com # Download webpage";; wget) example="wget https://example.com/file.zip # Download a file";; ssh) example="ssh user@hostname # Connect to remote server";; scp) example="scp file.txt user@host:/path/ # Copy file to remote server";; # System Info top) example="top # Monitor system processes";; ps) example="ps aux # List all running processes";; df) example="df -h # Show disk space usage";; du) example="du -sh folder/ # Show folder size";; free) example="free -h # Show memory usage";; uname) example="uname -a # Show system information";; # Process Management kill) example="kill 1234 # Terminate process with PID 1234";; pkill) example="pkill firefox # Kill all firefox processes";; # System Control sudo) example="sudo apt update # Run command as root";; systemctl) example="systemctl status nginx # Check service status";; # Package Management apt) example="apt install package # Install a package";; dpkg) example="dpkg -i package.deb # Install .deb package";; # Misc echo) example="echo 'Hello World' # Print text to screen";; date) example="date '+%Y-%m-%d' # Show current date";; history) example="history # Show command history";; man) example="man ls # Show manual for 'ls' command";; *) # Generic fallback example="$cmd --help # Show help for $cmd" ;; esac echo "$example" } # Colorize syntax elements by type for instant recognition colorize_syntax() { local syntax="$1" local colored_syntax="$syntax" # Color scheme for syntax elements: # - Command name (first word): Bright cyan # - [OPTIONS], [OPTION], etc.: Yellow # - UPPERCASE args (FILE, STRING, SET1, etc.): Magenta # - Lowercase/optional args: Green # - ... (ellipsis): Dim white # Extract command name (first word) local cmd_word=$(echo "$syntax" | awk '{print $1}') # Start with command in bright cyan colored_syntax=$(echo "$syntax" | sed "s/^$cmd_word/\\${CYAN}\\${BOLD}$cmd_word\\${NC}/") # Color [OPTIONS], [OPTION], [EXPRESSION], etc. in yellow colored_syntax=$(echo "$colored_syntax" | sed -E "s/\[([A-Z][A-Z_-]*)\]/[\\${YELLOW}\1\\${NC}]/g") # Color UPPERCASE arguments in magenta (FILE, STRING, PATH, SET1, etc.) colored_syntax=$(echo "$colored_syntax" | sed -E "s/([[:space:]])([A-Z][A-Z0-9_-]*[A-Z])([[:space:][:punct:]$])/\1\\${MAGENTA}\2\\${NC}\3/g") # Color lowercase optional arguments in green colored_syntax=$(echo "$colored_syntax" | sed -E "s/\[([a-z][a-z-]*)\]/[\\${GREEN}\1\\${NC}]/g") # Color ellipsis (...) in dim white colored_syntax=$(echo "$colored_syntax" | sed "s/\.\.\./\\${DIM}...\\${NC}/g") echo -e "$colored_syntax" } # Extract all flags from man page or help extract_all_flags() { local cmd_name="$1" local flags_list="" if type "$cmd_name" 2>/dev/null | grep -q "shell builtin"; then # Shell builtin - use help command flags_list=$(help "$cmd_name" 2>/dev/null | grep -E '^\s*-') elif command -v "$cmd_name" &> /dev/null; then # External command - use man page # Get lines that start with optional whitespace and a dash (flags) flags_list=$(man "$cmd_name" 2>/dev/null | col -b | grep -E '^[[:space:]]*-' | head -200) fi echo "$flags_list" } # Interactive flag browser with fuzzy search # Now includes syntax and description in the fzf interface itself browse_flags_fuzzy() { local cmd_name="$1" local cmd_syntax="$2" local cmd_desc="$3" # Extract all flags silently local all_flags=$(extract_all_flags "$cmd_name") if [ -z "$all_flags" ]; then echo "" echo -e "${RED}No flags found for: $cmd_name${NC}" echo "" return 1 fi # Count flags local flag_count=$(echo "$all_flags" | wc -l) # Colorize the syntax for the header local colored_syntax=$(colorize_syntax "$cmd_syntax") # Create multi-line header with command info local header="╔═══════════════════════════════════════════════════════════════════════════════╗ ║ Command: ${CYAN}${BOLD}$cmd_name${NC} ║ Syntax: $colored_syntax ║ Desc: ${GREEN}$cmd_desc${NC} ╚═══════════════════════════════════════════════════════════════════════════════╝ 🔍 Fuzzy search $flag_count flags | Multi-select: Ctrl-A/D | Esc: Back to menu" # Show in fzf with integrated command info echo "$all_flags" | fzf \ --height 95% \ --border=none \ --header "$header" \ --preview 'echo {}' \ --preview-window=down:3:wrap \ --prompt "⚡ Search: " \ --multi \ --bind 'ctrl-a:select-all' \ --bind 'ctrl-d:deselect-all' \ --ansi \ --color=header:bold:cyan return 0 } show_flags() { CMD_NAME=$1 CMD_SYNTAX=$2 CMD_DESC=$3 # Extract syntax if not provided if [ -z "$CMD_SYNTAX" ]; then if type "$CMD_NAME" 2>/dev/null | grep -q "shell builtin"; then # For builtins, get the first line of help CMD_SYNTAX=$(help "$CMD_NAME" 2>/dev/null | head -1 | sed 's/^[[:space:]]*//') [ -z "$CMD_SYNTAX" ] && CMD_SYNTAX="$CMD_NAME [options]" else # For regular commands, extract SYNOPSIS section from man page local synopsis=$(man "$CMD_NAME" 2>/dev/null | col -b | sed -n '/^SYNOPSIS/,/^DESCRIPTION/p' | head -15 | tail -n +2 | head -n -1 | tr '\n' ' ' | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//') if [ -n "$synopsis" ]; then CMD_SYNTAX="$synopsis" else # Fallback: try to get usage from command itself local usage=$("$CMD_NAME" --help 2>&1 | grep -i "usage" | head -1 | sed 's/^[Uu]sage: //') if [ -n "$usage" ]; then CMD_SYNTAX="$usage" else CMD_SYNTAX="$CMD_NAME [options]" fi fi fi fi # Default description if not provided [ -z "$CMD_DESC" ] && CMD_DESC="No description available" # Directly open fuzzy flag browser with integrated command info (no wasted space) browse_flags_fuzzy "$CMD_NAME" "$CMD_SYNTAX" "$CMD_DESC" echo "" echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" } list_commands() { echo "Available commands:" echo "" for cmd in "${COMMANDS[@]}"; do CMD_NAME=${cmd%%:*} REST=${cmd#*:} # Extract description (before pipe) and syntax (after pipe) if [[ "$REST" =~ (.+)\ \|\ (.+) ]]; then DESC="${BASH_REMATCH[1]}" SYNTAX="${BASH_REMATCH[2]}" printf " %-10s - %-40s | %s\n" "$CMD_NAME" "$DESC" "$SYNTAX" else printf " %-10s - %s\n" "$CMD_NAME" "$REST" fi done } get_command_description() { local search_cmd=$1 for cmd in "${COMMANDS[@]}"; do CMD_NAME=${cmd%%:*} REST=${cmd#*:} if [ "$CMD_NAME" = "$search_cmd" ]; then # Extract just the description part (before the pipe) if [[ "$REST" =~ (.+)\ \|\ (.+) ]]; then echo "${BASH_REMATCH[1]}" else echo "$REST" fi return 0 fi done echo "Command help" } # Command definitions for interactive mode (format: "cmd:Description | Syntax") COMMANDS=( # File Operations "ls:List directory contents | ls [OPTION]... [FILE]..." "cd:Change the current directory | cd [DIRECTORY]" "pwd:Print the name of the current working directory | pwd [OPTION]" "cp:Copy files and directories | cp [OPTION]... SOURCE DEST" "mv:Move or rename files and directories | mv [OPTION]... SOURCE DEST" "rm:Remove files or directories | rm [OPTION]... FILE..." "mkdir:Create a new directory | mkdir [OPTION]... DIRECTORY..." "rmdir:Remove an empty directory | rmdir [OPTION]... DIRECTORY..." "touch:Create empty file or update timestamp | touch [OPTION]... FILE..." "cat:Concatenate and display files | cat [OPTION]... [FILE]..." "less:View file contents with pagination | less [OPTION]... [FILE]..." "head:Display first lines of a file | head [OPTION]... [FILE]..." "tail:Display last lines of a file | tail [OPTION]... [FILE]..." "chmod:Change file permissions | chmod [OPTION] MODE FILE..." "chown:Change file owner | chown [OPTION]... OWNER[:GROUP] FILE..." "ln:Create links between files | ln [OPTION]... TARGET LINK_NAME" "file:Determine file type | file [OPTION]... FILE..." "stat:Display file status | stat [OPTION]... FILE..." # Text Processing "grep:Search for patterns in files | grep [OPTION]... PATTERN [FILE]..." "sed:Stream editor for text manipulation | sed [OPTION]... SCRIPT [FILE]..." "awk:Pattern scanning and text processing | awk [OPTION]... PROGRAM [FILE]..." "cut:Remove sections from lines of files | cut [OPTION]... [FILE]..." "sort:Sort lines of text | sort [OPTION]... [FILE]..." "uniq:Report or filter out repeated lines | uniq [OPTION]... [FILE]..." "tr:Translate or delete characters | tr [OPTION]... SET1 [SET2]" "wc:Count lines, words, and characters | wc [OPTION]... [FILE]..." "diff:Compare files line by line | diff [OPTION]... FILE1 FILE2" # File Search "find:Search for files in directory hierarchy | find [PATH] [OPTIONS] [EXPRESSION]" "locate:Find files by name quickly | locate [OPTION]... PATTERN..." "which:Show full path of commands | which [OPTION]... COMMAND..." "whereis:Locate binary, source, and manual pages | whereis [OPTION]... COMMAND..." # Compression "tar:Archive files | tar [OPTION]... [FILE]..." "gzip:Compress files | gzip [OPTION]... [FILE]..." "gunzip:Decompress gzip files | gunzip [OPTION]... [FILE]..." "zip:Package and compress files | zip [OPTION]... ZIPFILE FILES..." "unzip:Extract compressed zip files | unzip [OPTION]... FILE..." # Network "ping:Test network connectivity | ping [OPTION]... HOST" "curl:Transfer data from URLs | curl [OPTION]... URL..." "wget:Download files from the web | wget [OPTION]... URL..." "ssh:Secure shell remote login | ssh [OPTION]... USER@HOST [COMMAND]" "scp:Secure copy files between hosts | scp [OPTION]... SOURCE DEST" "netstat:Network statistics | netstat [OPTION]..." "ss:Socket statistics | ss [OPTION]..." "ip:Show/manipulate routing and devices | ip [OPTION]... OBJECT {COMMAND}" "ifconfig:Configure network interfaces | ifconfig [INTERFACE] [OPTIONS]" # System Info "top:Display system processes | top [OPTION]..." "htop:Interactive process viewer | htop [OPTION]..." "ps:Report process status | ps [OPTION]..." "df:Show disk space usage | df [OPTION]... [FILE]..." "du:Estimate file space usage | du [OPTION]... [FILE]..." "free:Display memory usage | free [OPTION]..." "uname:Print system information | uname [OPTION]..." "uptime:Show how long system has been running | uptime [OPTION]..." # Process Management "kill:Send signal to a process | kill [OPTION]... PID..." "pkill:Kill processes by name | pkill [OPTION]... PATTERN" "killall:Kill processes by name | killall [OPTION]... NAME..." "bg:Put job in background | bg [JOB_SPEC]" "fg:Bring job to foreground | fg [JOB_SPEC]" "jobs:List active jobs | jobs [OPTION]..." # System Control "sudo:Execute command as another user | sudo [OPTION]... COMMAND" "systemctl:Control systemd services | systemctl [OPTION]... COMMAND" "service:Control system services | service COMMAND [OPTIONS]" "reboot:Reboot the system | reboot [OPTION]..." "shutdown:Shut down the system | shutdown [OPTION]... [TIME]" # Package Management "apt:Package manager (Debian/Ubuntu) | apt [OPTION]... COMMAND" "apt-get:APT package handling utility | apt-get [OPTION]... COMMAND" "dpkg:Debian package manager | dpkg [OPTION]... ACTION" "snap:Snap package manager | snap [OPTION]... COMMAND" # User Management "useradd:Create new user | useradd [OPTION]... LOGIN" "userdel:Delete user account | userdel [OPTION]... LOGIN" "passwd:Change user password | passwd [OPTION]... [USER]" "su:Switch user | su [OPTION]... [USER]" "whoami:Print current username | whoami" "w:Show who is logged in | w [OPTION]... [USER]" "who:Show who is logged in | who [OPTION]..." # Misc Utilities "echo:Display a line of text | echo [OPTION]... [STRING]..." "date:Display or set system date and time | date [OPTION]... [+FORMAT]" "cal:Display calendar | cal [OPTION]... [[MONTH] YEAR]" "history:Show command history | history [n]" "alias:Create command aliases | alias [NAME[=VALUE]...]" "export:Set environment variables | export [NAME[=VALUE]...]" "env:Display environment variables | env [OPTION]... [NAME=VALUE]... [COMMAND]" "man:Display manual pages | man [OPTION]... [SECTION] PAGE" "watch:Execute command periodically | watch [OPTION]... COMMAND" ) # Parse arguments MODE="" QUERY="" INTERACTIVE=false COMMAND="" FILTER="" if [ $# -eq 0 ]; then INTERACTIVE=true else case "$1" in -h|--help) show_help exit 0 ;; -l|--list) list_commands exit 0 ;; -i|--interactive) INTERACTIVE=true ;; ask) MODE="ask" QUERY="$2" ;; task) MODE="task" QUERY="$2" ;; explain) MODE="explain" QUERY="$2" ;; category) MODE="category" QUERY="$2" ;; ai) MODE="ai" QUERY="$2" ;; -*) echo "Unknown option: $1" echo "Use --help for usage information" exit 1 ;; *) COMMAND=$1 FILTER=$2 ;; esac fi # Handle new modes if [ -n "$MODE" ]; then case "$MODE" in ask|task) mode_ask "$QUERY" exit $? ;; explain) mode_explain "$QUERY" exit $? ;; category) mode_category "$QUERY" exit $? ;; ai) mode_ai "$QUERY" exit $? ;; esac fi # Interactive mode with fzf (loopable) if [ "$INTERACTIVE" = true ]; then # Check if fzf is available if ! command -v fzf &> /dev/null; then echo "Error: fzf is not installed" echo "Install it with: sudo apt install fzf" echo "" echo "Or use direct mode: bash-helper COMMAND [FILTER]" exit 1 fi # Main interactive loop while true; do # Format commands for display: description on left, syntax on right # Get terminal width for proper alignment TERM_WIDTH=$(tput cols 2>/dev/null || echo 120) # Calculate widths more conservatively to prevent clipping # Reserve space for: command (15) + ": " (2) + description (40%) + spaces (5) + syntax (remaining) MAX_CMD_WIDTH=15 DESC_WIDTH=$((TERM_WIDTH * 40 / 100)) SYNTAX_WIDTH=$((TERM_WIDTH - MAX_CMD_WIDTH - DESC_WIDTH - 10)) # Ensure minimum widths and cap maximums [ $DESC_WIDTH -lt 20 ] && DESC_WIDTH=20 [ $SYNTAX_WIDTH -lt 30 ] && SYNTAX_WIDTH=30 [ $DESC_WIDTH -gt 50 ] && DESC_WIDTH=50 [ $SYNTAX_WIDTH -gt 60 ] && SYNTAX_WIDTH=60 # Format each command with colors and right-aligned syntax FORMATTED_COMMANDS=() for cmd in "${COMMANDS[@]}"; do CMD_NAME=${cmd%%:*} REST=${cmd#*:} # Extract description and syntax if [[ "$REST" =~ (.+)\ \|\ (.+) ]]; then DESC_PART="${BASH_REMATCH[1]}" SYNTAX_PART="${BASH_REMATCH[2]}" else DESC_PART="$REST" SYNTAX_PART="$CMD_NAME [options]" fi # Truncate if too long if [ ${#DESC_PART} -gt $DESC_WIDTH ]; then DESC_PART="${DESC_PART:0:$((DESC_WIDTH-3))}..." fi if [ ${#SYNTAX_PART} -gt $SYNTAX_WIDTH ]; then SYNTAX_PART="${SYNTAX_PART:0:$((SYNTAX_WIDTH-3))}..." fi # Calculate exact padding needed for right-alignment # Account for visual width only (not ANSI codes) CMD_LEN=${#CMD_NAME} DESC_LEN=${#DESC_PART} SYNTAX_LEN=${#SYNTAX_PART} # Calculate spaces needed: terminal width - all visible characters - separator chars # Visible: cmd + ": " + desc + syntax = CMD_LEN + 2 + DESC_LEN + SYNTAX_LEN VISIBLE_WIDTH=$((CMD_LEN + 2 + DESC_LEN + SYNTAX_LEN)) NEEDED_SPACES=$((TERM_WIDTH - VISIBLE_WIDTH - 8)) # Extra padding for safety # Minimum spacing [ $NEEDED_SPACES -lt 3 ] && NEEDED_SPACES=3 # Maximum spacing to prevent excessive gaps [ $NEEDED_SPACES -gt 20 ] && NEEDED_SPACES=20 # Create spacing string SPACES=$(printf '%*s' "$NEEDED_SPACES" '') # Format with colors: # - Bright cyan for command name # - White for description # - Dim cyan for syntax (right-aligned) formatted_line="\033[0;36m${CMD_NAME}\033[0m: ${DESC_PART}${SPACES}\033[2;36m${SYNTAX_PART}\033[0m" FORMATTED_COMMANDS+=("$formatted_line") done # Show in fzf with formatted display SELECTED=$(printf "%b\n" "${FORMATTED_COMMANDS[@]}" | fzf \ --height 60% \ --border \ --header "Select a command (Esc to exit) | cyan: command | description | dim: syntax" \ --prompt "🔍 Search: " \ --preview-window=hidden \ --ansi) if [ -z "$SELECTED" ]; then # User pressed Esc or cancelled echo "" echo -e "${YELLOW}Exiting Bash Buddy. Happy coding! 👋${NC}" exit 0 fi # Extract command name from selection (strip color codes) CMD=$(echo "$SELECTED" | sed 's/\x1b\[[0-9;]*m//g' | awk -F: '{print $1}' | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//') # Find the original command entry to get full details for cmd in "${COMMANDS[@]}"; do CMD_NAME=${cmd%%:*} if [ "$CMD_NAME" = "$CMD" ]; then REST=${cmd#*:} # Extract description and syntax if [[ "$REST" =~ (.+)\ \|\ (.+) ]]; then DESC="${BASH_REMATCH[1]}" SYNTAX="${BASH_REMATCH[2]}" else DESC="$REST" SYNTAX="$CMD [options]" fi break fi done # Directly show fuzzy flag browser with integrated command info (no wasted space) clear show_flags "$CMD" "$SYNTAX" "$DESC" # After viewing flags, ask if user wants to search another command echo "" echo -e "${YELLOW}───────────────────────────────────────────────────────────${NC}" echo -e "${CYAN}Press Enter to search another command, or Ctrl+C to exit${NC}" echo -e "${YELLOW}───────────────────────────────────────────────────────────${NC}" read -r # Clear screen for better UX clear done # Direct mode elif [ -n "$COMMAND" ]; then # Validate command exists in our list FOUND=false DESC_PART="" SYNTAX_PART="" for cmd in "${COMMANDS[@]}"; do CMD_NAME=${cmd%%:*} if [ "$CMD_NAME" = "$COMMAND" ]; then FOUND=true REST=${cmd#*:} # Extract description and syntax if [[ "$REST" =~ (.+)\ \|\ (.+) ]]; then DESC_PART="${BASH_REMATCH[1]}" SYNTAX_PART="${BASH_REMATCH[2]}" else DESC_PART="$REST" SYNTAX_PART="$COMMAND [options]" fi break fi done if [ "$FOUND" = false ]; then echo "Warning: '$COMMAND' is not in the predefined list" echo "Available commands:" list_commands echo "" echo "Attempting to show flags anyway..." echo "" DESC_PART=$(get_command_description "$COMMAND") SYNTAX_PART="$COMMAND [options]" fi # Show command info in enhanced format echo "" echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}" echo -e "${YELLOW}Command:${NC} ${MAGENTA}$COMMAND${NC}" echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}" echo "" echo -e "${CYAN}${BOLD}SYNTAX:${NC}" echo -e " ${GREEN}$SYNTAX_PART${NC}" echo "" echo -e "${GREEN}Description:${NC} $DESC_PART" echo "" echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}" echo "" show_flags "$COMMAND" "$FILTER" else show_help exit 1 fi