← back to Terminal Status

verification/install/before/.claude/skills/yellowdot/yellowdot.sh

95 lines

#!/usr/bin/env bash
# /yellowdot — mark a Claude session's iTerm2 tab YELLOW = "NEEDS DIRECTION — a question / decision fork is pending for Steve".
# A fixed SEMANTIC color (not /color's rotating golden-angle hue): yellow always means
# "this session finished its work and is holding." Lets Steve see at a glance, across a wall
# of tabs, which sessions are waiting on his ANSWER (yellow) vs gated in the viewer (purple) vs a terminal paste (orange) vs parked (pink) vs working (/color).
#
# Mechanism (identical to color.sh): the Claude Bash tool runs with NO controlling tty, so
# OSC escapes on stdout never reach the terminal. We resolve the `claude` process's own pty
# by walking process ancestry and write the OSC straight to /dev/ttysNNN. OSC color/title
# codes are non-destructive (no cursor moves) so this is glitch-safe.
#
# Usage:
#   yellowdot.sh              # yellow-dot THIS session (title "🟡 DIRECTION?")
#   yellowdot.sh "label"      # yellow tint + custom title text (🟡 prefixed)
#   yellowdot.sh --all        # yellow-dot EVERY live claude tab (rare)
#   yellowdot.sh --off        # clear the tint back to default on THIS session
set -u

# HARD RAIL (2026-09-08): a subagent has no tab of its own — its ancestry resolves to the
# PARENT session's pty, so painting would corrupt Steve's live pane (the input-field bug). No-op.
if [ -n "${CLAUDE_CODE_CHILD_SESSION:-}" ]; then
  echo "/yellowdot → subagent context — refusing to paint (would write to the parent session's live pane). no-op."
  exit 0
fi

# Semantic yellow (warm gold) — "needs direction". REPURPOSED 2026-09-02 (Steve: "where is color dot for direction"); idle/parked is now /pinkdot.
R=255; G=204; B=0
DOT="🟡"
# Smooth pulsate helper — ACTIVE state, so we pulse this color (Steve, 2026-09-04:
# "Pulsate the color whenever claude or codex is waiting for me or just running").
# Best-effort: if it's missing or the tty isn't writable, painting the solid color still succeeds.
PULSE="${HOME}/.claude/tab-dots/pulse.sh"

paint() { # $1 = tty_path, $2 = label(optional), $3 = off(1=reset)
  local tp="$1" label="${2:-}" off="${3:-0}"
  [[ -w "$tp" ]] || return 1
  if [[ "$off" == "1" ]]; then
    # Stop any active pulse FIRST (before the reset) so the pulse loop can't repaint after we clear.
    [[ -f "$PULSE" ]] && bash "$PULSE" --stop "$tp" >/dev/null 2>&1 || true
    { printf '\033]6;1;bg;*;default\007'; printf '\033]1337;SetBadgeFormat=\a'; } > "$tp" 2>/dev/null
    rm -f "${HOME}/.claude/tab-dots/$(basename "$tp").dot" 2>/dev/null
    return 0
  fi
  local title="$DOT ${label:-DIRECTION?}"
  {
    printf '\033]6;1;bg;red;brightness;%d\007'   "$R"
    printf '\033]6;1;bg;green;brightness;%d\007' "$G"
    printf '\033]6;1;bg;blue;brightness;%d\007'  "$B"
    printf '\033]1;%s\007' "$title"
    printf '\033]2;%s\007' "$title"
  } > "$tp" 2>/dev/null
  # Persist the dot so the 60s tab-ticket repainter PREFIXES it into the window title
  # (visible even when the tab bar is hidden), and show an in-window iTerm2 BADGE.
  mkdir -p "${HOME}/.claude/tab-dots"
  printf '%s' "$title" > "${HOME}/.claude/tab-dots/$(basename "$tp").dot"
  printf '\033]1337;SetBadgeFormat=%s\a' "$(printf '%s' "$title" | base64 | tr -d '\n')" > "$tp" 2>/dev/null
  # ACTIVE state → start a smooth pulse of this same color (pulse.sh's per-tty pidfile replaces
  # any existing pulse on this tty, so pulses never stack). Best-effort, never fatal.
  [[ -f "$PULSE" ]] && bash "$PULSE" "$R" "$G" "$B" "$tp" >/dev/null 2>&1 || true
}

# --all: paint every live claude tab yellow
if [[ "${1:-}" == "--all" ]]; then
  ttys="$(ps -axo tty=,command= 2>/dev/null \
    | awk '$1 ~ /^ttys/ && $0 ~ /claude/ && $0 !~ /grep/ {print $1}' | sort -u)"
  [[ -z "$ttys" ]] && { echo "/yellowdot --all → no live claude ttys"; exit 0; }
  n=0
  for tt in $ttys; do paint "/dev/$tt" "DIRECTION?" 0 && { echo "  $tt → 🟡 DIRECTION?"; n=$((n+1)); }; done
  echo "/yellowdot --all → marked ${n} tab(s) yellow (needs direction)"
  exit 0
fi

OFF=0; LABEL=""
[[ "${1:-}" == "--off" ]] && OFF=1 || LABEL="${1:-}"

# Resolve THIS session's controlling pty by walking ancestry (same as color.sh).
tty_path=""; p=$$
for _ in 1 2 3 4 5 6 7 8 9 10; do
  line="$(ps -o ppid=,tty= -p "$p" 2>/dev/null)" || break
  set -- $line
  pp="${1:-}"; tt="${2:-}"
  [[ -z "$pp" ]] && break
  if [[ "$tt" == ttys* ]]; then tty_path="/dev/$tt"; break; fi
  p="$pp"
done
if [[ -z "$tty_path" ]]; then
  echo "/yellowdot → could not resolve this session's tty (no ttysNNN in ancestry)"; exit 1
fi

if [[ "$OFF" == "1" ]]; then
  paint "$tty_path" "" 1 && echo "/yellowdot --off → cleared tint on $tty_path"
else
  paint "$tty_path" "$LABEL" 0 && echo "/yellowdot → $tty_path 🟡 ${LABEL:-DIRECTION?}  rgb(${R},${G},${B})"
fi