Compare commits

...

7 Commits

Author SHA1 Message Date
Виталий Никитенко
b3468095c9 docs(rules): authorize Eltex merge-request review writes 2026-08-21 15:42:04 +03:00
Виталий Никитенко
676b25217e docs(effort): DeepSeek V4 has 3 native effort levels
low->low, medium/xhigh->high, max->max on the DeepSeek API side.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-21 15:41:59 +03:00
Виталий Никитенко
9448212a26 fix(deepseek): USD-only balance display, drop CNY/¥
Launcher and statusline show only USD from balance_infos; ¥ removed.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-21 15:41:55 +03:00
Виталий Никитенко
4331ebb675 feat(statusline): dot-separated format, GLM brand color, blue branch
- сегменты через жирную серую точку · вместо пробелов, квадратные скобки
  убраны (ветка, effort, аккаунты)
- glm → фирменный кремовый Z.ai (#FFE4B5 ≈ 256-палитра 223)
- ветка → светло-синий (#8FB3EF ≈ 111) вместо серого
- суффикс [1m]/[1M] вычищается из отображения модели: окно задаёт
  CLAUDE_CODE_MAX_CONTEXT_TOKENS, суффиксу в статусной строке делать нечего

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-17 07:01:32 +03:00
Виталий Никитенко
8c7c2e9e0f fix(glm): correct context window to 1M for all ai-glm slots
Claude Code не знает моделей glm-* и для glm-5.3 без суффикса считал окно
= 200K: счётчик ctx: врал в ~5 раз, автокомпакт жал сессию в 200K.
Окно теперь задаётся CLAUDE_CODE_MAX_CONTEXT_TOKENS=1000000 (env применяется
к не-claude моделям независимо от суффикса), поэтому [1m] у Haiku-слота
стал дублирующим и убран — все три слота пикера теперь glm-5.3.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-17 07:01:22 +03:00
Виталий Никитенко
60b915de5f feat(setup): add ./setup.sh update CLI mode
./setup.sh update (или --update) запускает git pull --ff-only + перегенерацию
всех ai-* лаунчеров без интерактивного меню — то же, что пункт меню 7 (логика
вынесена в общую do_update). Неизвестный аргумент печатает usage и выходит
с кодом 1, без аргументов открывается меню как раньше.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-17 06:00:27 +03:00
Виталий Никитенко
f61f2cde84 feat(glm): switch ai-glm to GLM 5.3
glm-5.3 доступна на api.z.ai (вышла 2026-08-14, 1M контекст): opus/sonnet =
glm-5.3, haiku = glm-5.3[1m], subagent = glm-5.3, health-check ключа тоже
на 5.3. Секция OpenRouter остаётся на z-ai/glm-5.2 — там 5.3 ещё нет.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-17 06:00:11 +03:00
9 changed files with 138 additions and 70 deletions

View File

@@ -13,12 +13,12 @@
## Таблица маппинга ## Таблица маппинга
| Claude Code `/effort` | Anthropic (`ai-claude`) | DeepSeek V4 (`ai-deepseek`) | Kimi K2.7/K2.6 (`ai-kimi`) | GLM 5.2 (`ai-glm`) | OpenRouter/GPT-5.5 (`ai-openrouter`) | OpenCode Go (`ai-opencode`) | | Claude Code `/effort` | Anthropic (`ai-claude`) | DeepSeek V4 (`ai-deepseek`) | Kimi K2.7/K2.6 (`ai-kimi`) | GLM 5.3 (`ai-glm`) | OpenRouter/GPT-5.5 (`ai-openrouter`) | OpenCode Go (`ai-opencode`) |
|:---:|:---:|:---:|:---:|:---:|:---:|:---:| |:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| `low` | нативно `low` | провайдер поднимает до `high` | thinking on | на стороне сервера | без локального маппинга | без локального маппинга | | `low` | нативно `low` | нативно `low` | thinking on | на стороне сервера | без локального маппинга | без локального маппинга |
| `medium` | нативно `medium` | провайдер поднимает до `high` | thinking on | на стороне сервера | без локального маппинга | без локального маппинга | | `medium` | нативно `medium` | провайдер поднимает до `high` | thinking on | на стороне сервера | без локального маппинга | без локального маппинга |
| `high` | нативно `high` | нативно `high` | thinking on | на стороне сервера | без локального маппинга | без локального маппинга | | `high` | нативно `high` | нативно `high` | thinking on | на стороне сервера | без локального маппинга | без локального маппинга |
| `xhigh` | нативно `xhigh` | провайдер поднимает до `max` | thinking on | на стороне сервера | без локального маппинга | без локального маппинга | | `xhigh` | нативно `xhigh` | провайдер поднимает до `high` | thinking on | на стороне сервера | без локального маппинга | без локального маппинга |
| `max` | нативно `max` | нативно `max` | thinking on | на стороне сервера | без локального маппинга | без локального маппинга | | `max` | нативно `max` | нативно `max` | thinking on | на стороне сервера | без локального маппинга | без локального маппинга |
Для OpenRouter текущий лаунчер не делает локального преобразования effort и отправляет Для OpenRouter текущий лаунчер не делает локального преобразования effort и отправляет
@@ -45,12 +45,13 @@ low -> medium -> high -> xhigh -> max
### DeepSeek V4 ### DeepSeek V4
```text ```text
high -> max low -> high -> max
``` ```
- 2 реальных уровня. - 3 реальных уровня.
- `low` и `medium` фактически поднимаются до `high`. - `low` остаётся `low` — реально снижает thinking бюджет.
- `xhigh` фактически поднимается до `max`. - `medium` и `xhigh` фактически поднимаются до `high`.
- `max` остаётся `max` — максимальный бюджет thinking.
- Маппинг выполняется на стороне DeepSeek API. - Маппинг выполняется на стороне DeepSeek API.
- Дефолт `ai-deepseek`: `high`. - Дефолт `ai-deepseek`: `high`.
@@ -65,7 +66,7 @@ on / off
- Градаций глубины reasoning нет. - Градаций глубины reasoning нет.
- Дефолт `ai-kimi`: `high`. - Дефолт `ai-kimi`: `high`.
### GLM 5.2 (Zhipu AI) ### GLM 5.3 (Zhipu AI)
```text ```text
high / max high / max
@@ -99,7 +100,7 @@ Effort не преобразуется локально — thinking budget не
| Anthropic | Не нужен | - | | Anthropic | Не нужен | - |
| DeepSeek V4 | DeepSeek API | На стороне сервера | | DeepSeek V4 | DeepSeek API | На стороне сервера |
| Kimi K2.7/K2.6 | Moonshot API | На стороне сервера | | Kimi K2.7/K2.6 | Moonshot API | На стороне сервера |
| GLM 5.2 | Zhipu AI API | На стороне сервера | | GLM 5.3 | Zhipu AI API | На стороне сервера |
| OpenRouter/GPT-5.5 | Локального маппинга нет | `ai-openrouter` отправляет значение как есть | | OpenRouter/GPT-5.5 | Локального маппинга нет | `ai-openrouter` отправляет значение как есть |
| OpenCode Go | Локального маппинга нет | `ai-opencode` отправляет значение через routatic-proxy | | OpenCode Go | Локального маппинга нет | `ai-opencode` отправляет значение через routatic-proxy |
| Legacy GPT proxy | helper-маппинг `xhigh` -> `high` | `~/bin/claude-gpt-effort-proxy.py` или `~/.local/bin/claude-gpt-effort-proxy.py` | | Legacy GPT proxy | helper-маппинг `xhigh` -> `high` | `~/bin/claude-gpt-effort-proxy.py` или `~/.local/bin/claude-gpt-effort-proxy.py` |
@@ -125,4 +126,4 @@ ai-deepseek # без флага - использовать ур
- Для повседневной работы: `high` или `xhigh`. - Для повседневной работы: `high` или `xhigh`.
- `max` effort имеет реальный эффект у Anthropic и DeepSeek; у Kimi и GLM это всё тот же thinking on / маппинг на стороне сервера. - `max` effort имеет реальный эффект у Anthropic и DeepSeek; у Kimi и GLM это всё тот же thinking on / маппинг на стороне сервера.
- `low`/`medium` у DeepSeek, Kimi и GLM фактически не снижают reasoning. - `low`/`medium` у Kimi и GLM фактически не снижают reasoning; у DeepSeek `low` теперь даёт реальный уровень `low` и экономит бюджет.

View File

@@ -15,6 +15,11 @@ bash setup.sh
exec bash exec bash
``` ```
Обновление всех AI-инструментов одной командой:
```bash
./setup.sh update
```
## 2. Настроить сеть (Amnezia + ru-bypass) ## 2. Настроить сеть (Amnezia + ru-bypass)
Нужно один раз на каждой машине. .ru трафик идёт напрямую, Нужно один раз на каждой машине. .ru трафик идёт напрямую,

View File

@@ -56,6 +56,15 @@ bash setup.sh
exec bash exec bash
``` ```
### Обновление одной командой
```bash
./setup.sh update # или: ./setup.sh --update
```
То же, что пункт меню `7) Обновить`: `git pull --ff-only` + перегенерация всех
`ai-*` лаунчеров через `scripts/ai-setup.sh`. Без аргументов открывается меню.
## Сеть: ru-bypass + kill switch ## Сеть: ru-bypass + kill switch
Полная документация: [`home-configs/network/README.md`](home-configs/network/README.md) Полная документация: [`home-configs/network/README.md`](home-configs/network/README.md)
@@ -151,7 +160,7 @@ bash tests/test_network.sh # полные тесты
- `ai-gpt` - нативный OpenAI Codex CLI - `ai-gpt` - нативный OpenAI Codex CLI
- `ai-deepseek` - Claude Code через DeepSeek API - `ai-deepseek` - Claude Code через DeepSeek API
- `ai-kimi` - Claude Code через официальный Kimi Code API (`api.kimi.com/coding`) - `ai-kimi` - Claude Code через официальный Kimi Code API (`api.kimi.com/coding`)
- `ai-glm` - Claude Code через Zhipu AI GLM 5.2 API (`api.z.ai/api/anthropic`) - `ai-glm` - Claude Code через Zhipu AI GLM 5.3 API (`api.z.ai/api/anthropic`)
- `ai-openrouter` - Claude Code через OpenRouter (GPT-5.5, Grok, Qwen, GLM 5.2 и др.) - `ai-openrouter` - Claude Code через OpenRouter (GPT-5.5, Grok, Qwen, GLM 5.2 и др.)
- `ai-opencode` - Claude Code через OpenCode Go + routatic-proxy (DeepSeek, Kimi, Qwen, GLM, MiniMax, MiMo) - `ai-opencode` - Claude Code через OpenCode Go + routatic-proxy (DeepSeek, Kimi, Qwen, GLM, MiniMax, MiMo)
- `ai-gemini` - нативный Antigravity CLI `agy` - `ai-gemini` - нативный Antigravity CLI `agy`

View File

@@ -23,6 +23,7 @@ Use these defaults across projects, then apply any more specific repository inst
- For requests to answer, explain, review, diagnose, or plan, inspect the relevant materials and report the result. Do not implement changes unless the request also asks for them. - For requests to answer, explain, review, diagnose, or plan, inspect the relevant materials and report the result. Do not implement changes unless the request also asks for them.
- For requests to change, build, or fix, perform safe in-scope local edits and validation without asking for routine confirmation. - For requests to change, build, or fix, perform safe in-scope local edits and validation without asking for routine confirmation.
- Ask before destructive or difficult-to-recover actions, external writes or publication, purchases, or a material expansion of scope. - Ask before destructive or difficult-to-recover actions, external writes or publication, purchases, or a material expansion of scope.
- Exception for Eltex merge-request reviews: a bare review request or explicit `eltex-review` invocation authorizes the skill's documented GitLab writes, including inline discussions, approval, and its thank-you note. Explicit chat-only wording keeps the review read-only.
- Preserve unrelated user changes and adapt to a dirty working tree. Never discard or overwrite work you did not create. - Preserve unrelated user changes and adapt to a dirty working tree. Never discard or overwrite work you did not create.
## 4. Plan and Verify Proportionally ## 4. Plan and Verify Proportionally

View File

@@ -6,7 +6,7 @@ The custom statusline script renders **only ASCII-safe characters and ANSI color
- ASCII brackets `[ ]` for git branch and effort level - ASCII brackets `[ ]` for git branch and effort level
- ANSI 256-color escape sequences for model names, effort levels, and rate-limit percentages - ANSI 256-color escape sequences for model names, effort levels, and rate-limit percentages
- The `¥` symbol (U+00A5) for CNY balance in DeepSeek output - The `$` symbol for the DeepSeek balance (USD only)
It does **not** use Nerd Font glyphs, powerline separators, or emoji. It does **not** use Nerd Font glyphs, powerline separators, or emoji.

View File

@@ -4,6 +4,12 @@ input=$(cat)
cwd=$(echo "$input" | jq -r '.cwd') cwd=$(echo "$input" | jq -r '.cwd')
model=$(echo "$input" | jq -r '.model.display_name // empty') model=$(echo "$input" | jq -r '.model.display_name // empty')
model_id=$(echo "$input" | jq -r '.model.id // empty') model_id=$(echo "$input" | jq -r '.model.id // empty')
# Суффикс [1m] в отображении не нужен: окно контекста задаётся
# CLAUDE_CODE_MAX_CONTEXT_TOKENS, а не суффиксом модели
model="${model/\[1m\]/}"
model="${model% \[1M\]}"
model="${model% (1M context)}"
model_id="${model_id/\[1m\]/}"
five_pct=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty') five_pct=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty')
five_reset=$(echo "$input" | jq -r '.rate_limits.five_hour.resets_at // empty') five_reset=$(echo "$input" | jq -r '.rate_limits.five_hour.resets_at // empty')
week_pct=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty') week_pct=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty')
@@ -11,15 +17,18 @@ 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')
# Разделитель сегментов: жирная серая средняя точка ·
_sep() { printf " \033[1m\033[38;5;240m·\033[00m "; }
# Цвет effort: насыщенные, глубже чем пастельные (jewel tones) # Цвет effort: насыщенные, глубже чем пастельные (jewel tones)
_effort_color() { _effort_color() {
case "$1" in case "$1" in
low) printf '\033[38;5;220m[low]\033[00m' ;; # золотой low) printf '\033[38;5;220mlow\033[00m' ;; # золотой
medium) printf '\033[38;5;50m[medium]\033[00m' ;; # насыщенный teal medium) printf '\033[38;5;50mmedium\033[00m' ;; # насыщенный teal
high) printf '\033[38;5;38m[high]\033[00m' ;; # глубокий голубой high) printf '\033[38;5;38mhigh\033[00m' ;; # глубокий голубой
xhigh) printf '\033[38;5;206m[xhigh]\033[00m' ;; # насыщенный фиолетовый xhigh) printf '\033[38;5;206mxhigh\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' ;; # радуга max) printf '\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" ;; *) printf '\033[38;5;252m%s\033[00m' "$1" ;;
esac esac
} }
@@ -29,6 +38,7 @@ _brand_color() {
deepseek) printf '\033[38;5;69m' ;; # DeepSeek фирменный синий deepseek) printf '\033[38;5;69m' ;; # DeepSeek фирменный синий
claude) printf '\033[38;5;173m' ;; # Anthropic оранжевый claude) printf '\033[38;5;173m' ;; # Anthropic оранжевый
kimi) printf '\033[38;5;81m' ;; # Kimi голубой kimi) printf '\033[38;5;81m' ;; # Kimi голубой
glm) printf '\033[38;5;223m' ;; # Z.ai кремовый (бренд-фон z.ai, #FFE4B5)
openrouter) printf '\033[38;5;135m' ;; # OpenRouter фиолетовый openrouter) printf '\033[38;5;135m' ;; # OpenRouter фиолетовый
*) printf '\033[38;5;223m' ;; # кремовый (fallback) *) printf '\033[38;5;223m' ;; # кремовый (fallback)
esac esac
@@ -39,7 +49,7 @@ branch=$(git -C "$cwd" --no-optional-locks symbolic-ref --short HEAD 2>/dev/null
short_cwd="${cwd/#$HOME/\~}" short_cwd="${cwd/#$HOME/\~}"
printf "\033[38;5;252m%s\033[00m" "$short_cwd" printf "\033[38;5;252m%s\033[00m" "$short_cwd"
[ -n "$branch" ] && printf " \033[38;5;252m[%s]\033[00m" "$branch" [ -n "$branch" ] && { _sep; printf "\033[38;5;111m%s\033[00m" "$branch"; }
if [ -n "$model" ]; then if [ -n "$model" ]; then
brand_color=$(_brand_color "${AI_LAUNCHER:-}") brand_color=$(_brand_color "${AI_LAUNCHER:-}")
effort=$(echo "$input" | jq -r ".effort.level // empty") effort=$(echo "$input" | jq -r ".effort.level // empty")
@@ -84,19 +94,21 @@ if [ -n "$model" ]; then
fi fi
fi fi
fi fi
[ -n "$account" ] && printf " %s[%s]\033[00m" "$brand_color" "$account" [ -n "$account" ] && { _sep; printf "%s%s\033[00m" "$brand_color" "$account"; }
fi fi
# Аккаунт/ключ Kimi — показываем alias (account1/account2/...). # Аккаунт/ключ Kimi — показываем alias (account1/account2/...).
if [ "${AI_LAUNCHER:-}" = "kimi" ]; then if [ "${AI_LAUNCHER:-}" = "kimi" ]; then
kimi_account=$(cat "$HOME/.config/ai-setup/kimi_keys/current" 2>/dev/null) kimi_account=$(cat "$HOME/.config/ai-setup/kimi_keys/current" 2>/dev/null)
[ -n "$kimi_account" ] && printf " %s[%s]\033[00m" "$brand_color" "$kimi_account" [ -n "$kimi_account" ] && { _sep; printf "%s%s\033[00m" "$brand_color" "$kimi_account"; }
fi fi
if [ -n "$effort" ]; then if [ -n "$effort" ]; then
printf " %s%s " "$brand_color" "$model" _sep
printf "%s%s " "$brand_color" "$model"
_effort_color "$effort" _effort_color "$effort"
else else
printf " %s%s\033[00m" "$brand_color" "$model" _sep
printf "%s%s\033[00m" "$brand_color" "$model"
fi fi
fi fi
@@ -138,7 +150,7 @@ 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[38;5;78m%s\033[00m" "$balance" [ -n "$balance" ] && { _sep; printf "\033[38;5;78m%s\033[00m" "$balance"; }
fi fi
# Фоновое обновление баланса (не чаще раза в 30 секунд) # Фоновое обновление баланса (не чаще раза в 30 секунд)
@@ -159,13 +171,12 @@ 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', [])
symbols = {'USD': '\$', 'CNY': '\u00a5'}
parts = [] parts = []
for info in infos: for info in infos:
curr = info.get('currency', '') if info.get('currency', '').upper() != 'USD':
continue
total = info.get('total_balance', '0') total = info.get('total_balance', '0')
sym = symbols.get(curr, curr) parts.append(f'\${total}')
parts.append(f'{sym}{total}')
if parts: if parts:
print(' '.join(parts)) print(' '.join(parts))
" 2>/dev/null) " 2>/dev/null)
@@ -182,7 +193,7 @@ elif [ "${AI_LAUNCHER:-}" = "openrouter" ]; then
cache_file="$HOME/.cache/ai-setup/openrouter_balance" cache_file="$HOME/.cache/ai-setup/openrouter_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[38;5;78m%s\033[00m" "$balance" [ -n "$balance" ] && { _sep; printf "\033[38;5;78m%s\033[00m" "$balance"; }
fi fi
# Фоновое обновление баланса (не чаще раза в 30 секунд) # Фоновое обновление баланса (не чаще раза в 30 секунд)
@@ -247,13 +258,13 @@ else
five_int=$(printf '%.0f' "$five_pct") five_int=$(printf '%.0f' "$five_pct")
remaining=$(fmt_remaining "$five_reset") remaining=$(fmt_remaining "$five_reset")
color=$(pct_color "$five_int") color=$(pct_color "$five_int")
printf " %s%s:%s%%\033[00m" "$color" "$remaining" "$five_int" _sep; printf "%s%s:%s%%\033[00m" "$color" "$remaining" "$five_int"
fi fi
if [ -n "$week_pct" ] && [ -n "$week_reset" ]; then if [ -n "$week_pct" ] && [ -n "$week_reset" ]; then
week_int=$(printf '%.0f' "$week_pct") week_int=$(printf '%.0f' "$week_pct")
remaining=$(fmt_remaining "$week_reset") remaining=$(fmt_remaining "$week_reset")
color=$(pct_color "$week_int") color=$(pct_color "$week_int")
printf " %s%s:%s%%\033[00m" "$color" "$remaining" "$week_int" _sep; printf "%s%s:%s%%\033[00m" "$color" "$remaining" "$week_int"
fi fi
fi fi
@@ -263,7 +274,7 @@ fi
if [ -n "$ctx_pct" ]; then if [ -n "$ctx_pct" ]; then
ctx_int=$(printf '%.0f' "$ctx_pct") ctx_int=$(printf '%.0f' "$ctx_pct")
color=$(pct_color "$ctx_int") color=$(pct_color "$ctx_int")
printf " %sctx:%s%%\033[00m" "$color" "$ctx_int" _sep; printf "%sctx:%s%%\033[00m" "$color" "$ctx_int"
# Звуковой сигнал при первом достижении 60% # Звуковой сигнал при первом достижении 60%
alert_file="$HOME/.cache/ai-setup/ctx_alert_state" alert_file="$HOME/.cache/ai-setup/ctx_alert_state"

View File

@@ -706,25 +706,24 @@ try:
d = json.load(sys.stdin) d = json.load(sys.stdin)
available = d.get('is_available', False) available = d.get('is_available', False)
infos = d.get('balance_infos', []) infos = d.get('balance_infos', [])
# Показываем только USD (CNY не выводим)
infos = [i for i in infos if i.get('currency', '').upper() == 'USD']
if not infos: if not infos:
print(' \033[0;33m[БАЛАНС]\033[0m Нет данных о балансе') print(' \033[0;33m[БАЛАНС]\033[0m Нет данных о балансе')
sys.exit(0) sys.exit(0)
symbols = {'USD': '\$', 'CNY': '¥'}
cache_parts = [] cache_parts = []
for info in infos: for info in infos:
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} {status}')
if float(granted) > 0: if float(granted) > 0:
print(f' └─ Начислено: {granted} {curr}') print(f' └─ Начислено: \${granted}')
if float(topped_up) > 0: if float(topped_up) > 0:
print(f' └─ Пополнено: {topped_up} {curr}') print(f' └─ Пополнено: \${topped_up}')
cache_parts.append(f'{sym}{total}') cache_parts.append(f'\${total}')
# Cache all currencies with symbols for statusline # Cache USD balance for statusline
if cache_parts: if cache_parts:
cache_dir = os.path.expanduser('~/.cache/ai-setup') cache_dir = os.path.expanduser('~/.cache/ai-setup')
os.makedirs(cache_dir, exist_ok=True) os.makedirs(cache_dir, exist_ok=True)
@@ -1243,7 +1242,9 @@ fi
_build_ai_sys_prompt > /dev/null # сохраняет в ~/.codex/AGENTS.md (codex читает авто) _build_ai_sys_prompt > /dev/null # сохраняет в ~/.codex/AGENTS.md (codex читает авто)
_setup_codex_sound_hooks _setup_codex_sound_hooks
exec "$codex_bin" --dangerously-bypass-approvals-and-sandbox "$@" exec "$codex_bin" --dangerously-bypass-approvals-and-sandbox \
-c 'tui.status_line=["model-with-reasoning","git-branch","context-used","five-hour-limit","current-dir"]' \
"$@"
GPTEOF GPTEOF
chmod +x "$BIN_DIR/ai-gpt" chmod +x "$BIN_DIR/ai-gpt"
@@ -1560,7 +1561,7 @@ chmod +x "$BIN_DIR/ai-kimi"
# === ai-glm === # === ai-glm ===
cat > "$BIN_DIR/ai-glm" << 'GLMEOF' cat > "$BIN_DIR/ai-glm" << 'GLMEOF'
#!/usr/bin/env bash #!/usr/bin/env bash
# ai-glm - запуск Claude Code через Zhipu AI GLM 5.2 (Anthropic-compatible API) # ai-glm - запуск Claude Code через Zhipu AI GLM 5.3 (Anthropic-compatible API)
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/ai-api-helpers.sh" 2>/dev/null || true source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/ai-api-helpers.sh" 2>/dev/null || true
key_file="$HOME/.config/ai-setup/glm_key" key_file="$HOME/.config/ai-setup/glm_key"
@@ -1580,7 +1581,7 @@ glm_base_url="${GLM_BASE_URL:-https://api.z.ai/api/anthropic}"
if [ -n "$api_key" ]; then if [ -n "$api_key" ]; then
echo -n "Проверка сохранённого GLM ключа... " echo -n "Проверка сохранённого GLM ключа... "
_claude_test_api "$glm_base_url/v1/messages" "x-api-key: $api_key" "glm-5.2" _claude_test_api "$glm_base_url/v1/messages" "x-api-key: $api_key" "glm-5.3"
_handle_api_response "GLM" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" "Пополните баланс: https://z.ai/manage-apikey/apikey-list" _handle_api_response "GLM" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" "Пополните баланс: https://z.ai/manage-apikey/apikey-list"
ret=$_API_RET ret=$_API_RET
if [ $ret -eq 401 ]; then if [ $ret -eq 401 ]; then
@@ -1621,7 +1622,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 "$glm_base_url/v1/messages" "x-api-key: $api_key" "glm-5.2" _claude_test_api "$glm_base_url/v1/messages" "x-api-key: $api_key" "glm-5.3"
_handle_api_response "GLM" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" "Пополните баланс: https://z.ai/manage-apikey/apikey-list" _handle_api_response "GLM" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" "Пополните баланс: https://z.ai/manage-apikey/apikey-list"
ret=$_API_RET ret=$_API_RET
if [ $ret -eq 0 ] || [ $ret -eq 429 ]; then if [ $ret -eq 0 ] || [ $ret -eq 429 ]; then
@@ -1644,31 +1645,37 @@ trap 'rm -f "$_PROMPT_FILE"' EXIT INT TERM
_build_ai_sys_prompt > "$_PROMPT_FILE" _build_ai_sys_prompt > "$_PROMPT_FILE"
export AI_LAUNCHER=glm export AI_LAUNCHER=glm
export CLAUDE_CONFIG_DIR="$HOME/.config/ai-setup/cfg/glm" export CLAUDE_CONFIG_DIR="$HOME/.config/ai-setup/cfg/glm"
# Пикер: Opus/Sonnet = GLM 5.2, Haiku = GLM 5.2[1m]. availableModels НЕ задаём # Пикер: все три слота = glm-5.3. availableModels НЕ задаём (он схлопывает пикер).
# (он схлопывает пикер). Claude Code навязывает 3 слота; незаданный показал бы # Claude Code навязывает 3 слота; незаданный показал бы чужой Claude, поэтому
# чужой Claude, поэтому opus+sonnet = glm-5.2, haiku = glm-5.2[1m]. # opus+sonnet+haiku = glm-5.3. Окно контекста у всех слотов задаёт env
# CLAUDE_CODE_MAX_CONTEXT_TOKENS ниже — суффикс [1m] больше не нужен.
_setup_isolated_config glm opus high '' _setup_isolated_config glm opus high ''
# ai-glm стартует на /effort max. max невалиден в settings.json (там только # ai-glm стартует на /effort max. max невалиден в settings.json (там только
# low/medium/high/xhigh), поэтому задаём через CLI --effort, а не _apply_effort. # low/medium/high/xhigh), поэтому задаём через CLI --effort, а не _apply_effort.
# CLAUDE_CODE_ALWAYS_ENABLE_EFFORT заставляет Claude Code отправлять effort в API # CLAUDE_CODE_ALWAYS_ENABLE_EFFORT заставляет Claude Code отправлять effort в API
# для GLM (иначе glm-5.2 не распознан как effort-capable и параметр не уходит). # для GLM (иначе glm-5.3 не распознан как effort-capable и параметр не уходит).
# /effort внутри сессии работает; ручной override запуска: AI_EFFORT=high ai-glm. # /effort внутри сессии работает; ручной override запуска: AI_EFFORT=high ai-glm.
# CLAUDE_CODE_DISABLE_1M_CONTEXT НЕ выставляем: GLM 5.2 реально держит 1M контекста, # CLAUDE_CODE_DISABLE_1M_CONTEXT НЕ выставляем: GLM 5.3 реально держит 1M контекста,
# а флаг заставлял бы счётчик ctx: считать окно = 200K (врал в ~5 раз). Прочие # а флаг заставлял бы счётчик ctx: считать окно = 200K (врал в ~5 раз). Прочие
# лаунчеры (deepseek/kimi/openrouter/opencode) флаг оставляют — у них другой контекст # лаунчеры (deepseek/kimi/openrouter/opencode) флаг оставляют — у них другой контекст
# и флаг убирает [1M]-дубли из пикера; ai-claude его и так не использует. # и флаг убирает [1M]-дубли из пикера; ai-claude его и так не использует.
# CLAUDE_CODE_MAX_CONTEXT_TOKENS: glm-* нет в реестре моделей Claude Code, поэтому
# окно по умолчанию считается = 200K — ctx: врал бы в ~5 раз, а автокомпакт жал бы
# сессию в 200K. Задаём реальное окно 1M явно; env применяется к не-claude моделям
# независимо от суффикса, поэтому [1m] у Haiku-слота не нужен и убран.
ANTHROPIC_BASE_URL="$glm_base_url" \ ANTHROPIC_BASE_URL="$glm_base_url" \
ANTHROPIC_AUTH_TOKEN="$api_key" \ ANTHROPIC_AUTH_TOKEN="$api_key" \
ANTHROPIC_DEFAULT_OPUS_MODEL=glm-5.2 \ ANTHROPIC_DEFAULT_OPUS_MODEL=glm-5.3 \
ANTHROPIC_DEFAULT_OPUS_MODEL_NAME="GLM 5.2" \ ANTHROPIC_DEFAULT_OPUS_MODEL_NAME="GLM 5.3" \
ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION="GLM 5.2 — флагман Zhipu AI" \ ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION="GLM 5.3 — флагман Zhipu AI" \
ANTHROPIC_DEFAULT_SONNET_MODEL=glm-5.2 \ ANTHROPIC_DEFAULT_SONNET_MODEL=glm-5.3 \
ANTHROPIC_DEFAULT_SONNET_MODEL_NAME="GLM 5.2" \ ANTHROPIC_DEFAULT_SONNET_MODEL_NAME="GLM 5.3" \
ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION="GLM 5.2 — флагман Zhipu AI" \ ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION="GLM 5.3 — флагман Zhipu AI" \
ANTHROPIC_DEFAULT_HAIKU_MODEL='glm-5.2[1m]' \ ANTHROPIC_DEFAULT_HAIKU_MODEL=glm-5.3 \
ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME="GLM 5.2 [1M]" \ ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME="GLM 5.3" \
ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION="GLM 5.2 [1M] — расширенный контекст" \ ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION="GLM 5.3 — флагман Zhipu AI" \
CLAUDE_CODE_SUBAGENT_MODEL=glm-5.2 \ CLAUDE_CODE_SUBAGENT_MODEL=glm-5.3 \
CLAUDE_CODE_MAX_CONTEXT_TOKENS=1000000 \
CLAUDE_CODE_ALWAYS_ENABLE_EFFORT=1 \ CLAUDE_CODE_ALWAYS_ENABLE_EFFORT=1 \
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \ CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \
claude --dangerously-skip-permissions --effort "${AI_EFFORT:-max}" --system-prompt-file "$_PROMPT_FILE" "$@" claude --dangerously-skip-permissions --effort "${AI_EFFORT:-max}" --system-prompt-file "$_PROMPT_FILE" "$@"
@@ -2005,7 +2012,7 @@ 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.7 Code (через Claude Code, API ключ сохраняется)" echo -e " ${CYAN}ai-kimi${NC} - Kimi K2.7 Code (через Claude Code, API ключ сохраняется)"
echo -e " ${CYAN}ai-glm${NC} - GLM 5.2 (через Claude Code, Zhipu AI API)" echo -e " ${CYAN}ai-glm${NC} - GLM 5.3 (через Claude Code, Zhipu AI API)"
echo -e " ${CYAN}ai-openrouter${NC} - OpenRouter (через Claude Code: GPT-5.5, Grok, Qwen, GLM 5.2 и др.)" echo -e " ${CYAN}ai-openrouter${NC} - OpenRouter (через Claude Code: GPT-5.5, Grok, Qwen, GLM 5.2 и др.)"
echo -e " ${CYAN}ai-opencode${NC} - OpenCode Go (через Claude Code + routatic-proxy: DeepSeek, Kimi, Qwen, GLM, MiniMax, MiMo)" echo -e " ${CYAN}ai-opencode${NC} - OpenCode Go (через Claude Code + routatic-proxy: DeepSeek, Kimi, Qwen, GLM, MiniMax, MiMo)"
echo "" echo ""

View File

@@ -18,6 +18,34 @@ mkdir -p "$HOME/.config/ai-setup"
LOG="$HOME/.config/ai-setup/setup.log" LOG="$HOME/.config/ai-setup/setup.log"
_log() { printf '%s [%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" "$2" >> "$LOG"; } _log() { printf '%s [%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" "$2" >> "$LOG"; }
# Обновление: git pull + перегенерация всех ai-* лаунчеров (пункт меню 7)
do_update() {
local repo_dir
repo_dir="$(pwd)"
echo -e "${BLD}Обновляем репозиторий...${CLR}"
git -C "$repo_dir" pull --ff-only
echo ""
echo -e "${BLD}Перегенерация скриптов...${CLR}"
bash "$repo_dir/scripts/ai-setup.sh"
}
# CLI-режим: ./setup.sh update (или --update) — обновление без меню
case "${1:-}" in
update|--update)
_log "setup" "update (CLI) на $(hostname)"
do_update
exit $?
;;
"")
# без аргументов — интерактивное меню ниже
;;
*)
echo "Неизвестный аргумент: $1"
echo "Использование: ./setup.sh [update] — без аргументов открывается меню"
exit 1
;;
esac
if command -v whiptail >/dev/null 2>&1; then if command -v whiptail >/dev/null 2>&1; then
MENU_ARGS=() MENU_ARGS=()
MENU_ARGS+=("1" "AI-инструменты (установить лаунчеры + ключи)") MENU_ARGS+=("1" "AI-инструменты (установить лаунчеры + ключи)")
@@ -248,12 +276,7 @@ case "$choice" in
bash tests/test_network.sh bash tests/test_network.sh
;; ;;
7) 7)
REPO_DIR="$(cd "$(dirname "$0")" && pwd)" do_update
echo -e "${BLD}Обновляем репозиторий...${CLR}"
git -C "$REPO_DIR" pull --ff-only
echo ""
echo -e "${BLD}Перегенерация скриптов...${CLR}"
bash "$REPO_DIR/scripts/ai-setup.sh"
;; ;;
8) 8)
bash scripts/ai-setup.sh bash scripts/ai-setup.sh

View File

@@ -41,6 +41,15 @@ test_gpt_no_proxy() {
fi fi
} }
# ── ai-gpt: informative Codex status line ───────────────────────────────────
test_gpt_status_line() {
if echo "$GPT_SECTION" | grep -Fq 'tui.status_line=["model-with-reasoning","git-branch","context-used","five-hour-limit","current-dir"]'; then
ok "ai-gpt: shows model, branch, context usage, primary limit, and directory"
else
fail "ai-gpt: missing configured Codex status line"
fi
}
# ── ai-kimi: launches claude with Kimi backend ──────────────────────────── # ── ai-kimi: launches claude with Kimi backend ────────────────────────────
test_kimi_claude_launcher() { test_kimi_claude_launcher() {
if echo "$KIMI_SECTION" | grep -q 'claude --dangerously-skip-permissions' \ if echo "$KIMI_SECTION" | grep -q 'claude --dangerously-skip-permissions' \
@@ -85,12 +94,13 @@ test_glm_claude_launcher() {
# ── ai-glm: uses official Zhipu AI API ──────────────────────────────────── # ── ai-glm: uses official Zhipu AI API ────────────────────────────────────
test_glm_official_api() { test_glm_official_api() {
if echo "$GLM_SECTION" | grep -q 'api.z.ai/api/anthropic' \ if echo "$GLM_SECTION" | grep -q 'api.z.ai/api/anthropic' \
&& echo "$GLM_SECTION" | grep -q 'ANTHROPIC_DEFAULT_OPUS_MODEL=glm-5.2' \ && echo "$GLM_SECTION" | grep -q 'ANTHROPIC_DEFAULT_OPUS_MODEL=glm-5.3' \
&& echo "$GLM_SECTION" | grep -q "ANTHROPIC_DEFAULT_HAIKU_MODEL='glm-5.2\[1m\]'" \ && echo "$GLM_SECTION" | grep -q 'ANTHROPIC_DEFAULT_HAIKU_MODEL=glm-5.3' \
&& echo "$GLM_SECTION" | grep -q 'CLAUDE_CODE_MAX_CONTEXT_TOKENS=1000000' \
&& echo "$GLM_SECTION" | grep -q 'x-api-key'; then && echo "$GLM_SECTION" | grep -q 'x-api-key'; then
ok "ai-glm: uses official Zhipu AI API (glm-5.2 opus/sonnet, glm-5.2[1m] haiku)" ok "ai-glm: uses official Zhipu AI API (glm-5.3 everywhere, ctx window 1M via env)"
else else
fail "ai-glm: must use official Zhipu AI API with glm-5.2 and x-api-key" fail "ai-glm: must use official Zhipu AI API with glm-5.3, 1M context window and x-api-key"
fi fi
} }
@@ -285,6 +295,7 @@ test_add_hook_syntax() {
test_script_syntax test_script_syntax
test_gpt_autoinstall test_gpt_autoinstall
test_gpt_no_proxy test_gpt_no_proxy
test_gpt_status_line
test_kimi_claude_launcher test_kimi_claude_launcher
test_kimi_official_api test_kimi_official_api
test_kimi_no_artemox test_kimi_no_artemox