Dotfiles update 2025-12-25 16:11

This commit is contained in:
Aaron D. Lee
2025-12-25 16:11:04 -05:00
parent 1c0491d025
commit b93026f51f
2 changed files with 202 additions and 109 deletions

View File

@@ -11,9 +11,13 @@
# dotfiles-analytics.sh weekly # Usage by day of week # dotfiles-analytics.sh weekly # Usage by day of week
# dotfiles-analytics.sh projects # Group by directory # dotfiles-analytics.sh projects # Group by directory
# dotfiles-analytics.sh trends # Usage trends over time # 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
# ============================================================================ # ============================================================================
set -e # Don't exit on error - we handle errors ourselves
set +e
# Source bootstrap # Source bootstrap
source "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null || { source "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null || {
@@ -51,9 +55,22 @@ get_zsh_history_with_time() {
# Get plain history (command only) # Get plain history (command only)
get_history() { get_history() {
if [[ -f "$HISTORY_FILE" ]]; then if [[ -f "$HISTORY_FILE" ]]; then
grep "^:" "$HISTORY_FILE" 2>/dev/null | cut -d';' -f2- || cat "$HISTORY_FILE" grep "^:" "$HISTORY_FILE" 2>/dev/null | cut -d';' -f2- || cat "$HISTORY_FILE" 2>/dev/null
elif [[ -f "$BASH_HISTORY_FILE" ]]; then elif [[ -f "$BASH_HISTORY_FILE" ]]; then
cat "$BASH_HISTORY_FILE" 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 fi
} }
@@ -66,11 +83,10 @@ show_hourly() {
df_print_section "Command Usage by Hour of Day" df_print_section "Command Usage by Hour of Day"
echo "" echo ""
declare -A hour_counts
get_zsh_history_with_time | while IFS='|' read -r timestamp cmd; do get_zsh_history_with_time | while IFS='|' read -r timestamp cmd; do
if [[ -n "$timestamp" && "$timestamp" =~ ^[0-9]+$ ]]; then if [[ -n "$timestamp" && "$timestamp" =~ ^[0-9]+$ ]]; then
local hour=$(date -d "@$timestamp" '+%H' 2>/dev/null || date -r "$timestamp" '+%H' 2>/dev/null) local hour
hour=$(date -d "@$timestamp" '+%H' 2>/dev/null || date -r "$timestamp" '+%H' 2>/dev/null)
if [[ -n "$hour" ]]; then if [[ -n "$hour" ]]; then
echo "$hour" echo "$hour"
fi fi
@@ -92,16 +108,11 @@ show_weekly() {
echo "" echo ""
local days=("Mon" "Tue" "Wed" "Thu" "Fri" "Sat" "Sun") local days=("Mon" "Tue" "Wed" "Thu" "Fri" "Sat" "Sun")
declare -A day_counts
# Initialize
for day in "${days[@]}"; do
day_counts[$day]=0
done
get_zsh_history_with_time | while IFS='|' read -r timestamp cmd; do get_zsh_history_with_time | while IFS='|' read -r timestamp cmd; do
if [[ -n "$timestamp" && "$timestamp" =~ ^[0-9]+$ ]]; then if [[ -n "$timestamp" && "$timestamp" =~ ^[0-9]+$ ]]; then
local dow=$(date -d "@$timestamp" '+%a' 2>/dev/null || date -r "$timestamp" '+%a' 2>/dev/null) local dow
dow=$(date -d "@$timestamp" '+%a' 2>/dev/null || date -r "$timestamp" '+%a' 2>/dev/null)
if [[ -n "$dow" ]]; then if [[ -n "$dow" ]]; then
echo "$dow" echo "$dow"
fi fi
@@ -110,64 +121,58 @@ show_weekly() {
local bar="" local bar=""
local bar_len=$((count / 100 + 1)) local bar_len=$((count / 100 + 1))
for ((i=0; i<bar_len && i<40; i++)); do bar+="█"; done for ((i=0; i<bar_len && i<40; i++)); do bar+="█"; done
printf " %-3s %6d ${DF_CYAN}%s${DF_NC}\n" "$day" "$count" "$bar" printf " %-3s %5d ${DF_GREEN}%s${DF_NC}\n" "$day" "$count" "$bar"
done done
echo "" echo ""
} }
# Commands grouped by working directory # Commands grouped by project/directory
show_projects() { show_projects() {
df_print_section "Command Usage by Project/Directory" df_print_section "Command Usage by Directory"
echo "" echo ""
# This requires shell integration that saves PWD with commands # This requires that history records include directory info
# We'll analyze cd commands as a proxy # For now, show most common directory references
echo -e " ${DF_DIM}Analyzing directory patterns in commands...${DF_NC}"
df_print_indent "Most visited directories:"
echo "" echo ""
get_history | grep "^cd " | awk '{print $2}' | \ get_history | grep -oE '(~/[^ ]+|/home/[^ /]+/[^ ]+)' 2>/dev/null | \
sed 's|^~|'"$HOME"'|' | \ sed 's|/[^/]*$||' | sort | uniq -c | sort -rn | head -10 | \
sort | uniq -c | sort -rn | head -15 | while read -r count dir; do while read -r count dir; do
# Shorten home paths local short_dir="$dir"
local short_dir="${dir/#$HOME/~}" if [[ ${#dir} -gt 40 ]]; then
printf " %5d ${DF_CYAN}%s${DF_NC}\n" "$count" "$short_dir" short_dir="...${dir: -37}"
done fi
printf " %5d ${DF_CYAN}%s${DF_NC}\n" "$count" "$short_dir"
done
echo "" echo ""
df_print_indent "Git repositories worked on:"
echo ""
get_history | grep -E "^(git|g) " | grep -v "^git config" | \
awk '{print $1, $2}' | sort | uniq -c | sort -rn | head -10 | while read -r count cmd subcmd; do
printf " %5d ${DF_GREEN}%s %s${DF_NC}\n" "$count" "$cmd" "$subcmd"
done
} }
# Usage trends over time # Usage trends over time (last 30 days)
show_trends() { show_trends() {
df_print_section "Usage Trends (Last 30 Days)" df_print_section "Usage Trends (Last 30 Days)"
echo "" echo ""
local today=$(date +%s) local thirty_days_ago=$(($(date +%s) - 30*24*60*60))
local thirty_days_ago=$((today - 30*24*60*60))
df_print_indent "Commands per day:"
echo ""
get_zsh_history_with_time | while IFS='|' read -r timestamp cmd; do get_zsh_history_with_time | while IFS='|' read -r timestamp cmd; do
if [[ -n "$timestamp" && "$timestamp" =~ ^[0-9]+$ ]]; then if [[ -n "$timestamp" && "$timestamp" =~ ^[0-9]+$ && "$timestamp" -ge "$thirty_days_ago" ]]; then
if (( timestamp >= thirty_days_ago )); then local date_str
date -d "@$timestamp" '+%Y-%m-%d' 2>/dev/null || date -r "$timestamp" '+%Y-%m-%d' 2>/dev/null 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
fi fi
done | sort | uniq -c | tail -30 | while read -r count date; do done | sort | uniq -c | tail -30 | while read -r count date; do
local bar="" local bar=""
local bar_len=$((count / 20 + 1)) local bar_len=$((count / 20 + 1))
for ((i=0; i<bar_len && i<30; i++)); do bar+="▪"; done for ((i=0; i<bar_len && i<30; i++)); do bar+="▪"; done
printf " %s %4d ${DF_CYAN}%s${DF_NC}\n" "$date" "$count" "$bar" printf " %s %4d ${DF_BLUE}%s${DF_NC}\n" "$date" "$count" "$bar"
done done
echo ""
} }
# Command complexity analysis # Command complexity analysis
@@ -175,31 +180,47 @@ show_complexity() {
df_print_section "Command Complexity Analysis" df_print_section "Command Complexity Analysis"
echo "" echo ""
df_print_indent "Simple commands (single word):" local total=0
local simple=$(get_history | awk 'NF==1' | wc -l) local simple=0
echo " $simple" local piped=0
local redirected=0
local subshell=0
df_print_indent "Piped commands:" while read -r cmd; do
local piped=$(get_history | grep -c '|' || echo 0) ((total++)) || true
echo " $piped"
if [[ "$cmd" == *"|"* ]]; then
((piped++)) || true
elif [[ "$cmd" == *">"* || "$cmd" == *"<"* ]]; then
((redirected++)) || true
elif [[ "$cmd" == *'$('* || "$cmd" == *'`'* ]]; then
((subshell++)) || true
else
((simple++)) || true
fi
done < <(get_history)
df_print_indent "Commands with redirects:" if [[ $total -gt 0 ]]; then
local redirects=$(get_history | grep -cE '[<>]' || echo 0) echo -e " Simple commands: ${DF_GREEN}$simple${DF_NC} ($((simple * 100 / total))%)"
echo " $redirects" echo -e " Piped commands: ${DF_YELLOW}$piped${DF_NC} ($((piped * 100 / total))%)"
echo -e " With redirection: ${DF_CYAN}$redirected${DF_NC} ($((redirected * 100 / total))%)"
df_print_indent "Commands with subshells:" echo -e " With subshells: ${DF_MAGENTA}$subshell${DF_NC} ($((subshell * 100 / total))%)"
local subshell=$(get_history | grep -cE '\$\(' || echo 0) else
echo " $subshell" echo " No history data available"
fi
echo "" echo ""
df_print_section "Most Complex Commands (by pipe count)"
# Most complex commands (by pipe count)
df_print_indent "Most Complex Pipelines:"
echo "" echo ""
get_history | awk -F'|' 'NF>2 {print NF-1, $0}' | sort -rn | head -5 | while read -r pipes cmd; do get_history | awk -F'|' 'NF > 3 {print NF-1, $0}' | sort -rn | head -5 | \
local short_cmd="${cmd:0:60}" while read -r pipes cmd; do
[[ ${#cmd} -gt 60 ]] && short_cmd="${short_cmd}..." local short_cmd="${cmd:0:60}"
echo -e " ${DF_MAGENTA}$pipes pipes:${DF_NC} $short_cmd" [[ ${#cmd} -gt 60 ]] && short_cmd="${short_cmd}..."
done echo -e " ${DF_MAGENTA}$pipes pipes:${DF_NC} $short_cmd"
done
} }
# Tool usage breakdown # Tool usage breakdown
@@ -217,17 +238,25 @@ show_tools() {
"Network:curl wget ssh scp" "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 for category in "${categories[@]}"; do
local name="${category%%:*}" local name="${category%%:*}"
local tools="${category#*:}" local tools="${category#*:}"
local total=0 local total=0
for tool in $tools; do for tool in $tools; do
local count=$(get_history | awk '{print $1}' | grep -c "^${tool}$" 2>/dev/null || echo 0) local count
total=$((total + 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 done
if (( total > 0 )); then if [[ $total -gt 0 ]]; then
printf " %-12s ${DF_GREEN}%6d${DF_NC}\n" "$name:" "$total" printf " %-12s ${DF_GREEN}%6d${DF_NC}\n" "$name:" "$total"
fi fi
done done
@@ -243,10 +272,10 @@ show_suggestions() {
echo "" echo ""
get_history | awk 'length > 20' | sort | uniq -c | sort -rn | head -5 | while read -r count cmd; do get_history | awk 'length > 20' | sort | uniq -c | sort -rn | head -5 | while read -r count cmd; do
if (( count >= 5 )); then if [[ "$count" =~ ^[0-9]+$ ]] && [[ $count -ge 5 ]]; then
local short_cmd="${cmd:0:50}" local short_cmd="${cmd:0:50}"
[[ ${#cmd} -gt 50 ]] && short_cmd="${short_cmd}..." [[ ${#cmd} -gt 50 ]] && short_cmd="${short_cmd}..."
echo -e " ${DF_YELLOW}$count×${DF_NC} $short_cmd" echo -e " ${DF_YELLOW}${count}×${DF_NC} $short_cmd"
fi fi
done done
@@ -257,43 +286,78 @@ show_suggestions() {
echo "" echo ""
local typos=("gti:git" "sl:ls" "cta:cat" "grpe:grep" "suod:sudo") 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 for typo_pair in "${typos[@]}"; do
local typo="${typo_pair%%:*}" local typo="${typo_pair%%:*}"
local correct="${typo_pair#*:}" local correct="${typo_pair#*:}"
local count=$(get_history | grep -c "^${typo} " 2>/dev/null || echo 0) local count
if (( count > 0 )); then count=$(echo "$history_cache" | grep -c "^${typo}$" 2>/dev/null) || count=0
echo -e " ${DF_RED}$typo${DF_NC}$correct (${count}×)"
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 fi
done done
if [[ $found_typos -eq 0 ]]; then
echo -e " ${DF_GREEN}No common typos detected!${DF_NC}"
fi
echo ""
} }
# Full dashboard # Dashboard view
show_dashboard() { show_dashboard() {
local total=$(get_history | wc -l)
local unique=$(get_history | sort -u | wc -l)
df_print_section "Shell Analytics Dashboard" df_print_section "Shell Analytics Dashboard"
echo "" 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 " Total commands: ${DF_GREEN}$total${DF_NC}"
echo -e " Unique commands: ${DF_GREEN}$unique${DF_NC}" echo -e " Unique commands: ${DF_CYAN}$unique${DF_NC}"
echo -e " Efficiency ratio: ${DF_CYAN}$(( unique * 100 / (total + 1) ))%${DF_NC}"
if [[ $total -gt 0 ]]; then
local efficiency=$((unique * 100 / total))
echo -e " Efficiency ratio: ${DF_YELLOW}${efficiency}%${DF_NC}"
fi
echo "" echo ""
# Top commands
df_print_section "Top 15 Commands" df_print_section "Top 15 Commands"
echo "" echo ""
get_history | awk '{print $1}' | sort | uniq -c | sort -rn | head -15 | while read -r count cmd; do get_history | awk '{print $1}' | sort 2>/dev/null | uniq -c 2>/dev/null | sort -rn 2>/dev/null | head -15 | \
printf " %-20s ${DF_GREEN}%5d${DF_NC}\n" "$cmd" "$count" while read -r count cmd; do
done if [[ "$count" =~ ^[0-9]+$ ]] && [[ -n "$cmd" ]]; then
printf " ${DF_GREEN}%-20s${DF_NC} %5d\n" "$cmd" "$count"
fi
done
echo "" echo ""
# Tool usage
show_tools show_tools
} }
# ============================================================================
# Help # Help
# ============================================================================
show_help() { show_help() {
cat << 'EOF' cat << 'EOF'
Enhanced Shell Analytics Enhanced Shell Analytics
@@ -301,20 +365,20 @@ Enhanced Shell Analytics
Usage: dotfiles-analytics.sh [COMMAND] Usage: dotfiles-analytics.sh [COMMAND]
Commands: Commands:
dashboard Full analytics dashboard (default) (none) Show dashboard with overview
hourly Command usage by hour of day hourly Command usage by hour of day
weekly Command usage by day of week weekly Command usage by day of week
projects Commands grouped by directory projects Commands grouped by directory
trends Usage trends over last 30 days trends Usage trends over last 30 days
complexity Command complexity analysis complexity Analyze command complexity
tools Tool usage breakdown tools Tool/category usage breakdown
suggestions Optimization suggestions suggestions Alias and optimization suggestions
help Show this help all Show all analytics
Examples: Examples:
dotfiles-analytics.sh # Full dashboard dotfiles-analytics.sh # Dashboard
dotfiles-analytics.sh hourly # See peak coding hours dotfiles-analytics.sh hourly # When do you code most?
dotfiles-analytics.sh suggestions # Get alias suggestions dotfiles-analytics.sh suggestions # Get optimization tips
EOF EOF
} }
@@ -326,24 +390,48 @@ EOF
main() { main() {
df_print_header "dotfiles-analytics" df_print_header "dotfiles-analytics"
if [[ ! -f "$HISTORY_FILE" && ! -f "$BASH_HISTORY_FILE" ]]; then
echo "No history file found"
exit 1
fi
case "${1:-dashboard}" in case "${1:-dashboard}" in
dashboard|d) show_dashboard ;; dashboard|dash)
hourly|h) show_hourly ;; show_dashboard
weekly|w) show_weekly ;; ;;
projects|p) show_projects ;; hourly|hour|h)
trends|t) show_trends ;; show_hourly
complexity|c) show_complexity ;; ;;
tools) show_tools ;; weekly|week|w)
suggestions|s) show_suggestions ;; show_weekly
help|--help|-h) show_help ;; ;;
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 "Unknown command: $1"
show_help echo "Use --help for usage"
exit 1 exit 1
;; ;;
esac esac

View File

@@ -12,7 +12,7 @@
# dotfiles-diff.sh --audit # Full security audit # dotfiles-diff.sh --audit # Full security audit
# ============================================================================ # ============================================================================
set -e set +e
# Source bootstrap # Source bootstrap
source "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null || { source "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null || {
@@ -24,6 +24,7 @@ source "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null ||
df_print_success() { echo -e "${DF_GREEN}${DF_NC} $1"; } df_print_success() { echo -e "${DF_GREEN}${DF_NC} $1"; }
df_print_error() { echo -e "${DF_RED}${DF_NC} $1" >&2; } df_print_error() { echo -e "${DF_RED}${DF_NC} $1" >&2; }
df_print_warning() { echo -e "${DF_YELLOW}${DF_NC} $1"; } df_print_warning() { echo -e "${DF_YELLOW}${DF_NC} $1"; }
df_print_info() { echo -e "${DF_CYAN}${DF_NC} $1"; }
df_print_step() { echo -e "${DF_BLUE}==>${DF_NC} $1"; } df_print_step() { echo -e "${DF_BLUE}==>${DF_NC} $1"; }
df_print_section() { echo -e "${DF_CYAN}$1:${DF_NC}"; } df_print_section() { echo -e "${DF_CYAN}$1:${DF_NC}"; }
df_print_indent() { echo " $1"; } df_print_indent() { echo " $1"; }
@@ -336,9 +337,13 @@ compare_machines() {
return return
fi fi
local configs=("$machines_dir"/*.zsh(N)) # Get list of config files (bash-compatible)
local configs=()
for f in "$machines_dir"/*.zsh; do
[[ -f "$f" ]] && configs+=("$f")
done
if (( ${#configs[@]} < 2 )); then if [[ ${#configs[@]} -lt 2 ]]; then
df_print_info "Need at least 2 machine configs to compare" df_print_info "Need at least 2 machine configs to compare"
return return
fi fi