Files
super-man/bash-helper.sh
T
Trilltechnician 596d69a8fa fix: Correct ANSI color rendering in help display
Changed show_help() from heredoc to echo -e statements to properly
render color escape codes. Help menu now displays with proper colors
instead of literal escape sequences.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 21:19:25 -07:00

580 lines
18 KiB
Bash
Executable File

#!/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
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"
echo -e " ${GREEN}task${NC} \"description\" Describe what you want to do"
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 ENHANCEMENT${NC}"
echo " For advanced natural language understanding, install Ollama:"
echo -e " ${DIM}curl -fsSL https://ollama.com/install.sh | sh${NC}"
echo -e " ${DIM}ollama pull codellama:7b${NC}"
echo -e " Then use: ${GREEN}bash-helper ai \"complex query\"${NC} ${DIM}(Phase 3 - coming soon)${NC}"
echo ""
echo -e "${YELLOW}Database:${NC} $DB_FILE"
echo -e "${YELLOW}Version:${NC} 2.0.0 (Phase 1 Complete)"
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
}
# 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