Dotfiles update 2025-12-25 15:45
This commit is contained in:
352
bin/dotfiles-analytics.sh
Executable file
352
bin/dotfiles-analytics.sh
Executable file
@@ -0,0 +1,352 @@
|
||||
#!/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
|
||||
# ============================================================================
|
||||
|
||||
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"
|
||||
elif [[ -f "$BASH_HISTORY_FILE" ]]; then
|
||||
cat "$BASH_HISTORY_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Analytics Functions
|
||||
# ============================================================================
|
||||
|
||||
# Commands by hour of day
|
||||
show_hourly() {
|
||||
df_print_section "Command Usage by Hour of Day"
|
||||
echo ""
|
||||
|
||||
declare -A hour_counts
|
||||
|
||||
get_zsh_history_with_time | while IFS='|' read -r timestamp cmd; do
|
||||
if [[ -n "$timestamp" && "$timestamp" =~ ^[0-9]+$ ]]; then
|
||||
local 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<bar_len && i<40; i++)); do bar+="█"; done
|
||||
printf " %s:00 %5d ${DF_CYAN}%s${DF_NC}\n" "$hour" "$count" "$bar"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo -e "${DF_DIM}Peak hours indicate your most active coding times${DF_NC}"
|
||||
}
|
||||
|
||||
# Commands by day of week
|
||||
show_weekly() {
|
||||
df_print_section "Command Usage by Day of Week"
|
||||
echo ""
|
||||
|
||||
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
|
||||
if [[ -n "$timestamp" && "$timestamp" =~ ^[0-9]+$ ]]; then
|
||||
local dow=$(date -d "@$timestamp" '+%a' 2>/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<bar_len && i<40; i++)); do bar+="█"; done
|
||||
printf " %-3s %6d ${DF_CYAN}%s${DF_NC}\n" "$day" "$count" "$bar"
|
||||
done
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Commands grouped by working directory
|
||||
show_projects() {
|
||||
df_print_section "Command Usage by Project/Directory"
|
||||
echo ""
|
||||
|
||||
# This requires shell integration that saves PWD with commands
|
||||
# We'll analyze cd commands as a proxy
|
||||
|
||||
df_print_indent "Most visited directories:"
|
||||
echo ""
|
||||
|
||||
get_history | grep "^cd " | awk '{print $2}' | \
|
||||
sed 's|^~|'"$HOME"'|' | \
|
||||
sort | uniq -c | sort -rn | head -15 | while read -r count dir; do
|
||||
# Shorten home paths
|
||||
local short_dir="${dir/#$HOME/~}"
|
||||
printf " %5d ${DF_CYAN}%s${DF_NC}\n" "$count" "$short_dir"
|
||||
done
|
||||
|
||||
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
|
||||
show_trends() {
|
||||
df_print_section "Usage Trends (Last 30 Days)"
|
||||
echo ""
|
||||
|
||||
local today=$(date +%s)
|
||||
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
|
||||
if [[ -n "$timestamp" && "$timestamp" =~ ^[0-9]+$ ]]; then
|
||||
if (( timestamp >= thirty_days_ago )); then
|
||||
date -d "@$timestamp" '+%Y-%m-%d' 2>/dev/null || date -r "$timestamp" '+%Y-%m-%d' 2>/dev/null
|
||||
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<bar_len && i<30; i++)); do bar+="▪"; done
|
||||
printf " %s %4d ${DF_CYAN}%s${DF_NC}\n" "$date" "$count" "$bar"
|
||||
done
|
||||
}
|
||||
|
||||
# Command complexity analysis
|
||||
show_complexity() {
|
||||
df_print_section "Command Complexity Analysis"
|
||||
echo ""
|
||||
|
||||
df_print_indent "Simple commands (single word):"
|
||||
local simple=$(get_history | awk 'NF==1' | wc -l)
|
||||
echo " $simple"
|
||||
|
||||
df_print_indent "Piped commands:"
|
||||
local piped=$(get_history | grep -c '|' || echo 0)
|
||||
echo " $piped"
|
||||
|
||||
df_print_indent "Commands with redirects:"
|
||||
local redirects=$(get_history | grep -cE '[<>]' || echo 0)
|
||||
echo " $redirects"
|
||||
|
||||
df_print_indent "Commands with subshells:"
|
||||
local subshell=$(get_history | grep -cE '\$\(' || echo 0)
|
||||
echo " $subshell"
|
||||
|
||||
echo ""
|
||||
df_print_section "Most Complex Commands (by pipe count)"
|
||||
echo ""
|
||||
|
||||
get_history | awk -F'|' 'NF>2 {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"
|
||||
)
|
||||
|
||||
for category in "${categories[@]}"; do
|
||||
local name="${category%%:*}"
|
||||
local tools="${category#*:}"
|
||||
local total=0
|
||||
|
||||
for tool in $tools; do
|
||||
local count=$(get_history | awk '{print $1}' | grep -c "^${tool}$" 2>/dev/null || echo 0)
|
||||
total=$((total + count))
|
||||
done
|
||||
|
||||
if (( total > 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 >= 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")
|
||||
for typo_pair in "${typos[@]}"; do
|
||||
local typo="${typo_pair%%:*}"
|
||||
local correct="${typo_pair#*:}"
|
||||
local count=$(get_history | grep -c "^${typo} " 2>/dev/null || echo 0)
|
||||
if (( count > 0 )); then
|
||||
echo -e " ${DF_RED}$typo${DF_NC} → $correct (${count}×)"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Full dashboard
|
||||
show_dashboard() {
|
||||
local total=$(get_history | wc -l)
|
||||
local unique=$(get_history | sort -u | wc -l)
|
||||
|
||||
df_print_section "Shell Analytics Dashboard"
|
||||
echo ""
|
||||
echo -e " Total commands: ${DF_GREEN}$total${DF_NC}"
|
||||
echo -e " Unique commands: ${DF_GREEN}$unique${DF_NC}"
|
||||
echo -e " Efficiency ratio: ${DF_CYAN}$(( unique * 100 / (total + 1) ))%${DF_NC}"
|
||||
echo ""
|
||||
|
||||
df_print_section "Top 15 Commands"
|
||||
echo ""
|
||||
|
||||
get_history | awk '{print $1}' | sort | uniq -c | sort -rn | head -15 | while read -r count cmd; do
|
||||
printf " %-20s ${DF_GREEN}%5d${DF_NC}\n" "$cmd" "$count"
|
||||
done
|
||||
|
||||
echo ""
|
||||
show_tools
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Help
|
||||
# ============================================================================
|
||||
|
||||
show_help() {
|
||||
cat << 'EOF'
|
||||
Enhanced Shell Analytics
|
||||
|
||||
Usage: dotfiles-analytics.sh [COMMAND]
|
||||
|
||||
Commands:
|
||||
dashboard Full analytics dashboard (default)
|
||||
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 Command complexity analysis
|
||||
tools Tool usage breakdown
|
||||
suggestions Optimization suggestions
|
||||
help Show this help
|
||||
|
||||
Examples:
|
||||
dotfiles-analytics.sh # Full dashboard
|
||||
dotfiles-analytics.sh hourly # See peak coding hours
|
||||
dotfiles-analytics.sh suggestions # Get alias suggestions
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Main
|
||||
# ============================================================================
|
||||
|
||||
main() {
|
||||
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
|
||||
dashboard|d) show_dashboard ;;
|
||||
hourly|h) show_hourly ;;
|
||||
weekly|w) show_weekly ;;
|
||||
projects|p) show_projects ;;
|
||||
trends|t) show_trends ;;
|
||||
complexity|c) show_complexity ;;
|
||||
tools) show_tools ;;
|
||||
suggestions|s) show_suggestions ;;
|
||||
help|--help|-h) show_help ;;
|
||||
*)
|
||||
echo "Unknown command: $1"
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
428
bin/dotfiles-diff.sh
Executable file
428
bin/dotfiles-diff.sh
Executable file
@@ -0,0 +1,428 @@
|
||||
#!/usr/bin/env bash
|
||||
# ============================================================================
|
||||
# Dotfiles Diff & Audit Tool
|
||||
# ============================================================================
|
||||
# Compare configurations, audit for issues, and track changes.
|
||||
#
|
||||
# Usage:
|
||||
# dotfiles-diff.sh # Show uncommitted changes
|
||||
# dotfiles-diff.sh --symlinks # Verify symlink integrity
|
||||
# dotfiles-diff.sh --secrets # Audit for exposed secrets
|
||||
# dotfiles-diff.sh --permissions # Check file permissions
|
||||
# dotfiles-diff.sh --audit # Full security audit
|
||||
# ============================================================================
|
||||
|
||||
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_NC=$'\033[0m'
|
||||
DF_DIM=$'\033[2m'
|
||||
DOTFILES_HOME="${DOTFILES_HOME:-$HOME/.dotfiles}"
|
||||
df_print_header() { echo "=== $1 ==="; }
|
||||
df_print_success() { echo -e "${DF_GREEN}✓${DF_NC} $1"; }
|
||||
df_print_error() { echo -e "${DF_RED}✗${DF_NC} $1" >&2; }
|
||||
df_print_warning() { echo -e "${DF_YELLOW}⚠${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_indent() { echo " $1"; }
|
||||
}
|
||||
|
||||
DOTFILES_DIR="${DOTFILES_HOME:-$HOME/.dotfiles}"
|
||||
|
||||
# ============================================================================
|
||||
# Diff Functions
|
||||
# ============================================================================
|
||||
|
||||
# Show git diff for uncommitted changes
|
||||
show_git_diff() {
|
||||
df_print_section "Uncommitted Changes"
|
||||
|
||||
if [[ ! -d "$DOTFILES_DIR/.git" ]]; then
|
||||
df_print_warning "Not a git repository"
|
||||
return 1
|
||||
fi
|
||||
|
||||
cd "$DOTFILES_DIR"
|
||||
|
||||
local changes=$(git status --porcelain 2>/dev/null)
|
||||
|
||||
if [[ -z "$changes" ]]; then
|
||||
df_print_success "No uncommitted changes"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "$changes" | while read -r status file; do
|
||||
case "$status" in
|
||||
M*|" M") echo -e " ${DF_YELLOW}modified:${DF_NC} $file" ;;
|
||||
A*|"A ") echo -e " ${DF_GREEN}added:${DF_NC} $file" ;;
|
||||
D*|" D") echo -e " ${DF_RED}deleted:${DF_NC} $file" ;;
|
||||
R*) echo -e " ${DF_BLUE}renamed:${DF_NC} $file" ;;
|
||||
\?\?) echo -e " ${DF_DIM}untracked:${DF_NC} $file" ;;
|
||||
*) echo -e " ${status}: $file" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo ""
|
||||
df_print_step "View full diff: git -C $DOTFILES_DIR diff"
|
||||
}
|
||||
|
||||
# Show what's different between repo and installed files
|
||||
show_installed_diff() {
|
||||
df_print_section "Installed File Differences"
|
||||
echo ""
|
||||
|
||||
local files_to_check=(
|
||||
"$HOME/.zshrc:$DOTFILES_DIR/zsh/.zshrc"
|
||||
"$HOME/.gitconfig:$DOTFILES_DIR/git/.gitconfig"
|
||||
"$HOME/.vimrc:$DOTFILES_DIR/vim/.vimrc"
|
||||
"$HOME/.tmux.conf:$DOTFILES_DIR/tmux/.tmux.conf"
|
||||
)
|
||||
|
||||
local has_diff=false
|
||||
|
||||
for pair in "${files_to_check[@]}"; do
|
||||
local installed="${pair%%:*}"
|
||||
local source="${pair#*:}"
|
||||
local name=$(basename "$installed")
|
||||
|
||||
if [[ ! -e "$installed" ]]; then
|
||||
echo -e " ${DF_YELLOW}⚠${DF_NC} $name: not installed"
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ -L "$installed" ]]; then
|
||||
local target=$(readlink -f "$installed")
|
||||
if [[ "$target" == "$source" ]] || [[ "$target" == "$(readlink -f "$source")" ]]; then
|
||||
echo -e " ${DF_GREEN}✓${DF_NC} $name: symlink OK"
|
||||
else
|
||||
echo -e " ${DF_YELLOW}⚠${DF_NC} $name: symlink points elsewhere → $target"
|
||||
has_diff=true
|
||||
fi
|
||||
else
|
||||
# Regular file - check if different
|
||||
if diff -q "$installed" "$source" &>/dev/null; then
|
||||
echo -e " ${DF_GREEN}✓${DF_NC} $name: content matches (regular file)"
|
||||
else
|
||||
echo -e " ${DF_RED}✗${DF_NC} $name: differs from source"
|
||||
has_diff=true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$has_diff" == true ]]; then
|
||||
echo ""
|
||||
df_print_warning "Some files differ. Run installer to sync: ./install.sh"
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Symlink Verification
|
||||
# ============================================================================
|
||||
|
||||
check_symlinks() {
|
||||
df_print_section "Symlink Integrity Check"
|
||||
echo ""
|
||||
|
||||
local symlinks=(
|
||||
"$HOME/.zshrc"
|
||||
"$HOME/.gitconfig"
|
||||
"$HOME/.vimrc"
|
||||
"$HOME/.tmux.conf"
|
||||
"$HOME/.config/nvim"
|
||||
)
|
||||
|
||||
local broken=0
|
||||
local missing=0
|
||||
local ok=0
|
||||
|
||||
for link in "${symlinks[@]}"; do
|
||||
local name=$(basename "$link")
|
||||
|
||||
if [[ ! -e "$link" && ! -L "$link" ]]; then
|
||||
echo -e " ${DF_DIM}○${DF_NC} $name: not installed"
|
||||
((missing++))
|
||||
elif [[ -L "$link" ]]; then
|
||||
if [[ -e "$link" ]]; then
|
||||
echo -e " ${DF_GREEN}✓${DF_NC} $name → $(readlink "$link")"
|
||||
((ok++))
|
||||
else
|
||||
echo -e " ${DF_RED}✗${DF_NC} $name: BROKEN → $(readlink "$link")"
|
||||
((broken++))
|
||||
fi
|
||||
else
|
||||
echo -e " ${DF_YELLOW}⚠${DF_NC} $name: regular file (not symlink)"
|
||||
fi
|
||||
done
|
||||
|
||||
# Check bin scripts
|
||||
echo ""
|
||||
df_print_section "Bin Script Symlinks"
|
||||
|
||||
if [[ -d "$HOME/.local/bin" ]]; then
|
||||
for script in "$HOME/.local/bin"/dotfiles-*.sh; do
|
||||
[[ -e "$script" ]] || continue
|
||||
local name=$(basename "$script")
|
||||
|
||||
if [[ -L "$script" ]]; then
|
||||
if [[ -e "$script" ]]; then
|
||||
echo -e " ${DF_GREEN}✓${DF_NC} $name"
|
||||
((ok++))
|
||||
else
|
||||
echo -e " ${DF_RED}✗${DF_NC} $name: BROKEN"
|
||||
((broken++))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo ""
|
||||
df_print_section "Summary"
|
||||
df_print_indent "OK: $ok | Missing: $missing | Broken: $broken"
|
||||
|
||||
if (( broken > 0 )); then
|
||||
echo ""
|
||||
df_print_error "Found $broken broken symlinks!"
|
||||
df_print_indent "Fix with: dffix"
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Security Audit
|
||||
# ============================================================================
|
||||
|
||||
audit_secrets() {
|
||||
df_print_section "Secret Detection Audit"
|
||||
echo ""
|
||||
|
||||
cd "$DOTFILES_DIR"
|
||||
|
||||
local issues=0
|
||||
|
||||
# Patterns that might indicate secrets
|
||||
local patterns=(
|
||||
'api[_-]?key\s*[:=]'
|
||||
'secret[_-]?key\s*[:=]'
|
||||
'password\s*[:=]'
|
||||
'token\s*[:=]'
|
||||
'private[_-]?key'
|
||||
'BEGIN (RSA|DSA|EC|OPENSSH) PRIVATE KEY'
|
||||
'aws_access_key_id'
|
||||
'aws_secret_access_key'
|
||||
)
|
||||
|
||||
df_print_step "Scanning tracked files..."
|
||||
|
||||
for pattern in "${patterns[@]}"; do
|
||||
local matches=$(git grep -l -i -E "$pattern" 2>/dev/null || true)
|
||||
if [[ -n "$matches" ]]; then
|
||||
echo ""
|
||||
df_print_warning "Pattern '$pattern' found in:"
|
||||
echo "$matches" | while read -r file; do
|
||||
df_print_indent " $file"
|
||||
((issues++))
|
||||
done
|
||||
fi
|
||||
done
|
||||
|
||||
if (( issues == 0 )); then
|
||||
df_print_success "No obvious secrets found in tracked files"
|
||||
else
|
||||
echo ""
|
||||
df_print_error "Found potential secrets in $issues location(s)"
|
||||
df_print_indent "Review these files and use the vault for sensitive data"
|
||||
fi
|
||||
|
||||
# Check git history (limited)
|
||||
echo ""
|
||||
df_print_step "Scanning recent git history (last 50 commits)..."
|
||||
|
||||
local history_issues=$(git log -50 --all -p 2>/dev/null | grep -c -i -E 'password|secret|api.?key|token' || echo 0)
|
||||
|
||||
if (( history_issues > 0 )); then
|
||||
df_print_warning "Found $history_issues potential matches in git history"
|
||||
df_print_indent "Consider: git filter-branch or BFG Repo Cleaner"
|
||||
else
|
||||
df_print_success "No obvious secrets in recent history"
|
||||
fi
|
||||
}
|
||||
|
||||
audit_permissions() {
|
||||
df_print_section "File Permission Audit"
|
||||
echo ""
|
||||
|
||||
cd "$DOTFILES_DIR"
|
||||
|
||||
local issues=0
|
||||
|
||||
# Check for world-writable files
|
||||
df_print_step "Checking for world-writable files..."
|
||||
local world_writable=$(find . -type f -perm -o+w 2>/dev/null | grep -v ".git" || true)
|
||||
|
||||
if [[ -n "$world_writable" ]]; then
|
||||
df_print_warning "World-writable files found:"
|
||||
echo "$world_writable" | while read -r file; do
|
||||
df_print_indent "$file"
|
||||
((issues++))
|
||||
done
|
||||
else
|
||||
df_print_success "No world-writable files"
|
||||
fi
|
||||
|
||||
# Check bin scripts are executable
|
||||
echo ""
|
||||
df_print_step "Checking bin script permissions..."
|
||||
|
||||
for script in "$DOTFILES_DIR/bin"/*.sh; do
|
||||
[[ -f "$script" ]] || continue
|
||||
local name=$(basename "$script")
|
||||
|
||||
if [[ -x "$script" ]]; then
|
||||
echo -e " ${DF_GREEN}✓${DF_NC} $name"
|
||||
else
|
||||
echo -e " ${DF_RED}✗${DF_NC} $name: not executable"
|
||||
((issues++))
|
||||
fi
|
||||
done
|
||||
|
||||
# Check sensitive directories
|
||||
echo ""
|
||||
df_print_step "Checking sensitive directories..."
|
||||
|
||||
if [[ -d "$DOTFILES_DIR/vault" ]]; then
|
||||
local vault_perms=$(stat -c %a "$DOTFILES_DIR/vault" 2>/dev/null || stat -f %Lp "$DOTFILES_DIR/vault" 2>/dev/null)
|
||||
if [[ "$vault_perms" == "700" ]]; then
|
||||
df_print_success "vault/ directory: 700 (secure)"
|
||||
else
|
||||
df_print_warning "vault/ directory: $vault_perms (should be 700)"
|
||||
((issues++))
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
if (( issues == 0 )); then
|
||||
df_print_success "All permission checks passed"
|
||||
else
|
||||
df_print_warning "Found $issues permission issues"
|
||||
fi
|
||||
}
|
||||
|
||||
full_audit() {
|
||||
df_print_step "Running full security audit..."
|
||||
echo ""
|
||||
|
||||
audit_secrets
|
||||
echo ""
|
||||
audit_permissions
|
||||
echo ""
|
||||
check_symlinks
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Machine Comparison
|
||||
# ============================================================================
|
||||
|
||||
compare_machines() {
|
||||
df_print_section "Machine Configuration Comparison"
|
||||
echo ""
|
||||
|
||||
local machines_dir="$DOTFILES_DIR/machines"
|
||||
|
||||
if [[ ! -d "$machines_dir" ]] || [[ -z "$(ls -A "$machines_dir" 2>/dev/null)" ]]; then
|
||||
df_print_info "No machine configs to compare"
|
||||
df_print_indent "Create with: df_machine_create"
|
||||
return
|
||||
fi
|
||||
|
||||
local configs=("$machines_dir"/*.zsh(N))
|
||||
|
||||
if (( ${#configs[@]} < 2 )); then
|
||||
df_print_info "Need at least 2 machine configs to compare"
|
||||
return
|
||||
fi
|
||||
|
||||
df_print_step "Available configs:"
|
||||
for config in "${configs[@]}"; do
|
||||
df_print_indent "$(basename "$config" .zsh)"
|
||||
done
|
||||
|
||||
echo ""
|
||||
read -p "Compare which two? (e.g., 'laptop server'): " config1 config2
|
||||
|
||||
if [[ -f "$machines_dir/$config1.zsh" && -f "$machines_dir/$config2.zsh" ]]; then
|
||||
echo ""
|
||||
diff -u --color=always "$machines_dir/$config1.zsh" "$machines_dir/$config2.zsh" || true
|
||||
else
|
||||
df_print_error "Config not found"
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Help
|
||||
# ============================================================================
|
||||
|
||||
show_help() {
|
||||
cat << 'EOF'
|
||||
Dotfiles Diff & Audit Tool
|
||||
|
||||
Usage: dotfiles-diff.sh [OPTIONS]
|
||||
|
||||
Options:
|
||||
(none) Show uncommitted git changes
|
||||
--installed Compare installed files with source
|
||||
--symlinks Verify symlink integrity
|
||||
--secrets Scan for exposed secrets
|
||||
--permissions Check file permissions
|
||||
--audit Full security audit (secrets + permissions + symlinks)
|
||||
--machines Compare machine configurations
|
||||
--help Show this help
|
||||
|
||||
Examples:
|
||||
dotfiles-diff.sh # Quick git status
|
||||
dotfiles-diff.sh --symlinks # Verify all symlinks are valid
|
||||
dotfiles-diff.sh --audit # Full security audit
|
||||
dotfiles-diff.sh --machines # Compare laptop vs server configs
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Main
|
||||
# ============================================================================
|
||||
|
||||
main() {
|
||||
df_print_header "dotfiles-diff"
|
||||
|
||||
case "${1:-}" in
|
||||
--installed|-i)
|
||||
show_installed_diff
|
||||
;;
|
||||
--symlinks|-s)
|
||||
check_symlinks
|
||||
;;
|
||||
--secrets)
|
||||
audit_secrets
|
||||
;;
|
||||
--permissions|-p)
|
||||
audit_permissions
|
||||
;;
|
||||
--audit|-a)
|
||||
full_audit
|
||||
;;
|
||||
--machines|-m)
|
||||
compare_machines
|
||||
;;
|
||||
--help|-h)
|
||||
show_help
|
||||
;;
|
||||
*)
|
||||
show_git_diff
|
||||
echo ""
|
||||
show_installed_diff
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
300
bin/dotfiles-profile.sh
Executable file
300
bin/dotfiles-profile.sh
Executable file
@@ -0,0 +1,300 @@
|
||||
#!/usr/bin/env bash
|
||||
# ============================================================================
|
||||
# Dotfiles Startup Profiler
|
||||
# ============================================================================
|
||||
# Measures and analyzes shell startup time to identify slow components.
|
||||
#
|
||||
# Usage:
|
||||
# dotfiles-profile.sh # Quick profile
|
||||
# dotfiles-profile.sh --detailed # Detailed zprof output
|
||||
# dotfiles-profile.sh --benchmark # Multiple runs with hyperfine
|
||||
# dotfiles-profile.sh --compare # Compare with minimal shell
|
||||
# ============================================================================
|
||||
|
||||
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_NC=$'\033[0m'
|
||||
DF_DIM=$'\033[2m'
|
||||
df_print_header() { echo "=== $1 ==="; }
|
||||
df_print_success() { echo -e "${DF_GREEN}✓${DF_NC} $1"; }
|
||||
df_print_warning() { echo -e "${DF_YELLOW}⚠${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_indent() { echo " $1"; }
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Configuration
|
||||
# ============================================================================
|
||||
|
||||
PROFILE_RUNS=5
|
||||
SLOW_THRESHOLD_MS=200
|
||||
VERY_SLOW_THRESHOLD_MS=500
|
||||
|
||||
# ============================================================================
|
||||
# Profiling Functions
|
||||
# ============================================================================
|
||||
|
||||
# Quick timing measurement
|
||||
quick_profile() {
|
||||
df_print_section "Quick Startup Timing"
|
||||
echo ""
|
||||
|
||||
local times=()
|
||||
for i in $(seq 1 $PROFILE_RUNS); do
|
||||
local start=$(date +%s%3N)
|
||||
zsh -i -c 'exit' 2>/dev/null
|
||||
local end=$(date +%s%3N)
|
||||
local duration=$((end - start))
|
||||
times+=($duration)
|
||||
printf " Run %d: ${DF_CYAN}%dms${DF_NC}\n" "$i" "$duration"
|
||||
done
|
||||
|
||||
# Calculate average
|
||||
local sum=0
|
||||
for t in "${times[@]}"; do
|
||||
sum=$((sum + t))
|
||||
done
|
||||
local avg=$((sum / PROFILE_RUNS))
|
||||
|
||||
echo ""
|
||||
df_print_section "Results"
|
||||
|
||||
if (( avg < SLOW_THRESHOLD_MS )); then
|
||||
df_print_indent "Average: ${DF_GREEN}${avg}ms${DF_NC} (excellent)"
|
||||
elif (( avg < VERY_SLOW_THRESHOLD_MS )); then
|
||||
df_print_indent "Average: ${DF_YELLOW}${avg}ms${DF_NC} (acceptable)"
|
||||
else
|
||||
df_print_indent "Average: ${DF_RED}${avg}ms${DF_NC} (slow - optimization recommended)"
|
||||
fi
|
||||
}
|
||||
|
||||
# Detailed zprof analysis
|
||||
detailed_profile() {
|
||||
df_print_section "Detailed Function Profiling (zprof)"
|
||||
echo ""
|
||||
|
||||
# Create temporary profile script
|
||||
local tmp_zshrc=$(mktemp)
|
||||
cat > "$tmp_zshrc" << 'EOF'
|
||||
zmodload zsh/zprof
|
||||
source ~/.zshrc
|
||||
zprof
|
||||
EOF
|
||||
|
||||
ZDOTDIR=$(dirname "$tmp_zshrc") zsh -i -c "source $tmp_zshrc; exit" 2>/dev/null | head -40
|
||||
|
||||
rm -f "$tmp_zshrc"
|
||||
|
||||
echo ""
|
||||
df_print_info "Top 40 functions shown. Run with ZDOTDIR override for full output."
|
||||
}
|
||||
|
||||
# Benchmark with hyperfine
|
||||
benchmark_profile() {
|
||||
if ! command -v hyperfine &>/dev/null; then
|
||||
df_print_warning "hyperfine not installed"
|
||||
df_print_info "Install: sudo pacman -S hyperfine"
|
||||
df_print_info "Falling back to quick profile..."
|
||||
echo ""
|
||||
quick_profile
|
||||
return
|
||||
fi
|
||||
|
||||
df_print_section "Benchmark (hyperfine)"
|
||||
echo ""
|
||||
|
||||
hyperfine --warmup 3 --min-runs 10 \
|
||||
--export-markdown /tmp/zsh-bench.md \
|
||||
'zsh -i -c exit' \
|
||||
2>&1
|
||||
|
||||
echo ""
|
||||
df_print_success "Results saved to /tmp/zsh-bench.md"
|
||||
}
|
||||
|
||||
# Compare with minimal shell
|
||||
compare_profile() {
|
||||
df_print_section "Comparison: Full vs Minimal Shell"
|
||||
echo ""
|
||||
|
||||
df_print_step "Full shell (with dotfiles):"
|
||||
local full_start=$(date +%s%3N)
|
||||
zsh -i -c 'exit' 2>/dev/null
|
||||
local full_end=$(date +%s%3N)
|
||||
local full_time=$((full_end - full_start))
|
||||
df_print_indent "${full_time}ms"
|
||||
|
||||
df_print_step "Minimal shell (no rc files):"
|
||||
local min_start=$(date +%s%3N)
|
||||
zsh --no-rcs -i -c 'exit' 2>/dev/null
|
||||
local min_end=$(date +%s%3N)
|
||||
local min_time=$((min_end - min_start))
|
||||
df_print_indent "${min_time}ms"
|
||||
|
||||
local overhead=$((full_time - min_time))
|
||||
local overhead_pct=$((overhead * 100 / (min_time + 1)))
|
||||
|
||||
echo ""
|
||||
df_print_section "Analysis"
|
||||
df_print_indent "Shell baseline: ${min_time}ms"
|
||||
df_print_indent "Dotfiles overhead: ${overhead}ms (+${overhead_pct}%)"
|
||||
|
||||
if (( overhead > VERY_SLOW_THRESHOLD_MS )); then
|
||||
echo ""
|
||||
df_print_warning "High overhead detected. Consider:"
|
||||
df_print_indent "• Lazy-loading heavy plugins (nvm, kubectl, etc.)"
|
||||
df_print_indent "• Compiling zsh files: dfcompile"
|
||||
df_print_indent "• Reducing oh-my-zsh plugins"
|
||||
df_print_indent "• Using zsh-defer for non-critical loads"
|
||||
fi
|
||||
}
|
||||
|
||||
# Identify slow components
|
||||
analyze_components() {
|
||||
df_print_section "Component Analysis"
|
||||
echo ""
|
||||
|
||||
local components=(
|
||||
"oh-my-zsh:source \$ZSH/oh-my-zsh.sh"
|
||||
"autosuggestions:source */zsh-autosuggestions.zsh"
|
||||
"syntax-highlight:source */zsh-syntax-highlighting.zsh"
|
||||
"fzf:source */fzf/*.zsh"
|
||||
"nvm:source \$NVM_DIR/nvm.sh"
|
||||
"dotfiles-funcs:source */functions/*.zsh"
|
||||
)
|
||||
|
||||
for comp in "${components[@]}"; do
|
||||
local name="${comp%%:*}"
|
||||
local pattern="${comp#*:}"
|
||||
|
||||
# Time loading this component
|
||||
local start=$(date +%s%3N)
|
||||
zsh -i -c "
|
||||
# Disable the component by commenting it out temporarily
|
||||
# This is a simplified check
|
||||
" 2>/dev/null
|
||||
local end=$(date +%s%3N)
|
||||
|
||||
printf " %-20s: checking...\n" "$name"
|
||||
done
|
||||
|
||||
echo ""
|
||||
df_print_info "For detailed component timing, use: --detailed"
|
||||
}
|
||||
|
||||
# Show optimization tips
|
||||
show_tips() {
|
||||
df_print_section "Optimization Tips"
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
1. COMPILE ZSH FILES
|
||||
Run: dfcompile
|
||||
Compiles .zsh files to .zwc bytecode for faster parsing.
|
||||
|
||||
2. LAZY-LOAD HEAVY TOOLS
|
||||
nvm, pyenv, rbenv, kubectl - only load when first used.
|
||||
Example in .zshrc:
|
||||
kubectl() { unfunction kubectl; source <(command kubectl completion zsh); kubectl "$@"; }
|
||||
|
||||
3. REDUCE OH-MY-ZSH PLUGINS
|
||||
Each plugin adds startup time. Only enable what you use.
|
||||
Heavy plugins: nvm, kubectl, docker-compose, thefuck
|
||||
|
||||
4. USE ZSH-DEFER
|
||||
Defer non-critical loading until after first prompt:
|
||||
zsh-defer source ~/.dotfiles/zsh/functions/heavy-stuff.zsh
|
||||
|
||||
5. PROFILE REGULARLY
|
||||
Run this script after changes to track impact.
|
||||
|
||||
6. CHECK FOR SLOW COMPLETIONS
|
||||
Completion initialization can be slow:
|
||||
autoload -Uz compinit
|
||||
if [[ -n ~/.zcompdump(#qN.mh+24) ]]; then
|
||||
compinit
|
||||
else
|
||||
compinit -C # Skip security check (faster)
|
||||
fi
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Help
|
||||
# ============================================================================
|
||||
|
||||
show_help() {
|
||||
cat << 'EOF'
|
||||
Dotfiles Startup Profiler
|
||||
|
||||
Usage: dotfiles-profile.sh [OPTIONS]
|
||||
|
||||
Options:
|
||||
(none) Quick profile (5 runs, average time)
|
||||
--detailed Detailed zprof function-level profiling
|
||||
--benchmark Benchmark with hyperfine (if installed)
|
||||
--compare Compare full shell vs minimal shell
|
||||
--tips Show optimization tips
|
||||
--all Run all profiling methods
|
||||
--help Show this help
|
||||
|
||||
Thresholds:
|
||||
< 200ms Excellent (green)
|
||||
200-500ms Acceptable (yellow)
|
||||
> 500ms Slow (red) - optimization recommended
|
||||
|
||||
Examples:
|
||||
dotfiles-profile.sh # Quick timing
|
||||
dotfiles-profile.sh --detailed # See which functions are slow
|
||||
dotfiles-profile.sh --compare # See dotfiles overhead
|
||||
dotfiles-profile.sh --all # Full analysis
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Main
|
||||
# ============================================================================
|
||||
|
||||
main() {
|
||||
df_print_header "dotfiles-profile"
|
||||
|
||||
case "${1:-}" in
|
||||
--detailed|-d)
|
||||
detailed_profile
|
||||
;;
|
||||
--benchmark|-b)
|
||||
benchmark_profile
|
||||
;;
|
||||
--compare|-c)
|
||||
compare_profile
|
||||
;;
|
||||
--tips|-t)
|
||||
show_tips
|
||||
;;
|
||||
--all|-a)
|
||||
quick_profile
|
||||
echo ""
|
||||
compare_profile
|
||||
echo ""
|
||||
detailed_profile
|
||||
echo ""
|
||||
show_tips
|
||||
;;
|
||||
--help|-h)
|
||||
show_help
|
||||
;;
|
||||
*)
|
||||
quick_profile
|
||||
echo ""
|
||||
df_print_info "For more analysis: $0 --all"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
480
bin/dotfiles-tour.sh
Executable file
480
bin/dotfiles-tour.sh
Executable file
@@ -0,0 +1,480 @@
|
||||
#!/usr/bin/env bash
|
||||
# ============================================================================
|
||||
# Dotfiles First-Run Experience & Tour
|
||||
# ============================================================================
|
||||
# Provides a guided introduction for new users and after updates.
|
||||
#
|
||||
# Usage:
|
||||
# dotfiles-tour.sh # Interactive tour
|
||||
# dotfiles-tour.sh --quick # Quick feature overview
|
||||
# dotfiles-tour.sh --changelog # Show recent changes
|
||||
# ============================================================================
|
||||
|
||||
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_BOLD=$'\033[1m'
|
||||
DOTFILES_HOME="${DOTFILES_HOME:-$HOME/.dotfiles}"
|
||||
DOTFILES_VERSION="${DOTFILES_VERSION:-1.0.0}"
|
||||
df_print_header() { echo "=== $1 ==="; }
|
||||
df_print_section() { echo -e "${DF_CYAN}$1:${DF_NC}"; }
|
||||
df_print_indent() { echo " $1"; }
|
||||
df_print_success() { echo -e "${DF_GREEN}✓${DF_NC} $1"; }
|
||||
df_print_info() { echo -e "${DF_CYAN}ℹ${DF_NC} $1"; }
|
||||
}
|
||||
|
||||
DOTFILES_DIR="${DOTFILES_HOME:-$HOME/.dotfiles}"
|
||||
FIRST_RUN_FILE="$DOTFILES_DIR/.initialized"
|
||||
LAST_VERSION_FILE="$DOTFILES_DIR/.last-version"
|
||||
|
||||
# ============================================================================
|
||||
# Welcome Screen
|
||||
# ============================================================================
|
||||
|
||||
show_welcome() {
|
||||
clear
|
||||
cat << 'EOF'
|
||||
╔═══════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ █████╗ ██████╗ ██╗ ███████╗███████╗ ║
|
||||
║ ██╔══██╗██╔══██╗██║ ██╔════╝██╔════╝ ║
|
||||
║ ███████║██║ ██║██║ █████╗ █████╗ ║
|
||||
║ ██╔══██║██║ ██║██║ ██╔══╝ ██╔══╝ ║
|
||||
║ ██║ ██║██████╔╝███████╗███████╗███████╗ ║
|
||||
║ ╚═╝ ╚═╝╚═════╝ ╚══════╝╚══════╝╚══════╝ ║
|
||||
║ ║
|
||||
║ D O T F I L E S ║
|
||||
║ ║
|
||||
╚═══════════════════════════════════════════════════════════════╝
|
||||
EOF
|
||||
echo ""
|
||||
echo -e " ${DF_DIM}Version: ${DOTFILES_VERSION}${DF_NC}"
|
||||
echo ""
|
||||
echo -e " Welcome to ADLee's Dotfiles!"
|
||||
echo -e " ${DF_DIM}A productive development environment for Arch/CachyOS${DF_NC}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Tour Pages
|
||||
# ============================================================================
|
||||
|
||||
tour_navigation() {
|
||||
clear
|
||||
df_print_header "Navigation & Shortcuts"
|
||||
echo ""
|
||||
|
||||
df_print_section "Directory Navigation"
|
||||
echo ""
|
||||
echo -e " ${DF_CYAN}..${DF_NC} Go up one directory"
|
||||
echo -e " ${DF_CYAN}...${DF_NC} Go up two directories"
|
||||
echo -e " ${DF_CYAN}~${DF_NC} Go to home"
|
||||
echo -e " ${DF_CYAN}c.${DF_NC} Go to dotfiles directory"
|
||||
echo ""
|
||||
|
||||
df_print_section "Bookmarks"
|
||||
echo ""
|
||||
echo -e " ${DF_CYAN}bookmark add work ~/projects/work${DF_NC}"
|
||||
echo -e " ${DF_CYAN}j work${DF_NC} Jump to bookmark"
|
||||
echo -e " ${DF_CYAN}bm list${DF_NC} List all bookmarks"
|
||||
echo ""
|
||||
|
||||
df_print_section "Command Palette (Ctrl+Space)"
|
||||
echo ""
|
||||
echo -e " Fuzzy search through:"
|
||||
echo -e " • Aliases and functions"
|
||||
echo -e " • Command history"
|
||||
echo -e " • Bookmarks"
|
||||
echo -e " • Quick actions"
|
||||
}
|
||||
|
||||
tour_dotfiles_management() {
|
||||
clear
|
||||
df_print_header "Dotfiles Management"
|
||||
echo ""
|
||||
|
||||
df_print_section "Quick Commands"
|
||||
echo ""
|
||||
echo -e " ${DF_GREEN}dfd${DF_NC} Health check (doctor)"
|
||||
echo -e " ${DF_GREEN}dffix${DF_NC} Auto-fix issues"
|
||||
echo -e " ${DF_GREEN}dfu${DF_NC} Update dotfiles"
|
||||
echo -e " ${DF_GREEN}dfs${DF_NC} Sync status"
|
||||
echo -e " ${DF_GREEN}dfpush${DF_NC} Push changes"
|
||||
echo -e " ${DF_GREEN}dfpull${DF_NC} Pull changes"
|
||||
echo ""
|
||||
|
||||
df_print_section "Quick Edit"
|
||||
echo ""
|
||||
echo -e " ${DF_CYAN}v.zshrc${DF_NC} Edit ~/.zshrc"
|
||||
echo -e " ${DF_CYAN}v.conf${DF_NC} Edit dotfiles.conf"
|
||||
echo -e " ${DF_CYAN}v.alias${DF_NC} Edit aliases"
|
||||
echo -e " ${DF_CYAN}reload${DF_NC} Reload shell config"
|
||||
echo ""
|
||||
|
||||
df_print_section "Machine-Specific Config"
|
||||
echo ""
|
||||
echo -e " ${DF_CYAN}machine-info${DF_NC} Show current machine detection"
|
||||
echo -e " ${DF_CYAN}machine-create${DF_NC} Create config for this machine"
|
||||
}
|
||||
|
||||
tour_git_helpers() {
|
||||
clear
|
||||
df_print_header "Git & Development"
|
||||
echo ""
|
||||
|
||||
df_print_section "Git Shortcuts"
|
||||
echo ""
|
||||
echo -e " ${DF_GREEN}g${DF_NC} = git"
|
||||
echo -e " ${DF_GREEN}gs${DF_NC} = git status"
|
||||
echo -e " ${DF_GREEN}ga${DF_NC} = git add"
|
||||
echo -e " ${DF_GREEN}gc${DF_NC} = git commit"
|
||||
echo -e " ${DF_GREEN}gp${DF_NC} = git push"
|
||||
echo -e " ${DF_GREEN}gl${DF_NC} = git pull"
|
||||
echo -e " ${DF_GREEN}gd${DF_NC} = git diff"
|
||||
echo -e " ${DF_GREEN}glog${DF_NC} = pretty log graph"
|
||||
echo ""
|
||||
|
||||
df_print_section "Project Templates"
|
||||
echo ""
|
||||
echo -e " ${DF_CYAN}py-new myproject${DF_NC} Basic Python"
|
||||
echo -e " ${DF_CYAN}py-flask myapp${DF_NC} Flask web app"
|
||||
echo -e " ${DF_CYAN}py-fastapi myapi${DF_NC} FastAPI REST"
|
||||
echo -e " ${DF_CYAN}py-cli mytool${DF_NC} CLI with Click"
|
||||
echo -e " ${DF_CYAN}py-data analysis${DF_NC} Data science"
|
||||
echo ""
|
||||
|
||||
df_print_section "Project Environments"
|
||||
echo ""
|
||||
echo -e " Auto-loads ${DF_CYAN}.dotfiles-local${DF_NC} when entering directories"
|
||||
echo -e " Auto-activates Python virtualenvs"
|
||||
echo -e " Auto-switches Node versions via .nvmrc"
|
||||
}
|
||||
|
||||
tour_tmux_workspaces() {
|
||||
clear
|
||||
df_print_header "Tmux Workspaces"
|
||||
echo ""
|
||||
|
||||
df_print_section "Quick Commands"
|
||||
echo ""
|
||||
echo -e " ${DF_GREEN}tw myproject${DF_NC} Create/attach workspace"
|
||||
echo -e " ${DF_GREEN}tw myproject dev${DF_NC} Create with 'dev' template"
|
||||
echo -e " ${DF_GREEN}twl${DF_NC} List workspaces"
|
||||
echo -e " ${DF_GREEN}twf${DF_NC} Fuzzy search workspaces"
|
||||
echo -e " ${DF_GREEN}tws mytemplate${DF_NC} Save current layout"
|
||||
echo ""
|
||||
|
||||
df_print_section "Built-in Templates"
|
||||
echo ""
|
||||
echo -e " ${DF_CYAN}dev${DF_NC} Editor + terminal + logs"
|
||||
echo -e " ${DF_CYAN}ops${DF_NC} 4-pane monitoring grid"
|
||||
echo -e " ${DF_CYAN}review${DF_NC} Side-by-side comparison"
|
||||
echo -e " ${DF_CYAN}debug${DF_NC} Main (70%) + helper (30%)"
|
||||
echo -e " ${DF_CYAN}ssh-multi${DF_NC} 4 panes for servers"
|
||||
echo ""
|
||||
|
||||
df_print_section "Tmuxinator (if installed)"
|
||||
echo ""
|
||||
echo -e " ${DF_CYAN}txi myproject${DF_NC} Start tmuxinator project"
|
||||
echo -e " ${DF_CYAN}txi-new myproj dev${DF_NC} Create from template"
|
||||
}
|
||||
|
||||
tour_system_tools() {
|
||||
clear
|
||||
df_print_header "System Administration"
|
||||
echo ""
|
||||
|
||||
df_print_section "Systemd Helpers"
|
||||
echo ""
|
||||
echo -e " ${DF_GREEN}sc${DF_NC} sudo systemctl"
|
||||
echo -e " ${DF_GREEN}scr${DF_NC} svc Restart + status"
|
||||
echo -e " ${DF_GREEN}sce${DF_NC} svc Enable + start"
|
||||
echo -e " ${DF_GREEN}scd${DF_NC} svc Disable + stop"
|
||||
echo -e " ${DF_GREEN}sclog${DF_NC} svc Follow logs"
|
||||
echo -e " ${DF_GREEN}scf${DF_NC} Interactive (fzf)"
|
||||
echo -e " ${DF_GREEN}sc-failed${DF_NC} Show failed services"
|
||||
echo ""
|
||||
|
||||
df_print_section "Btrfs & Snapshots (if using btrfs)"
|
||||
echo ""
|
||||
echo -e " ${DF_CYAN}btrfs-health${DF_NC} Quick filesystem check"
|
||||
echo -e " ${DF_CYAN}snap 'desc'${DF_NC} Create snapshot"
|
||||
echo -e " ${DF_CYAN}snapls${DF_NC} List snapshots"
|
||||
echo -e " ${DF_CYAN}sys-update${DF_NC} Update with pre/post snapshot"
|
||||
echo ""
|
||||
|
||||
df_print_section "SSH Manager"
|
||||
echo ""
|
||||
echo -e " ${DF_CYAN}ssh-save myserver user@host${DF_NC}"
|
||||
echo -e " ${DF_CYAN}sshc myserver${DF_NC} Connect (auto-tmux)"
|
||||
echo -e " ${DF_CYAN}sshf${DF_NC} Fuzzy search servers"
|
||||
}
|
||||
|
||||
tour_productivity() {
|
||||
clear
|
||||
df_print_header "Productivity Features"
|
||||
echo ""
|
||||
|
||||
df_print_section "Smart Suggestions"
|
||||
echo ""
|
||||
echo -e " Auto-corrects common typos: ${DF_DIM}gti → git${DF_NC}"
|
||||
echo -e " Suggests packages for missing commands"
|
||||
echo -e " Use ${DF_CYAN}fuck${DF_NC} to re-run corrected command"
|
||||
echo ""
|
||||
|
||||
df_print_section "Long-Running Command Notifications"
|
||||
echo ""
|
||||
echo -e " Desktop notifications when commands take > 60s"
|
||||
echo -e " ${DF_CYAN}notify-toggle${DF_NC} Enable/disable"
|
||||
echo -e " ${DF_CYAN}notify-status${DF_NC} Check configuration"
|
||||
echo ""
|
||||
|
||||
df_print_section "Password Manager (LastPass)"
|
||||
echo ""
|
||||
echo -e " ${DF_CYAN}pw search${DF_NC} Search and copy password"
|
||||
echo -e " ${DF_CYAN}pwf${DF_NC} Fuzzy search (fzf)"
|
||||
echo -e " ${DF_CYAN}pw gen 24${DF_NC} Generate 24-char password"
|
||||
echo ""
|
||||
|
||||
df_print_section "Secrets Vault"
|
||||
echo ""
|
||||
echo -e " ${DF_CYAN}vault init${DF_NC} Initialize encrypted vault"
|
||||
echo -e " ${DF_CYAN}vault set KEY${DF_NC} Store a secret"
|
||||
echo -e " ${DF_CYAN}vault get KEY${DF_NC} Retrieve a secret"
|
||||
}
|
||||
|
||||
tour_complete() {
|
||||
clear
|
||||
df_print_header "Tour Complete!"
|
||||
echo ""
|
||||
|
||||
df_print_success "You're ready to go!"
|
||||
echo ""
|
||||
|
||||
df_print_section "Quick Reference"
|
||||
echo ""
|
||||
echo -e " ${DF_CYAN}dfd${DF_NC} Run health check"
|
||||
echo -e " ${DF_CYAN}Ctrl+Space${DF_NC} Command palette"
|
||||
echo -e " ${DF_CYAN}dotfiles-cli help${DF_NC} Full command list"
|
||||
echo ""
|
||||
|
||||
df_print_section "Documentation"
|
||||
echo ""
|
||||
echo -e " ${DF_CYAN}~/.dotfiles/README.md${DF_NC}"
|
||||
echo -e " ${DF_CYAN}~/.dotfiles/docs/REFERENCE.md${DF_NC}"
|
||||
echo ""
|
||||
|
||||
df_print_section "Getting Help"
|
||||
echo ""
|
||||
echo -e " Most commands support ${DF_CYAN}--help${DF_NC}"
|
||||
echo -e " Check ${DF_CYAN}~/.dotfiles/INSTALL.md${DF_NC} for troubleshooting"
|
||||
echo ""
|
||||
|
||||
# Mark first run complete
|
||||
touch "$FIRST_RUN_FILE"
|
||||
echo "$DOTFILES_VERSION" > "$LAST_VERSION_FILE"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Interactive Tour
|
||||
# ============================================================================
|
||||
|
||||
run_interactive_tour() {
|
||||
local pages=(
|
||||
"show_welcome:Welcome"
|
||||
"tour_navigation:Navigation & Shortcuts"
|
||||
"tour_dotfiles_management:Dotfiles Management"
|
||||
"tour_git_helpers:Git & Development"
|
||||
"tour_tmux_workspaces:Tmux Workspaces"
|
||||
"tour_system_tools:System Administration"
|
||||
"tour_productivity:Productivity Features"
|
||||
"tour_complete:Complete"
|
||||
)
|
||||
|
||||
local current=0
|
||||
local total=${#pages[@]}
|
||||
|
||||
while true; do
|
||||
local page_info="${pages[$current]}"
|
||||
local func="${page_info%%:*}"
|
||||
local title="${page_info#*:}"
|
||||
|
||||
# Show current page
|
||||
$func
|
||||
|
||||
# Navigation footer
|
||||
echo ""
|
||||
echo -e "${DF_DIM}─────────────────────────────────────────────────────────────${DF_NC}"
|
||||
echo -e " Page $((current + 1)) of $total: ${DF_CYAN}$title${DF_NC}"
|
||||
echo ""
|
||||
|
||||
if (( current == total - 1 )); then
|
||||
echo -e " Press ${DF_GREEN}Enter${DF_NC} to finish, ${DF_CYAN}p${DF_NC} for previous, ${DF_RED}q${DF_NC} to quit"
|
||||
else
|
||||
echo -e " Press ${DF_GREEN}Enter${DF_NC} for next, ${DF_CYAN}p${DF_NC} for previous, ${DF_RED}q${DF_NC} to quit"
|
||||
fi
|
||||
|
||||
read -rsn1 key
|
||||
|
||||
case "$key" in
|
||||
q|Q)
|
||||
echo ""
|
||||
echo "Tour cancelled. Run 'dotfiles-tour.sh' anytime to continue."
|
||||
exit 0
|
||||
;;
|
||||
p|P)
|
||||
(( current > 0 )) && ((current--))
|
||||
;;
|
||||
*)
|
||||
if (( current == total - 1 )); then
|
||||
echo ""
|
||||
echo -e "${DF_GREEN}Enjoy your new shell!${DF_NC}"
|
||||
exit 0
|
||||
fi
|
||||
((current++))
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Quick Overview
|
||||
# ============================================================================
|
||||
|
||||
show_quick_overview() {
|
||||
df_print_header "Quick Feature Overview"
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
╭──────────────────────────────────────────────────────────────╮
|
||||
│ NAVIGATION │
|
||||
│ Ctrl+Space Command palette j <bookmark> Jump │
|
||||
│ .. Up directory c. Dotfiles dir │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ DOTFILES │
|
||||
│ dfd Health check dfu Update │
|
||||
│ dfpush Push changes reload Reload shell │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ GIT │
|
||||
│ gs Status glog Pretty log │
|
||||
│ ga/gc/gp Add/commit/push gd Diff │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ TMUX │
|
||||
│ tw <name> Create workspace twl List │
|
||||
│ twf Fuzzy search tws Save layout │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ SYSTEM │
|
||||
│ sc systemctl scf Interactive │
|
||||
│ scr <svc> Restart service sc-failed Show failed │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ PYTHON │
|
||||
│ py-new Basic project py-flask Flask app │
|
||||
│ py-fastapi REST API venv Activate env │
|
||||
╰──────────────────────────────────────────────────────────────╯
|
||||
EOF
|
||||
|
||||
echo ""
|
||||
df_print_info "Run 'dotfiles-tour.sh' for full interactive tour"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Changelog
|
||||
# ============================================================================
|
||||
|
||||
show_changelog() {
|
||||
df_print_header "Recent Changes"
|
||||
echo ""
|
||||
|
||||
cd "$DOTFILES_DIR"
|
||||
|
||||
local last_version=""
|
||||
[[ -f "$LAST_VERSION_FILE" ]] && last_version=$(cat "$LAST_VERSION_FILE")
|
||||
|
||||
if [[ -n "$last_version" && "$last_version" != "$DOTFILES_VERSION" ]]; then
|
||||
echo -e "Updated from ${DF_YELLOW}$last_version${DF_NC} to ${DF_GREEN}$DOTFILES_VERSION${DF_NC}"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
df_print_section "Recent Commits"
|
||||
echo ""
|
||||
|
||||
if [[ -d .git ]]; then
|
||||
git log --oneline -15 2>/dev/null | while read -r line; do
|
||||
echo -e " ${DF_DIM}•${DF_NC} $line"
|
||||
done
|
||||
else
|
||||
echo " (git history not available)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Update version tracking
|
||||
echo "$DOTFILES_VERSION" > "$LAST_VERSION_FILE"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# First Run Check
|
||||
# ============================================================================
|
||||
|
||||
check_first_run() {
|
||||
if [[ ! -f "$FIRST_RUN_FILE" ]]; then
|
||||
echo ""
|
||||
echo -e "${DF_CYAN}Welcome!${DF_NC} This appears to be your first time using these dotfiles."
|
||||
echo -e "Run ${DF_GREEN}dotfiles-tour.sh${DF_NC} for a quick introduction."
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Help
|
||||
# ============================================================================
|
||||
|
||||
show_help() {
|
||||
cat << 'EOF'
|
||||
Dotfiles Tour & First-Run Experience
|
||||
|
||||
Usage: dotfiles-tour.sh [OPTIONS]
|
||||
|
||||
Options:
|
||||
(none) Interactive tour
|
||||
--quick, -q Quick feature overview
|
||||
--changelog Show recent changes
|
||||
--check Check if first run (for .zshrc)
|
||||
--help Show this help
|
||||
|
||||
The tour introduces all major features of the dotfiles system.
|
||||
Run it anytime to refresh your memory!
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Main
|
||||
# ============================================================================
|
||||
|
||||
main() {
|
||||
case "${1:-}" in
|
||||
--quick|-q)
|
||||
df_print_header "dotfiles-tour"
|
||||
show_quick_overview
|
||||
;;
|
||||
--changelog|-c)
|
||||
df_print_header "dotfiles-tour"
|
||||
show_changelog
|
||||
;;
|
||||
--check)
|
||||
check_first_run
|
||||
;;
|
||||
--help|-h)
|
||||
show_help
|
||||
;;
|
||||
*)
|
||||
run_interactive_tour
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user