← back to Terminal Status

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

99 lines

#!/usr/bin/env bash
# /pinkdot — mark a Claude session's iTerm2 tab PINK = "idle / parked / nothing left to progress".
# A fixed SEMANTIC color (not /color's rotating golden-angle hue): pink always means
# "this session finished its work and is holding." Supersedes /yellowdot as the parked marker
# (Steve, 2026-09-02: "make parked terminals show pink dot"). Lets Steve see at a glance, across
# a wall of tabs, which sessions are done/parked (pink) vs actively working (their /color tint).
#
# Mechanism (identical to color.sh / yellowdot.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:
#   pinkdot.sh              # pink-dot THIS session (title "🩷 idle")
#   pinkdot.sh "label"      # pink tint + custom title text (🩷 prefixed)
#   pinkdot.sh --all        # pink-dot EVERY live claude tab (mark whole fleet parked)
#   pinkdot.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 "/pinkdot → subagent context — refusing to paint (would write to the parent session's live pane). no-op."
  exit 0
fi

# Semantic pink (hot pink) — reads clearly as "idle/parked" and is distinct from /color hues.
R=255; G=105; B=180
DOT="🩷"
# PARKED state stays SOLID — pinkdot never STARTS a pulse. But it must KILL any active-state
# pulse (green/orange/yellow/purple) inherited on this tty so parking reads as a steady dot
# (Steve, 2026-09-04). Best-effort: missing helper / unwritable tty never blocks the paint.
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
  # Parking → kill any active-state pulse on this tty BEFORE setting the solid color, so nothing
  # repaints over the steady pink. pinkdot stays SOLID and never starts a pulse of its own.
  [[ -f "$PULSE" ]] && bash "$PULSE" --stop "$tp" >/dev/null 2>&1 || true
  local title="$DOT ${label:-idle}"
  # NOTE: do NOT send a leading '*;default' reset here — on iTerm2 it can leave the tab
  # at default and swallow the color set (bug found 2026-09-02). Set RGB directly and write
  # BOTH title channels (]1; icon/tab, ]2; window) exactly like the proven /color path.
  {
    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
}

# --all: paint every live claude tab pink
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 "/pinkdot --all → no live claude ttys"; exit 0; }
  n=0
  for tt in $ttys; do paint "/dev/$tt" "idle" 0 && { echo "  $tt → 🩷 idle"; n=$((n+1)); }; done
  echo "/pinkdot --all → marked ${n} tab(s) pink (parked)"
  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 "/pinkdot → could not resolve this session's tty (no ttysNNN in ancestry)"; exit 1
fi

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