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:
20
home-configs/claude/hooks/effort-save-hook.sh
Normal file
20
home-configs/claude/hooks/effort-save-hook.sh
Normal 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
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user