Compare commits
2 Commits
3f61f15507
...
54742d6a36
| Author | SHA1 | Date | |
|---|---|---|---|
| 54742d6a36 | |||
| 44e3ea90f9 |
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
|
||||||
@@ -9,15 +9,36 @@ week_pct=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empt
|
|||||||
week_reset=$(echo "$input" | jq -r '.rate_limits.seven_day.resets_at // empty')
|
week_reset=$(echo "$input" | jq -r '.rate_limits.seven_day.resets_at // empty')
|
||||||
ctx_pct=$(echo "$input" | jq -r '.context_window.used_percentage // empty')
|
ctx_pct=$(echo "$input" | jq -r '.context_window.used_percentage // empty')
|
||||||
|
|
||||||
|
|
||||||
|
# Цвет effort: мягкая гармоничная палитра
|
||||||
|
_effort_color() {
|
||||||
|
case "$1" in
|
||||||
|
low) printf '\033[38;5;220m[low]\033[00m' ;; # золотой
|
||||||
|
medium) printf '\033[38;5;43m[medium]\033[00m' ;; # бирюзовый
|
||||||
|
high) printf '\033[38;5;39m[high]\033[00m' ;; # небесно-голубой
|
||||||
|
xhigh) printf '\033[38;5;171m[xhigh]\033[00m' ;; # лавандовый
|
||||||
|
max) printf '\033[38;5;210m[\033[38;5;220mm\033[38;5;114ma\033[38;5;43mx\033[38;5;171m]\033[00m' ;; # радуга
|
||||||
|
*) printf '\033[38;5;250m[%s]\033[00m' "$1" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
branch=$(git -C "$cwd" --no-optional-locks symbolic-ref --short HEAD 2>/dev/null)
|
branch=$(git -C "$cwd" --no-optional-locks symbolic-ref --short HEAD 2>/dev/null)
|
||||||
|
|
||||||
short_cwd="${cwd/#$HOME/\~}"
|
short_cwd="${cwd/#$HOME/\~}"
|
||||||
printf "\033[00;37m%s\033[00m" "$short_cwd"
|
printf "\033[38;5;250m%s\033[00m" "$short_cwd"
|
||||||
|
|
||||||
[ -n "$branch" ] && printf " \033[00;37m[%s]\033[00m" "$branch"
|
[ -n "$branch" ] && printf " \033[38;5;250m[%s]\033[00m" "$branch"
|
||||||
if [ -n "$model" ]; then
|
if [ -n "$model" ]; then
|
||||||
effort=$(echo "$input" | jq -r ".effort.level // empty")
|
effort=$(echo "$input" | jq -r ".effort.level // empty")
|
||||||
[ -z "$effort" ] && effort=$(jq -r '.effortLevel // empty' ~/.claude/settings.json 2>/dev/null)
|
# Сохраняем effort для persistence между сессиями одного лаунчера
|
||||||
|
if [ -n "$effort" ] && [ -n "${AI_LAUNCHER:-}" ]; then
|
||||||
|
effort_file="$HOME/.cache/ai-setup/effort_${AI_LAUNCHER}"
|
||||||
|
prev_effort=$(cat "$effort_file" 2>/dev/null)
|
||||||
|
if [ "$effort" != "$prev_effort" ]; then
|
||||||
|
mkdir -p "$HOME/.cache/ai-setup"
|
||||||
|
echo "$effort" > "$effort_file"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
# Аккаунт Claude.ai актуален только для нативных моделей Claude
|
# Аккаунт Claude.ai актуален только для нативных моделей Claude
|
||||||
if [[ "$model_id" == claude-* ]]; then
|
if [[ "$model_id" == claude-* ]]; then
|
||||||
account=$(cat ~/.claude/accounts/current 2>/dev/null)
|
account=$(cat ~/.claude/accounts/current 2>/dev/null)
|
||||||
@@ -58,12 +79,13 @@ if [ -n "$model" ]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
[ -n "$account" ] && printf " \033[38;5;173m[%s]\033[00m" "$account"
|
[ -n "$account" ] && printf " \033[38;5;223m[%s]\033[00m" "$account"
|
||||||
fi
|
fi
|
||||||
if [ -n "$effort" ]; then
|
if [ -n "$effort" ]; then
|
||||||
printf " \033[38;5;173m%s [%s]\033[00m" "$model" "$effort"
|
printf " \033[38;5;223m%s " "$model"
|
||||||
|
_effort_color "$effort"
|
||||||
else
|
else
|
||||||
printf " \033[38;5;173m%s\033[00m" "$model"
|
printf " \033[38;5;223m%s\033[00m" "$model"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -90,21 +112,22 @@ fmt_remaining() {
|
|||||||
pct_color() {
|
pct_color() {
|
||||||
local pct="$1"
|
local pct="$1"
|
||||||
if [ "$pct" -lt 40 ]; then
|
if [ "$pct" -lt 40 ]; then
|
||||||
printf '\033[00;32m'
|
printf '\033[38;5;114m' # мягкий зелёный
|
||||||
elif [ "$pct" -lt 60 ]; then
|
elif [ "$pct" -lt 60 ]; then
|
||||||
printf '\033[00;33m'
|
printf '\033[38;5;221m' # золотистый
|
||||||
else
|
else
|
||||||
printf '\033[00;31m'
|
printf '\033[38;5;210m' # мягкий красный
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# --- Баланс DeepSeek ---
|
# --- Баланс DeepSeek ---
|
||||||
# Моментально показываем кэшированный баланс, в фоне обновляем через API.
|
# Моментально показываем кэшированный баланс, в фоне обновляем через API.
|
||||||
if [[ "$model_id" == *deepseek* ]]; then
|
if [[ "$model_id" == *deepseek* ]]; then
|
||||||
cache_file="$HOME/.cache/ai-setup/deepseek_balance"
|
cache_file="$HOME/.cache/ai-setup/deepseek_balance"
|
||||||
if [ -f "$cache_file" ]; then
|
if [ -f "$cache_file" ]; then
|
||||||
balance=$(head -1 "$cache_file")
|
balance=$(head -1 "$cache_file")
|
||||||
[ -n "$balance" ] && printf " \033[00;35m\$%s\033[00m" "$balance"
|
[ -n "$balance" ] && printf " \033[38;5;147m%s\033[00m" "$balance"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Фоновое обновление баланса (не чаще раза в 30 секунд)
|
# Фоновое обновление баланса (не чаще раза в 30 секунд)
|
||||||
@@ -125,10 +148,15 @@ if [[ "$model_id" == *deepseek* ]]; then
|
|||||||
import sys, json
|
import sys, json
|
||||||
d = json.load(sys.stdin)
|
d = json.load(sys.stdin)
|
||||||
infos = d.get('balance_infos', [])
|
infos = d.get('balance_infos', [])
|
||||||
if infos:
|
symbols = {'USD': '\$', 'CNY': '\u00a5'}
|
||||||
curr = infos[0].get('currency', '')
|
parts = []
|
||||||
total = infos[0].get('total_balance', '0')
|
for info in infos:
|
||||||
print(f'{total} {curr}')
|
curr = info.get('currency', '')
|
||||||
|
total = info.get('total_balance', '0')
|
||||||
|
sym = symbols.get(curr, curr)
|
||||||
|
parts.append(f'{sym}{total}')
|
||||||
|
if parts:
|
||||||
|
print(' '.join(parts))
|
||||||
" 2>/dev/null)
|
" 2>/dev/null)
|
||||||
if [ -n "$new_balance" ]; then
|
if [ -n "$new_balance" ]; then
|
||||||
echo "$new_balance" > "$cache_file"
|
echo "$new_balance" > "$cache_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"
|
||||||
|
|||||||
Reference in New Issue
Block a user