#!/bin/sh
# history - unified history viewer (encrypted with SQLCipher)
# Usage: history -m <mode> [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 <<EOF
history - unified history viewer (encrypted)
USAGE:
history -m <mode> [OPTIONS]
MODES (-m):
t, terminal Terminal command history
c, clipboard Clipboard history
s, search Browser search history
OPTIONS:
-o <format> Output format: t (table), j (json), p (plain) [default: p]
-n <count> Number of entries to show [default: 100, 0 for unlimited]
-d <date> Filter by date (YYYY.MM.DD)
-q <query> Search within entries
-b <browser> Filter by browser (search mode only)
--from <date> Start date (YYYY.MM.DD)
--to <date> 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