#!/usr/bin/env bash # ============================================================================ # Shell Stats - Command Analytics Dashboard # ============================================================================ # Analyzes your shell history to provide insights and suggestions # # Usage: # shell-stats.sh # Show dashboard # shell-stats.sh --top [n] # Top N commands # shell-stats.sh --suggest # Suggest aliases # shell-stats.sh --hours # Commands by hour # shell-stats.sh --dirs # Most used directories # shell-stats.sh --export # Export stats as JSON # ============================================================================ set -e # ============================================================================ # Configuration # ============================================================================ HISTFILE="${HISTFILE:-$HOME/.zsh_history}" BASH_HISTFILE="$HOME/.bash_history" STATS_CACHE="$HOME/.cache/shell-stats" STATS_FILE="$STATS_CACHE/stats.json" mkdir -p "$STATS_CACHE" # ============================================================================ # Colors # ============================================================================ RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' MAGENTA='\033[0;35m' DIM='\033[2m' BOLD='\033[1m' NC='\033[0m' # ============================================================================ # History Parsing # ============================================================================ get_history_file() { if [[ -f "$HISTFILE" ]]; then echo "$HISTFILE" elif [[ -f "$BASH_HISTFILE" ]]; then echo "$BASH_HISTFILE" else echo "" fi } parse_zsh_history() { # Zsh extended history format: : timestamp:0;command local histfile=$(get_history_file) [[ -z "$histfile" ]] && return if [[ "$histfile" == *"zsh"* ]]; then # Zsh format cat "$histfile" 2>/dev/null | sed 's/^: [0-9]*:[0-9]*;//' | grep -v '^$' else # Bash format cat "$histfile" 2>/dev/null | grep -v '^#' | grep -v '^$' fi } get_command_count() { parse_zsh_history | wc -l | tr -d ' ' } get_unique_commands() { parse_zsh_history | awk '{print $1}' | sort -u | wc -l | tr -d ' ' } # ============================================================================ # Analysis Functions # ============================================================================ top_commands() { local count="${1:-15}" parse_zsh_history | \ awk '{print $1}' | \ sort | \ uniq -c | \ sort -rn | \ head -n "$count" } top_full_commands() { local count="${1:-10}" parse_zsh_history | \ sort | \ uniq -c | \ sort -rn | \ head -n "$count" } commands_by_hour() { local histfile=$(get_history_file) [[ -z "$histfile" ]] && return # Try to extract timestamps from zsh history if [[ "$histfile" == *"zsh"* ]]; then grep '^:' "$histfile" 2>/dev/null | \ sed 's/^: \([0-9]*\):.*/\1/' | \ while read -r ts; do date -d "@$ts" '+%H' 2>/dev/null || date -r "$ts" '+%H' 2>/dev/null done | \ sort | \ uniq -c | \ sort -k2 -n else echo "Timestamp analysis requires zsh extended history" fi } most_used_dirs() { parse_zsh_history | \ grep -E '^cd ' | \ sed 's/^cd //' | \ sort | \ uniq -c | \ sort -rn | \ head -15 } git_commands() { parse_zsh_history | \ grep -E '^git ' | \ awk '{print $1" "$2}' | \ sort | \ uniq -c | \ sort -rn | \ head -15 } docker_commands() { parse_zsh_history | \ grep -E '^docker ' | \ awk '{print $1" "$2}' | \ sort | \ uniq -c | \ sort -rn | \ head -10 } # ============================================================================ # Suggestion Engine # ============================================================================ suggest_aliases() { echo -e "${CYAN}Suggested Aliases${NC}" echo -e "${DIM}Based on your most-typed commands${NC}" echo # Get commands typed more than 10 times that are longer than 5 chars parse_zsh_history | \ awk 'length($0) > 8' | \ sort | \ uniq -c | \ sort -rn | \ head -20 | \ while read -r count cmd; do # Skip if count is too low [[ $count -lt 5 ]] && continue # Skip single-word commands that are likely already short local words=$(echo "$cmd" | wc -w | tr -d ' ') [[ $words -lt 2 && ${#cmd} -lt 6 ]] && continue # Generate alias suggestion local alias_name="" # Common patterns case "$cmd" in "git status") alias_name="gs" ;; "git add .") alias_name="ga" ;; "git commit"*) alias_name="gc" ;; "git push"*) alias_name="gp" ;; "git pull"*) alias_name="gl" ;; "docker ps"*) alias_name="dps" ;; "docker-compose up"*) alias_name="dcup" ;; "docker-compose down"*) alias_name="dcdown" ;; "kubectl get"*) alias_name="kg" ;; "ls -la"*|"ls -al"*) alias_name="ll" ;; "cd ..") alias_name=".." ;; *) # Generate from first letters alias_name=$(echo "$cmd" | awk '{for(i=1;i<=NF && i<=3;i++) printf substr($i,1,1)}') ;; esac # Check if alias already exists if alias "$alias_name" &>/dev/null 2>&1; then echo -e " ${GREEN}✓${NC} ${DIM}$alias_name${NC} already defined (used $count times)" else local saved_chars=$(( (${#cmd} - ${#alias_name}) * count )) echo -e " ${YELLOW}→${NC} alias ${CYAN}$alias_name${NC}='$cmd'" echo -e " ${DIM}Used $count times, would save ~$saved_chars keystrokes${NC}" fi done } # ============================================================================ # Dashboard # ============================================================================ draw_bar() { local value=$1 local max=$2 local width=${3:-30} local filled=$((value * width / max)) local empty=$((width - filled)) printf "${GREEN}" printf "%${filled}s" | tr ' ' '█' printf "${DIM}" printf "%${empty}s" | tr ' ' '░' printf "${NC}" } show_dashboard() { clear local total=$(get_command_count) local unique=$(get_unique_commands) echo -e "${BLUE}╔═══════════════════════════════════════════════════════════════════╗${NC}" echo -e "${BLUE}║${NC} ${BOLD}Shell Analytics Dashboard${NC} ${BLUE}║${NC}" echo -e "${BLUE}╚═══════════════════════════════════════════════════════════════════╝${NC}" echo # Summary stats echo -e "${CYAN}Overview${NC}" echo -e " Total commands: ${GREEN}${total}${NC}" echo -e " Unique commands: ${GREEN}${unique}${NC}" echo -e " History file: ${DIM}$(get_history_file)${NC}" echo # Top commands echo -e "${CYAN}Top Commands${NC}" echo local max_count=$(top_commands 1 | awk '{print $1}') top_commands 10 | while read -r count cmd; do printf " %-12s %5d " "$cmd" "$count" draw_bar "$count" "$max_count" 25 echo done echo # Git breakdown (if git is in top commands) if parse_zsh_history | grep -q '^git '; then echo -e "${CYAN}Git Commands${NC}" echo git_commands | head -5 | while read -r count cmd; do printf " %-20s %5d\n" "$cmd" "$count" done echo fi # Directory usage echo -e "${CYAN}Most Visited Directories${NC}" echo most_used_dirs | head -5 | while read -r count dir; do printf " %-35s %5d\n" "$dir" "$count" done echo # Quick suggestions echo -e "${CYAN}💡 Quick Tips${NC}" echo # Find most-typed long command local long_cmd=$(parse_zsh_history | awk 'length($0) > 15' | sort | uniq -c | sort -rn | head -1) local long_count=$(echo "$long_cmd" | awk '{print $1}') local long_text=$(echo "$long_cmd" | sed 's/^[[:space:]]*[0-9]*[[:space:]]*//') if [[ $long_count -gt 10 ]]; then echo -e " ${YELLOW}→${NC} You've typed '${CYAN}$long_text${NC}' $long_count times" echo -e " Consider creating an alias for it!" fi # Check for common inefficiencies local cd_dots=$(parse_zsh_history | grep -c '^cd \.\.' || echo 0) if [[ $cd_dots -gt 50 ]]; then echo -e " ${YELLOW}→${NC} You use 'cd ..' a lot ($cd_dots times)" echo -e " Tip: alias ..='cd ..' and ...='cd ../..'" fi echo echo -e "${DIM}Run 'shell-stats.sh --suggest' for detailed alias suggestions${NC}" } # ============================================================================ # Export # ============================================================================ export_stats() { local output="${1:-$STATS_FILE}" echo "{" echo " \"generated\": \"$(date -Iseconds)\"," echo " \"total_commands\": $(get_command_count)," echo " \"unique_commands\": $(get_unique_commands)," echo " \"top_commands\": [" top_commands 20 | awk 'BEGIN{first=1} { if (!first) printf ",\n" printf " {\"command\": \"%s\", \"count\": %d}", $2, $1 first=0 }' echo echo " ]" echo "}" } # ============================================================================ # Activity Heatmap # ============================================================================ show_heatmap() { echo -e "${CYAN}Activity by Hour${NC}" echo # Create array for 24 hours declare -a hours for i in {0..23}; do hours[$i]=0 done # Count commands per hour local histfile=$(get_history_file) if [[ "$histfile" == *"zsh"* && -f "$histfile" ]]; then while IFS= read -r line; do if [[ "$line" =~ ^:\ ([0-9]+): ]]; then local ts="${BASH_REMATCH[1]}" local hour=$(date -d "@$ts" '+%H' 2>/dev/null || date -r "$ts" '+%H' 2>/dev/null) hour=${hour#0} # Remove leading zero ((hours[$hour]++)) || true fi done < "$histfile" # Find max for scaling local max=1 for count in "${hours[@]}"; do [[ $count -gt $max ]] && max=$count done # Draw heatmap echo -n " " for i in {0..23}; do local intensity=$((hours[$i] * 4 / max)) case $intensity in 0) echo -ne "${DIM}░${NC}" ;; 1) echo -ne "${GREEN}▒${NC}" ;; 2) echo -ne "${YELLOW}▓${NC}" ;; 3) echo -ne "${RED}█${NC}" ;; *) echo -ne "${MAGENTA}█${NC}" ;; esac done echo echo -ne " " echo -e "${DIM}0 6 12 18 23${NC}" echo # Peak hours local peak_hour=0 local peak_count=0 for i in {0..23}; do if [[ ${hours[$i]} -gt $peak_count ]]; then peak_count=${hours[$i]} peak_hour=$i fi done echo -e " Peak activity: ${GREEN}${peak_hour}:00${NC} ($peak_count commands)" else echo -e " ${YELLOW}⚠${NC} Heatmap requires zsh with extended history" fi } # ============================================================================ # Main # ============================================================================ show_help() { echo "Usage: dotfiles-stats.sh [COMMAND] [OPTIONS]" echo echo "Commands:" echo " (none) Show dashboard" echo " --top [n] Top N commands (default: 15)" echo " --full [n] Top N full command lines" echo " --suggest Suggest aliases based on usage" echo " --hours Show activity by hour" echo " --heatmap Show activity heatmap" echo " --dirs Most visited directories" echo " --git Git command breakdown" echo " --docker Docker command breakdown" echo " --export Export stats as JSON" echo " --help Show this help" echo echo "Aliases:" echo " dfstats, stats Show dashboard" echo " tophist Top commands" echo " suggest Suggest aliases" echo } main() { local histfile=$(get_history_file) if [[ -z "$histfile" || ! -f "$histfile" ]]; then echo -e "${RED}✗${NC} No history file found" echo " Checked: $HISTFILE" echo " Checked: $BASH_HISTFILE" exit 1 fi case "${1:-}" in --top|-t) echo -e "${CYAN}Top Commands${NC}" echo top_commands "${2:-15}" | while read -r count cmd; do printf " %5d %s\n" "$count" "$cmd" done ;; --full|-f) echo -e "${CYAN}Top Full Commands${NC}" echo top_full_commands "${2:-10}" | while read -r count cmd; do printf " %5d %s\n" "$count" "$cmd" done ;; --suggest|-s) suggest_aliases ;; --hours) commands_by_hour ;; --heatmap|-m) show_heatmap ;; --dirs|-d) echo -e "${CYAN}Most Visited Directories${NC}" echo most_used_dirs | while read -r count dir; do printf " %5d %s\n" "$count" "$dir" done ;; --git|-g) echo -e "${CYAN}Git Commands${NC}" echo git_commands | while read -r count cmd; do printf " %5d %s\n" "$count" "$cmd" done ;; --docker) echo -e "${CYAN}Docker Commands${NC}" echo docker_commands | while read -r count cmd; do printf " %5d %s\n" "$count" "$cmd" done ;; --export|-e) export_stats "${2:-}" ;; --help|-h) show_help ;; "") show_dashboard ;; *) echo "Unknown command: $1" show_help exit 1 ;; esac } main "$@"