Files
ai-setup/home-configs/claude/statusline-command.sh
vitaly 54742d6a36 feat: гармоничная палитра статусной строки и радужный effort max
Цветовая палитра (все из 256-color, мягкие оттенки):
  CWD/Branch:      серый (250)
  Model/Account:    кремовый (223)
  Effort [low]:     золотой (220)
  Effort [medium]:  бирюзовый (43)
  Effort [high]:    небесно-голубой (39)
  Effort [xhigh]:   лавандовый (171)
  Effort [max]:     радуга (210/220/114/43/171) — скобки участвуют
  DeepSeek баланс:  светло-фиолетовый (147)
  Rate/ctx <40%:    мягкий зелёный (114)
  Rate/ctx 40-60%:  золотистый (221)
  Rate/ctx >=60%:   мягкий красный (210)

- _effort_color выводит [level] целиком (для max — посимвольно)
- Effort persistence: StatusLine сохраняет effort в кэш лаунчера
- Мультивалютный баланс DeepSeek с символами ($ и ¥)
- Автоопределение аккаунта Claude.ai по OAuth токену

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-11 22:21:19 +03:00

230 lines
9.2 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
input=$(cat)
cwd=$(echo "$input" | jq -r '.cwd')
model=$(echo "$input" | jq -r '.model.display_name // empty')
model_id=$(echo "$input" | jq -r '.model.id // empty')
five_pct=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty')
five_reset=$(echo "$input" | jq -r '.rate_limits.five_hour.resets_at // empty')
week_pct=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty')
week_reset=$(echo "$input" | jq -r '.rate_limits.seven_day.resets_at // empty')
ctx_pct=$(echo "$input" | jq -r '.context_window.used_percentage // empty')
# Цвет effort: мягкая гармоничная палитра
_effort_color() {
case "$1" in
low) printf '\033[38;5;220m[low]\033[00m' ;; # золотой
medium) printf '\033[38;5;43m[medium]\033[00m' ;; # бирюзовый
high) printf '\033[38;5;39m[high]\033[00m' ;; # небесно-голубой
xhigh) printf '\033[38;5;171m[xhigh]\033[00m' ;; # лавандовый
max) printf '\033[38;5;210m[\033[38;5;220mm\033[38;5;114ma\033[38;5;43mx\033[38;5;171m]\033[00m' ;; # радуга
*) printf '\033[38;5;250m[%s]\033[00m' "$1" ;;
esac
}
branch=$(git -C "$cwd" --no-optional-locks symbolic-ref --short HEAD 2>/dev/null)
short_cwd="${cwd/#$HOME/\~}"
printf "\033[38;5;250m%s\033[00m" "$short_cwd"
[ -n "$branch" ] && printf " \033[38;5;250m[%s]\033[00m" "$branch"
if [ -n "$model" ]; then
effort=$(echo "$input" | jq -r ".effort.level // empty")
# Сохраняем effort для persistence между сессиями одного лаунчера
if [ -n "$effort" ] && [ -n "${AI_LAUNCHER:-}" ]; then
effort_file="$HOME/.cache/ai-setup/effort_${AI_LAUNCHER}"
prev_effort=$(cat "$effort_file" 2>/dev/null)
if [ "$effort" != "$prev_effort" ]; then
mkdir -p "$HOME/.cache/ai-setup"
echo "$effort" > "$effort_file"
fi
fi
# Аккаунт Claude.ai актуален только для нативных моделей Claude
if [[ "$model_id" == claude-* ]]; then
account=$(cat ~/.claude/accounts/current 2>/dev/null)
ACCOUNTS_DIR="$HOME/.claude/accounts"
# Автоопределение: если current пуст или файл не существует —
# ищем аккаунт по access-токену в .credentials.json
if [ -z "$account" ] || [ ! -f "$ACCOUNTS_DIR/${account}.credentials.json" ]; then
current_token=$(jq -r '.claudeAiOauth.accessToken // empty' "$HOME/.claude/.credentials.json" 2>/dev/null)
if [ -n "$current_token" ]; then
for f in "$ACCOUNTS_DIR"/*.credentials.json; do
[ ! -f "$f" ] && continue
token=$(jq -r '.claudeAiOauth.accessToken // empty' "$f" 2>/dev/null)
if [ "$token" = "$current_token" ]; then
account=$(basename "$f" .credentials.json)
echo "$account" > "$ACCOUNTS_DIR/current"
break
fi
done
# Если токен не найден — спрашиваем haiku (один раз на аккаунт)
if [ -z "$account" ]; then
sentinel="$HOME/.cache/ai-setup/email_fetch_token"
prev_token=$(cat "$sentinel" 2>/dev/null)
if [ "$prev_token" != "$current_token" ]; then
mkdir -p "$HOME/.cache/ai-setup"
echo "$current_token" > "$sentinel"
email=$(unset ANTHROPIC_BASE_URL ANTHROPIC_AUTH_TOKEN; \
echo 'какой имейл этого аккаунта? напиши только имейл без других слов.' | \
claude --print --model claude-haiku-4-5 --dangerously-skip-permissions \
--output-format text --max-turns 1 --tools "" --effort low 2>/dev/null)
email=$(echo "$email" | grep -oE '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}' | head -1)
if [ -n "$email" ]; then
cp "$HOME/.claude/.credentials.json" "$ACCOUNTS_DIR/${email}.credentials.json"
chmod 600 "$ACCOUNTS_DIR/${email}.credentials.json"
account="$email"
echo "$account" > "$ACCOUNTS_DIR/current"
fi
fi
fi
fi
fi
[ -n "$account" ] && printf " \033[38;5;223m[%s]\033[00m" "$account"
fi
if [ -n "$effort" ]; then
printf " \033[38;5;223m%s " "$model"
_effort_color "$effort"
else
printf " \033[38;5;223m%s\033[00m" "$model"
fi
fi
# Форматирует оставшееся время до сброса лимита
fmt_remaining() {
local reset_ts="$1"
local now
now=$(date +%s)
local diff=$(( reset_ts - now ))
[ "$diff" -le 0 ] && echo "скоро" && return
local d=$(( diff / 86400 ))
local h=$(( (diff % 86400) / 3600 ))
local m=$(( (diff % 3600) / 60 ))
if [ "$d" -gt 0 ]; then
echo "${d}д${h}ч"
elif [ "$h" -gt 0 ]; then
echo "${h}ч${m}м"
else
echo "${m}м"
fi
}
# Возвращает ANSI-цвет по проценту: зелёный <40%, жёлтый 40-60%, красный >=60%
pct_color() {
local pct="$1"
if [ "$pct" -lt 40 ]; then
printf '\033[38;5;114m' # мягкий зелёный
elif [ "$pct" -lt 60 ]; then
printf '\033[38;5;221m' # золотистый
else
printf '\033[38;5;210m' # мягкий красный
fi
}
# --- Баланс DeepSeek ---
# Моментально показываем кэшированный баланс, в фоне обновляем через API.
if [[ "$model_id" == *deepseek* ]]; then
cache_file="$HOME/.cache/ai-setup/deepseek_balance"
if [ -f "$cache_file" ]; then
balance=$(head -1 "$cache_file")
[ -n "$balance" ] && printf " \033[38;5;147m%s\033[00m" "$balance"
fi
# Фоновое обновление баланса (не чаще раза в 30 секунд)
refresh_ts="$HOME/.cache/ai-setup/deepseek_balance_refresh_ts"
now=$(date +%s)
last=$(cat "$refresh_ts" 2>/dev/null || echo 0)
if [ $(( now - last )) -gt 30 ]; then
key_file="$HOME/.config/ai-setup/deepseek_key"
if [ -f "$key_file" ]; then
echo "$now" > "$refresh_ts" 2>/dev/null
(
api_key=$(cat "$key_file")
resp=$(curl -s --max-time 10 "https://api.deepseek.com/user/balance" \
-H "Authorization: Bearer $api_key" \
-H "Accept: application/json" 2>/dev/null)
if [ -n "$resp" ]; then
new_balance=$(echo "$resp" | python3 -c "
import sys, json
d = json.load(sys.stdin)
infos = d.get('balance_infos', [])
symbols = {'USD': '\$', 'CNY': '\u00a5'}
parts = []
for info in infos:
curr = info.get('currency', '')
total = info.get('total_balance', '0')
sym = symbols.get(curr, curr)
parts.append(f'{sym}{total}')
if parts:
print(' '.join(parts))
" 2>/dev/null)
if [ -n "$new_balance" ]; then
echo "$new_balance" > "$cache_file"
fi
fi
) &
fi
fi
else
# Рейт-лимиты для НЕ-DeepSeek провайдеров
# Кеш специфичен для провайдера (по model_id) чтобы не смешивать claude/kimi/openrouter
_cache_key=$(echo "${model_id:-unknown}" | sed 's/[^a-zA-Z0-9._-]/_/g')
RATE_CACHE="$HOME/.cache/ai-setup/rate_limits_${_cache_key}.cache"
mkdir -p "$HOME/.cache/ai-setup"
# Если есть свежие данные - сохранить в кеш
if [ -n "$five_pct" ] || [ -n "$week_pct" ]; then
{
echo "FIVE_PCT=${five_pct}"
echo "FIVE_RESET=${five_reset}"
echo "WEEK_PCT=${week_pct}"
echo "WEEK_RESET=${week_reset}"
} > "$RATE_CACHE"
fi
# Если нет данных - читать из кеша (старт сессии до первого запроса)
if [ -z "$five_pct" ] && [ -z "$week_pct" ] && [ -f "$RATE_CACHE" ]; then
# shellcheck source=/dev/null
source "$RATE_CACHE" 2>/dev/null
five_pct="${FIVE_PCT:-}"
five_reset="${FIVE_RESET:-}"
week_pct="${WEEK_PCT:-}"
week_reset="${WEEK_RESET:-}"
fi
if [ -n "$five_pct" ] && [ -n "$five_reset" ]; then
five_int=$(printf '%.0f' "$five_pct")
remaining=$(fmt_remaining "$five_reset")
color=$(pct_color "$five_int")
printf " %s%s:%s%%\033[00m" "$color" "$remaining" "$five_int"
fi
if [ -n "$week_pct" ] && [ -n "$week_reset" ]; then
week_int=$(printf '%.0f' "$week_pct")
remaining=$(fmt_remaining "$week_reset")
color=$(pct_color "$week_int")
printf " %s%s:%s%%\033[00m" "$color" "$remaining" "$week_int"
fi
fi
# ctx:0% при старте новой сессии (нет данных от API)
[ -z "$ctx_pct" ] && ctx_pct="0"
if [ -n "$ctx_pct" ]; then
ctx_int=$(printf '%.0f' "$ctx_pct")
color=$(pct_color "$ctx_int")
printf " %sctx:%s%%\033[00m" "$color" "$ctx_int"
# Звуковой сигнал при первом достижении 60%
alert_file="$HOME/.cache/ai-setup/ctx_alert_state"
if [ "$ctx_int" -ge 60 ]; then
if [ ! -f "$alert_file" ] || [ "$(cat "$alert_file")" != "alerted" ]; then
mkdir -p "$HOME/.cache/ai-setup"
echo "alerted" > "$alert_file"
(timeout 1s paplay /usr/share/sounds/freedesktop/stereo/alarm-clock-elapsed.oga 2>/dev/null; true) &
fi
elif [ "$ctx_int" -lt 50 ]; then
rm -f "$alert_file" 2>/dev/null
fi
fi
exit 0