From 44e3ea90f9823c21de856d8122117f18083b1773 Mon Sep 17 00:00:00 2001 From: vitaly Date: Thu, 11 Jun 2026 22:21:13 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20=D0=B8=D0=B7=D0=BE=D0=BB=D1=8F=D1=86?= =?UTF-8?q?=D0=B8=D1=8F=20effort=20=D0=BC=D0=B5=D0=B6=D0=B4=D1=83=20ai-*?= =?UTF-8?q?=20=D0=BB=D0=B0=D1=83=D0=BD=D1=87=D0=B5=D1=80=D0=B0=D0=BC=D0=B8?= =?UTF-8?q?=20=D0=B8=20=D0=BF=D0=B5=D1=80=D1=81=D0=B8=D1=81=D1=82=D0=B5?= =?UTF-8?q?=D0=BD=D1=82=D0=BD=D0=BE=D1=81=D1=82=D1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - _restore_effort: каждый лаунчер читает свой effort из ~/.cache/ai-setup/effort_ и записывает в 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 --- home-configs/claude/hooks/effort-save-hook.sh | 20 ++++ scripts/ai-setup.sh | 91 +++++++++++++++++-- 2 files changed, 103 insertions(+), 8 deletions(-) create mode 100644 home-configs/claude/hooks/effort-save-hook.sh diff --git a/home-configs/claude/hooks/effort-save-hook.sh b/home-configs/claude/hooks/effort-save-hook.sh new file mode 100644 index 0000000..6705535 --- /dev/null +++ b/home-configs/claude/hooks/effort-save-hook.sh @@ -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 diff --git a/scripts/ai-setup.sh b/scripts/ai-setup.sh index e6c12f6..6f0e67c 100755 --- a/scripts/ai-setup.sh +++ b/scripts/ai-setup.sh @@ -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_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 + 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(' '.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"