AGENT TASK: установить статус-бар Claude Code

Этот документ — промпт для AI-агента (Claude Code, Cursor, Codex и т.п.). Пользователь передаёт файл агенту со словами «выполни инструкцию» — дальше всё делает агент. Все команды выполняются от имени текущего пользователя, без sudo, если не указано иное.


Пример того, как это выглядит

В нижней части окна Claude Code появляется одна строка со статусом сессии:

Статус-бар Claude Code: проект, лимиты 5h/7d, цена, effort, модель

Слева направо: имя проекта, остаток 5-часового лимита со временем до сброса, остаток 7-дневного лимита, заполнение контекста (если показывает), стоимость текущей сессии, уровень effort, модель.


ROLE

Ты — агент, устанавливающий кастомный статус-бар для Claude Code. Работай идемпотентно: если что-то уже сделано — не переделывай. Не затирай существующий конфиг. После каждого изменяющего шага проверяй результат. В конце отчитайся одним коротким сообщением.

GOAL

В терминале Claude Code внизу должна появиться строка вида:

• <project> | 5h:NN% Xh Ym | 7d:NN% Xd Yh | ctx:NN% | $X.XX | effort:<level> | <model>

Цвета (ANSI): зелёный/жёлтый/красный для лимитов и контекста по порогам ниже.

SUCCESS CRITERIA

Все пункты должны быть true:

  1. Файл ~/.claude/statusline-command.sh существует, исполняемый.
  2. ~/.claude/settings.json — валидный JSON и содержит:
    "statusLine": { "type": "command", "command": "bash ~/.claude/statusline-command.sh" }
  3. Остальные ключи settings.json, существовавшие до правки, на месте и неизменны.
  4. jq и awk доступны в PATH.
  5. Версия Claude Code ≥ 2.1.80 (если меньше — предупреди, но не падай).

PRECONDITIONS — проверь до начала

Выполни параллельно и зафиксируй результаты:

claude --version || echo "claude_missing"
command -v jq || echo "jq_missing"
command -v awk || echo "awk_missing"
uname -s
test -f ~/.claude/settings.json && echo "settings_exists" || echo "settings_missing"

Реакция:

STEPS

Step 1. Бэкап существующего settings.json (если есть)

if [ -f ~/.claude/settings.json ]; then
  cp ~/.claude/settings.json ~/.claude/settings.json.bak.$(date +%s)
fi
mkdir -p ~/.claude

Step 2. Записать скрипт статус-бара

Запиши дословно содержимое из секции SCRIPT в ~/.claude/statusline-command.sh. Не меняй ничего, не «улучшай», не добавляй комментарии от себя. После записи:

chmod +x ~/.claude/statusline-command.sh

Проверка:

test -x ~/.claude/statusline-command.sh && echo OK

Step 3. Мердж в settings.json (идемпотентно, без затирания)

Используй jq для безопасного мерджа. Не парси JSON руками, не пиши cat > settings.json.

TMP=$(mktemp)
if [ -f ~/.claude/settings.json ]; then
  jq '.statusLine = {"type":"command","command":"bash ~/.claude/statusline-command.sh"}' \
    ~/.claude/settings.json > "$TMP" && mv "$TMP" ~/.claude/settings.json
else
  printf '%s\n' '{"statusLine":{"type":"command","command":"bash ~/.claude/statusline-command.sh"}}' \
    | jq . > ~/.claude/settings.json
fi

Если jq упал — НЕ перезаписывай файл; восстанови из бэкапа и сообщи пользователю.

Step 4. Smoke-test

Запусти скрипт с моковым JSON-инпутом и убедись, что он печатает строку:

echo '{"model":{"id":"claude-opus-4-7"},"workspace":{"current_dir":"/tmp"},"rate_limits":{"five_hour":{"used_percentage":30,"resets_at":'$(($(date +%s)+7200))'},"seven_day":{"used_percentage":50,"resets_at":'$(($(date +%s)+86400))'}},"context_window":{"used_percentage":42},"cost":{"total_cost_usd":1.23}}' \
  | bash ~/.claude/statusline-command.sh
echo

Ожидаемо: одна строка с • tmp | 5h:70% ... | 7d:50% ... | ctx:42% | $1.23 | ... | opus 4.7. Если пусто или ошибка — диагностируй (см. TROUBLESHOOTING).

VERIFY (финальная проверка)

Выполни всё и убедись, что нет ошибок:

jq . ~/.claude/settings.json > /dev/null && echo "json_ok"
jq -e '.statusLine.command' ~/.claude/settings.json > /dev/null && echo "statusline_ok"
test -x ~/.claude/statusline-command.sh && echo "script_ok"

REPORT (что вывести пользователю в конце)

Одно короткое сообщение, формат:

✓ Статус-бар установлен.
- Скрипт: ~/.claude/statusline-command.sh
- settings.json: добавлен ключ statusLine, остальное не тронуто
- Бэкап: ~/.claude/settings.json.bak.<ts> (если был оригинал)

Перезапусти Claude Code — внизу появится строка с лимитами 5h/7d, контекстом, ценой сессии и моделью.

Если были warnings (старая версия CC, free-тариф без rate_limits) — добавь отдельной строкой.

TROUBLESHOOTING (для агента при сбоях)

Симптом Причина Действие
Step 4 даёт пустой вывод jq или awk не найден Перепроверь command -v jq awk
jq: error в Step 3 settings.json уже сломан Восстанови из последнего .bak, повтори Step 3
Скрипт печатает \033[..m буквально Тест запущен в pipe без ANSI; в реальном терминале будет цвет Игнорируй
claude --version не находится Claude Code не в PATH Спроси пользователя, где установлен
После рестарта статус-бар не виден settings.json не подхватился Проверь jq .statusLine ~/.claude/settings.json

CUSTOMIZATION (по запросу пользователя)

Если пользователь просит изменить:

После любой кастомизации повтори Step 4 и сохрани новую версию скрипта.

REFERENCE: что показывают поля (для пользователя, если спросит)

INPUT CONTRACT (что Claude Code передаёт скрипту в stdin)

JSON со структурой (используются только эти поля):

{
  "model": { "id": "claude-opus-4-7" },
  "workspace": { "current_dir": "/abs/path" },
  "cwd": "/abs/path",
  "rate_limits": {
    "five_hour": { "used_percentage": 0-100, "resets_at": <unix_ts> },
    "seven_day": { "used_percentage": 0-100, "resets_at": <unix_ts> }
  },
  "context_window": { "used_percentage": 0-100 },
  "cost": { "total_cost_usd": <float> }
}

Любые поля могут отсутствовать — скрипт это переживает (использует // empty в jq).


SCRIPT

Запиши дословно в ~/.claude/statusline-command.sh:

#!/bin/bash

# Claude Code Status Line — shows project, 5h/7d limits, model
input=$(cat)

# Extract fields
model_id=$(echo "$input" | jq -r '.model.id // "unknown"')
current_dir=$(echo "$input" | jq -r '.workspace.current_dir // .cwd // ""')

# Project name from directory
dir_name=$(basename "${current_dir:-$(pwd)}")
project_name="$dir_name"

# Rate limits (available for Pro/Max subscribers, v2.1.80+)
five_hr=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty')
five_hr_reset=$(echo "$input" | jq -r '.rate_limits.five_hour.resets_at // empty')
seven_day=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty')
seven_day_reset=$(echo "$input" | jq -r '.rate_limits.seven_day.resets_at // empty')

# Build limits display
limits=""
if [ -n "$five_hr" ] && [ "$five_hr" != "null" ]; then
    five_remain=$(awk "BEGIN {printf \"%.0f\", 100 - $five_hr}")

    five_reset_str=""
    if [ -n "$five_hr_reset" ] && [ "$five_hr_reset" != "null" ]; then
        now=$(date +%s)
        diff=$(( five_hr_reset - now ))
        if [ $diff -gt 0 ]; then
            h=$(( diff / 3600 ))
            m=$(( (diff % 3600) / 60 ))
            if [ $h -ge 1 ]; then
                five_reset_str=" ${h}h${m}m"
            else
                five_reset_str=" ${m}m"
            fi
        fi
    fi

    if [ "$five_remain" -gt 50 ]; then
        five_color="\033[32m"
    elif [ "$five_remain" -gt 20 ]; then
        five_color="\033[33m"
    else
        five_color="\033[31m"
    fi

    limits="${five_color}5h:${five_remain}%${five_reset_str}\033[0m"
fi

if [ -n "$seven_day" ] && [ "$seven_day" != "null" ]; then
    seven_remain=$(awk "BEGIN {printf \"%.0f\", 100 - $seven_day}")

    seven_reset_str=""
    if [ -n "$seven_day_reset" ] && [ "$seven_day_reset" != "null" ]; then
        now=$(date +%s)
        diff=$(( seven_day_reset - now ))
        if [ $diff -gt 0 ]; then
            d=$(( diff / 86400 ))
            h=$(( (diff % 86400) / 3600 ))
            if [ $d -ge 1 ]; then
                seven_reset_str=" ${d}d${h}h"
            else
                seven_reset_str=" ${h}h"
            fi
        fi
    fi

    if [ "$seven_remain" -gt 50 ]; then
        seven_color="\033[32m"
    elif [ "$seven_remain" -gt 20 ]; then
        seven_color="\033[33m"
    else
        seven_color="\033[31m"
    fi

    if [ -n "$limits" ]; then
        limits="${limits} ${seven_color}7d:${seven_remain}%${seven_reset_str}\033[0m"
    else
        limits="${seven_color}7d:${seven_remain}%${seven_reset_str}\033[0m"
    fi
fi

if [ -z "$limits" ]; then
    limits="\033[90mwaiting...\033[0m"
fi

# Context window usage
ctx_pct=$(echo "$input" | jq -r '.context_window.used_percentage // empty')
ctx=""
if [ -n "$ctx_pct" ] && [ "$ctx_pct" != "null" ]; then
    ctx_int=$(awk "BEGIN {printf \"%.0f\", $ctx_pct}")
    if [ "$ctx_int" -lt 50 ]; then
        ctx_color="\033[32m"
    elif [ "$ctx_int" -lt 80 ]; then
        ctx_color="\033[33m"
    else
        ctx_color="\033[31m"
    fi
    ctx="${ctx_color}ctx:${ctx_int}%\033[0m"
fi

# Session cost
cost_usd=$(echo "$input" | jq -r '.cost.total_cost_usd // empty')
cost=""
if [ -n "$cost_usd" ] && [ "$cost_usd" != "null" ]; then
    cost="\033[36m\$$(awk "BEGIN {printf \"%.2f\", $cost_usd}")\033[0m"
fi

# Short model name — detect 1M context variant
ctx_suffix=""
case "$model_id" in
    *\[1m\]*|*-1m*|*_1m*) ctx_suffix=" 1M" ;;
esac

case "$model_id" in
    *opus*4-7*|*opus-4-7*|*opus*4*7*)       model_base="opus 4.7" ;;
    *opus*4-6*|*opus-4-6*|*opus*4*6*)       model_base="opus 4.6" ;;
    *opus*4-5*|*opus-4-5*|*opus*4*5*)       model_base="opus 4.5" ;;
    *sonnet*4-6*|*sonnet-4-6*|*sonnet*4*6*) model_base="sonnet 4.6" ;;
    *sonnet*4-5*|*sonnet-4-5*|*sonnet*4*5*) model_base="sonnet 4.5" ;;
    *sonnet*4*|*sonnet-4*)                  model_base="sonnet 4" ;;
    *haiku*4-5*|*haiku-4-5*)                model_base="haiku 4.5" ;;
    *haiku*)                                 model_base="haiku" ;;
    *)                                       model_base="${model_id:-claude}" ;;
esac
model_short="${model_base}${ctx_suffix}"

# Effort level from settings.json
effort=""
effort_level=$(jq -r '.effortLevel // empty' ~/.claude/settings.json 2>/dev/null)
if [ -n "$effort_level" ]; then
    case "$effort_level" in
        xhigh|high)  effort_color="\033[35m" ;;
        medium)      effort_color="\033[33m" ;;
        low)         effort_color="\033[90m" ;;
        *)           effort_color="\033[37m" ;;
    esac
    effort="${effort_color}effort:${effort_level}\033[0m"
fi

# Assemble: project | limits | ctx | cost | effort | model
parts="$limits"
[ -n "$ctx" ] && parts="$parts | $ctx"
[ -n "$cost" ] && parts="$parts | $cost"
[ -n "$effort" ] && parts="$parts | $effort"

printf "• %s | %b | %s" "$project_name" "$parts" "$model_short"

DONE-CHECK (агент перед завершением сам себе)