#!/bin/mksh # Interactive mksh configuration # Source login profile if not already loaded [ -z "$EDITOR" ] && . "$HOME/.profile" # Vi mode set -o vi # Prompt: user@host:dir > PS1='$(print -n "\033[34m")${USER}$(print -n "\033[0m")@$(print -n "\033[35m")$(hostname -s)$(print -n "\033[0m"):$(print -n "\033[36m")${PWD/#$HOME/\~}$(print -n "\033[0m") > ' # History HISTFILE="$HOME/.mksh_history" HISTSIZE=10000 # Source config modules . "$HOME/.config/.mksh/load.sh" # Zoxide (smarter cd) command -v zoxide >/dev/null 2>&1 && eval "$(zoxide init posix --hook prompt)" # Direnv (bash hook works under mksh; there is no dedicated ksh hook) command -v direnv >/dev/null 2>&1 && eval "$(direnv hook bash)" # History logging (encrypted with SQLCipher) _kh_db="$HOME/.local/share/history/history.db" _kh_key_file="$HOME/.local/share/history/.key" _kh_log() { typeset cmd="$1" exit_code="$2" pwd_path="$PWD" [ -z "$cmd" ] && return case "$cmd" in history*) return ;; esac [ -f "$_kh_key_file" ] || return [ -f "$_kh_db" ] || return # backslash-escape command names: interactive mksh expands aliases # inside command substitution, and the modules alias cat/sed/etc. typeset escaped_cmd=$(print -r -- "$cmd" | \sed "s/'/''/g") typeset escaped_pwd=$(print -r -- "$pwd_path" | \sed "s/'/''/g") typeset key=$(\cat "$_kh_key_file") (\sqlcipher "$_kh_db" "PRAGMA key = '$key'; INSERT INTO terminal_history (command, exit_code, pwd, shell) VALUES ('$escaped_cmd', $exit_code, '$escaped_pwd', 'mksh');" &) >/dev/null 2>&1 } # Command history logging hook. if [ -n "$BASH_VERSION" ]; then # bash: DEBUG trap captures the command, PROMPT_COMMAND logs it _kh_last_cmd="" trap '_kh_last_cmd=$(fc -ln -1 2>/dev/null | sed "s/^[[:space:]]*//")' DEBUG PROMPT_COMMAND='_kh_log "$_kh_last_cmd" "$?"' else # mksh: no DEBUG trap / PROMPT_COMMAND, and PS1 command substitution # runs in a subshell, so hook state cannot live in shell variables -- # it is persisted to a per-PID file instead. We detect that a new # command ran by watching HISTFILE grow, then read the last entry # straight from the file (fc in the PS1 subshell is lagged by one and # would drop the final command of every session). # # mksh HISTFILE record layout: 0xFF, 4-byte big-endian counter, # command bytes, NUL. So split on 0xFF, take the last record, drop the # 4 counter bytes (cut -b5-), strip the trailing NUL. [ -d "$HOME/.cache" ] || mkdir -p "$HOME/.cache" _kh_state="$HOME/.cache/kh_mksh_$$" _kh_prompt() { typeset ec="$1" sz prev cmd [ -f "$HISTFILE" ] || return # backslash-escapes bypass the module aliases (wc=tokei, tr=..., cat=bat) sz=$(\wc -c < "$HISTFILE" 2>/dev/null) prev=$(\cat "$_kh_state" 2>/dev/null) print -r -- "$sz" > "$_kh_state" # first prompt of the session: record baseline, do not log [ -z "$prev" ] && return # only log when a new command was appended to history [ "$sz" -gt "$prev" ] 2>/dev/null || return cmd=$(\tail -c 4096 "$HISTFILE" | \tr "\377" "\n" | \tail -n1 | \cut -b5- | \tr -d "\0") _kh_log "$cmd" "$ec" } # leading $(...) captures $? first so the exit code is accurate; the # hook prints nothing so the visible prompt is unchanged PS1='$(_kh_prompt "$?")'"$PS1" trap 'rm -f "$_kh_state"' EXIT fi