#!/bin/sh
# 소리맥 Claude Code 훅 중계: 표준입력(훅 JSON)을 소리맥 실행 파일의 --agent-hook 에 그대로 넘긴다.
# 소리맥을 못 찾거나 실패해도 언제나 0 으로 끝난다(Claude 를 막지 않는다).
# 앱 번들은 Info.plist 에 SorimacAgentHook 표식이 있을 때만 쓴다. 표식이 없는 옛 버전은 --agent-hook 을 몰라
# 앱이 통째로 한 번 더 떠 버린다(두 번째 인스턴스 사고) — 그런 앱은 부르지 않는다.
app_exe() {
  plist="$1/Contents/Info.plist"
  [ -f "$plist" ] || return 1
  /usr/libexec/PlistBuddy -c "Print :SorimacAgentHook" "$plist" >/dev/null 2>&1 || return 1
  name=$(/usr/libexec/PlistBuddy -c "Print :CFBundleExecutable" "$plist" 2>/dev/null)
  [ -n "$name" ] && [ -x "$1/Contents/MacOS/$name" ] || return 1
  echo "$1/Contents/MacOS/$name"
}
find_exe() {
  if [ -n "$SORIMAC_BIN" ]; then
    [ -x "$SORIMAC_BIN" ] && echo "$SORIMAC_BIN"
    return   # 명시한 경로가 틀리면 다른 곳을 찾지 않는다(시험이 운영 앱을 건드리지 않게).
  fi
  for app in "/Applications/Sorimac.app" "$HOME/Applications/Sorimac.app"; do
    app_exe "$app" && return
  done
  mdfind "kMDItemCFBundleIdentifier == 'com.sorimac.neo'" 2>/dev/null | while IFS= read -r app; do
    app_exe "$app" && break
  done
}
# --post(PostToolUse, 도구마다 불림): 소리맥 소켓이 없으면 앱을 찾지도 않고 곧바로 끝낸다(지연 0에 가깝게).
if [ "$1" = "--post" ]; then
  sock="${SORIMAC_AGENT_SOCK:-$HOME/.config/sorimac-neo/agent.sock}"
  if [ ! -S "$sock" ]; then cat >/dev/null; exit 0; fi
  # 빠른 길: 실행 파일을 띄우지 않고 nc 로 소켓에 바로 묻는다(수 ms). 회신이 곧 훅 출력 JSON, 덧붙임이 없으면 {}.
  # 훅 입력 JSON 의 줄바꿈은 문자열 밖에만 있으므로(문자열 안은 \n 으로 이스케이프) 지워 한 줄로 보낸다.
  if [ -x /usr/bin/nc ]; then
    out=$( { printf '{"op":"posttool-hook","payload":'; tr -d '\n\r'; printf '}\n'; } | /usr/bin/nc -U -w 2 "$sock" 2>/dev/null )
    case "$out" in *hookSpecificOutput*) printf '%s\n' "$out" ;; esac
    exit 0
  fi
fi
# --prompt(UserPromptSubmit, 터미널에서 지시를 제출할 때마다): 앱에 넘기고, 앱이 준 소리 요약 지시
# (hookSpecificOutput.additionalContext)만 출력한다 — 이 훅의 표준출력은 Claude 문맥에 들어간다.
# 프롬프트를 바꾸거나 막지 않는다. 앱이 없으면 곧바로 끝.
if [ "$1" = "--prompt" ]; then
  sock="${SORIMAC_AGENT_SOCK:-$HOME/.config/sorimac-neo/agent.sock}"
  if [ ! -S "$sock" ]; then cat >/dev/null; exit 0; fi
  if [ -x /usr/bin/nc ]; then
    line=$( { printf '{"op":"prompt-hook","payload":'; tr -d '\n\r'; printf '}\n'; } )
    out=$(printf '%s\n' "$line" | /usr/bin/nc -U -w 2 "$sock" 2>/dev/null)
    case "$out" in
      *hookSpecificOutput*) printf '%s\n' "$out" ;;
      *"unknown op"*)   # 옛 앱: 예전 op 로 다시 넘기기만(출력 없음)
        printf '%s\n' "$line" | sed 's/^{"op":"prompt-hook"/{"op":"prompt"/' | /usr/bin/nc -U -w 1 "$sock" >/dev/null 2>&1 ;;
    esac
    exit 0
  fi
fi
exe=$(find_exe)
if [ -z "$exe" ]; then cat >/dev/null; exit 0; fi
# 소리맥 쪽 소켓 대기는 2초 상한. 혹시 멈춰도 8초 뒤 끊는다(훅 타임아웃 10초 안).
# --stop(Stop 훅): 카드로 대답하기 — 앱이 사용자 발화를 최대 30분(설정) 기다린다. 감시는 1825초(훅 timeout 1830 안).
# 앱이 없거나 표식 없는 앱이면 위에서 이미 0 종료, 앱이 기다리지 않으면 곧바로 회신이 와 바로 끝난다.
limit=8
[ "$1" = "--stop" ] && limit=1825
[ "$1" = "--post" ] && limit=3
[ "$1" = "--prompt" ] && limit=3
# 비대화형 sh 는 백그라운드 작업의 표준입력을 /dev/null 로 바꾼다 — 명시적으로 원래 입력을 넘긴다.
exec 3<&0
"$exe" --agent-hook <&3 &
pid=$!
( sleep "$limit"; kill "$pid" 2>/dev/null ) >/dev/null 2>&1 &
watcher=$!
wait "$pid" 2>/dev/null
kill "$watcher" 2>/dev/null
exit 0
