From 571334d67bde0a5819f2d536d92cd272cd65be7d Mon Sep 17 00:00:00 2001 From: Dmitry Dementiev Date: Sun, 26 Jul 2026 17:12:56 +0300 Subject: [PATCH] =?UTF-8?q?feat(claude):=20=D1=81=D1=82=D0=B0=D1=82=D1=83?= =?UTF-8?q?=D1=81=D0=BD=D0=B0=D1=8F=20=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B0?= =?UTF-8?q?=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BF=D0=B8=D1=81=D0=B0=D0=BD=D0=B0?= =?UTF-8?q?=20=D0=B8=20=D1=83=D1=81=D1=82=D0=B0=D0=BD=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B8=D0=B2=D0=B0=D0=B5=D1=82=D1=81=D1=8F=20install.sh?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Зачем: - statusLine ссылался на ~/.claude/statusline-command.sh, которого install.sh никогда не создавал, а сам скрипт не округлял проценты и резал путь по числу сегментов вместо длины. - Что: - скрипт переписан: округление процентов до целых, порог длины пути в переменной MAX_PATH_LEN (28), резервный разбор JSON без jq, USER/USERNAME и HOSTNAME вместо hostname -s, нормализация обратных слэшей — для Git Bash. - install.sh копирует statusline-command.sh в ~/.claude с правами 755. - Проверка: - printf '%s' "$json" | bash claude/.claude/statusline-command.sh на JSON с дробными процентами, null в rate_limits и без блока rate_limits. --- claude/.claude/statusline-command.sh | 167 ++++++++++++++++----------- install.sh | 8 ++ 2 files changed, 106 insertions(+), 69 deletions(-) mode change 100644 => 100755 claude/.claude/statusline-command.sh diff --git a/claude/.claude/statusline-command.sh b/claude/.claude/statusline-command.sh old mode 100644 new mode 100755 index 736c1b7..686c43d --- a/claude/.claude/statusline-command.sh +++ b/claude/.claude/statusline-command.sh @@ -1,87 +1,116 @@ #!/usr/bin/env bash +# Статусная строка Claude Code. JSON приходит на stdin. +# +# user@host:.../dir (branch) | Model Name (NM context) ctx:N% | 5h:N% 7d:N% +# +# Работает в Linux и в Git Bash на Windows: без `hostname -s`, jq не обязателен. -# Читаем и парсим JSON одним вызовом jq -IFS=$'\t' read -r cwd display_name ctx_pct rate5h rate7d < <(jq -r '[ - (.workspace.current_dir // ""), - (.model.display_name // ""), - (.context_window.used_percentage // ""), - (.rate_limits.five_hour.used_percentage // ""), - (.rate_limits.seven_day.used_percentage // "") -] | @tsv') +# Порог, после которого путь сокращается до .../<последний каталог> +MAX_PATH_LEN="${STATUSLINE_MAX_PATH_LEN:-28}" -# Путь: $HOME → ~, длинные пути → .../ -path="$cwd" -home="$HOME" -if [[ "$path" == "$home" ]]; then - path="~" -elif [[ "$path" == "$home"/* ]]; then - path="~${path#$home}" +input="$(cat)" + +# ── Разбор JSON ─────────────────────────────────────────────────────── + +# Числовое поле $2 внутри объекта $1 (резервный разбор, без jq). +json_num() { + printf '%s' "$input" \ + | tr -d ' \t\n\r' \ + | grep -o "\"$1\":{[^}]*}" \ + | grep -o "\"$2\":[0-9.eE+-]*" \ + | head -1 \ + | cut -d: -f2 +} + +# Строковое поле $2 внутри объекта $1 (резервный разбор, без jq). +json_str() { + printf '%s' "$input" \ + | tr -d '\n\r' \ + | grep -o "\"$1\"[[:space:]]*:[[:space:]]*{[^}]*}" \ + | grep -o "\"$2\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" \ + | head -1 \ + | sed 's/.*"[[:space:]]*:[[:space:]]*"//; s/"$//' +} + +if command -v jq >/dev/null 2>&1; then + IFS=$'\t' read -r cwd display_name ctx_pct rate5h rate7d </dev/null) +EOF +else + cwd="$(json_str workspace current_dir)" + display_name="$(json_str model display_name)" + ctx_pct="$(json_num context_window used_percentage)" + rate5h="$(json_num five_hour used_percentage)" + rate7d="$(json_num seven_day used_percentage)" fi -# Считаем сегменты (кроме ведущего ~) -IFS='/' read -ra segs <<< "${path#\~}" -segs=("${segs[@]/#/}") # убираем пустые -real_segs=() -for s in "${segs[@]}"; do [[ -n "$s" ]] && real_segs+=("$s"); done +# Проценты в JSON дробные — округляем до целых. +round_pct() { + case "$1" in + ''|null) return 0 ;; + esac + awk -v v="$1" 'BEGIN { printf "%d", (v < 0 ? -int(-v + 0.5) : int(v + 0.5)) }' 2>/dev/null +} -if (( ${#real_segs[@]} > 2 )); then - last="${real_segs[-1]}" - if [[ "$path" == ~* ]]; then - path="~/.../$last" - else - path=".../$last" - fi +ctx_pct="$(round_pct "$ctx_pct")" +rate5h="$(round_pct "$rate5h")" +rate7d="$(round_pct "$rate7d")" + +# ── Путь ────────────────────────────────────────────────────────────── + +# В Git Bash current_dir может прийти с обратными слэшами. +path="${cwd//\\//}" +home="${HOME//\\//}" + +if [ -n "$home" ]; then + if [ "$path" = "$home" ]; then + path="~" + else + case "$path" in + "$home"/*) path="~${path#"$home"}" ;; + esac + fi fi -# Git branch +if [ "${#path}" -gt "$MAX_PATH_LEN" ]; then + path=".../${path##*/}" +fi + +# ── Остальные секции ────────────────────────────────────────────────── + branch="" -if [[ -n "$cwd" ]]; then - branch=$(git -C "$cwd" rev-parse --abbrev-ref HEAD 2>/dev/null) +if [ -n "$cwd" ]; then + branch="$(git -C "$cwd" rev-parse --abbrev-ref HEAD 2>/dev/null)" fi -# Модель: убрать префикс "Claude " +# Размер контекста уже внутри display_name, убираем только префикс «Claude ». model="${display_name#Claude }" -# ANSI цвета -GREEN='\033[32m' -YELLOW='\033[33m' -PURPLE='\033[35m' -CYAN='\033[36m' -RESET='\033[0m' +user="${USER:-${USERNAME:-$(id -un 2>/dev/null)}}" +host="${HOSTNAME:-$(hostname 2>/dev/null)}" +host="${host%%.*}" -# user@hostname -user_host="${GREEN}${USER}@$(hostname -s)${RESET}" +GREEN=$'\033[32m' +YELLOW=$'\033[33m' +PURPLE=$'\033[35m' +CYAN=$'\033[36m' +RESET=$'\033[0m' -# путь -colored_path="${YELLOW}${path}${RESET}" +out="${GREEN}${user}@${host}${RESET}:${YELLOW}${path}${RESET}" +[ -n "$branch" ] && out="${out} ${PURPLE}(${branch})${RESET}" +out="${out} | ${CYAN}${model}${RESET}" +[ -n "$ctx_pct" ] && out="${out} ctx:${ctx_pct}%" -# ветка -colored_branch="" -if [[ -n "$branch" ]]; then - colored_branch=" ${PURPLE}(${branch})${RESET}" -fi - -# модель -colored_model="${CYAN}${model}${RESET}" - -# Собираем секции -out="${user_host}:${colored_path}${colored_branch} | ${colored_model}" - -# ctx: только если не пустое и не null -if [[ -n "$ctx_pct" && "$ctx_pct" != "null" ]]; then - out+=" ctx:${ctx_pct}%" -fi - -# rate limits: только если не пустые и не null rates="" -if [[ -n "$rate5h" && "$rate5h" != "null" ]]; then - rates+=" 5h:${rate5h}%" -fi -if [[ -n "$rate7d" && "$rate7d" != "null" ]]; then - rates+=" 7d:${rate7d}%" -fi -if [[ -n "$rates" ]]; then - out+=" |${rates}" -fi +[ -n "$rate5h" ] && rates="${rates} 5h:${rate5h}%" +[ -n "$rate7d" ] && rates="${rates} 7d:${rate7d}%" +[ -n "$rates" ] && out="${out} |${rates}" -printf '%b\n' "$out" +printf '%s\n' "$out" diff --git a/install.sh b/install.sh index 4485b4b..6d307a6 100755 --- a/install.sh +++ b/install.sh @@ -393,6 +393,14 @@ if [ -f "$CLAUDE_LOCAL_SRC" ]; then fi fi +# statusline-command.sh — копия из репозитория (на Windows симлинки ненадёжны) +CLAUDE_STATUSLINE_SRC="$DOTFILES_DIR/claude/.claude/statusline-command.sh" +CLAUDE_STATUSLINE_DST="$HOME/.claude/statusline-command.sh" +if [ -f "$CLAUDE_STATUSLINE_SRC" ]; then + install -m 755 "$CLAUDE_STATUSLINE_SRC" "$CLAUDE_STATUSLINE_DST" + info "statusline-command.sh скопирован в ~/.claude/" +fi + # ── Bash: .bashrc ──────────────────────────────────────────────────── BASHRC="$HOME/.bashrc"