Skip to content

Instantly share code, notes, and snippets.

@SippieCup
Last active March 30, 2026 02:36
Show Gist options
  • Select an option

  • Save SippieCup/0cd256789b6350196fc34c6d0ac09871 to your computer and use it in GitHub Desktop.

Select an option

Save SippieCup/0cd256789b6350196fc34c6d0ac09871 to your computer and use it in GitHub Desktop.
My claude status line.
#!/bin/bash
# PreToolUse hook — Claude is actively processing; mark session as running.
# Reads the hook JSON from stdin, extracts session_id, and writes a state file.
# Also stores the project_dir so the statusline can identify agent team peers.
input=$(cat)
session_id=$(echo "$input" | jq -r '.session_id // "default"' 2>/dev/null | tr -cd '[:alnum:]-_')
[ -z "$session_id" ] && session_id="default"
project_dir=$(echo "$input" | jq -r '.workspace.project_dir // ""' 2>/dev/null)
mkdir -p /tmp/claude
printf '{"status":"running","idle_since":0,"project_dir":"%s"}' "$project_dir" \
> "/tmp/claude/activity-state-${session_id}.json"
#!/bin/bash
# Line 1: Model | tokens used/total | % used <fullused> | % remain <fullremain> | thinking: on/off | running/idle since HH:MM
# Line 2: current: <progressbar> % | weekly: <progressbar> % | extra: <progressbar> $used/$limit
# Line 3: resets <time> | resets <datetime> | resets <date>
#
# Dot coloring logic:
# Filled dots reflect pace: blue=under pace, green=within 20% above pace,
# yellow=20-50% above pace, red=50%+ above pace
# Empty dots reflect absolute remaining: dim (green), orange, yellow, red
set -f # disable globbing
input=$(cat)
if [ -z "$input" ]; then
printf "Claude"
exit 0
fi
# ANSI colors matching oh-my-posh theme
blue='\033[38;2;0;153;255m'
orange='\033[38;2;255;176;85m'
green='\033[38;2;0;160;0m'
cyan='\033[38;2;46;149;153m'
red='\033[38;2;255;85;85m'
yellow='\033[38;2;230;200;0m'
white='\033[38;2;220;220;220m'
dim='\033[2m'
reset='\033[0m'
# Format token counts (e.g., 50k / 200k)
format_tokens() {
local num=$1
if [ "$num" -ge 1000000 ]; then
awk "BEGIN {printf \"%.1fm\", $num / 1000000}"
elif [ "$num" -ge 1000 ]; then
awk "BEGIN {printf \"%.0fk\", $num / 1000}"
else
printf "%d" "$num"
fi
}
# Format number with commas (e.g., 134,938)
format_commas() {
printf "%'d" "$1"
}
# Build a colored progress bar with pace-aware dot coloring.
# Filled dots reflect pace vs usage; empty dots reflect absolute remaining.
#
# Usage: build_bar <pct> <width> [pace_pct]
# pct — actual usage percentage (0-100)
# width — total number of dots
# pace_pct — expected usage percentage at this point in the window (optional)
# If omitted, falls back to legacy absolute coloring for filled dots.
#
# Filled dot colors (pace-aware):
# blue — usage < pace (under-pacing, healthy buffer)
# green — usage within 20% above pace (on track)
# yellow — usage 20-50% above pace (outpacing)
# red — usage 50%+ above pace (significantly outpacing)
#
# Empty dot colors (absolute remaining — same as before):
# dim — under 50% used
# orange — 50-69% used
# yellow — 70-89% used
# red — 90%+ used
build_bar() {
local pct=$1
local width=$2
local pace_pct="${3:-}"
[ "$pct" -lt 0 ] 2>/dev/null && pct=0
[ "$pct" -gt 100 ] 2>/dev/null && pct=100
local filled=$(( pct * width / 100 ))
local empty=$(( width - filled ))
# --- Filled dot color: pace-based (or legacy absolute if no pace given) ---
local filled_color
if [ -n "$pace_pct" ] && [ "$pace_pct" -ge 0 ] 2>/dev/null; then
# How far above/below pace are we?
# above_pace_pct = usage - pace (positive = outpacing)
local above_pace=$(( pct - pace_pct ))
if [ "$above_pace" -lt 0 ]; then
# Under pace — solid blue
filled_color="$blue"
elif [ "$above_pace" -le 20 ]; then
# Within 20% above pace — solid green
filled_color="$green"
elif [ "$above_pace" -le 50 ]; then
# 20-50% above pace — solid yellow
filled_color="$yellow"
else
# 50%+ above pace — solid red
filled_color="$red"
fi
else
# Legacy: color based on absolute usage
if [ "$pct" -ge 90 ]; then filled_color="$red"
elif [ "$pct" -ge 70 ]; then filled_color="$yellow"
elif [ "$pct" -ge 50 ]; then filled_color="$orange"
else filled_color="$green"
fi
fi
# --- Empty dot color: absolute remaining (warning scale) ---
local empty_color
if [ "$pct" -ge 90 ]; then empty_color="$red"
elif [ "$pct" -ge 70 ]; then empty_color="$yellow"
elif [ "$pct" -ge 50 ]; then empty_color="$orange"
else empty_color="$dim"
fi
local filled_str="" empty_str=""
for ((i=0; i<filled; i++)); do filled_str+="●"; done
for ((i=0; i<empty; i++)); do empty_str+="○"; done
printf "${filled_color}${filled_str}${empty_color}${empty_str}${reset}"
}
# ===== Extract data from JSON =====
model_name=$(echo "$input" | jq -r '.model.display_name // "Claude"')
cwd_raw=$(echo "$input" | jq -r '.cwd // ""')
# Build "parent/cwd" display from the current working directory
# Replaces $HOME prefix with ~, then shows the last two path components
build_pwd_display() {
local dir="$1"
# Replace home dir with ~
dir="${dir/#$HOME/\~}"
# Split into components
local parent base
base="${dir##*/}"
parent="${dir%/*}"
# If parent is empty or the same as dir (root or single component), just show dir
if [ -z "$parent" ] || [ "$parent" = "$dir" ]; then
echo "$dir"
else
# parent could be "~" or empty-ish; show "parent/base"
local parent_base="${parent##*/}"
if [ -z "$parent_base" ]; then
parent_base="$parent"
fi
echo "${parent_base}/${base}"
fi
}
pwd_display=$(build_pwd_display "$cwd_raw")
# Context window
size=$(echo "$input" | jq -r '.context_window.context_window_size // 200000')
[ "$size" -eq 0 ] 2>/dev/null && size=200000
# Token usage
input_tokens=$(echo "$input" | jq -r '.context_window.current_usage.input_tokens // 0')
cache_create=$(echo "$input" | jq -r '.context_window.current_usage.cache_creation_input_tokens // 0')
cache_read=$(echo "$input" | jq -r '.context_window.current_usage.cache_read_input_tokens // 0')
current=$(( input_tokens + cache_create + cache_read ))
used_tokens=$(format_tokens $current)
total_tokens=$(format_tokens $size)
if [ "$size" -gt 0 ]; then
pct_used=$(( current * 100 / size ))
else
pct_used=0
fi
pct_remain=$(( 100 - pct_used ))
used_comma=$(format_commas $current)
remain_comma=$(format_commas $(( size - current )))
# Check thinking status
thinking_on=false
settings_path="$HOME/.claude/settings.json"
if [ -f "$settings_path" ]; then
thinking_val=$(jq -r '.alwaysThinkingEnabled // false' "$settings_path" 2>/dev/null)
[ "$thinking_val" = "true" ] && thinking_on=true
fi
# ===== Running / Idle detection =====
# State is written by Claude Code hooks (pre-tool-use-activity.sh / stop-activity.sh).
# PreToolUse hook writes {"status":"running","idle_since":0,"project_dir":"..."}
# Stop hook writes {"status":"idle","idle_since":<epoch>,"project_dir":"..."}
# If the state file doesn't exist yet (first launch), treat as idle from now.
#
# Agent-team awareness: a session is only considered idle when no other session
# sharing the same project_dir is still running. This covers agent teams where
# multiple Claude instances collaborate on the same project.
session_id=$(echo "$input" | jq -r '.session_id // "default"' | tr -cd '[:alnum:]-_')
activity_state_file="/tmp/claude/activity-state-${session_id}.json"
mkdir -p /tmp/claude
now_epoch=$(date +%s)
activity_status="idle"
idle_since_epoch=0
this_project_dir=""
if [ -f "$activity_state_file" ]; then
activity_status=$(jq -r '.status // "idle"' "$activity_state_file" 2>/dev/null)
idle_since_epoch=$(jq -r '.idle_since // 0' "$activity_state_file" 2>/dev/null)
this_project_dir=$(jq -r '.project_dir // ""' "$activity_state_file" 2>/dev/null)
fi
# If this session's own state file is missing, fall back to the cwd from input
if [ -z "$this_project_dir" ]; then
this_project_dir=$(echo "$input" | jq -r '.workspace.project_dir // ""')
fi
# Check whether any peer session (same project_dir) is still running.
# A session is a peer if its state file records the same non-empty project_dir.
# Ignore stale state files (not modified in over 5 minutes) — these are likely
# from dead/crashed agents whose stop hook never fired.
stale_threshold=300 # seconds
team_running=false
if [ -n "$this_project_dir" ]; then
for peer_file in /tmp/claude/activity-state-*.json; do
[ "$peer_file" = "$activity_state_file" ] && continue
[ -f "$peer_file" ] || continue
# Skip stale files
peer_mtime=$(stat -c %Y "$peer_file" 2>/dev/null || stat -f %m "$peer_file" 2>/dev/null)
if [ -n "$peer_mtime" ]; then
peer_age=$(( now_epoch - peer_mtime ))
[ "$peer_age" -gt "$stale_threshold" ] && continue
fi
peer_project=$(jq -r '.project_dir // ""' "$peer_file" 2>/dev/null)
[ "$peer_project" != "$this_project_dir" ] && continue
peer_status=$(jq -r '.status // "idle"' "$peer_file" 2>/dev/null)
if [ "$peer_status" = "running" ]; then
team_running=true
break
fi
done
fi
# Determine display: running vs idle
# Running if this session is running OR a team peer is running.
is_running=false
if [ "$activity_status" = "running" ] || $team_running; then
is_running=true
fi
if $is_running; then
running_status="${green}running${reset}"
else
if [ "$idle_since_epoch" -gt 0 ]; then
idle_time=$(date -d "@${idle_since_epoch}" +"%H:%M:%S" 2>/dev/null || date -r "${idle_since_epoch}" +"%H:%M:%S" 2>/dev/null)
running_status="${dim}${idle_time}${reset}"
else
running_status="${dim}idle${reset}"
fi
fi
# ===== LINE 1: pwd | Model | tokens | % used | % remain | thinking =====
line1=""
line1+="${cyan}${pwd_display}${reset}"
line1+=" ${dim}|${reset} "
line1+="${blue}${model_name}${reset}"
line1+=" ${dim}|${reset} "
line1+="${orange}${used_tokens} / ${total_tokens}${reset}"
line1+=" ${dim}|${reset} "
line1+="${green}${pct_used}% used${reset}"
line1+=" ${dim}|${reset} "
line1+="${cyan}${pct_remain}% remain${reset}"
line1+=" ${dim}|${reset} "
if $thinking_on; then
line1+="${orange}On${reset}"
else
line1+="${dim}Off${reset}"
fi
line1+=" ${dim}|${reset} "
line1+="${running_status}"
# ===== Cross-platform OAuth token resolution (from statusline.sh) =====
# Tries credential sources in order: env var → macOS Keychain → Linux creds file → GNOME Keyring
get_oauth_token() {
local token=""
# 1. Explicit env var override
if [ -n "$CLAUDE_CODE_OAUTH_TOKEN" ]; then
echo "$CLAUDE_CODE_OAUTH_TOKEN"
return 0
fi
# 2. macOS Keychain
if command -v security >/dev/null 2>&1; then
local blob
blob=$(security find-generic-password -s "Claude Code-credentials" -w 2>/dev/null)
if [ -n "$blob" ]; then
token=$(echo "$blob" | jq -r '.claudeAiOauth.accessToken // empty' 2>/dev/null)
if [ -n "$token" ] && [ "$token" != "null" ]; then
echo "$token"
return 0
fi
fi
fi
# 3. Linux credentials file
local creds_file="${HOME}/.claude/.credentials.json"
if [ -f "$creds_file" ]; then
token=$(jq -r '.claudeAiOauth.accessToken // empty' "$creds_file" 2>/dev/null)
if [ -n "$token" ] && [ "$token" != "null" ]; then
echo "$token"
return 0
fi
fi
# 4. GNOME Keyring via secret-tool
if command -v secret-tool >/dev/null 2>&1; then
local blob
blob=$(timeout 2 secret-tool lookup service "Claude Code-credentials" 2>/dev/null)
if [ -n "$blob" ]; then
token=$(echo "$blob" | jq -r '.claudeAiOauth.accessToken // empty' 2>/dev/null)
if [ -n "$token" ] && [ "$token" != "null" ]; then
echo "$token"
return 0
fi
fi
fi
echo ""
}
# ===== LINE 2 & 3: Usage limits with progress bars (cached) =====
cache_file="/tmp/claude/statusline-usage-cache.json"
cache_max_age=300 # seconds between API calls
mkdir -p /tmp/claude
is_valid_usage() {
[ -n "$1" ] && echo "$1" | jq -e '.five_hour' >/dev/null 2>&1
}
needs_refresh=true
usage_data=""
# Load existing cache (if valid)
if [ -f "$cache_file" ]; then
cached=$(cat "$cache_file" 2>/dev/null)
if is_valid_usage "$cached"; then
usage_data="$cached"
cache_mtime=$(stat -c %Y "$cache_file" 2>/dev/null || stat -f %m "$cache_file" 2>/dev/null)
now=$(date +%s)
cache_age=$(( now - cache_mtime ))
[ "$cache_age" -lt "$cache_max_age" ] && needs_refresh=false
fi
fi
# Fetch fresh data if cache is stale or missing
if $needs_refresh; then
token=$(get_oauth_token)
if [ -n "$token" ] && [ "$token" != "null" ]; then
response=$(curl -s --max-time 5 \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $token" \
-H "anthropic-beta: oauth-2025-04-20" \
-H "User-Agent: claude-code/2.1.69" \
"https://api.anthropic.com/api/oauth/usage" 2>/dev/null)
if is_valid_usage "$response"; then
usage_data="$response"
echo "$response" > "$cache_file"
fi
# If fetch failed, keep stale usage_data (loaded above)
fi
fi
# Cross-platform ISO to epoch conversion
# Converts ISO 8601 timestamp (e.g. "2025-06-15T12:30:00Z" or "2025-06-15T12:30:00.123+00:00") to epoch seconds.
# Properly handles UTC timestamps and converts to local time.
iso_to_epoch() {
local iso_str="$1"
# Try GNU date first (Linux) — handles ISO 8601 format automatically
local epoch
epoch=$(date -d "${iso_str}" +%s 2>/dev/null)
if [ -n "$epoch" ]; then
echo "$epoch"
return 0
fi
# BSD date (macOS) - handle various ISO 8601 formats
local stripped="${iso_str%%.*}" # Remove fractional seconds (.123456)
stripped="${stripped%%Z}" # Remove trailing Z
stripped="${stripped%%+*}" # Remove timezone offset (+00:00)
stripped="${stripped%%-[0-9][0-9]:[0-9][0-9]}" # Remove negative timezone offset
# Check if timestamp is UTC (has Z or +00:00 or -00:00)
if [[ "$iso_str" == *"Z"* ]] || [[ "$iso_str" == *"+00:00"* ]] || [[ "$iso_str" == *"-00:00"* ]]; then
# For UTC timestamps, parse with timezone set to UTC
epoch=$(env TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%S" "$stripped" +%s 2>/dev/null)
else
epoch=$(date -j -f "%Y-%m-%dT%H:%M:%S" "$stripped" +%s 2>/dev/null)
fi
if [ -n "$epoch" ]; then
echo "$epoch"
return 0
fi
return 1
}
# Format ISO reset time to compact local time
# Usage: format_reset_time <iso_string> <style: time|datetime|date>
format_reset_time() {
local iso_str="$1"
local style="$2"
[ -z "$iso_str" ] || [ "$iso_str" = "null" ] && return
# Parse ISO datetime and convert to local time (cross-platform)
local epoch
epoch=$(iso_to_epoch "$iso_str")
[ -z "$epoch" ] && return
# Format based on style — try BSD date first, fall back to GNU date
local result=""
case "$style" in
time)
result=$(date -j -r "$epoch" +"%l:%M%p" 2>/dev/null)
if [ -n "$result" ]; then
echo "$result" | sed 's/^ //' | tr '[:upper:]' '[:lower:]'
else
date -d "@$epoch" +"%l:%M%P" 2>/dev/null | sed 's/^ //'
fi
;;
datetime)
result=$(date -j -r "$epoch" +"%b %-d, %l:%M%p" 2>/dev/null)
if [ -n "$result" ]; then
echo "$result" | sed 's/ / /g; s/^ //' | tr '[:upper:]' '[:lower:]'
else
date -d "@$epoch" +"%b %-d, %l:%M%P" 2>/dev/null | sed 's/ / /g; s/^ //'
fi
;;
*)
result=$(date -j -r "$epoch" +"%b %-d" 2>/dev/null)
if [ -n "$result" ]; then
echo "$result" | tr '[:upper:]' '[:lower:]'
else
date -d "@$epoch" +"%b %-d" 2>/dev/null
fi
;;
esac
}
# Calculate the expected usage percentage ("pace") at the current moment
# within a rolling window, given the reset epoch and window size in seconds.
#
# Usage: calc_pace_pct <resets_at_epoch> <window_seconds>
# Returns an integer 0-100 (or "" on failure).
calc_pace_pct() {
local resets_epoch=$1
local window_secs=$2
[ -z "$resets_epoch" ] || [ -z "$window_secs" ] && return
local now_ts
now_ts=$(date +%s)
local window_start=$(( resets_epoch - window_secs ))
local elapsed=$(( now_ts - window_start ))
[ "$elapsed" -lt 0 ] && elapsed=0
[ "$elapsed" -gt "$window_secs" ] && elapsed="$window_secs"
awk "BEGIN {printf \"%.0f\", ($elapsed / $window_secs) * 100}"
}
# Pad column to fixed width (ignoring ANSI codes)
# Usage: pad_column <text_with_ansi> <visible_length> <column_width>
pad_column() {
local text="$1"
local visible_len=$2
local col_width=$3
local padding=$(( col_width - visible_len ))
if [ "$padding" -gt 0 ]; then
printf "%s%*s" "$text" "$padding" ""
else
printf "%s" "$text"
fi
}
line2=""
line3=""
sep=" ${dim}|${reset} "
if [ -n "$usage_data" ] && echo "$usage_data" | jq -e . >/dev/null 2>&1; then
bar_width=10
col1w=23
col2w=22
# ---- 5-hour (current) ----
five_hour_pct=$(echo "$usage_data" | jq -r '.five_hour.utilization // 0' | awk '{printf "%.0f", $1}')
five_hour_reset_iso=$(echo "$usage_data" | jq -r '.five_hour.resets_at // empty')
five_hour_reset=$(format_reset_time "$five_hour_reset_iso" "time")
# Compute pace for 5-hour window (5h = 18000 seconds)
five_hour_reset_epoch=""
[ -n "$five_hour_reset_iso" ] && five_hour_reset_epoch=$(iso_to_epoch "$five_hour_reset_iso")
five_hour_pace=""
[ -n "$five_hour_reset_epoch" ] && five_hour_pace=$(calc_pace_pct "$five_hour_reset_epoch" 18000)
five_hour_bar=$(build_bar "$five_hour_pct" "$bar_width" "$five_hour_pace")
# Pace indicator suffix for the reset line (e.g. "pace 42%")
five_hour_pace_suffix=""
five_hour_pace_suffix_plain=""
if [ -n "$five_hour_pace" ]; then
five_hour_pace_suffix=" ${dim}pace ${five_hour_pace}%${reset}"
five_hour_pace_suffix_plain=" pace ${five_hour_pace}%"
fi
# Calculate visible length: "current: " + bar + " " + "XX%"
col1_bar_vis_len=$(( 9 + bar_width + 1 + ${#five_hour_pct} + 1 ))
col1_bar="${white}current:${reset} ${five_hour_bar} ${cyan}${five_hour_pct}%${reset}"
col1_bar=$(pad_column "$col1_bar" "$col1_bar_vis_len" "$col1w")
col1_reset_plain="resets ${five_hour_reset}${five_hour_pace_suffix_plain}"
col1_reset="${white}resets ${five_hour_reset}${reset}${five_hour_pace_suffix}"
col1_reset=$(pad_column "$col1_reset" "${#col1_reset_plain}" "$col1w")
# ---- 7-day (weekly) ----
seven_day_pct=$(echo "$usage_data" | jq -r '.seven_day.utilization // 0' | awk '{printf "%.0f", $1}')
seven_day_reset_iso=$(echo "$usage_data" | jq -r '.seven_day.resets_at // empty')
seven_day_reset=$(format_reset_time "$seven_day_reset_iso" "datetime")
# Compute pace for 7-day window (7d = 604800 seconds)
seven_day_reset_epoch=""
[ -n "$seven_day_reset_iso" ] && seven_day_reset_epoch=$(iso_to_epoch "$seven_day_reset_iso")
seven_day_pace=""
[ -n "$seven_day_reset_epoch" ] && seven_day_pace=$(calc_pace_pct "$seven_day_reset_epoch" 604800)
seven_day_bar=$(build_bar "$seven_day_pct" "$bar_width" "$seven_day_pace")
# Pace indicator suffix for the reset line
seven_day_pace_suffix=""
seven_day_pace_suffix_plain=""
if [ -n "$seven_day_pace" ]; then
seven_day_pace_suffix=" ${dim}pace ${seven_day_pace}%${reset}"
seven_day_pace_suffix_plain=" pace ${seven_day_pace}%"
fi
col2_bar_vis_len=$(( 8 + bar_width + 1 + ${#seven_day_pct} + 1 ))
col2_bar="${white}weekly:${reset} ${seven_day_bar} ${cyan}${seven_day_pct}%${reset}"
col2_bar=$(pad_column "$col2_bar" "$col2_bar_vis_len" "$col2w")
col2_reset_plain="resets ${seven_day_reset}${seven_day_pace_suffix_plain}"
col2_reset="${white}resets ${seven_day_reset}${reset}${seven_day_pace_suffix}"
col2_reset=$(pad_column "$col2_reset" "${#col2_reset_plain}" "$col2w")
# ---- Extra usage ----
col3_bar=""
col3_reset=""
extra_enabled=$(echo "$usage_data" | jq -r '.extra_usage.is_enabled // false')
if [ "$extra_enabled" = "true" ]; then
extra_pct=$(echo "$usage_data" | jq -r '.extra_usage.utilization // 0' | awk '{printf "%.0f", $1}')
extra_used=$(echo "$usage_data" | jq -r '.extra_usage.used_credits // 0' | awk '{printf "%.2f", $1/100}')
extra_limit=$(echo "$usage_data" | jq -r '.extra_usage.monthly_limit // 0' | awk '{printf "%.2f", $1/100}')
# Extra is a monthly window; compute pace using days remaining until next month 1st
extra_reset_epoch=$(date -v+1m -v1d -v0H -v0M -v0S +%s 2>/dev/null || date -d "$(date +%Y-%m-01) +1 month" +%s 2>/dev/null)
extra_pace=""
if [ -n "$extra_reset_epoch" ]; then
# Days in current month
days_in_month=$(date +%-d -d "$(date +%Y-%m-01) +1 month -1 day" 2>/dev/null || cal "$(date +%m %Y)" | awk 'NF{f=$NF} END{print f}')
month_secs=$(( ${days_in_month:-30} * 86400 ))
extra_pace=$(calc_pace_pct "$extra_reset_epoch" "$month_secs")
fi
extra_bar=$(build_bar "$extra_pct" "$bar_width" "$extra_pace")
# Next month 1st for reset date (cross-platform)
extra_reset=$(date -v+1m -v1d +"%b %-d" 2>/dev/null || date -d "$(date +%Y-%m-01) +1 month" +"%b %-d" 2>/dev/null)
extra_reset=$(echo "$extra_reset" | tr '[:upper:]' '[:lower:]')
extra_pace_suffix=""
if [ -n "$extra_pace" ]; then
extra_pace_suffix=" ${dim}pace ${extra_pace}%${reset}"
fi
col3_bar="${white}extra:${reset} ${extra_bar} ${cyan}${extra_used}/${extra_limit}${reset}"
col3_reset="${white}resets ${extra_reset}${reset}${extra_pace_suffix}"
fi
# Assemble line 2: bars row
line2="${col1_bar}${sep}${col2_bar}"
[ -n "$col3_bar" ] && line2+="${sep}${col3_bar}"
# Assemble line 3: resets row
line3="${col1_reset}${sep}${col2_reset}"
[ -n "$col3_reset" ] && line3+="${sep}${col3_reset}"
fi
# Output all lines
printf "%b" "$line1"
[ -n "$line2" ] && printf "\n%b" "$line2"
[ -n "$line3" ] && printf "\n%b" "$line3"
exit 0
#!/bin/bash
# Stop hook — Claude finished its turn; mark session as idle with current timestamp.
# Reads the hook JSON from stdin, extracts session_id, and writes a state file.
# Also stores the project_dir so the statusline can identify agent team peers.
input=$(cat)
session_id=$(echo "$input" | jq -r '.session_id // "default"' 2>/dev/null | tr -cd '[:alnum:]-_')
[ -z "$session_id" ] && session_id="default"
project_dir=$(echo "$input" | jq -r '.workspace.project_dir // ""' 2>/dev/null)
now_epoch=$(date +%s)
mkdir -p /tmp/claude
printf '{"status":"idle","idle_since":%s,"project_dir":"%s"}' "$now_epoch" "$project_dir" \
> "/tmp/claude/activity-state-${session_id}.json"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment