Compare commits

..

2 Commits

Author SHA1 Message Date
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
44e3ea90f9 fix: изоляция effort между ai-* лаунчерами и персистентность
- _restore_effort: каждый лаунчер читает свой effort из
  ~/.cache/ai-setup/effort_<launcher> и записывает в settings.json
- effort-save-hook.sh: сохраняет effortLevel из settings.json в кэш
  при завершении сессии (через Claude Code hooks)
- Все лаунчеры (claude/deepseek/kimi/openrouter) экспортируют
  AI_LAUNCHER для идентификации в statusline и хуках
- _deepseek_balance: мультивалютный вывод (USD + CNY с символами $ и ¥)
- Дефолтные effort: claude=xhigh, deepseek/kimi/openrouter=high

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-11 22:21:13 +03:00
3 changed files with 145 additions and 22 deletions

View File

@@ -0,0 +1,20 @@
#!/usr/bin/env bash
# Сохраняет текущий effortLevel в кэш лаунчера при завершении сессии.
# /effort внутри Claude Code обновляет settings.json - мы читаем оттуда.
launcher="${AI_LAUNCHER:-}"
[ -z "$launcher" ] && exit 0
cat /dev/stdin > /dev/null 2>&1 # drain stdin (Claude Code передаёт JSON)
effort=$(python3 -c "
import json, os
p = os.path.expanduser('~/.claude/settings.json')
if os.path.exists(p):
try:
d = json.load(open(p))
print(d.get('effortLevel', ''))
except Exception:
pass
" 2>/dev/null)
[ -z "$effort" ] && exit 0
mkdir -p "$HOME/.cache/ai-setup"
echo "$effort" > "$HOME/.cache/ai-setup/effort_${launcher}"
exit 0

View File

@@ -9,15 +9,36 @@ week_pct=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empt
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[00;37m%s\033[00m" "$short_cwd"
printf "\033[38;5;250m%s\033[00m" "$short_cwd"
[ -n "$branch" ] && printf " \033[00;37m[%s]\033[00m" "$branch"
[ -n "$branch" ] && printf " \033[38;5;250m[%s]\033[00m" "$branch"
if [ -n "$model" ]; then
effort=$(echo "$input" | jq -r ".effort.level // empty")
[ -z "$effort" ] && effort=$(jq -r '.effortLevel // empty' ~/.claude/settings.json 2>/dev/null)
# Сохраняем 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)
@@ -58,12 +79,13 @@ if [ -n "$model" ]; then
fi
fi
fi
[ -n "$account" ] && printf " \033[38;5;173m[%s]\033[00m" "$account"
[ -n "$account" ] && printf " \033[38;5;223m[%s]\033[00m" "$account"
fi
if [ -n "$effort" ]; then
printf " \033[38;5;173m%s [%s]\033[00m" "$model" "$effort"
printf " \033[38;5;223m%s " "$model"
_effort_color "$effort"
else
printf " \033[38;5;173m%s\033[00m" "$model"
printf " \033[38;5;223m%s\033[00m" "$model"
fi
fi
@@ -90,21 +112,22 @@ fmt_remaining() {
pct_color() {
local pct="$1"
if [ "$pct" -lt 40 ]; then
printf '\033[00;32m'
printf '\033[38;5;114m' # мягкий зелёный
elif [ "$pct" -lt 60 ]; then
printf '\033[00;33m'
printf '\033[38;5;221m' # золотистый
else
printf '\033[00;31m'
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[00;35m\$%s\033[00m" "$balance"
[ -n "$balance" ] && printf " \033[38;5;147m%s\033[00m" "$balance"
fi
# Фоновое обновление баланса (не чаще раза в 30 секунд)
@@ -125,10 +148,15 @@ if [[ "$model_id" == *deepseek* ]]; then
import sys, json
d = json.load(sys.stdin)
infos = d.get('balance_infos', [])
if infos:
curr = infos[0].get('currency', '')
total = infos[0].get('total_balance', '0')
print(f'{total} {curr}')
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"

View File

@@ -667,6 +667,8 @@ if os.path.exists(settings_path):
except json.JSONDecodeError:
pass
data["statusLine"] = {"type": "command", "command": f"bash {script_path}"}
# Дефолтный effort для ai-claude (не передаётся через CLI, берётся отсюда)
data.setdefault("effortLevel", "xhigh")
# SessionStart хук - триггерит вызов statusLine при старте сессии
if "hooks" not in data:
data["hooks"] = {}
@@ -681,6 +683,40 @@ else
warn "Файл $STATUSLINE_SRC не найден, пропускаю"
fi
# ── 6.7.0. Хук effort-save (сохраняет effort при завершении сессии) ──
info "Деплою хук effort-save..."
EFFORT_HOOK_SRC="$SCRIPT_DIR/home-configs/claude/hooks/effort-save-hook.sh"
EFFORT_HOOK_DST="$HOME/.claude/hooks/effort-save-hook.sh"
mkdir -p "$HOME/.claude/hooks"
if [ -f "$EFFORT_HOOK_SRC" ]; then
cp "$EFFORT_HOOK_SRC" "$EFFORT_HOOK_DST"
chmod +x "$EFFORT_HOOK_DST"
python3 - "$HOME/.claude/settings.json" "$EFFORT_HOOK_DST" <<'PYEOF'
import sys, json, os
settings_path, hook_path = sys.argv[1], sys.argv[2]
data = {}
if os.path.exists(settings_path):
with open(settings_path) as f:
try: data = json.load(f)
except json.JSONDecodeError: pass
data.setdefault("hooks", {}).setdefault("Stop", [{"hooks": []}])
hook_cmd = f'bash "{hook_path}"'
stop_hooks = data["hooks"]["Stop"]
already = any(
any(h.get("command", "") == hook_cmd for h in entry.get("hooks", []))
for entry in stop_hooks
)
if not already:
stop_hooks[0]["hooks"].append({"type": "command", "command": hook_cmd})
with open(settings_path, "w") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
f.write("\n")
PYEOF
success "Хук effort-save установлен"
else
warn "Файл $EFFORT_HOOK_SRC не найден, пропускаю"
fi
# ── 6.7.1. Хук switch-account ───────────────────────────────────
info "Деплою хук switch-account..."
SWITCH_HOOK_SRC="$SCRIPT_DIR/home-configs/claude/hooks/switch-account-hook.sh"
@@ -858,25 +894,27 @@ try:
if not infos:
print(' \033[0;33m[БАЛАНС]\033[0m Нет данных о балансе')
sys.exit(0)
first = True
symbols = {'USD': '\$', 'CNY': '¥'}
cache_parts = []
for info in infos:
curr = info.get('currency', '???')
total = info.get('total_balance', '0')
granted = info.get('granted_balance', '0')
topped_up = info.get('topped_up_balance', '0')
sym = symbols.get(curr, curr)
status = '✅ доступен' if available else '❌ не активен'
print(f' \033[1;36m💰 Баланс DeepSeek:\033[0m {total} {curr} {status}')
if float(granted) > 0:
print(f' └─ Начислено: {granted} {curr}')
if float(topped_up) > 0:
print(f' └─ Пополнено: {topped_up} {curr}')
# Cache first currency entry for statusline
if first:
cache_parts.append(f'{sym}{total}')
# Cache all currencies with symbols for statusline
if cache_parts:
cache_dir = os.path.expanduser('~/.cache/ai-setup')
os.makedirs(cache_dir, exist_ok=True)
with open(os.path.join(cache_dir, 'deepseek_balance'), 'w') as f:
f.write(f'{total} {curr}\n')
first = False
f.write(' '.join(cache_parts) + '\n')
except Exception as e:
print(f' ⚠️ Не удалось разобрать баланс: {e}')
" 2>/dev/null || echo -e " \033[0;33m[БАЛАНС]\033[0m Ошибка парсинга ответа"
@@ -1039,6 +1077,35 @@ _open_browser() {
else echo "Откройте вручную: $url"; fi
}
# _restore_effort: читает сохранённый effort для текущего AI_LAUNCHER из кэша
# и записывает его в settings.json, чтобы Claude Code подхватил нужный уровень.
# Не передаём --effort через CLI, чтобы /effort внутри сессии работал без блокировки.
_restore_effort() {
local default_effort="${1:-high}"
local launcher="${AI_LAUNCHER:-}"
[ -z "$launcher" ] && return
local effort_file="$HOME/.cache/ai-setup/effort_${launcher}"
local effort
effort=$(cat "$effort_file" 2>/dev/null)
[ -z "$effort" ] && effort="$default_effort"
mkdir -p "$HOME/.cache/ai-setup"
python3 - "$HOME/.claude/settings.json" "$effort" <<'PYEOF'
import sys, json, os
settings_path, effort = sys.argv[1], sys.argv[2]
data = {}
if os.path.exists(settings_path):
try:
with open(settings_path) as f:
data = json.load(f)
except Exception:
pass
data['effortLevel'] = effort
with open(settings_path, 'w') as f:
json.dump(data, f, indent=2, ensure_ascii=False)
f.write('\n')
PYEOF
}
_build_ai_sys_prompt() {
local global_rules="$HOME/.config/ai-setup/global_rules.md"
local global_rendered=""
@@ -1160,6 +1227,8 @@ fi
_PROMPT_FILE=$(mktemp /tmp/ai-sys-prompt.XXXXXX)
trap 'rm -f "$_PROMPT_FILE"' EXIT INT TERM
_build_ai_sys_prompt > "$_PROMPT_FILE"
export AI_LAUNCHER=deepseek
_restore_effort high
ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic \
ANTHROPIC_AUTH_TOKEN="$api_key" \
ANTHROPIC_MODEL=deepseek-v4-pro \
@@ -1232,6 +1301,8 @@ fi
_PROMPT_FILE=$(mktemp /tmp/ai-sys-prompt.XXXXXX)
trap 'rm -f "$_PROMPT_FILE"' EXIT INT TERM
_build_ai_sys_prompt > "$_PROMPT_FILE"
export AI_LAUNCHER=kimi
_restore_effort high
ANTHROPIC_BASE_URL=https://api.kimi.com/coding \
ANTHROPIC_AUTH_TOKEN="$api_key" \
ANTHROPIC_MODEL=kimi-k2.6 \
@@ -1304,6 +1375,8 @@ fi
_PROMPT_FILE=$(mktemp /tmp/ai-sys-prompt.XXXXXX)
trap 'rm -f "$_PROMPT_FILE"' EXIT INT TERM
_build_ai_sys_prompt > "$_PROMPT_FILE"
export AI_LAUNCHER=openrouter
_restore_effort high
ANTHROPIC_BASE_URL=https://openrouter.ai/api \
ANTHROPIC_AUTH_TOKEN="$api_key" \
ANTHROPIC_MODEL=openai/gpt-5.5 \
@@ -1356,6 +1429,8 @@ source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/ai-api-helpers.sh" 2>/dev/
_PROMPT_FILE=$(mktemp /tmp/ai-sys-prompt.XXXXXX)
trap 'rm -f "$_PROMPT_FILE"' EXIT INT TERM
_build_ai_sys_prompt > "$_PROMPT_FILE"
export AI_LAUNCHER=claude
_restore_effort xhigh
claude --dangerously-skip-permissions --model sonnet --system-prompt-file "$_PROMPT_FILE" "$@"
CLAUDEEOF
chmod +x "$BIN_DIR/ai-claude"