Compare commits
2 Commits
89fbfec1b8
...
3220238c67
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3220238c67 | ||
|
|
265d99b378 |
@@ -4,20 +4,21 @@ description: OmniRoute v3.6.5 на LXC 132 (10.0.0.179) — AI-шлюз для 6
|
||||
type: project
|
||||
originSessionId: 9f26284f-db92-456f-813d-fd8210b7d7b6
|
||||
---
|
||||
## OmniRoute v3.6.5 (LXC 132, 10.0.0.179)
|
||||
## OmniRoute v3.6.9+ (LXC 132, 10.0.0.179)
|
||||
|
||||
- **Путь**: /root/OmniRoute/, Node.js 22, Next.js 16.2, git repo (github.com/diegosouzapw/OmniRoute)
|
||||
- **Dashboard**: http://10.0.0.179:20128, пароль: 1qaz!QAZ
|
||||
- **API**: http://10.0.0.179:20128/v1
|
||||
- **API Key**: sk-225e902dc95ff192-6bdad7-3ec8cdc6
|
||||
- **API**: http://10.0.0.179:20128/v1 (или https://ai.dttb.ru/v1 публично через NPM с Bearer)
|
||||
- **API Key**: `sk-225e902dc95ff192-6bdad7-3ec8cdc6` (test-key) / `sk-225e902dc95ff192-e71c19-7cc9283e` (claw)
|
||||
- **SQLite DB**: /root/.omniroute/storage.sqlite
|
||||
- **Service**: omniroute.service (systemd, enabled), запускает **npx-кэш** (НЕ git repo)
|
||||
- **ExecStart**: `/usr/bin/node /root/.npm/_npx/<hash>/node_modules/omniroute/app/server.js` (hash меняется при обновлении)
|
||||
- **Конфиг**: /root/OmniRoute/.env (секреты сгенерированы)
|
||||
- **Каталог моделей**: 145+ моделей (на 2026-05-06)
|
||||
|
||||
### SSH доступ к LXC 132
|
||||
- **Прямой SSH (root@10.0.0.179) НЕ РАБОТАЕТ** — пароль 1qaz!QAZ не подходит, ключ claude-code отсутствует
|
||||
- **Доступ через Proxmox**: `sshpass -p '1qaz!QAZ' ssh root@10.0.0.250 "pct exec 132 -- bash -c 'COMMAND'"` — РАБОТАЕТ
|
||||
- **Прямой SSH** `ssh root@10.0.0.179` — РАБОТАЕТ если на машине есть claude-code ключ (на Mac Олега есть)
|
||||
- **Через Proxmox**: `sshpass -p '1qaz!QAZ' ssh root@10.0.0.250 "pct exec 132 -- bash -c 'COMMAND'"` — РАБОТАЕТ всегда
|
||||
- **Hostname**: code-server
|
||||
|
||||
### Обновление OmniRoute (npx способ — используется сервисом)
|
||||
@@ -30,18 +31,34 @@ originSessionId: 9f26284f-db92-456f-813d-fd8210b7d7b6
|
||||
### Обновление git-репозитория (отдельно, для разработки)
|
||||
1. `cd /root/OmniRoute && git pull origin main && npm install && npm run build`
|
||||
|
||||
### Подключённые провайдеры (2026-04-11)
|
||||
| Провайдер | Аккаунт | Модели | Статус |
|
||||
|-----------|---------|--------|--------|
|
||||
| Codex (OpenAI) | batlaew@gmail.com (free plan) | gpt-5.4, gpt-5.4-mini | Работает, refresh проблема |
|
||||
| Claude | OAuth | claude-opus-4-6, sonnet | Токен есть, refresh проблема |
|
||||
| Kiro (AWS) | OAuth | claude-sonnet-4.5, haiku | Токен есть, refresh проблема |
|
||||
### Подключённые провайдеры (актуально 2026-05-06)
|
||||
| Префикс | Провайдер | Модели Opus | Статус |
|
||||
|---------|-----------|-------------|--------|
|
||||
| **`cc/`** | Claude Code OAuth (Max-подписка) | **claude-opus-4-7**, opus-4-6, sonnet-4-6, sonnet-4-5, haiku-4-5 | ✅ Primary path к Opus |
|
||||
| `claude/` | прямой Anthropic API (платный) | claude-opus-4-7, opus-4-6, sonnet-4-6, haiku-4-5 | ✅ Резерв per-token |
|
||||
| `kr/` | Kiro/AWS Bedrock | sonnet-4.5 (Opus НЕТ) | ✅ для Sonnet, ❌ Opus |
|
||||
| `cx/` | Codex (OpenAI free) | gpt-5.4, gpt-5.4-mini, gpt-5.5 | ✅ для GPT |
|
||||
| `gh/` | GitHub Copilot integrator (MS) | sonnet-4.5/4.6, ~~opus-4.6~~, ~~opus-4.7~~ | ⚠️ Sonnet работает, Opus сломан (см. ниже) |
|
||||
| `antigravity/` | Google Antigravity | opus-4-6-thinking | ❌ Нужен OAuth reconnect (projectId пуст) |
|
||||
| `kc/` `kilocode/` | KiloCode aggregator | opus-4.7 | ❌ Empty response |
|
||||
| `glm/` | Zhipu Z.ai | glm-5.1 | ✅ |
|
||||
|
||||
### Главный путь к Opus 4.7 — `omniroute/cc/claude-opus-4-7`
|
||||
|
||||
Через **CLIProxy + Claude Code OAuth + Max-подписку**. Фиксированная стоимость, без per-token. Подтверждено 2026-05-06: smoke-test вернул корректный ответ от `claude-opus-4-7`. См. [[../decisions/2026-05-06-openclaw-opus-4-7-via-max-cliproxy]].
|
||||
|
||||
### Известные сломы
|
||||
|
||||
- **`gh/claude-opus-4.6`**: MS Copilot integrator убрал Opus 4.6 из своего scope, отдаёт «model not available for integrator vscode-chat»
|
||||
- **`gh/claude-opus-4.7`**: ID mismatch — OmniRoute шлёт `4.7`, MS ждёт `4-7` (точка vs дефис). Баг текущей версии OmniRoute. Workaround: использовать `cc/claude-opus-4-7` пока не починят.
|
||||
- **`antigravity/*`**: возвращает `"Missing Google projectId for Antigravity account. Please reconnect OAuth in Providers → Antigravity"`. Триггер из [[user_profile]]: до OAuth нужен вход на antigravity.google и создание Cloud Code project.
|
||||
|
||||
### Использование
|
||||
- Prefix модели: `cx/gpt-5.4` (Codex), `cc/claude-opus-4-6` (Claude)
|
||||
- 181 модель доступна через API (на 2026-04-14)
|
||||
- Codex OAuth: callback на localhost:1455 (нужен SSH-туннель для удалённого доступа)
|
||||
- Token refresh: требует периодическую перелогинку OAuth
|
||||
|
||||
- Prefix модели в API: `cc/claude-opus-4-7`, `kr/claude-sonnet-4.5`, `cx/gpt-5.4`
|
||||
- Codex OAuth callback: localhost:1455 (нужен SSH-туннель для удалённого онбординга)
|
||||
- Token refresh: периодическая перелогинка OAuth — проверять Dashboard → Providers
|
||||
- Health-check moniter: на LXC 132 в `journalctl -u omniroute -f` смотреть USAGE/STREAM строки
|
||||
|
||||
### Важно
|
||||
- OAuth Codex привязан к localhost:1455 — для удалённого доступа: `ssh -L 20128:localhost:20128 -L 1455:localhost:1455 root@10.0.0.179`
|
||||
|
||||
@@ -125,6 +125,25 @@ Grizzlysms TJ-pool **полностью забанен** Apple на текущи
|
||||
|
||||
**Когда НЕ подходит:** долгосрочное хранение Apple Cash, привязка карт с большим балансом, использование как primary iCloud (там этот US Apple ID лучше не использовать вообще, только App Store).
|
||||
|
||||
## Статус на 2026-05-06
|
||||
|
||||
**Прогресс:**
|
||||
- ✅ IPRoyal Royal Residential куплен (1 GB pay-as-you-go), TJ residential подтверждён рабочим (см. [[../snippets/iproyal-gost-relay]])
|
||||
- ✅ Локальный gost-relay отлажен (порт 9999 для TJ, 9998 для FI), FoxyProxy multi-port
|
||||
- ✅ Firefox настроен: `intl.accept_languages = tg-TJ`, WebRTC отключён, приватное окно
|
||||
- ✅ Apple footer показывает «Tajikistan» при правильной комбинации IP + locale + cookies-чистка
|
||||
- ✅ US Adidas-аккаунт куплен (`hbuggle819@icloud.com`), 2FA через trusted devices, карта привязана
|
||||
- ⚠️ **Сложности с virtual SMS** — grizzlysms TJ-pool забанен Apple, требуются альтернативы (5sim, smsbower) или реальная TJ SIM
|
||||
- ⚠️ **Митинский радиорынок «высох»** (2026-05-04 проверено лично): продажа TJ-SIM мигрантам сократилась после ужесточения ФЗ-178. Альтернативные источники: Avito с доставкой (1500-3500 ₽), Telegram-каналы, знакомые в TJ
|
||||
- 🔴 **Регион US Adidas пока не менялся на TJ** — ждём реальную TJ-SIM либо premium DID. Аккаунт пока используется только для скачивания приложений.
|
||||
|
||||
**Текущий план:**
|
||||
1. Найти реальную TJ SIM-карту (Avito / Telegram / знакомые)
|
||||
2. После получения SIM — менять регион Adidas-аккаунта на TJ через TJ-IP (gost), привязка к новой TJ-карте
|
||||
3. До этого момента — US Adidas-аккаунт используется только в App Store на iPhone для скачивания заблокированных в RU приложений (по схеме [[../snippets/apple-id-us-on-russia]])
|
||||
|
||||
**Параллельная находка:** Google гораздо строже Apple к virtual SMS — на TJ-IP при регистрации нового @gmail сразу требует QR-верификацию через trusted device. Web-регистрация Google под TJ через grizzlysms — путь в тупик, требует Android-эмулятор или готовый купленный аккаунт.
|
||||
|
||||
## Связанные
|
||||
|
||||
- [[../README]] — индекс vault
|
||||
|
||||
72
decisions/2026-05-05-mac-dictation-groq-hammerspoon.md
Normal file
72
decisions/2026-05-05-mac-dictation-groq-hammerspoon.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# 2026-05-05 — Mac dictation: Hammerspoon + Groq Whisper
|
||||
|
||||
## Контекст
|
||||
Олег на Intel MacBook Pro (i9-9880H, 2019), нужна голосовая диктовка для русского. Все современные приложения (superwhisper PRO, VoiceInk, Spokenly Parakeet) либо требуют Apple Silicon, либо платную PRO-лицензию ($150-250). Бесплатный free-tier у superwhisper исчерпан и валидируется на их сервере (см. [feedback_superwhisper_no_license](../../.claude/projects/-Users-ai-knowledge-base/memory/feedback_superwhisper_no_license.md)).
|
||||
|
||||
## Решение
|
||||
Свой скрипт + Hammerspoon + Groq Whisper API:
|
||||
- **Hammerspoon** ловит глобальный hotkey `⌘⇧D`
|
||||
- **Bash скрипт** toggle-режим: первый запуск → ffmpeg запись с микрофона; второй → стоп → POST в Groq → текст в `/tmp/groq-dictate.last`
|
||||
- **Lua callback** в Hammerspoon читает файл, кладёт в pasteboard, нажимает Cmd+V через `hs.eventtap.keyStroke`
|
||||
- **Groq Whisper-large-v3-turbo** — бесплатно ~14400 запросов/день, 0.5с на 4-сек запись, RU IP не блочится
|
||||
|
||||
## Файлы
|
||||
- `~/bin/groq-dictate.sh` — скрипт записи + Groq POST + **fallback на whisper-cpp** + write to `/tmp/groq-dictate.last`
|
||||
- `~/bin/dictation-doctor.sh` — health-check всех компонентов (Hammerspoon / TCC / зависимости / Groq / mic / Fn-key); запускать когда «не работает»
|
||||
- `~/.hammerspoon/init.lua` — Fn (одиночное нажатие) trigger через eventtap, paste через `hs.eventtap.keyStroke`
|
||||
- `~/.cache/whisper-cpp/ggml-tiny-q5_1.bin` — 31MB локальная модель для offline fallback
|
||||
- Groq API key — в `reference_groq_api.md` private memory
|
||||
|
||||
## Финальный hotkey
|
||||
**Fn (Globe) одиночное нажатие** — toggle (старт/стоп). Срабатывает быстрее ⌘⇧D, освобождает руки. Apple Dictation на двойное Fn остаётся (если не отключить «Нажатие клавиши Fn» в System Settings → Keyboard).
|
||||
|
||||
## Критические грабли (и фиксы)
|
||||
1. **Hammerspoon Accessibility кеширует статус** — после Enable в System Settings нужен **`killall Hammerspoon && open -a Hammerspoon`**, иначе Hammerspoon продолжает показывать WARNING и hotkey не работает.
|
||||
|
||||
2. **`hs.hotkey.bind({"cmd","shift"}, "d", ...)`** на русской раскладке выдаёт warning `key 'd' not found in active keymap; using ANSI-standard US keyboard layout as fallback, returning '2'`. Решение: **биндить по числовому keycode** — `hs.hotkey.bind({"cmd","shift"}, 2, ...)` (2 = физическая клавиша D). Так работает на любой раскладке.
|
||||
|
||||
3. **`osascript -e 'tell application "System Events" to keystroke "v" using command down'`** на русской раскладке вместо Cmd+V вставляет UTF-8 байты текста как символы → получается мусор типа `—В—ь—А—∞—ь—В?` для строки «Ты меня слышишь?». Решение: **никогда не использовать `keystroke` для paste**. Использовать `hs.eventtap.keyStroke({"cmd"}, "v")` напрямую из Lua (отправляет настоящий low-level KeyDown event).
|
||||
|
||||
4. **Toggle через PID-файл** — `/tmp/groq-dictate.pid`. Если процесс упал/убит — удалить руками. Скрипт устойчив: `kill -INT` корректно закрывает .wav, ждёт до 0.5с дописать заголовок.
|
||||
|
||||
5. **ffmpeg avfoundation `:0`** = default mic. Если нужен другой — `ffmpeg -f avfoundation -list_devices true -i ""`.
|
||||
|
||||
## Стоимость
|
||||
- Hammerspoon бесплатный
|
||||
- Groq бесплатный (14400 req/day, ~120 минут диктовки в день — намного больше нужного)
|
||||
- Итого: 0₽
|
||||
|
||||
## Альтернативы которые НЕ подошли
|
||||
- **superwhisper** — free tier 530 сек, потом сервер бракует
|
||||
- **VoiceInk** — официально Apple Silicon only, на Intel CPU крутит большую модель часами
|
||||
- **Spokenly** — `cdn.spokenly.app` блокирует RU IP (3.8 KB/s), Parakeet требует Neural Engine
|
||||
- **Wispr Flow** — `dl.wisprflow.com` блокирует RU IP, плюс $144/год Pro для регулярного использования
|
||||
- **MacWhisper** — $59 lifetime + не пробовали (всё уже работало бесплатно)
|
||||
- **OpenWhispr** — 273MB dmg в GitHub, не докачался
|
||||
|
||||
## Связано
|
||||
- [RU-заблокированные сервисы](../notes/ru-geoblocked-services.md)
|
||||
- [Groq API](../../.claude/projects/-Users-ai-knowledge-base/memory/reference_groq_api.md)
|
||||
- [Superwhisper — нет PRO](../../.claude/projects/-Users-ai-knowledge-base/memory/feedback_superwhisper_no_license.md)
|
||||
|
||||
## Воспроизведение на новом Mac
|
||||
```bash
|
||||
brew install --cask hammerspoon
|
||||
brew install whisper-cpp jq ffmpeg
|
||||
mkdir -p ~/bin ~/.hammerspoon ~/.cache/whisper-cpp
|
||||
|
||||
# скопировать ~/bin/groq-dictate.sh + ~/bin/dictation-doctor.sh → chmod +x
|
||||
# скопировать ~/.hammerspoon/init.lua
|
||||
|
||||
# скачать локальную fallback-модель (31 MB, GitHub не блочит RU)
|
||||
curl -sSL -o ~/.cache/whisper-cpp/ggml-tiny-q5_1.bin \
|
||||
"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny-q5_1.bin?download=true"
|
||||
|
||||
open -a Hammerspoon
|
||||
# Hammerspoon Preferences → Enable Accessibility → System Settings → включить
|
||||
killall Hammerspoon && open -a Hammerspoon # ОБЯЗАТЕЛЬНО после grant — кеш
|
||||
|
||||
# System Settings → Клавиатура → Нажатие клавиши Fn → "Действие не требуется"
|
||||
~/bin/dictation-doctor.sh # должно быть всё зелёное
|
||||
```
|
||||
Готово. **Fn → говори → Fn** — текст вставится в активное окно.
|
||||
118
decisions/2026-05-06-openclaw-opus-4-7-via-max-cliproxy.md
Normal file
118
decisions/2026-05-06-openclaw-opus-4-7-via-max-cliproxy.md
Normal file
@@ -0,0 +1,118 @@
|
||||
---
|
||||
date: 2026-05-06
|
||||
type: decision
|
||||
tags: [decision, openclaw, omniroute, claude, opus, cliproxy, max-plan]
|
||||
---
|
||||
|
||||
# 2026-05-06: openclaw перешёл на Claude Opus 4.7 через Max-подписку (cc/* в OmniRoute)
|
||||
|
||||
## Контекст
|
||||
|
||||
Искали стабильный источник Claude Opus 4.6/4.7 для openclaw — обсуждали построение Workspace-фарма на новом домене с пулом аккаунтов под Antigravity. Перед тем как затевать фарм ($12/домен + время на регистрации + риск массового бана), проверили что фактически работает в текущей OmniRoute.
|
||||
|
||||
## Что обнаружили в OmniRoute (10.0.0.179:20128)
|
||||
|
||||
В каталоге **17 моделей с opus в id**, но реально пингуются только три:
|
||||
|
||||
| Model ID | Источник | Статус |
|
||||
|---|---|---|
|
||||
| **`cc/claude-opus-4-7`** | Claude Code OAuth (Max-подписка Олега) | ✅ Работает |
|
||||
| `cc/claude-opus-4-6` | то же | ✅ Работает |
|
||||
| `claude/claude-opus-4-7` | прямой Anthropic API | ✅ Работает (платный per-token) |
|
||||
| `gh/claude-opus-4.6` | GitHub Copilot integrator MS | ❌ MS убрала Opus 4.6 из integrator scope, отдаёт «model not available» |
|
||||
| `gh/claude-opus-4.7` | то же | ❌ ID mismatch: OmniRoute шлёт `4.7`, MS ждёт `4-7` (баг mapping в текущей версии) |
|
||||
| `kr/claude-opus-4.6` / `4.7` | Kiro/AWS | ❌ Kiro Opus не выдаёт, только Sonnet |
|
||||
| `kiro/claude-opus-*` | то же | ❌ |
|
||||
| `kc/anthropic/claude-opus-4.7` | KiloCode | ❌ Empty response |
|
||||
| `kilocode/anthropic/claude-opus-4.7` | то же | ❌ |
|
||||
| `antigravity/claude-opus-4-6-thinking` | Google Antigravity | ❌ «Missing Google projectId — reconnect OAuth in Providers → Antigravity» |
|
||||
|
||||
## Решение
|
||||
|
||||
**Workspace-фарм для Antigravity не нужен.** У Олега уже есть рабочий Opus 4.7 через **`cc/claude-opus-4-7`** — это его собственная Max-подписка Anthropic, проксированная через CLIProxy в OmniRoute. Лучше любого фарма потому что:
|
||||
|
||||
- Нет per-token биллинга, фиксированная стоимость Max ($200/мес за Max20x)
|
||||
- Не нужны множественные аккаунты, recovery emails, virtual cards
|
||||
- Один источник = один ключ = простая интеграция
|
||||
- Нет риска массового бана как у фарм-аккаунтов
|
||||
- Самая свежая модель Opus 4.7 (новее чем 4.6 которая в фармовых Antigravity)
|
||||
|
||||
### Изменения в `/root/.openclaw/openclaw.json` (LXC 137)
|
||||
|
||||
**Было:**
|
||||
```json
|
||||
"agents.defaults.model": {
|
||||
"primary": "omniroute/kr/claude-sonnet-4.5",
|
||||
"fallbacks": [
|
||||
"omniroute/cc/claude-sonnet-4-6",
|
||||
"omniroute/cx/gpt-5.4"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Стало:**
|
||||
```json
|
||||
"agents.defaults.model": {
|
||||
"primary": "omniroute/cc/claude-opus-4-7",
|
||||
"fallbacks": [
|
||||
"omniroute/cc/claude-sonnet-4-6",
|
||||
"omniroute/kr/claude-sonnet-4.5",
|
||||
"omniroute/cx/gpt-5.4"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Также добавлена запись в `models.providers.omniroute.models[]`:
|
||||
```json
|
||||
{
|
||||
"id": "cc/claude-opus-4-7",
|
||||
"name": "Claude Opus 4.7 (CLIProxy/Max)",
|
||||
"reasoning": false,
|
||||
"input": ["text", "image"],
|
||||
"contextWindow": 200000,
|
||||
"maxTokens": 8192
|
||||
}
|
||||
```
|
||||
|
||||
Без этой записи openclaw fallback'нет на следующий — модель должна быть в каталоге провайдера.
|
||||
|
||||
### Применение
|
||||
|
||||
OpenClaw подхватил изменения **через hot-reload без рестарта** (видно в журнале: `[reload] config hot reload applied`). После я ещё раз сделал `systemctl --user restart openclaw-gateway.service` для чистоты, но это было необязательно.
|
||||
|
||||
В логах после старта:
|
||||
```
|
||||
[gateway] agent model: omniroute/cc/claude-opus-4-7
|
||||
[gateway] http server listening (7 plugins: ..., telegram; 8.6s)
|
||||
[telegram] [default] starting provider (@maxim_dttb_bot)
|
||||
[gateway] ready
|
||||
```
|
||||
|
||||
## Бэкап и rollback
|
||||
|
||||
Автобэкап перед изменениями: `/root/.openclaw/openclaw.json.bak.opus47-20260506-090832`
|
||||
|
||||
Rollback одной командой:
|
||||
```bash
|
||||
sshpass -p '1qaz!QAZ' ssh root@10.0.0.250 "pct exec 137 -- bash -c '
|
||||
cp /root/.openclaw/openclaw.json.bak.opus47-20260506-090832 /root/.openclaw/openclaw.json
|
||||
XDG_RUNTIME_DIR=/run/user/0 systemctl --user restart openclaw-gateway.service
|
||||
'"
|
||||
```
|
||||
|
||||
## Известные риски и мониторинг
|
||||
|
||||
1. **Max-подписка fair-use лимиты** — Anthropic размывает по нагрузке, точных цифр нет. Когда упрёмся — openclaw сам перейдёт на `cc/claude-sonnet-4-6` (видно в логах `model fallback decision`).
|
||||
2. **CLIProxy OAuth refresh** — может протухнуть. Мониторинг: `journalctl --user -u openclaw-gateway -f` на LXC 137. При ошибке 401 от Anthropic API → переподключить Claude Code OAuth в OmniRoute (Dashboard → Providers → Claude → Reconnect).
|
||||
3. **Скорость** — Opus 4.7 в среднем 2-5 сек медленнее Sonnet 4.5. Для интерактива в Telegram это норма.
|
||||
|
||||
## Параллельные находки
|
||||
|
||||
- **Antigravity** требует OAuth re-connect. По заметке `feedback_antigravity_onboarding.md`: открыть [antigravity.google](https://antigravity.google), залогиниться, создать Cloud Code project → projectId подтянется → `antigravity/claude-opus-4-6-thinking` оживёт. Сейчас не критично.
|
||||
- **`Unknown model: cx/gpt-5.4`** — каждые 30 минут в логах ошибка. Health-check тыкается в модель которой нет в каталоге `omniroute.models[]` openclaw. Не блокер — failover работает. Решение: либо добавить `cx/gpt-5.4` в каталог openclaw, либо убрать из health-check'ов.
|
||||
|
||||
## Связанные
|
||||
|
||||
- [[../claude-memory/omniroute]] — OmniRoute setup, провайдеры, версии
|
||||
- [[../projects/dttb/openclaw]] — справочник по openclaw
|
||||
- [[../snippets/clawdbot-cliproxy-config]] — старый шаблон CLIProxy
|
||||
30
notes/ru-geoblocked-services.md
Normal file
30
notes/ru-geoblocked-services.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Сервисы, заблокированные для RU IP
|
||||
|
||||
Реестр CDN/сайтов/сервисов, которые блокируют российские IP — чтобы не искать причину каждый раз. Обход — через NetBird `Trance` group → finland exit-node, либо через App Store (Apple не блочит RU аккаунты), либо через GitHub Releases (не блочит).
|
||||
|
||||
## Скачивание / CDN
|
||||
|
||||
| Сервис | Домен | Симптом | Обход |
|
||||
|---|---|---|---|
|
||||
| **superwhisper PRO API** | `*.superwhisper.com` (api endpoints) | Облачные модели возвращают пустой `result`, free tier 530 сек кончился | Купить PRO ($249) или альтернативы |
|
||||
| **Spokenly CDN** | `cdn.spokenly.app` | TCP качает ~150 Б/с, dmg на 21 MB → 36 мин не докачивается | App Store / NetBird finland / VoiceInk вместо |
|
||||
| **MS Windows Update / setup.exe** | `download.microsoft.com`, `dl.delivery.mp.microsoft.com` | Геоблок | NetBird `Trance` group → finland exit-node ([feedback_win11_unattended_upgrade](../../.claude/projects/-Users-ai-knowledge-base/memory/feedback_win11_unattended_upgrade.md)) |
|
||||
| **Apple ID TJ** | account.apple.com (region change) | KYC требует TJ-резидентства | IPRoyal residential proxy (см. [2026-05-02-apple-id-tj-via-residential-proxy](../decisions/2026-05-02-apple-id-tj-via-residential-proxy.md)) |
|
||||
| **НСПД (gov.ru, кадастр)** | `nspd.gov.ru`, customers_p2p_b16 | МТС B2B блокирует | NetBird route `2.63.246.0/24` → `pve-LionART` ([feedback_nspd_blocks_mts](../../.claude/projects/-Users-ai-knowledge-base/memory/feedback_nspd_blocks_mts.md)) |
|
||||
| **gov.ru разные** | `*.gov.ru` | Несколько классов: FakeIP, WAF-MTS, ru-trust, ГОСТ-mTLS, anti-bot | См. playbook `projects/niikn/govru-quickfix-playbook.md` |
|
||||
|
||||
## Принцип
|
||||
- Если при первом запуске любой `brew install --cask`, `curl`, `wget`, `npm install` падает по timeout с CDN — **сразу** проверять RU-блок (curl с timeout 5 + look at recv speed).
|
||||
- Не дёргать `--retry` / `-C -` — потеря времени.
|
||||
- Default решения: 1) App Store, 2) GitHub Releases, 3) NetBird finland exit-node, 4) IPRoyal residential.
|
||||
|
||||
## Проверка скорости (быстрый snippet)
|
||||
```bash
|
||||
curl -sS -o /dev/null --max-time 5 -w "HTTP %{http_code} speed=%{speed_download}B/s\n" <URL>
|
||||
```
|
||||
Если speed < 50 KB/s на CDN — почти наверняка геоблок/throttle.
|
||||
|
||||
## Альтернативы по категориям
|
||||
- **Mac dictation app** (вместо superwhisper / Spokenly из CDN): VoiceInk из GitHub Releases (open source, $40 lifetime или собрать самому), Apple Dictation встроенная.
|
||||
- **Win update** (MS блок): NetBird Trance → finland.
|
||||
- **gov.ru**: см. govru playbook.
|
||||
100
snippets/iproyal-gost-relay.md
Normal file
100
snippets/iproyal-gost-relay.md
Normal file
@@ -0,0 +1,100 @@
|
||||
---
|
||||
date: 2026-05-06
|
||||
type: snippet
|
||||
tags: [proxy, iproyal, gost, foxyproxy, apple, google]
|
||||
---
|
||||
|
||||
# IPRoyal Royal Residential через локальный gost-relay
|
||||
|
||||
Постоянный «вечный» способ использовать IPRoyal residential без проблем с FoxyProxy (он режет длинный пароль с password-модификаторами `_country-X_session-Y_lifetime-Z`). Решение: запускаем локальный `gost` который слушает 127.0.0.1:PORT и форвардит на IPRoyal с правильным паролем зашитым в команду. Браузер тогда указывает только `127.0.0.1:PORT` без auth.
|
||||
|
||||
## Установка
|
||||
|
||||
```bash
|
||||
brew install gost # macOS, ~1 мин
|
||||
# или
|
||||
apt install golang && go install github.com/go-gost/gost/cmd/gost@latest
|
||||
```
|
||||
|
||||
## Базовая команда
|
||||
|
||||
```bash
|
||||
gost -L "http://127.0.0.1:PORT" \
|
||||
-F "http://USER:PASS_country-XX_session-LABEL_lifetime-30m@geo.iproyal.com:12321"
|
||||
```
|
||||
|
||||
Где:
|
||||
- `PORT` — локальный порт для FoxyProxy (выбираешь сам, например 9999)
|
||||
- `USER` / `PASS` — base creds из IPRoyal Dashboard → Royal Residential → Proxy List
|
||||
- `XX` — ISO-код страны **lowercase** (`tj`, `fi`, `us`, `de`...). `country-Tajikistan` НЕ работает.
|
||||
- `LABEL` — любая метка sticky-сессии (одна метка = один и тот же IP в течение `lifetime`)
|
||||
- `lifetime-30m` — IP не меняется 30 минут. Доступно `1m`-`120m`.
|
||||
|
||||
## Twin-port схема (рекомендуется)
|
||||
|
||||
Запусти **два gost одновременно** на разных портах для разных стран — переключаешься между ними в FoxyProxy одной кнопкой:
|
||||
|
||||
```bash
|
||||
# Порт 9999 → TJ (для Apple ID)
|
||||
gost -L "http://127.0.0.1:9999" \
|
||||
-F "http://OYB0rKuOA5qnNS6o:fXtn1jdd0xIt2EPf_country-tj_session-applemod_lifetime-30m@geo.iproyal.com:12321" &
|
||||
|
||||
# Порт 9998 → FI (для Google Antigravity)
|
||||
gost -L "http://127.0.0.1:9998" \
|
||||
-F "http://OYB0rKuOA5qnNS6o:fXtn1jdd0xIt2EPf_country-fi_session-googlemod_lifetime-30m@geo.iproyal.com:12321" &
|
||||
```
|
||||
|
||||
В FoxyProxy создаёшь **два профиля**:
|
||||
| Title | Type | Host | Port |
|
||||
|---|---|---|---|
|
||||
| 🇹🇯 IPRoyal-TJ | HTTP | 127.0.0.1 | 9999 |
|
||||
| 🇫🇮 IPRoyal-FI | HTTP | 127.0.0.1 | 9998 |
|
||||
|
||||
Username/Password — **пустые** (gost уже знает auth).
|
||||
|
||||
## Подтверждённые рабочие IP (история)
|
||||
|
||||
| Дата | Страна | Sticky-метка | Реальный IP | ASN |
|
||||
|------|--------|--------------|-------------|-----|
|
||||
| 2026-05-03 | TJ | `appletj01` | 185.177.2.130 (Dushanbe) | AS51346 Tojiktelecom |
|
||||
| 2026-05-03 | TJ | `appletj02` | 94.199.21.228 (Khujand) | AS24722 Babilon-T |
|
||||
| 2026-05-04 | TJ | `applemod` | 109.75.61.86 (Dushanbe) | AS47139 INDIGO |
|
||||
| 2026-05-04 | TJ | `applemod3` | 109.74.74.60 (Dushanbe) | AS24722 Babilon-T |
|
||||
| 2026-05-04 | FI | `googletest` | 212.149.238.126 (Oulu) | AS16086 DNA Oyj |
|
||||
|
||||
Все ASN из списка реальных residential провайдеров — **Apple/Google не палят как proxy**, в отличие от Hostkey/OVH/AWS.
|
||||
|
||||
## Важно — почему не FoxyProxy напрямую к IPRoyal
|
||||
|
||||
FoxyProxy парсер при сохранении пароля «режет» строку на специальных символах (`_` или после первого `:`). Из-за этого `pass_country-tj_session-X` уходит в IPRoyal как просто `pass` → IPRoyal отдаёт **random country**, не TJ. Подтверждено 2026-05-03: с обрезанным паролем вернулся IP Бразилии (Claro NXT).
|
||||
|
||||
Через gost модификаторы передаются как часть upstream URL и до FoxyProxy не доходят — он видит только `127.0.0.1:PORT` без auth.
|
||||
|
||||
## Тест что работает
|
||||
|
||||
```bash
|
||||
# С FI residential
|
||||
curl -x http://127.0.0.1:9998 https://ipinfo.io
|
||||
# Ожидание: "country":"FI", residential ASN (DNA, Elisa, Telia)
|
||||
|
||||
# С TJ residential
|
||||
curl -x http://127.0.0.1:9999 https://ipinfo.io
|
||||
# Ожидание: "country":"TJ", AS24722 Babilon / AS51346 Tojiktelecom / AS47139 INDIGO
|
||||
```
|
||||
|
||||
## WebRTC + Accept-Language (обязательно для Apple ID)
|
||||
|
||||
Прокси сам по себе **недостаточен** — Firefox шлёт RU-локаль и WebRTC может слить реальный IP.
|
||||
|
||||
В Firefox `about:config`:
|
||||
- `intl.accept_languages` = `tg-TJ, tg, en-US, en` (для TJ) или `fi-FI, fi, en-US, en` (для FI)
|
||||
- `media.peerconnection.enabled` = `false`
|
||||
- `media.peerconnection.ice.default_address_only` = `true`
|
||||
|
||||
И регистрироваться **в приватном окне** (Cmd+Shift+P) чтобы не было RU-cookies.
|
||||
|
||||
## Стоимость
|
||||
|
||||
IPRoyal Royal Residential pay-as-you-go: $7/GB (на момент 2026-05-03). 1 GB трафика хватает на ~10 регистраций Apple ID или ~5 часов работы с веб-консолями.
|
||||
|
||||
См. также: [[../decisions/2026-05-02-apple-id-tj-via-residential-proxy]]
|
||||
23
snippets/mac-dictation/README.md
Normal file
23
snippets/mac-dictation/README.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Mac Dictation — Hammerspoon + Groq
|
||||
|
||||
Текущая рабочая версия (2026-05-05) скриптов для голосовой диктовки в любое поле macOS через Groq Whisper API.
|
||||
|
||||
## Файлы
|
||||
- `groq-dictate.sh` → `~/bin/groq-dictate.sh` (chmod +x)
|
||||
- `dictation-doctor.sh` → `~/bin/dictation-doctor.sh` (chmod +x)
|
||||
- `init.lua` → `~/.hammerspoon/init.lua`
|
||||
|
||||
## Как это работает
|
||||
- **Fn (Globe)** — одиночное нажатие, toggle (старт/стоп записи)
|
||||
- Запись через `ffmpeg avfoundation :0` → `/tmp/groq-dictate.wav`
|
||||
- Транскрипция: **Groq Whisper-large-v3-turbo** (cloud), fallback → **whisper-cpp tiny** (local 31MB)
|
||||
- Результат → pasteboard → `hs.eventtap.event.newKeyEvent({"cmd"}, 9, true|false):post()` (⌘V на физический keycode 9 = V)
|
||||
|
||||
## Полный гайд
|
||||
[`decisions/2026-05-05-mac-dictation-groq-hammerspoon.md`](../../decisions/2026-05-05-mac-dictation-groq-hammerspoon.md) — детали, грабли, восстановление на новом Mac.
|
||||
|
||||
## Если сломалось
|
||||
```bash
|
||||
~/bin/dictation-doctor.sh
|
||||
```
|
||||
Покажет что сломано (Hammerspoon / TCC / Groq / mic / Fn behavior).
|
||||
86
snippets/mac-dictation/dictation-doctor.sh
Executable file
86
snippets/mac-dictation/dictation-doctor.sh
Executable file
@@ -0,0 +1,86 @@
|
||||
#!/bin/bash
|
||||
# Health-check для голосовой диктовки. Запускать когда «не работает».
|
||||
# Проверяет: Hammerspoon, Accessibility, init.lua, скрипт, модели, Groq, ffmpeg, mic, Fn-key поведение
|
||||
|
||||
set +e
|
||||
GREEN="\033[32m"; RED="\033[31m"; YELLOW="\033[33m"; CYAN="\033[36m"; RST="\033[0m"
|
||||
ok() { echo -e "${GREEN}✅ $1${RST}"; }
|
||||
fail() { echo -e "${RED}❌ $1${RST}"; ((FAILS++)); }
|
||||
warn() { echo -e "${YELLOW}⚠️ $1${RST}"; }
|
||||
hdr() { echo -e "\n${CYAN}── $1 ──${RST}"; }
|
||||
FAILS=0
|
||||
|
||||
hdr "1. Hammerspoon"
|
||||
if pgrep -x Hammerspoon >/dev/null; then ok "Hammerspoon запущен (pid $(pgrep -x Hammerspoon))"
|
||||
else fail "Hammerspoon не запущен → open -a Hammerspoon"; fi
|
||||
|
||||
if [ -f ~/.hammerspoon/init.lua ]; then ok "init.lua на месте"
|
||||
else fail "~/.hammerspoon/init.lua отсутствует"; fi
|
||||
|
||||
hdr "2. Accessibility (TCC)"
|
||||
TCC=$(echo " " | sudo -S sqlite3 "/Library/Application Support/com.apple.TCC/TCC.db" \
|
||||
"SELECT auth_value FROM access WHERE service='kTCCServiceAccessibility' AND client='org.hammerspoon.Hammerspoon'" 2>/dev/null)
|
||||
case "$TCC" in
|
||||
2) ok "Hammerspoon Accessibility: ALLOWED" ;;
|
||||
0) fail "Hammerspoon Accessibility: DENIED → System Settings → Universal Access → включить" ;;
|
||||
"") fail "Hammerspoon в TCC отсутствует → System Settings → Universal Access → добавить и включить" ;;
|
||||
*) warn "Hammerspoon TCC: $TCC (странное значение)" ;;
|
||||
esac
|
||||
|
||||
hdr "3. Скрипт диктовки"
|
||||
if [ -x ~/bin/groq-dictate.sh ]; then ok "~/bin/groq-dictate.sh executable"
|
||||
else fail "~/bin/groq-dictate.sh отсутствует или не +x"; fi
|
||||
|
||||
hdr "4. Зависимости"
|
||||
for cmd in ffmpeg jq curl whisper-cli osascript; do
|
||||
if command -v "$cmd" >/dev/null; then ok "$cmd найден ($(command -v $cmd))"
|
||||
else fail "$cmd не установлен → brew install $cmd"; fi
|
||||
done
|
||||
|
||||
hdr "5. Модели"
|
||||
if [ -f "$HOME/.cache/whisper-cpp/ggml-tiny-q5_1.bin" ]; then
|
||||
SIZE=$(ls -lh "$HOME/.cache/whisper-cpp/ggml-tiny-q5_1.bin" | awk '{print $5}')
|
||||
ok "whisper-cpp tiny: $SIZE"
|
||||
else
|
||||
warn "локальная модель whisper-cpp отсутствует (fallback будет недоступен)"
|
||||
fi
|
||||
|
||||
hdr "6. Groq API"
|
||||
HTTP=$(curl -sS -o /tmp/_groq_test.json -w "%{http_code}" --max-time 10 \
|
||||
-H "Authorization: Bearer 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" \
|
||||
https://api.groq.com/openai/v1/models 2>/dev/null)
|
||||
case "$HTTP" in
|
||||
200) ok "Groq API отвечает (HTTP 200)" ;;
|
||||
401) fail "Groq: 401 — ключ невалиден или просрочен" ;;
|
||||
403) fail "Groq: 403 — доступ запрещён (возможно, RU IP заблокирован)" ;;
|
||||
429) warn "Groq: 429 — rate limit, подожди" ;;
|
||||
000) fail "Groq не отвечает (нет сети?)" ;;
|
||||
*) fail "Groq HTTP $HTTP" ;;
|
||||
esac
|
||||
rm -f /tmp/_groq_test.json
|
||||
|
||||
hdr "7. Микрофон"
|
||||
DEFAULT_MIC=$(system_profiler SPAudioDataType 2>/dev/null | awk '/Default Input Device: Yes/{found=1} found && /Input Source:/{print $3, $4, $5; exit}')
|
||||
[ -n "$DEFAULT_MIC" ] && ok "Default mic: $DEFAULT_MIC" || warn "Не удалось определить default mic"
|
||||
|
||||
hdr "8. Fn-key behavior"
|
||||
FN_PREF=$(defaults read com.apple.HIToolbox AppleFnUsageType 2>/dev/null)
|
||||
case "$FN_PREF" in
|
||||
0) ok "Fn = 'действие не требуется' (правильно)" ;;
|
||||
1) warn "Fn = 'переключает источник ввода' — конфликт с диктовкой" ;;
|
||||
2) warn "Fn = 'эмодзи панель' — конфликт" ;;
|
||||
3) warn "Fn = 'начинает Apple Dictation' — КОНФЛИКТ! Поменяй на 'Не требуется'" ;;
|
||||
*) warn "Fn behavior unknown: $FN_PREF" ;;
|
||||
esac
|
||||
|
||||
hdr "9. Лог последних попыток"
|
||||
[ -f /tmp/groq-dictate.log ] && tail -10 /tmp/groq-dictate.log || warn "Лог пуст"
|
||||
|
||||
echo
|
||||
if [ $FAILS -eq 0 ]; then
|
||||
echo -e "${GREEN}═══ Всё в порядке ═══${RST}"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}═══ Найдено проблем: $FAILS ═══${RST}"
|
||||
exit 1
|
||||
fi
|
||||
70
snippets/mac-dictation/groq-dictate.sh
Executable file
70
snippets/mac-dictation/groq-dictate.sh
Executable file
@@ -0,0 +1,70 @@
|
||||
#!/bin/bash
|
||||
# Toggle dictation: ⌘⇧D / Fn → запись с микрофона; повторное нажатие → Groq Whisper → текст в /tmp/groq-dictate.last
|
||||
# Hammerspoon Lua callback вставляет через hs.eventtap.keyStroke({"cmd"},"v") в активное окно.
|
||||
# Fallback: если Groq недоступен (нет сети / 429 / 5xx) → локальный whisper-cli (whisper-cpp tiny ru).
|
||||
|
||||
PID_FILE="/tmp/groq-dictate.pid"
|
||||
WAV_FILE="/tmp/groq-dictate.wav"
|
||||
WAV_16K="/tmp/groq-dictate-16k.wav"
|
||||
OUT_FILE="/tmp/groq-dictate.last"
|
||||
LOG="/tmp/groq-dictate.log"
|
||||
GROQ_KEY="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"
|
||||
WHISPER_MODEL="$HOME/.cache/whisper-cpp/ggml-tiny-q5_1.bin"
|
||||
|
||||
log() { echo "[$(date +%H:%M:%S)] $*" >> "$LOG"; }
|
||||
notify() { osascript -e "display notification \"$1\" with title \"Groq Dictate\""; }
|
||||
|
||||
groq_transcribe() {
|
||||
local response
|
||||
response=$(curl -sS -X POST "https://api.groq.com/openai/v1/audio/transcriptions" \
|
||||
-H "Authorization: Bearer $GROQ_KEY" \
|
||||
-F "file=@$WAV_FILE" \
|
||||
-F "model=whisper-large-v3-turbo" \
|
||||
-F "language=ru" \
|
||||
-F "response_format=json" \
|
||||
--max-time 15 2>/dev/null) || return 1
|
||||
local code
|
||||
code=$(echo "$response" | jq -r '.error.code // empty' 2>/dev/null)
|
||||
[ -n "$code" ] && { log "Groq error: $response"; return 1; }
|
||||
echo "$response" | jq -r '.text // empty' 2>/dev/null | sed 's/^ *//;s/ *$//'
|
||||
}
|
||||
|
||||
local_transcribe() {
|
||||
[ ! -f "$WHISPER_MODEL" ] && { log "no local model: $WHISPER_MODEL"; return 1; }
|
||||
ffmpeg -hide_banner -loglevel error -i "$WAV_FILE" -ar 16000 -ac 1 -y "$WAV_16K" 2>>"$LOG" || return 1
|
||||
whisper-cli -m "$WHISPER_MODEL" -l ru -nt -np "$WAV_16K" 2>>"$LOG" | sed 's/^ *//;s/ *$//' | tr -d '\n'
|
||||
}
|
||||
|
||||
if [ -f "$PID_FILE" ]; then
|
||||
# === STOP & TRANSCRIBE ===
|
||||
PID=$(cat "$PID_FILE")
|
||||
log "STOP pid=$PID"
|
||||
kill -INT "$PID" 2>/dev/null
|
||||
for i in 1 2 3 4 5; do kill -0 "$PID" 2>/dev/null || break; sleep 0.1; done
|
||||
rm -f "$PID_FILE"
|
||||
|
||||
if [ ! -s "$WAV_FILE" ]; then
|
||||
log "ERR empty wav"; notify "Запись пустая"; exit 1
|
||||
fi
|
||||
|
||||
log "POST size=$(stat -f%z "$WAV_FILE")B"
|
||||
TEXT=$(groq_transcribe)
|
||||
if [ -z "$TEXT" ]; then
|
||||
log "Groq fail → trying local whisper-cli"
|
||||
notify "☁️→💻 Groq не отвечает, локальная модель"
|
||||
TEXT=$(local_transcribe)
|
||||
[ -z "$TEXT" ] && { log "Local also empty"; notify "Не распознано"; exit 1; }
|
||||
log "LOCAL DONE: $TEXT"
|
||||
else
|
||||
log "GROQ DONE: $TEXT"
|
||||
fi
|
||||
|
||||
printf '%s' "$TEXT" > "$OUT_FILE"
|
||||
else
|
||||
# === START RECORDING ===
|
||||
rm -f "$WAV_FILE" "$WAV_16K" "$OUT_FILE"
|
||||
log "START → $WAV_FILE"
|
||||
ffmpeg -hide_banner -loglevel error -f avfoundation -i ":0" -ar 16000 -ac 1 -y "$WAV_FILE" >/dev/null 2>>"$LOG" &
|
||||
echo $! > "$PID_FILE"
|
||||
notify "🎙️ Говори"
|
||||
fi
|
||||
52
snippets/mac-dictation/init.lua
Normal file
52
snippets/mac-dictation/init.lua
Normal file
@@ -0,0 +1,52 @@
|
||||
-- Groq Dictate: trigger по одиночному нажатию Fn (Globe)
|
||||
-- Fn нельзя биндить через hs.hotkey, поэтому слушаем flagsChanged
|
||||
|
||||
local SCRIPT = os.getenv("HOME") .. "/bin/groq-dictate.sh"
|
||||
|
||||
local function runScript()
|
||||
hs.task.new("/bin/bash", function(exitCode, stdOut, stdErr)
|
||||
local f = io.open("/tmp/groq-dictate.last", "r")
|
||||
if f then
|
||||
local text = f:read("*a")
|
||||
f:close()
|
||||
os.remove("/tmp/groq-dictate.last")
|
||||
if text and #text > 0 then
|
||||
hs.pasteboard.setContents(text)
|
||||
-- keycode 9 = физическая V, в обход keymap lookup чтобы не спамить warnings на ru-раскладке
|
||||
hs.eventtap.event.newKeyEvent({"cmd"}, 9, true):post()
|
||||
hs.eventtap.event.newKeyEvent({"cmd"}, 9, false):post()
|
||||
end
|
||||
end
|
||||
end, {SCRIPT}):start()
|
||||
end
|
||||
|
||||
-- Отслеживаем Fn: keycode 63 — это physical Fn/Globe
|
||||
-- Триггер по press (а не по release), чтобы было снапи
|
||||
local fnPressed = false
|
||||
local fnTime = 0
|
||||
|
||||
fnTap = hs.eventtap.new({hs.eventtap.event.types.flagsChanged}, function(event)
|
||||
local kc = event:getKeyCode()
|
||||
if kc ~= 63 then return false end -- 63 = Fn/Globe
|
||||
|
||||
local flags = event:getFlags()
|
||||
local now = hs.timer.absoluteTime() / 1e6 -- ms
|
||||
|
||||
if flags.fn and not fnPressed then
|
||||
-- Fn pressed
|
||||
fnPressed = true
|
||||
fnTime = now
|
||||
elseif (not flags.fn) and fnPressed then
|
||||
-- Fn released
|
||||
fnPressed = false
|
||||
local elapsed = now - fnTime
|
||||
-- Только короткое нажатие (<400ms) триггерит — long-press для других целей
|
||||
if elapsed < 400 then
|
||||
runScript()
|
||||
end
|
||||
end
|
||||
return false -- не блокировать event для других слушателей
|
||||
end)
|
||||
fnTap:start()
|
||||
|
||||
hs.alert.show("Groq Dictate: Fn (Globe) ready")
|
||||
Reference in New Issue
Block a user