Compare commits

..

35 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
Виталий Никитенко
7304c5c84b docs(rules): document per-project GitLab tokens
GitLab access uses project access tokens, one per project, and a token from
another project answers 404 instead of 403 - a wrong token is indistinguishable
from a missing project. Nothing described where those tokens live, so agents
either guessed or gave up.

Added section 10 with the file -> project mapping (superapp-launcher,
common-library, smart_home), a working curl example, and the note that the
generic `gitlab_token` fallback is gone: helpers such as eltex-review need an
explicit --token-file.
2026-08-12 11:24:06 +03:00
Виталий Никитенко
ee32371d30 refactor(rules): simplify global agent guidance 2026-08-08 21:02:00 +03:00
Виталий Никитенко
4723d21693 fix(codex): prevent sound hook timeouts 2026-08-08 19:20:10 +03:00
Виталий Никитенко
532d70f478 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 19:20:10 +03:00
Виталий Никитенко
eb6a7bddad feat(rules): доступ к Redmine (red.eltex.loc) через API-ключ
Правило №6: читать задачи Redmine по персональному API-ключу из
~/.config/ai-setup/redmine_key, вытаскивать ТЗ/статус/комментарии
через /issues/<N>.json?include=journals. Ключ держать только в этом
файле, не вписывать в git.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 09:04:08 +03:00
Виталий Никитенко
57857cda4d remove bundled skills; skills now come from separate repo
Удалены все кастомные скиллы из home-configs/claude/skills и логика
их деплоя (секция 6.6 ai-setup.sh для Claude и Gemini). Из README
убраны упоминания про деплой скиллов, из test_fixes.sh — тест наличия
android-скилла. Хуки аккаунтов, команда android init, правило #5 про
reusable skills и симлинк общей папки ~/.claude/skills оставлены
нетронутыми — скиллы теперь устанавливаются из отдельного репозитория.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 11:44:05 +03:00
ekaterina soloveva
0c5057bc21 require node >=18 in ai-setup, not just presence
ai-setup.sh проверял только наличие node через command -v, поэтому системный Node v12 (Ubuntu 22.04) проходил проверку и установка NodeSource 22 не запускалась. Из-за этого хук claude-notifier падал с SyntaxError на optional chaining (?.) и nullish coalescing (??), которые поддерживаются только начиная с Node 14. Теперь скрипт извлекает мажорную версию и при значении меньше 18 ставит/обновляет Node, чтобы регенерация не возвращала сломанные хуки.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-14 11:26:49 +03:00
Виталий Никитенко
c379ee8efc feat(ai-setup): /logout для ai-glm, ai-deepseek, ai-kimi
Новый UserPromptSubmit хук logout-account-hook.sh перехватывает /logout
(провайдер по AI_LAUNCHER) и /logout-glm|/logout-deepseek|/logout-kimi
(явно), удаляет сохранённый ключ провайдера. Для ai-claude /logout
отступает — работает нативный oauth-logout.

- хук: удаляет glm_key/deepseek_key или текущий kimi_keys/<current>.key
  (+.meta) с пересчётом current; exit 2 глушит нативную /logout для
  api-key провайдеров
- 3 скилла logout-{glm,deepseek,kimi} для автокомплита
- ai-setup.sh: секция деплоя 6.7.3 + хук в копирование/регистрацию
  изолированных cfg

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-14 10:10:14 +03:00
1bfef33da1 fix(ai-glm): чиним разорванный env-блок и включаем /effort max
Регрессия из c571584: поясняющий комментарий оказался посреди цепочки
continuation-строк (\), из-за чего statement разрывался — claude
запускался без ANTHROPIC_BASE_URL/AUTH_TOKEN/DEFAULT_*_MODEL (дефолтный
Anthropic, "Not logged in", glm-5.2 not recognized, откат на Haiku 4.5).
Комментарий перенесён над env-блоком, цепочка восстановлена.

Дополнительно: ai-glm стартует на /effort max. max невалиден в settings.json
(там только low/medium/high/xhigh), поэтому задаётся через CLI --effort +
CLAUDE_CODE_ALWAYS_ENABLE_EFFORT=1 (без неё glm-5.2 не распознан как
effort-capable и параметр не уходит к Z.ai). Проверено перехватом трафика:
Claude Code шлёт output_config.effort=max, Z.ai включает thinking
(+35% reasoning tokens vs low на нетривиальной задаче). /effort внутри
сессии работает; ручной override запуска: AI_EFFORT=high ai-glm.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-04 18:59:57 +03:00
c57158420e fix: корректный счётчик контекста в ai-glm (GLM 5.2 = 1M)
CLAUDE_CODE_DISABLE_1M_CONTEXT=1 заставлял Claude Code считать
контекстное окно = 200K, из-за чего ctx: в статус-баре заполнялся
в ~5 раз раньше реального лимита GLM 5.2 (1M токенов).

Флаг убран только в секции ai-glm. Прочие лаунчеры (deepseek/kimi/
openrouter/opencode) флаг оставляют — у них другой контекст, и флаг
убирает [1M]-дубли из пикера. ai-claude его не использует.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-04 17:35:10 +03:00
Виталий Никитенко
0f88f1e07a feat: полная поддержка macOS (Darwin arm64/x86_64)
- ai-setup.sh: brew shellenv + brew-ветки для jq/Node; npm-prefix теперь после установки Node
- ai-setup.sh: install_proxy использует OS_TAG/ARCH_TAG (качает darwin-arm64/amd64)
- ai-setup.sh: afplay в 5 звуковых блоках, портабельный sed -i (BSD), touch ~/.zshrc
- setup.sh: скрытие сетевых пунктов (2/3/4/6) на macOS + адаптация Статуса

Устраняет на маке: npm not found, «не удалось определить пакетный менеджер»,
«неизвестная архитектура: arm64». Сетевые скрипты (ru-bypass/ks-*) оставлены Linux-only.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-01 09:12:06 +03:00
9e64088e46 chore: убрать VLESS-прокси, proxychains и fuck-rkn.sh
Пользователь перешёл на Amnezia VPN — встроенный VLESS-прокси (xray + proxychains4)
и отдельный server-installer fuck-rkn.sh (telemt + xray) больше не нужны. Убран
весь код в ai-setup.sh/setup.sh, конфиги с VPN-кредами, неактуальный тест и
упоминания в документации.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-06-30 21:50:50 +03:00
Виталий Никитенко
9b906823cd feat: добавить ai-cursor лаунчер (нативный Cursor CLI agent)
Генерация нативного лаунчера ai-cursor для Cursor CLI (agent) с
автоустановкой через cursor.com/install, интеграцией proxychains4 в
VLESS-режиме, обновлением ~/.cursorrules и флагом --force (--yolo).
Очистка старого ai-cursor из ~/.local/bin при нестандартном BIN_DIR.
Добавлены тест генерации и упоминания в EFFORT_MAPPING/QUICK_START/README.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-30 09:27:02 +03:00
5038b4f8c6 Merge pull request 'Добавлена лицензия MIT и копирайты' (#21) from add-license into main
Reviewed-on: #21
2026-06-30 12:15:13 +07:00
Кокос Артем Николаевич
d250099dc5 Добавлена лицензия MIT и копирайты 2026-06-30 12:12:42 +07:00
Виталий Никитенко
1626453813 feat: добавить ~/.cursorrules в глобальные правила — Cursor видит то же, что Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-24 11:10:22 +03:00
Виталий Никитенко
0e15033351 refactor: убрать multi-agent из /el-review — инлайн-ревью силами Claude
Убран вызов встроенного /review (multi-agent workflow). Теперь Claude
читает дифф сам и выдаёт только критические проблемы с готовыми diff-фиксами.

- Никаких sub-agent'ов и deep cross-file investigations
- Только баги, утечки, падения, гонки, нарушения инвариантов
- Исключены: форматирование, нейминг, стиль, рефакторинг
- Готовый diff на каждую проблему, коротко, на русском

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-23 09:19:39 +03:00
Виталий Никитенко
a74597dba6 feat: добавить ai-opencode лаунчер (OpenCode Go + routatic-proxy)
- Новый лаунчер ai-opencode с routatic-proxy на 127.0.0.1:3456
- Автоустановка routatic-proxy под linux/darwin amd64/arm64
- Модели: deepseek-v4-pro (opus/sonnet), kimi-k2.6 (haiku)
- AI_RESET_KEY, OPENCODE_GO_API_KEY env fallback, управление opencode_key
- Документация: EFFORT_MAPPING.md, QUICK_START.md, README.md
- Статус-проверка в setup.sh: ai-opencode, opencode_key, opencode.ai
- Тесты: 4 новых теста для ai-opencode в test_fixes.sh

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-23 08:41:38 +03:00
Виталий Никитенко
cf0cd749e9 chore: убрать правило про типографику из глобальных правил агентов
Удалён пункт Typography (дефис-минус вместо длинных тире),
последующие пункты перенумерованы.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-22 14:42:41 +03:00
Виталий Никитенко
c4fea61d74 refactor: пункт 1 больше не трогает сеть, управление VLESS только в пункте 9
Пункты 1/7/8 вызывали AI_SETUP_VLESS=0, что запускало откат сетевых настроек
(stop xray, сброс прокси GNOME, перепись Firefox user.js, IPv6 on), хотя они
относятся только к установке AI-инструментов. Добавлен режим skip (дефолт):
сеть не трогается, лаунчеры синхронизируются с текущим состоянием xray.
Включение/отключение VLESS - только пункт 9.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-22 14:41:29 +03:00
24416e88e4 refactor: убрать MiniMax/Llama из ai-openrouter, заменить на GLM 5.2
- слот HAIKU переведён с MiniMax M3 на GLM 5.2 (z-ai/glm-5.2)
- слот FABLE (Llama 4 Maverick) удалён полностью
- README и заголовок ai-setup.sh обновлены соответственно

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-21 20:48:02 +03:00
b99b9fcb7d Add AI launcher sound notifications 2026-06-21 09:46:33 +03:00
c0f27600cb feat: сброс ключа через AI_RESET_KEY и переввод при 429
- ai-deepseek, ai-glm, ai-openrouter: AI_RESET_KEY=1 принудительно
  удаляет сохранённый ключ перед запуском
- при 429 предлагается ввести новый ключ вместо немедленного выхода
- OpenRouter сбрасывает ключ без reauth (там нет переменной reauth)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-20 11:27:22 +03:00
41aadbc50f fix: автоматическая установка jq и неинтерактивный пункт 1 setup
- устанавливать jq автоматически, необходим для statusline-command.sh
- не спрашивать GitHub PAT в пункте 1, пропускать marketplace без GITHUB_TOKEN
- добавлена документация по Unicode-рендерингу статусной строки

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-20 11:00:41 +03:00
Виталий Никитенко
3e15729a81 refactor: вынести вопрос про VLESS из пункта 1 в отдельный пункт меню (9)
Пункты 1 (AI-инструменты), 7 (Обновить), 8 (Android CLI) теперь запускают
ai-setup.sh с AI_SETUP_VLESS=0 без вопросов. Пункт 9 (VLESS) спрашивает
включить/отключить и передаёт AI_SETUP_VLESS=1/0 соответственно.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 06:46:14 +03:00
Виталий Никитенко
83a7790235 feat: добавить ai-glm - лаунчер Claude Code через Zhipu AI GLM 5.2
Новый лаунчер ai-glm запускает Claude Code через официальный Anthropic-совместимый
API Zhipu AI (api.z.ai). Модели: glm-5.2 (opus/sonnet), glm-5.2[1m] (haiku).
Обновлены документация, статус, тесты и EFFORT_MAPPING.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 06:41:41 +03:00
Виталий Никитенко
463a3f0bc6 feat: добавить study-git и study-red в исключения ru-bypass
Добавлены домены study-git.eltex-co.ru и study-red.eltex-co.ru в builtin kill switch exceptions и /etc/hosts блок.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-18 08:09:21 +03:00
Виталий Никитенко
61a4421ed2 fix: статусбар показывает реальный email активного Claude-аккаунта
Статусбар слепо доверял accounts/current и показывал legacy-имя даже когда
активна сессия другого аккаунта. Теперь сверяем accessToken из
<current>.credentials.json с активным ~/.claude/.credentials.json и при
расхождении резолвим аккаунт по токену: локальный матч плюс account-email.sh
вместо haiku-велосипеда. Kimi-блок показывает alias (account1/account2).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 06:41:33 +03:00
31 changed files with 1332 additions and 2957 deletions

View File

@@ -2,4 +2,4 @@
Данные правила применяются ко всем агентам, работающим конкретно в этом репозитории. Данные правила применяются ко всем агентам, работающим конкретно в этом репозитории.
1. **Полная перегенерация standalone-скриптов при запуске сетап-скрипта (Full regeneration of standalone scripts on setup run):** При каждом запуске `ai-setup.sh` все генерируемые скрипты в выбранном `BIN_DIR` (`~/bin`, если он уже есть в `PATH`, иначе `~/.local/bin`: `ai-gpt`, `ai-deepseek`, `ai-kimi`, `ai-openrouter`, `ai-gemini`, `ai-api-helpers.sh`, `ai-claude`, `claude-gpt-effort-proxy.py`) должны быть **полностью перезаписаны** актуальными версиями. Запрещено выполнять слияние (merge) старого и нового содержимого или дополнение (append). Скрипт обязан привести все генерируемые файлы к эталонному виду, однозначно определяемому текущей конфигурацией. 1. **Полная перегенерация standalone-скриптов при запуске сетап-скрипта (Full regeneration of standalone scripts on setup run):** При каждом запуске `ai-setup.sh` все генерируемые скрипты в выбранном `BIN_DIR` (`~/bin`, если он уже есть в `PATH`, иначе `~/.local/bin`: `ai-gpt`, `ai-deepseek`, `ai-kimi`, `ai-glm`, `ai-openrouter`, `ai-gemini`, `ai-api-helpers.sh`, `ai-claude`, `claude-gpt-effort-proxy.py`) должны быть **полностью перезаписаны** актуальными версиями. Запрещено выполнять слияние (merge) старого и нового содержимого или дополнение (append). Скрипт обязан привести все генерируемые файлы к эталонному виду, однозначно определяемому текущей конфигурацией.

View File

@@ -1,3 +1,4 @@
# Copyright (c) 2026 Виталий Никитенко. MIT License.
FROM ubuntu:24.04 FROM ubuntu:24.04
# Avoid interactive prompts during package installation # Avoid interactive prompts during package installation

View File

@@ -3,27 +3,33 @@
## Как это работает ## Как это работает
`/effort` и `AI_EFFORT` относятся к лаунчерам, которые запускают Claude Code: `/effort` и `AI_EFFORT` относятся к лаунчерам, которые запускают Claude Code:
`ai-claude`, `ai-deepseek`, `ai-kimi`, `ai-openrouter`. `ai-claude`, `ai-deepseek`, `ai-kimi`, `ai-glm`, `ai-openrouter`, `ai-opencode`.
Нативные лаунчеры `ai-gpt` (OpenAI Codex) и `ai-gemini` (Antigravity `agy`) не используют Нативные лаунчеры `ai-gpt` (OpenAI Codex), `ai-gemini` (Antigravity `agy`) и `ai-cursor`
эту схему. У них свои настройки reasoning внутри соответствующего CLI. (Cursor CLI `agent`) не используют эту схему. У них свои настройки reasoning внутри
соответствующего CLI.
Документ отражает текущую реализацию `scripts/ai-setup.sh`. Документ отражает текущую реализацию `scripts/ai-setup.sh`.
## Таблица маппинга ## Таблица маппинга
| Claude Code `/effort` | Anthropic (`ai-claude`) | DeepSeek V4 (`ai-deepseek`) | Kimi K2.7/K2.6 (`ai-kimi`) | OpenRouter/GPT-5.5 (`ai-openrouter`) | | 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 и отправляет
значение дальше через Claude Code/OpenRouter. Конкретная интерпретация зависит от выбранной значение дальше через Claude Code/OpenRouter. Конкретная интерпретация зависит от выбранной
модели и backend-а OpenRouter. модели и backend-а OpenRouter.
Для OpenCode Go лаунчер работает через `routatic-proxy`, который транслирует Anthropic-запросы
в OpenAI/Gemini-формат для OpenCode Go API. Локального маппинга effort нет — thinking budget
не транслируется напрямую в OpenCode Go бекенд. Прокси и бекенд-модели выполняют reasoning
на своей стороне независимо от уровня effort.
## Нативные уровни каждого провайдера ## Нативные уровни каждого провайдера
### Anthropic (Claude) ### Anthropic (Claude)
@@ -39,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`.
@@ -59,12 +66,29 @@ on / off
- Градаций глубины reasoning нет. - Градаций глубины reasoning нет.
- Дефолт `ai-kimi`: `high`. - Дефолт `ai-kimi`: `high`.
### GLM 5.3 (Zhipu AI)
```text
high / max
```
- Маппинг выполняется на стороне Zhipu AI API.
- Текущий `ai-glm` не делает локального преобразования effort.
- Дефолт `ai-glm`: `high`.
### OpenRouter / GPT-5.5 ### OpenRouter / GPT-5.5
Текущий `ai-openrouter` работает через Claude Code с `ANTHROPIC_BASE_URL=https://openrouter.ai/api` Текущий `ai-openrouter` работает через Claude Code с `ANTHROPIC_BASE_URL=https://openrouter.ai/api`
и не вызывает локальный effort proxy. Поэтому `low`/`medium`/`high`/`xhigh`/`max` не и не вызывает локальный effort proxy. Поэтому `low`/`medium`/`high`/`xhigh`/`max` не
преобразуются в shell-лаунчере. преобразуются в shell-лаунчере.
### OpenCode Go (routatic-proxy)
Текущий `ai-opencode` работает через Claude Code с локальным `routatic-proxy`
(`ANTHROPIC_BASE_URL=http://127.0.0.1:3456`), который транслирует запросы в OpenCode Go API.
Effort не преобразуется локально — thinking budget не имеет прямого эффекта на OpenCode Go бекенд.
Дефолт `ai-opencode`: `high`.
`claude-gpt-effort-proxy.py` все еще генерируется как совместимый helper для старого `claude-gpt-effort-proxy.py` все еще генерируется как совместимый helper для старого
`claude-code-proxy` backend-а. Если использовать его вручную, он маппит только `claude-code-proxy` backend-а. Если использовать его вручную, он маппит только
`xhigh` -> `high`, потому что `claude-code-proxy` принимает `low`, `medium`, `high`, `max`. `xhigh` -> `high`, потому что `claude-code-proxy` принимает `low`, `medium`, `high`, `max`.
@@ -76,12 +100,14 @@ on / off
| 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 | На стороне сервера |
| OpenRouter/GPT-5.5 | Локального маппинга нет | `ai-openrouter` отправляет значение как есть | | OpenRouter/GPT-5.5 | Локального маппинга нет | `ai-openrouter` отправляет значение как есть |
| 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` |
## Persistence effort между сессиями ## Persistence effort между сессиями
Каждый Claude Code лаунчер (`ai-claude`, `ai-deepseek`, `ai-kimi`, `ai-openrouter`) Каждый Claude Code лаунчер (`ai-claude`, `ai-deepseek`, `ai-kimi`, `ai-glm`, `ai-openrouter`, `ai-opencode`)
запоминает свой уровень effort отдельно в `settings.json` лаунчера. запоминает свой уровень effort отдельно в `settings.json` лаунчера.
`/effort` внутри сессии работает для всех уровней. `CLAUDE_CODE_EFFORT_LEVEL` не используется. `/effort` внутри сессии работает для всех уровней. `CLAUDE_CODE_EFFORT_LEVEL` не используется.
@@ -99,5 +125,5 @@ ai-deepseek # без флага - использовать ур
## Рекомендации ## Рекомендации
- Для повседневной работы: `high` или `xhigh`. - Для повседневной работы: `high` или `xhigh`.
- `max` effort имеет реальный эффект у Anthropic и DeepSeek; у Kimi это все тот же thinking on. - `max` effort имеет реальный эффект у Anthropic и DeepSeek; у Kimi и GLM это всё тот же thinking on / маппинг на стороне сервера.
- `low`/`medium` у DeepSeek и Kimi фактически не снижают reasoning. - `low`/`medium` у Kimi и GLM фактически не снижают reasoning; у DeepSeek `low` теперь даёт реальный уровень `low` и экономит бюджет.

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Виталий Никитенко
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -6,8 +6,8 @@
bash setup.sh bash setup.sh
``` ```
Спросит про VLESS-прокси (по умолчанию `Y`; если не нужен, ответь `n`), затем установит лаунчеры Установит лаунчеры
`ai-claude`, `ai-gpt`, `ai-deepseek`, `ai-kimi`, `ai-openrouter`, `ai-gemini` `ai-claude`, `ai-gpt`, `ai-deepseek`, `ai-kimi`, `ai-glm`, `ai-openrouter`, `ai-opencode`, `ai-gemini`, `ai-cursor`
в `~/bin`, если он уже есть в `PATH`, иначе в `~/.local/bin/`, и запишет API-ключи. в `~/bin`, если он уже есть в `PATH`, иначе в `~/.local/bin/`, и запишет API-ключи.
После установки, если команды не видны: После установки, если команды не видны:
@@ -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

@@ -10,7 +10,6 @@
setup.sh # мастер-скрипт с меню setup.sh # мастер-скрипт с меню
scripts/ scripts/
├── ai-setup.sh # устанавливает AI-инструменты в выбранный BIN_DIR ├── ai-setup.sh # устанавливает AI-инструменты в выбранный BIN_DIR
├── fuck-rkn.sh # серверный установщик nginx + telemt + xray
├── ru-bypass.sh # .ru трафик напрямую, kill switch для остального ├── ru-bypass.sh # .ru трафик напрямую, kill switch для остального
├── ks-off.sh # временно отключить kill switch ├── ks-off.sh # временно отключить kill switch
└── ks-on.sh # восстановить kill switch └── ks-on.sh # восстановить kill switch
@@ -18,18 +17,13 @@ home-configs/
├── GLOBAL_RULES.md # глобальные правила для всех агентов ├── GLOBAL_RULES.md # глобальные правила для всех агентов
├── claude/ ├── claude/
│ ├── hooks/ # хуки аккаунтов Claude Code │ ├── hooks/ # хуки аккаунтов Claude Code
│ ├── skills/ # кастомные скиллы для Claude Code (и Gemini)
│ └── statusline-command.sh # статусная строка Claude Code │ └── statusline-command.sh # статусная строка Claude Code
── network/ ── network/
└── README.md # подробная документация по сетевой настройке └── README.md # подробная документация по сетевой настройке
├── vless/
│ └── servers.conf # список VLESS-серверов для прокси
└── proxychains/
└── proxychains-xray.conf # конфиг proxychains (SOCKS5 через xray)
tests/ tests/
├── test_fixes.sh # юнит-тесты структуры ai-setup.sh ├── test_fixes.sh # юнит-тесты структуры ai-setup.sh
└── test_network.sh # тесты маршрутизации └── test_network.sh # тесты маршрутизации
test_isolated.sh # проверка автоустановки ai-gpt и ai-kimi test_isolated.sh # проверка автоустановки ai-gpt, ai-kimi и ai-glm
test_sigint.sh # ручной тест поведения SIGINT test_sigint.sh # ручной тест поведения SIGINT
``` ```
@@ -62,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)
@@ -157,8 +160,11 @@ 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-openrouter` - Claude Code через OpenRouter (GPT-5.5, Grok, Qwen, MiniMax, Llama и др.) - `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-opencode` - Claude Code через OpenCode Go + routatic-proxy (DeepSeek, Kimi, Qwen, GLM, MiniMax, MiMo)
- `ai-gemini` - нативный Antigravity CLI `agy` - `ai-gemini` - нативный Antigravity CLI `agy`
- `ai-cursor` - нативный Cursor CLI `agent`
- `ai-api-helpers.sh` - вспомогательные функции для лаунчеров - `ai-api-helpers.sh` - вспомогательные функции для лаунчеров
- `claude-gpt-effort-proxy.py` - совместимый helper для старого GPT/claude-code-proxy backend - `claude-gpt-effort-proxy.py` - совместимый helper для старого GPT/claude-code-proxy backend
@@ -166,7 +172,6 @@ bash tests/test_network.sh # полные тесты
Также устанавливается: Также устанавливается:
- `~/.config/ai-setup/global_rules.md` и native rule-файлы (`~/.claude/CLAUDE.md`, `~/.codex/AGENTS.md`, `~/.kimi-code/AGENTS.md`, `~/.gemini/GEMINI.md`) - `~/.config/ai-setup/global_rules.md` и native rule-файлы (`~/.claude/CLAUDE.md`, `~/.codex/AGENTS.md`, `~/.kimi-code/AGENTS.md`, `~/.gemini/GEMINI.md`)
- Скиллы из `home-configs/claude/skills/` в `~/.claude/skills/` и `~/.gemini/config/plugins/local-setup/skills/`
## Android CLI ## Android CLI
@@ -207,48 +212,37 @@ android init
- Android CLI находится в preview, API/команды могут меняться. - Android CLI находится в preview, API/команды могут меняться.
- На Windows в v0.7 отключена команда `android emulator`. - На Windows в v0.7 отключена команда `android emulator`.
- Если уже установлен Android Studio, Android CLI может использовать тот же SDK или создать новый в `~/.android/sdk`. Проверяй активный путь через `android info`. - Если уже установлен Android Studio, Android CLI может использовать тот же SDK или создать новый в `~/.android/sdk`. Проверяй активный путь через `android info`.
- Android CLI - нативный бинарь, который спавнит дочерние процессы (emulator, gradle, adb), поэтому он не оборачивается в `proxychains4`.
## VLESS / Xray (опционально)
При запуске `scripts/ai-setup.sh` спрашивает, нужен ли VLESS-прокси для AI API запросов.
Если выбрать **Y**:
- Читает список серверов из `home-configs/vless/servers.conf`
- Проверяет каждый сервер реальным curl'ом через SOCKS5
- Устанавливает Xray, генерирует конфиг, создаёт systemd сервис
- Все лаунчеры оборачиваются в `proxychains4`
Если выбрать **n** - VLESS отключается, xray останавливается, системный/Firefox прокси
сбрасываются в direct. IPv6 включается обратно только если UFW kill switch не активен.
## Ключи и конфиги ## Ключи и конфиги
- `~/.config/ai-setup/deepseek_key` - DeepSeek API key (права 600) - `~/.config/ai-setup/deepseek_key` - DeepSeek API key (права 600)
- `~/.config/ai-setup/kimi_key` - Kimi API key (права 600) - `~/.config/ai-setup/kimi_key` - Kimi API key (права 600)
- `~/.config/ai-setup/glm_key` - Zhipu AI (GLM) API key (права 600)
- `~/.config/ai-setup/openrouter_key` - OpenRouter API key (права 600) - `~/.config/ai-setup/openrouter_key` - OpenRouter API key (права 600)
- `~/.config/ai-setup/opencode_key` - OpenCode Go API key (права 600)
- `~/.config/ai-setup/global_rules.md` - глобальные правила агентов - `~/.config/ai-setup/global_rules.md` - глобальные правила агентов
- `~/.config/ai-setup/network_<profile>.conf` - сохранённые сетевые профили (`GATEWAY`, `DEV`, `LOCAL_DNS`, `AMNEZIA_SERVER`, `KILL_SWITCH_EXCEPTIONS`) - `~/.config/ai-setup/network_<profile>.conf` - сохранённые сетевые профили (`GATEWAY`, `DEV`, `LOCAL_DNS`, `AMNEZIA_SERVER`, `KILL_SWITCH_EXCEPTIONS`)
## Права запуска агентов ## Права запуска агентов
- `ai-gpt` использует `--dangerously-bypass-approvals-and-sandbox` - `ai-gpt` использует `--dangerously-bypass-approvals-and-sandbox`
- `ai-claude`, `ai-deepseek`, `ai-kimi`, `ai-openrouter`, `ai-gemini` используют `--dangerously-skip-permissions` - `ai-claude`, `ai-deepseek`, `ai-kimi`, `ai-glm`, `ai-openrouter`, `ai-opencode`, `ai-gemini` используют `--dangerously-skip-permissions`
- `ai-cursor` использует `--force` (алиас `--yolo`)
Удобно для локального coding workflow, но это не sandbox для недоверенного кода. Удобно для локального coding workflow, но это не sandbox для недоверенного кода.
## Правила агентов ## Правила агентов
В `home-configs/GLOBAL_RULES.md` - правила Карпати (Think Before Coding, Simplicity First, В `home-configs/GLOBAL_RULES.md` - компактные правила о границах автономности, минимальных
Surgical Changes, Goal-Driven Execution) плюс пользовательские правила (отвечать по-русски, изменениях, пропорциональной проверке, работе с Git и контекстом проекта, а также пользовательские
не коммитить без команды, не делать git add без команды и др.). настройки: русский язык, доступ к Redmine и запрет на commit/staging без явной команды.
`scripts/ai-setup.sh` копирует их в native rule-файлы всех CLI. Лаунчеры обновляют их при каждом запуске. `scripts/ai-setup.sh` копирует их в native rule-файлы всех CLI. Лаунчеры обновляют их при каждом запуске.
## Effort Mapping ## Effort Mapping
`EFFORT_MAPPING.md` - таблица effort-уровней (`low`/`medium`/`high`/`xhigh`/`max`) `EFFORT_MAPPING.md` - таблица effort-уровней (`low`/`medium`/`high`/`xhigh`/`max`)
для Claude Code лаунчеров (`ai-claude`, `ai-deepseek`, `ai-kimi`, `ai-openrouter`). для Claude Code лаунчеров (`ai-claude`, `ai-deepseek`, `ai-kimi`, `ai-glm`, `ai-openrouter`, `ai-opencode`).
Нативные `ai-gpt` и `ai-gemini` живут отдельно. Нативные `ai-gpt` и `ai-gemini` живут отдельно.
`claude-gpt-effort-proxy.py` маппит `xhigh` -> `high` для совместимости со старым `claude-gpt-effort-proxy.py` маппит `xhigh` -> `high` для совместимости со старым
@@ -259,14 +253,17 @@ GPT/claude-code-proxy backend; текущие лаунчеры его напря
- `bash`, `curl`, `python3` - `bash`, `curl`, `python3`
- Node.js/npm (для ai-gpt через Codex CLI и ai-gemini через agy) - Node.js/npm (для ai-gpt через Codex CLI и ai-gemini через agy)
- `ipset` (устанавливается автоматически при запуске ru-bypass.sh) - `ipset` (устанавливается автоматически при запуске ru-bypass.sh)
- (опционально) `proxychains-ng` - для VLESS-режима
- (опционально) `gsettings` - для автонастройки системного прокси GNOME - (опционально) `gsettings` - для автонастройки системного прокси GNOME
## Тесты ## Тесты
```bash ```bash
bash tests/test_fixes.sh # структура ai-setup.sh и синтаксис bash bash tests/test_fixes.sh # структура ai-setup.sh и синтаксис bash
bash test_isolated.sh # автоустановка ai-gpt и ai-kimi через mock curl bash test_isolated.sh # автоустановка ai-gpt, ai-kimi и ai-glm через mock curl
bash tests/test_network.sh # маршрутизация (нужен активный ru-bypass) bash tests/test_network.sh # маршрутизация (нужен активный ru-bypass)
# test_sigint.sh # ручной диагностический тест SIGINT # test_sigint.sh # ручной диагностический тест SIGINT
``` ```
## Лицензия
MIT License. Copyright (c) 2026 Виталий Никитенко. См. [LICENSE](LICENSE).

View File

@@ -1,95 +1,92 @@
# Global AI Agent Rules # Global AI Agent Rules
Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed. Use these defaults across projects, then apply any more specific repository instructions. Prefer clear outcomes and proportional safeguards over process for its own sake.
**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment. ## 1. Resolve Intent Without Needless Blocking
## 1. Think Before Coding - Inspect the available context before asking the user for information.
- 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.
**Don't assume. Don't hide confusion. Surface tradeoffs.** ## 2. Keep Changes Minimal and Scoped
Before implementing: - Implement only the requested behavior and the support required to make it correct.
- State your assumptions explicitly. If uncertain, ask. - Do not add speculative features, single-use abstractions, unrequested configurability, or handling for implausible scenarios.
- If multiple interpretations exist, present them - don't pick silently. - Preserve existing public behavior and local conventions unless the task requires changing them.
- If a simpler approach exists, say so. Push back when warranted. - Do not refactor, reformat, or clean up unrelated code. Mention unrelated issues only when they are relevant risks or blockers.
- If something is unclear, stop. Name what's confusing. Ask. - Remove imports, variables, functions, files, or comments made obsolete by your own changes; leave pre-existing dead code alone unless asked.
- Every changed line should trace to the requested outcome.
## 2. Simplicity First ## 3. Act Autonomously Within the Request
**Minimum code that solves the problem. Nothing speculative.** - 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.
- 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.
- No features beyond what was asked. ## 4. Plan and Verify Proportionally
- 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.
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify. - Define observable success criteria before changing code.
- 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.
## 3. Surgical Changes ## 5. Keep Git Changes Reviewable
**Touch only what you must. Clean up only your own mess.** - Never run `git commit` unless the user explicitly asks for a commit.
- 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.
When editing existing code: ## 6. Load Relevant Project Context
- 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.
When your changes create orphans: - Before acting, read the applicable agent instruction files and the project documentation directly relevant to the task.
- Remove imports/variables/functions that YOUR changes made unused. - Follow referenced documents when they are needed to understand requirements, commands, architecture, or validation. Do not scan unrelated Markdown files indiscriminately.
- Don't remove pre-existing dead code unless asked. - Use the repository's existing tools, conventions, and test commands unless the task requires otherwise.
The test: Every changed line should trace directly to the user's request. ## 7. Communication Style
## 4. Goal-Driven Execution - Reply in Russian, use informal "ты", and communicate like a friendly programmer teammate.
- 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.
**Define success criteria. Loop until verified.** ## 8. Reusable Skills
Transform tasks into verifiable goals: - After completing the current task, propose a focused reusable skill only when the user has repeated a workflow and reuse would clearly help.
- "Add validation" -> "Write tests for invalid inputs, then make them pass" - Do not interrupt active work to create a skill, and do not create or edit skill files without explicit user approval.
- "Fix the bug" -> "Write a test that reproduces it, then make it pass" - If approved skill changes live outside the current repository, report their exact paths and how the user can inspect them.
- "Refactor X" -> "Ensure tests pass before and after"
For multi-step tasks, state a brief plan: ## 9. Redmine (red.eltex.loc)
```
1. [Step] -> verify: [check]
2. [Step] -> verify: [check]
3. [Step] -> verify: [check]
```
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification. - 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`).
- 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)
**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. - 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:
# Global Rules for All AI Agents | File | Project | id |
| :--- | :--- | :--- |
| `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 |
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. - 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.
```bash
1. **Communication style:** curl -fsS -H "PRIVATE-TOKEN: $(cat ~/.config/ai-setup/gitlab_token_superapp)" \
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. "https://gitlab.eltex.loc/api/v4/projects/4928/merge_requests/241"
```
2. **No commits without an explicit request:** - 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>`.
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. - `local.properties` in superapp-launcher also holds `gitlab.token` and `redmine.apiKey`; it is gitignored and must stay that way.
- 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. **Typography:**
Always use the regular hyphen-minus (`-`) instead of long dash characters.
5. **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.
6. **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`.

View File

@@ -0,0 +1,28 @@
# Claude Code configuration
## Statusline (`statusline-command.sh`)
The custom statusline script renders **only ASCII-safe characters and ANSI color codes**:
- ASCII brackets `[ ]` for git branch and effort level
- ANSI 256-color escape sequences for model names, effort levels, and rate-limit percentages
- The `$` symbol for the DeepSeek balance (USD only)
It does **not** use Nerd Font glyphs, powerline separators, or emoji.
### Unicode symbols you may see in the TUI
Some symbols that appear in the Claude Code interface are **rendered by Claude Code's own TUI**, not by this statusline script. For example, arrow symbols such as `⏵` (used before "bypass permissions on" prompts) are drawn by Claude Code itself.
If these symbols display as boxes or question marks, install a Unicode-capable font such as:
- **Noto Color Emoji** (recommended on Linux)
- Any terminal font with full Unicode support
To install Noto Color Emoji on Debian/Ubuntu:
```bash
sudo apt-get install -y fonts-noto-color-emoji
```
The statusline script itself does not require emoji fonts.

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Copyright (c) 2026 Виталий Никитенко. MIT License.
# Определяет email Claude.ai аккаунта по OAuth-токену в credentials-файле. # Определяет email Claude.ai аккаунта по OAuth-токену в credentials-файле.
# Источник истины — сам токен (НЕ claude auth status, который читает # Источник истины — сам токен (НЕ claude auth status, который читает
# рассинхронизированный oauthAccount из ~/.claude.json). # рассинхронизированный oauthAccount из ~/.claude.json).

View File

@@ -1,9 +1,13 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# UserPromptSubmit hook: перехватывает /add-account. # Copyright (c) 2026 Виталий Никитенко. MIT License.
# 1) сохраняет текущий аккаунт по его реальному email (account-email.sh) # UserPromptSubmit hook: перехватывает /add-account и /add-account <код>.
# 2) запускает oauth-логин в фоне (открывает браузер) # Логин в два шага, целиком в этом же терминале (без второго окна):
# 3) после логина фоновый процесс сам определяет email нового аккаунта по токену # /add-account — сохраняет текущий аккаунт, поднимает фоновый
# и сохраняет его credentials + делает current # claude auth login со stdin из fifo и печатает ссылку;
# /add-account <код> — отдаёт код из браузера в fifo, ждёт обмена, определяет
# email нового аккаунта по токену, сохраняет и делает current.
# CLI отдаёт redirect_uri на platform.claude.com и ждёт код в stdin — при
# </dev/null он висит вечно, поэтому stdin держит fifo, а не закрытый дескриптор.
input=$(cat) input=$(cat)
prompt=$(echo "$input" | jq -r '.user_prompt // .prompt // empty' 2>/dev/null) prompt=$(echo "$input" | jq -r '.user_prompt // .prompt // empty' 2>/dev/null)
@@ -21,10 +25,16 @@ case "$LAUNCHER" in
ACCOUNTS_DIR="$HOME/.claude/accounts" ACCOUNTS_DIR="$HOME/.claude/accounts"
CURRENT_FILE="$ACCOUNTS_DIR/current" CURRENT_FILE="$ACCOUNTS_DIR/current"
EMAIL_HELPER="$HOME/.claude/hooks/account-email.sh" EMAIL_HELPER="$HOME/.claude/hooks/account-email.sh"
FIFO="$HOME/.claude/add-account.fifo"
LOG="$HOME/.claude/add-account.log"
LOGIN_PID_FILE="$HOME/.claude/add-account.login.pid"
HOLDER_PID_FILE="$HOME/.claude/add-account.holder.pid"
mkdir -p "$ACCOUNTS_DIR" mkdir -p "$ACCOUNTS_DIR"
# Сохраняем текущий активный аккаунт под его реальным email (по токену) # Сохраняем текущий активный аккаунт под его реальным email (по токену),
# чтобы логин новым аккаунтом не затёр его креды безвозвратно.
cur_email=""
if [ -f "$CREDS" ]; then if [ -f "$CREDS" ]; then
cur_email=$(bash "$EMAIL_HELPER" "$CREDS" 2>/dev/null) cur_email=$(bash "$EMAIL_HELPER" "$CREDS" 2>/dev/null)
if [ -n "$cur_email" ]; then if [ -n "$cur_email" ]; then
@@ -34,23 +44,99 @@ case "$LAUNCHER" in
fi fi
fi fi
# Фоновый процесс: логин нового аккаунта + автосохранение после успеха. # Процессы гасим строго по своим PID: pkill -f "claude auth login" выкосил бы
# claude auth login ждёт авторизации в браузере и завершается после неё, # любой процесс, в чьей командной строке встретилась эта подстрока.
# затем определяем email нового аккаунта по токену (через API) и сохраняем. pid_of() { # $1=pid-файл, $2=ожидаемое имя процесса; печатает живой pid
( [ -f "$1" ] || return 1
claude auth login --claudeai </dev/null >/tmp/claude-add-account.log 2>&1 pid=$(cat "$1" 2>/dev/null)
[ -n "$pid" ] || return 1
[ "$(ps -p "$pid" -o comm= 2>/dev/null)" = "$2" ] || return 1
printf '%s' "$pid"
}
login_alive() { pid_of "$LOGIN_PID_FILE" claude >/dev/null; }
# Останавливаем предыдущую попытку логина, чтобы не копить висящие процессы.
stop_login() {
pid=$(pid_of "$LOGIN_PID_FILE" claude) && kill "$pid" 2>/dev/null
pid=$(pid_of "$HOLDER_PID_FILE" sleep) && kill "$pid" 2>/dev/null
rm -f "$LOGIN_PID_FILE" "$HOLDER_PID_FILE" "$FIFO"
}
code=$(echo "$normalized" | awk '{print $2}')
# ── Шаг 2: код из браузера ───────────────────────────────────────
if [ -n "$code" ]; then
if ! login_alive || [ ! -p "$FIFO" ]; then
echo "Сессия логина не запущена (или уже закрылась). Начни заново: /add-account" >&2
stop_login
exit 2
fi
printf '%s\n' "$code" > "$FIFO"
# Ждём, пока CLI обменяет код на токен и завершится.
for _ in $(seq 1 60); do
login_alive || break
sleep 0.5
done
stop_login
if grep -q "Login failed" "$LOG" 2>/dev/null; then
echo "$(grep -o 'Login failed.*' "$LOG" | tail -1)" >&2
echo "Код одноразовый и живёт недолго — начни заново: /add-account" >&2
exit 2
fi
new_email=$(bash "$EMAIL_HELPER" "$CREDS" 2>/dev/null) new_email=$(bash "$EMAIL_HELPER" "$CREDS" 2>/dev/null)
if [ -n "$new_email" ] && [ -f "$CREDS" ]; then if [ -z "$new_email" ]; then
echo "✗ Логин завершился, но email аккаунта определить не удалось. Аккаунт не сохранён." >&2
exit 2
fi
cp "$CREDS" "$ACCOUNTS_DIR/${new_email}.credentials.json" cp "$CREDS" "$ACCOUNTS_DIR/${new_email}.credentials.json"
chmod 600 "$ACCOUNTS_DIR/${new_email}.credentials.json" chmod 600 "$ACCOUNTS_DIR/${new_email}.credentials.json"
echo "$new_email" > "$CURRENT_FILE" echo "$new_email" > "$CURRENT_FILE"
echo "SAVED: $new_email" >> /tmp/claude-add-account.log
if [ "$new_email" = "$cur_email" ]; then
echo "⚠ Вошёл тот же аккаунт ($new_email) — новых аккаунтов не добавилось." >&2
else
echo "✓ Аккаунт $new_email добавлен и стал активным. /switch-account — переключение по кругу." >&2
fi fi
) & exit 2
fi
# ── Шаг 1: запуск логина и ссылка ────────────────────────────────
stop_login
mkfifo -m 600 "$FIFO" || { echo "Не удалось создать $FIFO" >&2; exit 2; }
: > "$LOG"
# stdin из fifo: CLI ждёт код в stdin, закрытый дескриптор его подвешивает.
# Держатель fifo не даёт CLI получить EOF и задаёт таймаут попытки (15 мин).
claude auth login --claudeai < "$FIFO" > "$LOG" 2>&1 &
echo $! > "$LOGIN_PID_FILE"
disown
sleep 900 > "$FIFO" 2>/dev/null &
echo $! > "$HOLDER_PID_FILE"
disown disown
# exit 0: Claude загружает скилл add-account и говорит что делать url=""
exit 0 for _ in $(seq 1 40); do
url=$(grep -o 'https://claude\.com/[^ ]*' "$LOG" 2>/dev/null | head -1)
[ -n "$url" ] && break
sleep 0.5
done
if [ -z "$url" ]; then
stop_login
echo "Не дождался ссылки для входа. Лог: $LOG" >&2
exit 2
fi
echo "Авторизуйся в браузере (ссылка ниже), скопируй код и пришли его сюда: /add-account <код>" >&2
echo "" >&2
echo "$url" >&2
exit 2
;; ;;
kimi) kimi)

View File

@@ -0,0 +1,60 @@
#!/usr/bin/env bash
# Copyright (c) 2026 Виталий Никитенко. MIT License.
# UserPromptSubmit hook: перехватывает /logout, /logout-glm, /logout-deepseek, /logout-kimi.
# Удаляет сохранённый API-ключ провайдера, чтобы при следующем запуске лаунчер
# запросил новый.
# /logout-glm | /logout-deepseek | /logout-kimi — явно указанный провайдер.
# /logout (без суффикса) — провайдер по AI_LAUNCHER (glm/deepseek/kimi).
# Для ai-claude и прочих лаунчеров /logout отступает (exit 0) — пусть работает
# нативный oauth-logout. exit 2 после удаления: LLM не нужна, и нативная
# /logout не пытается сделать бессмысленный oauth-logout для api-key провайдеров.
input=$(cat)
prompt=$(echo "$input" | jq -r '.user_prompt // .prompt // empty' 2>/dev/null)
normalized=$(echo "$prompt" | sed 's|^[[:space:]]*/||; s|[[:space:]]*$||')
cmd=$(echo "$normalized" | awk '{print $1}')
target=""
case "$cmd" in
logout-glm) target=glm ;;
logout-deepseek) target=deepseek ;;
logout-kimi) target=kimi ;;
logout)
case "${AI_LAUNCHER:-claude}" in
glm|deepseek|kimi) target="$AI_LAUNCHER" ;;
*) exit 0 ;;
esac
;;
*) exit 0 ;;
esac
CFG="$HOME/.config/ai-setup"
case "$target" in
glm)
rm -f "$CFG/glm_key"
echo "✓ GLM ключ удалён. Перезапусти ai-glm, чтобы ввести новый." >&2
;;
deepseek)
rm -f "$CFG/deepseek_key"
echo "✓ DeepSeek ключ удалён. Перезапусти ai-deepseek, чтобы ввести новый." >&2
;;
kimi)
KEYS_DIR="$CFG/kimi_keys"
current=$(cat "$KEYS_DIR/current" 2>/dev/null || true)
if [ -n "$current" ]; then
rm -f "$KEYS_DIR/${current}.key" "$KEYS_DIR/${current}.meta"
fi
# Пересчёт current: переключаемся на первый оставшийся ключ либо сбрасываем.
mapfile -t keys < <(ls "$KEYS_DIR"/*.key 2>/dev/null | xargs -I{} basename {} .key | sort)
if [ "${#keys[@]}" -gt 0 ]; then
echo "${keys[0]}" > "$KEYS_DIR/current"
echo "✓ Kimi ключ (${current:-нет}) удалён. Активен: ${keys[0]}. Перезапусти ai-kimi." >&2
else
rm -f "$KEYS_DIR/current"
echo "✓ Kimi ключ (${current:-нет}) удалён. Других ключей нет. Перезапусти ai-kimi." >&2
fi
;;
esac
exit 2

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Copyright (c) 2026 Виталий Никитенко. MIT License.
# UserPromptSubmit hook: перехватывает /switch-account и циклически меняет аккаунт/ключ. # UserPromptSubmit hook: перехватывает /switch-account и циклически меняет аккаунт/ключ.
# Текущий аккаунт определяется ПО ТОКЕНУ в .credentials.json (account-email.sh), # Текущий аккаунт определяется ПО ТОКЕНУ в .credentials.json (account-email.sh),
# а не по claude auth status — он читает рассинхронизированный oauthAccount. # а не по claude auth status — он читает рассинхронизированный oauthAccount.

View File

@@ -1,12 +0,0 @@
---
name: add-account
description: Add a new account or API key (handled by UserPromptSubmit hook, no LLM needed)
---
Хук уже обработал запрос. Ответь ТОЛЬКО этим текстом (без markdown, без лишних слов):
Для Claude.ai: браузер открыт — авторизуйся там. После авторизации новый аккаунт сохранится автоматически и сразу станет активным (Claude Code на Linux перечитывает токен на лету).
Для Kimi: укажи API-ключ в том же сообщении: `/add-account sk-...`. Хук проверит и сохранит ключ; ai-kimi перезапустится с ним.
/switch-account переключает между всеми сохранёнными аккаунтами/ключами по кругу.

View File

@@ -1,88 +0,0 @@
---
name: android-cli
description: Google Android CLI - официальный терминальный инструмент для Android-разработки, SDK, эмуляторов и проектов
---
# Android CLI
## Назначение
Google Android CLI (preview) - официальный инструмент от Google для Android-разработки через терминал. Он создан специально для AI-агентов, CI/CD и автоматизации. Позволяет управлять SDK, создавать проекты, запускать эмуляторы и собирать APK без необходимости вручную разбираться с `sdkmanager`, `adb` и Gradle.
## Установка и обновление
```bash
# Установка (выполняется ai-setup.sh)
curl -fsSL https://dl.google.com/android/cli/latest/linux_x86_64/install.sh | bash
# Обновление
android update
```
## Ключевые команды
| Команда | Назначение |
|---------|------------|
| `android --version` | Версия Android CLI |
| `android info` | Информация об SDK, путях, эмуляторах |
| `android sdk list` | Список доступных SDK-пакетов |
| `android sdk install <package>` | Установить пакет SDK |
| `android create --name="App" --output=./app` | Создать новый Android-проект |
| `android emulator create` | Создать виртуальное устройство |
| `android emulator start` | Запустить эмулятор |
| `android run --apks <path>` | Установить и запустить APK на устройстве/эмуляторе |
| `android init` | Развернуть skills для AI-агентов в `~/.claude/skills/` |
| `android update` | Обновить сам Android CLI |
## Workflow с Claude Code
1. **Проверь окружение**:
```bash
android info
```
2. **Установи нужные SDK-компоненты**:
```bash
android sdk install platforms/android-35 build-tools/35.0.0
```
3. **Создай проект** (если нужен новый):
```bash
android create --name="MyApp" --output=./myapp
```
4. **Собери проект**:
```bash
./gradlew assembleDebug
```
5. **Запусти на эмуляторе или устройстве**:
```bash
android emulator start
android run --apks app/build/outputs/apk/debug/app-debug.apk
```
## Когда использовать Android CLI
- Для управления SDK и установки build-tools/platforms без Android Studio.
- Для создания новых Android-проектов из терминала.
- Для запуска эмулятора и установки APK одной командой.
- Для работы в CI/CD или внутри AI-агентов.
## Когда НЕ нужен Android CLI
- Для визуального редактирования UI/Layout - используй Android Studio.
- Для отладки с breakpoints, Layout Inspector, Compose Preview - используй Android Studio.
- Для сложного рефакторинга больших проектов - используй Claude Code в паре с Android Studio.
## Ограничения
- **Preview-версия**: API и команды могут меняться. Обновляй через `android update`.
- **Windows**: `android emulator` отключён в v0.7.
- **Существующий SDK**: если уже установлен Android Studio или `sdkmanager`, Android CLI может использовать тот же SDK или создать новый в `~/.android/sdk`. Проверяй активный путь через `android info`.
- **Прокси**: Android CLI - нативный бинарь, который спавнит дочерние процессы (emulator, gradle, adb). Не оборачивай его в `proxychains4`, это может сломать child-процессы.
## Полезные ссылки
- Официальная документация: https://d.android.com/tools/agents
- Android CLI skills: https://github.com/android/skills

View File

@@ -1,62 +0,0 @@
---
name: el-review-heavy
description: Use when пользователь запрашивает тяжелое code-review между двумя Git-ветками с диффом в стиле GitLab (от merge-base)
---
# Eltex Review Heavy
## Overview
Тяжелое ревью диффа между двумя Git-ветками в стиле GitLab Merge Request (от merge-base). Запускает полный `/code-review` с кучей агентов.
## When to Use
- Пользователь просит "поревьюй ветку feature относительно master" тщательно
- Нужен дифф как на GitLab (без лишних изменений из целевой ветки)
- Формат вызова: `/el-review-heavy <наша-ветка-с-изменениями> <целевая-ветка>`
## Workflow
1. Определи аргументы:
- `source` — первая ветка (наша, с изменениями, которую ревьюим)
- `target` — вторая ветка (целевая, в которую мерж-реквест)
2. Выполни `git fetch origin` — обнови remote refs
3. Если текущая checkout-ветка == `source`, выполни `git pull --ff-only origin source` — обнови локальную ветку
4. Построй дифф от merge-base: `git diff origin/target...origin/source > /tmp/el-review-heavy.diff` (три точки!)
5. Прочитай файл `/tmp/el-review-heavy.diff`
6. Запусти скилл `code-review` с этим файлом как аргумент
7. Полученные findings переформатируй:
- Пронумеруй проблемы
- Оставь только критические замечания
- Для каждой предложи готовый дифф с исправлением
- Объясни простым языком
## Примеры вызова
```
/el-review-heavy feature main
/el-review-heavy my-fix develop
/el-review-heavy TASK-123-payment-screen master
```
## Ошибки и крайние случаи
- Если ветка не существует на remote - сообщи пользователю и не продолжай
- Если дифф пустой - сообщи "изменений между ветками нет" и не продолжай
- Если аргументы не указаны - попроси уточнить: `/el-review-heavy <source-ветка> <target-ветка>`
## Формат вывода
```
## Проблема N: [краткое название]
**Где:** `file.kt:123`
**Что не так:** простым языком, зачем это плохо
**Исправление:**
```diff
- старая строка
+ новая строка
```
```

View File

@@ -1,62 +0,0 @@
---
name: el-review
description: Use when пользователь запрашивает легкое code-review между двумя Git-ветками с диффом в стиле GitLab (от merge-base)
---
# Eltex Review
## Overview
Легкое ревью диффа между двумя Git-ветками в стиле GitLab Merge Request (от merge-base). Запускает быстрый `/review` вместо тяжелого `/code-review`.
## When to Use
- Пользователь просит "поревьюй ветку feature относительно master" быстро
- Нужен дифф как на GitLab (без лишних изменений из целевой ветки)
- Формат вызова: `/el-review <наша-ветка-с-изменениями> <целевая-ветка>`
## Workflow
1. Определи аргументы:
- `source` — первая ветка (наша, с изменениями, которую ревьюим)
- `target` — вторая ветка (целевая, в которую мерж-реквест)
2. Выполни `git fetch origin` — обнови remote refs
3. Если текущая checkout-ветка == `source`, выполни `git pull --ff-only origin source` — обнови локальную ветку
4. Построй дифф от merge-base: `git diff origin/target...origin/source > /tmp/el-review.diff` (три точки!)
5. Прочитай файл `/tmp/el-review.diff`
6. Запусти скилл `review` с этим файлом как аргумент
7. Полученные findings переформатируй:
- Пронумеруй проблемы
- Оставь только критические замечания
- Для каждой предложи готовый дифф с исправлением
- Объясни простым языком
## Примеры вызова
```
/el-review feature main
/el-review my-fix develop
/el-review TASK-123-payment-screen master
```
## Ошибки и крайние случаи
- Если ветка не существует на remote - сообщи пользователю и не продолжай
- Если дифф пустой - сообщи "изменений между ветками нет" и не продолжай
- Если аргументы не указаны - попроси уточнить: `/el-review <source-ветка> <target-ветка>`
## Формат вывода
```
## Проблема N: [краткое название]
**Где:** `file.kt:123`
**Что не так:** простым языком, зачем это плохо
**Исправление:**
```diff
- старая строка
+ новая строка
```
```

View File

@@ -1,8 +0,0 @@
---
name: switch-account
description: Switch to next account or API key (handled by UserPromptSubmit hook, no LLM needed)
---
Переключение аккаунта/ключа уже выполнено хуком до того, как ты это читаешь.
Ответь ровно одним символом: `✓`
Никаких инструментов. Никаких объяснений. Только `✓`.

View File

@@ -1,8 +1,15 @@
#!/bin/bash #!/bin/bash
# Copyright (c) 2026 Виталий Никитенко. MIT License.
input=$(cat) 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')
@@ -10,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
} }
@@ -28,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
@@ -38,19 +49,25 @@ 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")
# Аккаунт Claude.ai актуален только для нативных моделей Claude # Аккаунт Claude.ai актуален только для нативных моделей Claude
if [[ "$model_id" == claude-* ]]; then if [[ "$model_id" == claude-* ]]; then
account=$(cat ~/.claude/accounts/current 2>/dev/null)
ACCOUNTS_DIR="$HOME/.claude/accounts" ACCOUNTS_DIR="$HOME/.claude/accounts"
# Автоопределение: если current пуст или файл не существует — account=$(cat "$ACCOUNTS_DIR/current" 2>/dev/null)
# ищем аккаунт по 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) current_token=$(jq -r '.claudeAiOauth.accessToken // empty' "$HOME/.claude/.credentials.json" 2>/dev/null)
# current надёжен, только если его файл существует И токен совпадает с активной сессией.
account_token=""
if [ -n "$account" ] && [ -f "$ACCOUNTS_DIR/${account}.credentials.json" ]; then
account_token=$(jq -r '.claudeAiOauth.accessToken // empty' "$ACCOUNTS_DIR/${account}.credentials.json" 2>/dev/null)
fi
# Рассинхрон (пусто или чужой токен) — определяем аккаунт по активному токену.
if [ -z "$account" ] || [ "$account_token" != "$current_token" ]; then
account=""
if [ -n "$current_token" ]; then if [ -n "$current_token" ]; then
# 1) Локальный матч по токену среди сохранённых аккаунтов (мгновенно)
for f in "$ACCOUNTS_DIR"/*.credentials.json; do for f in "$ACCOUNTS_DIR"/*.credentials.json; do
[ ! -f "$f" ] && continue [ ! -f "$f" ] && continue
token=$(jq -r '.claudeAiOauth.accessToken // empty' "$f" 2>/dev/null) token=$(jq -r '.claudeAiOauth.accessToken // empty' "$f" 2>/dev/null)
@@ -60,18 +77,13 @@ if [ -n "$model" ]; then
break break
fi fi
done done
# Если токен не найден — спрашиваем haiku (один раз на аккаунт) # 2) Не нашли локально — резолвим email через account-email.sh (один раз на токен)
if [ -z "$account" ]; then if [ -z "$account" ]; then
sentinel="$HOME/.cache/ai-setup/email_fetch_token" sentinel="$HOME/.cache/ai-setup/email_fetch_token"
prev_token=$(cat "$sentinel" 2>/dev/null) if [ "$(cat "$sentinel" 2>/dev/null)" != "$current_token" ]; then
if [ "$prev_token" != "$current_token" ]; then
mkdir -p "$HOME/.cache/ai-setup" mkdir -p "$HOME/.cache/ai-setup"
echo "$current_token" > "$sentinel" echo "$current_token" > "$sentinel"
email=$(unset ANTHROPIC_BASE_URL ANTHROPIC_AUTH_TOKEN; \ email=$(bash "$HOME/.claude/hooks/account-email.sh" "$HOME/.claude/.credentials.json" 2>/dev/null)
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 if [ -n "$email" ]; then
cp "$HOME/.claude/.credentials.json" "$ACCOUNTS_DIR/${email}.credentials.json" cp "$HOME/.claude/.credentials.json" "$ACCOUNTS_DIR/${email}.credentials.json"
chmod 600 "$ACCOUNTS_DIR/${email}.credentials.json" chmod 600 "$ACCOUNTS_DIR/${email}.credentials.json"
@@ -82,22 +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 — показываем email/имя из .meta, если оно есть, иначе alias. # Аккаунт/ключ 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)
if [ -n "$kimi_account" ]; then [ -n "$kimi_account" ] && { _sep; printf "%s%s\033[00m" "$brand_color" "$kimi_account"; }
kimi_display=$(cat "$HOME/.config/ai-setup/kimi_keys/${kimi_account}.meta" 2>/dev/null || echo "$kimi_account")
printf " %s[%s]\033[00m" "$brand_color" "$kimi_display"
fi
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
@@ -139,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 секунд)
@@ -160,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)
@@ -183,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 секунд)
@@ -248,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
@@ -264,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

@@ -1,9 +0,0 @@
# ProxyChains-NG config for xray SOCKS5 proxy
strict_chain
proxy_dns
remote_dns_subnet 224
tcp_read_time_out 15000
tcp_connect_time_out 8000
[ProxyList]
socks5 127.0.0.1 1080

View File

@@ -1,6 +0,0 @@
# VLESS servers — один URL на строку
# Имя после # используется для отображения в меню выбора
vless://495ac855-d718-4581-8344-f1e62105a904@92.118.8.29:443?encryption=mlkem768x25519plus.native.0rtt.gNGR8kO_UDWjoeer_TenoRbA09ajfrSfahqkS4q0dTeCFutCHGJjIdhSl8Fv8HyYUyWB_2GqMrSIzLS9l1BiidClOnliyUmZylQwQoUFl6CsLsZxdrrDz3w2isuIhtXPfZw8lANOErk7p7mm4QN0H2ZKCOQQaxkL3XA8EcxqNuGhK4XLqlFBDommPJUQfPuP7LV3V7s8YTcwq1EUr-Nyv0CZRdcF9WCNCWq26TVYXFwHSkeknBBFKYEJiytB8DpIvlmTvREQfEVnTziwOdYKikQXeOYdbcuKSWGb2okDZjFzYckULaWhavOwRWnOquZqdXiatrNPmCWnG2mfcSxA0UdLu8ALiOtuPjBGDUqqaXhZLLuH1zhdsUE4PMqqkNnMvsRwmvAYgWwSKWioz3wTQ1sKVfW1jTh8LyouZaVkuqRTunwWBcVCRDYEFUh4TBB_-WOpu7eh4ROmj9w0mhWBrtRO-uin8ZwqhkeScQhwoFUEvlwCsAl8AevGmAm1qpdOzfXNUgBTtVoXG9OTMUk9ZORiTzO0tutVTlKjaiBb1vOqgiFqiPuG1csvaPt63LQ1e9ywvEek7NkLVnrJopmCnTiu9UhE7GwZbxI8FBFYrBmPpIUszLiWLQZqvySbqBrIFTC5lIw9WymdW5wKONs5DZmYtFZOHUVEFtVFJvcZqwnEJARKI_M-rvkS7FWPPCkKHpEVe_IJsgOgoUzI9yQI5kNlS5XAzUG7ZMGWtzYjOZNZ1FYP5NbBTTy9I8kpLRQv3yp7PYBGdBQapaYX82Qjc4orJdghG5UOTQwsokojPVaAzSWG4qk5hoNugvogx9KHdsitKXCDlhdksQS5Obc8uMg1TxO2c0AnR2nNNVqgb_prVUlaNHcP4wARfCqrMwdgYQSSoLZluah9a2eZxGcC6CMxPBsThCEwNzIJUZiuPIvEQVFyJpiCHeUErGZFLpmaE0MK2cEzD9N7pRVgEExvwIMnO3jIGmCz80FFuAmEjDG5iyMkzQpsEnBHwAaihjkSwgCsZzTL26OtIDUUYMZ1N8ciRRqTvSGzn_K6EUp7JaWygZGw3QCBRocO7SEWpuGvakJc0QV32vpty3c8UeIcJZlgGKgtcNcsVYvLpEkkYviNCbpq6jnECRYUuXGp_oQv1mwqx2WMqGic7LVZNtYCJ3fHAsGi4yh5nfU0dNSCC0cgeyakNXvL3eyqHIB_e8RrEiMywUg7_MGP2oE9ZOqayLRapVTGQ-YATgcnZ_Eg5bKHNcRVqBpo0heElCtMPoEdQYE_HZSOm1cf9nFIYqRJ6_RPJNKGDOBwy1wJWJel54y-CeY3S2vAWRibOFiwVGW01tHNSCEQBaiLEzQ2Xml6IkSAHNm9eifPrdxuBChibzco4dUFpumbxrRkJYAuqmQfWgi36ziojeEDLlUd55ZDeCOVn4MoHMcD4yzAkpdUelyBafeUoQEs6hCDf2p7CvCquWKoEzcwvuccR0GcN5un2sqQAMVKw3Gs3fsRwkgcdsK69oE4KfUKVjrBGkIk9ChYCJMYGZJ-pGkWFp1qLua2QnNmPVJlpPjurRJVoiAiOVE&security=reality&sni=www.amd.com&fp=chrome&pbk=AMoGtXyztJYD6GRP6m8K82Mi-5vthrNrppJkEmconTU&sid=9cd28fd258f8b99c&type=xhttp&path=%2F&mode=auto#RF-VPS1
vless://91749001-1f60-4afb-b7b5-25d1480de980@194.87.213.143:443?encryption=mlkem768x25519plus.native.0rtt.D4eIFKFhlMVUsInLgmx5lWuTpBJ8B3lEWnA4gCKdZgw1rjlvHsWd38rC35BAQeKSeBmbZ_sIk6w1gzCCAcYKlJN8rMZyr4TEecnADumEI0e05fRcYCXH70BdHnJxkhklzUIwAKyZHmwlk7euozhYinwq-piRuEBvl0NS-tITgOaje8jLuMyrBTnBKBtzwDR43aMsDqZhzRi-qIaiy0GXjEgHmypFIFB3EOdmCukbvgyJuBAbttTHqdCedQCeJTNfs3tzmzqFKuJXVGRvAvipWGqC_4hzlTWaVXBiuysAmGMC0oqERTyUS9qKqytOr_oyu-jPzjCdIUTPAjdpw4kzXEiuetAuZNuwdbRAHAB_DadVIAsqyVapNIVP0UlN-PJE95pAS6Yfpru2uIBWcJZhV7LE2FKBtAcC31lHeVKy12pG_NlZopYltcmdacVeXBRd1wURrrDFNNTCKaG5wAFm3HFYrzY6qZfNanebgQeYIQm3rbbGGCLBYLyHxcmKeuWVIMTJlISicHdnNyok7KJ2gWKTWtREkfQMXrZKF8BeiSp-9FQX_UCwx0LMd0axB8hqCVtpGspbH8ugKamFr4olFyKrUdQuLhEM1_xzNgZT3dWhvCxDw1VOp3qMd6G4X4YhnABioLOPl2JEyjOLMdAFRlRrdXnNpJdTA0esrJFw5Mte2gNYDAgWEFoq5uupAZlYwniB69BCeEouQiU0GYdWkxwAqwy8xtCEs9Wi4RGPBmdZnhOb5LMUBACwvFm_haF5zgwDrTmUZWdKhXMDXOMOdrcQaDvMf3uk64Q6obe47hkn1LFQS5mKOYYAjblg1RV0y1ovWtFSVBuuCsckyNUPSjETKthQQ6OUgZmpN-pMlBgzGzi3rowqdjNzrMh894ufrptX2GBqEANIM3h5HkR2ObIlJdOv6rQRnmu09EVKp7BHAVLKJcJff9HJYWIqUueArllu5IivueGPG5gPMDIp5SoHVvk_eCMUY2lLtxEE1PCjbgt8ahKTQLcFCXmJf7h945F4jOsDLCxiBSSxaMZdK5AiGGcQm-mcQ4aXBLgiHLCfD3SU7UzIF9key8u35bBm2OVjyTWei-AOssdVV2qFMppuF7Myv4GaEMpQ1JGaLpxT9zSSdVqbO6aJNBkkFxJhcnWqm8CMWVkaGubHkSeDInS65_KFe6I8IrZVm9lBThN1mvdqWkCvZ_ooRiChX7hNKTAXH9cgW-OaOTY1eissDkmW2FEGcPU5iRcDY5kgubZYgTSaHKFAIqg-6NIQX1l3d6lx41EkwyKUCWU-zfjBGsdgszjHXykOrcNQ75o_M3k_-_IBdkOpaESrhSsj2bdNUVcx7fx1S2d6STiLIrW--WeoeBhb6BcytgkbnlRlmvfNDZbOTJYzXQLJtCRZv3xJyfcEB0UqvKFIaCtN4ubHTpyRZ5PHtYHDh9lmIASIItMqeXQlazktz-cajiOFX8RCIkcDFkZ-0KpmYiBToVpi-zSjdLCPPVMf14V5kqAP6EA1IiTJPwFuroWluOJ5j8mx4c0i8fbd1n_mTH_7Ff8Ognu-LFLBU9r9yWcVhBkm_JQ&security=reality&sni=vkvideo.ru&fp=chrome&pbk=BWwAjxuB6QbC2ta23L8_U4QjjkUs17NDi79Mm2RjQHQ&sid=17c863e8490260b6&type=xhttp&path=%2F&mode=packet-up#RF-Bridge-PQ
vless://f3f3d573-5440-425c-a13c-203b34a9279f@202.148.54.122:443?encryption=none&flow=xtls-rprx-vision&security=reality&sni=www.samsung.com&fp=chrome&pbk=aTBwueWs3WYoxA0M051KZn3hr2EogEULd3GOdvl7x0Y&sid=5a0048d376dc1c00#AmneziaVPN
vless://40ee74ff-9865-498a-b464-12fa6a3772fe@45.43.91.139:853?encryption=mlkem768x25519plus.native.0rtt.TZBtUoTBrKNMXKtza2TElwtqc9W_zgAJEDcxH1fBEwQgbZCAXHgK9edzewlVFSBK3zK2p4IZ6hsESffCSOesy4Mll_CKPiY0kFGCV-yNl6w45eav7RUhDOuiPBrHR3hC-bgH4xoClLajDfG7nHu2ecppcsEedkt7MLSacYI3EsJ-rKa-vbN3vgI5w8ZtunFF-PYVRJuYrbw711RwbcO_AWfPt1J25ZhVcxQ5CWufWXG1XLtaZLdk-2cmKsiVIgcr6tOIYIuh8XULoCVh5rsjwlcH4slu4cYGGgxwNIg7C_wYs8zLyvKWgBUL0UwctTyNbxChWktTuWaGRnS27PJbdbJ6T9gTzMsWh3dbVnlGZbIjbJMe3NAvVOJUhGOQ7NpEP9x9wgVzO-dUJQkOwKAoDfAtvbRIAaRg_5w20ZRy0sutSwUCoKtUfxCxB4CwcSmFgqWRrsvHNTy2U7We6GibAwy90iiC3sGCVSM69XcN1xirfVVGRbAMrfk0DehWNAvQFHFNkThvi0qh-1Q3p4YK6EWhgswTqmMbM2iOTQA_KaxUQKwfPnIfCBMIuTBdFDYOMyyvZayFq4Ul62vEMvUOCQCHL7WtdzhVR5cdquggX3C4Zzutlug_n4q2jLt2ldYvAwEMErPOlSq2DKmIcWFE5YWSjUhYxvg76rOEK_WSSGNrtrO5n6eRFtiIyaxXPxEhjsFdoLMekDSdhZWyhwa3-9Vqf_xDgrs2CMlOkpQbIvqe92QkVdWzhWeulBu4XGsnOVCivvvLrUBfGDs9FrCYGLEtOLtMlHcny1tXjdjEVkejrvOcOTCvxKoidTzJVsVi2Oxn9VGfO6TOURvP4KwsDDVZbgUgAYqfBbkrnpNhaaZFuoYw-7GuZawl8LyOy8SU9NQesDt4YUZ3MrWcHwALrKqCD3OSf0wzq0N-BDY6frAQf8W3KSO8ivKfxMQhN5xPfSuTlRivWuq1lpEjFiqi-vZF-ZkVA9qRWmyXRfaUYcKqqQFoO5Ks0FuainJmxPocseUIxVF3qCINQLsaxVgWITAcWOOZV4m021Yy2LxnwcubRUkDHPGvIocQE8JF7fpAWVqjAxwT7oCHiHRf6WtzEHhCWZx7y9tR-AV4_XgZNEwRj7DM-kdztDl78Wg9E7Ze9lnK3UCXPxQf9THOd_wqQ0aF5bkwf_qqj0klX9WphfBvPzVuI0lmVeWPDKyqVDqCw4An8oFB7bxt_lPDzIOInvB5U8hXUuMjsQS6iEcSSHcfqed8adcKvzTCK6JcjLCTyImVS3hsxMOC9qFMtDpmsxcNdFphlZc9ODjGgZtVLYLIYka-JKQWPgC2R-s8H6EU0ymjIhsmdMe93KyHAcC3anpUKTu9oSk5sthVFhB0l2RCpSKlWOhFG8RkAspKWYlu-DGC3ROvnbJojjpGkpDFEWoVnzWQ8VK5hcxSeFQ17auDWMKBkDuhR1ciVURnCBBVO1FEGvGGVyAbYoUri8moPeZ8jNBvztFZiahBeWQBsxQcA9C78Yus34S2aOwCfHVQ9hN2slx5tPY5abR_EwhyS4NhAZ7how7k0Fds42glbOE&security=reality&sni=www.amd.com&fp=chrome&pbk=3PdVmi-ZFgQpvTD8Tj4ubL1kicezogKf7-qkMLfjxxw&sid=846683ae0dde8bd1&type=xhttp&path=%2F&mode=auto#EURO-Reality-PQ

View File

@@ -1,51 +0,0 @@
{
"inbounds": [
{
"port": 1080,
"listen": "127.0.0.1",
"protocol": "socks",
"settings": {
"udp": true
}
},
{
"port": 2080,
"listen": "127.0.0.1",
"protocol": "http"
}
],
"outbounds": [
{
"protocol": "vless",
"settings": {
"vnext": [
{
"address": "92.118.8.29",
"port": 443,
"users": [
{
"id": "495ac855-d718-4581-8344-f1e62105a904",
"encryption": "mlkem768x25519plus.native.0rtt.gNGR8kO_UDWjoeer_TenoRbA09ajfrSfahqkS4q0dTeCFutCHGJjIdhSl8Fv8HyYUyWB_2GqMrSIzLS9l1BiidClOnliyUmZylQwQoUFl6CsLsZxdrrDz3w2isuIhtXPfZw8lANOErk7p7mm4QN0H2ZKCOQQaxkL3XA8EcxqNuGhK4XLqlFBDommPJUQfPuP7LV3V7s8YTcwq1EUr-Nyv0CZRdcF9WCNCWq26TVYXFwHSkeknBBFKYEJiytB8DpIvlmTvREQfEVnTziwOdYKikQXeOYdbcuKSWGb2okDZjFzYckULaWhavOwRWnOquZqdXiatrNPmCWnG2mfcSxA0UdLu8ALiOtuPjBGDUqqaXhZLLuH1zhdsUE4PMqqkNnMvsRwmvAYgWwSKWioz3wTQ1sKVfW1jTh8LyouZaVkuqRTunwWBcVCRDYEFUh4TBB_-WOpu7eh4ROmj9w0mhWBrtRO-uin8ZwqhkeScQhwoFUEvlwCsAl8AevGmAm1qpdOzfXNUgBTtVoXG9OTMUk9ZORiTzO0tutVTlKjaiBb1vOqgiFqiPuG1csvaPt63LQ1e9ywvEek7NkLVnrJopmCnTiu9UhE7GwZbxI8FBFYrBmPpIUszLiWLQZqvySbqBrIFTC5lIw9WymdW5wKONs5DZmYtFZOHUVEFtVFJvcZqwnEJARKI_M-rvkS7FWPPCkKHpEVe_IJsgOgoUzI9yQI5kNlS5XAzUG7ZMGWtzYjOZNZ1FYP5NbBTTy9I8kpLRQv3yp7PYBGdBQapaYX82Qjc4orJdghG5UOTQwsokojPVaAzSWG4qk5hoNugvogx9KHdsitKXCDlhdksQS5Obc8uMg1TxO2c0AnR2nNNVqgb_prVUlaNHcP4wARfCqrMwdgYQSSoLZluah9a2eZxGcC6CMxPBsThCEwNzIJUZiuPIvEQVFyJpiCHeUErGZFLpmaE0MK2cEzD9N7pRVgEExvwIMnO3jIGmCz80FFuAmEjDG5iyMkzQpsEnBHwAaihjkSwgCsZzTL26OtIDUUYMZ1N8ciRRqTvSGzn_K6EUp7JaWygZGw3QCBRocO7SEWpuGvakJc0QV32vpty3c8UeIcJZlgGKgtcNcsVYvLpEkkYviNCbpq6jnECRYUuXGp_oQv1mwqx2WMqGic7LVZNtYCJ3fHAsGi4yh5nfU0dNSCC0cgeyakNXvL3eyqHIB_e8RrEiMywUg7_MGP2oE9ZOqayLRapVTGQ-YATgcnZ_Eg5bKHNcRVqBpo0heElCtMPoEdQYE_HZSOm1cf9nFIYqRJ6_RPJNKGDOBwy1wJWJel54y-CeY3S2vAWRibOFiwVGW01tHNSCEQBaiLEzQ2Xml6IkSAHNm9eifPrdxuBChibzco4dUFpumbxrRkJYAuqmQfWgi36ziojeEDLlUd55ZDeCOVn4MoHMcD4yzAkpdUelyBafeUoQEs6hCDf2p7CvCquWKoEzcwvuccR0GcN5un2sqQAMVKw3Gs3fsRwkgcdsK69oE4KfUKVjrBGkIk9ChYCJMYGZJ-pGkWFp1qLua2QnNmPVJlpPjurRJVoiAiOVE",
"flow": ""
}
]
}
]
},
"streamSettings": {
"network": "xhttp",
"security": "reality",
"realitySettings": {
"serverName": "www.amd.com",
"fingerprint": "chrome",
"publicKey": "AMoGtXyztJYD6GRP6m8K82Mi-5vthrNrppJkEmconTU",
"shortId": "9cd28fd258f8b99c"
},
"xhttpSettings": {
"path": "/",
"mode": "auto"
}
}
}
]
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
#!/bin/bash #!/bin/bash
# Copyright (c) 2026 Виталий Никитенко. MIT License.
# ks-off.sh — временно отключить kill switch UFW # ks-off.sh — временно отключить kill switch UFW
# После этого отключи Amnezia через её GUI — трафик пойдёт напрямую через провайдера. # После этого отключи Amnezia через её GUI — трафик пойдёт напрямую через провайдера.
# Обратная команда: sudo bash ks-on.sh # Обратная команда: sudo bash ks-on.sh

View File

@@ -1,4 +1,5 @@
#!/bin/bash #!/bin/bash
# Copyright (c) 2026 Виталий Никитенко. MIT License.
# ks-on.sh — включить/восстановить kill switch UFW # ks-on.sh — включить/восстановить kill switch UFW
# Перед этим подключи Amnezia через её GUI, иначе интернет будет полностью заблокирован. # Перед этим подключи Amnezia через её GUI, иначе интернет будет полностью заблокирован.

View File

@@ -1,4 +1,5 @@
#!/bin/bash #!/bin/bash
# Copyright (c) 2026 Виталий Никитенко. MIT License.
# ru-bypass.sh — .ru трафик напрямую мимо Amnezia, всё остальное через VPN + kill switch # ru-bypass.sh — .ru трафик напрямую мимо Amnezia, всё остальное через VPN + kill switch
# #
# Принцип: ipset + более специфичные маршруты имеют приоритет над amn0. # Принцип: ipset + более специфичные маршруты имеют приоритет над amn0.
@@ -37,7 +38,7 @@ KILL_SWITCH_EXCEPTIONS="${KILL_SWITCH_EXCEPTIONS:-}"
# Базовые исключения, необходимые для работы корпоративных сервисов # Базовые исключения, необходимые для работы корпоративных сервисов
# Добавляются автоматически, даже если не указаны в конфиге # Добавляются автоматически, даже если не указаны в конфиге
_BUILTIN_EXCEPTIONS="mattermost.eltex-co.ru elph.eltex-co.ru 10.80.0.15" _BUILTIN_EXCEPTIONS="mattermost.eltex-co.ru elph.eltex-co.ru study-git.eltex-co.ru study-red.eltex-co.ru 10.80.0.15"
for _exc in $_BUILTIN_EXCEPTIONS; do for _exc in $_BUILTIN_EXCEPTIONS; do
case " $KILL_SWITCH_EXCEPTIONS " in case " $KILL_SWITCH_EXCEPTIONS " in
*" $_exc "*) ;; *" $_exc "*) ;;
@@ -180,7 +181,7 @@ $HOSTS_MARKER
10.80.0.16 ssw.eltex.loc 10.80.0.16 ssw.eltex.loc
172.16.5.78 nexus.eltex.loc 172.16.5.78 nexus.eltex.loc
172.16.1.149 cpe-worker.eltex.loc 172.16.1.149 cpe-worker.eltex.loc
172.16.5.22 mattermost.eltex-co.ru elph.eltex-co.ru ecss-elph-proxy.eltex-co.ru 172.16.5.22 mattermost.eltex-co.ru elph.eltex-co.ru ecss-elph-proxy.eltex-co.ru study-git.eltex-co.ru study-red.eltex-co.ru
_HOSTS _HOSTS
echo "Локальные хосты: *.eltex.loc, mattermost, elph → /etc/hosts" echo "Локальные хосты: *.eltex.loc, mattermost, elph → /etc/hosts"

View File

@@ -1,7 +1,13 @@
#!/bin/bash #!/bin/bash
# Copyright (c) 2026 Виталий Никитенко. MIT License.
# Мастер-скрипт. Запускай от обычного пользователя (sudo попросит сам где нужно). # Мастер-скрипт. Запускай от обычного пользователя (sudo попросит сам где нужно).
cd "$(dirname "$0")" cd "$(dirname "$0")"
# Платформа: на macOS сетевые функции (ru-bypass/kill switch) неприменимы
# (они построены на Linux ipset/ufw/systemd; Amnezia на маке — клиент с utun).
IS_MAC=0
[ "$(uname -s)" = "Darwin" ] && IS_MAC=1
BLD='\033[1m' BLD='\033[1m'
GRN='\033[0;32m' GRN='\033[0;32m'
YEL='\033[0;33m' YEL='\033[0;33m'
@@ -12,19 +18,54 @@ 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+=("1" "AI-инструменты (установить лаунчеры + ключи)")
if [ "$IS_MAC" -eq 0 ]; then
MENU_ARGS+=("2" "Сеть: ru-bypass + kill switch")
fi
MENU_ARGS+=("" "─── Дополнительно ───────────────────────────")
if [ "$IS_MAC" -eq 0 ]; then
MENU_ARGS+=("3" "Отключить kill switch (прямой доступ без VPN)")
MENU_ARGS+=("4" "Включить kill switch (восстановить защиту)")
fi
MENU_ARGS+=("5" "Статус (Amnezia, UFW, AI инструменты, ключи)")
if [ "$IS_MAC" -eq 0 ]; then
MENU_ARGS+=("6" "Проверить сеть (маршрутизация, geo)")
fi
MENU_ARGS+=("7" "Обновить (git pull + перегенерация скриптов)")
MENU_ARGS+=("8" "Android CLI (установить Google Android CLI)")
choice=$(whiptail --title "AI Setup" \ choice=$(whiptail --title "AI Setup" \
--menu "Выбери действие (стрелки + Enter):" 24 70 10 \ --menu "Выбери действие (стрелки + Enter):" 24 70 10 \
"1" "AI-инструменты (установить лаунчеры + ключи)" \ "${MENU_ARGS[@]}" 3>&1 1>&2 2>&3) || exit 0
"2" "Сеть: ru-bypass + kill switch" \
"" "─── Дополнительно ───────────────────────────" \
"3" "Отключить kill switch (прямой доступ без VPN)" \
"4" "Включить kill switch (восстановить защиту)" \
"5" "Статус (Amnezia, UFW, AI инструменты, ключи)" \
"6" "Проверить сеть (маршрутизация, geo)" \
"7" "Обновить (git pull + перегенерация скриптов)" \
"8" "Android CLI (установить Google Android CLI)" \
3>&1 1>&2 2>&3) || exit 0
echo "" echo ""
else else
echo "" echo ""
@@ -33,29 +74,35 @@ else
echo -e "${YEL}Шаги для новой машины (выполнить по порядку):${CLR}" echo -e "${YEL}Шаги для новой машины (выполнить по порядку):${CLR}"
echo "" echo ""
echo -e " ${BLD}1) AI-инструменты${CLR}" echo -e " ${BLD}1) AI-инструменты${CLR}"
echo -e " ${GRY}Устанавливает ai-claude, ai-gpt, ai-deepseek, ai-gemini и др.${CLR}" echo -e " ${GRY}Устанавливает ai-claude, ai-gpt, ai-deepseek, ai-kimi, ai-glm, ai-gemini и др.${CLR}"
echo -e " ${GRY}Запрашивает API-ключи. Запускать один раз.${CLR}" echo -e " ${GRY}Запрашивает API-ключи. Запускать один раз.${CLR}"
echo "" echo ""
if [ "$IS_MAC" -eq 0 ]; then
echo -e " ${BLD}2) Сеть: ru-bypass + kill switch${CLR}" echo -e " ${BLD}2) Сеть: ru-bypass + kill switch${CLR}"
echo -e " ${GRY}.ru сайты (ozon, госуслуги) — напрямую с российским IP.${CLR}" echo -e " ${GRY}.ru сайты (ozon, госуслуги) — напрямую с российским IP.${CLR}"
echo -e " ${GRY}*.loc офисные адреса — тоже напрямую.${CLR}" echo -e " ${GRY}*.loc офисные адреса — тоже напрямую.${CLR}"
echo -e " ${GRY}Всё остальное — только через Amnezia (kill switch).${CLR}" echo -e " ${GRY}Всё остальное — только через Amnezia (kill switch).${CLR}"
echo -e " ${GRY}Запускать один раз на каждой машине.${CLR}" echo -e " ${GRY}Запускать один раз на каждой машине.${CLR}"
echo "" echo ""
fi
echo -e "${YEL}Дополнительно (по необходимости):${CLR}" echo -e "${YEL}Дополнительно (по необходимости):${CLR}"
echo "" echo ""
if [ "$IS_MAC" -eq 0 ]; then
echo -e " ${BLD}3) Отключить kill switch${CLR}" echo -e " ${BLD}3) Отключить kill switch${CLR}"
echo -e " ${GRY}Временно — когда нужен прямой доступ без VPN (российский IP).${CLR}" echo -e " ${GRY}Временно — когда нужен прямой доступ без VPN (российский IP).${CLR}"
echo "" echo ""
echo -e " ${BLD}4) Включить kill switch${CLR}" echo -e " ${BLD}4) Включить kill switch${CLR}"
echo -e " ${GRY}Вернуть защиту обратно после отключения.${CLR}" echo -e " ${GRY}Вернуть защиту обратно после отключения.${CLR}"
echo "" echo ""
fi
echo -e " ${BLD}5) Статус${CLR}" echo -e " ${BLD}5) Статус${CLR}"
echo -e " ${GRY}Amnezia, UFW, сервисы, установленные AI инструменты и ключи.${CLR}" echo -e " ${GRY}Amnezia, UFW, сервисы, установленные AI инструменты и ключи.${CLR}"
echo "" echo ""
if [ "$IS_MAC" -eq 0 ]; then
echo -e " ${BLD}6) Проверить сеть${CLR}" echo -e " ${BLD}6) Проверить сеть${CLR}"
echo -e " ${GRY}Тесты маршрутизации: .ru напрямую, остальное через Amnezia.${CLR}" echo -e " ${GRY}Тесты маршрутизации: .ru напрямую, остальное через Amnezia.${CLR}"
echo "" echo ""
fi
echo -e " ${BLD}7) Обновить${CLR}" echo -e " ${BLD}7) Обновить${CLR}"
echo -e " ${GRY}git pull + перегенерация всех скриптов в ~/bin (или ~/.local/bin).${CLR}" echo -e " ${GRY}git pull + перегенерация всех скриптов в ~/bin (или ~/.local/bin).${CLR}"
echo "" echo ""
@@ -74,6 +121,7 @@ case "$choice" in
bash scripts/ai-setup.sh bash scripts/ai-setup.sh
;; ;;
2) 2)
if [ "$IS_MAC" -eq 1 ]; then echo "Неприменимо на macOS"; exit 0; fi
echo -e "${GRY}Нужно указать параметры твоей локальной сети:${CLR}" echo -e "${GRY}Нужно указать параметры твоей локальной сети:${CLR}"
echo -e "${GRY} GATEWAY — IP домашнего/офисного роутера (через него пойдёт .ru трафик напрямую)${CLR}" echo -e "${GRY} GATEWAY — IP домашнего/офисного роутера (через него пойдёт .ru трафик напрямую)${CLR}"
echo -e "${GRY} DEV — сетевой интерфейс (wifi или провод), через который ты подключён к роутеру${CLR}" echo -e "${GRY} DEV — сетевой интерфейс (wifi или провод), через который ты подключён к роутеру${CLR}"
@@ -130,6 +178,7 @@ case "$choice" in
sudo GATEWAY="$gw" DEV="$dev" LOCAL_DNS="$local_dns" AMNEZIA_SERVER="$amn_srv" KILL_SWITCH_EXCEPTIONS="$ks_exc" USER_HOME="$HOME" bash scripts/ru-bypass.sh sudo GATEWAY="$gw" DEV="$dev" LOCAL_DNS="$local_dns" AMNEZIA_SERVER="$amn_srv" KILL_SWITCH_EXCEPTIONS="$ks_exc" USER_HOME="$HOME" bash scripts/ru-bypass.sh
;; ;;
3) 3)
if [ "$IS_MAC" -eq 1 ]; then echo "Неприменимо на macOS"; exit 0; fi
echo -e "${YEL}Перед этим выйди из Claude Code — сессия сменит IP.${CLR}" echo -e "${YEL}Перед этим выйди из Claude Code — сессия сменит IP.${CLR}"
echo -n "Продолжить? [y/N] " echo -n "Продолжить? [y/N] "
read -r confirm read -r confirm
@@ -137,12 +186,14 @@ case "$choice" in
sudo USER_HOME="$HOME" bash scripts/ks-off.sh sudo USER_HOME="$HOME" bash scripts/ks-off.sh
;; ;;
4) 4)
if [ "$IS_MAC" -eq 1 ]; then echo "Неприменимо на macOS"; exit 0; fi
sudo USER_HOME="$HOME" bash scripts/ks-on.sh sudo USER_HOME="$HOME" bash scripts/ks-on.sh
;; ;;
5) 5)
echo -e "${BLD}=== Статус ===${CLR}" echo -e "${BLD}=== Статус ===${CLR}"
echo "" echo ""
if [ "$IS_MAC" -eq 0 ]; then
echo -e "${BLD}Сеть:${CLR}" echo -e "${BLD}Сеть:${CLR}"
if ip link show amn0 &>/dev/null; then if ip link show amn0 &>/dev/null; then
echo -e " ${GRN}${CLR} Amnezia (amn0) подключена" echo -e " ${GRN}${CLR} Amnezia (amn0) подключена"
@@ -172,8 +223,14 @@ case "$choice" in
fi fi
echo "" echo ""
else
echo -e "${BLD}Сеть:${CLR}"
echo -e " ${GRY}(ru-bypass/kill switch неприменимы на macOS)${CLR}"
echo ""
fi
echo -e "${BLD}AI инструменты:${CLR}" echo -e "${BLD}AI инструменты:${CLR}"
for cmd in ai-claude ai-gpt ai-deepseek ai-kimi ai-openrouter ai-gemini android; do for cmd in ai-claude ai-gpt ai-deepseek ai-kimi ai-glm ai-openrouter ai-opencode ai-gemini android; do
if command -v "$cmd" &>/dev/null; then if command -v "$cmd" &>/dev/null; then
echo -e " ${GRN}${CLR} $cmd" echo -e " ${GRN}${CLR} $cmd"
else else
@@ -184,7 +241,7 @@ case "$choice" in
echo "" echo ""
echo -e "${BLD}API ключи:${CLR}" echo -e "${BLD}API ключи:${CLR}"
cfg="$HOME/.config/ai-setup" cfg="$HOME/.config/ai-setup"
for f in deepseek_key kimi_key openrouter_key; do for f in deepseek_key kimi_key glm_key openrouter_key opencode_key; do
name="${f/_key/}" name="${f/_key/}"
if [ -s "$cfg/$f" ]; then if [ -s "$cfg/$f" ]; then
echo -e " ${GRN}${CLR} $name" echo -e " ${GRN}${CLR} $name"
@@ -195,7 +252,7 @@ case "$choice" in
echo "" echo ""
echo -e "${BLD}API доступность:${CLR}" echo -e "${BLD}API доступность:${CLR}"
for entry in "Anthropic:api.anthropic.com" "DeepSeek:api.deepseek.com" "OpenAI:api.openai.com" "Kimi:api.kimi.com" "OpenRouter:openrouter.ai"; do for entry in "Anthropic:api.anthropic.com" "DeepSeek:api.deepseek.com" "OpenAI:api.openai.com" "Kimi:api.kimi.com" "GLM:api.z.ai" "OpenRouter:openrouter.ai" "OpenCode:opencode.ai"; do
_name="${entry%%:*}" _name="${entry%%:*}"
_host="${entry##*:}" _host="${entry##*:}"
_ms=$(curl -s -o /dev/null -w "%{time_connect}" --max-time 5 "https://$_host" 2>/dev/null) _ms=$(curl -s -o /dev/null -w "%{time_connect}" --max-time 5 "https://$_host" 2>/dev/null)
@@ -215,15 +272,11 @@ case "$choice" in
fi fi
;; ;;
6) 6)
if [ "$IS_MAC" -eq 1 ]; then echo "Неприменимо на macOS"; exit 0; fi
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

@@ -1,4 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Copyright (c) 2026 Виталий Никитенко. MIT License.
# test_isolated.sh — проверка логики автоустановки без реальных скачиваний # test_isolated.sh — проверка логики автоустановки без реальных скачиваний
# Использует mock для curl, чтобы проверить, что скрипты вызывают правильные URL # Использует mock для curl, чтобы проверить, что скрипты вызывают правильные URL
@@ -21,6 +22,11 @@ awk '/^cat > "\$BIN_DIR\/ai-kimi"/,/^KIMIEOF/' scripts/ai-setup.sh | \
sed "s|\\\$BIN_DIR|$BIN_DIR|g" | bash sed "s|\\\$BIN_DIR|$BIN_DIR|g" | bash
chmod +x "$BIN_DIR/ai-kimi" chmod +x "$BIN_DIR/ai-kimi"
# Извлекаем ai-glm
awk '/^cat > "\$BIN_DIR\/ai-glm"/,/^GLMEOF/' scripts/ai-setup.sh | \
sed "s|\\\$BIN_DIR|$BIN_DIR|g" | bash
chmod +x "$BIN_DIR/ai-glm"
echo "=== Test 1: ai-gpt calls codex install URL when missing ===" echo "=== Test 1: ai-gpt calls codex install URL when missing ==="
MOCK_LOG="$TMPDIR/mock_curl.log" MOCK_LOG="$TMPDIR/mock_curl.log"
@@ -62,5 +68,17 @@ else
exit 1 exit 1
fi fi
echo ""
echo "=== Test 3: ai-glm is a Claude Code launcher with Zhipu AI backend ==="
if grep -q 'ANTHROPIC_BASE_URL' "$BIN_DIR/ai-glm" \
&& grep -q 'api.z.ai/api/anthropic' "$BIN_DIR/ai-glm" \
&& grep -q 'claude --dangerously-skip-permissions' "$BIN_DIR/ai-glm"; then
echo "[PASS] ai-glm запускает claude с официальным Zhipu AI API"
else
echo "[FAIL] ai-glm должен запускать claude с api.z.ai/api/anthropic"
exit 1
fi
echo "" echo ""
echo "=== All tests passed ===" echo "=== All tests passed ==="

View File

@@ -1,4 +1,5 @@
#!/bin/bash #!/bin/bash
# Copyright (c) 2026 Виталий Никитенко. MIT License.
python3 -c "import time; print('python started'); time.sleep(100)" & python3 -c "import time; print('python started'); time.sleep(100)" &
py_pid=$! py_pid=$!
node -e " node -e "

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Copyright (c) 2026 Виталий Никитенко. MIT License.
# tests/test_fixes.sh - unit tests for code-review fixes in ai-setup.sh # tests/test_fixes.sh - unit tests for code-review fixes in ai-setup.sh
# Run: bash tests/test_fixes.sh # Run: bash tests/test_fixes.sh
@@ -6,6 +7,7 @@ set -euo pipefail
SCRIPT="$(cd "$(dirname "$0")/.." && pwd)/scripts/ai-setup.sh" SCRIPT="$(cd "$(dirname "$0")/.." && pwd)/scripts/ai-setup.sh"
GLOBAL_RULES_SOURCE="$(cd "$(dirname "$0")/.." && pwd)/home-configs/GLOBAL_RULES.md" GLOBAL_RULES_SOURCE="$(cd "$(dirname "$0")/.." && pwd)/home-configs/GLOBAL_RULES.md"
ADD_HOOK="$(cd "$(dirname "$0")/.." && pwd)/home-configs/claude/hooks/add-account-hook.sh"
PASS=0; FAIL=0 PASS=0; FAIL=0
ok() { echo "[PASS] $1"; PASS=$((PASS+1)); } ok() { echo "[PASS] $1"; PASS=$((PASS+1)); }
@@ -14,8 +16,12 @@ fail() { echo "[FAIL] $1"; FAIL=$((FAIL+1)); }
# Extract sections # Extract sections
GPT_SECTION=$(awk '/^cat > "\$BIN_DIR\/ai-gpt"/,/^GPTEOF/' "$SCRIPT") GPT_SECTION=$(awk '/^cat > "\$BIN_DIR\/ai-gpt"/,/^GPTEOF/' "$SCRIPT")
KIMI_SECTION=$(awk '/^cat > "\$BIN_DIR\/ai-kimi"/,/^KIMIEOF/' "$SCRIPT") KIMI_SECTION=$(awk '/^cat > "\$BIN_DIR\/ai-kimi"/,/^KIMIEOF/' "$SCRIPT")
GLM_SECTION=$(awk '/^cat > "\$BIN_DIR\/ai-glm"/,/^GLMEOF/' "$SCRIPT")
GEMINI_SECTION=$(awk '/^cat > "\$BIN_DIR\/ai-gemini"/,/^GEMINIEOF/' "$SCRIPT") GEMINI_SECTION=$(awk '/^cat > "\$BIN_DIR\/ai-gemini"/,/^GEMINIEOF/' "$SCRIPT")
OPENCODE_SECTION=$(awk '/^cat > "\$BIN_DIR\/ai-opencode"/,/^OPENCODEEOF/' "$SCRIPT")
CURSOR_SECTION=$(awk '/^cat > "\$BIN_DIR\/ai-cursor"/,/^CURSOREOF/' "$SCRIPT")
HELPERS_SECTION=$(awk '/^cat > "\$HELPERS_FILE"/,/^HELPEREOF/' "$SCRIPT") HELPERS_SECTION=$(awk '/^cat > "\$HELPERS_FILE"/,/^HELPEREOF/' "$SCRIPT")
CODEX_SOUND_HOOKS_SECTION=$(awk '/^_setup_codex_sound_hooks\(\)/,/^}/' "$SCRIPT")
# ── ai-gpt: auto-install codex ──────────────────────────────────────────── # ── ai-gpt: auto-install codex ────────────────────────────────────────────
test_gpt_autoinstall() { test_gpt_autoinstall() {
@@ -35,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' \
@@ -66,6 +81,29 @@ test_kimi_no_artemox() {
fi fi
} }
# ── ai-glm: launches claude with GLM backend ──────────────────────────────
test_glm_claude_launcher() {
if echo "$GLM_SECTION" | grep -q 'claude --dangerously-skip-permissions' \
&& echo "$GLM_SECTION" | grep -q 'ANTHROPIC_BASE_URL'; then
ok "ai-glm: launches claude with ANTHROPIC_BASE_URL set"
else
fail "ai-glm: must launch claude with ANTHROPIC_BASE_URL"
fi
}
# ── ai-glm: uses official Zhipu AI API ────────────────────────────────────
test_glm_official_api() {
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_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
ok "ai-glm: uses official Zhipu AI API (glm-5.3 everywhere, ctx window 1M via env)"
else
fail "ai-glm: must use official Zhipu AI API with glm-5.3, 1M context window and x-api-key"
fi
}
# ── ai-gemini: native launcher generation ───────────────────────────────── # ── ai-gemini: native launcher generation ─────────────────────────────────
test_gemini_native_launcher() { test_gemini_native_launcher() {
if echo "$GEMINI_SECTION" | grep -q 'antigravity CLI (agy)' \ if echo "$GEMINI_SECTION" | grep -q 'antigravity CLI (agy)' \
@@ -76,20 +114,24 @@ test_gemini_native_launcher() {
fi fi
} }
# ── global rules: Karpathy-style guidelines and native rule files ─────────── # ── ai-cursor: native launcher generation ──────────────────────────────────
test_global_rules_include_quality_guidelines() { test_cursor_native_launcher() {
karpathy_line=$(grep -n '^## 1\. Think Before Coding$' "$GLOBAL_RULES_SOURCE" | head -1 | cut -d: -f1) if echo "$CURSOR_SECTION" | grep -q 'Cursor CLI (agent)' \
global_line=$(grep -n '^# Global Rules for All AI Agents$' "$GLOBAL_RULES_SOURCE" | head -1 | cut -d: -f1) && echo "$CURSOR_SECTION" | grep -q 'https://cursor.com/install'; then
if [ -n "$karpathy_line" ] \ ok "ai-cursor: native Cursor CLI (agent) launcher is generated"
&& [ -n "$global_line" ] \ else
&& [ "$karpathy_line" -lt "$global_line" ] \ fail "ai-cursor: missing native Cursor CLI launcher generation"
&& grep -q 'Always reply in Russian' "$GLOBAL_RULES_SOURCE" \ fi
&& grep -q 'Plain git diff visibility' "$GLOBAL_RULES_SOURCE" \ }
# ── global rules: source and native rule files ───────────────────────
test_global_rules_source_installed() {
if [ -s "$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: source file includes English Karpathy guidelines before user rules" ok "global rules: non-empty source file is installed as global_rules.md"
else else
fail "global rules: missing source file, English Karpathy guidelines, or user rules" fail "global rules: missing source file or installation wiring"
fi fi
} }
@@ -109,6 +151,16 @@ test_native_rule_files_generated() {
fi fi
} }
test_codex_sound_hooks_detach_stdio() {
local detached_count
detached_count=$(printf '%s\n' "$CODEX_SOUND_HOOKS_SECTION" | grep -Fc ') </dev/null >/dev/null 2>&1 &' || true)
if [ "$detached_count" -eq 2 ]; then
ok "codex hooks: background sound players detach standard streams"
else
fail "codex hooks: background sound players can keep hook pipes open"
fi
}
# ── Fix 7: trap quotes $TMP correctly ──────────────────────────────────────── # ── Fix 7: trap quotes $TMP correctly ────────────────────────────────────────
test_fix7_trap_tmp() { test_fix7_trap_tmp() {
if grep -q "trap 'rm -rf \"\$TMP\"' EXIT" "$SCRIPT"; then if grep -q "trap 'rm -rf \"\$TMP\"' EXIT" "$SCRIPT"; then
@@ -152,20 +204,90 @@ test_android_symlink_to_bin_dir() {
fi fi
} }
test_no_proxychains_for_android() { # ── ai-opencode: launches claude with routatic-proxy backend ──────────────────
if grep -q 'proxychains4.*android' "$SCRIPT"; then test_opencode_claude_launcher() {
fail "android-cli: should not be wrapped in proxychains4 (breaks child processes)" if echo "$OPENCODE_SECTION" | grep -q 'claude --dangerously-skip-permissions' \
&& echo "$OPENCODE_SECTION" | grep -q 'ANTHROPIC_BASE_URL'; then
ok "ai-opencode: launches claude with ANTHROPIC_BASE_URL set"
else else
ok "android-cli: not wrapped in proxychains4" fail "ai-opencode: must launch claude with ANTHROPIC_BASE_URL"
fi fi
} }
test_android_skill_exists() { # ── ai-opencode: uses routatic-proxy (local proxy) ─────────────────────────────
skill_file="$(cd "$(dirname "$0")/.." && pwd)/home-configs/claude/skills/android-cli/SKILL.md" test_opencode_routatic_proxy() {
if [ -f "$skill_file" ]; then if echo "$OPENCODE_SECTION" | grep -q 'routatic-proxy' \
ok "android-cli: custom skill file exists" && echo "$OPENCODE_SECTION" | grep -q '127.0.0.1:3456' \
&& echo "$OPENCODE_SECTION" | grep -q 'ROUTATIC_PROXY_API_KEY'; then
ok "ai-opencode: uses routatic-proxy on 127.0.0.1:3456"
else else
fail "android-cli: custom skill file is missing" fail "ai-opencode: must use routatic-proxy with local proxy and API key env"
fi
}
# ── ai-opencode: manages opencode_key ──────────────────────────────────────────
test_opencode_key_management() {
if echo "$OPENCODE_SECTION" | grep -q 'opencode_key' \
&& echo "$OPENCODE_SECTION" | grep -q 'AI_RESET_KEY' \
&& echo "$OPENCODE_SECTION" | grep -q 'OPENCODE_GO_API_KEY'; then
ok "ai-opencode: opencode_key with AI_RESET_KEY and OPENCODE_GO_API_KEY env fallback"
else
fail "ai-opencode: must manage opencode_key with reset and env fallback"
fi
}
# ── ai-opencode: correct default models ────────────────────────────────────────
test_opencode_models() {
if echo "$OPENCODE_SECTION" | grep -q 'ANTHROPIC_DEFAULT_OPUS_MODEL="deepseek-v4-pro"' \
&& echo "$OPENCODE_SECTION" | grep -q 'ANTHROPIC_DEFAULT_HAIKU_MODEL="kimi-k2.6"' \
&& echo "$OPENCODE_SECTION" | grep -q 'CLAUDE_CODE_DISABLE_1M_CONTEXT=1'; then
ok "ai-opencode: correct default models (deepseek-v4-pro opus, kimi-k2.6 haiku)"
else
fail "ai-opencode: must have deepseek-v4-pro opus and kimi-k2.6 haiku"
fi
}
# ── add-account: логину нужен tty, фоновый запуск висит вечно ────────────────
test_add_account_no_headless_login() {
if grep -q 'claude auth login.*</dev/null' "$ADD_HOOK"; then
fail "add-account: логин запускается с закрытым stdin — код из браузера вставить некуда"
else
ok "add-account: логин не запускается в фоне с закрытым stdin"
fi
}
test_add_account_single_terminal_flow() {
if grep -q 'mkfifo' "$ADD_HOOK" \
&& grep -q 'add-account <код>' "$ADD_HOOK" \
&& ! grep -q 'gnome-terminal' "$ADD_HOOK"; then
ok "add-account: двухшаговый флоу в одном терминале, без второго окна"
else
fail "add-account: логин должен идти через fifo в текущем терминале, без отдельного окна"
fi
}
test_add_account_kills_by_pid() {
if grep -v '^[[:space:]]*#' "$ADD_HOOK" | grep -q 'pkill'; then
fail "add-account: pkill -f выкосит любой процесс с этой подстрокой в cmdline"
else
ok "add-account: процессы гасятся по своим PID, без широкого pkill -f"
fi
}
test_add_account_checks_login_result() {
if grep -q 'Login failed' "$ADD_HOOK" \
&& ! grep -q 'SAVED:' "$ADD_HOOK"; then
ok "add-account: результат логина проверяется, провал не выдаётся за успех"
else
fail "add-account: провал логина не должен маскироваться под успешное сохранение"
fi
}
test_add_hook_syntax() {
if bash -n "$ADD_HOOK" 2>&1; then
ok "syntax: add-account-hook.sh passes 'bash -n'"
else
fail "syntax: add-account-hook.sh has syntax errors"
fi fi
} }
@@ -173,18 +295,30 @@ test_android_skill_exists() {
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
test_glm_claude_launcher
test_glm_official_api
test_gemini_native_launcher test_gemini_native_launcher
test_global_rules_include_quality_guidelines test_cursor_native_launcher
test_global_rules_source_installed
test_native_rule_files_generated test_native_rule_files_generated
test_codex_sound_hooks_detach_stdio
test_fix7_trap_tmp test_fix7_trap_tmp
test_android_cli_install_url test_android_cli_install_url
test_android_init_called test_android_init_called
test_android_symlink_to_bin_dir test_android_symlink_to_bin_dir
test_no_proxychains_for_android test_opencode_claude_launcher
test_android_skill_exists test_opencode_routatic_proxy
test_opencode_key_management
test_opencode_models
test_add_hook_syntax
test_add_account_no_headless_login
test_add_account_single_terminal_flow
test_add_account_kills_by_pid
test_add_account_checks_login_result
echo "" echo ""
echo "Results: $PASS passed, $FAIL failed" echo "Results: $PASS passed, $FAIL failed"

View File

@@ -1,4 +1,5 @@
#!/bin/bash #!/bin/bash
# Copyright (c) 2026 Виталий Никитенко. MIT License.
# Тесты сетевой настройки: Amnezia + ru-bypass + kill switch # Тесты сетевой настройки: Amnezia + ru-bypass + kill switch
# Запускать без sudo (проверяет что доступно обычному пользователю) # Запускать без sudo (проверяет что доступно обычному пользователю)