Auto-sync from catchthesethighs
This commit is contained in:
516
bin/dotfiles-stats.sh
Normal file
516
bin/dotfiles-stats.sh
Normal file
@@ -0,0 +1,516 @@
|
||||
#!/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 "$@"
|
||||
Reference in New Issue
Block a user