#!/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() { cat << "EOF" [0;36m ____ __ ____ __ __ / __ )____ ______/ /_ / __ )__ ______/ /___/ /_ __ / __ / __ `/ ___/ __ \ / __ / / / / __ / __ / / / / / /_/ / /_/ (__ ) / / / / /_/ / /_/ / /_/ / /_/ / /_/ / /_____/\__,_/____/_/ /_/ /_____/\__,_/\__,_/\__,_/\__, / /____/ [0;33m Your Intelligent CLI Assistant for Bash [0m EOF } show_help() { show_banner cat << EOF ${BOLD}${YELLOW}QUICK START${NC} ${GREEN}bash-helper ask "find large files"${NC} # Ask a question ${GREEN}bash-helper category files${NC} # Browse by category ${GREEN}bash-helper explain "tar -czf"${NC} # Explain a command ${BOLD}${YELLOW}MODES${NC} ${CYAN}Natural Language Queries:${NC} ${GREEN}ask${NC} "query" Ask in natural language ${GREEN}task${NC} "description" Describe what you want to do ${CYAN}Browse & Explore:${NC} ${GREEN}category${NC} NAME Browse by category (files/text/network/system) ${GREEN}explain${NC} "command" Explain what a command does ${CYAN}Direct Lookup:${NC} ${GREEN}COMMAND [FILTER]${NC} Show flags for command (e.g., ls size) ${GREEN}-i${NC}, ${GREEN}--interactive${NC} Interactive fzf mode ${GREEN}-l${NC}, ${GREEN}--list${NC} List all commands ${BOLD}${YELLOW}CATEGORIES${NC} (20+ tasks) ${CYAN}files${NC} File operations (compress, find, permissions, symlinks) ${CYAN}text${NC} Text processing (search, replace, compare, count) ${CYAN}network${NC} Network ops (download, ports, ping) ${CYAN}system${NC} System monitoring (cpu, memory, processes) ${BOLD}${YELLOW}EXAMPLES${NC} ${DIM}# Natural language${NC} bash-helper ask "compress folder" bash-helper ask "search text in files" bash-helper task "monitor cpu usage" ${DIM}# Browse and learn${NC} bash-helper category network bash-helper explain "find . -name '*.txt'" ${DIM}# Original modes (still work)${NC} bash-helper ls size # Direct flag lookup bash-helper # Interactive fzf mode ${BOLD}${YELLOW}REQUIREMENTS${NC} ${GREEN}✓${NC} jq (for natural language queries) ${GREEN}✓${NC} fzf (for interactive mode only) ${BOLD}${YELLOW}AI ENHANCEMENT${NC} For advanced natural language understanding, install Ollama: ${DIM}curl -fsSL https://ollama.com/install.sh | sh${NC} ${DIM}ollama pull codellama:7b${NC} Then use: ${GREEN}bash-helper ai "complex query"${NC} ${DIM}(Phase 3 - coming soon)${NC} ${YELLOW}Database:${NC} $DB_FILE ${YELLOW}Version:${NC} 2.0.0 (Phase 1 Complete) EOF } # 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 } # 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 "" # 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}" } show_flags() { CMD_NAME=$1 FILTER=$2 echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo -e "${YELLOW}Flags for: ${GREEN}$CMD_NAME${NC}" if [ -n "$FILTER" ]; then echo -e "${YELLOW}Filter: ${GREEN}$FILTER${NC}" fi echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo "" if type "$CMD_NAME" | grep -q "shell builtin"; then if [ -n "$FILTER" ]; then help "$CMD_NAME" | grep -E ' -' | grep -i "$FILTER" else help "$CMD_NAME" | grep -E ' -' fi elif command -v "$CMD_NAME" &> /dev/null; then if [ -n "$FILTER" ]; then man "$CMD_NAME" 2>/dev/null | col -b | grep -E '^\s*-' | grep -i "$FILTER" || \ man "$CMD_NAME" 2>/dev/null | col -b | grep -E ' -' | grep -i "$FILTER" else man "$CMD_NAME" 2>/dev/null | col -b | grep -E '^\s*-' || \ man "$CMD_NAME" 2>/dev/null | col -b | grep -E ' -' fi else echo -e "${RED}Could not find help for: $CMD_NAME${NC}" echo "" echo "Make sure the command is installed or check the spelling." return 1 fi echo "" echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" } list_commands() { echo "Available commands:" echo "" for cmd in "${COMMANDS[@]}"; do CMD_NAME=${cmd%%:*} DESC=${cmd#*:} printf " %-10s - %s\n" "$CMD_NAME" "$DESC" done } get_command_description() { local search_cmd=$1 for cmd in "${COMMANDS[@]}"; do CMD_NAME=${cmd%%:*} DESC=${cmd#*:} if [ "$CMD_NAME" = "$search_cmd" ]; then echo "$DESC" return 0 fi done echo "Command help" } # Command definitions COMMANDS=( "ls:List directory contents" "cd:Change the current directory" "pwd:Print the name of the current working directory" "cp:Copy files and directories" "mv:Move or rename files and directories" "rm:Remove files or directories" "mkdir:Create a new directory" "rmdir:Remove an empty directory" "grep:Print lines matching a pattern" "find:Search for files in a directory hierarchy" "echo:Display a line of text" ) # 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" ;; -*) 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 $? ;; esac fi # Interactive mode with fzf 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 FZF_COMMAND=$(printf "%s\n" "${COMMANDS[@]}" | fzf --height 40% --border --preview 'echo {}' --preview-window=up:1:wrap) if [ -n "$FZF_COMMAND" ]; then CMD=${FZF_COMMAND%%:*} DESC=${FZF_COMMAND#*:} echo "" echo -e "\033[0;34m═══════════════════════════════════════════════════════════\033[0m" echo -e "\033[0;32mCommand:\033[0m $CMD" echo -e "\033[0;32mDescription:\033[0m $DESC" echo -e "\033[0;34m═══════════════════════════════════════════════════════════\033[0m" echo "" read -p "Filter flags (optional, press Enter to skip): " FILTER echo "" show_flags "$CMD" "$FILTER" else echo "No command selected" exit 0 fi # Direct mode elif [ -n "$COMMAND" ]; then # Validate command exists in our list FOUND=false for cmd in "${COMMANDS[@]}"; do CMD_NAME=${cmd%%:*} if [ "$CMD_NAME" = "$COMMAND" ]; then FOUND=true DESC=${cmd#*:} 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 "" fi # Show command info DESC=$(get_command_description "$COMMAND") echo "" echo -e "\033[0;34m═══════════════════════════════════════════════════════════\033[0m" echo -e "\033[0;32mCommand:\033[0m $COMMAND" echo -e "\033[0;32mDescription:\033[0m $DESC" echo -e "\033[0;34m═══════════════════════════════════════════════════════════\033[0m" echo "" show_flags "$COMMAND" "$FILTER" else show_help exit 1 fi