73c3b62f6c
Major improvements to interactive UI:
1. Enhanced Flag Descriptions:
- Completely rewrote extract_all_flags() function
- Now extracts full descriptions from man pages
- Format: "FLAG — description" (e.g., "-a, --all — do not ignore entries starting with .")
- Handles both OPTIONS and DESCRIPTION sections (GNU coreutils use DESCRIPTION)
- Uses portable AWK to parse multi-line descriptions
- Supports two man page formats:
* Flag and description on same line
* Flag on one line, description on next (indented)
2. Fixed Flush-Right Alignment:
- Corrected spacing calculation for interactive menu
- Removed extra -1 that caused 1-char gap
- Now exactly flush to terminal edge (80 chars = 80 chars)
- Formula: LEFT_WIDTH + FILL_SPACE + SYNTAX_LEN = TERM_WIDTH
3. Color Rendering:
- Already working from previous fix
- colorize_syntax() returns raw \033 codes
- Caller uses printf %b for proper rendering
Testing:
- ✓ All flag descriptions working (ls, grep, tar tested)
- ✓ Right-alignment exactly flush (80/80 chars)
- ✓ Color coordination working ([OPTIONS] yellow, FILE magenta)
User-requested features:
- ✓ "is there a way to have an explanation for the flags appear"
- ✓ "the right justification on the fuzzy search interactive menu is still not working properly"
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
1243 lines
45 KiB
Bash
Executable File
1243 lines
45 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() {
|
|
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
|
|
local colored=$(colorize_syntax "$raw_syntax")
|
|
printf " %b\n" "$colored"
|
|
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
|
|
# Returns raw string with ANSI codes (caller should use echo -e or printf %b)
|
|
colorize_syntax() {
|
|
local syntax="$1"
|
|
local colored_syntax="$syntax"
|
|
|
|
# Color scheme for syntax elements:
|
|
# - Command name (first word): Bright cyan + bold
|
|
# - [OPTIONS], [OPTION], etc.: Yellow
|
|
# - UPPERCASE args (FILE, STRING, SET1, etc.): Magenta
|
|
# - Lowercase/optional args: Green
|
|
# - ... (ellipsis): Dim white
|
|
|
|
# Extract command name (first word) - escape special chars for sed
|
|
local cmd_word=$(echo "$syntax" | awk '{print $1}' | sed 's/[]\/$*.^[]/\\&/g')
|
|
|
|
# Start with command in bright cyan + bold
|
|
colored_syntax=$(echo "$syntax" | sed "s/^$cmd_word/\\\\033[0;36m\\\\033[1m$cmd_word\\\\033[0m/")
|
|
|
|
# Color [OPTIONS], [OPTION], [EXPRESSION], etc. in yellow
|
|
colored_syntax=$(echo "$colored_syntax" | sed -E "s/\[([A-Z][A-Z_-]*)\]/[\\\\033[0;33m\1\\\\033[0m]/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\\\\033[0;35m\2\\\\033[0m\3/g")
|
|
|
|
# Color lowercase optional arguments in green
|
|
colored_syntax=$(echo "$colored_syntax" | sed -E "s/\[([a-z][a-z-]*)\]/[\\\\033[0;32m\1\\\\033[0m]/g")
|
|
|
|
# Color ellipsis (...) in dim white
|
|
colored_syntax=$(echo "$colored_syntax" | sed "s/\.\.\./\\\\033[2m...\\\\033[0m/g")
|
|
|
|
# Return raw string (no echo -e, let caller handle interpretation)
|
|
printf "%s" "$colored_syntax"
|
|
}
|
|
|
|
# Extract flags with descriptions from man pages
|
|
# New enhanced version that includes what each flag does
|
|
extract_all_flags() {
|
|
local cmd_name="$1"
|
|
local flags_output=""
|
|
|
|
if type "$cmd_name" 2>/dev/null | grep -q "shell builtin"; then
|
|
# Shell builtin - use help command (basic format)
|
|
flags_output=$(help "$cmd_name" 2>/dev/null | grep -E '^\s*-')
|
|
elif command -v "$cmd_name" &> /dev/null; then
|
|
# External command - extract from man page OPTIONS or DESCRIPTION section with descriptions
|
|
local man_content=$(man "$cmd_name" 2>/dev/null | col -b)
|
|
|
|
# Try OPTIONS section first, then DESCRIPTION (GNU coreutils use DESCRIPTION)
|
|
# Exclude the section headers themselves to prevent early AWK exit
|
|
local options_section=$(echo "$man_content" | sed -n '/^OPTIONS/,/^[A-Z][A-Z]/{/^OPTIONS/d; /^[A-Z][A-Z]/d; p;}' | head -300)
|
|
|
|
if [ -z "$options_section" ]; then
|
|
# Try DESCRIPTION section instead
|
|
options_section=$(echo "$man_content" | sed -n '/^DESCRIPTION/,/^[A-Z][A-Z]/{/^DESCRIPTION/d; /^[A-Z][A-Z]/d; p;}' | head -300)
|
|
fi
|
|
|
|
if [ -n "$options_section" ]; then
|
|
# Parse flags with their descriptions using portable AWK
|
|
# Man pages have two formats:
|
|
# 1. " --help Output..." (description on same line)
|
|
# 2. " -a, --all\n description..." (description on next line, deeply indented)
|
|
flags_output=$(echo "$options_section" | awk '
|
|
BEGIN { flag=""; desc=""; }
|
|
|
|
/^[[:space:]]*-/ {
|
|
# Print previous flag if exists
|
|
if (flag != "") {
|
|
gsub(/^[[:space:]]+|[[:space:]]+$/, "", desc)
|
|
if (desc != "") {
|
|
printf "%s — %s\n", flag, desc
|
|
} else {
|
|
printf "%s\n", flag
|
|
}
|
|
}
|
|
|
|
# New flag line - extract flag and check for inline description
|
|
line = $0
|
|
gsub(/^[[:space:]]+/, "", line) # Remove leading whitespace
|
|
|
|
# Check if there is content after the flag (separated by multiple spaces/tabs)
|
|
# Flag pattern: starts with -, may have comma and long form
|
|
# Look for 2+ spaces/tabs separating flag from description
|
|
if (match(line, /^[^[:space:]]+([[:space:]]+[^[:space:]]+)?[[:space:]][[:space:]]+/)) {
|
|
# Description on same line
|
|
flag_end = RSTART + RLENGTH - 1
|
|
flag = substr(line, 1, flag_end)
|
|
gsub(/[[:space:]]+$/, "", flag) # Trim trailing spaces
|
|
desc = substr(line, flag_end + 1)
|
|
gsub(/^[[:space:]]+/, "", desc) # Trim leading spaces from description
|
|
} else {
|
|
# No inline description, will be on next line(s)
|
|
flag = line
|
|
desc = ""
|
|
}
|
|
next
|
|
}
|
|
|
|
/^[[:space:]]+[^-]/ {
|
|
# Description continuation line (indented, no leading dash)
|
|
if (flag != "") {
|
|
line = $0
|
|
gsub(/^[[:space:]]+|[[:space:]]+$/, "", line)
|
|
if (desc == "") {
|
|
desc = line
|
|
} else {
|
|
desc = desc " " line
|
|
}
|
|
}
|
|
}
|
|
|
|
/^[A-Z][A-Z]/ {
|
|
# Major section header - end of options
|
|
if (flag != "") {
|
|
gsub(/^[[:space:]]+|[[:space:]]+$/, "", desc)
|
|
if (desc != "") {
|
|
printf "%s — %s\n", flag, desc
|
|
} else {
|
|
printf "%s\n", flag
|
|
}
|
|
}
|
|
exit
|
|
}
|
|
|
|
END {
|
|
# Print last flag
|
|
if (flag != "") {
|
|
gsub(/^[[:space:]]+|[[:space:]]+$/, "", desc)
|
|
if (desc != "") {
|
|
printf "%s — %s\n", flag, desc
|
|
} else {
|
|
printf "%s\n", flag
|
|
}
|
|
}
|
|
}
|
|
' | head -150)
|
|
fi
|
|
|
|
# Fallback: if no OPTIONS section, just get flag lines
|
|
if [ -z "$flags_output" ]; then
|
|
flags_output=$(echo "$man_content" | grep -E '^[[:space:]]*-' | head -100)
|
|
fi
|
|
fi
|
|
|
|
echo "$flags_output"
|
|
}
|
|
|
|
# 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
|
|
# colorize_syntax now returns raw \033 codes that printf %b will interpret
|
|
local colored_syntax=$(colorize_syntax "$cmd_syntax")
|
|
|
|
# Create multi-line header with command info using printf %b for proper escape code interpretation
|
|
# Use actual ANSI codes for command and description, use %b for colored syntax
|
|
local header
|
|
printf -v header "╔═══════════════════════════════════════════════════════════════════════════════╗\n║ Command: \033[0;36m\033[1m%s\033[0m\n║ Syntax: %b\n║ Desc: \033[0;32m%s\033[0m\n╚═══════════════════════════════════════════════════════════════════════════════╝\n🔍 Fuzzy search %d flags | Multi-select: Ctrl-A/D | Esc: Back to menu" \
|
|
"$cmd_name" \
|
|
"$colored_syntax" \
|
|
"$cmd_desc" \
|
|
"$flag_count"
|
|
|
|
# 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 description if too long
|
|
if [ ${#DESC_PART} -gt $DESC_WIDTH ]; then
|
|
DESC_PART="${DESC_PART:0:$((DESC_WIDTH-3))}..."
|
|
fi
|
|
|
|
# Truncate syntax if too long (before colorizing)
|
|
if [ ${#SYNTAX_PART} -gt $SYNTAX_WIDTH ]; then
|
|
SYNTAX_PART="${SYNTAX_PART:0:$((SYNTAX_WIDTH-3))}..."
|
|
fi
|
|
|
|
# Colorize the syntax (command in cyan, [OPTIONS] in yellow, FILE in magenta, etc.)
|
|
# colorize_syntax returns raw string with \033 codes, need to preserve them
|
|
COLORIZED_SYNTAX=$(colorize_syntax "$SYNTAX_PART")
|
|
|
|
# Calculate exact padding needed for right-alignment
|
|
# Use UNCOLORIZED lengths for spacing calculation
|
|
CMD_LEN=${#CMD_NAME}
|
|
DESC_LEN=${#DESC_PART}
|
|
SYNTAX_LEN=${#SYNTAX_PART} # Use original length, not colorized
|
|
|
|
# For tighter right-alignment, calculate remaining space and fill it
|
|
# Terminal width - command - ": " - description = space for syntax
|
|
LEFT_SIDE_WIDTH=$((CMD_LEN + 2 + DESC_LEN))
|
|
REMAINING_WIDTH=$((TERM_WIDTH - LEFT_SIDE_WIDTH)) # Exact flush-right alignment
|
|
|
|
# Create padding to push syntax all the way right
|
|
# Fill space between description and syntax
|
|
FILL_WIDTH=$((REMAINING_WIDTH - SYNTAX_LEN))
|
|
[ $FILL_WIDTH -lt 1 ] && FILL_WIDTH=1
|
|
|
|
SPACES=$(printf '%*s' "$FILL_WIDTH" '')
|
|
|
|
# Format with colors:
|
|
# - Bright cyan for command name
|
|
# - White for description
|
|
# - Colorized syntax (absolutely right-aligned)
|
|
formatted_line="\033[0;36m${CMD_NAME}\033[0m: ${DESC_PART}${SPACES}${COLORIZED_SYNTAX}"
|
|
|
|
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) | Colorized: cmd=[cyan] [OPTIONS]=[yellow] FILE=[magenta]" \
|
|
--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
|