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:
|
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"
|
||||||
|
|||||||
Reference in New Issue
Block a user