another claude code statusline
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 308 lines 9.4 kB view raw
1#!/usr/bin/env bash 2 3KLAUS_THEME="${KLAUS_THEME:-dark}" 4KLAUS_GIT_CACHE_TTL="${KLAUS_GIT_CACHE_TTL:-5}" 5KLAUS_USAGE_CACHE_TTL="${KLAUS_USAGE_CACHE_TTL:-60}" 6BAR_LABEL_WIDTH=3 7 8mkdir -p /tmp/klaus 9 10RST='\033[0m' 11 12if [[ "$KLAUS_THEME" == "light" ]]; then 13 RED='\033[38;2;157;0;6m' 14 GREEN='\033[38;2;121;116;14m' 15 EMPTY_FG='\033[38;2;251;241;199m' 16 MODEL_BG='\033[48;2;143;63;113m' 17 MODEL_FG='\033[38;2;251;241;199m' 18 IN_BG='\033[48;2;7;102;120m' 19 IN_FG='\033[38;2;251;241;199m' 20 OUT_BG='\033[48;2;66;123;88m' 21 OUT_FG='\033[38;2;251;241;199m' 22 CTX_BG='\033[48;2;181;118;20m' 23 CTX_FG='\033[38;2;251;241;199m' 24 DAY_BG='\033[48;2;121;116;14m' 25 DAY_FG='\033[38;2;251;241;199m' 26 WK_BG='\033[48;2;157;0;6m' 27 WK_FG='\033[38;2;251;241;199m' 28 REPO_BG='\033[48;2;143;63;113m' 29 REPO_FG='\033[38;2;251;241;199m' 30 BRANCH_BG='\033[48;2;66;123;88m' 31 BRANCH_FG='\033[38;2;251;241;199m' 32 DIFF_BG='\033[48;2;60;56;54m' 33 AGENT_BG='\033[48;2;143;63;113m' 34 AGENT_FG='\033[38;2;251;241;199m' 35else 36 RED='\033[38;2;204;36;29m' 37 GREEN='\033[38;2;152;151;26m' 38 EMPTY_FG='\033[38;2;235;219;178m' 39 MODEL_BG='\033[48;2;177;98;134m' 40 MODEL_FG='\033[38;2;40;40;40m' 41 IN_BG='\033[48;2;69;133;136m' 42 IN_FG='\033[38;2;40;40;40m' 43 OUT_BG='\033[48;2;104;157;106m' 44 OUT_FG='\033[38;2;40;40;40m' 45 CTX_BG='\033[48;2;215;153;33m' 46 CTX_FG='\033[38;2;40;40;40m' 47 DAY_BG='\033[48;2;152;151;26m' 48 DAY_FG='\033[38;2;40;40;40m' 49 WK_BG='\033[48;2;204;36;29m' 50 WK_FG='\033[38;2;40;40;40m' 51 REPO_BG='\033[48;2;177;98;134m' 52 REPO_FG='\033[38;2;40;40;40m' 53 BRANCH_BG='\033[48;2;104;157;106m' 54 BRANCH_FG='\033[38;2;40;40;40m' 55 DIFF_BG='\033[48;2;80;73;69m' 56 AGENT_BG='\033[48;2;177;98;134m' 57 AGENT_FG='\033[38;2;40;40;40m' 58fi 59 60JSON="$(cat)" 61 62PARSED=$(echo "$JSON" | jq -r ' 63 (.model.display_name // ""), 64 (.workspace.current_dir // ""), 65 (.context_window.used_percentage // 0), 66 (.context_window.total_input_tokens // 0), 67 (.context_window.total_output_tokens // 0), 68 (.cost.total_lines_added // 0), 69 (.cost.total_lines_removed // 0), 70 (.agent.name // "") 71') 72 73{ 74 read -r MODEL 75 read -r DIR 76 read -r PCT 77 read -r IN_TOKENS 78 read -r OUT_TOKENS 79 read -r LINES_ADD 80 read -r LINES_DEL 81 read -r AGENT_NAME 82} <<<"$PARSED" 83 84get_usage() { 85 local cache_file="/tmp/klaus/usage" 86 local now=$(date +%s) 87 88 if [[ -f "$cache_file" ]]; then 89 local mtime 90 if [[ "$(uname)" == "Darwin" ]]; then 91 mtime=$(stat -f %m "$cache_file" 2>/dev/null || echo 0) 92 else 93 mtime=$(stat -c %Y "$cache_file" 2>/dev/null || echo 0) 94 fi 95 if [[ $((now - mtime)) -lt "$KLAUS_USAGE_CACHE_TTL" ]]; then 96 cat "$cache_file" 97 return 98 fi 99 fi 100 101 local token 102 token=$(security find-generic-password -s "Claude Code-credentials" -w 2>/dev/null | 103 jq -r '.claudeAiOauth.accessToken // empty' 2>/dev/null) 104 105 if [[ -z "$token" ]]; then 106 echo "0|0" 107 return 108 fi 109 110 local resp 111 resp=$(curl -sf --max-time 3 \ 112 -H "Authorization: Bearer $token" \ 113 -H "anthropic-beta: oauth-2025-04-20" \ 114 "https://api.anthropic.com/api/oauth/usage" 2>/dev/null) 115 116 if [[ -z "$resp" ]] || echo "$resp" | jq -e '.error' &>/dev/null; then 117 # fall back to stale cache if available 118 [[ -f "$cache_file" ]] && cat "$cache_file" && return 119 echo "0|0" 120 return 121 fi 122 123 local result 124 result=$(echo "$resp" | jq -r ' 125 ((.five_hour.utilization // 0) | round), 126 ((.seven_day.utilization // 0) | round) 127 ' 2>/dev/null | paste -sd'|' -) 128 129 if [[ -z "$result" || "$result" == "|" ]]; then 130 [[ -f "$cache_file" ]] && cat "$cache_file" && return 131 echo "0|0" 132 return 133 fi 134 135 echo "$result" >"$cache_file" 136 echo "$result" 137} 138 139USAGE_DATA=$(get_usage) 140IFS='|' read -r FIVE_HOUR_PCT SEVEN_DAY_PCT <<<"$USAGE_DATA" 141 142format_tokens() { 143 echo "$1" | awk '{ 144 if ($1 >= 1000000) printf "%.1fM", $1/1000000 145 else if ($1 >= 1000) printf "%.0fk", $1/1000 146 else printf "%s", $1 147 }' 148} 149 150osc_link() { 151 printf '\033]8;;%s\033\\%s\033]8;;\033\\' "$1" "$2" 152} 153 154progress_bar() { 155 local pct="$1" label="$2" bar_width="${3:-8}" bg="$4" fg="$5" 156 local pct_int=${pct%.*} 157 pct_int=${pct_int:-0} 158 [[ "$pct_int" -gt 100 ]] && pct_int=100 159 160 local filled=$((pct_int * bar_width / 100)) 161 local empty=$((bar_width - filled)) 162 local filled_bar=$(printf "%${filled}s" | tr ' ' '▓') 163 local empty_bar=$(printf "%${empty}s" | tr ' ' '░') 164 165 local right_len=$((bar_width + 7)) 166 local label_pad=$((W - right_len - 1)) 167 [[ "$label_pad" -lt ${#label} ]] && label_pad=${#label} 168 169 printf "${bg}${fg} %-${label_pad}s %s${EMPTY_FG}%s${fg} %3d%% ${RST}" "$label" "$filled_bar" "$empty_bar" "$pct_int" 170} 171 172strip_ansi() { 173 printf '%s' "$1" | sed $'s/\033\\[[0-9;]*m//g' | sed $'s/\033\\]8;;[^\033]*\033\\\\//g' 174} 175 176pad_line() { 177 local content="$1" bg="$2" fg="$3" 178 local visible=$(strip_ansi "$content") 179 local vlen=${#visible} 180 local pad=$((W - vlen)) 181 [[ "$pad" -lt 0 ]] && pad=0 182 local padding=$(printf "%${pad}s" "") 183 printf '%s%b%s%b' "$content" "${bg}${fg}" "$padding" "$RST" 184} 185 186get_git_info() { 187 local dir="$1" 188 [[ -z "$dir" ]] && return 189 190 local cache_file="/tmp/klaus/git" 191 local now=$(date +%s) 192 193 if [[ -f "$cache_file" ]]; then 194 local mtime 195 if [[ "$(uname)" == "Darwin" ]]; then 196 mtime=$(stat -f %m "$cache_file" 2>/dev/null || echo 0) 197 else 198 mtime=$(stat -c %Y "$cache_file" 2>/dev/null || echo 0) 199 fi 200 if [[ $((now - mtime)) -lt "$KLAUS_GIT_CACHE_TTL" ]]; then 201 cat "$cache_file" 202 return 203 fi 204 fi 205 206 local result="" 207 if git -C "$dir" rev-parse --is-inside-work-tree &>/dev/null; then 208 local branch=$(git -C "$dir" symbolic-ref --short HEAD 2>/dev/null || git -C "$dir" rev-parse --short HEAD 2>/dev/null) 209 local staged=$(git -C "$dir" diff --cached --numstat 2>/dev/null | wc -l | tr -d ' ') 210 local modified=$(git -C "$dir" diff --numstat 2>/dev/null | wc -l | tr -d ' ') 211 212 local link_part="" 213 local remote_url=$(git -C "$dir" remote get-url origin 2>/dev/null) 214 if [[ -n "$remote_url" ]]; then 215 local repo_name=$(echo "$remote_url" | sed -E 's#\.git$##; s#.*[:/]([^/]+/[^/]+)$#\1#') 216 local web_url=$(echo "$remote_url" | sed -E 's#^git@([^:]+):#https://\1/#; s#\.git$##') 217 link_part="${repo_name}|${web_url}" 218 fi 219 220 result="${branch}|${staged}|${modified}|${link_part}" 221 fi 222 223 echo "$result" >"$cache_file" 224 echo "$result" 225} 226 227IN_FMT="$(format_tokens "$IN_TOKENS")" 228OUT_FMT="$(format_tokens "$OUT_TOKENS")" 229 230GIT_INFO=$(get_git_info "$DIR") 231branch_text="" diff_text="" diff_len=0 repo_label="" 232if [[ -n "$GIT_INFO" ]]; then 233 IFS='|' read -r BRANCH STAGED MODIFIED REPO_NAME WEB_URL <<<"$GIT_INFO" 234 branch_text=" ${BRANCH}" 235 [[ "$STAGED" -gt 0 ]] && branch_text+=" +${STAGED}" 236 [[ "$MODIFIED" -gt 0 ]] && branch_text+=" ~${MODIFIED}" 237 if [[ "$LINES_ADD" -gt 0 || "$LINES_DEL" -gt 0 ]]; then 238 diff_text=" +${LINES_ADD} -${LINES_DEL} " 239 diff_len=${#diff_text} 240 fi 241 repo_label="${REPO_NAME:-$(basename "$DIR")}" 242fi 243 244model_min=$((${#MODEL} + ${#IN_FMT} + ${#OUT_FMT} + 8)) 245bar_min=$((BAR_LABEL_WIDTH + 8 + 4)) 246if [[ -n "$GIT_INFO" ]]; then 247 git_min=$((${#repo_label} + 2 + ${#branch_text} + 1 + diff_len)) 248elif [[ -n "$DIR" ]]; then 249 dn="$(basename "$DIR")" 250 git_min=$((${#dn} + 2)) 251else 252 git_min=0 253fi 254 255agent_min=0 256[[ -n "$AGENT_NAME" ]] && agent_min=$((${#AGENT_NAME} + 2)) 257 258W=$model_min 259[[ "$bar_min" -gt "$W" ]] && W=$bar_min 260[[ "$git_min" -gt "$W" ]] && W=$git_min 261[[ "$agent_min" -gt "$W" ]] && W=$agent_min 262 263# fill space with bar (label 3 chars + 8 fixed chars) 264BAR_WIDTH=$((W - 11)) 265[[ "$BAR_WIDTH" -lt 4 ]] && BAR_WIDTH=4 266 267tokens_len=$((${#IN_FMT} + ${#OUT_FMT} + 6)) 268model_pad=$((W - tokens_len - 1)) 269[[ "$model_pad" -lt $((${#MODEL} + 1)) ]] && model_pad=$((${#MODEL} + 1)) 270 271line1="$(printf "${MODEL_BG}${MODEL_FG} %-${model_pad}s${RST}" "$MODEL")" 272line1+="$(printf "${IN_BG}${IN_FG} ↓%s ${RST}${OUT_BG}${OUT_FG} ↑%s ${RST}" "$IN_FMT" "$OUT_FMT")" 273 274line2="" 275if [[ -n "$GIT_INFO" ]]; then 276 right_len=$((${#branch_text} + 1 + diff_len)) 277 repo_pad=$((W - right_len - 1)) 278 [[ "$repo_pad" -lt $((${#repo_label} + 1)) ]] && repo_pad=$((${#repo_label} + 1)) 279 280 if [[ -n "$WEB_URL" ]]; then 281 pad_spaces=$((repo_pad - ${#repo_label})) 282 [[ "$pad_spaces" -lt 0 ]] && pad_spaces=0 283 line2+="$(printf "${REPO_BG}${REPO_FG} %s%${pad_spaces}s${RST}" "$(osc_link "$WEB_URL" "$repo_label")" "")" 284 else 285 line2+="$(printf "${REPO_BG}${REPO_FG} %-${repo_pad}s${RST}" "$repo_label")" 286 fi 287 line2+="$(printf "${BRANCH_BG}${BRANCH_FG}%s ${RST}" "$branch_text")" 288 if [[ -n "$diff_text" ]]; then 289 line2+="$(printf "${DIFF_BG}${GREEN} +%s ${RED}-%s ${RST}" "$LINES_ADD" "$LINES_DEL")" 290 fi 291elif [[ -n "$DIR" ]]; then 292 dir_name="$(basename "$DIR")" 293 dir_pad=$((W - 1)) 294 [[ "$dir_pad" -lt $((${#dir_name} + 1)) ]] && dir_pad=$((${#dir_name} + 1)) 295 line2+="$(printf "${REPO_BG}${REPO_FG} %-${dir_pad}s${RST}" "$dir_name")" 296fi 297 298if [[ -n "$AGENT_NAME" ]]; then 299 agent_label="$(echo "$AGENT_NAME" | tr '[:lower:]' '[:upper:]')" 300 agent_pad=$((W - 1)) 301 [[ "$agent_pad" -lt $((${#agent_label} + 1)) ]] && agent_pad=$((${#agent_label} + 1)) 302 printf '%b\n' "$(printf "${AGENT_BG}${AGENT_FG} %-${agent_pad}s${RST}" "$agent_label")" 303fi 304printf '%b\n' "$line1" 305printf '%b\n' "$(progress_bar "$PCT" "ctx" "$BAR_WIDTH" "$CTX_BG" "$CTX_FG")" 306printf '%b\n' "$(progress_bar "$FIVE_HOUR_PCT" "5h" "$BAR_WIDTH" "$DAY_BG" "$DAY_FG")" 307printf '%b\n' "$(progress_bar "$SEVEN_DAY_PCT" "7d" "$BAR_WIDTH" "$WK_BG" "$WK_FG")" 308printf '%b\n' "$line2"