#!/bin/bash
# Called from Claude Code / Codex CLI hooks to report the agent's working state to the terminal (both send the same hook JSON).
# kTerm 1.0.7 or later registers the copy bundled inside the app from Settings > Agent Hooks. To set it up by hand, register this script
# for each event (SessionStart / UserPromptSubmit / PreToolUse / PostToolUse / PermissionRequest / Notification / Stop / StopFailure /
# Interrupt / SessionEnd) in ~/.claude/settings.json for Claude Code, or ~/.codex/hooks.json for Codex CLI.
# In kTerm it sends one line, in the same form as the OSC body, to the per-session Unix domain socket in KTERM_ACTIVITY_SOCKET;
# in iTerm2 it hands off to the bundled cc-status. In any other terminal it does nothing.
# It writes to the socket rather than the terminal device, because writes from another process would interleave with
# Claude Code / Codex output on screen and corrupt it.

ITERM_CC_STATUS="/Applications/iTerm.app/Contents/Resources/utilities/cc-status"
KTERM_ACTIVITY_OSC=7777

input=$(cat)

case "$TERM_PROGRAM" in
  kTerm) ;;
  iTerm.app)
    [ -x "$ITERM_CC_STATUS" ] && printf '%s' "$input" | exec "$ITERM_CC_STATUS"
    exit 0
    ;;
  *) exit 0 ;;
esac

# Extract only the needed fields from the JSON without depending on jq. Newlines inside values are already escaped in JSON, so it is safe to join everything into one line
json=$(printf '%s' "$input" | tr -d '\n')
field() {
  printf '%s' "$json" | sed -n "s/.*\"$1\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\".*/\1/p"
}
event=$(field hook_event_name)
tool=$(field tool_name)
notification=$(field notification_type)

label=""
case "$event" in
  SessionStart | SessionEnd) state=off ;;
  UserPromptSubmit | PostToolUse) state=busy ;;
  PreToolUse)
    # Question and plan-approval tools wait for the user's answer, but no Notification arrives while waiting, so make it the user's turn here
    case "$tool" in
      AskUserQuestion | ExitPlanMode) state=attention ;;
      *) state=tool; label=$tool ;;
    esac
    ;;
  PermissionRequest) state=attention ;;
  Notification)
    # permission_prompt means waiting for permission; idle_prompt means the finished session has been left idle for a while. Either way it is the user's turn
    case "$notification" in
      permission_prompt | idle_prompt) state=attention ;;
      *) exit 0 ;;
    esac
    ;;
  Stop | Interrupt) state=done ;;
  StopFailure) state=error ;;
  *) exit 0 ;;
esac

socket_path=$KTERM_ACTIVITY_SOCKET
[ -S "$socket_path" ] || exit 0

# A hook started by a shared resident server (the Codex CLI app-server daemon) cannot tell which window called it.
# Its environment carries the socket of the window that first launched the server, so sending would show the state in another window.
# Send only when an ancestor process has a terminal (kTerm infers that window from its title and bell)
pid=$PPID
on_terminal=false
while [ "${pid:-0}" -gt 1 ]; do
  tty_name=$(ps -o tty= -p "$pid" 2>/dev/null | tr -d ' ')
  if [ -n "$tty_name" ] && [ "$tty_name" != "??" ]; then on_terminal=true; break; fi
  pid=$(ps -o ppid= -p "$pid" 2>/dev/null | tr -d ' ')
done
$on_terminal || exit 0

# Keep delimiters (; and newlines) out of the label
label=$(printf '%s' "$label" | tr -d ';\r\n')
printf '%s;activity;%s;%s\n' "$KTERM_ACTIVITY_OSC" "$state" "$label" | nc -U -w 1 "$socket_path" >/dev/null 2>&1
exit 0
