Compare commits

..

2 Commits

Author SHA1 Message Date
901af7d17d fix(codex): prevent sound hook timeouts 2026-08-08 19:09:04 +03:00
22b4daa5ce fix(add-account): unhang oauth login via fifo stdin
Логин Claude.ai подвисал навсегда: claude auth login ждёт код из браузера в stdin, а хук запускал его в фоне с </dev/null, куда код вставить невозможно. Теперь /add-account поднимает логин со stdin из fifo и печатает ссылку, а /add-account <код> отдаёт код, дожидается обмена и сохраняет новый аккаунт по email из токена. Провал логина больше не маскируется под успешное сохранение текущего аккаунта, а фоновые процессы гасятся строго по своим PID вместо широкого pkill -f. Поведение закреплено пятью тестами в tests/test_fixes.sh.
2026-08-08 18:33:54 +03:00
9 changed files with 161 additions and 214 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.3 (`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.2 (`ai-glm`) | OpenRouter/GPT-5.5 (`ai-openrouter`) | OpenCode Go (`ai-opencode`) |
|:---:|:---:|:---:|:---:|:---:|:---:|:---:| |:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| `low` | нативно `low` | нативно `low` | thinking on | на стороне сервера | без локального маппинга | без локального маппинга | | `low` | нативно `low` | провайдер поднимает до `high` | thinking on | на стороне сервера | без локального маппинга | без локального маппинга |
| `medium` | нативно `medium` | провайдер поднимает до `high` | thinking on | на стороне сервера | без локального маппинга | без локального маппинга | | `medium` | нативно `medium` | провайдер поднимает до `high` | thinking on | на стороне сервера | без локального маппинга | без локального маппинга |
| `high` | нативно `high` | нативно `high` | thinking on | на стороне сервера | без локального маппинга | без локального маппинга | | `high` | нативно `high` | нативно `high` | thinking on | на стороне сервера | без локального маппинга | без локального маппинга |
| `xhigh` | нативно `xhigh` | провайдер поднимает до `high` | thinking on | на стороне сервера | без локального маппинга | без локального маппинга | | `xhigh` | нативно `xhigh` | провайдер поднимает до `max` | thinking on | на стороне сервера | без локального маппинга | без локального маппинга |
| `max` | нативно `max` | нативно `max` | thinking on | на стороне сервера | без локального маппинга | без локального маппинга | | `max` | нативно `max` | нативно `max` | thinking on | на стороне сервера | без локального маппинга | без локального маппинга |
Для OpenRouter текущий лаунчер не делает локального преобразования effort и отправляет Для OpenRouter текущий лаунчер не делает локального преобразования effort и отправляет
@@ -45,13 +45,12 @@ low -> medium -> high -> xhigh -> max
### DeepSeek V4 ### DeepSeek V4
```text ```text
low -> high -> max high -> max
``` ```
- 3 реальных уровня. - 2 реальных уровня.
- `low` остаётся `low` — реально снижает thinking бюджет. - `low` и `medium` фактически поднимаются до `high`.
- `medium` и `xhigh` фактически поднимаются до `high`. - `xhigh` фактически поднимается до `max`.
- `max` остаётся `max` — максимальный бюджет thinking.
- Маппинг выполняется на стороне DeepSeek API. - Маппинг выполняется на стороне DeepSeek API.
- Дефолт `ai-deepseek`: `high`. - Дефолт `ai-deepseek`: `high`.
@@ -66,7 +65,7 @@ on / off
- Градаций глубины reasoning нет. - Градаций глубины reasoning нет.
- Дефолт `ai-kimi`: `high`. - Дефолт `ai-kimi`: `high`.
### GLM 5.3 (Zhipu AI) ### GLM 5.2 (Zhipu AI)
```text ```text
high / max high / max
@@ -100,7 +99,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.3 | Zhipu AI API | На стороне сервера | | GLM 5.2 | 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` |
@@ -126,4 +125,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` у Kimi и GLM фактически не снижают reasoning; у DeepSeek `low` теперь даёт реальный уровень `low` и экономит бюджет. - `low`/`medium` у DeepSeek, Kimi и GLM фактически не снижают reasoning.

View File

@@ -15,11 +15,6 @@ 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,15 +56,6 @@ 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)
@@ -160,7 +151,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.3 API (`api.z.ai/api/anthropic`) - `ai-glm` - Claude Code через Zhipu AI GLM 5.2 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`
@@ -233,9 +224,9 @@ android init
## Правила агентов ## Правила агентов
В `home-configs/GLOBAL_RULES.md` - компактные правила о границах автономности, минимальных В `home-configs/GLOBAL_RULES.md` - правила Карпати (Think Before Coding, Simplicity First,
изменениях, пропорциональной проверке, работе с Git и контекстом проекта, а также пользовательские Surgical Changes, Goal-Driven Execution) плюс пользовательские правила (отвечать по-русски,
настройки: русский язык, доступ к Redmine и запрет на commit/staging без явной команды. не коммитить без команды, не делать git add без команды и др.).
`scripts/ai-setup.sh` копирует их в native rule-файлы всех CLI. Лаунчеры обновляют их при каждом запуске. `scripts/ai-setup.sh` копирует их в native rule-файлы всех CLI. Лаунчеры обновляют их при каждом запуске.

View File

@@ -1,92 +1,100 @@
# Global AI Agent Rules # Global AI Agent Rules
Use these defaults across projects, then apply any more specific repository instructions. Prefer clear outcomes and proportional safeguards over process for its own sake. Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.
## 1. Resolve Intent Without Needless Blocking **Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.
- Inspect the available context before asking the user for information. ## 1. Think Before Coding
- Ask a focused question only when an ambiguity would materially change the result, create meaningful risk, or cannot be resolved from the repository or task context.
- Otherwise choose the simplest reasonable interpretation and state only assumptions that materially affect the result.
- Surface important tradeoffs and recommend a simpler or safer approach when it better serves the user's goal.
## 2. Keep Changes Minimal and Scoped **Don't assume. Don't hide confusion. Surface tradeoffs.**
- Implement only the requested behavior and the support required to make it correct. Before implementing:
- Do not add speculative features, single-use abstractions, unrequested configurability, or handling for implausible scenarios. - State your assumptions explicitly. If uncertain, ask.
- Preserve existing public behavior and local conventions unless the task requires changing them. - If multiple interpretations exist, present them - don't pick silently.
- Do not refactor, reformat, or clean up unrelated code. Mention unrelated issues only when they are relevant risks or blockers. - If a simpler approach exists, say so. Push back when warranted.
- Remove imports, variables, functions, files, or comments made obsolete by your own changes; leave pre-existing dead code alone unless asked. - If something is unclear, stop. Name what's confusing. Ask.
- Every changed line should trace to the requested outcome.
## 3. Act Autonomously Within the Request ## 2. Simplicity First
- 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. **Minimum code that solves the problem. Nothing speculative.**
- 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.
- 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.
## 4. Plan and Verify Proportionally - No features beyond what was asked.
- No abstractions for single-use code.
- No "flexibility" or "configurability" that wasn't requested.
- No error handling for impossible scenarios.
- If you write 200 lines and it could be 50, rewrite it.
- Define observable success criteria before changing code. Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
- For non-trivial multi-step work, keep a brief plan whose steps include their verification. Skip planning ceremony for simple tasks.
- For a bug fix, reproduce the failure first when feasible. Add a focused regression test when it provides durable value and the repository has an appropriate test layer.
- Run the smallest relevant checks first, then broader checks when the change's risk justifies them.
- Do not claim completion without fresh evidence. Report the commands run, their results, and anything that remains unverified.
## 5. Keep Git Changes Reviewable ## 3. Surgical Changes
- Never run `git commit` unless the user explicitly asks for a commit. **Touch only what you must. Clean up only your own mess.**
- Do not stage changes unless the user explicitly asks for staging, committing, or another operation that requires it. Do not disturb changes already staged by the user.
- Leave tracked edits unstaged so they remain visible in `git diff`.
- Remember that untracked files do not appear in ordinary `git diff`. Show them with `git status --short`, list their paths in the handoff, and use `git diff --no-index -- /dev/null <path>` when a patch view is useful.
- Before handing off, inspect both `git diff` and `git status --short` and distinguish your changes from unrelated existing work.
## 6. Load Relevant Project Context When editing existing code:
- Don't "improve" adjacent code, comments, or formatting.
- Don't refactor things that aren't broken.
- Match existing style, even if you'd do it differently.
- If you notice unrelated dead code, mention it - don't delete it.
- Before acting, read the applicable agent instruction files and the project documentation directly relevant to the task. When your changes create orphans:
- Follow referenced documents when they are needed to understand requirements, commands, architecture, or validation. Do not scan unrelated Markdown files indiscriminately. - Remove imports/variables/functions that YOUR changes made unused.
- Use the repository's existing tools, conventions, and test commands unless the task requires otherwise. - Don't remove pre-existing dead code unless asked.
## 7. Communication Style The test: Every changed line should trace directly to the user's request.
- Reply in Russian, use informal "ты", and communicate like a friendly programmer teammate. ## 4. Goal-Driven Execution
- Lead with the outcome and keep the explanation concise while preserving evidence, material caveats, and next steps.
- Keep identifiers, commands, exact error messages, and source quotations in their original language when accuracy matters.
- Humor, irony, and swearing are welcome when they fit the conversation; clarity and respect come first.
## 8. Reusable Skills **Define success criteria. Loop until verified.**
- After completing the current task, propose a focused reusable skill only when the user has repeated a workflow and reuse would clearly help. Transform tasks into verifiable goals:
- Do not interrupt active work to create a skill, and do not create or edit skill files without explicit user approval. - "Add validation" -> "Write tests for invalid inputs, then make them pass"
- If approved skill changes live outside the current repository, report their exact paths and how the user can inspect them. - "Fix the bug" -> "Write a test that reproduces it, then make it pass"
- "Refactor X" -> "Ensure tests pass before and after"
## 9. Redmine (red.eltex.loc) For multi-step tasks, state a brief plan:
```
1. [Step] -> verify: [check]
2. [Step] -> verify: [check]
3. [Step] -> verify: [check]
```
- When the request or an Eltex branch identifies a Redmine issue, read its specification, status, and comments before implementing. Eltex branches commonly start with the issue number (for example, `485448_keyboard_opening_fix` maps to issue `485448`). Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
- Use the personal API key stored only in `~/.config/ai-setup/redmine_key` (mode 600):
```bash
curl -fsS -H "X-Redmine-API-Key: $(cat ~/.config/ai-setup/redmine_key)" \
"https://red.eltex.loc/issues/<N>.json?include=journals"
```
- Read `issue.description`, `issue.status.name`, and recent non-empty `issue.journals[].notes`. If the key is missing or the request fails, report that instead of guessing the task requirements.
- Never print, log, or write the API key into repository files.
## 10. GitLab (gitlab.eltex.loc) ---
- GitLab access uses **per-project** tokens stored in `~/.config/ai-setup/` (mode 600). Each one is a project access token and works only for its own project: **These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.
| File | Project | id | # Global Rules for All AI Agents
| :--- | :--- | :--- |
| `gitlab_token_superapp` | `voip/vp-apps/superapp-launcher` | 4928 |
| `gitlab_token_common_library` | `voip/vp-apps/common-library` | 4825 |
| `gitlab_token_smart_home` | `android_apps/smart_home` | 1409 |
- Pick the file matching the project you are working with. A token from another project returns HTTP 404, not 403, so a wrong token looks exactly like a missing project — check the token before concluding the resource does not exist. The rules below are mandatory for every interaction and task. They are intentionally placed after the general coding guidelines above, but they have higher priority when a conflict exists.
```bash
curl -fsS -H "PRIVATE-TOKEN: $(cat ~/.config/ai-setup/gitlab_token_superapp)" \ 1. **Communication style:**
"https://gitlab.eltex.loc/api/v4/projects/4928/merge_requests/241" Always reply in Russian, in a friendly peer-to-peer tone, using informal "ты". Appropriate swearing, humor, sarcasm, and irony are allowed and welcome. Communicate like a live programmer teammate, not like a dry robot.
```
- There is no generic `gitlab_token` fallback any more. Tools that accept an explicit token path (for example the `eltex-review` helper) must be given `--token-file <path>`. 2. **No commits without an explicit request:**
- `local.properties` in superapp-launcher also holds `gitlab.token` and `redmine.apiKey`; it is gitignored and must stay that way. Never run `git commit` unless the user has directly and unambiguously asked for it. The final commit always remains with the user, or is made strictly by the user's command.
- Never print, log, or write these tokens into repository files.
3. **Plain git diff visibility:**
All changes must remain visible to the user through the standard `git diff` command. Leave modified files in the working directory unstaged. Do not add files to the Git index with `git add` unless the user explicitly asks for staging, committing, or another action that requires staging, because staging hides changes from plain `git diff`.
4. **Project context:**
At the start of work, pay close attention to all provided project `.md` files, because they are provided automatically and contain the current repository's context and specifics.
5. **Reusable skills:**
When the user repeats the same instruction, output format, correction, or workflow, treat it as a candidate for a reusable skill.
Do not interrupt active work just to create a skill. Finish the current task first, then briefly propose the skill name, trigger description, and what files or tools it should contain.
Do not create or edit skill files silently. Create or update a skill only after explicit user approval.
Keep skills focused: prefer several small skills over one broad skill. A useful skill should have a precise description for when to use it, short instructions for how to act, and reusable tools, templates, references, or examples when they make the result more stable.
If skill files are outside the current git repository, clearly state the exact paths changed and how the user can inspect them, because those changes are not visible in the project `git diff`.
6. **Redmine (red.eltex.loc) access:**
Redmine issues require authentication. To read a task's spec and comments, use the personal API key stored in `~/.config/ai-setup/redmine_key` (chmod 600). Eltex branches usually start with the issue number (e.g. `485448_keyboard_opening_fix` → issue `485448` → https://red.eltex.loc/issues/485448). Fetch the issue with its comments (journals) via:
```bash
curl -s -H "X-Redmine-API-Key: $(cat ~/.config/ai-setup/redmine_key)" \
"https://red.eltex.loc/issues/<N>.json?include=journals"
```
Read `issue.description` for the spec, `issue.journals[].notes` for comments (recent non-empty ones matter most), `issue.status.name` for status. Keep the key only in that file — never write it into files tracked by git.

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 for the DeepSeek balance (USD only) - The `¥` symbol (U+00A5) for CNY balance in DeepSeek output
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,12 +4,6 @@ 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')
@@ -17,18 +11,15 @@ 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;220mlow\033[00m' ;; # золотой low) printf '\033[38;5;220m[low]\033[00m' ;; # золотой
medium) printf '\033[38;5;50mmedium\033[00m' ;; # насыщенный teal medium) printf '\033[38;5;50m[medium]\033[00m' ;; # насыщенный teal
high) printf '\033[38;5;38mhigh\033[00m' ;; # глубокий голубой high) printf '\033[38;5;38m[high]\033[00m' ;; # глубокий голубой
xhigh) printf '\033[38;5;206mxhigh\033[00m' ;; # насыщенный фиолетовый xhigh) printf '\033[38;5;206m[xhigh]\033[00m' ;; # насыщенный фиолетовый
max) printf '\033[38;5;220mm\033[38;5;114ma\033[38;5;50mx\033[38;5;206m\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" ;; *) printf '\033[38;5;252m[%s]\033[00m' "$1" ;;
esac esac
} }
@@ -38,7 +29,6 @@ _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
@@ -49,7 +39,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" ] && { _sep; printf "\033[38;5;111m%s\033[00m" "$branch"; } [ -n "$branch" ] && printf " \033[38;5;252m[%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")
@@ -94,21 +84,19 @@ if [ -n "$model" ]; then
fi fi
fi fi
fi fi
[ -n "$account" ] && { _sep; printf "%s%s\033[00m" "$brand_color" "$account"; } [ -n "$account" ] && 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" ] && { _sep; printf "%s%s\033[00m" "$brand_color" "$kimi_account"; } [ -n "$kimi_account" ] && printf " %s[%s]\033[00m" "$brand_color" "$kimi_account"
fi fi
if [ -n "$effort" ]; then if [ -n "$effort" ]; then
_sep printf " %s%s " "$brand_color" "$model"
printf "%s%s " "$brand_color" "$model"
_effort_color "$effort" _effort_color "$effort"
else else
_sep printf " %s%s\033[00m" "$brand_color" "$model"
printf "%s%s\033[00m" "$brand_color" "$model"
fi fi
fi fi
@@ -150,7 +138,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" ] && { _sep; printf "\033[38;5;78m%s\033[00m" "$balance"; } [ -n "$balance" ] && printf " \033[38;5;78m%s\033[00m" "$balance"
fi fi
# Фоновое обновление баланса (не чаще раза в 30 секунд) # Фоновое обновление баланса (не чаще раза в 30 секунд)
@@ -171,12 +159,13 @@ 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:
if info.get('currency', '').upper() != 'USD': curr = info.get('currency', '')
continue
total = info.get('total_balance', '0') total = info.get('total_balance', '0')
parts.append(f'\${total}') sym = symbols.get(curr, curr)
parts.append(f'{sym}{total}')
if parts: if parts:
print(' '.join(parts)) print(' '.join(parts))
" 2>/dev/null) " 2>/dev/null)
@@ -193,7 +182,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" ] && { _sep; printf "\033[38;5;78m%s\033[00m" "$balance"; } [ -n "$balance" ] && printf " \033[38;5;78m%s\033[00m" "$balance"
fi fi
# Фоновое обновление баланса (не чаще раза в 30 секунд) # Фоновое обновление баланса (не чаще раза в 30 секунд)
@@ -258,13 +247,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")
_sep; printf "%s%s:%s%%\033[00m" "$color" "$remaining" "$five_int" 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")
_sep; printf "%s%s:%s%%\033[00m" "$color" "$remaining" "$week_int" printf " %s%s:%s%%\033[00m" "$color" "$remaining" "$week_int"
fi fi
fi fi
@@ -274,7 +263,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")
_sep; printf "%sctx:%s%%\033[00m" "$color" "$ctx_int" 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,24 +706,25 @@ 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} {status}') print(f' \033[1;36m💰 Баланс DeepSeek:\033[0m {total} {curr} {status}')
if float(granted) > 0: if float(granted) > 0:
print(f' └─ Начислено: \${granted}') print(f' └─ Начислено: {granted} {curr}')
if float(topped_up) > 0: if float(topped_up) > 0:
print(f' └─ Пополнено: \${topped_up}') print(f' └─ Пополнено: {topped_up} {curr}')
cache_parts.append(f'\${total}') cache_parts.append(f'{sym}{total}')
# Cache USD balance for statusline # Cache all currencies with symbols 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)
@@ -1242,9 +1243,7 @@ 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"
@@ -1561,7 +1560,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.3 (Anthropic-compatible API) # ai-glm - запуск Claude Code через Zhipu AI GLM 5.2 (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"
@@ -1581,7 +1580,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.3" _claude_test_api "$glm_base_url/v1/messages" "x-api-key: $api_key" "glm-5.2"
_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
@@ -1622,7 +1621,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.3" _claude_test_api "$glm_base_url/v1/messages" "x-api-key: $api_key" "glm-5.2"
_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
@@ -1645,37 +1644,31 @@ 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"
# Пикер: все три слота = glm-5.3. availableModels НЕ задаём (он схлопывает пикер). # Пикер: Opus/Sonnet = GLM 5.2, Haiku = GLM 5.2[1m]. availableModels НЕ задаём
# Claude Code навязывает 3 слота; незаданный показал бы чужой Claude, поэтому # (он схлопывает пикер). Claude Code навязывает 3 слота; незаданный показал бы
# opus+sonnet+haiku = glm-5.3. Окно контекста у всех слотов задаёт env # чужой Claude, поэтому opus+sonnet = glm-5.2, haiku = glm-5.2[1m].
# 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.3 не распознан как effort-capable и параметр не уходит). # для GLM (иначе glm-5.2 не распознан как effort-capable и параметр не уходит).
# /effort внутри сессии работает; ручной override запуска: AI_EFFORT=high ai-glm. # /effort внутри сессии работает; ручной override запуска: AI_EFFORT=high ai-glm.
# CLAUDE_CODE_DISABLE_1M_CONTEXT НЕ выставляем: GLM 5.3 реально держит 1M контекста, # CLAUDE_CODE_DISABLE_1M_CONTEXT НЕ выставляем: GLM 5.2 реально держит 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.3 \ ANTHROPIC_DEFAULT_OPUS_MODEL=glm-5.2 \
ANTHROPIC_DEFAULT_OPUS_MODEL_NAME="GLM 5.3" \ ANTHROPIC_DEFAULT_OPUS_MODEL_NAME="GLM 5.2" \
ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION="GLM 5.3 — флагман Zhipu AI" \ ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION="GLM 5.2 — флагман Zhipu AI" \
ANTHROPIC_DEFAULT_SONNET_MODEL=glm-5.3 \ ANTHROPIC_DEFAULT_SONNET_MODEL=glm-5.2 \
ANTHROPIC_DEFAULT_SONNET_MODEL_NAME="GLM 5.3" \ ANTHROPIC_DEFAULT_SONNET_MODEL_NAME="GLM 5.2" \
ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION="GLM 5.3 — флагман Zhipu AI" \ ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION="GLM 5.2 — флагман Zhipu AI" \
ANTHROPIC_DEFAULT_HAIKU_MODEL=glm-5.3 \ ANTHROPIC_DEFAULT_HAIKU_MODEL='glm-5.2[1m]' \
ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME="GLM 5.3" \ ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME="GLM 5.2 [1M]" \
ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION="GLM 5.3 — флагман Zhipu AI" \ ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION="GLM 5.2 [1M] — расширенный контекст" \
CLAUDE_CODE_SUBAGENT_MODEL=glm-5.3 \ CLAUDE_CODE_SUBAGENT_MODEL=glm-5.2 \
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" "$@"
@@ -2012,7 +2005,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.3 (через Claude Code, Zhipu AI API)" echo -e " ${CYAN}ai-glm${NC} - GLM 5.2 (через 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,34 +18,6 @@ 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-инструменты (установить лаунчеры + ключи)")
@@ -276,7 +248,12 @@ case "$choice" in
bash tests/test_network.sh bash tests/test_network.sh
;; ;;
7) 7)
do_update REPO_DIR="$(cd "$(dirname "$0")" && pwd)"
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,15 +41,6 @@ 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' \
@@ -94,13 +85,12 @@ 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.3' \ && echo "$GLM_SECTION" | grep -q 'ANTHROPIC_DEFAULT_OPUS_MODEL=glm-5.2' \
&& echo "$GLM_SECTION" | grep -q 'ANTHROPIC_DEFAULT_HAIKU_MODEL=glm-5.3' \ && echo "$GLM_SECTION" | grep -q "ANTHROPIC_DEFAULT_HAIKU_MODEL='glm-5.2\[1m\]'" \
&& 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.3 everywhere, ctx window 1M via env)" ok "ai-glm: uses official Zhipu AI API (glm-5.2 opus/sonnet, glm-5.2[1m] haiku)"
else else
fail "ai-glm: must use official Zhipu AI API with glm-5.3, 1M context window and x-api-key" fail "ai-glm: must use official Zhipu AI API with glm-5.2 and x-api-key"
fi fi
} }
@@ -124,14 +114,20 @@ test_cursor_native_launcher() {
fi fi
} }
# ── global rules: source and native rule files ─────────────────────── # ── global rules: Karpathy-style guidelines and native rule files ───────────
test_global_rules_source_installed() { test_global_rules_include_quality_guidelines() {
if [ -s "$GLOBAL_RULES_SOURCE" ] \ karpathy_line=$(grep -n '^## 1\. Think Before Coding$' "$GLOBAL_RULES_SOURCE" | head -1 | cut -d: -f1)
global_line=$(grep -n '^# Global Rules for All AI Agents$' "$GLOBAL_RULES_SOURCE" | head -1 | cut -d: -f1)
if [ -n "$karpathy_line" ] \
&& [ -n "$global_line" ] \
&& [ "$karpathy_line" -lt "$global_line" ] \
&& grep -q 'Always reply in Russian' "$GLOBAL_RULES_SOURCE" \
&& grep -q 'Plain git diff visibility' "$GLOBAL_RULES_SOURCE" \
&& grep -q 'GLOBAL_RULES_SOURCE=' "$SCRIPT" \ && grep -q 'GLOBAL_RULES_SOURCE=' "$SCRIPT" \
&& grep -q 'cp "$GLOBAL_RULES_SOURCE" "$CONFIG_DIR/global_rules.md"' "$SCRIPT"; then && grep -q 'cp "$GLOBAL_RULES_SOURCE" "$CONFIG_DIR/global_rules.md"' "$SCRIPT"; then
ok "global rules: non-empty source file is installed as global_rules.md" ok "global rules: source file includes English Karpathy guidelines before user rules"
else else
fail "global rules: missing source file or installation wiring" fail "global rules: missing source file, English Karpathy guidelines, or user rules"
fi fi
} }
@@ -295,7 +291,6 @@ 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
@@ -303,7 +298,7 @@ test_glm_claude_launcher
test_glm_official_api test_glm_official_api
test_gemini_native_launcher test_gemini_native_launcher
test_cursor_native_launcher test_cursor_native_launcher
test_global_rules_source_installed test_global_rules_include_quality_guidelines
test_native_rule_files_generated test_native_rule_files_generated
test_codex_sound_hooks_detach_stdio test_codex_sound_hooks_detach_stdio
test_fix7_trap_tmp test_fix7_trap_tmp