Compare commits
22 Commits
57d171a592
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 6c2844ce92 | |||
| dfa706e7a0 | |||
| 648c9f068b | |||
| f3d1b6d5c5 | |||
| fe439fd4a6 | |||
| f8465580e0 | |||
| 07983ea84e | |||
| 222bb129eb | |||
| 54742d6a36 | |||
| 44e3ea90f9 | |||
| 3f61f15507 | |||
|
|
23256d9579 | ||
| cff3ed880d | |||
| 08f23e857e | |||
| 986abf5101 | |||
| 7187aa6669 | |||
| c86110fbd6 | |||
| 76fb86f910 | |||
| 50c26736f1 | |||
| 71ef0f76f3 | |||
| 88061f310a | |||
| c6161c3332 |
@@ -9,9 +9,9 @@
|
|||||||
|
|
||||||
## Таблица маппинга
|
## Таблица маппинга
|
||||||
|
|
||||||
> Актуально на 30 мая 2026 г.
|
> Актуально на 12 июня 2026 г.
|
||||||
|
|
||||||
| Claude Code<br>`/effort` | Anthropic<br>(Claude) | GPT-5.5<br>(ChatGPT) | DeepSeek V4 | Kimi K2.6<br>(Moonshot) | Gemini 3.x |
|
| Claude Code<br>`/effort` | Anthropic<br>(Claude) | GPT-5.5<br>(ChatGPT) | DeepSeek V4 | Kimi K2.7<br>(Moonshot) | Gemini 3.x |
|
||||||
|:---:|:---:|:---:|:---:|:---:|:---:|
|
|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||||
| `low` | ✅ `low` | ✅ `low` | ⬆ `high` | 🔛 thinking on | ✅ `LOW` |
|
| `low` | ✅ `low` | ✅ `low` | ⬆ `high` | 🔛 thinking on | ✅ `LOW` |
|
||||||
| `medium` | ✅ `medium` | ✅ `medium` | ⬆ `high` | 🔛 thinking on | ✅ `MEDIUM` |
|
| `medium` | ✅ `medium` | ✅ `medium` | ⬆ `high` | 🔛 thinking on | ✅ `MEDIUM` |
|
||||||
@@ -54,7 +54,7 @@ high → max
|
|||||||
- `xhigh` → автоматически поднимается до `max`
|
- `xhigh` → автоматически поднимается до `max`
|
||||||
- Маппинг выполняется на стороне DeepSeek API
|
- Маппинг выполняется на стороне DeepSeek API
|
||||||
|
|
||||||
### Kimi K2.6 (Moonshot AI)
|
### Kimi K2.7 (Moonshot AI)
|
||||||
```
|
```
|
||||||
on / off
|
on / off
|
||||||
```
|
```
|
||||||
@@ -80,11 +80,37 @@ MINIMAL → LOW → MEDIUM → HIGH
|
|||||||
| Anthropic | Не нужен | — |
|
| Anthropic | Не нужен | — |
|
||||||
| GPT-5.5 | effort-proxy (наш) | `~/.local/bin/claude-gpt-effort-proxy.py` |
|
| GPT-5.5 | effort-proxy (наш) | `~/.local/bin/claude-gpt-effort-proxy.py` |
|
||||||
| DeepSeek V4 | DeepSeek API | На стороне сервера |
|
| DeepSeek V4 | DeepSeek API | На стороне сервера |
|
||||||
| Kimi K2.6 | Moonshot API | На стороне сервера |
|
| Kimi K2.7 | Moonshot API | На стороне сервера |
|
||||||
| Gemini 3.x | antigravity-claude-proxy | npm пакет |
|
| Gemini 3.x | antigravity-claude-proxy | npm пакет |
|
||||||
|
|
||||||
|
## Persistence effort между сессиями
|
||||||
|
|
||||||
|
Каждый лаунчер (`ai-claude`, `ai-deepseek`, `ai-kimi`, `ai-openrouter`) запоминает свой
|
||||||
|
уровень effort отдельно. Логика гибридная:
|
||||||
|
|
||||||
|
- **`low` / `medium` / `high` / `xhigh`** живут нативно в `settings.json` лаунчера.
|
||||||
|
`/effort` внутри сессии работает как обычно, уровень сохраняется между сессиями.
|
||||||
|
- **`max`** — единственный, который Claude Code **не сохраняет** в `settings.json`
|
||||||
|
(он session-only). Поэтому его восстанавливаем через `CLAUDE_CODE_EFFORT_LEVEL`.
|
||||||
|
Текущий уровень (включая `max`) статусбар пишет в `~/.cache/ai-setup/effort_<launcher>`.
|
||||||
|
|
||||||
|
**Важное следствие (только для `max`):** когда восстановлена `max`-сессия, выставлена
|
||||||
|
`CLAUDE_CODE_EFFORT_LEVEL=max`, и `/effort` внутри неё **не сменит** уровень
|
||||||
|
(env-переменная — жёсткий override Claude Code). На остальных уровнях `/effort` свободен.
|
||||||
|
|
||||||
|
**Как выйти из `max` (или форсить любой уровень):** перезапусти лаунчер с `AI_EFFORT`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
AI_EFFORT=max ai-deepseek # включить и запомнить max
|
||||||
|
AI_EFFORT=high ai-deepseek # вернуться на high (выйти из max)
|
||||||
|
ai-deepseek # без флага - восстанавливает последний уровень
|
||||||
|
```
|
||||||
|
|
||||||
|
Дефолты при пустом кэше: `xhigh` для `ai-claude`, `high` для остальных.
|
||||||
|
|
||||||
## Рекомендации
|
## Рекомендации
|
||||||
|
|
||||||
- **Для повседневной работы:** `high` или `xhigh` — работает одинаково хорошо у всех провайдеров
|
- **Для повседневной работы:** `high` или `xhigh` — работает одинаково хорошо у всех провайдеров
|
||||||
- **`max` effort:** имеет реальный эффект только у **Anthropic** и **DeepSeek**. Для GPT маппится в `xhigh`, для Gemini и Kimi — в их максимальный уровень
|
- **`max` effort:** имеет реальный эффект только у **Anthropic** и **DeepSeek**. Для GPT маппится в `xhigh`, для Gemini и Kimi — в их максимальный уровень
|
||||||
- **`low`/`medium`:** у DeepSeek и Kimi фактически не снижают reasoning — DeepSeek поднимет до `high`, Kimi просто включит thinking
|
- **`low`/`medium`:** у DeepSeek и Kimi фактически не снижают reasoning — DeepSeek поднимет до `high`, Kimi просто включит thinking
|
||||||
|
- **Смена уровня:** на `low..xhigh` обычным `/effort`; из `max` — через `AI_EFFORT=<lvl> ai-<launcher>` (в max-сессии `/effort` залочен env-переменной, см. «Persistence effort»)
|
||||||
|
|||||||
33
home-configs/claude/hooks/account-email.sh
Normal file
33
home-configs/claude/hooks/account-email.sh
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Определяет email Claude.ai аккаунта по OAuth-токену в credentials-файле.
|
||||||
|
# Источник истины — сам токен (НЕ claude auth status, который читает
|
||||||
|
# рассинхронизированный oauthAccount из ~/.claude.json).
|
||||||
|
# Сначала локальный матчинг с сохранёнными accounts/, затем API /api/oauth/profile.
|
||||||
|
# Использование: account-email.sh [credentials-file] (по умолчанию ~/.claude/.credentials.json)
|
||||||
|
|
||||||
|
CREDS="${1:-$HOME/.claude/.credentials.json}"
|
||||||
|
ACCOUNTS_DIR="$HOME/.claude/accounts"
|
||||||
|
|
||||||
|
[ -f "$CREDS" ] || exit 0
|
||||||
|
token=$(jq -r '.claudeAiOauth.accessToken // empty' "$CREDS" 2>/dev/null)
|
||||||
|
[ -z "$token" ] && exit 0
|
||||||
|
|
||||||
|
# 1) Локальный матчинг по токену с сохранёнными аккаунтами (мгновенно)
|
||||||
|
if [ -d "$ACCOUNTS_DIR" ]; then
|
||||||
|
for f in "$ACCOUNTS_DIR"/*.credentials.json; do
|
||||||
|
[ -f "$f" ] || continue
|
||||||
|
t=$(jq -r '.claudeAiOauth.accessToken // empty' "$f" 2>/dev/null)
|
||||||
|
if [ -n "$t" ] && [ "$t" = "$token" ]; then
|
||||||
|
basename "$f" .credentials.json
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2) Достоверно через API (по реальному владельцу токена)
|
||||||
|
email=$(curl -s --max-time 15 "https://api.anthropic.com/api/oauth/profile" \
|
||||||
|
-H "Authorization: Bearer $token" \
|
||||||
|
-H "anthropic-beta: oauth-2025-04-20" 2>/dev/null \
|
||||||
|
| jq -r '.account.email // empty' 2>/dev/null)
|
||||||
|
[ -n "$email" ] && echo "$email"
|
||||||
|
exit 0
|
||||||
47
home-configs/claude/hooks/add-account-hook.sh
Executable file
47
home-configs/claude/hooks/add-account-hook.sh
Executable file
@@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# UserPromptSubmit hook: перехватывает /add-account.
|
||||||
|
# 1) сохраняет текущий аккаунт по его реальному email (account-email.sh)
|
||||||
|
# 2) запускает oauth-логин в фоне (открывает браузер)
|
||||||
|
# 3) после логина фоновый процесс сам определяет email нового аккаунта по токену
|
||||||
|
# и сохраняет его credentials + делает current
|
||||||
|
|
||||||
|
input=$(cat)
|
||||||
|
prompt=$(echo "$input" | jq -r '.user_prompt // .prompt // empty' 2>/dev/null)
|
||||||
|
normalized=$(echo "$prompt" | sed 's|^[[:space:]]*/||; s|[[:space:]]*$||')
|
||||||
|
|
||||||
|
[ "$normalized" != "add-account" ] && exit 0
|
||||||
|
|
||||||
|
CREDS="$HOME/.claude/.credentials.json"
|
||||||
|
ACCOUNTS_DIR="$HOME/.claude/accounts"
|
||||||
|
CURRENT_FILE="$ACCOUNTS_DIR/current"
|
||||||
|
EMAIL_HELPER="$HOME/.claude/hooks/account-email.sh"
|
||||||
|
|
||||||
|
mkdir -p "$ACCOUNTS_DIR"
|
||||||
|
|
||||||
|
# Сохраняем текущий активный аккаунт под его реальным email (по токену)
|
||||||
|
if [ -f "$CREDS" ]; then
|
||||||
|
cur_email=$(bash "$EMAIL_HELPER" "$CREDS" 2>/dev/null)
|
||||||
|
if [ -n "$cur_email" ]; then
|
||||||
|
cp "$CREDS" "$ACCOUNTS_DIR/${cur_email}.credentials.json"
|
||||||
|
chmod 600 "$ACCOUNTS_DIR/${cur_email}.credentials.json"
|
||||||
|
echo "$cur_email" > "$CURRENT_FILE"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Фоновый процесс: логин нового аккаунта + автосохранение после успеха.
|
||||||
|
# claude auth login ждёт авторизации в браузере и завершается после неё,
|
||||||
|
# затем определяем email нового аккаунта по токену (через API) и сохраняем.
|
||||||
|
(
|
||||||
|
claude auth login --claudeai </dev/null >/tmp/claude-add-account.log 2>&1
|
||||||
|
new_email=$(bash "$EMAIL_HELPER" "$CREDS" 2>/dev/null)
|
||||||
|
if [ -n "$new_email" ] && [ -f "$CREDS" ]; then
|
||||||
|
cp "$CREDS" "$ACCOUNTS_DIR/${new_email}.credentials.json"
|
||||||
|
chmod 600 "$ACCOUNTS_DIR/${new_email}.credentials.json"
|
||||||
|
echo "$new_email" > "$CURRENT_FILE"
|
||||||
|
echo "SAVED: $new_email" >> /tmp/claude-add-account.log
|
||||||
|
fi
|
||||||
|
) &
|
||||||
|
disown
|
||||||
|
|
||||||
|
# exit 0: Claude загружает скилл add-account и говорит что делать
|
||||||
|
exit 0
|
||||||
54
home-configs/claude/hooks/switch-account-hook.sh
Executable file
54
home-configs/claude/hooks/switch-account-hook.sh
Executable file
@@ -0,0 +1,54 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# UserPromptSubmit hook: перехватывает /switch-account и циклически меняет аккаунт.
|
||||||
|
# Текущий аккаунт определяется ПО ТОКЕНУ в .credentials.json (account-email.sh),
|
||||||
|
# а не по claude auth status — он читает рассинхронизированный oauthAccount.
|
||||||
|
# На Linux Claude Code перечитывает .credentials.json на лету: новый аккаунт
|
||||||
|
# применяется со следующего сообщения, перезапуск не нужен.
|
||||||
|
# exit 0 (не exit 2): /switch-account доходит до Claude, грузится скилл,
|
||||||
|
# отвечает "✓" (1 токен) — так перерисовывается статусная строка.
|
||||||
|
|
||||||
|
input=$(cat)
|
||||||
|
prompt=$(echo "$input" | jq -r '.user_prompt // .prompt // empty' 2>/dev/null)
|
||||||
|
normalized=$(echo "$prompt" | sed 's|^[[:space:]]*/||; s|[[:space:]]*$||')
|
||||||
|
|
||||||
|
[ "$normalized" != "switch-account" ] && exit 0
|
||||||
|
|
||||||
|
ACCOUNTS_DIR="$HOME/.claude/accounts"
|
||||||
|
CREDS="$HOME/.claude/.credentials.json"
|
||||||
|
CURRENT_FILE="$ACCOUNTS_DIR/current"
|
||||||
|
EMAIL_HELPER="$HOME/.claude/hooks/account-email.sh"
|
||||||
|
|
||||||
|
mkdir -p "$ACCOUNTS_DIR"
|
||||||
|
|
||||||
|
# Реальный текущий аккаунт — по токену активной сессии (не по хрупкому current)
|
||||||
|
current=$(bash "$EMAIL_HELPER" "$CREDS" 2>/dev/null)
|
||||||
|
[ -z "$current" ] && current=$(cat "$CURRENT_FILE" 2>/dev/null)
|
||||||
|
|
||||||
|
# Сохранить актуальные (возможно обновлённые рефрешем) токены под реальным email.
|
||||||
|
# current выведен из самого токена — порча файла другого аккаунта исключена.
|
||||||
|
if [ -n "$current" ] && [ -f "$CREDS" ]; then
|
||||||
|
cp "$CREDS" "$ACCOUNTS_DIR/${current}.credentials.json"
|
||||||
|
chmod 600 "$ACCOUNTS_DIR/${current}.credentials.json"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mapfile -t accounts < <(ls "$ACCOUNTS_DIR"/*.credentials.json 2>/dev/null \
|
||||||
|
| xargs -I{} basename {} .credentials.json | sort)
|
||||||
|
|
||||||
|
if [ ${#accounts[@]} -le 1 ]; then
|
||||||
|
echo "Только один аккаунт (${current:-нет}). Добавь второй через /add-account." >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Найти следующий по кругу
|
||||||
|
idx=-1
|
||||||
|
for i in "${!accounts[@]}"; do
|
||||||
|
[ "${accounts[$i]}" = "$current" ] && idx=$i && break
|
||||||
|
done
|
||||||
|
next_idx=$(( (idx + 1) % ${#accounts[@]} ))
|
||||||
|
next="${accounts[$next_idx]}"
|
||||||
|
|
||||||
|
cp "$ACCOUNTS_DIR/${next}.credentials.json" "$CREDS"
|
||||||
|
chmod 600 "$CREDS"
|
||||||
|
echo "$next" > "$CURRENT_FILE"
|
||||||
|
|
||||||
|
exit 0
|
||||||
8
home-configs/claude/skills/add-account/SKILL.md
Normal file
8
home-configs/claude/skills/add-account/SKILL.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
name: add-account
|
||||||
|
description: Add a new Claude.ai account (handled by UserPromptSubmit hook, no LLM needed)
|
||||||
|
---
|
||||||
|
|
||||||
|
Хук сохранил текущий аккаунт и открыл браузер для логина нового. Ответь ТОЛЬКО этим текстом (без markdown, без лишних слов):
|
||||||
|
|
||||||
|
Браузер открыт — авторизуйся там. После авторизации новый аккаунт сохранится автоматически и сразу станет активным (Claude Code на Linux перечитывает токен на лету). /switch-account переключает между всеми сохранёнными аккаунтами по кругу.
|
||||||
8
home-configs/claude/skills/switch-account/SKILL.md
Normal file
8
home-configs/claude/skills/switch-account/SKILL.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
name: switch-account
|
||||||
|
description: Switch to next Claude.ai account (handled by UserPromptSubmit hook, no LLM needed)
|
||||||
|
---
|
||||||
|
|
||||||
|
Переключение аккаунта уже выполнено хуком до того, как ты это читаешь.
|
||||||
|
Ответь ровно одним символом: `✓`
|
||||||
|
Никаких инструментов. Никаких объяснений. Только `✓`.
|
||||||
159
home-configs/claude/statusline-command.sh
Normal file → Executable file
159
home-configs/claude/statusline-command.sh
Normal file → Executable file
@@ -9,13 +9,98 @@ 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: насыщенные, глубже чем пастельные (jewel tones)
|
||||||
|
_effort_color() {
|
||||||
|
case "$1" in
|
||||||
|
low) printf '\033[38;5;220m[low]\033[00m' ;; # золотой
|
||||||
|
medium) printf '\033[38;5;50m[medium]\033[00m' ;; # насыщенный teal
|
||||||
|
high) printf '\033[38;5;38m[high]\033[00m' ;; # глубокий голубой
|
||||||
|
xhigh) printf '\033[38;5;206m[xhigh]\033[00m' ;; # насыщенный фиолетовый
|
||||||
|
max) printf '\033[38;5;210m[\033[38;5;220mm\033[38;5;114ma\033[38;5;50mx\033[38;5;206m]\033[00m' ;; # радуга
|
||||||
|
*) printf '\033[38;5;252m[%s]\033[00m' "$1" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Брендовый цвет имени модели по лаунчеру
|
||||||
|
_brand_color() {
|
||||||
|
case "${1:-}" in
|
||||||
|
deepseek) printf '\033[38;5;69m' ;; # DeepSeek фирменный синий
|
||||||
|
claude) printf '\033[38;5;173m' ;; # Anthropic оранжевый
|
||||||
|
kimi) printf '\033[38;5;81m' ;; # Kimi голубой
|
||||||
|
openrouter) printf '\033[38;5;135m' ;; # OpenRouter фиолетовый
|
||||||
|
*) printf '\033[38;5;223m' ;; # кремовый (fallback)
|
||||||
|
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;252m%s\033[00m" "$short_cwd"
|
||||||
|
|
||||||
[ -n "$branch" ] && printf " \033[00;37m[%s]\033[00m" "$branch"
|
[ -n "$branch" ] && printf " \033[38;5;252m[%s]\033[00m" "$branch"
|
||||||
[ -n "$model" ] && printf " \033[38;5;173m%s\033[00m" "$model"
|
if [ -n "$model" ]; then
|
||||||
|
brand_color=$(_brand_color "${AI_LAUNCHER:-}")
|
||||||
|
effort=$(echo "$input" | jq -r ".effort.level // empty")
|
||||||
|
# Ловим выбранный уровень в кэш лаунчера (чтобы запомнить max между сессиями).
|
||||||
|
# Когда CLAUDE_CODE_EFFORT_LEVEL выставлена (восстановленная max-сессия) - уровень
|
||||||
|
# форсится env, кэш НЕ трогаем, чтобы дисплей-баг (.effort.level=xhigh) не затёр max.
|
||||||
|
if [ -n "${AI_LAUNCHER:-}" ] && [ -z "${CLAUDE_CODE_EFFORT_LEVEL:-}" ] && [ -n "$effort" ]; then
|
||||||
|
effort_file="$HOME/.cache/ai-setup/effort_${AI_LAUNCHER}"
|
||||||
|
if [ "$effort" != "$(cat "$effort_file" 2>/dev/null)" ]; then
|
||||||
|
mkdir -p "$HOME/.cache/ai-setup"
|
||||||
|
echo "$effort" > "$effort_file"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
# Аккаунт Claude.ai актуален только для нативных моделей Claude
|
||||||
|
if [[ "$model_id" == claude-* ]]; then
|
||||||
|
account=$(cat ~/.claude/accounts/current 2>/dev/null)
|
||||||
|
ACCOUNTS_DIR="$HOME/.claude/accounts"
|
||||||
|
# Автоопределение: если current пуст или файл не существует —
|
||||||
|
# ищем аккаунт по access-токену в .credentials.json
|
||||||
|
if [ -z "$account" ] || [ ! -f "$ACCOUNTS_DIR/${account}.credentials.json" ]; then
|
||||||
|
current_token=$(jq -r '.claudeAiOauth.accessToken // empty' "$HOME/.claude/.credentials.json" 2>/dev/null)
|
||||||
|
if [ -n "$current_token" ]; then
|
||||||
|
for f in "$ACCOUNTS_DIR"/*.credentials.json; do
|
||||||
|
[ ! -f "$f" ] && continue
|
||||||
|
token=$(jq -r '.claudeAiOauth.accessToken // empty' "$f" 2>/dev/null)
|
||||||
|
if [ "$token" = "$current_token" ]; then
|
||||||
|
account=$(basename "$f" .credentials.json)
|
||||||
|
echo "$account" > "$ACCOUNTS_DIR/current"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
# Если токен не найден — спрашиваем haiku (один раз на аккаунт)
|
||||||
|
if [ -z "$account" ]; then
|
||||||
|
sentinel="$HOME/.cache/ai-setup/email_fetch_token"
|
||||||
|
prev_token=$(cat "$sentinel" 2>/dev/null)
|
||||||
|
if [ "$prev_token" != "$current_token" ]; then
|
||||||
|
mkdir -p "$HOME/.cache/ai-setup"
|
||||||
|
echo "$current_token" > "$sentinel"
|
||||||
|
email=$(unset ANTHROPIC_BASE_URL ANTHROPIC_AUTH_TOKEN; \
|
||||||
|
echo 'какой имейл этого аккаунта? напиши только имейл без других слов.' | \
|
||||||
|
claude --print --model claude-haiku-4-5 --dangerously-skip-permissions \
|
||||||
|
--output-format text --max-turns 1 --tools "" --effort low 2>/dev/null)
|
||||||
|
email=$(echo "$email" | grep -oE '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}' | head -1)
|
||||||
|
if [ -n "$email" ]; then
|
||||||
|
cp "$HOME/.claude/.credentials.json" "$ACCOUNTS_DIR/${email}.credentials.json"
|
||||||
|
chmod 600 "$ACCOUNTS_DIR/${email}.credentials.json"
|
||||||
|
account="$email"
|
||||||
|
echo "$account" > "$ACCOUNTS_DIR/current"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
[ -n "$account" ] && printf " %s[%s]\033[00m" "$brand_color" "$account"
|
||||||
|
fi
|
||||||
|
if [ -n "$effort" ]; then
|
||||||
|
printf " %s%s " "$brand_color" "$model"
|
||||||
|
_effort_color "$effort"
|
||||||
|
else
|
||||||
|
printf " %s%s\033[00m" "$brand_color" "$model"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Форматирует оставшееся время до сброса лимита
|
# Форматирует оставшееся время до сброса лимита
|
||||||
fmt_remaining() {
|
fmt_remaining() {
|
||||||
@@ -40,21 +125,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;220m' # золотой (как effort low)
|
||||||
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;78m%s\033[00m" "$balance"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Фоновое обновление баланса (не чаще раза в 30 секунд)
|
# Фоновое обновление баланса (не чаще раза в 30 секунд)
|
||||||
@@ -75,10 +161,54 @@ 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)
|
||||||
|
if [ -n "$new_balance" ]; then
|
||||||
|
echo "$new_balance" > "$cache_file"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
) &
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
elif [ "${AI_LAUNCHER:-}" = "openrouter" ]; then
|
||||||
|
# --- Баланс OpenRouter ---
|
||||||
|
# Моментально показываем кэшированный остаток, в фоне обновляем через API.
|
||||||
|
cache_file="$HOME/.cache/ai-setup/openrouter_balance"
|
||||||
|
if [ -f "$cache_file" ]; then
|
||||||
|
balance=$(head -1 "$cache_file")
|
||||||
|
[ -n "$balance" ] && printf " \033[38;5;78m%s\033[00m" "$balance"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Фоновое обновление баланса (не чаще раза в 30 секунд)
|
||||||
|
refresh_ts="$HOME/.cache/ai-setup/openrouter_balance_refresh_ts"
|
||||||
|
now=$(date +%s)
|
||||||
|
last=$(cat "$refresh_ts" 2>/dev/null || echo 0)
|
||||||
|
if [ $(( now - last )) -gt 30 ]; then
|
||||||
|
key_file="$HOME/.config/ai-setup/openrouter_key"
|
||||||
|
if [ -f "$key_file" ]; then
|
||||||
|
echo "$now" > "$refresh_ts" 2>/dev/null
|
||||||
|
(
|
||||||
|
api_key=$(cat "$key_file")
|
||||||
|
resp=$(curl -s --max-time 10 "https://openrouter.ai/api/v1/credits" \
|
||||||
|
-H "Authorization: Bearer $api_key" \
|
||||||
|
-H "Accept: application/json" 2>/dev/null)
|
||||||
|
if [ -n "$resp" ]; then
|
||||||
|
new_balance=$(echo "$resp" | python3 -c "
|
||||||
|
import sys, json
|
||||||
|
d = json.load(sys.stdin)
|
||||||
|
data = d.get('data', {})
|
||||||
|
total = data.get('total_credits', 0) or 0
|
||||||
|
usage = data.get('total_usage', 0) or 0
|
||||||
|
remaining = total - usage
|
||||||
|
print(f'${remaining:.2f}')
|
||||||
" 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"
|
||||||
@@ -88,9 +218,10 @@ if infos:
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
# Рейт-лимиты для НЕ-DeepSeek провайдеров
|
# Рейт-лимиты для НЕ-DeepSeek/OpenRouter провайдеров
|
||||||
# Кеш специфичен для провайдера (по model_id) чтобы не смешивать claude/kimi/openrouter
|
# Кеш специфичен для провайдера (model_id) И аккаунта (account): лимиты привязаны
|
||||||
_cache_key=$(echo "${model_id:-unknown}" | sed 's/[^a-zA-Z0-9._-]/_/g')
|
# к аккаунту, поэтому при переключении /switch-account проценты не должны смешиваться.
|
||||||
|
_cache_key=$(echo "${model_id:-unknown}_${account:-}" | sed 's/[^a-zA-Z0-9._-]/_/g')
|
||||||
RATE_CACHE="$HOME/.cache/ai-setup/rate_limits_${_cache_key}.cache"
|
RATE_CACHE="$HOME/.cache/ai-setup/rate_limits_${_cache_key}.cache"
|
||||||
mkdir -p "$HOME/.cache/ai-setup"
|
mkdir -p "$HOME/.cache/ai-setup"
|
||||||
|
|
||||||
|
|||||||
@@ -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,121 @@ else
|
|||||||
warn "Файл $STATUSLINE_SRC не найден, пропускаю"
|
warn "Файл $STATUSLINE_SRC не найден, пропускаю"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ── 6.7.0. Чистка устаревшего хука effort-save ──────────────
|
||||||
|
# effort/model теперь персистятся нативно в settings.json каждого CLAUDE_CONFIG_DIR
|
||||||
|
# (полная изоляция лаунчеров), самопальный кэш ~/.cache/ai-setup/{effort,model}_* не нужен.
|
||||||
|
# Удаляем старый хук с диска и из ~/.claude/settings.json (нотифаер в Stop сохраняем).
|
||||||
|
info "Удаляю устаревший хук effort-save..."
|
||||||
|
rm -f "$HOME/.claude/hooks/effort-save-hook.sh"
|
||||||
|
# Осиротевший кэш моделей (источник старой протечки между ai-*); effort_* НЕ трогаем -
|
||||||
|
# он снова используется для персиста effort (включая max) через CLAUDE_CODE_EFFORT_LEVEL.
|
||||||
|
rm -f "$HOME"/.cache/ai-setup/model_*
|
||||||
|
python3 - "$HOME/.claude/settings.json" <<'PYEOF'
|
||||||
|
import sys, json, os
|
||||||
|
settings_path = sys.argv[1]
|
||||||
|
if not os.path.exists(settings_path):
|
||||||
|
sys.exit(0)
|
||||||
|
try:
|
||||||
|
with open(settings_path) as f:
|
||||||
|
data = json.load(f)
|
||||||
|
except Exception:
|
||||||
|
sys.exit(0)
|
||||||
|
stop = data.get("hooks", {}).get("Stop")
|
||||||
|
if isinstance(stop, list):
|
||||||
|
for entry in stop:
|
||||||
|
hooks = entry.get("hooks")
|
||||||
|
if isinstance(hooks, list):
|
||||||
|
entry["hooks"] = [h for h in hooks if "effort-save-hook" not in h.get("command", "")]
|
||||||
|
data["hooks"]["Stop"] = [e for e in stop if e.get("hooks")]
|
||||||
|
if not data["hooks"]["Stop"]:
|
||||||
|
del data["hooks"]["Stop"]
|
||||||
|
with open(settings_path, "w") as f:
|
||||||
|
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||||
|
f.write("\n")
|
||||||
|
PYEOF
|
||||||
|
success "Старый хук effort-save удалён"
|
||||||
|
|
||||||
|
# ── 6.7.05. Хелпер account-email (определение email по токену) ──
|
||||||
|
# Вспомогательный скрипт для хуков switch-account/add-account.
|
||||||
|
# Не регистрируется в settings.json — вызывается из хуков напрямую.
|
||||||
|
EMAIL_HELPER_SRC="$SCRIPT_DIR/home-configs/claude/hooks/account-email.sh"
|
||||||
|
EMAIL_HELPER_DST="$HOME/.claude/hooks/account-email.sh"
|
||||||
|
mkdir -p "$HOME/.claude/hooks"
|
||||||
|
if [ -f "$EMAIL_HELPER_SRC" ]; then
|
||||||
|
cp "$EMAIL_HELPER_SRC" "$EMAIL_HELPER_DST"
|
||||||
|
chmod +x "$EMAIL_HELPER_DST"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── 6.7.1. Хук switch-account ───────────────────────────────────
|
||||||
|
info "Деплою хук switch-account..."
|
||||||
|
SWITCH_HOOK_SRC="$SCRIPT_DIR/home-configs/claude/hooks/switch-account-hook.sh"
|
||||||
|
SWITCH_HOOK_DST="$HOME/.claude/hooks/switch-account-hook.sh"
|
||||||
|
mkdir -p "$HOME/.claude/hooks"
|
||||||
|
if [ -f "$SWITCH_HOOK_SRC" ]; then
|
||||||
|
cp "$SWITCH_HOOK_SRC" "$SWITCH_HOOK_DST"
|
||||||
|
chmod +x "$SWITCH_HOOK_DST"
|
||||||
|
# Прописываем хук в settings.json (идемпотентно)
|
||||||
|
python3 - "$HOME/.claude/settings.json" "$SWITCH_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("UserPromptSubmit", [{"hooks": []}])
|
||||||
|
hook_cmd = f'bash "{hook_path}"'
|
||||||
|
ups = data["hooks"]["UserPromptSubmit"]
|
||||||
|
already = any(
|
||||||
|
any(h.get("command", "") == hook_cmd for h in entry.get("hooks", []))
|
||||||
|
for entry in ups
|
||||||
|
)
|
||||||
|
if not already:
|
||||||
|
ups[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 "Хук switch-account установлен"
|
||||||
|
else
|
||||||
|
warn "Файл $SWITCH_HOOK_SRC не найден, пропускаю"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── 6.7.2. Хук add-account ──────────────────────────────────────
|
||||||
|
info "Деплою хук add-account..."
|
||||||
|
ADD_HOOK_SRC="$SCRIPT_DIR/home-configs/claude/hooks/add-account-hook.sh"
|
||||||
|
ADD_HOOK_DST="$HOME/.claude/hooks/add-account-hook.sh"
|
||||||
|
mkdir -p "$HOME/.claude/hooks"
|
||||||
|
if [ -f "$ADD_HOOK_SRC" ]; then
|
||||||
|
cp "$ADD_HOOK_SRC" "$ADD_HOOK_DST"
|
||||||
|
chmod +x "$ADD_HOOK_DST"
|
||||||
|
# Прописываем хук в settings.json (идемпотентно)
|
||||||
|
python3 - "$HOME/.claude/settings.json" "$ADD_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("UserPromptSubmit", [{"hooks": []}])
|
||||||
|
hook_cmd = f'bash "{hook_path}"'
|
||||||
|
ups = data["hooks"]["UserPromptSubmit"]
|
||||||
|
already = any(
|
||||||
|
any(h.get("command", "") == hook_cmd for h in entry.get("hooks", []))
|
||||||
|
for entry in ups
|
||||||
|
)
|
||||||
|
if not already:
|
||||||
|
ups[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 "Хук add-account установлен"
|
||||||
|
else
|
||||||
|
warn "Файл $ADD_HOOK_SRC не найден, пропускаю"
|
||||||
|
fi
|
||||||
|
|
||||||
# ── 6.8. Регистрация официального маркетплейса плагинов Claude ──
|
# ── 6.8. Регистрация официального маркетплейса плагинов Claude ──
|
||||||
info "Настраиваю маркетплейс плагинов Claude Code..."
|
info "Настраиваю маркетплейс плагинов Claude Code..."
|
||||||
if ! command -v claude &>/dev/null; then
|
if ! command -v claude &>/dev/null; then
|
||||||
@@ -823,25 +940,60 @@ 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:
|
||||||
|
print(f' ⚠️ Не удалось разобрать баланс: {e}')
|
||||||
|
" 2>/dev/null || echo -e " \033[0;33m[БАЛАНС]\033[0m Ошибка парсинга ответа"
|
||||||
|
}
|
||||||
|
|
||||||
|
# _openrouter_balance: Query OpenRouter credits API and print balance
|
||||||
|
# Uses /api/v1/credits (works with regular API key too)
|
||||||
|
_openrouter_balance() {
|
||||||
|
local api_key="$1"
|
||||||
|
local response
|
||||||
|
response=$(curl -s --max-time 10 "https://openrouter.ai/api/v1/credits" \
|
||||||
|
-H "Authorization: Bearer $api_key" \
|
||||||
|
-H "Accept: application/json" \
|
||||||
|
2>/dev/null || echo "")
|
||||||
|
if [ -z "$response" ]; then
|
||||||
|
echo -e " \033[0;33m[БАЛАНС]\033[0m Не удалось получить баланс (сеть?)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
echo "$response" | python3 -c "
|
||||||
|
import sys, json, os
|
||||||
|
try:
|
||||||
|
d = json.load(sys.stdin)
|
||||||
|
data = d.get('data', {})
|
||||||
|
total = data.get('total_credits', 0) or 0
|
||||||
|
usage = data.get('total_usage', 0) or 0
|
||||||
|
remaining = total - usage
|
||||||
|
cache_dir = os.path.expanduser('~/.cache/ai-setup')
|
||||||
|
os.makedirs(cache_dir, exist_ok=True)
|
||||||
|
cache_file = os.path.join(cache_dir, 'openrouter_balance')
|
||||||
|
# Зелёный как у DeepSeek: \033[38;5;78m
|
||||||
|
print(f' \033[1;36m💰 Баланс OpenRouter:\033[0m \033[38;5;78m\${remaining:.2f}\033[0m (загружено \${total:.2f}, потрачено \${usage:.2f})')
|
||||||
|
with open(cache_file, 'w') as f:
|
||||||
|
f.write(f'\${remaining:.2f}\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 Ошибка парсинга ответа"
|
||||||
@@ -1004,6 +1156,97 @@ _open_browser() {
|
|||||||
else echo "Откройте вручную: $url"; fi
|
else echo "Откройте вручную: $url"; fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# _setup_isolated_config: готовит изолированную папку CLAUDE_CONFIG_DIR для лаунчера.
|
||||||
|
# Каждый сторонний провайдер получает собственные settings.json и .claude.json,
|
||||||
|
# поэтому выбор модели и кэш кастом-моделей НЕ протекают между ai-* лаунчерами.
|
||||||
|
# Общие ресурсы (skills, CLAUDE.md) шарятся симлинком из ~/.claude.
|
||||||
|
# model и effortLevel сидируются как дефолты - выбор юзера через /model и /effort
|
||||||
|
# (для low/medium/high/xhigh) сохраняется нативно в этом же settings.json.
|
||||||
|
# Уровень max обрабатывается отдельно в _apply_effort (settings.json его не хранит).
|
||||||
|
# Использование: _setup_isolated_config <launcher> <default_model> <default_effort> <available_models_json>
|
||||||
|
_setup_isolated_config() {
|
||||||
|
local launcher="$1" default_model="$2" default_effort="${3:-high}" avail="${4:-}"
|
||||||
|
local cfg="$HOME/.config/ai-setup/cfg/$launcher"
|
||||||
|
mkdir -p "$cfg"
|
||||||
|
# Общие ресурсы из ~/.claude (единый источник правды)
|
||||||
|
ln -sfn "$HOME/.claude/skills" "$cfg/skills"
|
||||||
|
ln -sfn "$HOME/.claude/CLAUDE.md" "$cfg/CLAUDE.md"
|
||||||
|
[ -e "$HOME/.claude/agents" ] && ln -sfn "$HOME/.claude/agents" "$cfg/agents"
|
||||||
|
# .claude.json в свежей папке: пропускаем онбординг
|
||||||
|
[ -f "$cfg/.claude.json" ] || echo '{"hasCompletedOnboarding": true}' > "$cfg/.claude.json"
|
||||||
|
python3 - "$cfg/settings.json" "$HOME/.claude/statusline-command.sh" \
|
||||||
|
"$default_model" "$default_effort" "$avail" <<'PYEOF'
|
||||||
|
import sys, json, os
|
||||||
|
cfg_settings, statusline, model, effort, avail = sys.argv[1:6]
|
||||||
|
data = {}
|
||||||
|
if os.path.exists(cfg_settings):
|
||||||
|
try:
|
||||||
|
with open(cfg_settings) as f:
|
||||||
|
data = json.load(f)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
# Структурные настройки лаунчера (переустанавливаем всегда)
|
||||||
|
data["statusLine"] = {"type": "command", "command": f"bash {statusline}"}
|
||||||
|
data["skipDangerousModePermissionPrompt"] = True
|
||||||
|
data.setdefault("hooks", {})["SessionStart"] = [{"hooks": [{"type": "command", "command": "true"}]}]
|
||||||
|
# availableModels - белый список пикера (политика лаунчера)
|
||||||
|
if avail:
|
||||||
|
data["availableModels"] = json.loads(avail)
|
||||||
|
else:
|
||||||
|
data.pop("availableModels", None)
|
||||||
|
# model/effortLevel - сидируем дефолты, не перетирая выбор юзера (нативный persistence)
|
||||||
|
if model:
|
||||||
|
data.setdefault("model", model)
|
||||||
|
data.setdefault("effortLevel", effort)
|
||||||
|
with open(cfg_settings, "w") as f:
|
||||||
|
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||||
|
f.write("\n")
|
||||||
|
PYEOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# _apply_effort: per-launcher persistence уровня effort (гибрид).
|
||||||
|
# - low/medium/high/xhigh живут нативно в settings.json лаунчера -> /effort работает,
|
||||||
|
# уровень сохраняется между сессиями, env-переменная НЕ ставится.
|
||||||
|
# - max единственный нельзя сохранить в settings.json (он session-only), поэтому
|
||||||
|
# его восстанавливаем через CLAUDE_CODE_EFFORT_LEVEL. В такой max-сессии /effort
|
||||||
|
# залочен env-переменной (ограничение Claude Code).
|
||||||
|
# Текущий уровень (вкл. max) ловит статусбар в ~/.cache/ai-setup/effort_<launcher>.
|
||||||
|
# Сменить уровень из max: AI_EFFORT=<lvl> ai-<launcher>.
|
||||||
|
# Использование: _apply_effort <launcher> <default_effort>
|
||||||
|
_apply_effort() {
|
||||||
|
local launcher="$1" default_effort="${2:-high}"
|
||||||
|
local f="$HOME/.cache/ai-setup/effort_${launcher}"
|
||||||
|
local settings="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/settings.json"
|
||||||
|
local eff
|
||||||
|
if [ -n "${AI_EFFORT:-}" ]; then
|
||||||
|
# Явный override: запоминаем и применяем
|
||||||
|
eff="$AI_EFFORT"
|
||||||
|
mkdir -p "$HOME/.cache/ai-setup"
|
||||||
|
echo "$eff" > "$f"
|
||||||
|
else
|
||||||
|
eff=$(cat "$f" 2>/dev/null)
|
||||||
|
fi
|
||||||
|
if [ "$eff" = "max" ]; then
|
||||||
|
# Единственный способ восстановить max между сессиями
|
||||||
|
export CLAUDE_CODE_EFFORT_LEVEL=max
|
||||||
|
elif [ -n "${AI_EFFORT:-}" ] && [ -n "$eff" ]; then
|
||||||
|
# Явный сброс на low/medium/high/xhigh - пишем нативно в settings.json лаунчера
|
||||||
|
python3 - "$settings" "$eff" <<'PYEOF'
|
||||||
|
import sys, json, os
|
||||||
|
p, eff = sys.argv[1], sys.argv[2]
|
||||||
|
d = {}
|
||||||
|
if os.path.exists(p):
|
||||||
|
try: d = json.load(open(p))
|
||||||
|
except Exception: pass
|
||||||
|
d["effortLevel"] = eff
|
||||||
|
os.makedirs(os.path.dirname(p), exist_ok=True)
|
||||||
|
with open(p, "w") as fp:
|
||||||
|
json.dump(d, fp, indent=2, ensure_ascii=False); fp.write("\n")
|
||||||
|
PYEOF
|
||||||
|
fi
|
||||||
|
# Иначе (≤xhigh без AI_EFFORT): ничего не делаем - effortLevel уже персистнут нативно.
|
||||||
|
}
|
||||||
|
|
||||||
_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=""
|
||||||
@@ -1125,13 +1368,28 @@ 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
|
||||||
|
export CLAUDE_CONFIG_DIR="$HOME/.config/ai-setup/cfg/deepseek"
|
||||||
|
# Пикер: DeepSeek V4 Pro (opus+sonnet, дефолт) и DeepSeek V4 Flash (haiku).
|
||||||
|
# availableModels НЕ задаём: при кастомном провайдере он схлопывает пикер в Default.
|
||||||
|
# Claude Code навязывает 3 слота opus/sonnet/haiku; незаданный слот показал бы чужой
|
||||||
|
# Claude, поэтому sonnet тоже мапим на Pro (лёгкий дубль, но все пункты - DeepSeek).
|
||||||
|
# FABLE не навязывается - не задаём. DISABLE_1M убирает [1M] дубли из пикера.
|
||||||
|
_setup_isolated_config deepseek opus high ''
|
||||||
|
_apply_effort deepseek 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_DEFAULT_OPUS_MODEL=deepseek-v4-pro \
|
ANTHROPIC_DEFAULT_OPUS_MODEL=deepseek-v4-pro \
|
||||||
|
ANTHROPIC_DEFAULT_OPUS_MODEL_NAME="DeepSeek V4 Pro" \
|
||||||
|
ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION="DeepSeek V4 Pro - флагман для сложных задач" \
|
||||||
ANTHROPIC_DEFAULT_SONNET_MODEL=deepseek-v4-pro \
|
ANTHROPIC_DEFAULT_SONNET_MODEL=deepseek-v4-pro \
|
||||||
|
ANTHROPIC_DEFAULT_SONNET_MODEL_NAME="DeepSeek V4 Pro" \
|
||||||
|
ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION="DeepSeek V4 Pro - флагман для сложных задач" \
|
||||||
ANTHROPIC_DEFAULT_HAIKU_MODEL=deepseek-v4-flash \
|
ANTHROPIC_DEFAULT_HAIKU_MODEL=deepseek-v4-flash \
|
||||||
|
ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME="DeepSeek V4 Flash" \
|
||||||
|
ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION="DeepSeek V4 Flash - быстрый и дешёвый" \
|
||||||
CLAUDE_CODE_SUBAGENT_MODEL=deepseek-v4-flash \
|
CLAUDE_CODE_SUBAGENT_MODEL=deepseek-v4-flash \
|
||||||
|
CLAUDE_CODE_DISABLE_1M_CONTEXT=1 \
|
||||||
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \
|
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \
|
||||||
claude --dangerously-skip-permissions --system-prompt-file "$_PROMPT_FILE" "$@"
|
claude --dangerously-skip-permissions --system-prompt-file "$_PROMPT_FILE" "$@"
|
||||||
DEEPSEEKEOF
|
DEEPSEEKEOF
|
||||||
@@ -1150,7 +1408,7 @@ api_key=""
|
|||||||
|
|
||||||
if [ -n "$api_key" ]; then
|
if [ -n "$api_key" ]; then
|
||||||
echo -n "Проверка сохранённого Kimi ключа... "
|
echo -n "Проверка сохранённого Kimi ключа... "
|
||||||
_claude_test_api "https://api.kimi.com/coding/v1/messages" "x-api-key: $api_key" "kimi-k2.6"
|
_claude_test_api "https://api.kimi.com/coding/v1/messages" "x-api-key: $api_key" "kimi-k2.7"
|
||||||
_handle_api_response "Kimi" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" "Пополните баланс: https://www.kimi.com/code"
|
_handle_api_response "Kimi" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" "Пополните баланс: https://www.kimi.com/code"
|
||||||
ret=$_API_RET
|
ret=$_API_RET
|
||||||
if [ $ret -eq 401 ]; then
|
if [ $ret -eq 401 ]; then
|
||||||
@@ -1170,7 +1428,7 @@ if [ -z "$api_key" ]; then
|
|||||||
[ -z "$api_key" ] && { echo "Выход."; exit 1; }
|
[ -z "$api_key" ] && { echo "Выход."; exit 1; }
|
||||||
|
|
||||||
echo -n "Проверяю ключ и баланс... "
|
echo -n "Проверяю ключ и баланс... "
|
||||||
_claude_test_api "https://api.kimi.com/coding/v1/messages" "x-api-key: $api_key" "kimi-k2.6"
|
_claude_test_api "https://api.kimi.com/coding/v1/messages" "x-api-key: $api_key" "kimi-k2.7"
|
||||||
_handle_api_response "Kimi" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" "Пополните баланс: https://www.kimi.com/code"
|
_handle_api_response "Kimi" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" "Пополните баланс: https://www.kimi.com/code"
|
||||||
ret=$_API_RET
|
ret=$_API_RET
|
||||||
if [ $ret -eq 0 ] || [ $ret -eq 429 ]; then
|
if [ $ret -eq 0 ] || [ $ret -eq 429 ]; then
|
||||||
@@ -1197,13 +1455,27 @@ 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
|
||||||
|
export CLAUDE_CONFIG_DIR="$HOME/.config/ai-setup/cfg/kimi"
|
||||||
|
# Пикер: Opus/Sonnet = Kimi K2.7 Code, Haiku = Kimi K2.6. availableModels НЕ задаём
|
||||||
|
# (он схлопывает пикер в Default). Claude Code навязывает 3 слота opus/sonnet/haiku;
|
||||||
|
# незаданный показал бы чужой Claude, поэтому opus+sonnet = K2.7, haiku = K2.6
|
||||||
|
# DISABLE_1M убирает [1M] дубли из пикера.
|
||||||
|
_setup_isolated_config kimi opus high ''
|
||||||
|
_apply_effort kimi 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_DEFAULT_OPUS_MODEL=kimi-k2.7 \
|
||||||
ANTHROPIC_DEFAULT_OPUS_MODEL=kimi-k2.6 \
|
ANTHROPIC_DEFAULT_OPUS_MODEL_NAME="Kimi K2.7 Code" \
|
||||||
ANTHROPIC_DEFAULT_SONNET_MODEL=kimi-k2.6 \
|
ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION="Kimi K2.7 Code — флагманский программист (Moonshot AI)" \
|
||||||
|
ANTHROPIC_DEFAULT_SONNET_MODEL=kimi-k2.7 \
|
||||||
|
ANTHROPIC_DEFAULT_SONNET_MODEL_NAME="Kimi K2.7 Code" \
|
||||||
|
ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION="Kimi K2.7 Code — флагманский программист (Moonshot AI)" \
|
||||||
ANTHROPIC_DEFAULT_HAIKU_MODEL=kimi-k2.6 \
|
ANTHROPIC_DEFAULT_HAIKU_MODEL=kimi-k2.6 \
|
||||||
|
ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME="Kimi K2.6" \
|
||||||
|
ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION="Kimi K2.6 — быстрый универсал (Moonshot AI)" \
|
||||||
CLAUDE_CODE_SUBAGENT_MODEL=kimi-k2.6 \
|
CLAUDE_CODE_SUBAGENT_MODEL=kimi-k2.6 \
|
||||||
|
CLAUDE_CODE_DISABLE_1M_CONTEXT=1 \
|
||||||
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \
|
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \
|
||||||
claude --dangerously-skip-permissions --system-prompt-file "$_PROMPT_FILE" "$@"
|
claude --dangerously-skip-permissions --system-prompt-file "$_PROMPT_FILE" "$@"
|
||||||
KIMIEOF
|
KIMIEOF
|
||||||
@@ -1229,10 +1501,13 @@ if [ -n "$api_key" ]; then
|
|||||||
rm -f "$key_file"
|
rm -f "$key_file"
|
||||||
api_key=""
|
api_key=""
|
||||||
elif [ $ret -eq 429 ]; then
|
elif [ $ret -eq 429 ]; then
|
||||||
|
_openrouter_balance "$api_key"
|
||||||
echo -n "Продолжить всё равно? (запросы могут не проходить) [y/N] "
|
echo -n "Продолжить всё равно? (запросы могут не проходить) [y/N] "
|
||||||
read -r _ans; case "${_ans:-N}" in [Yy]*) ;; *) exit 1 ;; esac
|
read -r _ans; case "${_ans:-N}" in [Yy]*) ;; *) exit 1 ;; esac
|
||||||
elif [ $ret -ne 0 ]; then
|
elif [ $ret -ne 0 ]; then
|
||||||
exit 1
|
exit 1
|
||||||
|
else
|
||||||
|
_openrouter_balance "$api_key"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -1250,6 +1525,7 @@ if [ -z "$api_key" ]; then
|
|||||||
echo "$api_key" > "$key_file"
|
echo "$api_key" > "$key_file"
|
||||||
chmod 600 "$key_file"
|
chmod 600 "$key_file"
|
||||||
echo "Ключ сохранён."
|
echo "Ключ сохранён."
|
||||||
|
_openrouter_balance "$api_key"
|
||||||
if [ $ret -eq 429 ]; then
|
if [ $ret -eq 429 ]; then
|
||||||
echo -n "Продолжить всё равно? (запросы могут не проходить) [y/N] "
|
echo -n "Продолжить всё равно? (запросы могут не проходить) [y/N] "
|
||||||
read -r _ans; case "${_ans:-N}" in [Yy]*) ;; *) exit 1 ;; esac
|
read -r _ans; case "${_ans:-N}" in [Yy]*) ;; *) exit 1 ;; esac
|
||||||
@@ -1269,13 +1545,32 @@ 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
|
||||||
|
export CLAUDE_CONFIG_DIR="$HOME/.config/ai-setup/cfg/openrouter"
|
||||||
|
# openrouter - модели, которых НЕТ в других ai-* лаунчерах (без anthropic/deepseek/
|
||||||
|
# kimi/gemini). Пикер строится из 4 алиас-слотов + 1 custom-пункта (потолок Claude Code).
|
||||||
|
# availableModels НЕ задаём (он схлопывает пикер). Дефолт - GPT-5.5 (custom-пункт).
|
||||||
|
export ANTHROPIC_CUSTOM_MODEL_OPTION="openai/gpt-5.5"
|
||||||
|
export ANTHROPIC_CUSTOM_MODEL_OPTION_NAME="GPT-5.5"
|
||||||
|
export ANTHROPIC_CUSTOM_MODEL_OPTION_DESCRIPTION="OpenAI GPT-5.5 (OpenRouter)"
|
||||||
|
_setup_isolated_config openrouter "openai/gpt-5.5" high ''
|
||||||
|
_apply_effort openrouter 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_DEFAULT_OPUS_MODEL=x-ai/grok-4.20 \
|
||||||
ANTHROPIC_DEFAULT_OPUS_MODEL=anthropic/claude-4.8-opus \
|
ANTHROPIC_DEFAULT_OPUS_MODEL_NAME="Grok 4.20" \
|
||||||
ANTHROPIC_DEFAULT_SONNET_MODEL=anthropic/claude-4.6-sonnet \
|
ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION="xAI Grok 4.20 (OpenRouter)" \
|
||||||
ANTHROPIC_DEFAULT_HAIKU_MODEL=openai/gpt-5.5 \
|
ANTHROPIC_DEFAULT_SONNET_MODEL=qwen/qwen3.7-max \
|
||||||
|
ANTHROPIC_DEFAULT_SONNET_MODEL_NAME="Qwen3.7 Max" \
|
||||||
|
ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION="Qwen3.7 Max (OpenRouter)" \
|
||||||
|
ANTHROPIC_DEFAULT_HAIKU_MODEL=minimax/minimax-m3 \
|
||||||
|
ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME="MiniMax M3" \
|
||||||
|
ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION="MiniMax M3 (OpenRouter)" \
|
||||||
|
ANTHROPIC_DEFAULT_FABLE_MODEL=meta-llama/llama-4-maverick \
|
||||||
|
ANTHROPIC_DEFAULT_FABLE_MODEL_NAME="Llama 4 Maverick" \
|
||||||
|
ANTHROPIC_DEFAULT_FABLE_MODEL_DESCRIPTION="Meta Llama 4 Maverick (OpenRouter)" \
|
||||||
CLAUDE_CODE_SUBAGENT_MODEL=openai/gpt-5.5 \
|
CLAUDE_CODE_SUBAGENT_MODEL=openai/gpt-5.5 \
|
||||||
|
CLAUDE_CODE_DISABLE_1M_CONTEXT=1 \
|
||||||
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \
|
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \
|
||||||
claude --dangerously-skip-permissions --system-prompt-file "$_PROMPT_FILE" "$@"
|
claude --dangerously-skip-permissions --system-prompt-file "$_PROMPT_FILE" "$@"
|
||||||
OPENROUTEREOF
|
OPENROUTEREOF
|
||||||
@@ -1321,7 +1616,13 @@ 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"
|
||||||
claude --dangerously-skip-permissions --model sonnet --system-prompt-file "$_PROMPT_FILE" "$@"
|
export AI_LAUNCHER=claude
|
||||||
|
# ai-claude работает в дефолтном ~/.claude (нативный логин и аккаунты).
|
||||||
|
# Модель хранится нативно в ~/.claude/settings.json; другие ai-* лаунчеры теперь
|
||||||
|
# изолированы в своих CLAUDE_CONFIG_DIR, поэтому в пикер не протекают чужие модели -
|
||||||
|
# показываются только нативные модели Claude Code.
|
||||||
|
_apply_effort claude xhigh
|
||||||
|
claude --dangerously-skip-permissions --system-prompt-file "$_PROMPT_FILE" "$@"
|
||||||
CLAUDEEOF
|
CLAUDEEOF
|
||||||
chmod +x "$BIN_DIR/ai-claude"
|
chmod +x "$BIN_DIR/ai-claude"
|
||||||
|
|
||||||
@@ -1361,7 +1662,7 @@ echo ""
|
|||||||
echo " На базе Claude Code:"
|
echo " На базе Claude Code:"
|
||||||
echo -e " ${CYAN}ai-claude${NC} - Оригинальный Claude Code (Anthropic)"
|
echo -e " ${CYAN}ai-claude${NC} - Оригинальный Claude Code (Anthropic)"
|
||||||
echo -e " ${CYAN}ai-deepseek${NC} - DeepSeek (через Claude Code, API ключ сохраняется)"
|
echo -e " ${CYAN}ai-deepseek${NC} - DeepSeek (через Claude Code, API ключ сохраняется)"
|
||||||
echo -e " ${CYAN}ai-kimi${NC} - Kimi K2.6 (через Claude Code, API ключ сохраняется)"
|
echo -e " ${CYAN}ai-kimi${NC} - Kimi K2.7 Code (через Claude Code, API ключ сохраняется)"
|
||||||
echo -e " ${CYAN}ai-openrouter${NC} - OpenRouter (через Claude Code: GPT-5.5, Opus 4.8, Sonnet 4.6)"
|
echo -e " ${CYAN}ai-openrouter${NC} - OpenRouter (через Claude Code: GPT-5.5, Opus 4.8, Sonnet 4.6)"
|
||||||
echo ""
|
echo ""
|
||||||
echo " Нативные CLI:"
|
echo " Нативные CLI:"
|
||||||
|
|||||||
@@ -48,11 +48,12 @@ test_kimi_claude_launcher() {
|
|||||||
# ── ai-kimi: uses official Kimi API ──────────────────────────────────────
|
# ── ai-kimi: uses official Kimi API ──────────────────────────────────────
|
||||||
test_kimi_official_api() {
|
test_kimi_official_api() {
|
||||||
if echo "$KIMI_SECTION" | grep -q 'api.kimi.com/coding' \
|
if echo "$KIMI_SECTION" | grep -q 'api.kimi.com/coding' \
|
||||||
&& echo "$KIMI_SECTION" | grep -q 'ANTHROPIC_MODEL=kimi-k2.6' \
|
&& echo "$KIMI_SECTION" | grep -q 'ANTHROPIC_DEFAULT_OPUS_MODEL=kimi-k2.7' \
|
||||||
|
&& echo "$KIMI_SECTION" | grep -q 'ANTHROPIC_DEFAULT_HAIKU_MODEL=kimi-k2.6' \
|
||||||
&& ! echo "$KIMI_SECTION" | grep -q 'artemox'; then
|
&& ! echo "$KIMI_SECTION" | grep -q 'artemox'; then
|
||||||
ok "ai-kimi: uses official Kimi API and model"
|
ok "ai-kimi: uses official Kimi API (K2.7 opus/sonnet, K2.6 haiku)"
|
||||||
else
|
else
|
||||||
fail "ai-kimi: must use official Kimi API (api.kimi.com/coding) and model kimi-k2.6"
|
fail "ai-kimi: must use official Kimi API (api.kimi.com/coding) with K2.7 opus/sonnet, K2.6 haiku"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user