ohmyzsh/plugins/eternalhist/eternalhist.plugin.zsh
AdminatComposeDAO 1353791277 initial, for PR
2025-07-01 20:18:50 -07:00

428 lines
No EOL
14 KiB
Bash

#!/usr/bin/env zsh
# EternalHist Plugin for Oh My Zsh
# Advanced persistent command history with multi-remote synchronization
# Default configuration
ETERNALHIST_LOCAL_FILE="${ETERNALHIST_LOCAL_FILE:-$HOME/.eternal_history}"
ETERNALHIST_DEFAULT_REMOTE="${ETERNALHIST_DEFAULT_REMOTE:-}"
ETERNALHIST_SYNC_ALL_REMOTES="${ETERNALHIST_SYNC_ALL_REMOTES:-false}"
ETERNALHIST_AUTO_SYNC="${ETERNALHIST_AUTO_SYNC:-false}"
ETERNALHIST_SEARCH_LIMIT="${ETERNALHIST_SEARCH_LIMIT:-100}"
ETERNALHIST_COLOR_OUTPUT="${ETERNALHIST_COLOR_OUTPUT:-true}"
# Internal variables
_ETERNALHIST_PLUGIN_DIR="${0:A:h}"
# Utility functions
_eternalhist_log() {
local level="$1"
shift
if [[ "$ETERNALHIST_DEBUG" == "true" ]]; then
echo "[eternalhist:$level] $*" >&2
fi
}
_eternalhist_error() {
echo "eternalhist: error: $*" >&2
}
_eternalhist_warn() {
echo "eternalhist: warning: $*" >&2
}
_eternalhist_colorize() {
if [[ "$ETERNALHIST_COLOR_OUTPUT" == "true" ]]; then
case "$1" in
"red") echo -e "\033[31m$2\033[0m" ;;
"green") echo -e "\033[32m$2\033[0m" ;;
"yellow") echo -e "\033[33m$2\033[0m" ;;
"blue") echo -e "\033[34m$2\033[0m" ;;
"purple") echo -e "\033[35m$2\033[0m" ;;
"cyan") echo -e "\033[36m$2\033[0m" ;;
*) echo "$2" ;;
esac
else
echo "$2"
fi
}
# Core eternal history functions
_eternalhist_ensure_file() {
touch "$ETERNALHIST_LOCAL_FILE"
chmod 600 "$ETERNALHIST_LOCAL_FILE"
}
_eternalhist_add_entry() {
local cmd="$1"
local timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
local pwd="$(pwd)"
local exit_code="${2:-0}"
_eternalhist_ensure_file
echo "$timestamp|$pwd|$exit_code|$cmd" >> "$ETERNALHIST_LOCAL_FILE"
_eternalhist_log "debug" "Added entry: $cmd"
}
_eternalhist_search_local() {
local search_terms=("$@")
local search_cmd="cat '$ETERNALHIST_LOCAL_FILE'"
_eternalhist_ensure_file
for term in "${search_terms[@]}"; do
search_cmd="$search_cmd | grep -i '$term'"
done
if [[ -n "$ETERNALHIST_SEARCH_LIMIT" ]] && [[ "$ETERNALHIST_SEARCH_LIMIT" -gt 0 ]]; then
search_cmd="$search_cmd | tail -n $ETERNALHIST_SEARCH_LIMIT"
fi
eval "$search_cmd"
}
# Remote configuration functions
_eternalhist_get_remotes() {
if [[ -n "$ETERNALHIST_REMOTES" ]]; then
echo "$ETERNALHIST_REMOTES" | tr ',' ' '
fi
}
_eternalhist_get_remote_var() {
local remote="$1"
local var="$2"
local remote_upper="$(echo "$remote" | tr '[:lower:]' '[:upper:]')"
local var_name="ETERNALHIST_${remote_upper}_${var}"
echo "${(P)var_name}"
}
_eternalhist_is_remote_enabled() {
local remote="$1"
local enabled="$(_eternalhist_get_remote_var "$remote" "ENABLED")"
[[ "$enabled" == "true" ]]
}
_eternalhist_list_remotes() {
local remotes=($(_eternalhist_get_remotes))
if [[ ${#remotes[@]} -eq 0 ]]; then
echo "No remotes configured."
return 0
fi
printf "%-15s %-10s %-10s %-20s %s\n" "NAME" "ENABLED" "PRIORITY" "PROVIDER" "PATH"
printf "%-15s %-10s %-10s %-20s %s\n" "----" "-------" "--------" "--------" "----"
for remote in "${remotes[@]}"; do
local enabled="$(_eternalhist_get_remote_var "$remote" "ENABLED")"
local priority="$(_eternalhist_get_remote_var "$remote" "PRIORITY")"
local provider="$(_eternalhist_get_remote_var "$remote" "PROVIDER")"
local path="$(_eternalhist_get_remote_var "$remote" "PATH")"
enabled="${enabled:-false}"
priority="${priority:-0}"
provider="${provider:-unknown}"
path="${path:-unknown}"
local color="red"
[[ "$enabled" == "true" ]] && color="green"
printf "%-15s %s %-10s %-20s %s\n" \
"$remote" \
"$(_eternalhist_colorize "$color" "$enabled")" \
"$priority" \
"$provider" \
"$path"
done
}
_eternalhist_test_remote() {
local remote="$1"
local provider="$(_eternalhist_get_remote_var "$remote" "PROVIDER")"
case "$provider" in
"ssh")
local host="$(_eternalhist_get_remote_var "$remote" "HOST")"
local user="$(_eternalhist_get_remote_var "$remote" "USER")"
local ssh_key="$(_eternalhist_get_remote_var "$remote" "SSH_KEY")"
local port="${$(_eternalhist_get_remote_var "$remote" "PORT"):-22}"
local ssh_opts="-o ConnectTimeout=10 -o BatchMode=yes"
[[ -n "$ssh_key" ]] && ssh_opts="$ssh_opts -i $ssh_key"
[[ -n "$port" ]] && ssh_opts="$ssh_opts -p $port"
if ssh $ssh_opts "${user}@${host}" "echo 'Connection test successful'" 2>/dev/null; then
_eternalhist_colorize "green" "✓ SSH connection to $remote successful"
return 0
else
_eternalhist_colorize "red" "✗ SSH connection to $remote failed"
return 1
fi
;;
"dropbox"|"gdrive"|"s3")
_eternalhist_warn "Test not implemented for provider: $provider"
return 1
;;
*)
_eternalhist_error "Unknown provider: $provider"
return 1
;;
esac
}
# Sync functions (placeholder implementations)
_eternalhist_sync_remote() {
local remote="$1"
local provider="$(_eternalhist_get_remote_var "$remote" "PROVIDER")"
if ! _eternalhist_is_remote_enabled "$remote"; then
_eternalhist_log "debug" "Remote $remote is disabled, skipping sync"
return 0
fi
_eternalhist_log "info" "Syncing with remote: $remote ($provider)"
case "$provider" in
"ssh")
_eternalhist_sync_ssh "$remote"
;;
"dropbox")
_eternalhist_sync_dropbox "$remote"
;;
"gdrive")
_eternalhist_sync_gdrive "$remote"
;;
"s3")
_eternalhist_sync_s3 "$remote"
;;
*)
_eternalhist_error "Unsupported provider: $provider"
return 1
;;
esac
}
_eternalhist_sync_ssh() {
local remote="$1"
local host="$(_eternalhist_get_remote_var "$remote" "HOST")"
local user="$(_eternalhist_get_remote_var "$remote" "USER")"
local remote_path="$(_eternalhist_get_remote_var "$remote" "PATH")"
local ssh_key="$(_eternalhist_get_remote_var "$remote" "SSH_KEY")"
local port="$(_eternalhist_get_remote_var "$remote" "PORT")"
local ssh_opts="-o ConnectTimeout=30"
[[ -n "$ssh_key" ]] && ssh_opts="$ssh_opts -i $ssh_key"
[[ -n "$port" ]] && ssh_opts="$ssh_opts -p $port"
# Simple sync: upload local file to remote
if scp $ssh_opts "$ETERNALHIST_LOCAL_FILE" "${user}@${host}:${remote_path}" 2>/dev/null; then
_eternalhist_colorize "green" "✓ Successfully synced to $remote"
return 0
else
_eternalhist_colorize "red" "✗ Failed to sync to $remote"
return 1
fi
}
# Placeholder sync functions for other providers
_eternalhist_sync_dropbox() {
local remote="$1"
_eternalhist_warn "Dropbox sync not yet implemented for remote: $remote"
return 1
}
_eternalhist_sync_gdrive() {
local remote="$1"
_eternalhist_warn "Google Drive sync not yet implemented for remote: $remote"
return 1
}
_eternalhist_sync_s3() {
local remote="$1"
_eternalhist_warn "S3 sync not yet implemented for remote: $remote"
return 1
}
# Main eternalhist command
eternalhist() {
# Handle empty command (show recent history)
if [[ $# -eq 0 ]]; then
_eternalhist_search_local | tail -n "${ETERNALHIST_SEARCH_LIMIT:-20}"
return 0
fi
local cmd="$1"
# Check for escape mechanism: \command means search for "command" literally
if [[ "$cmd" = \\* ]]; then
# Remove the escape backslash and treat as search term
local escaped_term="${cmd#\\}"
_eternalhist_search_local "$escaped_term" "${@:2}"
# Also search current session history
echo
_eternalhist_colorize "blue" "=== Current Session History ==="
local hist_cmd="history | grep -i '$escaped_term'"
for term in "${@:2}"; do
hist_cmd="$hist_cmd | grep -i '$term'"
done
eval "$hist_cmd"
return 0
fi
# Check if first argument is a known command
case "$cmd" in
"add")
shift
local entry="$*"
[[ -z "$entry" ]] && entry="$(history | tail -n 1 | sed 's/^[[:space:]]*[0-9]*[[:space:]]*//')"
_eternalhist_add_entry "$entry"
;;
"clear")
read -q "?Are you sure you want to clear eternal history? (y/N) "
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
> "$ETERNALHIST_LOCAL_FILE"
echo "Eternal history cleared."
fi
;;
"show")
shift
local limit="${1:-20}"
tail -n "$limit" "$ETERNALHIST_LOCAL_FILE"
;;
"stats")
_eternalhist_ensure_file
local total_lines="$(wc -l < "$ETERNALHIST_LOCAL_FILE")"
local unique_commands="$(cut -d'|' -f4 "$ETERNALHIST_LOCAL_FILE" | sort | uniq | wc -l)"
local oldest_entry="$(head -n 1 "$ETERNALHIST_LOCAL_FILE" | cut -d'|' -f1)"
local newest_entry="$(tail -n 1 "$ETERNALHIST_LOCAL_FILE" | cut -d'|' -f1)"
echo "Eternal History Statistics:"
echo " Total entries: $total_lines"
echo " Unique commands: $unique_commands"
echo " Oldest entry: ${oldest_entry:-N/A}"
echo " Newest entry: ${newest_entry:-N/A}"
echo " File location: $ETERNALHIST_LOCAL_FILE"
;;
"sync")
shift
local target_remotes=("$@")
if [[ ${#target_remotes[@]} -eq 0 ]]; then
if [[ "$ETERNALHIST_SYNC_ALL_REMOTES" == "true" ]]; then
target_remotes=($(_eternalhist_get_remotes))
elif [[ -n "$ETERNALHIST_DEFAULT_REMOTE" ]]; then
target_remotes=("$ETERNALHIST_DEFAULT_REMOTE")
else
_eternalhist_error "No remotes specified and no default remote configured"
return 1
fi
fi
for remote in "${target_remotes[@]}"; do
_eternalhist_sync_remote "$remote"
done
;;
"remotes")
shift
local subcmd="$1"
shift
case "$subcmd" in
"list"|"")
_eternalhist_list_remotes
;;
"test")
local remote="$1"
if [[ -z "$remote" ]]; then
_eternalhist_error "Remote name required for test"
return 1
fi
_eternalhist_test_remote "$remote"
;;
"add"|"remove"|"enable"|"disable")
_eternalhist_error "Remote management commands not yet implemented"
return 1
;;
*)
_eternalhist_error "Unknown remotes subcommand: $subcmd"
return 1
;;
esac
;;
"config")
echo "EternalHist Configuration:"
echo " Local file: $ETERNALHIST_LOCAL_FILE"
echo " Default remote: ${ETERNALHIST_DEFAULT_REMOTE:-none}"
echo " Auto sync: $ETERNALHIST_AUTO_SYNC"
echo " Configured remotes: ${ETERNALHIST_REMOTES:-none}"
;;
"help"|"-h"|"--help")
cat << 'EOF'
EternalHist - Advanced persistent command history
USAGE:
eternalhist [SEARCH_TERMS...] # Search eternal history (default behavior)
eternalhist [COMMAND] [OPTIONS] # Execute specific command
COMMANDS:
add [COMMAND] Add command to eternal history
clear Clear all eternal history
show [LIMIT] Show recent eternal history entries
stats Display eternal history statistics
sync [REMOTE...] Synchronize with remote storage
remotes Manage remote configurations
list List all configured remotes
test REMOTE Test connection to remote
config Show current configuration
help Show this help message
SEARCH BEHAVIOR:
eternalhist git commit # Search for "git" AND "commit"
eternalhist "git commit" # Search for exact phrase "git commit"
eternalhist \add something # Search for "add" (escaped to avoid add command)
eternalhist \sync # Search for "sync" (escaped to avoid sync command)
EXAMPLES:
eternalhist git # Search for git commands
eternalhist docker run # Search for docker run commands
eternalhist \add file # Search for "add file" (not add command)
eternalhist add "custom cmd" # Add custom command to history
eternalhist sync primary # Sync with primary remote
eternalhist remotes list # List all remotes
EOF
;;
*)
# Default behavior: treat as search terms
_eternalhist_search_local "$@"
# Also search current session history like original ht function
echo
_eternalhist_colorize "blue" "=== Current Session History ==="
local hist_cmd="history"
for term in "$@"; do
hist_cmd="$hist_cmd | grep -i '$term'"
done
eval "$hist_cmd"
;;
esac
}
# Backward compatibility with existing ht function
ht() {
eternalhist "$@"
}
# Auto-add commands to eternal history (optional)
if [[ "$ETERNALHIST_AUTO_ADD" == "true" ]]; then
preexec() {
_eternalhist_add_entry "$1" 0
}
fi
# Auto-sync on shell exit (optional)
if [[ "$ETERNALHIST_SYNC_ON_EXIT" == "true" ]]; then
zshexit() {
eternalhist sync >/dev/null 2>&1 &
}
fi
_eternalhist_log "info" "EternalHist plugin loaded"