#!/usr/bin/env bash # ============================================================================ # Enhanced Shell Analytics # ============================================================================ # Advanced command history analysis with time-based patterns, project grouping, # and actionable insights. # # Usage: # dotfiles-analytics.sh # Dashboard # dotfiles-analytics.sh hourly # Commands by hour # dotfiles-analytics.sh weekly # Usage by day of week # dotfiles-analytics.sh projects # Group by directory # dotfiles-analytics.sh trends # Usage trends over time # dotfiles-analytics.sh complexity # Command complexity analysis # dotfiles-analytics.sh tools # Tool usage breakdown # dotfiles-analytics.sh suggestions # Alias suggestions # ============================================================================ # Don't exit on error - we handle errors ourselves set +e # Source bootstrap source "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null || { DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' DF_RED=$'\033[0;31m' DF_CYAN=$'\033[0;36m' DF_BLUE=$'\033[0;34m' DF_MAGENTA=$'\033[0;35m' DF_NC=$'\033[0m' DF_DIM=$'\033[2m' df_print_header() { echo "=== $1 ==="; } df_print_section() { echo -e "${DF_CYAN}$1:${DF_NC}"; } df_print_indent() { echo " $1"; } } # ============================================================================ # Configuration # ============================================================================ HISTORY_FILE="${HISTFILE:-$HOME/.zsh_history}" BASH_HISTORY_FILE="$HOME/.bash_history" # ============================================================================ # History Parsing # ============================================================================ # Get zsh history with timestamps get_zsh_history_with_time() { if [[ -f "$HISTORY_FILE" ]]; then # Zsh extended history format: : timestamp:0;command grep "^:" "$HISTORY_FILE" 2>/dev/null | while read -r line; do local timestamp=$(echo "$line" | cut -d':' -f2) local cmd=$(echo "$line" | cut -d';' -f2-) echo "${timestamp}|${cmd}" done fi } # Get plain history (command only) get_history() { if [[ -f "$HISTORY_FILE" ]]; then grep "^:" "$HISTORY_FILE" 2>/dev/null | cut -d';' -f2- || cat "$HISTORY_FILE" 2>/dev/null elif [[ -f "$BASH_HISTORY_FILE" ]]; then cat "$BASH_HISTORY_FILE" 2>/dev/null fi } # Safe count function - handles grep errors gracefully safe_count() { local pattern="$1" local result result=$(get_history | awk '{print $1}' | grep -c "^${pattern}$" 2>/dev/null) || result=0 # Ensure we return a valid number if [[ "$result" =~ ^[0-9]+$ ]]; then echo "$result" else echo "0" fi } # ============================================================================ # Analytics Functions # ============================================================================ # Commands by hour of day show_hourly() { df_print_section "Command Usage by Hour of Day" echo "" get_zsh_history_with_time | while IFS='|' read -r timestamp cmd; do if [[ -n "$timestamp" && "$timestamp" =~ ^[0-9]+$ ]]; then local hour hour=$(date -d "@$timestamp" '+%H' 2>/dev/null || date -r "$timestamp" '+%H' 2>/dev/null) if [[ -n "$hour" ]]; then echo "$hour" fi fi done | sort | uniq -c | sort -k2 -n | while read -r count hour; do local bar="" local bar_len=$((count / 50 + 1)) for ((i=0; i/dev/null || date -r "$timestamp" '+%a' 2>/dev/null) if [[ -n "$dow" ]]; then echo "$dow" fi fi done | sort | uniq -c | while read -r count day; do local bar="" local bar_len=$((count / 100 + 1)) for ((i=0; i/dev/null | \ sed 's|/[^/]*$||' | sort | uniq -c | sort -rn | head -10 | \ while read -r count dir; do local short_dir="$dir" if [[ ${#dir} -gt 40 ]]; then short_dir="...${dir: -37}" fi printf " %5d ${DF_CYAN}%s${DF_NC}\n" "$count" "$short_dir" done echo "" } # Usage trends over time (last 30 days) show_trends() { df_print_section "Usage Trends (Last 30 Days)" echo "" local thirty_days_ago=$(($(date +%s) - 30*24*60*60)) get_zsh_history_with_time | while IFS='|' read -r timestamp cmd; do if [[ -n "$timestamp" && "$timestamp" =~ ^[0-9]+$ && "$timestamp" -ge "$thirty_days_ago" ]]; then local date_str date_str=$(date -d "@$timestamp" '+%Y-%m-%d' 2>/dev/null || date -r "$timestamp" '+%Y-%m-%d' 2>/dev/null) if [[ -n "$date_str" ]]; then echo "$date_str" fi fi done | sort | uniq -c | tail -30 | while read -r count date; do local bar="" local bar_len=$((count / 20 + 1)) for ((i=0; i"* || "$cmd" == *"<"* ]]; then ((redirected++)) || true elif [[ "$cmd" == *'$('* || "$cmd" == *'`'* ]]; then ((subshell++)) || true else ((simple++)) || true fi done < <(get_history) if [[ $total -gt 0 ]]; then echo -e " Simple commands: ${DF_GREEN}$simple${DF_NC} ($((simple * 100 / total))%)" echo -e " Piped commands: ${DF_YELLOW}$piped${DF_NC} ($((piped * 100 / total))%)" echo -e " With redirection: ${DF_CYAN}$redirected${DF_NC} ($((redirected * 100 / total))%)" echo -e " With subshells: ${DF_MAGENTA}$subshell${DF_NC} ($((subshell * 100 / total))%)" else echo " No history data available" fi echo "" # Most complex commands (by pipe count) df_print_indent "Most Complex Pipelines:" echo "" get_history | awk -F'|' 'NF > 3 {print NF-1, $0}' | sort -rn | head -5 | \ while read -r pipes cmd; do local short_cmd="${cmd:0:60}" [[ ${#cmd} -gt 60 ]] && short_cmd="${short_cmd}..." echo -e " ${DF_MAGENTA}$pipes pipes:${DF_NC} $short_cmd" done } # Tool usage breakdown show_tools() { df_print_section "Tool Usage Breakdown" echo "" local categories=( "Git:git g" "Docker:docker docker-compose d dc" "Package:pacman paru yay npm pip cargo" "Editor:vim nvim vi nano code" "Navigation:cd ls ll la cat less" "System:sudo systemctl journalctl" "Network:curl wget ssh scp" ) # Cache history to avoid repeated reads local history_cache history_cache=$(get_history | awk '{print $1}') for category in "${categories[@]}"; do local name="${category%%:*}" local tools="${category#*:}" local total=0 for tool in $tools; do local count count=$(echo "$history_cache" | grep -c "^${tool}$" 2>/dev/null) || count=0 # Validate count is a number if [[ "$count" =~ ^[0-9]+$ ]]; then total=$((total + count)) fi done if [[ $total -gt 0 ]]; then printf " %-12s ${DF_GREEN}%6d${DF_NC}\n" "$name:" "$total" fi done } # Suggestions based on usage show_suggestions() { df_print_section "Optimization Suggestions" echo "" # Find frequently typed long commands df_print_indent "Consider creating aliases for:" echo "" get_history | awk 'length > 20' | sort | uniq -c | sort -rn | head -5 | while read -r count cmd; do if [[ "$count" =~ ^[0-9]+$ ]] && [[ $count -ge 5 ]]; then local short_cmd="${cmd:0:50}" [[ ${#cmd} -gt 50 ]] && short_cmd="${short_cmd}..." echo -e " ${DF_YELLOW}${count}×${DF_NC} $short_cmd" fi done echo "" # Check for common mistakes df_print_indent "Common typos detected:" echo "" local typos=("gti:git" "sl:ls" "cta:cat" "grpe:grep" "suod:sudo") local found_typos=0 # Cache history local history_cache history_cache=$(get_history | awk '{print $1}') for typo_pair in "${typos[@]}"; do local typo="${typo_pair%%:*}" local correct="${typo_pair#*:}" local count count=$(echo "$history_cache" | grep -c "^${typo}$" 2>/dev/null) || count=0 if [[ "$count" =~ ^[0-9]+$ ]] && [[ $count -gt 0 ]]; then echo -e " ${DF_RED}$typo${DF_NC} → ${DF_GREEN}$correct${DF_NC} (${count}×)" found_typos=1 fi done if [[ $found_typos -eq 0 ]]; then echo -e " ${DF_GREEN}No common typos detected!${DF_NC}" fi echo "" } # Dashboard view show_dashboard() { df_print_section "Shell Analytics Dashboard" echo "" # Basic stats local total total=$(get_history | wc -l 2>/dev/null) || total=0 local unique unique=$(get_history | sort -u | wc -l 2>/dev/null) || unique=0 # Ensure we have valid numbers [[ ! "$total" =~ ^[0-9]+$ ]] && total=0 [[ ! "$unique" =~ ^[0-9]+$ ]] && unique=0 # Trim whitespace total=$(echo "$total" | tr -d ' ') unique=$(echo "$unique" | tr -d ' ') echo -e " Total commands: ${DF_GREEN}$total${DF_NC}" echo -e " Unique commands: ${DF_CYAN}$unique${DF_NC}" if [[ $total -gt 0 ]]; then local efficiency=$((unique * 100 / total)) echo -e " Efficiency ratio: ${DF_YELLOW}${efficiency}%${DF_NC}" fi echo "" # Top commands df_print_section "Top 15 Commands" echo "" get_history | awk '{print $1}' | sort 2>/dev/null | uniq -c 2>/dev/null | sort -rn 2>/dev/null | head -15 | \ while read -r count cmd; do if [[ "$count" =~ ^[0-9]+$ ]] && [[ -n "$cmd" ]]; then printf " ${DF_GREEN}%-20s${DF_NC} %5d\n" "$cmd" "$count" fi done echo "" # Tool usage show_tools } # Help show_help() { cat << 'EOF' Enhanced Shell Analytics Usage: dotfiles-analytics.sh [COMMAND] Commands: (none) Show dashboard with overview hourly Command usage by hour of day weekly Command usage by day of week projects Commands grouped by directory trends Usage trends over last 30 days complexity Analyze command complexity tools Tool/category usage breakdown suggestions Alias and optimization suggestions all Show all analytics Examples: dotfiles-analytics.sh # Dashboard dotfiles-analytics.sh hourly # When do you code most? dotfiles-analytics.sh suggestions # Get optimization tips EOF } # ============================================================================ # Main # ============================================================================ main() { df_print_header "dotfiles-analytics" case "${1:-dashboard}" in dashboard|dash) show_dashboard ;; hourly|hour|h) show_hourly ;; weekly|week|w) show_weekly ;; projects|proj|p) show_projects ;; trends|trend|t) show_trends ;; complexity|complex|c) show_complexity ;; tools|tool) show_tools ;; suggestions|suggest|s) show_suggestions ;; all|a) show_dashboard echo "" show_hourly echo "" show_weekly echo "" show_complexity echo "" show_suggestions ;; help|--help|-h) show_help ;; *) echo "Unknown command: $1" echo "Use --help for usage" exit 1 ;; esac } main "$@"