#!/bin/sh # history - unified history viewer (encrypted with SQLCipher) # Usage: history -m [options] set -e DB_PATH="$HOME/.local/share/history/history.db" KEY_FILE="$HOME/.local/share/history/.key" DEFAULT_LIMIT=100 # Load encryption key load_key() { if [ ! -f "$KEY_FILE" ]; then echo "Encryption key not found. Run 'history-init' first." >&2 exit 1 fi cat "$KEY_FILE" } KEY=$(load_key) # SQLCipher wrapper for queries sql_query() { sqlcipher "$DB_PATH" "PRAGMA key = '$KEY'; $1" } sql_json() { sqlcipher -json "$DB_PATH" "PRAGMA key = '$KEY'; $1" } sql_table() { sqlcipher -header -column "$DB_PATH" "PRAGMA key = '$KEY'; $1" } sql_raw() { sqlcipher -separator '|' "$DB_PATH" "PRAGMA key = '$KEY'; $1" } # Colors C_RESET='\033[0m' C_DIM='\033[2m' C_CYAN='\033[36m' C_GREEN='\033[32m' C_YELLOW='\033[33m' C_MAGENTA='\033[35m' usage() { cat < [OPTIONS] MODES (-m): t, terminal Terminal command history c, clipboard Clipboard history s, search Browser search history OPTIONS: -o Output format: t (table), j (json), p (plain) [default: p] -n Number of entries to show [default: 100, 0 for unlimited] -d Filter by date (YYYY.MM.DD) -q Search within entries -b Filter by browser (search mode only) --from Start date (YYYY.MM.DD) --to End date (YYYY.MM.DD) --all Show all entries (no limit) --stats Show statistics instead of entries -h, --help Show this help EXAMPLES: history -m t # Last 100 terminal commands history -m t -n 50 # Last 50 terminal commands history -m s -b librewolf # Searches from Librewolf history -m c -d 2026.01.23 # Clipboard entries from specific day history -m t -q "git" # Terminal commands containing "git" history -m s --from 2026.01.01 --to 2026.01.15 history -m t --stats # Terminal statistics EOF exit 0 } # Check if database exists if [ ! -f "$DB_PATH" ]; then echo "Database not found. Run 'history-init' first." >&2 exit 1 fi # Parse arguments MODE="" OUTPUT="p" LIMIT=$DEFAULT_LIMIT DATE="" QUERY="" BROWSER="" FROM_DATE="" TO_DATE="" SHOW_STATS=0 while [ $# -gt 0 ]; do case "$1" in -m) MODE="$2" shift 2 ;; -o) OUTPUT="$2" shift 2 ;; -n) LIMIT="$2" shift 2 ;; -d) DATE="$2" shift 2 ;; -q) QUERY="$2" shift 2 ;; -b) BROWSER="$2" shift 2 ;; --from) FROM_DATE="$2" shift 2 ;; --to) TO_DATE="$2" shift 2 ;; --all) LIMIT=0 shift ;; --stats) SHOW_STATS=1 shift ;; -h|--help) usage ;; *) echo "Unknown option: $1" >&2 usage ;; esac done # Validate mode case "$MODE" in t|terminal) MODE="terminal" ;; c|clipboard) MODE="clipboard" ;; s|search) MODE="search" ;; "") echo "Error: Mode (-m) is required" >&2 usage ;; *) echo "Error: Invalid mode '$MODE'" >&2 usage ;; esac # Convert date format YYYY.MM.DD to YYYY-MM-DD convert_date() { echo "$1" | tr '.' '-' } # Build WHERE clause build_where() { local conditions="" if [ -n "$DATE" ]; then DATE=$(convert_date "$DATE") conditions="date(timestamp) = '$DATE'" fi if [ -n "$FROM_DATE" ]; then FROM_DATE=$(convert_date "$FROM_DATE") [ -n "$conditions" ] && conditions="$conditions AND " conditions="${conditions}date(timestamp) >= '$FROM_DATE'" fi if [ -n "$TO_DATE" ]; then TO_DATE=$(convert_date "$TO_DATE") [ -n "$conditions" ] && conditions="$conditions AND " conditions="${conditions}date(timestamp) <= '$TO_DATE'" fi if [ -n "$QUERY" ]; then [ -n "$conditions" ] && conditions="$conditions AND " case "$MODE" in terminal) conditions="${conditions}command LIKE '%$QUERY%'" ;; clipboard) conditions="${conditions}content LIKE '%$QUERY%'" ;; search) conditions="${conditions}query LIKE '%$QUERY%'" ;; esac fi if [ -n "$BROWSER" ] && [ "$MODE" = "search" ]; then [ -n "$conditions" ] && conditions="$conditions AND " conditions="${conditions}browser = '$BROWSER'" fi [ -n "$conditions" ] && echo "WHERE $conditions" || echo "" } # Build LIMIT clause build_limit() { if [ "$LIMIT" -gt 0 ] 2>/dev/null; then echo "LIMIT $LIMIT" fi } # Show statistics show_stats() { case "$MODE" in terminal) echo "Terminal History Statistics" echo "============================" sql_query "SELECT 'Total commands: ' || COUNT(*) FROM terminal_history;" sql_query "SELECT 'Unique commands: ' || COUNT(DISTINCT command) FROM terminal_history;" sql_query "SELECT 'First entry: ' || MIN(timestamp) FROM terminal_history;" sql_query "SELECT 'Last entry: ' || MAX(timestamp) FROM terminal_history;" echo "" echo "Top 10 commands:" sql_query "SELECT ' ' || COUNT(*) || 'x ' || command FROM terminal_history GROUP BY command ORDER BY COUNT(*) DESC LIMIT 10;" ;; clipboard) echo "Clipboard History Statistics" echo "=============================" sql_query "SELECT 'Total entries: ' || COUNT(*) FROM clipboard_history;" sql_query "SELECT 'First entry: ' || MIN(timestamp) FROM clipboard_history;" sql_query "SELECT 'Last entry: ' || MAX(timestamp) FROM clipboard_history;" ;; search) echo "Search History Statistics" echo "=========================" sql_query "SELECT 'Total searches: ' || COUNT(*) FROM search_history;" sql_query "SELECT 'First entry: ' || MIN(timestamp) FROM search_history;" sql_query "SELECT 'Last entry: ' || MAX(timestamp) FROM search_history;" echo "" echo "By browser:" sql_query "SELECT ' ' || browser || ': ' || COUNT(*) FROM search_history GROUP BY browser ORDER BY COUNT(*) DESC;" echo "" echo "Top 10 searches:" sql_query "SELECT ' ' || COUNT(*) || 'x ' || query FROM search_history GROUP BY query ORDER BY COUNT(*) DESC LIMIT 10;" ;; esac } # Format output format_output() { local table="$1" local where="$2" local limit="$3" case "$MODE" in terminal) FIELDS="timestamp, command, exit_code, pwd" ;; clipboard) FIELDS="timestamp, substr(content, 1, 100) as content" ;; search) FIELDS="timestamp, browser, query" ;; esac SQL="SELECT $FIELDS FROM ${table}_history $where ORDER BY timestamp DESC $limit" case "$OUTPUT" in j|json) sql_json "$SQL" ;; t|table) sql_table "$SQL" ;; p|plain|*) case "$MODE" in terminal) sql_raw "$SQL" | while IFS='|' read -r ts cmd code pwd; do printf "${C_DIM}%s${C_RESET} ${C_GREEN}%s${C_RESET}" "$ts" "$cmd" [ -n "$code" ] && [ "$code" != "0" ] && printf " ${C_YELLOW}[%s]${C_RESET}" "$code" printf "\n" done ;; clipboard) sql_raw "$SQL" | while IFS='|' read -r ts content; do printf "${C_DIM}%s${C_RESET} %s\n" "$ts" "$content" done ;; search) sql_raw "$SQL" | while IFS='|' read -r ts browser query; do printf "${C_DIM}%s${C_RESET} ${C_MAGENTA}[%s]${C_RESET} %s\n" "$ts" "$browser" "$query" done ;; esac ;; esac } # Main if [ "$SHOW_STATS" -eq 1 ]; then show_stats else WHERE=$(build_where) LIMIT_CLAUSE=$(build_limit) format_output "$MODE" "$WHERE" "$LIMIT_CLAUSE" fi