if [[ -n "$_TIRITH_BASH_LOADED" ]]; then
if [[ "$(declare -p _TIRITH_BASH_LOADED 2>/dev/null)" =~ ^declare\ -[a-zA-Z]*x ]]; then
unset _TIRITH_BASH_LOADED else
return fi
fi
_TIRITH_BASH_LOADED=1
unset _TIRITH_PENDING_EVAL _TIRITH_PENDING_SOURCE
[[ "$(declare -p _TIRITH_TEST_SKIP_HEALTH 2>/dev/null)" =~ ^declare\ -[a-zA-Z]*x ]] && unset _TIRITH_TEST_SKIP_HEALTH
[[ "$(declare -p _TIRITH_TEST_FAIL_HEALTH 2>/dev/null)" =~ ^declare\ -[a-zA-Z]*x ]] && unset _TIRITH_TEST_FAIL_HEALTH
if [[ -z "${TIRITH_SESSION_ID:-}" ]]; then
TIRITH_SESSION_ID="$(printf '%x-%x' "$$" "$(date +%s)")"
export TIRITH_SESSION_ID
fi
if [[ -z "${TIRITH_SSH_REMOTE:-}" ]] \
&& { [[ -n "${SSH_CONNECTION:-}" ]] || [[ -n "${SSH_CLIENT:-}" ]] || [[ -n "${SSH_TTY:-}" ]]; }; then
TIRITH_SSH_REMOTE=1
export TIRITH_SSH_REMOTE
fi
if [[ $- == *i* ]]; then
command tirith env snapshot >/dev/null 2>&1 &
disown 2>/dev/null || true
fi
_tirith_output() {
if [[ "${TIRITH_OUTPUT:-}" == "tty" ]]; then
printf '%s\n' "$@" >/dev/tty
else
printf '%s\n' "$@" >&2
fi
}
_tirith_escape_preview() {
printf '%q' "$1"
}
_tirith_parse_approval() {
local file="$1"
_tirith_ap_required="no"
_tirith_ap_timeout=0
_tirith_ap_fallback="block"
_tirith_ap_rule=""
_tirith_ap_desc=""
if [[ ! -r "$file" ]]; then
_tirith_output "tirith: warning: approval file missing or unreadable, failing closed"
command rm -f "$file" _tirith_ap_required="yes"
_tirith_ap_fallback="block"
_tirith_ap_timeout=0
return 1
fi
local valid_keys=0
while IFS='=' read -r key value; do
case "$key" in
TIRITH_REQUIRES_APPROVAL) _tirith_ap_required="$value"; valid_keys=$((valid_keys + 1)) ;;
TIRITH_APPROVAL_TIMEOUT) _tirith_ap_timeout="$value" ;;
TIRITH_APPROVAL_FALLBACK) _tirith_ap_fallback="$value" ;;
TIRITH_APPROVAL_RULE) _tirith_ap_rule="$value" ;;
TIRITH_APPROVAL_DESCRIPTION) _tirith_ap_desc="$value" ;;
esac
done < "$file"
command rm -f "$file"
if [[ $valid_keys -eq 0 ]]; then
_tirith_output "tirith: warning: approval file corrupt, failing closed"
_tirith_ap_required="yes"
_tirith_ap_fallback="block"
_tirith_ap_timeout=0
return 1
fi
return 0
}
_tirith_parse_warn_ack() {
local file="$1"
_tirith_wa_findings=0
_tirith_wa_max_severity=""
if [[ ! -r "$file" ]]; then
command rm -f "$file"
return 1
fi
while IFS='=' read -r key value; do
case "$key" in
TIRITH_WARN_ACK_FINDINGS) _tirith_wa_findings="$value" ;;
TIRITH_WARN_ACK_MAX_SEVERITY) _tirith_wa_max_severity="$value" ;;
esac
done < "$file"
command rm -f "$file"
return 0
}
_TIRITH_STATE_DIR="${XDG_STATE_HOME:-}"
_TIRITH_STATE_DIR="${_TIRITH_STATE_DIR#"${_TIRITH_STATE_DIR%%[![:space:]]*}"}"
_TIRITH_STATE_DIR="${_TIRITH_STATE_DIR%"${_TIRITH_STATE_DIR##*[![:space:]]}"}"
_TIRITH_STATE_DIR="${_TIRITH_STATE_DIR:-$HOME/.local/state}/tirith"
_TIRITH_SAFE_MODE_FLAG="$_TIRITH_STATE_DIR/bash-safe-mode"
_tirith_check_safe_mode() { [[ -f "$_TIRITH_SAFE_MODE_FLAG" ]]; }
_tirith_persist_safe_mode() {
if ! mkdir -p "$_TIRITH_STATE_DIR" 2>/dev/null || ! printf '1\n' > "$_TIRITH_SAFE_MODE_FLAG" 2>/dev/null; then
echo "tirith: warning: could not persist safe-mode flag" >&2
fi
}
_TIRITH_ENTER_CAP_SCHEMA=1
_TIRITH_ENTER_CAP_FILE="$_TIRITH_STATE_DIR/bash-enter-capability"
_tirith_enter_capability_proven() {
[[ -r "$_TIRITH_ENTER_CAP_FILE" ]] || return 1
local size
size="$(wc -c < "$_TIRITH_ENTER_CAP_FILE" 2>/dev/null)" || return 1
size="${size//[^0-9]/}"
[[ -n "$size" ]] || return 1
(( size > 4096 )) && return 1
local schema="" cache_bash_version="" cache_bash_path="" capability=""
local key value
while IFS='=' read -r key value; do
case "$key" in
schema) schema="$value" ;;
bash_version) cache_bash_version="$value" ;;
bash_path) cache_bash_path="$value" ;;
enter_capability) capability="$value" ;;
esac
done < "$_TIRITH_ENTER_CAP_FILE"
[[ "$schema" == "$_TIRITH_ENTER_CAP_SCHEMA" ]] || return 1
[[ "$capability" == "works" ]] || return 1
[[ -n "$cache_bash_version" ]] || return 1
[[ "$cache_bash_version" == "$BASH_VERSION" ]] || return 1
[[ -n "$cache_bash_path" ]] || return 1
[[ "$cache_bash_path" == "${BASH:-}" ]] || return 1
return 0
}
_tirith_read_history_entry() {
local HISTTIMEFORMAT=''
local raw
raw="$(builtin history 1 2>/dev/null)" || return 1
[[ -z "$raw" ]] && return 1
if [[ "$raw" =~ ^[[:space:]]*([0-9]+)[[:space:]]+(.*)$ ]]; then
printf '%s|%s\n' "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}"
return 0
fi
return 1
}
_tirith_normalize_spacing() {
local s="$1"
s="$(printf '%s' "$s" | tr -s '[:space:]' ' ')"
s="${s# }"
s="${s% }"
local op
for op in '|' '&' ';' '>' '<'; do
while [[ "$s" == *" $op"* ]]; do s="${s//" $op"/$op}"; done
while [[ "$s" == *"$op "* ]]; do s="${s//"$op "/$op}"; done
done
printf '%s' "$s"
}
_tirith_regex_escape() {
local s="$1" out="" i c
for ((i=0; i<${#s}; i++)); do
c="${s:i:1}"
case "$c" in
'\'|'.'|'*'|'+'|'?'|'|'|'('|')'|'['|']'|'{'|'}'|'^'|'$')
out+='\'"$c" ;;
*)
out+="$c" ;;
esac
done
printf '%s' "$out"
}
_tirith_cmd_is_in_line() {
local needle="$1" haystack="$2"
[[ -z "$needle" || -z "$haystack" ]] && return 1
[[ "$haystack" == "$needle" ]] && return 0
local esc boundary
boundary='(^|[[:space:]|&;<>()])'
esc="$(_tirith_regex_escape "$needle")"
if [[ "$haystack" =~ ${boundary}${esc}([[:space:]|&\;<>()]|$) ]]; then
return 0
fi
local n_needle n_haystack
n_needle="$(_tirith_normalize_spacing "$needle")"
n_haystack="$(_tirith_normalize_spacing "$haystack")"
[[ "$n_haystack" == "$n_needle" ]] && return 0
esc="$(_tirith_regex_escape "$n_needle")"
if [[ "$n_haystack" =~ ${boundary}${esc}([[:space:]|&\;<>()]|$) ]]; then
return 0
fi
local first_token="${needle%%[[:space:]]*}"
[[ -z "$first_token" ]] && return 1
esc="$(_tirith_regex_escape "$first_token")"
if [[ "$haystack" =~ ${boundary}${esc}([[:space:]|&\;<>()]|$) ]]; then
return 0
fi
return 1
}
_tirith_history_is_trustworthy_for_enforcement() {
case ":${HISTCONTROL:-}:" in
*:ignorespace:*|*:ignoredups:*|*:ignoreboth:*) return 1 ;;
esac
[[ -n "${HISTIGNORE:-}" ]] && return 1
if ! shopt -oq history 2>/dev/null; then
return 1
fi
return 0
}
_tirith_enable_extdebug() {
if shopt -q extdebug; then
return 0
fi
shopt -s extdebug
_TIRITH_OWNS_EXTDEBUG=1
}
_tirith_debug_trampoline() {
local _user_line_id="${BASH_LINENO[0]:-0}"
if [[ -n "${_TIRITH_PREV_DEBUG_TRAP:-}" ]]; then
eval "$_TIRITH_PREV_DEBUG_TRAP" || true
fi
_tirith_preexec "$_user_line_id"
}
_tirith_install_debug_trap() {
local current
current="$(trap -p DEBUG 2>/dev/null)"
[[ "$current" == *"_tirith_debug_trampoline"* ]] && return 0
_TIRITH_PREV_DEBUG_TRAP="$(trap -p DEBUG 2>/dev/null | sed "s/^trap -- '//;s/' DEBUG\$//")"
trap '_tirith_debug_trampoline' DEBUG
}
_tirith_set_status() {
TIRITH_STATUS="$1"
}
_tirith_warn_degraded_once() {
[[ -n "${_TIRITH_DEGRADE_WARNED:-}" ]] && return 0
_TIRITH_DEGRADE_WARNED=1
[[ $- == *i* ]] || return 0
_tirith_output "tirith: protection downgraded to warn-only (does not block) — run 'tirith doctor' for details"
[[ -n "${1:-}" ]] && _tirith_output " $1"
return 0
}
_tirith_session_degrade_to_warn_only() {
local reason="$1"
_TIRITH_PREEXEC_ENFORCE=0
_TIRITH_WARN_ONLY_USE_BASH_COMMAND=1
_TIRITH_PREEXEC_WARNED=1 export TIRITH_BASH_EFFECTIVE_PROTECTION="warn-only"
_tirith_set_status "degraded"
_tirith_warn_degraded_once "$reason"
}
_tirith_preexec() {
[[ "${_TIRITH_BASH_INTERNAL:-0}" == "1" ]] && return 0
if [[ -z "${_TIRITH_PREEXEC_WARNED:-}" ]] \
&& [[ $- == *i* ]] \
&& [[ "${_TIRITH_PREEXEC_ENFORCE:-0}" != "1" ]]; then
_TIRITH_PREEXEC_WARNED=1
_tirith_output "tirith: bash is in preexec mode (warn-only, does not block)"
_tirith_output " Run 'tirith doctor' to test enter mode (blocking) for this shell"
fi
local bash_cmd="$BASH_COMMAND"
local entry history_index="" history_line=""
if entry="$(_tirith_read_history_entry)"; then
history_index="${entry%%|*}"
history_line="${entry#*|}"
fi
local line_id="${1:-${BASH_LINENO[${#BASH_LINENO[@]}-1]:-0}}"
local _tirith_prev_internal="${_TIRITH_BASH_INTERNAL:-0}"
local rc
if [[ "${_TIRITH_PREEXEC_ENFORCE:-0}" == "1" ]]; then
if [[ -z "$history_index" ]]; then
_tirith_session_degrade_to_warn_only \
"tirith: bash history is unavailable in this shell (history disabled or buffer empty), cannot enforce whole-line semantics; falling back to warn-only. For guaranteed blocking, use enter mode (export TIRITH_BASH_MODE=enter)."
return 1
fi
if ! _tirith_cmd_is_in_line "$bash_cmd" "$history_line"; then
_tirith_last_key="$line_id"
_tirith_last_rc=1
_tirith_session_degrade_to_warn_only \
"tirith: bash history no longer matches BASH_COMMAND (likely HISTCONTROL/HISTIGNORE filtering, an alias, or a shell transformation outside the whole-line drift check); cannot enforce whole-line semantics; falling back to warn-only. For guaranteed blocking, use enter mode (export TIRITH_BASH_MODE=enter)."
return 1
fi
if [[ "${_tirith_last_key:-}" == "$line_id" ]]; then
return "${_tirith_last_rc:-0}"
fi
_TIRITH_BASH_INTERNAL=1
command tirith check --shell posix -- "$history_line"
rc=$?
_TIRITH_BASH_INTERNAL="$_tirith_prev_internal"
case "$rc" in
0|2)
_tirith_last_key="$line_id"
_tirith_last_rc=0
return 0
;;
1)
_tirith_last_key="$line_id"
_tirith_last_rc=1
return 1
;;
*)
_tirith_last_key="$line_id"
_tirith_last_rc=1
_tirith_session_degrade_to_warn_only \
"tirith: preexec enforcement failed unexpectedly (exit $rc), blocking this command and disabling enforcement for this shell"
return 1
;;
esac
fi
if [[ "${_tirith_last_key:-}" == "$line_id" ]] \
&& [[ "${_tirith_last_rc:-}" == "1" ]]; then
return 1
fi
local scan_target
if [[ "${_TIRITH_WARN_ONLY_USE_BASH_COMMAND:-0}" == "1" ]]; then
scan_target="$bash_cmd"
elif [[ -n "$history_line" ]]; then
scan_target="$history_line"
else
scan_target="$bash_cmd"
fi
local dedupe_key="${line_id}|${scan_target}"
[[ "${_tirith_last_cmd:-}" == "$dedupe_key" ]] && return 0
_tirith_last_cmd="$dedupe_key"
_TIRITH_BASH_INTERNAL=1
command tirith check --shell posix --warn-only -- "$scan_target" || true
_TIRITH_BASH_INTERNAL="$_tirith_prev_internal"
return 0
}
_tirith_degrade_to_preexec() {
local reason="${1:-unknown}"
if [[ "${_TIRITH_BINDS_INSTALLED:-0}" == "1" ]]; then
bind '"\C-m": accept-line' 2>/dev/null || true
bind '"\C-j": accept-line' 2>/dev/null || true
bind '"\e[200~": bracketed-paste-begin' 2>/dev/null || bind -r '"\e[200~"' 2>/dev/null || true
_TIRITH_BINDS_INSTALLED=0
fi
_tirith_install_debug_trap
_TIRITH_BASH_MODE="preexec"
_tirith_persist_safe_mode
if [[ $- == *i* ]]; then
export TIRITH_BASH_EFFECTIVE_MODE="preexec"
export TIRITH_BASH_EFFECTIVE_PROTECTION="warn-only"
_tirith_set_status "degraded"
fi
_tirith_warn_degraded_once \
"enter mode failed ($reason); now warn-only. Persistent — restart your shell, or re-enable with TIRITH_BASH_MODE=enter."
}
_tirith_prompt_hook() {
local pending_eval="${_TIRITH_PENDING_EVAL:-}"
local pending_source="${_TIRITH_PENDING_SOURCE:-}"
unset _TIRITH_PENDING_EVAL _TIRITH_PENDING_SOURCE
if [[ -n "$pending_source" ]]; then
source "$pending_source"
command rm -f "$pending_source"
elif [[ -n "$pending_eval" ]]; then
eval -- "$pending_eval"
fi
}
_tirith_is_prompt_hook_attached() {
local pc_decl
pc_decl="$(declare -p PROMPT_COMMAND 2>/dev/null)" || return 1
if [[ "$pc_decl" == "declare -a"* ]]; then
local entry
for entry in "${PROMPT_COMMAND[@]}"; do
[[ "$entry" == "_tirith_prompt_hook" ]] && return 0
done
return 1
else
[[ "$PROMPT_COMMAND" =~ (^|;)[[:space:]]*_tirith_prompt_hook[[:space:]]*(;|$) ]] && return 0
return 1
fi
}
_tirith_ensure_prompt_hook() {
_tirith_is_prompt_hook_attached && return 0
local pc_decl
pc_decl="$(declare -p PROMPT_COMMAND 2>/dev/null)" || pc_decl=""
if [[ "$pc_decl" == "declare -a"* ]]; then
PROMPT_COMMAND=(_tirith_prompt_hook "${PROMPT_COMMAND[@]}") 2>/dev/null || return 1
elif [[ -n "${PROMPT_COMMAND:-}" ]]; then
PROMPT_COMMAND="_tirith_prompt_hook;${PROMPT_COMMAND}" 2>/dev/null || return 1
else
PROMPT_COMMAND="_tirith_prompt_hook" 2>/dev/null || return 1
fi
return 0
}
if [[ -n "${TIRITH_BASH_MODE:-}" ]]; then
_TIRITH_BASH_MODE="$TIRITH_BASH_MODE"
elif _tirith_check_safe_mode; then
_TIRITH_BASH_MODE="preexec"
[[ $- == *i* ]] && _tirith_output "tirith: safe mode active (preexec) — previous enter-mode failure detected"
[[ $- == *i* ]] && _tirith_output " Re-enable: TIRITH_BASH_MODE=enter or tirith doctor --reset-bash-safe-mode"
elif [[ -n "${SSH_CONNECTION:-}" || -n "${SSH_TTY:-}" || -n "${SSH_CLIENT:-}" ]]; then
_TIRITH_BASH_MODE="preexec"
elif _tirith_enter_capability_proven; then
_TIRITH_BASH_MODE="enter"
else
_TIRITH_BASH_MODE="preexec"
fi
if [[ $- == *i* ]]; then
export TIRITH_BASH_EFFECTIVE_MODE="$_TIRITH_BASH_MODE"
if [[ "$_TIRITH_BASH_MODE" == "enter" ]]; then
export TIRITH_BASH_EFFECTIVE_PROTECTION="blocks"
_tirith_set_status "blocks"
else
export TIRITH_BASH_EFFECTIVE_PROTECTION="warn-only"
_tirith_set_status "warn-only"
fi
fi
_TIRITH_PREEXEC_ENFORCE=0
_TIRITH_OWNS_EXTDEBUG=0
_TIRITH_WARN_ONLY_USE_BASH_COMMAND=0
_tirith_env_is_truthy() {
case "${1:-}" in
1|true|TRUE|True|yes|YES|Yes|on|ON|On) return 0 ;;
esac
return 1
}
if [[ "$_TIRITH_BASH_MODE" == "preexec" ]] \
&& [[ $- == *i* ]] \
&& _tirith_env_is_truthy "${TIRITH_BASH_PREEXEC_ENFORCE:-}"; then
if _tirith_history_is_trustworthy_for_enforcement; then
_TIRITH_PREEXEC_ENFORCE=1
_tirith_enable_extdebug
export TIRITH_BASH_EFFECTIVE_PROTECTION="blocks"
_tirith_set_status "blocks"
else
_TIRITH_WARN_ONLY_USE_BASH_COMMAND=1
_tirith_set_status "degraded"
_tirith_warn_degraded_once \
"preexec enforcement could not engage (HISTCONTROL/HISTIGNORE or disabled history prevents a trustworthy whole-line view). For guaranteed blocking, use enter mode (export TIRITH_BASH_MODE=enter)."
fi
fi
_tirith_unsafe_to_eval() {
local cmd="$1"
if [[ "$cmd" == *$'\n'* ]]; then
return 0
fi
if [[ "$cmd" == *'\' ]]; then
return 0
fi
if [[ "$cmd" == *'<<'* ]]; then
return 0
fi
local keywords='(^|[;&| ])(\{|\}|function |case |select |for |while |until |coproc )'
if [[ "$cmd" =~ $keywords ]]; then
return 0
fi
if [[ "$cmd" == *'; do'* ]] || [[ "$cmd" == *'; then'* ]]; then
return 0
fi
if [[ "$cmd" == *'( '* ]] || [[ "$cmd" == *' )'* ]]; then
return 0
fi
return 1
}
_tirith_startup_health_check() {
[[ "${_TIRITH_TEST_SKIP_HEALTH:-}" == "1" ]] && return 0
[[ "${_TIRITH_TEST_FAIL_HEALTH:-}" == "1" ]] && return 1
local binds
binds="$(bind -X 2>/dev/null)" || return 1
[[ "$binds" =~ \\C-m.*_tirith_enter ]] || return 1
[[ "$binds" =~ \\C-j.*_tirith_enter ]] || return 1
_tirith_is_prompt_hook_attached || return 1
return 0
}
if [[ "$_TIRITH_BASH_MODE" == "enter" ]] && [[ $- == *i* ]]; then
_TIRITH_BINDS_INSTALLED=0
if ! _tirith_ensure_prompt_hook; then
_tirith_degrade_to_preexec "PROMPT_COMMAND is readonly or unattachable"
else
_tirith_enter() {
local _saved_stty
_saved_stty=$(stty -g 2>/dev/null) || true
trap 'stty "$_saved_stty" 2>/dev/null || true' RETURN
if ! _tirith_ensure_prompt_hook; then
_tirith_degrade_to_preexec "PROMPT_COMMAND reattachment failed"
return fi
if [[ -n "${_TIRITH_PENDING_EVAL:-}" || -n "${_TIRITH_PENDING_SOURCE:-}" ]]; then
[[ -n "${_TIRITH_PENDING_SOURCE:-}" ]] && command rm -f "${_TIRITH_PENDING_SOURCE}"
unset _TIRITH_PENDING_EVAL _TIRITH_PENDING_SOURCE
_tirith_degrade_to_preexec "previous command not delivered (check shell history)"
return fi
if [[ -z "$READLINE_LINE" ]]; then
READLINE_LINE=""
READLINE_POINT=0
return
fi
local syntax_err
syntax_err=$(bash -n <<< "$READLINE_LINE" 2>&1)
local syntax_rc=$?
if [[ $syntax_rc -ne 0 ]] && [[ "$syntax_err" == *"unexpected EOF"* || "$syntax_err" == *"unexpected end of file"* ]]; then
READLINE_LINE+=$'\n'
READLINE_POINT=${#READLINE_LINE}
return
fi
local errfile=$(mktemp)
local approval_path
local _tirith_prev_internal="${_TIRITH_BASH_INTERNAL:-0}"
_TIRITH_BASH_INTERNAL=1
approval_path=$(command tirith check --approval-check --non-interactive --interactive --shell posix -- "$READLINE_LINE" 2>"$errfile")
local rc=$?
_TIRITH_BASH_INTERNAL="$_tirith_prev_internal"
local output=$(<"$errfile")
command rm -f "$errfile"
local warn_ack_path=""
if [[ $rc -eq 3 ]]; then
local _first_line _rest
IFS=$'\n' read -r _first_line <<< "$approval_path"
_rest="${approval_path#*$'\n'}"
if [[ "$_rest" != "$approval_path" ]]; then
warn_ack_path="$_rest"
fi
approval_path="$_first_line"
fi
if [[ $rc -eq 0 ]]; then
: elif [[ $rc -eq 2 || $rc -eq 3 ]]; then
local escaped_line
escaped_line=$(_tirith_escape_preview "$READLINE_LINE")
_tirith_output ""
_tirith_output "command> $escaped_line"
[[ -n "$output" ]] && _tirith_output "$output"
elif [[ $rc -eq 1 ]]; then
local escaped_line
escaped_line=$(_tirith_escape_preview "$READLINE_LINE")
_tirith_output ""
_tirith_output "command> $escaped_line"
[[ -n "$output" ]] && _tirith_output "$output"
else
local escaped_line
escaped_line=$(_tirith_escape_preview "$READLINE_LINE")
_tirith_output ""
_tirith_output "command> $escaped_line"
[[ -n "$output" ]] && _tirith_output "$output"
[[ -n "$approval_path" ]] && command rm -f "$approval_path"
[[ -n "$warn_ack_path" ]] && command rm -f "$warn_ack_path"
_tirith_degrade_to_preexec "tirith returned unexpected exit code $rc"
return fi
if [[ -n "$approval_path" ]]; then
_tirith_parse_approval "$approval_path"
if [[ "$_tirith_ap_required" == "yes" ]]; then
_tirith_output "tirith: approval required for $_tirith_ap_rule"
[[ -n "$_tirith_ap_desc" ]] && _tirith_output " $_tirith_ap_desc"
local response=""
if [[ "$_tirith_ap_timeout" -gt 0 ]]; then
read -t "$_tirith_ap_timeout" -p "Approve? (${_tirith_ap_timeout}s timeout) [y/N] " response </dev/tty 2>/dev/null
else
read -p "Approve? [y/N] " response </dev/tty 2>/dev/null
fi
if [[ "$response" == [yY]* ]]; then
: else
case "$_tirith_ap_fallback" in
allow)
_tirith_output "tirith: approval not granted — fallback: allow"
;;
warn)
_tirith_output "tirith: approval not granted — fallback: warn"
;;
*)
_tirith_output "tirith: approval not granted — fallback: block"
[[ -n "$warn_ack_path" ]] && command rm -f "$warn_ack_path"
READLINE_LINE=""
READLINE_POINT=0
return
;;
esac
fi
elif [[ $rc -eq 1 ]]; then
[[ -n "$warn_ack_path" ]] && command rm -f "$warn_ack_path"
READLINE_LINE=""
READLINE_POINT=0
return
fi
elif [[ $rc -eq 1 ]]; then
READLINE_LINE=""
READLINE_POINT=0
return
fi
if [[ $rc -eq 3 && -n "$warn_ack_path" ]]; then
_tirith_parse_warn_ack "$warn_ack_path"
local response=""
read -p "tirith: proceed with ${_tirith_wa_findings} warning(s)? [y/N] " response </dev/tty 2>/dev/null
if [[ "$response" == [yY]* ]]; then
: else
_tirith_output "tirith: warnings not acknowledged — command blocked"
READLINE_LINE=""
READLINE_POINT=0
return
fi
elif [[ -n "$warn_ack_path" ]]; then
command rm -f "$warn_ack_path"
fi
local cmd="$READLINE_LINE"
READLINE_LINE=""
READLINE_POINT=0
if _tirith_unsafe_to_eval "$cmd"; then
history -s -- "$cmd"
>&2 printf 'tirith: complex command — executing without block capability\n'
local tmpf
tmpf=$(mktemp "${TMPDIR:-/tmp}/tirith.XXXXXX") || {
_TIRITH_PENDING_EVAL="$cmd"
return
}
printf '%s\n' "$cmd" > "$tmpf"
_TIRITH_PENDING_SOURCE="$tmpf"
return
fi
history -s -- "$cmd"
_TIRITH_PENDING_EVAL="$cmd"
}
_tirith_paste() {
local _saved_stty
_saved_stty=$(stty -g 2>/dev/null) || true
trap 'stty "$_saved_stty" 2>/dev/null || true' RETURN
local pasted=""
local char
while IFS= read -r -n 1 -d '' -t 1 char; do
pasted+="$char"
if [[ "$pasted" == *$'\e[201~' ]]; then
pasted="${pasted%$'\e[201~'}"
break
fi
done
if [[ -n "$pasted" ]]; then
local tmpfile=$(mktemp)
local _tirith_prev_internal="${_TIRITH_BASH_INTERNAL:-0}"
_TIRITH_BASH_INTERNAL=1
printf '%s' "$pasted" | command tirith paste --shell posix --interactive >"$tmpfile" 2>&1
local rc=$?
_TIRITH_BASH_INTERNAL="$_tirith_prev_internal"
local output=$(<"$tmpfile")
command rm -f "$tmpfile"
if [[ $rc -eq 0 ]]; then
:
elif [[ $rc -eq 2 ]]; then
[[ -n "$output" ]] && { _tirith_output ""; _tirith_output "$output"; }
else
local escaped_paste
escaped_paste=$(_tirith_escape_preview "$pasted")
_tirith_output ""
_tirith_output "paste> $escaped_paste"
[[ -n "$output" ]] && _tirith_output "$output"
[[ $rc -ne 1 ]] && _tirith_output "tirith: paste check failed (exit code $rc)"
return
fi
fi
READLINE_LINE="${READLINE_LINE:0:$READLINE_POINT}${pasted}${READLINE_LINE:$READLINE_POINT}"
READLINE_POINT=$((READLINE_POINT + ${#pasted}))
}
bind -x '"\C-m": _tirith_enter' || true
bind -x '"\C-j": _tirith_enter' || true
bind -x '"\e[200~": _tirith_paste' || true
_TIRITH_BINDS_INSTALLED=1
if ! _tirith_startup_health_check; then
_tirith_degrade_to_preexec "startup health check failed (bind-x or PROMPT_COMMAND)"
fi
fi
fi
_tirith_exit_summary() {
[[ -n "${TIRITH_SESSION_ID:-}" ]] || return
local _sd="${XDG_STATE_HOME:-$HOME/.local/state}/tirith"
[[ -f "$_sd/sessions/$TIRITH_SESSION_ID.json" ]] || return
command tirith warnings --summary
}
_tirith_prev_exit_trap=$(trap -p EXIT 2>/dev/null | sed "s/^trap -- '//;s/' EXIT$//")
if [[ -n "$_tirith_prev_exit_trap" ]]; then
eval "trap '${_tirith_prev_exit_trap}; _tirith_exit_summary' EXIT"
else
trap '_tirith_exit_summary' EXIT
fi
unset _tirith_prev_exit_trap
if [[ "$_TIRITH_BASH_MODE" == "preexec" ]] && [[ $- == *i* ]]; then
_tirith_install_debug_trap
fi