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>
This commit is contained in:
2026-06-11 22:21:13 +03:00
parent 3f61f15507
commit 44e3ea90f9
2 changed files with 103 additions and 8 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

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