#!/bin/zsh
# check-accessibility-status — read-only inspector for the user's TCC
# Accessibility grant table. Useful before doing anything that depends on
# Accessibility, and after a reset to see what was wiped.
#
# Why this exists:
#   The 2026-04-28 incident showed that without quick visibility into who
#   currently has (and lacks) Accessibility, you can't quickly answer the
#   one question that matters during recovery: "which of my apps still
#   need to be re-authorized?"
#
#   This tool is read-only. It only does SELECTs against TCC.db and never
#   writes. Reading TCC.db requires the calling process to have Full Disk
#   Access; if you don't, the tool tells you cleanly and exits.
#
# Usage:
#   check-accessibility-status                  # human table
#   check-accessibility-status --json           # JSON for piping
#   check-accessibility-status --diff <list>    # mark which entries from
#                                                 stdin (one per line) are
#                                                 missing from the grant
#                                                 table (typical use:
#                                                 paste a recovery
#                                                 checklist of bundle ids
#                                                 / paths)
#
# Auth value legend (TCC.db enum):
#   0  denied          1  unknown      2  allowed (legacy)
#   3  allowed         4  limited

set -euo pipefail

USER_DB="$HOME/Library/Application Support/com.apple.TCC/TCC.db"
SYS_DB="/Library/Application Support/com.apple.TCC/TCC.db"
SVC=kTCCServiceAccessibility

mode=table
diff_input=""
while (( $# )); do
  case "$1" in
    --json) mode=json; shift ;;
    --diff)
      mode=diff
      if [[ $# -ge 2 && "$2" != --* ]]; then
        diff_input="$2"
        shift 2
      else
        diff_input="/dev/stdin"
        shift
      fi
      ;;
    -h|--help) sed -n '2,/^$/p' "$0" | sed 's/^# //; s/^#//'; exit 0 ;;
    *) echo "unknown arg: $1" >&2; exit 2 ;;
  esac
done

check_db_readable() {
  local db="$1"
  [[ -r "$db" ]] && /usr/bin/sqlite3 "$db" "SELECT 1;" >/dev/null 2>&1
}

sql_quote() {
  print -r -- "$1" | sed "s/'/''/g"
}

user_readable=false
sys_readable=false
if check_db_readable "$USER_DB"; then user_readable=true; fi
if check_db_readable "$SYS_DB"; then sys_readable=true; fi

if [[ "$user_readable" != true && "$sys_readable" != true ]]; then
  cat >&2 <<EOF
check-accessibility-status: cannot read user or system TCC.db

  TCC.db is readable only when the calling shell host has Full Disk
  Access. To grant it once for this terminal:

    System Settings → Privacy & Security → Full Disk Access
       → + add /Applications/<your-terminal>.app

  Without this, this tool can't see which apps are currently authorized.
EOF
  exit 3
fi

MAIN_DB="$USER_DB"
ATTACH_SQL=""
selects=()
if [[ "$user_readable" == true ]]; then
  selects+=("SELECT client, auth_value, datetime(last_modified,'unixepoch','localtime') AS modified, 'user' AS source FROM access WHERE service='$SVC'")
  if [[ "$sys_readable" == true ]]; then
    ATTACH_SQL="ATTACH DATABASE '$(sql_quote "$SYS_DB")' AS sys;"
    selects+=("SELECT client, auth_value, datetime(last_modified,'unixepoch','localtime') AS modified, 'system' AS source FROM sys.access WHERE service='$SVC'")
  fi
else
  MAIN_DB="$SYS_DB"
  selects+=("SELECT client, auth_value, datetime(last_modified,'unixepoch','localtime') AS modified, 'system' AS source FROM access WHERE service='$SVC'")
fi

QUERY="$ATTACH_SQL ${(@j: UNION ALL :)selects} ORDER BY modified DESC;"

case "$mode" in
  json)
    /usr/bin/sqlite3 -json "$MAIN_DB" "$QUERY"
    ;;
  diff)
    # Read candidate clients (one per line) and mark which are missing.
    granted="$(/usr/bin/sqlite3 "$MAIN_DB" "$QUERY" | cut -d'|' -f1 | sort -u)"
    while IFS= read -r line; do
      [[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && continue
      candidate="$(echo "$line" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')"
      if grep -Fxq "$candidate" <<< "$granted"; then
        printf '  ✓  %s\n' "$candidate"
      else
        printf '  ✗  %s\n' "$candidate"
      fi
    done <"$diff_input"
    ;;
  table)
    printf '%-70s %-12s %-8s %s\n' 'CLIENT' 'AUTH' 'SOURCE' 'LAST_MODIFIED'
    printf '%-70s %-12s %-8s %s\n' '------' '----' '------' '-------------'
    /usr/bin/sqlite3 "$MAIN_DB" "$QUERY" | while IFS='|' read -r client auth modified source; do
      case "$auth" in
        0) verdict='✗ denied'  ;;
        1) verdict='? unknown' ;;
        2) verdict='✓ allowed' ;;
        3) verdict='✓ allowed' ;;
        4) verdict='~ limited' ;;
        *) verdict="? auth=$auth" ;;
      esac
      printf '%-70s %-12s %-8s %s\n' "$client" "$verdict" "$source" "$modified"
    done
    count=$(/usr/bin/sqlite3 "$MAIN_DB" "$QUERY" | wc -l | tr -d ' ')
    echo ""
    echo "$count Accessibility grants across readable TCC.db files"
    ;;
esac
