#!/usr/bin/env bash
# Auto-generated by nerftools: PreToolUse Bash hint dispatcher.
#
# When the agent invokes a Bash command, this hook checks the command against
# package-declared regex patterns. If any match, the hook emits a `deny`
# permission decision listing the relevant skill(s), redirecting the agent to
# the safety-constrained wrappers. The deny is silent to the user.
#
# Sentinel `# nerf:bypass <reason>` (non-empty reason) anywhere in the
# command allows the call through without matching. Empty reason still denies,
# with an explanation.
#
# Fail-open behavior: if bash < 4 or jq is missing, the hook exits 0 silently.
# A SessionStart hook surfaces a warning so the user knows the redirect is
# disabled.

# Fail open if bash is too old; the SessionStart companion warns the user.
if [[ "${BASH_VERSINFO[0]:-0}" -lt 4 ]]; then
  exit 0
fi

set -uo pipefail

_WRAPPER_PREFIX="nerf-"
_BRAND="nerf"
# Brand pre-escaped for regex use so a future prefix containing meta-chars
# (e.g. ".", "+") doesn't break the bypass sentinel match.
_BRAND_RE="nerf"

# Tab-separated <regex>\t<skill> rows, declaration order. \b boundaries in
# manifest patterns are translated to portable POSIX ERE at generation time
# so the hook works on macOS BSD regex (where \b is not supported).
_PATTERNS=(
  $'(^|$|[^[:alnum:]_])az account(^|$|[^[:alnum:]_])	nerf-az-account'
  $'(^|$|[^[:alnum:]_])az aks(^|$|[^[:alnum:]_])	nerf-az-aks'
  $'(^|$|[^[:alnum:]_])az boards(^|$|[^[:alnum:]_])	nerf-az-boards'
  $'(^|$|[^[:alnum:]_])az cosmosdb(^|$|[^[:alnum:]_])	nerf-az-cosmosdb'
  $'(^|$|[^[:alnum:]_])az devops(^|$|[^[:alnum:]_])	nerf-az-devops'
  $'(^|$|[^[:alnum:]_])az keyvault(^|$|[^[:alnum:]_])	nerf-az-keyvault'
  $'(^|$|[^[:alnum:]_])az monitor(^|$|[^[:alnum:]_])	nerf-az-monitor'
  $'(^|$|[^[:alnum:]_])az network(^|$|[^[:alnum:]_])	nerf-az-network'
  $'(^|$|[^[:alnum:]_])az pipelines(^|$|[^[:alnum:]_])	nerf-az-pipelines'
  $'(^|$|[^[:alnum:]_])az postgres(^|$|[^[:alnum:]_])	nerf-az-postgres'
  $'(^|$|[^[:alnum:]_])az repos(^|$|[^[:alnum:]_])	nerf-az-repos'
  $'(^|$|[^[:alnum:]_])az resource(^|$|[^[:alnum:]_])	nerf-az-resource'
  $'(^|$|[^[:alnum:]_])az group(^|$|[^[:alnum:]_])	nerf-az-resource'
  $'(^|$|[^[:alnum:]_])az role(^|$|[^[:alnum:]_])	nerf-az-role'
  $'(^|$|[^[:alnum:]_])az storage(^|$|[^[:alnum:]_])	nerf-az-storage'
  $'(^|$|[^[:alnum:]_])gh(^|$|[^[:alnum:]_])	nerf-gh'
  $'(^|$|[^[:alnum:]_])git(^|$|[^[:alnum:]_])	nerf-git'
  $'(^|$|[^[:alnum:]_])kubectl(^|$|[^[:alnum:]_])	nerf-kubectl'
  $'(^|$|[^[:alnum:]_])nx(^|$|[^[:alnum:]_])	nerf-nx'
  $'(^|$|[^[:alnum:]_])cspell(^|$|[^[:alnum:]_])	nerf-pkgrun'
  $'(^|$|[^[:alnum:]_])markdownlint(^|$|[^[:alnum:]_])	nerf-pkgrun'
  $'(^|$|[^[:alnum:]_])prettier(^|$|[^[:alnum:]_])	nerf-pkgrun'
  $'(^|$|[^[:alnum:]_])terraform(^|$|[^[:alnum:]_])	nerf-tf'
  $'(^|$|[^[:alnum:]_])terragrunt(^|$|[^[:alnum:]_])	nerf-tg'
  $'(^|$|[^[:alnum:]_])uv(^|$|[^[:alnum:]_])	nerf-uv'
)

# Fail open if jq is unavailable.
command -v jq >/dev/null 2>&1 || exit 0

_input=$(cat)
_tool=$(printf '%s' "$_input" | jq -r '.tool_name // empty' 2>/dev/null) || exit 0
[[ "$_tool" == "Bash" ]] || exit 0

_cmd=$(printf '%s' "$_input" | jq -r '.tool_input.command // empty' 2>/dev/null) || exit 0
[[ -n "$_cmd" ]] || exit 0

# Skip the redirect when the command is invoking a wrapper itself.
# We split on common shell separators (&&, ||, ;, |, &) into segments,
# skip any leading "VAR=val" env-var assignments in each segment, and
# check whether the first remaining token's basename starts with the
# wrapper prefix. This handles "cd /repo && nerf-git status", bash
# env-var prefixes ("FOO=bar nerf-git pull"), and absolute-path
# invocations like "/abs/path/nerf-git-add ." without skipping
# unrelated tokens that happen to contain the prefix at arg position
# (e.g. "git log --grep nerf-X"). Commands fronted by the POSIX `env`
# binary (or `nice`, `time`, `sudo`, etc.) are not specially recognized
# -- the runner is the executable; agents can use the bypass sentinel
# for that case.
if [[ -n "$_WRAPPER_PREFIX" ]]; then
  _norm="${_cmd//&&/$'\n'}"
  _norm="${_norm//||/$'\n'}"
  _norm="${_norm//[|;&]/$'\n'}"
  while IFS= read -r _seg; do
    _seg="${_seg#"${_seg%%[![:space:]]*}"}"
    [[ -z "$_seg" ]] && continue
    read -r -a _toks <<< "$_seg"
    _exec=""
    for _t in "${_toks[@]:-}"; do
      if [[ "$_t" =~ ^[A-Za-z_][A-Za-z0-9_]*= ]]; then
        continue
      fi
      _exec="$_t"
      break
    done
    [[ -z "$_exec" ]] && continue
    _base="${_exec##*/}"
    if [[ "$_base" == "$_WRAPPER_PREFIX"* ]]; then
      exit 0
    fi
  done <<< "$_norm"
fi

emit_deny() {
  jq -nc --arg r "$1" \
    '{hookSpecificOutput: {hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: $r}}'
}

# Bypass sentinel: "# <brand>:bypass <reason>" anywhere in the command.
# The phrase must be followed by at least one whitespace char and at
# least one non-whitespace char, so partial words like "bypassed" don't
# trigger and a bare marker with no reason isn't accepted. Malformed
# markers don't match here -- they fall through to the standard redirect
# below, whose deny message already documents the proper syntax.
_bypass_re="# ${_BRAND_RE}:bypass[[:space:]]+[^[:space:]]"
if [[ "$_cmd" =~ $_bypass_re ]]; then
  exit 0
fi

# Collect matching skills in declaration order, deduped.
_matched=()
for _row in "${_PATTERNS[@]:-}"; do
  _pat="${_row%%$'\t'*}"
  _skill="${_row##*$'\t'}"
  _already=0
  for _m in "${_matched[@]:-}"; do
    if [[ "$_m" == "$_skill" ]]; then _already=1; break; fi
  done
  [[ $_already -eq 1 ]] && continue
  if [[ "$_cmd" =~ $_pat ]]; then
    _matched+=("$_skill")
  fi
done

[[ ${#_matched[@]} -eq 0 ]] && exit 0

# Format skill list.
_list=""
for _s in "${_matched[@]}"; do
  if [[ -z "$_list" ]]; then
    _list="\`${_s}\`"
  else
    _list="${_list}, \`${_s}\`"
  fi
done

_msg="The following ${_BRAND} skill(s) may wrap this command: ${_list}.

Use one if it covers what you need. To run the command directly anyway, retry with a brief reason appended as \`# ${_BRAND}:bypass <one-line explanation>\`."

emit_deny "$_msg"
