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"