Compare commits
20 Commits
5038b4f8c6
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3468095c9 | ||
|
|
676b25217e | ||
|
|
9448212a26 | ||
|
|
4331ebb675 | ||
|
|
8c7c2e9e0f | ||
|
|
60b915de5f | ||
|
|
f61f2cde84 | ||
|
|
7304c5c84b | ||
|
|
ee32371d30 | ||
|
|
4723d21693 | ||
|
|
532d70f478 | ||
|
|
eb6a7bddad | ||
|
|
57857cda4d | ||
|
|
0c5057bc21 | ||
|
|
c379ee8efc | ||
| 1bfef33da1 | |||
| c57158420e | |||
|
|
0f88f1e07a | ||
| 9e64088e46 | |||
|
|
9b906823cd |
@@ -5,19 +5,20 @@
|
|||||||
`/effort` и `AI_EFFORT` относятся к лаунчерам, которые запускают Claude Code:
|
`/effort` и `AI_EFFORT` относятся к лаунчерам, которые запускают Claude Code:
|
||||||
`ai-claude`, `ai-deepseek`, `ai-kimi`, `ai-glm`, `ai-openrouter`, `ai-opencode`.
|
`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`) | GLM 5.2 (`ai-glm`) | OpenRouter/GPT-5.5 (`ai-openrouter`) | OpenCode Go (`ai-opencode`) |
|
| Claude Code `/effort` | Anthropic (`ai-claude`) | DeepSeek V4 (`ai-deepseek`) | Kimi K2.7/K2.6 (`ai-kimi`) | GLM 5.3 (`ai-glm`) | OpenRouter/GPT-5.5 (`ai-openrouter`) | OpenCode Go (`ai-opencode`) |
|
||||||
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||||
| `low` | нативно `low` | провайдер поднимает до `high` | thinking on | на стороне сервера | без локального маппинга | без локального маппинга |
|
| `low` | нативно `low` | нативно `low` | thinking on | на стороне сервера | без локального маппинга | без локального маппинга |
|
||||||
| `medium` | нативно `medium` | провайдер поднимает до `high` | thinking on | на стороне сервера | без локального маппинга | без локального маппинга |
|
| `medium` | нативно `medium` | провайдер поднимает до `high` | thinking on | на стороне сервера | без локального маппинга | без локального маппинга |
|
||||||
| `high` | нативно `high` | нативно `high` | thinking on | на стороне сервера | без локального маппинга | без локального маппинга |
|
| `high` | нативно `high` | нативно `high` | thinking on | на стороне сервера | без локального маппинга | без локального маппинга |
|
||||||
| `xhigh` | нативно `xhigh` | провайдер поднимает до `max` | thinking on | на стороне сервера | без локального маппинга | без локального маппинга |
|
| `xhigh` | нативно `xhigh` | провайдер поднимает до `high` | thinking on | на стороне сервера | без локального маппинга | без локального маппинга |
|
||||||
| `max` | нативно `max` | нативно `max` | thinking on | на стороне сервера | без локального маппинга | без локального маппинга |
|
| `max` | нативно `max` | нативно `max` | thinking on | на стороне сервера | без локального маппинга | без локального маппинга |
|
||||||
|
|
||||||
Для OpenRouter текущий лаунчер не делает локального преобразования effort и отправляет
|
Для OpenRouter текущий лаунчер не делает локального преобразования effort и отправляет
|
||||||
@@ -44,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`.
|
||||||
|
|
||||||
@@ -64,7 +66,7 @@ on / off
|
|||||||
- Градаций глубины reasoning нет.
|
- Градаций глубины reasoning нет.
|
||||||
- Дефолт `ai-kimi`: `high`.
|
- Дефолт `ai-kimi`: `high`.
|
||||||
|
|
||||||
### GLM 5.2 (Zhipu AI)
|
### GLM 5.3 (Zhipu AI)
|
||||||
|
|
||||||
```text
|
```text
|
||||||
high / max
|
high / max
|
||||||
@@ -98,7 +100,7 @@ Effort не преобразуется локально — thinking budget не
|
|||||||
| Anthropic | Не нужен | - |
|
| Anthropic | Не нужен | - |
|
||||||
| DeepSeek V4 | DeepSeek API | На стороне сервера |
|
| DeepSeek V4 | DeepSeek API | На стороне сервера |
|
||||||
| Kimi K2.7/K2.6 | Moonshot API | На стороне сервера |
|
| Kimi K2.7/K2.6 | Moonshot API | На стороне сервера |
|
||||||
| GLM 5.2 | Zhipu AI API | На стороне сервера |
|
| GLM 5.3 | Zhipu AI API | На стороне сервера |
|
||||||
| OpenRouter/GPT-5.5 | Локального маппинга нет | `ai-openrouter` отправляет значение как есть |
|
| OpenRouter/GPT-5.5 | Локального маппинга нет | `ai-openrouter` отправляет значение как есть |
|
||||||
| OpenCode Go | Локального маппинга нет | `ai-opencode` отправляет значение через routatic-proxy |
|
| OpenCode Go | Локального маппинга нет | `ai-opencode` отправляет значение через routatic-proxy |
|
||||||
| Legacy GPT proxy | helper-маппинг `xhigh` -> `high` | `~/bin/claude-gpt-effort-proxy.py` или `~/.local/bin/claude-gpt-effort-proxy.py` |
|
| Legacy GPT proxy | helper-маппинг `xhigh` -> `high` | `~/bin/claude-gpt-effort-proxy.py` или `~/.local/bin/claude-gpt-effort-proxy.py` |
|
||||||
@@ -124,4 +126,4 @@ ai-deepseek # без флага - использовать ур
|
|||||||
|
|
||||||
- Для повседневной работы: `high` или `xhigh`.
|
- Для повседневной работы: `high` или `xhigh`.
|
||||||
- `max` effort имеет реальный эффект у Anthropic и DeepSeek; у Kimi и GLM это всё тот же thinking on / маппинг на стороне сервера.
|
- `max` effort имеет реальный эффект у Anthropic и DeepSeek; у Kimi и GLM это всё тот же thinking on / маппинг на стороне сервера.
|
||||||
- `low`/`medium` у DeepSeek, Kimi и GLM фактически не снижают reasoning.
|
- `low`/`medium` у Kimi и GLM фактически не снижают reasoning; у DeepSeek `low` теперь даёт реальный уровень `low` и экономит бюджет.
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
bash setup.sh
|
bash setup.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Спросит про VLESS-прокси (по умолчанию `Y`; если не нужен, ответь `n`), затем установит лаунчеры
|
Установит лаунчеры
|
||||||
`ai-claude`, `ai-gpt`, `ai-deepseek`, `ai-kimi`, `ai-glm`, `ai-openrouter`, `ai-opencode`, `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 трафик идёт напрямую,
|
||||||
|
|||||||
45
README.md
45
README.md
@@ -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,14 +17,9 @@ 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 # тесты маршрутизации
|
||||||
@@ -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,10 +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-glm` - Claude Code через Zhipu AI GLM 5.2 API (`api.z.ai/api/anthropic`)
|
- `ai-glm` - Claude Code через Zhipu AI GLM 5.3 API (`api.z.ai/api/anthropic`)
|
||||||
- `ai-openrouter` - Claude Code через OpenRouter (GPT-5.5, Grok, Qwen, GLM 5.2 и др.)
|
- `ai-openrouter` - Claude Code через OpenRouter (GPT-5.5, Grok, Qwen, GLM 5.2 и др.)
|
||||||
- `ai-opencode` - Claude Code через OpenCode Go + routatic-proxy (DeepSeek, Kimi, Qwen, GLM, MiniMax, MiMo)
|
- `ai-opencode` - Claude Code через OpenCode Go + routatic-proxy (DeepSeek, Kimi, Qwen, GLM, MiniMax, MiMo)
|
||||||
- `ai-gemini` - нативный Antigravity CLI `agy`
|
- `ai-gemini` - нативный Antigravity CLI `agy`
|
||||||
|
- `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
|
||||||
|
|
||||||
@@ -168,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
|
||||||
|
|
||||||
@@ -209,20 +212,6 @@ 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 не активен.
|
|
||||||
|
|
||||||
## Ключи и конфиги
|
## Ключи и конфиги
|
||||||
|
|
||||||
@@ -238,14 +227,15 @@ android init
|
|||||||
|
|
||||||
- `ai-gpt` использует `--dangerously-bypass-approvals-and-sandbox`
|
- `ai-gpt` использует `--dangerously-bypass-approvals-and-sandbox`
|
||||||
- `ai-claude`, `ai-deepseek`, `ai-kimi`, `ai-glm`, `ai-openrouter`, `ai-opencode`, `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. Лаунчеры обновляют их при каждом запуске.
|
||||||
|
|
||||||
@@ -263,7 +253,6 @@ 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
|
||||||
|
|
||||||
## Тесты
|
## Тесты
|
||||||
|
|||||||
@@ -1,92 +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. **Project context:**
|
|
||||||
At the start of work, pay close attention to all provided project `.md` files, because they are provided automatically and contain the current repository's context and specifics.
|
|
||||||
|
|
||||||
5. **Reusable skills:**
|
|
||||||
When the user repeats the same instruction, output format, correction, or workflow, treat it as a candidate for a reusable skill.
|
|
||||||
|
|
||||||
Do not interrupt active work just to create a skill. Finish the current task first, then briefly propose the skill name, trigger description, and what files or tools it should contain.
|
|
||||||
|
|
||||||
Do not create or edit skill files silently. Create or update a skill only after explicit user approval.
|
|
||||||
|
|
||||||
Keep skills focused: prefer several small skills over one broad skill. A useful skill should have a precise description for when to use it, short instructions for how to act, and reusable tools, templates, references, or examples when they make the result more stable.
|
|
||||||
|
|
||||||
If skill files are outside the current git repository, clearly state the exact paths changed and how the user can inspect them, because those changes are not visible in the project `git diff`.
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ The custom statusline script renders **only ASCII-safe characters and ANSI color
|
|||||||
|
|
||||||
- ASCII brackets `[ ]` for git branch and effort level
|
- ASCII brackets `[ ]` for git branch and effort level
|
||||||
- ANSI 256-color escape sequences for model names, effort levels, and rate-limit percentages
|
- ANSI 256-color escape sequences for model names, effort levels, and rate-limit percentages
|
||||||
- The `¥` symbol (U+00A5) for CNY balance in DeepSeek output
|
- The `$` symbol for the DeepSeek balance (USD only)
|
||||||
|
|
||||||
It does **not** use Nerd Font glyphs, powerline separators, or emoji.
|
It does **not** use Nerd Font glyphs, powerline separators, or emoji.
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Copyright (c) 2026 Виталий Никитенко. MIT License.
|
# Copyright (c) 2026 Виталий Никитенко. MIT License.
|
||||||
# UserPromptSubmit hook: перехватывает /add-account.
|
# UserPromptSubmit hook: перехватывает /add-account и /add-account <код>.
|
||||||
# 1) сохраняет текущий аккаунт по его реальному email (account-email.sh)
|
# Логин в два шага, целиком в этом же терминале (без второго окна):
|
||||||
# 2) запускает oauth-логин в фоне (открывает браузер)
|
# /add-account — сохраняет текущий аккаунт, поднимает фоновый
|
||||||
# 3) после логина фоновый процесс сам определяет email нового аккаунта по токену
|
# claude auth login со stdin из fifo и печатает ссылку;
|
||||||
# и сохраняет его credentials + делает current
|
# /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)
|
||||||
@@ -22,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
|
||||||
@@ -35,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)
|
||||||
|
|||||||
60
home-configs/claude/hooks/logout-account-hook.sh
Executable file
60
home-configs/claude/hooks/logout-account-hook.sh
Executable 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
|
||||||
@@ -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 переключает между всеми сохранёнными аккаунтами/ключами по кругу.
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
- старая строка
|
|
||||||
+ новая строка
|
|
||||||
```
|
|
||||||
```
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
---
|
|
||||||
name: el-review
|
|
||||||
description: Use when пользователь запрашивает легкое/быстрое code-review между двумя Git-ветками с диффом в стиле GitLab (от merge-base). Триггеры: "быстрое ревью", "легкое ревью", "/el-review".
|
|
||||||
---
|
|
||||||
|
|
||||||
# Eltex Review
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
Лёгкое ревью диффа между двумя Git-ветками в стиле GitLab Merge Request (от merge-base). **Claude делает ревью сам, без multi-agent workflows** — читает дифф и выдаёт только критические проблемы с готовыми исправлениями.
|
|
||||||
|
|
||||||
## 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`, не спавни multi-agent workflows, не делай deep cross-file investigations:
|
|
||||||
- Найди только критические проблемы: баги, утечки, падения, гонки, ошибки в бизнес-логике, нарушения инвариантов
|
|
||||||
- **Не репорти:** форматирование, именование переменных, стиль кода, предложения по рефакторингу (если это не баг)
|
|
||||||
- Для каждой проблемы сразу пиши готовый diff с исправлением
|
|
||||||
- Объясняй кратко, на русском, простым языком
|
|
||||||
- Если критических проблем нет — напиши **«Критических проблем не найдено»** и остановись
|
|
||||||
|
|
||||||
## Примеры вызова
|
|
||||||
|
|
||||||
```
|
|
||||||
/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
|
|
||||||
- старая строка
|
|
||||||
+ новая строка
|
|
||||||
```
|
|
||||||
```
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
---
|
|
||||||
name: switch-account
|
|
||||||
description: Switch to next account or API key (handled by UserPromptSubmit hook, no LLM needed)
|
|
||||||
---
|
|
||||||
|
|
||||||
Переключение аккаунта/ключа уже выполнено хуком до того, как ты это читаешь.
|
|
||||||
Ответь ровно одним символом: `✓`
|
|
||||||
Никаких инструментов. Никаких объяснений. Только `✓`.
|
|
||||||
@@ -4,6 +4,12 @@ input=$(cat)
|
|||||||
cwd=$(echo "$input" | jq -r '.cwd')
|
cwd=$(echo "$input" | jq -r '.cwd')
|
||||||
model=$(echo "$input" | jq -r '.model.display_name // empty')
|
model=$(echo "$input" | jq -r '.model.display_name // empty')
|
||||||
model_id=$(echo "$input" | jq -r '.model.id // empty')
|
model_id=$(echo "$input" | jq -r '.model.id // empty')
|
||||||
|
# Суффикс [1m] в отображении не нужен: окно контекста задаётся
|
||||||
|
# CLAUDE_CODE_MAX_CONTEXT_TOKENS, а не суффиксом модели
|
||||||
|
model="${model/\[1m\]/}"
|
||||||
|
model="${model% \[1M\]}"
|
||||||
|
model="${model% (1M context)}"
|
||||||
|
model_id="${model_id/\[1m\]/}"
|
||||||
five_pct=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty')
|
five_pct=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty')
|
||||||
five_reset=$(echo "$input" | jq -r '.rate_limits.five_hour.resets_at // empty')
|
five_reset=$(echo "$input" | jq -r '.rate_limits.five_hour.resets_at // empty')
|
||||||
week_pct=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty')
|
week_pct=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty')
|
||||||
@@ -11,15 +17,18 @@ week_reset=$(echo "$input" | jq -r '.rate_limits.seven_day.resets_at // empty')
|
|||||||
ctx_pct=$(echo "$input" | jq -r '.context_window.used_percentage // empty')
|
ctx_pct=$(echo "$input" | jq -r '.context_window.used_percentage // empty')
|
||||||
|
|
||||||
|
|
||||||
|
# Разделитель сегментов: жирная серая средняя точка ·
|
||||||
|
_sep() { printf " \033[1m\033[38;5;240m·\033[00m "; }
|
||||||
|
|
||||||
# Цвет effort: насыщенные, глубже чем пастельные (jewel tones)
|
# Цвет effort: насыщенные, глубже чем пастельные (jewel tones)
|
||||||
_effort_color() {
|
_effort_color() {
|
||||||
case "$1" in
|
case "$1" in
|
||||||
low) printf '\033[38;5;220m[low]\033[00m' ;; # золотой
|
low) printf '\033[38;5;220mlow\033[00m' ;; # золотой
|
||||||
medium) printf '\033[38;5;50m[medium]\033[00m' ;; # насыщенный teal
|
medium) printf '\033[38;5;50mmedium\033[00m' ;; # насыщенный teal
|
||||||
high) printf '\033[38;5;38m[high]\033[00m' ;; # глубокий голубой
|
high) printf '\033[38;5;38mhigh\033[00m' ;; # глубокий голубой
|
||||||
xhigh) printf '\033[38;5;206m[xhigh]\033[00m' ;; # насыщенный фиолетовый
|
xhigh) printf '\033[38;5;206mxhigh\033[00m' ;; # насыщенный фиолетовый
|
||||||
max) printf '\033[38;5;210m[\033[38;5;220mm\033[38;5;114ma\033[38;5;50mx\033[38;5;206m]\033[00m' ;; # радуга
|
max) printf '\033[38;5;220mm\033[38;5;114ma\033[38;5;50mx\033[38;5;206m\033[00m' ;; # радуга
|
||||||
*) printf '\033[38;5;252m[%s]\033[00m' "$1" ;;
|
*) printf '\033[38;5;252m%s\033[00m' "$1" ;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,6 +38,7 @@ _brand_color() {
|
|||||||
deepseek) printf '\033[38;5;69m' ;; # DeepSeek фирменный синий
|
deepseek) printf '\033[38;5;69m' ;; # DeepSeek фирменный синий
|
||||||
claude) printf '\033[38;5;173m' ;; # Anthropic оранжевый
|
claude) printf '\033[38;5;173m' ;; # Anthropic оранжевый
|
||||||
kimi) printf '\033[38;5;81m' ;; # Kimi голубой
|
kimi) printf '\033[38;5;81m' ;; # Kimi голубой
|
||||||
|
glm) printf '\033[38;5;223m' ;; # Z.ai кремовый (бренд-фон z.ai, #FFE4B5)
|
||||||
openrouter) printf '\033[38;5;135m' ;; # OpenRouter фиолетовый
|
openrouter) printf '\033[38;5;135m' ;; # OpenRouter фиолетовый
|
||||||
*) printf '\033[38;5;223m' ;; # кремовый (fallback)
|
*) printf '\033[38;5;223m' ;; # кремовый (fallback)
|
||||||
esac
|
esac
|
||||||
@@ -39,7 +49,7 @@ branch=$(git -C "$cwd" --no-optional-locks symbolic-ref --short HEAD 2>/dev/null
|
|||||||
short_cwd="${cwd/#$HOME/\~}"
|
short_cwd="${cwd/#$HOME/\~}"
|
||||||
printf "\033[38;5;252m%s\033[00m" "$short_cwd"
|
printf "\033[38;5;252m%s\033[00m" "$short_cwd"
|
||||||
|
|
||||||
[ -n "$branch" ] && printf " \033[38;5;252m[%s]\033[00m" "$branch"
|
[ -n "$branch" ] && { _sep; printf "\033[38;5;111m%s\033[00m" "$branch"; }
|
||||||
if [ -n "$model" ]; then
|
if [ -n "$model" ]; then
|
||||||
brand_color=$(_brand_color "${AI_LAUNCHER:-}")
|
brand_color=$(_brand_color "${AI_LAUNCHER:-}")
|
||||||
effort=$(echo "$input" | jq -r ".effort.level // empty")
|
effort=$(echo "$input" | jq -r ".effort.level // empty")
|
||||||
@@ -84,19 +94,21 @@ if [ -n "$model" ]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
[ -n "$account" ] && printf " %s[%s]\033[00m" "$brand_color" "$account"
|
[ -n "$account" ] && { _sep; printf "%s%s\033[00m" "$brand_color" "$account"; }
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Аккаунт/ключ Kimi — показываем alias (account1/account2/...).
|
# Аккаунт/ключ Kimi — показываем alias (account1/account2/...).
|
||||||
if [ "${AI_LAUNCHER:-}" = "kimi" ]; then
|
if [ "${AI_LAUNCHER:-}" = "kimi" ]; then
|
||||||
kimi_account=$(cat "$HOME/.config/ai-setup/kimi_keys/current" 2>/dev/null)
|
kimi_account=$(cat "$HOME/.config/ai-setup/kimi_keys/current" 2>/dev/null)
|
||||||
[ -n "$kimi_account" ] && printf " %s[%s]\033[00m" "$brand_color" "$kimi_account"
|
[ -n "$kimi_account" ] && { _sep; printf "%s%s\033[00m" "$brand_color" "$kimi_account"; }
|
||||||
fi
|
fi
|
||||||
if [ -n "$effort" ]; then
|
if [ -n "$effort" ]; then
|
||||||
printf " %s%s " "$brand_color" "$model"
|
_sep
|
||||||
|
printf "%s%s " "$brand_color" "$model"
|
||||||
_effort_color "$effort"
|
_effort_color "$effort"
|
||||||
else
|
else
|
||||||
printf " %s%s\033[00m" "$brand_color" "$model"
|
_sep
|
||||||
|
printf "%s%s\033[00m" "$brand_color" "$model"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -138,7 +150,7 @@ if [[ "$model_id" == *deepseek* ]]; then
|
|||||||
cache_file="$HOME/.cache/ai-setup/deepseek_balance"
|
cache_file="$HOME/.cache/ai-setup/deepseek_balance"
|
||||||
if [ -f "$cache_file" ]; then
|
if [ -f "$cache_file" ]; then
|
||||||
balance=$(head -1 "$cache_file")
|
balance=$(head -1 "$cache_file")
|
||||||
[ -n "$balance" ] && printf " \033[38;5;78m%s\033[00m" "$balance"
|
[ -n "$balance" ] && { _sep; printf "\033[38;5;78m%s\033[00m" "$balance"; }
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Фоновое обновление баланса (не чаще раза в 30 секунд)
|
# Фоновое обновление баланса (не чаще раза в 30 секунд)
|
||||||
@@ -159,13 +171,12 @@ if [[ "$model_id" == *deepseek* ]]; then
|
|||||||
import sys, json
|
import sys, json
|
||||||
d = json.load(sys.stdin)
|
d = json.load(sys.stdin)
|
||||||
infos = d.get('balance_infos', [])
|
infos = d.get('balance_infos', [])
|
||||||
symbols = {'USD': '\$', 'CNY': '\u00a5'}
|
|
||||||
parts = []
|
parts = []
|
||||||
for info in infos:
|
for info in infos:
|
||||||
curr = info.get('currency', '')
|
if info.get('currency', '').upper() != 'USD':
|
||||||
|
continue
|
||||||
total = info.get('total_balance', '0')
|
total = info.get('total_balance', '0')
|
||||||
sym = symbols.get(curr, curr)
|
parts.append(f'\${total}')
|
||||||
parts.append(f'{sym}{total}')
|
|
||||||
if parts:
|
if parts:
|
||||||
print(' '.join(parts))
|
print(' '.join(parts))
|
||||||
" 2>/dev/null)
|
" 2>/dev/null)
|
||||||
@@ -182,7 +193,7 @@ elif [ "${AI_LAUNCHER:-}" = "openrouter" ]; then
|
|||||||
cache_file="$HOME/.cache/ai-setup/openrouter_balance"
|
cache_file="$HOME/.cache/ai-setup/openrouter_balance"
|
||||||
if [ -f "$cache_file" ]; then
|
if [ -f "$cache_file" ]; then
|
||||||
balance=$(head -1 "$cache_file")
|
balance=$(head -1 "$cache_file")
|
||||||
[ -n "$balance" ] && printf " \033[38;5;78m%s\033[00m" "$balance"
|
[ -n "$balance" ] && { _sep; printf "\033[38;5;78m%s\033[00m" "$balance"; }
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Фоновое обновление баланса (не чаще раза в 30 секунд)
|
# Фоновое обновление баланса (не чаще раза в 30 секунд)
|
||||||
@@ -247,13 +258,13 @@ else
|
|||||||
five_int=$(printf '%.0f' "$five_pct")
|
five_int=$(printf '%.0f' "$five_pct")
|
||||||
remaining=$(fmt_remaining "$five_reset")
|
remaining=$(fmt_remaining "$five_reset")
|
||||||
color=$(pct_color "$five_int")
|
color=$(pct_color "$five_int")
|
||||||
printf " %s%s:%s%%\033[00m" "$color" "$remaining" "$five_int"
|
_sep; printf "%s%s:%s%%\033[00m" "$color" "$remaining" "$five_int"
|
||||||
fi
|
fi
|
||||||
if [ -n "$week_pct" ] && [ -n "$week_reset" ]; then
|
if [ -n "$week_pct" ] && [ -n "$week_reset" ]; then
|
||||||
week_int=$(printf '%.0f' "$week_pct")
|
week_int=$(printf '%.0f' "$week_pct")
|
||||||
remaining=$(fmt_remaining "$week_reset")
|
remaining=$(fmt_remaining "$week_reset")
|
||||||
color=$(pct_color "$week_int")
|
color=$(pct_color "$week_int")
|
||||||
printf " %s%s:%s%%\033[00m" "$color" "$remaining" "$week_int"
|
_sep; printf "%s%s:%s%%\033[00m" "$color" "$remaining" "$week_int"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -263,7 +274,7 @@ fi
|
|||||||
if [ -n "$ctx_pct" ]; then
|
if [ -n "$ctx_pct" ]; then
|
||||||
ctx_int=$(printf '%.0f' "$ctx_pct")
|
ctx_int=$(printf '%.0f' "$ctx_pct")
|
||||||
color=$(pct_color "$ctx_int")
|
color=$(pct_color "$ctx_int")
|
||||||
printf " %sctx:%s%%\033[00m" "$color" "$ctx_int"
|
_sep; printf "%sctx:%s%%\033[00m" "$color" "$ctx_int"
|
||||||
|
|
||||||
# Звуковой сигнал при первом достижении 60%
|
# Звуковой сигнал при первом достижении 60%
|
||||||
alert_file="$HOME/.cache/ai-setup/ctx_alert_state"
|
alert_file="$HOME/.cache/ai-setup/ctx_alert_state"
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -17,6 +17,29 @@ PROXY_BIN="$BIN_DIR/claude-code-proxy"
|
|||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
GLOBAL_RULES_SOURCE="$SCRIPT_DIR/home-configs/GLOBAL_RULES.md"
|
GLOBAL_RULES_SOURCE="$SCRIPT_DIR/home-configs/GLOBAL_RULES.md"
|
||||||
|
|
||||||
|
# ── 0. Определение ОС и архитектуры (глобально) ─────────────
|
||||||
|
# bash 3.2 на macOS: lowercasing через case, не через ${var,,}
|
||||||
|
OS_KERNEL="$(uname -s)" # Linux | Darwin | ...
|
||||||
|
OS_TAG=""
|
||||||
|
ARCH_TAG=""
|
||||||
|
case "$OS_KERNEL" in
|
||||||
|
Linux) OS_TAG="linux" ;;
|
||||||
|
Darwin) OS_TAG="darwin" ;;
|
||||||
|
*) OS_TAG="" ;;
|
||||||
|
esac
|
||||||
|
case "$(uname -m)" in
|
||||||
|
x86_64) ARCH_TAG="amd64" ;;
|
||||||
|
arm64|aarch64) ARCH_TAG="arm64" ;;
|
||||||
|
*) ARCH_TAG="" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Портабельный sed -i: BSD (macOS) требует суффикс, GNU — нет
|
||||||
|
if [ "$OS_KERNEL" = "Darwin" ]; then
|
||||||
|
SED_INPLACE=(-i '')
|
||||||
|
else
|
||||||
|
SED_INPLACE=(-i)
|
||||||
|
fi
|
||||||
|
|
||||||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
|
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
|
||||||
info() { echo -e "${CYAN}[INFO]${NC} $*"; }
|
info() { echo -e "${CYAN}[INFO]${NC} $*"; }
|
||||||
success() { echo -e "${GREEN}[OK]${NC} $*"; }
|
success() { echo -e "${GREEN}[OK]${NC} $*"; }
|
||||||
@@ -30,6 +53,20 @@ if [ "$EUID" -eq 0 ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ── Homebrew shellenv (macOS) ───────────────────────────────
|
||||||
|
# brew может быть установлен, но не в PATH текущего шелла (особенно
|
||||||
|
# после свежей установки). Подключаем shellenv, чтобы node/npm/jq
|
||||||
|
# стали видны через command -v в этой же сессии.
|
||||||
|
if [ "$OS_KERNEL" = "Darwin" ]; then
|
||||||
|
for _brew_prefix in /opt/homebrew /usr/local "$HOME/.homebrew"; do
|
||||||
|
if [ -x "$_brew_prefix/bin/brew" ]; then
|
||||||
|
eval "$("$_brew_prefix/bin/brew" shellenv)"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
unset _brew_prefix
|
||||||
|
fi
|
||||||
|
|
||||||
info "Проверяю зависимости (python3)..."
|
info "Проверяю зависимости (python3)..."
|
||||||
if ! command -v python3 &>/dev/null; then
|
if ! command -v python3 &>/dev/null; then
|
||||||
err "Python 3 не установлен, но он требуется для работы скрипта."
|
err "Python 3 не установлен, но он требуется для работы скрипта."
|
||||||
@@ -38,8 +75,10 @@ success "Python 3 найден"
|
|||||||
|
|
||||||
info "Проверяю зависимости (jq)..."
|
info "Проверяю зависимости (jq)..."
|
||||||
if ! command -v jq &>/dev/null; then
|
if ! command -v jq &>/dev/null; then
|
||||||
info "Устанавливаю jq (нужен sudo)..."
|
info "Устанавливаю jq..."
|
||||||
if command -v apt-get &>/dev/null; then
|
if command -v brew &>/dev/null; then
|
||||||
|
brew install jq
|
||||||
|
elif command -v apt-get &>/dev/null; then
|
||||||
sudo apt-get install -y jq
|
sudo apt-get install -y jq
|
||||||
elif command -v dnf &>/dev/null; then
|
elif command -v dnf &>/dev/null; then
|
||||||
sudo dnf install -y jq
|
sudo dnf install -y jq
|
||||||
@@ -53,279 +92,30 @@ else
|
|||||||
warn "jq не найден. statusline-command.sh и некоторые функции могут не работать."
|
warn "jq не найден. statusline-command.sh и некоторые функции могут не работать."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── VLESS URL parser ───────────────────────────────────────────
|
# ── 1. Node.js ───────────────────────────────────────────────
|
||||||
# Принимает vless:// URL, устанавливает переменные VL_*
|
|
||||||
parse_vless_url() {
|
|
||||||
local url="$1"
|
|
||||||
eval "$(python3 -c "
|
|
||||||
import urllib.parse, sys
|
|
||||||
|
|
||||||
url = sys.argv[1]
|
|
||||||
rest = url[8:] # strip 'vless://'
|
|
||||||
|
|
||||||
at_pos = rest.index('@')
|
|
||||||
uuid = rest[:at_pos]
|
|
||||||
rest = rest[at_pos+1:]
|
|
||||||
|
|
||||||
colon_pos = rest.index(':')
|
|
||||||
q_pos = rest.index('?')
|
|
||||||
host = rest[:colon_pos]
|
|
||||||
port = rest[colon_pos+1:q_pos]
|
|
||||||
|
|
||||||
rest = rest[q_pos+1:]
|
|
||||||
hash_pos = rest.index('#') if '#' in rest else len(rest)
|
|
||||||
qs = rest[:hash_pos]
|
|
||||||
name = rest[hash_pos+1:] if '#' in rest else ''
|
|
||||||
|
|
||||||
params = urllib.parse.parse_qs(qs)
|
|
||||||
def get(p, default=''):
|
|
||||||
vals = params.get(p, [default])
|
|
||||||
return vals[0] if vals else default
|
|
||||||
|
|
||||||
flow = get('flow')
|
|
||||||
vtype = get('type')
|
|
||||||
if not vtype:
|
|
||||||
vtype = 'xhttp' if not flow else 'tcp'
|
|
||||||
spx_default = '' if vtype == 'tcp' else '/'
|
|
||||||
|
|
||||||
print(f'VL_UUID={uuid}')
|
|
||||||
print(f'VL_ADDRESS={host}')
|
|
||||||
print(f'VL_PORT={port}')
|
|
||||||
print(f'VL_ENCRYPTION={get(\"encryption\")}')
|
|
||||||
print(f'VL_FLOW={flow}')
|
|
||||||
print(f'VL_SECURITY={get(\"security\")}')
|
|
||||||
print(f'VL_SNI={get(\"sni\")}')
|
|
||||||
print(f'VL_FP={get(\"fp\", \"chrome\")}')
|
|
||||||
print(f'VL_PBK={get(\"pbk\")}')
|
|
||||||
print(f'VL_SID={get(\"sid\")}')
|
|
||||||
print(f'VL_TYPE={vtype}')
|
|
||||||
print(f'VL_PATH={urllib.parse.unquote(get(\"path\", \"/\"))}')
|
|
||||||
print(f'VL_MODE={get(\"mode\", \"auto\")}')
|
|
||||||
print(f'VL_SPX={get(\"spx\", spx_default)}')
|
|
||||||
print(f'VL_NAME={name}')
|
|
||||||
" "$url")"
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── VLESS connectivity test ────────────────────────────────────
|
|
||||||
# Запускает xray с тестовым конфигом и проверяет curl'ом
|
|
||||||
# Возвращает: "ok" или "fail"
|
|
||||||
test_vless_server() {
|
|
||||||
local url="$1" test_port="$2"
|
|
||||||
|
|
||||||
parse_vless_url "$url"
|
|
||||||
|
|
||||||
# Генерируем временный конфиг
|
|
||||||
_TMP_CONF="/tmp/xray_test_${test_port}.json"
|
|
||||||
_VL_XHTTP=""
|
|
||||||
if [ "$VL_TYPE" = "xhttp" ]; then
|
|
||||||
_VL_XHTTP=$(printf ',\n "xhttpSettings": {\n "path": "%s",\n "mode": "%s"\n }' "$VL_PATH" "$VL_MODE")
|
|
||||||
fi
|
|
||||||
|
|
||||||
cat > "$_TMP_CONF" << XRAYEOF
|
|
||||||
{
|
|
||||||
"log": { "loglevel": "none" },
|
|
||||||
"inbounds": [
|
|
||||||
{
|
|
||||||
"port": $test_port,
|
|
||||||
"listen": "127.0.0.1",
|
|
||||||
"protocol": "socks",
|
|
||||||
"settings": { "udp": true }
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outbounds": [
|
|
||||||
{
|
|
||||||
"protocol": "vless",
|
|
||||||
"settings": {
|
|
||||||
"vnext": [
|
|
||||||
{
|
|
||||||
"address": "$VL_ADDRESS",
|
|
||||||
"port": $VL_PORT,
|
|
||||||
"users": [
|
|
||||||
{
|
|
||||||
"id": "$VL_UUID",
|
|
||||||
"encryption": "$VL_ENCRYPTION",
|
|
||||||
"flow": "$VL_FLOW"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"streamSettings": {
|
|
||||||
"network": "$VL_TYPE",
|
|
||||||
"security": "$VL_SECURITY",
|
|
||||||
"realitySettings": {
|
|
||||||
"serverName": "$VL_SNI",
|
|
||||||
"fingerprint": "$VL_FP",
|
|
||||||
"publicKey": "$VL_PBK",
|
|
||||||
"shortId": "$VL_SID",
|
|
||||||
"spiderX": "$VL_SPX"
|
|
||||||
}$_VL_XHTTP
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
XRAYEOF
|
|
||||||
|
|
||||||
# Запускаем xray
|
|
||||||
/usr/local/bin/xray run -c "$_TMP_CONF" >/dev/null 2>&1 &
|
|
||||||
local xpid=$!
|
|
||||||
sleep 2
|
|
||||||
|
|
||||||
# Тест через SOCKS5
|
|
||||||
local result="fail"
|
|
||||||
if curl --socks5 "127.0.0.1:${test_port}" --max-time 5 -s https://ifconfig.co/ >/dev/null 2>&1; then
|
|
||||||
result="ok"
|
|
||||||
fi
|
|
||||||
|
|
||||||
kill $xpid 2>/dev/null; wait $xpid 2>/dev/null
|
|
||||||
rm -f "$_TMP_CONF"
|
|
||||||
echo "$result"
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── 0. Выбор режима работы VLESS (on / off / skip) ─────────────
|
|
||||||
# AI_SETUP_VLESS:
|
|
||||||
# 1 / on — включить VLESS (установка xray + proxychains + обёртки лаунчеров)
|
|
||||||
# 0 / off — отключить VLESS (откат: stop xray, сброс прокси, IPv6 on)
|
|
||||||
# skip — НЕ трогать сеть (умолчание). Лаунчеры синхронизируются
|
|
||||||
# с текущим состоянием (xray активен?).
|
|
||||||
# Устанавливается из меню setup.sh. По умолчанию skip, чтобы прямой запуск
|
|
||||||
# ai-setup.sh и пункты 1/7/8 не меняли сетевые настройки.
|
|
||||||
DO_VLESS_INSTALL=0
|
|
||||||
case "${AI_SETUP_VLESS:-skip}" in
|
|
||||||
1|on)
|
|
||||||
USE_VLESS=1
|
|
||||||
DO_VLESS_INSTALL=1
|
|
||||||
|
|
||||||
# Читаем список серверов
|
|
||||||
_VL_URLS=()
|
|
||||||
_VL_LABELS=()
|
|
||||||
_SERVERS_FILE="$SCRIPT_DIR/home-configs/vless/servers.conf"
|
|
||||||
|
|
||||||
if [ ! -f "$_SERVERS_FILE" ]; then
|
|
||||||
err "Файл servers.conf не найден: $_SERVERS_FILE"
|
|
||||||
fi
|
|
||||||
|
|
||||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
||||||
[[ "$line" =~ ^[[:space:]]*# ]] && continue
|
|
||||||
[[ -z "$line" ]] && continue
|
|
||||||
_vl_rest="${line#vless://}"
|
|
||||||
_vl_rest="${_vl_rest#*@}"
|
|
||||||
_vl_ip="${_vl_rest%%:*}"
|
|
||||||
_vl_name="${line##*#}"
|
|
||||||
[[ "$_vl_name" == "$line" ]] && _vl_name=""
|
|
||||||
_VL_URLS+=("$line")
|
|
||||||
_VL_LABELS+=("$_vl_ip ($_vl_name)")
|
|
||||||
done < "$_SERVERS_FILE"
|
|
||||||
|
|
||||||
if [ "${#_VL_URLS[@]}" -eq 0 ]; then
|
|
||||||
err "Нет VLESS серверов в $_SERVERS_FILE"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Реальная проверка VLESS (запускаем xray для каждого сервера)
|
|
||||||
echo ""
|
|
||||||
info "Проверяю VLESS-доступность серверов..."
|
|
||||||
_VL_STATUS=()
|
|
||||||
for i in "${!_VL_URLS[@]}"; do
|
|
||||||
_label="${_VL_LABELS[$i]}"
|
|
||||||
_test_port=$((10980 + i))
|
|
||||||
printf " %-45s" "$((i+1))) $_label"
|
|
||||||
_status=$(test_vless_server "${_VL_URLS[$i]}" "$_test_port")
|
|
||||||
if [ "$_status" = "ok" ]; then
|
|
||||||
echo -e "${GREEN}✓ работает${NC}"
|
|
||||||
_VL_STATUS+=("ok")
|
|
||||||
else
|
|
||||||
echo -e "${RED}✗ не работает${NC}"
|
|
||||||
_VL_STATUS+=("fail")
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
read -r -p "Выбери сервер [1-${#_VL_URLS[@]}]: " _vl_choice
|
|
||||||
_vl_choice="${_vl_choice:-1}"
|
|
||||||
if ! [[ "$_vl_choice" =~ ^[0-9]+$ ]] || [ "$_vl_choice" -lt 1 ] || [ "$_vl_choice" -gt "${#_VL_URLS[@]}" ]; then
|
|
||||||
err "Неверный выбор: $_vl_choice"
|
|
||||||
fi
|
|
||||||
|
|
||||||
_VL_SELECTED="${_VL_URLS[$((_vl_choice-1))]}"
|
|
||||||
parse_vless_url "$_VL_SELECTED"
|
|
||||||
info "Выбран: $VL_ADDRESS ($VL_NAME)"
|
|
||||||
;;
|
|
||||||
0|off)
|
|
||||||
USE_VLESS=0
|
|
||||||
DO_VLESS_INSTALL=0
|
|
||||||
info "Отключение VLESS (откат настроек)"
|
|
||||||
|
|
||||||
# ── Откат всех настроек vless ──────────────────────────────
|
|
||||||
info "Откатываю настройки прокси..."
|
|
||||||
|
|
||||||
# Останавливаем и отключаем xray
|
|
||||||
sudo systemctl stop xray 2>/dev/null || true
|
|
||||||
sudo systemctl disable xray 2>/dev/null || true
|
|
||||||
success "xray остановлен и отключён"
|
|
||||||
|
|
||||||
# Системный прокси → none
|
|
||||||
if command -v gsettings &>/dev/null; then
|
|
||||||
gsettings set org.gnome.system.proxy mode 'none' 2>/dev/null || true
|
|
||||||
success "Системный прокси отключён"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Firefox → direct (type=0)
|
|
||||||
FIREFOX_PROFILE=""
|
|
||||||
if [ -d "$HOME/snap/firefox/common/.mozilla/firefox" ]; then
|
|
||||||
FIREFOX_PROFILE=$(find "$HOME/snap/firefox/common/.mozilla/firefox" -name "*.default*" -type d | head -1)
|
|
||||||
elif [ -d "$HOME/.mozilla/firefox" ]; then
|
|
||||||
FIREFOX_PROFILE=$(find "$HOME/.mozilla/firefox" -name "*.default*" -type d | head -1)
|
|
||||||
fi
|
|
||||||
if [ -n "$FIREFOX_PROFILE" ]; then
|
|
||||||
cat > "$FIREFOX_PROFILE/user.js" << 'FJSEOF'
|
|
||||||
user_pref("network.proxy.type", 0);
|
|
||||||
FJSEOF
|
|
||||||
success "Firefox переключён на прямой доступ"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Включаем IPv6 обратно (только если kill switch не активен)
|
|
||||||
if ufw status | grep -qE "активен|active"; then
|
|
||||||
info "UFW kill switch активен — оставляю IPv6 отключённым"
|
|
||||||
else
|
|
||||||
sudo rm -f /etc/sysctl.d/99-disable-ipv6.conf
|
|
||||||
sudo sysctl -w net.ipv6.conf.all.disable_ipv6=0 2>/dev/null || true
|
|
||||||
sudo sysctl -w net.ipv6.conf.default.disable_ipv6=0 2>/dev/null || true
|
|
||||||
sudo systemctl restart systemd-resolved 2>/dev/null || true
|
|
||||||
success "IPv6 восстановлен"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
# skip — сеть не трогаем; синхронизируем лаунчеры с текущим состоянием xray
|
|
||||||
if systemctl is-active --quiet xray 2>/dev/null; then
|
|
||||||
USE_VLESS=1
|
|
||||||
info "Сеть не трогаю. VLESS активен — лаунчеры будут с proxychains4."
|
|
||||||
else
|
|
||||||
USE_VLESS=0
|
|
||||||
info "Сеть не трогаю. VLESS не активен — прямые лаунчеры."
|
|
||||||
fi
|
|
||||||
info "Управление VLESS: пункт 9 (включить / отключить)."
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# ── 1. npm prefix в домашнюю папку ──────────────────────────
|
|
||||||
info "Настраиваю npm prefix..."
|
|
||||||
mkdir -p "$NPM_GLOBAL"
|
|
||||||
npm config set prefix "$NPM_GLOBAL"
|
|
||||||
success "npm prefix -> $NPM_GLOBAL"
|
|
||||||
|
|
||||||
export PATH="$NPM_GLOBAL/bin:$BIN_DIR:$PATH"
|
|
||||||
|
|
||||||
# ── 2. Node.js ───────────────────────────────────────────────
|
|
||||||
info "Проверяю Node.js..."
|
info "Проверяю Node.js..."
|
||||||
if ! command -v node &>/dev/null; then
|
# Хуки (claude-notifier и др.) используют optional chaining (?.) и nullish
|
||||||
info "Попытка установки Node.js (нужен sudo)..."
|
# coalescing (??) — нужен Node >=18. Проверяем не только наличие node, но и
|
||||||
if command -v apt-get &>/dev/null; then
|
# мажорную версию, иначе системный v12 (Ubuntu 22.04) проходит проверку, а хуки падают.
|
||||||
|
node_major=0
|
||||||
|
if command -v node &>/dev/null; then
|
||||||
|
node_major="$(node -p 'process.versions.node.split(".")[0]' 2>/dev/null || echo 0)"
|
||||||
|
fi
|
||||||
|
if [ "${node_major:-0}" -lt 18 ]; then
|
||||||
|
if [ "${node_major:-0}" -gt 0 ]; then
|
||||||
|
info "Найден Node.js v${node_major}, нужен >=18. Обновляю..."
|
||||||
|
else
|
||||||
|
info "Пытаюсь установить Node.js..."
|
||||||
|
fi
|
||||||
|
if command -v brew &>/dev/null; then
|
||||||
|
brew install node
|
||||||
|
elif command -v apt-get &>/dev/null; then
|
||||||
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
|
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
|
||||||
sudo apt-get install -y nodejs
|
sudo apt-get install -y nodejs
|
||||||
elif command -v dnf &>/dev/null; then
|
elif command -v dnf &>/dev/null; then
|
||||||
sudo dnf install -y nodejs
|
sudo dnf install -y nodejs
|
||||||
else
|
else
|
||||||
warn "Не удалось определить пакетный менеджер. Установите Node.js вручную."
|
warn "Не удалось определить пакетный менеджер. Установите Homebrew (https://brew.sh) или Node.js вручную."
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
if command -v node &>/dev/null; then
|
if command -v node &>/dev/null; then
|
||||||
@@ -334,175 +124,19 @@ else
|
|||||||
warn "Node.js не найден. Некоторые функции могут не работать."
|
warn "Node.js не найден. Некоторые функции могут не работать."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── 2.5. proxychains-ng + xray (только при включении VLESS) ──
|
# ── 2. npm prefix в домашнюю папку ──────────────────────────
|
||||||
if [ "$DO_VLESS_INSTALL" -eq 1 ]; then
|
# После установки Node, чтобы npm гарантированно был доступен.
|
||||||
info "Проверяю proxychains-ng..."
|
info "Настраиваю npm prefix..."
|
||||||
if ! command -v proxychains4 &>/dev/null; then
|
mkdir -p "$NPM_GLOBAL"
|
||||||
info "Устанавливаю proxychains-ng (нужен sudo)..."
|
if command -v npm &>/dev/null; then
|
||||||
if command -v apt-get &>/dev/null; then
|
npm config set prefix "$NPM_GLOBAL"
|
||||||
sudo apt-get install -y proxychains-ng
|
success "npm prefix -> $NPM_GLOBAL"
|
||||||
elif command -v dnf &>/dev/null; then
|
else
|
||||||
sudo dnf install -y proxychains-ng
|
warn "npm не найден — пропускаю настройку prefix (установите Node.js)."
|
||||||
else
|
|
||||||
warn "Не удалось установить proxychains-ng автоматически. Установите вручную."
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
if command -v proxychains4 &>/dev/null; then
|
|
||||||
success "proxychains4 найден"
|
|
||||||
else
|
|
||||||
warn "proxychains4 не найден. Продолжаю без проксирования."
|
|
||||||
fi
|
|
||||||
|
|
||||||
info "Устанавливаю xray..."
|
|
||||||
|
|
||||||
# Останавливаем старый процесс (мог остаться от предыдущей установки)
|
|
||||||
sudo systemctl stop xray 2>/dev/null || true
|
|
||||||
|
|
||||||
XRAY_VERSION="26.3.27"
|
|
||||||
XRAY_ARCH="64"
|
|
||||||
XRAY_URL="https://github.com/XTLS/Xray-core/releases/download/v${XRAY_VERSION}/Xray-linux-${XRAY_ARCH}.zip"
|
|
||||||
|
|
||||||
TMPDIR=$(mktemp -d)
|
|
||||||
curl -fsSL "$XRAY_URL" -o "$TMPDIR/xray.zip"
|
|
||||||
unzip -q "$TMPDIR/xray.zip" -d "$TMPDIR"
|
|
||||||
sudo install -m 755 "$TMPDIR/xray" /usr/local/bin/xray
|
|
||||||
rm -rf "$TMPDIR"
|
|
||||||
|
|
||||||
sudo mkdir -p /etc/xray
|
|
||||||
|
|
||||||
# xhttpSettings — только для xhttp-транспорта
|
|
||||||
_VL_XHTTP=""
|
|
||||||
if [ "$VL_TYPE" = "xhttp" ]; then
|
|
||||||
_VL_XHTTP=$(printf ',\n "xhttpSettings": {\n "path": "%s",\n "mode": "%s"\n }' "$VL_PATH" "$VL_MODE")
|
|
||||||
fi
|
|
||||||
|
|
||||||
sudo tee /etc/xray/config.json > /dev/null << XRAYEOF
|
|
||||||
{
|
|
||||||
"log": { "loglevel": "warning" },
|
|
||||||
"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": "$VL_ADDRESS",
|
|
||||||
"port": $VL_PORT,
|
|
||||||
"users": [
|
|
||||||
{
|
|
||||||
"id": "$VL_UUID",
|
|
||||||
"encryption": "$VL_ENCRYPTION",
|
|
||||||
"flow": "$VL_FLOW"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"streamSettings": {
|
|
||||||
"network": "$VL_TYPE",
|
|
||||||
"security": "$VL_SECURITY",
|
|
||||||
"realitySettings": {
|
|
||||||
"serverName": "$VL_SNI",
|
|
||||||
"fingerprint": "$VL_FP",
|
|
||||||
"publicKey": "$VL_PBK",
|
|
||||||
"shortId": "$VL_SID",
|
|
||||||
"spiderX": "$VL_SPX"
|
|
||||||
}$_VL_XHTTP
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
XRAYEOF
|
|
||||||
sudo chmod 644 /etc/xray/config.json
|
|
||||||
|
|
||||||
sudo tee /etc/systemd/system/xray.service > /dev/null << 'SVCEOF'
|
|
||||||
[Unit]
|
|
||||||
Description=Xray Service
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
ExecStart=/usr/local/bin/xray run -c /etc/xray/config.json
|
|
||||||
Restart=on-failure
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
SVCEOF
|
|
||||||
|
|
||||||
# Удаляем чужие drop-in оверрайды (могут переопределять ExecStart на старый конфиг)
|
|
||||||
sudo rm -rf /etc/systemd/system/xray.service.d/
|
|
||||||
# Удаляем старый дефолтный конфиг xray из других путей
|
|
||||||
sudo rm -rf /usr/local/etc/xray/
|
|
||||||
|
|
||||||
sudo systemctl daemon-reload
|
|
||||||
sudo systemctl enable --now xray
|
|
||||||
success "xray установлен и запущен"
|
|
||||||
|
|
||||||
# ── Отключение IPv6 (VLESS не тянет IPv6, браузеры зависают) ──
|
|
||||||
info "Отключаю IPv6 на уровне системы..."
|
|
||||||
sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1
|
|
||||||
sudo sysctl -w net.ipv6.conf.default.disable_ipv6=1
|
|
||||||
sudo tee /etc/sysctl.d/99-disable-ipv6.conf > /dev/null << 'SYSCTEOF'
|
|
||||||
# Отключение IPv6 — требуется для стабильной работы VLESS/xray
|
|
||||||
net.ipv6.conf.all.disable_ipv6=1
|
|
||||||
net.ipv6.conf.default.disable_ipv6=1
|
|
||||||
SYSCTEOF
|
|
||||||
sudo systemctl restart systemd-resolved
|
|
||||||
success "IPv6 отключён, DNS-кэш очищен"
|
|
||||||
|
|
||||||
cp "$SCRIPT_DIR/home-configs/proxychains/proxychains-xray.conf" "$HOME/.proxychains-xray.conf"
|
|
||||||
success "Proxychains конфиг обновлён"
|
|
||||||
|
|
||||||
# ── Настройка Firefox на SOCKS5 + remote DNS ────────────────
|
|
||||||
info "Настраиваю Firefox на SOCKS5 прокси..."
|
|
||||||
FIREFOX_PROFILE=""
|
|
||||||
if [ -d "$HOME/snap/firefox/common/.mozilla/firefox" ]; then
|
|
||||||
FIREFOX_PROFILE=$(find "$HOME/snap/firefox/common/.mozilla/firefox" -name "*.default*" -type d | head -1)
|
|
||||||
elif [ -d "$HOME/.mozilla/firefox" ]; then
|
|
||||||
FIREFOX_PROFILE=$(find "$HOME/.mozilla/firefox" -name "*.default*" -type d | head -1)
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$FIREFOX_PROFILE" ]; then
|
|
||||||
cat > "$FIREFOX_PROFILE/user.js" << 'FJSEOF'
|
|
||||||
user_pref("network.proxy.type", 1);
|
|
||||||
user_pref("network.proxy.socks", "127.0.0.1");
|
|
||||||
user_pref("network.proxy.socks_port", 1080);
|
|
||||||
user_pref("network.proxy.socks_remote_dns", true);
|
|
||||||
user_pref("network.proxy.http", "");
|
|
||||||
user_pref("network.proxy.http_port", 0);
|
|
||||||
user_pref("network.proxy.ssl", "");
|
|
||||||
user_pref("network.proxy.ssl_port", 0);
|
|
||||||
FJSEOF
|
|
||||||
success "Firefox настроен на SOCKS5 (профиль: $FIREFOX_PROFILE)"
|
|
||||||
else
|
|
||||||
warn "Firefox не найден, пропускаю настройку прокси"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── Настройка системного прокси (для Chrome/Chromium) ───────
|
|
||||||
info "Настраиваю системный прокси..."
|
|
||||||
if command -v gsettings &>/dev/null; then
|
|
||||||
gsettings set org.gnome.system.proxy mode 'manual' 2>/dev/null || true
|
|
||||||
gsettings set org.gnome.system.proxy.http host '127.0.0.1' 2>/dev/null || true
|
|
||||||
gsettings set org.gnome.system.proxy.http port 2080 2>/dev/null || true
|
|
||||||
gsettings set org.gnome.system.proxy.socks host '127.0.0.1' 2>/dev/null || true
|
|
||||||
gsettings set org.gnome.system.proxy.socks port 1080 2>/dev/null || true
|
|
||||||
success "Системный прокси настроен (HTTP 2080 + SOCKS 1080)"
|
|
||||||
else
|
|
||||||
warn "gsettings не найден, пропускаю настройку системного прокси"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
export PATH="$NPM_GLOBAL/bin:$BIN_DIR:$PATH"
|
||||||
|
|
||||||
# ── 3. Claude Code ───────────────────────────────────────────
|
# ── 3. Claude Code ───────────────────────────────────────────
|
||||||
info "Проверяю Claude Code..."
|
info "Проверяю Claude Code..."
|
||||||
if ! command -v claude &>/dev/null; then
|
if ! command -v claude &>/dev/null; then
|
||||||
@@ -522,12 +156,9 @@ mkdir -p "$BIN_DIR"
|
|||||||
|
|
||||||
install_proxy() {
|
install_proxy() {
|
||||||
info "Устанавливаю claude-code-proxy..."
|
info "Устанавливаю claude-code-proxy..."
|
||||||
ARCH=$(uname -m)
|
if [ -z "$OS_TAG" ] || [ -z "$ARCH_TAG" ]; then
|
||||||
case "$ARCH" in
|
err "Неизвестная платформа: kernel=$OS_KERNEL arch=$ARCH_TAG"
|
||||||
x86_64) ARCH_TAG="amd64" ;;
|
fi
|
||||||
aarch64) ARCH_TAG="arm64" ;;
|
|
||||||
*) err "Неизвестная архитектура: $ARCH" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
LATEST=$(curl -fsSL "https://api.github.com/repos/raine/claude-code-proxy/releases/latest" \
|
LATEST=$(curl -fsSL "https://api.github.com/repos/raine/claude-code-proxy/releases/latest" \
|
||||||
| grep '"tag_name"' | sed 's/.*"tag_name": *"\(.*\)".*/\1/')
|
| grep '"tag_name"' | sed 's/.*"tag_name": *"\(.*\)".*/\1/')
|
||||||
@@ -535,7 +166,7 @@ install_proxy() {
|
|||||||
|
|
||||||
TMP=$(mktemp -d)
|
TMP=$(mktemp -d)
|
||||||
trap 'rm -rf "$TMP"' EXIT
|
trap 'rm -rf "$TMP"' EXIT
|
||||||
URL="https://github.com/raine/claude-code-proxy/releases/download/${LATEST}/claude-code-proxy-linux-${ARCH_TAG}.tar.gz"
|
URL="https://github.com/raine/claude-code-proxy/releases/download/${LATEST}/claude-code-proxy-${OS_TAG}-${ARCH_TAG}.tar.gz"
|
||||||
info "Скачиваю $URL"
|
info "Скачиваю $URL"
|
||||||
curl -fsSL "$URL" -o "$TMP/proxy.tar.gz" || err "Не удалось скачать claude-code-proxy"
|
curl -fsSL "$URL" -o "$TMP/proxy.tar.gz" || err "Не удалось скачать claude-code-proxy"
|
||||||
tar -xzf "$TMP/proxy.tar.gz" -C "$TMP"
|
tar -xzf "$TMP/proxy.tar.gz" -C "$TMP"
|
||||||
@@ -752,40 +383,6 @@ cp "$CONFIG_DIR/global_rules.md" "$HOME/.gemini/GEMINI.md"
|
|||||||
cp "$CONFIG_DIR/global_rules.md" "$HOME/.cursorrules"
|
cp "$CONFIG_DIR/global_rules.md" "$HOME/.cursorrules"
|
||||||
success "Native rule-файлы обновлены"
|
success "Native rule-файлы обновлены"
|
||||||
|
|
||||||
# ── 6.6. Деплой Claude и Gemini skills ───────────────────────
|
|
||||||
info "Обновляю skills для Claude и Gemini..."
|
|
||||||
SKILLS_SRC="$SCRIPT_DIR/home-configs/claude/skills"
|
|
||||||
CLAUDE_SKILLS_DST="$HOME/.claude/skills"
|
|
||||||
GEMINI_SKILLS_DST="$HOME/.gemini/config/plugins/local-setup/skills"
|
|
||||||
if [ -d "$SKILLS_SRC" ]; then
|
|
||||||
mkdir -p "$CLAUDE_SKILLS_DST" "$GEMINI_SKILLS_DST"
|
|
||||||
|
|
||||||
# Для Gemini нужен plugin.json, чтобы плагин со скилами загрузился
|
|
||||||
GEMINI_PLUGIN_DIR="$HOME/.gemini/config/plugins/local-setup"
|
|
||||||
cat <<EOF > "$GEMINI_PLUGIN_DIR/plugin.json"
|
|
||||||
{
|
|
||||||
"name": "local-setup",
|
|
||||||
"description": "Local custom skills deployed via ai-setup"
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
|
|
||||||
for skill_dir in "$SKILLS_SRC"/*; do
|
|
||||||
[ -d "$skill_dir" ] || continue
|
|
||||||
skill_name=$(basename "$skill_dir")
|
|
||||||
|
|
||||||
# Деплой для Claude
|
|
||||||
mkdir -p "$CLAUDE_SKILLS_DST/$skill_name"
|
|
||||||
cp -r "$skill_dir/"* "$CLAUDE_SKILLS_DST/$skill_name/"
|
|
||||||
|
|
||||||
# Деплой для Gemini (agy)
|
|
||||||
mkdir -p "$GEMINI_SKILLS_DST/$skill_name"
|
|
||||||
cp -r "$skill_dir/"* "$GEMINI_SKILLS_DST/$skill_name/"
|
|
||||||
done
|
|
||||||
success "Skills обновлены для Claude и Gemini"
|
|
||||||
else
|
|
||||||
info "Папка со skills не найдена, пропускаю"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── 6.7. Статусная строка Claude Code ───────────────────────
|
# ── 6.7. Статусная строка Claude Code ───────────────────────
|
||||||
info "Настраиваю статусную строку Claude Code..."
|
info "Настраиваю статусную строку Claude Code..."
|
||||||
STATUSLINE_SRC="$SCRIPT_DIR/home-configs/claude/statusline-command.sh"
|
STATUSLINE_SRC="$SCRIPT_DIR/home-configs/claude/statusline-command.sh"
|
||||||
@@ -937,6 +534,41 @@ else
|
|||||||
warn "Файл $ADD_HOOK_SRC не найден, пропускаю"
|
warn "Файл $ADD_HOOK_SRC не найден, пропускаю"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ── 6.7.3. Хук logout-account ───────────────────────────────────
|
||||||
|
info "Деплою хук logout-account..."
|
||||||
|
LOGOUT_HOOK_SRC="$SCRIPT_DIR/home-configs/claude/hooks/logout-account-hook.sh"
|
||||||
|
LOGOUT_HOOK_DST="$HOME/.claude/hooks/logout-account-hook.sh"
|
||||||
|
mkdir -p "$HOME/.claude/hooks"
|
||||||
|
if [ -f "$LOGOUT_HOOK_SRC" ]; then
|
||||||
|
cp "$LOGOUT_HOOK_SRC" "$LOGOUT_HOOK_DST"
|
||||||
|
chmod +x "$LOGOUT_HOOK_DST"
|
||||||
|
# Прописываем хук в settings.json (идемпотентно)
|
||||||
|
python3 - "$HOME/.claude/settings.json" "$LOGOUT_HOOK_DST" <<'PYEOF'
|
||||||
|
import sys, json, os
|
||||||
|
settings_path, hook_path = sys.argv[1], sys.argv[2]
|
||||||
|
data = {}
|
||||||
|
if os.path.exists(settings_path):
|
||||||
|
with open(settings_path) as f:
|
||||||
|
try: data = json.load(f)
|
||||||
|
except json.JSONDecodeError: pass
|
||||||
|
data.setdefault("hooks", {}).setdefault("UserPromptSubmit", [{"hooks": []}])
|
||||||
|
hook_cmd = f'bash "{hook_path}"'
|
||||||
|
ups = data["hooks"]["UserPromptSubmit"]
|
||||||
|
already = any(
|
||||||
|
any(h.get("command", "") == hook_cmd for h in entry.get("hooks", []))
|
||||||
|
for entry in ups
|
||||||
|
)
|
||||||
|
if not already:
|
||||||
|
ups[0]["hooks"].append({"type": "command", "command": hook_cmd})
|
||||||
|
with open(settings_path, "w") as f:
|
||||||
|
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||||
|
f.write("\n")
|
||||||
|
PYEOF
|
||||||
|
success "Хук logout-account установлен"
|
||||||
|
else
|
||||||
|
warn "Файл $LOGOUT_HOOK_SRC не найден, пропускаю"
|
||||||
|
fi
|
||||||
|
|
||||||
# ── 6.8. Регистрация официального маркетплейса плагинов Claude ──
|
# ── 6.8. Регистрация официального маркетплейса плагинов Claude ──
|
||||||
info "Настраиваю маркетплейс плагинов Claude Code..."
|
info "Настраиваю маркетплейс плагинов Claude Code..."
|
||||||
if ! command -v claude &>/dev/null; then
|
if ! command -v claude &>/dev/null; then
|
||||||
@@ -1013,7 +645,11 @@ PATHEOF
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
add_path_to_rc "$HOME/.bashrc"
|
add_path_to_rc "$HOME/.bashrc"
|
||||||
[ -f "$HOME/.zshrc" ] && add_path_to_rc "$HOME/.zshrc"
|
# На macOS дефолтный шелл — zsh, но ~/.zshrc может не существовать.
|
||||||
|
# Гарантируем наличие файла, иначе PATH не добавится и лаунчеры будут
|
||||||
|
# недоступны без ручного `exec`.
|
||||||
|
[ -f "$HOME/.zshrc" ] || touch "$HOME/.zshrc"
|
||||||
|
add_path_to_rc "$HOME/.zshrc"
|
||||||
|
|
||||||
|
|
||||||
# ── 8. Генерация Standalone скриптов ────────────────────────
|
# ── 8. Генерация Standalone скриптов ────────────────────────
|
||||||
@@ -1070,25 +706,24 @@ try:
|
|||||||
d = json.load(sys.stdin)
|
d = json.load(sys.stdin)
|
||||||
available = d.get('is_available', False)
|
available = d.get('is_available', False)
|
||||||
infos = d.get('balance_infos', [])
|
infos = d.get('balance_infos', [])
|
||||||
|
# Показываем только USD (CNY не выводим)
|
||||||
|
infos = [i for i in infos if i.get('currency', '').upper() == 'USD']
|
||||||
if not infos:
|
if not infos:
|
||||||
print(' \033[0;33m[БАЛАНС]\033[0m Нет данных о балансе')
|
print(' \033[0;33m[БАЛАНС]\033[0m Нет данных о балансе')
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
symbols = {'USD': '\$', 'CNY': '¥'}
|
|
||||||
cache_parts = []
|
cache_parts = []
|
||||||
for info in infos:
|
for info in infos:
|
||||||
curr = info.get('currency', '???')
|
|
||||||
total = info.get('total_balance', '0')
|
total = info.get('total_balance', '0')
|
||||||
granted = info.get('granted_balance', '0')
|
granted = info.get('granted_balance', '0')
|
||||||
topped_up = info.get('topped_up_balance', '0')
|
topped_up = info.get('topped_up_balance', '0')
|
||||||
sym = symbols.get(curr, curr)
|
|
||||||
status = '✅ доступен' if available else '❌ не активен'
|
status = '✅ доступен' if available else '❌ не активен'
|
||||||
print(f' \033[1;36m💰 Баланс DeepSeek:\033[0m {total} {curr} {status}')
|
print(f' \033[1;36m💰 Баланс DeepSeek:\033[0m \${total} {status}')
|
||||||
if float(granted) > 0:
|
if float(granted) > 0:
|
||||||
print(f' └─ Начислено: {granted} {curr}')
|
print(f' └─ Начислено: \${granted}')
|
||||||
if float(topped_up) > 0:
|
if float(topped_up) > 0:
|
||||||
print(f' └─ Пополнено: {topped_up} {curr}')
|
print(f' └─ Пополнено: \${topped_up}')
|
||||||
cache_parts.append(f'{sym}{total}')
|
cache_parts.append(f'\${total}')
|
||||||
# Cache all currencies with symbols for statusline
|
# Cache USD balance for statusline
|
||||||
if cache_parts:
|
if cache_parts:
|
||||||
cache_dir = os.path.expanduser('~/.cache/ai-setup')
|
cache_dir = os.path.expanduser('~/.cache/ai-setup')
|
||||||
os.makedirs(cache_dir, exist_ok=True)
|
os.makedirs(cache_dir, exist_ok=True)
|
||||||
@@ -1365,16 +1000,16 @@ PYEOF
|
|||||||
# ai-claude использует ~/.claude/hooks напрямую; остальные лаунчеры получают
|
# ai-claude использует ~/.claude/hooks напрямую; остальные лаунчеры получают
|
||||||
# свою копию, чтобы /switch-account и /add-account работали изолированно.
|
# свою копию, чтобы /switch-account и /add-account работали изолированно.
|
||||||
mkdir -p "$cfg/hooks"
|
mkdir -p "$cfg/hooks"
|
||||||
for hook in switch-account-hook.sh add-account-hook.sh account-email.sh; do
|
for hook in switch-account-hook.sh add-account-hook.sh logout-account-hook.sh account-email.sh; do
|
||||||
if [ -f "$HOME/.claude/hooks/$hook" ]; then
|
if [ -f "$HOME/.claude/hooks/$hook" ]; then
|
||||||
cp "$HOME/.claude/hooks/$hook" "$cfg/hooks/$hook"
|
cp "$HOME/.claude/hooks/$hook" "$cfg/hooks/$hook"
|
||||||
chmod +x "$cfg/hooks/$hook"
|
chmod +x "$cfg/hooks/$hook"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
# Регистрируем UserPromptSubmit хуки в изолированном settings.json
|
# Регистрируем UserPromptSubmit хуки в изолированном settings.json
|
||||||
python3 - "$cfg/settings.json" "$cfg/hooks/switch-account-hook.sh" "$cfg/hooks/add-account-hook.sh" <<'PYEOF'
|
python3 - "$cfg/settings.json" "$cfg/hooks/switch-account-hook.sh" "$cfg/hooks/add-account-hook.sh" "$cfg/hooks/logout-account-hook.sh" <<'PYEOF'
|
||||||
import sys, json, os
|
import sys, json, os
|
||||||
settings_path, switch_hook, add_hook = sys.argv[1:4]
|
settings_path = sys.argv[1]
|
||||||
data = {}
|
data = {}
|
||||||
if os.path.exists(settings_path):
|
if os.path.exists(settings_path):
|
||||||
try:
|
try:
|
||||||
@@ -1384,7 +1019,7 @@ if os.path.exists(settings_path):
|
|||||||
pass
|
pass
|
||||||
data.setdefault("hooks", {}).setdefault("UserPromptSubmit", [{"hooks": []}])
|
data.setdefault("hooks", {}).setdefault("UserPromptSubmit", [{"hooks": []}])
|
||||||
ups = data["hooks"]["UserPromptSubmit"]
|
ups = data["hooks"]["UserPromptSubmit"]
|
||||||
for hook_path in (switch_hook, add_hook):
|
for hook_path in sys.argv[2:]:
|
||||||
hook_cmd = f'bash "{hook_path}"'
|
hook_cmd = f'bash "{hook_path}"'
|
||||||
already = any(
|
already = any(
|
||||||
any(h.get("command", "") == hook_cmd for h in entry.get("hooks", []))
|
any(h.get("command", "") == hook_cmd for h in entry.get("hooks", []))
|
||||||
@@ -1406,6 +1041,7 @@ PYEOF
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Minimal task-completion sound fallback
|
# Minimal task-completion sound fallback
|
||||||
(
|
(
|
||||||
|
afplay /System/Library/Sounds/Glass.aiff 2>/dev/null ||
|
||||||
paplay /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null ||
|
paplay /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null ||
|
||||||
pw-play --volume=1 /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null ||
|
pw-play --volume=1 /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null ||
|
||||||
aplay /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null
|
aplay /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null
|
||||||
@@ -1497,20 +1133,22 @@ _setup_codex_sound_hooks() {
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Minimal Codex turn-completion sound.
|
# Minimal Codex turn-completion sound.
|
||||||
(
|
(
|
||||||
|
afplay /System/Library/Sounds/Glass.aiff 2>/dev/null ||
|
||||||
paplay /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null ||
|
paplay /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null ||
|
||||||
pw-play --volume=1 /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null ||
|
pw-play --volume=1 /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null ||
|
||||||
aplay /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null
|
aplay /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null
|
||||||
) &
|
) </dev/null >/dev/null 2>&1 &
|
||||||
SOUNDEOF
|
SOUNDEOF
|
||||||
|
|
||||||
cat > "$hooks_dir/ai-setup-sound-on-auto-compact.sh" <<'SOUNDEOF'
|
cat > "$hooks_dir/ai-setup-sound-on-auto-compact.sh" <<'SOUNDEOF'
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Minimal Codex context auto-compact warning sound.
|
# Minimal Codex context auto-compact warning sound.
|
||||||
(
|
(
|
||||||
|
afplay /System/Library/Sounds/Basso.aiff 2>/dev/null ||
|
||||||
paplay /usr/share/sounds/freedesktop/stereo/alarm-clock-elapsed.oga 2>/dev/null ||
|
paplay /usr/share/sounds/freedesktop/stereo/alarm-clock-elapsed.oga 2>/dev/null ||
|
||||||
pw-play --volume=1 /usr/share/sounds/freedesktop/stereo/alarm-clock-elapsed.oga 2>/dev/null ||
|
pw-play --volume=1 /usr/share/sounds/freedesktop/stereo/alarm-clock-elapsed.oga 2>/dev/null ||
|
||||||
aplay /usr/share/sounds/freedesktop/stereo/alarm-clock-elapsed.oga 2>/dev/null
|
aplay /usr/share/sounds/freedesktop/stereo/alarm-clock-elapsed.oga 2>/dev/null
|
||||||
) &
|
) </dev/null >/dev/null 2>&1 &
|
||||||
SOUNDEOF
|
SOUNDEOF
|
||||||
chmod +x "$hooks_dir/ai-setup-sound-on-stop.sh" "$hooks_dir/ai-setup-sound-on-auto-compact.sh"
|
chmod +x "$hooks_dir/ai-setup-sound-on-stop.sh" "$hooks_dir/ai-setup-sound-on-auto-compact.sh"
|
||||||
|
|
||||||
@@ -1604,7 +1242,9 @@ fi
|
|||||||
|
|
||||||
_build_ai_sys_prompt > /dev/null # сохраняет в ~/.codex/AGENTS.md (codex читает авто)
|
_build_ai_sys_prompt > /dev/null # сохраняет в ~/.codex/AGENTS.md (codex читает авто)
|
||||||
_setup_codex_sound_hooks
|
_setup_codex_sound_hooks
|
||||||
exec "$codex_bin" --dangerously-bypass-approvals-and-sandbox "$@"
|
exec "$codex_bin" --dangerously-bypass-approvals-and-sandbox \
|
||||||
|
-c 'tui.status_line=["model-with-reasoning","git-branch","context-used","five-hour-limit","current-dir"]' \
|
||||||
|
"$@"
|
||||||
GPTEOF
|
GPTEOF
|
||||||
chmod +x "$BIN_DIR/ai-gpt"
|
chmod +x "$BIN_DIR/ai-gpt"
|
||||||
|
|
||||||
@@ -1921,7 +1561,7 @@ chmod +x "$BIN_DIR/ai-kimi"
|
|||||||
# === ai-glm ===
|
# === ai-glm ===
|
||||||
cat > "$BIN_DIR/ai-glm" << 'GLMEOF'
|
cat > "$BIN_DIR/ai-glm" << 'GLMEOF'
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# ai-glm - запуск Claude Code через Zhipu AI GLM 5.2 (Anthropic-compatible API)
|
# ai-glm - запуск Claude Code через Zhipu AI GLM 5.3 (Anthropic-compatible API)
|
||||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/ai-api-helpers.sh" 2>/dev/null || true
|
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/ai-api-helpers.sh" 2>/dev/null || true
|
||||||
|
|
||||||
key_file="$HOME/.config/ai-setup/glm_key"
|
key_file="$HOME/.config/ai-setup/glm_key"
|
||||||
@@ -1941,7 +1581,7 @@ glm_base_url="${GLM_BASE_URL:-https://api.z.ai/api/anthropic}"
|
|||||||
|
|
||||||
if [ -n "$api_key" ]; then
|
if [ -n "$api_key" ]; then
|
||||||
echo -n "Проверка сохранённого GLM ключа... "
|
echo -n "Проверка сохранённого GLM ключа... "
|
||||||
_claude_test_api "$glm_base_url/v1/messages" "x-api-key: $api_key" "glm-5.2"
|
_claude_test_api "$glm_base_url/v1/messages" "x-api-key: $api_key" "glm-5.3"
|
||||||
_handle_api_response "GLM" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" "Пополните баланс: https://z.ai/manage-apikey/apikey-list"
|
_handle_api_response "GLM" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" "Пополните баланс: https://z.ai/manage-apikey/apikey-list"
|
||||||
ret=$_API_RET
|
ret=$_API_RET
|
||||||
if [ $ret -eq 401 ]; then
|
if [ $ret -eq 401 ]; then
|
||||||
@@ -1982,7 +1622,7 @@ if [ -z "$api_key" ]; then
|
|||||||
[ -z "$api_key" ] && { echo "Выход."; exit 1; }
|
[ -z "$api_key" ] && { echo "Выход."; exit 1; }
|
||||||
|
|
||||||
echo -n "Проверяю ключ... "
|
echo -n "Проверяю ключ... "
|
||||||
_claude_test_api "$glm_base_url/v1/messages" "x-api-key: $api_key" "glm-5.2"
|
_claude_test_api "$glm_base_url/v1/messages" "x-api-key: $api_key" "glm-5.3"
|
||||||
_handle_api_response "GLM" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" "Пополните баланс: https://z.ai/manage-apikey/apikey-list"
|
_handle_api_response "GLM" "$_CLAUDE_TEST_CODE" "$_CLAUDE_TEST_BODY" "Пополните баланс: https://z.ai/manage-apikey/apikey-list"
|
||||||
ret=$_API_RET
|
ret=$_API_RET
|
||||||
if [ $ret -eq 0 ] || [ $ret -eq 429 ]; then
|
if [ $ret -eq 0 ] || [ $ret -eq 429 ]; then
|
||||||
@@ -2005,26 +1645,40 @@ trap 'rm -f "$_PROMPT_FILE"' EXIT INT TERM
|
|||||||
_build_ai_sys_prompt > "$_PROMPT_FILE"
|
_build_ai_sys_prompt > "$_PROMPT_FILE"
|
||||||
export AI_LAUNCHER=glm
|
export AI_LAUNCHER=glm
|
||||||
export CLAUDE_CONFIG_DIR="$HOME/.config/ai-setup/cfg/glm"
|
export CLAUDE_CONFIG_DIR="$HOME/.config/ai-setup/cfg/glm"
|
||||||
# Пикер: Opus/Sonnet = GLM 5.2, Haiku = GLM 5.2[1m]. availableModels НЕ задаём
|
# Пикер: все три слота = glm-5.3. availableModels НЕ задаём (он схлопывает пикер).
|
||||||
# (он схлопывает пикер). Claude Code навязывает 3 слота; незаданный показал бы
|
# Claude Code навязывает 3 слота; незаданный показал бы чужой Claude, поэтому
|
||||||
# чужой Claude, поэтому opus+sonnet = glm-5.2, haiku = glm-5.2[1m].
|
# opus+sonnet+haiku = glm-5.3. Окно контекста у всех слотов задаёт env
|
||||||
|
# CLAUDE_CODE_MAX_CONTEXT_TOKENS ниже — суффикс [1m] больше не нужен.
|
||||||
_setup_isolated_config glm opus high ''
|
_setup_isolated_config glm opus high ''
|
||||||
_apply_effort glm high
|
# ai-glm стартует на /effort max. max невалиден в settings.json (там только
|
||||||
|
# low/medium/high/xhigh), поэтому задаём через CLI --effort, а не _apply_effort.
|
||||||
|
# CLAUDE_CODE_ALWAYS_ENABLE_EFFORT заставляет Claude Code отправлять effort в API
|
||||||
|
# для GLM (иначе glm-5.3 не распознан как effort-capable и параметр не уходит).
|
||||||
|
# /effort внутри сессии работает; ручной override запуска: AI_EFFORT=high ai-glm.
|
||||||
|
# CLAUDE_CODE_DISABLE_1M_CONTEXT НЕ выставляем: GLM 5.3 реально держит 1M контекста,
|
||||||
|
# а флаг заставлял бы счётчик ctx: считать окно = 200K (врал в ~5 раз). Прочие
|
||||||
|
# лаунчеры (deepseek/kimi/openrouter/opencode) флаг оставляют — у них другой контекст
|
||||||
|
# и флаг убирает [1M]-дубли из пикера; ai-claude его и так не использует.
|
||||||
|
# CLAUDE_CODE_MAX_CONTEXT_TOKENS: glm-* нет в реестре моделей Claude Code, поэтому
|
||||||
|
# окно по умолчанию считается = 200K — ctx: врал бы в ~5 раз, а автокомпакт жал бы
|
||||||
|
# сессию в 200K. Задаём реальное окно 1M явно; env применяется к не-claude моделям
|
||||||
|
# независимо от суффикса, поэтому [1m] у Haiku-слота не нужен и убран.
|
||||||
ANTHROPIC_BASE_URL="$glm_base_url" \
|
ANTHROPIC_BASE_URL="$glm_base_url" \
|
||||||
ANTHROPIC_AUTH_TOKEN="$api_key" \
|
ANTHROPIC_AUTH_TOKEN="$api_key" \
|
||||||
ANTHROPIC_DEFAULT_OPUS_MODEL=glm-5.2 \
|
ANTHROPIC_DEFAULT_OPUS_MODEL=glm-5.3 \
|
||||||
ANTHROPIC_DEFAULT_OPUS_MODEL_NAME="GLM 5.2" \
|
ANTHROPIC_DEFAULT_OPUS_MODEL_NAME="GLM 5.3" \
|
||||||
ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION="GLM 5.2 — флагман Zhipu AI" \
|
ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION="GLM 5.3 — флагман Zhipu AI" \
|
||||||
ANTHROPIC_DEFAULT_SONNET_MODEL=glm-5.2 \
|
ANTHROPIC_DEFAULT_SONNET_MODEL=glm-5.3 \
|
||||||
ANTHROPIC_DEFAULT_SONNET_MODEL_NAME="GLM 5.2" \
|
ANTHROPIC_DEFAULT_SONNET_MODEL_NAME="GLM 5.3" \
|
||||||
ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION="GLM 5.2 — флагман Zhipu AI" \
|
ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION="GLM 5.3 — флагман Zhipu AI" \
|
||||||
ANTHROPIC_DEFAULT_HAIKU_MODEL='glm-5.2[1m]' \
|
ANTHROPIC_DEFAULT_HAIKU_MODEL=glm-5.3 \
|
||||||
ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME="GLM 5.2 [1M]" \
|
ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME="GLM 5.3" \
|
||||||
ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION="GLM 5.2 [1M] — расширенный контекст" \
|
ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION="GLM 5.3 — флагман Zhipu AI" \
|
||||||
CLAUDE_CODE_SUBAGENT_MODEL=glm-5.2 \
|
CLAUDE_CODE_SUBAGENT_MODEL=glm-5.3 \
|
||||||
CLAUDE_CODE_DISABLE_1M_CONTEXT=1 \
|
CLAUDE_CODE_MAX_CONTEXT_TOKENS=1000000 \
|
||||||
|
CLAUDE_CODE_ALWAYS_ENABLE_EFFORT=1 \
|
||||||
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \
|
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \
|
||||||
claude --dangerously-skip-permissions --system-prompt-file "$_PROMPT_FILE" "$@"
|
claude --dangerously-skip-permissions --effort "${AI_EFFORT:-max}" --system-prompt-file "$_PROMPT_FILE" "$@"
|
||||||
GLMEOF
|
GLMEOF
|
||||||
chmod +x "$BIN_DIR/ai-glm"
|
chmod +x "$BIN_DIR/ai-glm"
|
||||||
|
|
||||||
@@ -2169,6 +1823,7 @@ source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/ai-api-helpers.sh" 2>/dev/
|
|||||||
"$agy_bin" --dangerously-skip-permissions "$@"
|
"$agy_bin" --dangerously-skip-permissions "$@"
|
||||||
agy_exit=$?
|
agy_exit=$?
|
||||||
(
|
(
|
||||||
|
afplay /System/Library/Sounds/Glass.aiff 2>/dev/null ||
|
||||||
paplay /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null ||
|
paplay /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null ||
|
||||||
pw-play --volume=1 /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null ||
|
pw-play --volume=1 /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null ||
|
||||||
aplay /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null
|
aplay /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null
|
||||||
@@ -2177,7 +1832,52 @@ exit $agy_exit
|
|||||||
GEMINIEOF
|
GEMINIEOF
|
||||||
chmod +x "$BIN_DIR/ai-gemini"
|
chmod +x "$BIN_DIR/ai-gemini"
|
||||||
# Подменяем путь к agy, если BIN_DIR отличается от ~/.local/bin
|
# Подменяем путь к agy, если BIN_DIR отличается от ~/.local/bin
|
||||||
[ "$BIN_DIR" != "$HOME/.local/bin" ] && sed -i "s|\$HOME/\.local/bin|\$HOME/${BIN_DIR#$HOME/}|g" "$BIN_DIR/ai-gemini"
|
[ "$BIN_DIR" != "$HOME/.local/bin" ] && sed "${SED_INPLACE[@]}" "s|\$HOME/\.local/bin|\$HOME/${BIN_DIR#$HOME/}|g" "$BIN_DIR/ai-gemini"
|
||||||
|
|
||||||
|
# === ai-cursor ===
|
||||||
|
cat > "$BIN_DIR/ai-cursor" << 'CURSOREOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# ============================================================
|
||||||
|
# ai-cursor - запуск нативного Cursor CLI (agent)
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
cursor_bin="$HOME/.local/bin/agent"
|
||||||
|
[ ! -f "$cursor_bin" ] && cursor_bin="$(command -v agent 2>/dev/null)"
|
||||||
|
|
||||||
|
if [ -z "$cursor_bin" ] || [ ! -f "$cursor_bin" ]; then
|
||||||
|
echo "Cursor CLI (agent) не найден. Устанавливаю..."
|
||||||
|
curl -fsSL https://cursor.com/install -fsS | bash
|
||||||
|
cursor_bin="$HOME/.local/bin/agent"
|
||||||
|
[ ! -f "$cursor_bin" ] && cursor_bin="$(command -v agent 2>/dev/null)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$cursor_bin" ] || [ ! -f "$cursor_bin" ]; then
|
||||||
|
echo "Ошибка: не удалось установить Cursor CLI."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/ai-api-helpers.sh" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Cursor CLI нативно подтягивает ~/.cursorrules и проектные правила.
|
||||||
|
# Обновляем ~/.cursorrules актуальными глобальными правилами (как AGENTS.md для codex).
|
||||||
|
_build_ai_sys_prompt > /dev/null
|
||||||
|
|
||||||
|
# --force (alias --yolo) — авто-одобрение правок и команд, аналог
|
||||||
|
# --dangerously-skip-permissions. Логин аккаунтом Cursor (OAuth при первом запуске);
|
||||||
|
# опционально CURSOR_API_KEY прокидывается через env для headless/скриптов.
|
||||||
|
"$cursor_bin" --force "$@"
|
||||||
|
cursor_exit=$?
|
||||||
|
(
|
||||||
|
afplay /System/Library/Sounds/Glass.aiff 2>/dev/null ||
|
||||||
|
paplay /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null ||
|
||||||
|
pw-play --volume=1 /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null ||
|
||||||
|
aplay /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null
|
||||||
|
) &
|
||||||
|
exit $cursor_exit
|
||||||
|
CURSOREOF
|
||||||
|
chmod +x "$BIN_DIR/ai-cursor"
|
||||||
|
# Подменяем путь к agent, если BIN_DIR отличается от ~/.local/bin
|
||||||
|
[ "$BIN_DIR" != "$HOME/.local/bin" ] && sed "${SED_INPLACE[@]}" "s|\$HOME/\.local/bin|\$HOME/${BIN_DIR#$HOME/}|g" "$BIN_DIR/ai-cursor"
|
||||||
|
|
||||||
# === ai-claude ===
|
# === ai-claude ===
|
||||||
cat > "$BIN_DIR/ai-claude" << 'CLAUDEEOF'
|
cat > "$BIN_DIR/ai-claude" << 'CLAUDEEOF'
|
||||||
@@ -2276,20 +1976,6 @@ claude --dangerously-skip-permissions --system-prompt-file "$_PROMPT_FILE" "$@"
|
|||||||
OPENCODEEOF
|
OPENCODEEOF
|
||||||
chmod +x "$BIN_DIR/ai-opencode"
|
chmod +x "$BIN_DIR/ai-opencode"
|
||||||
|
|
||||||
# ── 8.5. Proxychains инъекция (только в режиме vless) ────────
|
|
||||||
if [ "$USE_VLESS" -eq 1 ]; then
|
|
||||||
info "Включаю proxychains4 в ai-лаунчеры..."
|
|
||||||
sed -i 's/^exec "\$codex_bin"/exec proxychains4 -f "\$HOME\/\.proxychains-xray\.conf" "\$codex_bin"/' "$BIN_DIR/ai-gpt"
|
|
||||||
sed -i 's/^claude --dangerously-skip-permissions/proxychains4 -f "\$HOME\/\.proxychains-xray\.conf" claude --dangerously-skip-permissions/' "$BIN_DIR/ai-deepseek"
|
|
||||||
sed -i 's/^claude --dangerously-skip-permissions/proxychains4 -f "\$HOME\/\.proxychains-xray\.conf" claude --dangerously-skip-permissions/' "$BIN_DIR/ai-kimi"
|
|
||||||
sed -i 's/^claude --dangerously-skip-permissions/proxychains4 -f "\$HOME\/\.proxychains-xray\.conf" claude --dangerously-skip-permissions/' "$BIN_DIR/ai-glm"
|
|
||||||
sed -i 's/^claude --dangerously-skip-permissions/proxychains4 -f "\$HOME\/\.proxychains-xray\.conf" claude --dangerously-skip-permissions/' "$BIN_DIR/ai-openrouter"
|
|
||||||
sed -i 's|^\([[:space:]]*\)"\$agy_bin" --dangerously-skip-permissions "\$@"|\1proxychains4 -f "\$HOME/.proxychains-xray.conf" "\$agy_bin" --dangerously-skip-permissions "\$@"|' "$BIN_DIR/ai-gemini"
|
|
||||||
sed -i 's/^claude --dangerously-skip-permissions/proxychains4 -f "\$HOME\/\.proxychains-xray\.conf" claude --dangerously-skip-permissions/' "$BIN_DIR/ai-claude"
|
|
||||||
sed -i 's/^claude --dangerously-skip-permissions/proxychains4 -f "\$HOME\/\.proxychains-xray\.conf" claude --dangerously-skip-permissions/' "$BIN_DIR/ai-opencode"
|
|
||||||
success "proxychains4 интегрирован"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Подготавливаем изолированные config dir'ы для Claude Code-лаунчеров,
|
# Подготавливаем изолированные config dir'ы для Claude Code-лаунчеров,
|
||||||
# чтобы hooks (/switch-account, /add-account) и settings.json были на месте
|
# чтобы hooks (/switch-account, /add-account) и settings.json были на месте
|
||||||
# ещё до первого запуска пользователем.
|
# ещё до первого запуска пользователем.
|
||||||
@@ -2307,7 +1993,7 @@ rm -f "$BIN_DIR/claude_gpt" "$BIN_DIR/claude_deepseek" "$BIN_DIR/claude_kimi" "$
|
|||||||
if [ "$BIN_DIR" != "$HOME/.local/bin" ]; then
|
if [ "$BIN_DIR" != "$HOME/.local/bin" ]; then
|
||||||
warn "BIN_DIR=$BIN_DIR — удаляю старые скрипты из ~/.local/bin/ ..."
|
warn "BIN_DIR=$BIN_DIR — удаляю старые скрипты из ~/.local/bin/ ..."
|
||||||
rm -f "$HOME/.local/bin/ai-gpt" "$HOME/.local/bin/ai-deepseek" "$HOME/.local/bin/ai-kimi" \
|
rm -f "$HOME/.local/bin/ai-gpt" "$HOME/.local/bin/ai-deepseek" "$HOME/.local/bin/ai-kimi" \
|
||||||
"$HOME/.local/bin/ai-glm" "$HOME/.local/bin/ai-openrouter" "$HOME/.local/bin/ai-gemini" "$HOME/.local/bin/ai-claude" \
|
"$HOME/.local/bin/ai-glm" "$HOME/.local/bin/ai-openrouter" "$HOME/.local/bin/ai-gemini" "$HOME/.local/bin/ai-cursor" "$HOME/.local/bin/ai-claude" \
|
||||||
"$HOME/.local/bin/ai-opencode" \
|
"$HOME/.local/bin/ai-opencode" \
|
||||||
"$HOME/.local/bin/ai-api-helpers.sh" "$HOME/.local/bin/claude-gpt-effort-proxy.py"
|
"$HOME/.local/bin/ai-api-helpers.sh" "$HOME/.local/bin/claude-gpt-effort-proxy.py"
|
||||||
fi
|
fi
|
||||||
@@ -2326,13 +2012,14 @@ echo " На базе Claude Code:"
|
|||||||
echo -e " ${CYAN}ai-claude${NC} - Оригинальный Claude Code (Anthropic)"
|
echo -e " ${CYAN}ai-claude${NC} - Оригинальный Claude Code (Anthropic)"
|
||||||
echo -e " ${CYAN}ai-deepseek${NC} - DeepSeek (через Claude Code, API ключ сохраняется)"
|
echo -e " ${CYAN}ai-deepseek${NC} - DeepSeek (через Claude Code, API ключ сохраняется)"
|
||||||
echo -e " ${CYAN}ai-kimi${NC} - Kimi K2.7 Code (через Claude Code, API ключ сохраняется)"
|
echo -e " ${CYAN}ai-kimi${NC} - Kimi K2.7 Code (через Claude Code, API ключ сохраняется)"
|
||||||
echo -e " ${CYAN}ai-glm${NC} - GLM 5.2 (через Claude Code, Zhipu AI API)"
|
echo -e " ${CYAN}ai-glm${NC} - GLM 5.3 (через Claude Code, Zhipu AI API)"
|
||||||
echo -e " ${CYAN}ai-openrouter${NC} - OpenRouter (через Claude Code: GPT-5.5, Grok, Qwen, GLM 5.2 и др.)"
|
echo -e " ${CYAN}ai-openrouter${NC} - OpenRouter (через Claude Code: GPT-5.5, Grok, Qwen, GLM 5.2 и др.)"
|
||||||
echo -e " ${CYAN}ai-opencode${NC} - OpenCode Go (через Claude Code + routatic-proxy: DeepSeek, Kimi, Qwen, GLM, MiniMax, MiMo)"
|
echo -e " ${CYAN}ai-opencode${NC} - OpenCode Go (через Claude Code + routatic-proxy: DeepSeek, Kimi, Qwen, GLM, MiniMax, MiMo)"
|
||||||
echo ""
|
echo ""
|
||||||
echo " Нативные CLI:"
|
echo " Нативные CLI:"
|
||||||
echo -e " ${CYAN}ai-gpt${NC} - OpenAI Codex (нативный CLI, автоустановка)"
|
echo -e " ${CYAN}ai-gpt${NC} - OpenAI Codex (нативный CLI, автоустановка)"
|
||||||
echo -e " ${CYAN}ai-gemini${NC} - Gemini (нативный agy CLI, автоустановка)"
|
echo -e " ${CYAN}ai-gemini${NC} - Gemini (нативный agy CLI, автоустановка)"
|
||||||
|
echo -e " ${CYAN}ai-cursor${NC} - Cursor (нативный Cursor CLI, автоустановка)"
|
||||||
echo ""
|
echo ""
|
||||||
echo " Android CLI:"
|
echo " Android CLI:"
|
||||||
echo -e " ${CYAN}android${NC} - Google Android CLI (SDK, эмулятор, проекты)"
|
echo -e " ${CYAN}android${NC} - Google Android CLI (SDK, эмулятор, проекты)"
|
||||||
|
|||||||
1909
scripts/fuck-rkn.sh
1909
scripts/fuck-rkn.sh
File diff suppressed because it is too large
Load Diff
104
setup.sh
104
setup.sh
@@ -3,6 +3,11 @@
|
|||||||
# Мастер-скрипт. Запускай от обычного пользователя (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'
|
||||||
@@ -13,20 +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)" \
|
|
||||||
"9" "VLESS: настроить встроенный прокси (включить / отключить)" \
|
|
||||||
3>&1 1>&2 2>&3) || exit 0
|
|
||||||
echo ""
|
echo ""
|
||||||
else
|
else
|
||||||
echo ""
|
echo ""
|
||||||
@@ -38,36 +77,39 @@ else
|
|||||||
echo -e " ${GRY}Устанавливает ai-claude, ai-gpt, ai-deepseek, ai-kimi, ai-glm, 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 ""
|
||||||
echo -e " ${BLD}8) Android CLI${CLR}"
|
echo -e " ${BLD}8) Android CLI${CLR}"
|
||||||
echo -e " ${GRY}Установить Google Android CLI для Android-разработки.${CLR}"
|
echo -e " ${GRY}Установить Google Android CLI для Android-разработки.${CLR}"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e " ${BLD}9) VLESS${CLR}"
|
echo -n "Выбери [1-8] или Enter для выхода: "
|
||||||
echo -e " ${GRY}Включить или отключить встроенный VLESS-прокси для AI API запросов.${CLR}"
|
|
||||||
echo ""
|
|
||||||
echo -n "Выбери [1-9] или Enter для выхода: "
|
|
||||||
read -r choice
|
read -r choice
|
||||||
echo ""
|
echo ""
|
||||||
fi
|
fi
|
||||||
@@ -76,9 +118,10 @@ fi
|
|||||||
|
|
||||||
case "$choice" in
|
case "$choice" in
|
||||||
1)
|
1)
|
||||||
AI_SETUP_VLESS=skip 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}"
|
||||||
@@ -135,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
|
||||||
@@ -142,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) подключена"
|
||||||
@@ -177,6 +223,12 @@ 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-glm ai-openrouter ai-opencode 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
|
||||||
@@ -220,28 +272,14 @@ 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}"
|
|
||||||
AI_SETUP_VLESS=skip bash "$REPO_DIR/scripts/ai-setup.sh"
|
|
||||||
;;
|
;;
|
||||||
8)
|
8)
|
||||||
AI_SETUP_VLESS=skip bash scripts/ai-setup.sh
|
bash scripts/ai-setup.sh
|
||||||
;;
|
|
||||||
9)
|
|
||||||
echo -n "Включить VLESS-прокси? [Y/n] "
|
|
||||||
read -r _vless_choice
|
|
||||||
_vless_choice="${_vless_choice:-Y}"
|
|
||||||
if [[ "$_vless_choice" =~ ^[Yy]$ ]]; then
|
|
||||||
AI_SETUP_VLESS=1 bash scripts/ai-setup.sh
|
|
||||||
else
|
|
||||||
AI_SETUP_VLESS=0 bash scripts/ai-setup.sh
|
|
||||||
fi
|
|
||||||
;;
|
;;
|
||||||
"")
|
"")
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
@@ -7,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)); }
|
||||||
@@ -18,7 +19,9 @@ KIMI_SECTION=$(awk '/^cat > "\$BIN_DIR\/ai-kimi"/,/^KIMIEOF/' "$SCRIPT")
|
|||||||
GLM_SECTION=$(awk '/^cat > "\$BIN_DIR\/ai-glm"/,/^GLMEOF/' "$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")
|
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() {
|
||||||
@@ -38,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' \
|
||||||
@@ -82,12 +94,13 @@ test_glm_claude_launcher() {
|
|||||||
# ── ai-glm: uses official Zhipu AI API ────────────────────────────────────
|
# ── ai-glm: uses official Zhipu AI API ────────────────────────────────────
|
||||||
test_glm_official_api() {
|
test_glm_official_api() {
|
||||||
if echo "$GLM_SECTION" | grep -q 'api.z.ai/api/anthropic' \
|
if echo "$GLM_SECTION" | grep -q 'api.z.ai/api/anthropic' \
|
||||||
&& echo "$GLM_SECTION" | grep -q 'ANTHROPIC_DEFAULT_OPUS_MODEL=glm-5.2' \
|
&& echo "$GLM_SECTION" | grep -q 'ANTHROPIC_DEFAULT_OPUS_MODEL=glm-5.3' \
|
||||||
&& echo "$GLM_SECTION" | grep -q "ANTHROPIC_DEFAULT_HAIKU_MODEL='glm-5.2\[1m\]'" \
|
&& echo "$GLM_SECTION" | grep -q 'ANTHROPIC_DEFAULT_HAIKU_MODEL=glm-5.3' \
|
||||||
|
&& echo "$GLM_SECTION" | grep -q 'CLAUDE_CODE_MAX_CONTEXT_TOKENS=1000000' \
|
||||||
&& echo "$GLM_SECTION" | grep -q 'x-api-key'; then
|
&& echo "$GLM_SECTION" | grep -q 'x-api-key'; then
|
||||||
ok "ai-glm: uses official Zhipu AI API (glm-5.2 opus/sonnet, glm-5.2[1m] haiku)"
|
ok "ai-glm: uses official Zhipu AI API (glm-5.3 everywhere, ctx window 1M via env)"
|
||||||
else
|
else
|
||||||
fail "ai-glm: must use official Zhipu AI API with glm-5.2 and x-api-key"
|
fail "ai-glm: must use official Zhipu AI API with glm-5.3, 1M context window and x-api-key"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,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
|
||||||
@@ -177,23 +204,6 @@ test_android_symlink_to_bin_dir() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
test_no_proxychains_for_android() {
|
|
||||||
if grep -q 'proxychains4.*android' "$SCRIPT"; then
|
|
||||||
fail "android-cli: should not be wrapped in proxychains4 (breaks child processes)"
|
|
||||||
else
|
|
||||||
ok "android-cli: not wrapped in proxychains4"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
test_android_skill_exists() {
|
|
||||||
skill_file="$(cd "$(dirname "$0")/.." && pwd)/home-configs/claude/skills/android-cli/SKILL.md"
|
|
||||||
if [ -f "$skill_file" ]; then
|
|
||||||
ok "android-cli: custom skill file exists"
|
|
||||||
else
|
|
||||||
fail "android-cli: custom skill file is missing"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── ai-opencode: launches claude with routatic-proxy backend ──────────────────
|
# ── ai-opencode: launches claude with routatic-proxy backend ──────────────────
|
||||||
test_opencode_claude_launcher() {
|
test_opencode_claude_launcher() {
|
||||||
if echo "$OPENCODE_SECTION" | grep -q 'claude --dangerously-skip-permissions' \
|
if echo "$OPENCODE_SECTION" | grep -q 'claude --dangerously-skip-permissions' \
|
||||||
@@ -237,28 +247,78 @@ test_opencode_models() {
|
|||||||
fi
|
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
|
||||||
|
}
|
||||||
|
|
||||||
# ── run all tests ─────────────────────────────────────────────────────────────
|
# ── run all tests ─────────────────────────────────────────────────────────────
|
||||||
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_claude_launcher
|
||||||
test_glm_official_api
|
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_android_skill_exists
|
|
||||||
test_opencode_claude_launcher
|
test_opencode_claude_launcher
|
||||||
test_opencode_routatic_proxy
|
test_opencode_routatic_proxy
|
||||||
test_opencode_key_management
|
test_opencode_key_management
|
||||||
test_opencode_models
|
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"
|
||||||
|
|||||||
Reference in New Issue
Block a user