encx-cli — это Go-клиент и CLI для API движка городских квестов Encounter (en.cx).
Если короче: здесь есть и библиотека для встраивания в свой код, и консольная утилита, которой можно быстро залогиниться, посмотреть игру, прочитать задания и отправить код без браузера.
- Модуль:
github.com/skrashevich/encx-cli - CLI:
cmd/encli - Пакет-библиотека:
encx - Тестовый домен для примеров и интеграционных тестов:
tech.en.cx
Снимки автоматически пересоздаются при изменении web-ui. Локальная генерация и CI.
Общий вид: боковая панель с историей чатов, выбор домена и игры, авторизация, поле ввода и режим агента (только чтение / с согласованием / полный доступ). В левом верхнем углу — текущая LLM-модель.
Чат с агентом: запрос на естественном языке и пошаговые вызовы инструментов (admin API, чтение уровней и т.д.).
Тёмная тема (переключатель ◐ в верхней панели).
Проект пригодится в двух сценариях:
- хотите написать свой тулинг поверх Encounter API — берите пакет
encx; - хотите просто работать из терминала — ставьте
encli.
Пакет agenttools превращает движок Encounter в каталог инструментов для
LLM-агента, а agentmcp отдаёт этот каталог наружу по MCP:
encli mcp -domain tech.en.cx # только чтение (по умолчанию)
encli mcp -domain tech.en.cx -security approve # мутации подтверждаются клиентомТот же каталог встроен в Encx.xcframework как агент PicoClaw
(MIT) — см. EncClient.NewAgentSession в mobile/encxmobile. Каталог, политики доступа
(readonly / approve / full) и конфигурация внешнего PicoClaw описаны в
docs/agent-tools.md.
encli --llm и encli -web также работают на runtime и HTTP-провайдере PicoClaw.
Их расширенный CLI-каталог (админские команды, локальные файлы и Wikipedia)
подключён к tools.ToolRegistry через адаптер совместимости.
Подписанные и нотаризованные бинарники для macOS, Linux и Windows доступны на странице Releases. macOS-бинарники подписаны сертификатом Developer ID и прошли нотаризацию Apple — Gatekeeper не покажет предупреждение о недоверенном разработчике.
go install github.com/skrashevich/encx-cli/cmd/encli@latest
go install github.com/skrashevich/encx-cli/cmd/encx-mock@latestДля локальной разработки и ручной проверки CLI в репозитории есть encx-mock — небольшой HTTP-сервер, который имитирует домен Encounter без похода в реальный tech.en.cx.
Скрипты VHS: docs/vhs/. GIF генерируются в CI и публикуются на GitHub Pages.
# запустить mock-сервер на 0.0.0.0:18080
encx-mock
# или публичный demo (HTTPS, без локального сервера)
encli login -domain encounter.exe.xyz -login demo -password demo
encli game-list -domain encounter.exe.xyz
# или через Docker
docker run --rm -p 18080:18080 -e ENCX_MOCK_ADDR=0.0.0.0:18080 ghcr.io/skrashevich/encx-mock
# подключиться к нему через encli
encli login -domain 127.0.0.1:18080 -http -login demo -password demo
encli game-list -domain 127.0.0.1:18080 -http
encli status -domain 127.0.0.1:18080 -http -game-id 424242
encli send-code -domain 127.0.0.1:18080 -http -game-id 424242 "CODE-1"Особенности mock-сервера:
- слушает адрес из
ENCX_MOCK_ADDRили0.0.0.0:18080по умолчанию; - принимает любой логин/пароль, кроме
fail:fail; - поднимает тестовую игру
424242с тремя уровнями и кодами секторов1–12; - умеет загружать экспорт
Game scenario.htmlчерез-scenarioилиENCX_MOCK_SCENARIO; - умеет через
ENCX_MOCK_HARпотоково извлекать из всего HAR только очищенные формы и варианты протокольных ответов; raw HAR, cookies, ответы, идентификаторы и игровой контент в репозиторий не добавляются; - код
PZDCвключает минутную "потерю сети" для текущей пары логин/пароль, чтобы проверять retry/timeout-логику клиента.
Подробное описание режимов, кодов и ограничений: cmd/encx-mock/README.md.
docker run --rm ghcr.io/skrashevich/encx-cli -v
docker run --rm ghcr.io/skrashevich/encx-cli games -domain tech.en.cx
docker run --rm -p 18080:18080 -e ENCX_MOCK_ADDR=0.0.0.0:18080 ghcr.io/skrashevich/encx-mock
# LLM-режим: передайте ключ и модель через -e
docker run --rm \
-e ENCX_LOGIN=user -e ENCX_PASSWORD=secret -e ENCX_GAME_ID=12345 \
-e LLM_API_KEY=sk-or-v1-... \
-e LLM_MODEL=anthropic/claude-sonnet-4 \
ghcr.io/skrashevich/encx-cli -game-id 12345 --llm "покажи уровни"go get github.com/skrashevich/encx-cli/encxНиже минимальный пример: логинимся, смотрим список игр, читаем состояние и пробуем отправить код.
package main
import (
"context"
"fmt"
"log"
"github.com/skrashevich/encx-cli/encx"
)
func main() {
client := encx.New("tech.en.cx", encx.WithInsecureTLS())
ctx := context.Background()
resp, err := client.Login(ctx, "user", "password")
if err != nil {
log.Fatal(err)
}
if resp.Error != 0 {
log.Fatalf("Login error %d: %s", resp.Error, encx.LoginErrorText(resp.Error))
}
// Список игр
list, _ := client.GetGameList(ctx)
for _, g := range list.ActiveGames {
fmt.Printf("%d: %s\n", g.GameID, g.Title)
}
// Состояние игры
model, _ := client.GetGameModel(ctx, 12345)
if model.Level != nil {
fmt.Printf("Уровень %d: %s\n", model.Level.Number, model.Level.Name)
}
// Отправка кода
result, _ := client.SendCode(ctx, 12345, model.Level.LevelId, model.Level.Number, "КОД123")
if result.EngineAction != nil && result.EngineAction.LevelAction != nil &&
result.EngineAction.LevelAction.IsCorrectAnswer != nil &&
*result.EngineAction.LevelAction.IsCorrectAnswer {
fmt.Println("Верный код!")
}
// Список игр с пагинацией
page2, _ := client.GetGameList(ctx, 2)
for _, g := range page2.ComingGames {
fmt.Printf("%d: %s (levels: %d)\n", g.GameID, g.Title, g.LevelNumber)
}
// Статистика игры
stats, _ := client.GetGameStatistics(ctx, 12345)
if stats.Game != nil {
fmt.Printf("Игра: %s, Уровней: %d\n", stats.Game.Title, len(stats.Levels))
for _, l := range stats.Levels {
fmt.Printf(" Уровень %d: %s\n", l.LevelNumber, l.LevelName)
}
}
}Можно подкрутить поведение клиента через опции:
| Опция | Описание |
|---|---|
WithInsecureTLS() |
Пропустить проверку TLS-сертификата |
WithHTTP() |
Использовать HTTP вместо HTTPS |
WithTimeout(d) |
Установить таймаут HTTP-клиента |
WithUserAgent(ua) |
Установить User-Agent |
WithLang(lang) |
Язык запросов (по умолчанию: ru) |
WithEngine(mode) |
Движок Encounter: EngineAuto (по умолчанию), EngineLegacy, EngineNew |
WithAPIBaseURL(url) |
Хост нового движка (по умолчанию выводится из домена: tech.en.cx → api.en.cx) |
Encounter переезжает с ASP.NET на новый REST-бэкенд. encx реализует оба и
переключается между ними прозрачно: сигнатуры методов и возвращаемые типы
одинаковы, меняется только то, кто отвечает на запрос.
По умолчанию движок определяется автоматически: клиент один раз спрашивает
GET {api}/sites/domain/{domain} — новый бэкенд отвечает описанием сайта для
доменов, которые уже переехали, и 404 domain_unregistered для остальных.
Вопрос именно про домен, а не про хост: один API-хост обслуживает всю зону и
отвечает на любой запрос, поэтому проверка доступности хоста объявила бы
переехавшими вообще все сайты.
c := encx.New("tech.en.cx") // auto: определит сам
fmt.Println(c.Engine()) // legacy | new
c := encx.New("demo.en.cx", encx.WithEngine(encx.EngineNew)) // принудительно
c := encx.New("tech.en.cx", encx.WithEngine(encx.EngineLegacy))То же самое даёт переменная окружения ENCX_ENGINE=legacy|new|auto и флаг
encli -engine. Хост нового движка выводится из домена (demo.en.cx →
api.en.cx); для локального mock-сервера или своей инсталляции его задают
через encx.WithAPIBaseURL(...), флаг -api-base-url или ENCX_API_BASE_URL.
Выбор можно не повторять при каждом запуске: мастер первого запуска и Web UI
сохраняют его в ~/.config/encli/engine/settings.json (путь переопределяется
через ENCLI_ENGINE_SETTINGS_FILE). Файл — самый младший источник: флаг и
переменная окружения по-прежнему старше него. Испорченный файл не роняет
команды, а игнорируется с сообщением в -debug: умолчание auto всё равно
опрашивает хост и исправляет себя само.
Хост нового движка выводится только для зон Encounter (en.cx, encounter.cx,
encounter.ru, en-world.org, quest.ua), и сайт в ответе обязан сам назвать
запрошенный домен — иначе на неподтверждённый хост не уходят ни заголовок
сайта, ни учётные данные. Для домена вне этих зон хост не выводится: в auto
клиент останется на старом движке, а при явном new вернёт ошибку с просьбой
указать -api-base-url.
Спецификация нового API, протокол WebSocket движка и матрица паритета методов — в docs/newengine.
Нативное приложение вынесено в отдельный репозиторий enkapp. Здесь остаётся только gomobile-библиотека и сборка Encx.xcframework.
Пакет mobile/encxmobile — gomobile-обёртка над encx. API возвращает JSON-строки, которые декодируются в Swift через JSONDecoder.
# из корня репозитория
./mobile/bind-ios.sh
# результат: mobile/build/Encx.xcframeworkСкрипт устанавливает gomobile, прогоняет тесты и собирает фреймворк для устройства и симулятора.
См. также enkapp — готовый Xcode-проект с этим фреймворком.
- Перетащите
Encx.xcframeworkв проект (Embed & Sign). - Добавьте в Info.plist разрешение на сеть (если ещё нет):
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>Для production лучше настроить ATS exceptions только для нужных доменов (
*.en.cx).
import Encx
guard let client = EncxmobileNewClient("tech.en.cx", true) else { return }
// Авторизация
var loginErr: NSError?
let loginJSON = client.login("user", password: "secret", error: &loginErr)
if let err = loginErr { print(err); return }
struct LoginResponse: Decodable {
let Error: Int
let Message: String
}
let login = try JSONDecoder().decode(LoginResponse.self, from: loginJSON.data(using: .utf8)!)
guard login.Error == 0 else {
print(EncxmobileLoginErrorText(login.Error))
return
}
// Сохранение сессии (Keychain / UserDefaults)
if let cookies = client.exportCookies(&loginErr) {
UserDefaults.standard.set(cookies, forKey: "encx_cookies")
}
// Состояние игры
let gameJSON = client.getGameModel(12345, error: &loginErr)
// decode GameModel from gameJSON...
// Отправка кода
let resultJSON = client.sendCode(12345, levelID: 67890, levelNumber: 1, code: "КОД123", error: &loginErr)| Go (encxmobile) | Описание |
|---|---|
NewClient / NewClientWithOptions |
Создание клиента |
Login / LoginWithCaptcha |
Авторизация |
GetGameModel |
Состояние игры |
GetGameModelLevel |
Состояние выбранного уровня штурмовой игры |
SendCode |
Отправка кодов |
AdminGetLevelSequence / AdminSetLevelSequence |
Чтение и смена выдачи уровней (3 = штурмовая) |
GetPenaltyHint |
Штрафная подсказка |
GetGameList / GetDomainGames |
Список игр |
GetGameStatistics |
Статистика |
EnterGame |
Вступление в игру |
GetProfile |
Профиль пользователя |
ExportCookies / ImportCookies |
Сохранение сессии |
LoginErrorText / EventText |
Тексты ошибок |
Тот же пакет mobile/encxmobile доступен из PHP: он собирается в разделяемую библиотеку
(-buildmode=c-shared), которую PHP вызывает через FFI. Клиент живёт на стороне Go, поэтому
сессия и куки сохраняются между вызовами.
require __DIR__ . '/bindings/php/autoload.php';
$client = Encx\Client::newClient('demo.en.cx', false);
$client->login('user', 'password');
$model = json_decode($client->getGameModel(82448), true);Обёртки не пишутся руками: cgo-экспорты, C-заголовок и PHP-классы генерируются из Go-исходника
командой go generate ./bindings/..., а отставание ловят drift-тест, CI и pre-commit hook.
Сборка, установка и полный список связанных методов — в bindings/php/README.md.
encli полезен, когда нужно быстро дернуть API руками и не городить под это отдельный код.
Демо использует публичный mock-сервер encounter.exe.xyz. GIF обновляются автоматически через GitHub Pages.
Типичный поток такой:
- залогиниться;
- выбрать игру;
- смотреть статус, задания и сообщения;
- отправлять коды и запрашивать подсказки.
# Авторизация (интерактивный ввод пароля)
encli login -domain tech.en.cx -insecure
# Список игр
encli games
encli game-list
# Статус игры
encli status -game-id 12345
# Задание текущего уровня
encli level -game-id 12345
# В штурмовой игре посмотреть задание №2 и отправить в него код
encli level -game-id 12345 -level-number 2
encli send-code -game-id 12345 -level-number 2 "КОД123"
# Все уровни с прогрессом
encli levels -game-id 12345
# Бонусы текущего уровня
encli bonuses -game-id 12345
# Подсказки (обычные и штрафные)
encli hints -game-id 12345
# Секторы текущего уровня
encli sectors -game-id 12345
# Лог пробитий кодов
encli log -game-id 12345
# Сообщения от организаторов
encli messages -game-id 12345
# Вступить в игру
encli enter -game-id 12345
# Отправка кода уровня/сектора
encli send-code -game-id 12345 "КОД123"
# Отправка бонусного кода
encli send-bonus -game-id 12345 "БОНУС123"
# Запрос штрафной подсказки
encli hint -game-id 12345 42
# Статистика игры
encli game-stats -game-id 12345
# Профиль
encli profile
# Версия
encli -v
# LLM-агент (кратко; подробнее — раздел «LLM-агент и OpenRouter» ниже)
export LLM_API_KEY=sk-or-v1-... # ключ с https://openrouter.ai/keys
export LLM_MODEL=anthropic/claude-sonnet-4 # любая модель из каталога OpenRouter
encli -game-id 12345 --llm "создай 3 уровня с бонусами и подсказками"
encli -game-id 12345 --llm "пройдись по уровням, проверь ответы и предложи исправления"
encli -readonly -game-id 12345 --llm "покажи содержимое всех уровней" # без записи в игру
# Web UI с тем же ключом и моделью
encli -web
# Выход
encli logout
# --- Admin-команды ---
# Список авторских игр
encli admin-games
# Список уровней с ID
encli admin-levels -game-id 12345
# Посмотреть режим выдачи; переключить на штурмовую выдачу
encli admin-level-sequence -game-id 12345
encli admin-level-sequence -game-id 12345 assault
# Создать 3 уровня
encli admin-create-levels -game-id 12345 3
# Удалить уровень №5
encli admin-delete-level -game-id 12345 5
# Переименовать уровень (ID из admin-levels)
encli admin-rename-level -game-id 12345 67890 "Новое название"
# Установить автопереход 1ч 30мин с штрафом 15мин
encli admin-set-autopass -game-id 12345 1 1:30:00 0:15:00
# Блокировка: 3 попытки за 1 минуту, на игрока
encli admin-set-block -game-id 12345 1 3 0:01:00 player
# Создать бонус (уровень 1, level-id 67890, название, ответы)
encli admin-create-bonus -game-id 12345 1 67890 "Бонус 1" "ответ1" "ответ2" -- award_minutes=3 award_seconds=0
# Удалить бонус
# bonus-id берите из admin-level-content
# там бонусы печатаются как [bonus <id>]
encli admin-delete-bonus -game-id 12345 1 <bonus-id>
# Создать сектор
encli admin-create-sector -game-id 12345 1 "Сектор А" "код1" "код2"
# Посмотреть содержимое уровня и найти sector-id
encli admin-level-content -game-id 12345 1
# Удалить сектор
# sector-id берите из admin-level-content
# там секторы печатаются как [sector <id>]
encli admin-delete-sector -game-id 12345 1 <sector-id>
# Обновить сектор
# sector-id берите из admin-level-content
# пример: переименовать сектор и заменить список ответов
encli admin-update-sector -game-id 12345 1 <sector-id> name="Сектор Б" answers="код3,код4"
# Создать подсказку (откроется через 30 минут)
encli admin-create-hint -game-id 12345 1 0:30:00 "Текст подсказки"
# Удалить подсказку
# hint-id берите из admin-level-content
# там подсказки печатаются как [hint <id>]
encli admin-delete-hint -game-id 12345 1 <hint-id>
# Создать задание
encli admin-create-task -game-id 12345 1 "Текст задания уровня"
# Обновить задание
# task-id берите из admin-level-content
# там задания печатаются как [task <id>]
encli admin-update-task -game-id 12345 1 <task-id> "Новый текст задания"
# Установить имя и комментарий уровня
encli admin-set-comment -game-id 12345 1 "Название" "Комментарий для орга"
# Список команд в игре
encli admin-teams -game-id 12345
# Начисления бонусного/штрафного времени
encli admin-corrections -game-id 12345
encli admin-add-correction -game-id 12345 "Team Name" bonus 0:10:00 0 "за красоту"
encli admin-delete-correction -game-id 12345 444
# Чтение содержимого уровня (задание, секторы, бонусы, подсказки, настройки)
encli admin-level-content -game-id 12345 1
# Сообщения уровня
# сначала посмотрите список сообщений и их message-id
encli admin-messages -game-id 12345 1
# создать сообщение
# здесь 67890 — это level-id, его берите из admin-levels
encli admin-create-message -game-id 12345 67890 "Текст сообщения"
# обновить сообщение
# message-id берите из admin-messages
encli admin-update-message -game-id 12345 1 <message-id> text="Новый текст" mode=chosen levels=67890
# удалить сообщение
# message-id берите из admin-messages
encli admin-delete-message -game-id 12345 1 <message-id>
# Создать новую игру (title, start, finish обязательны; даты в RFC3339)
encli admin-create-game title="Новая игра" start="2026-09-10T18:00:00+03:00" finish="2026-09-11T18:00:00+03:00"
# Информация об игре (название, авторы, описание, даты, модерация заявок)
encli admin-game-info -game-id 12345
# Обновить настройки игры
# Ключи: title, authors, description, prize, start, finish, request_last_date, moderated
# Не указанные ключи сохраняют текущее значение
encli admin-update-game -game-id 12345 title="Новое название" description="Описание"
# Перенести старт и включить автоприём заявок (moderated=false)
# Даты в RFC3339; старый движок понимает и DD.MM.YYYY HH:MM:SS
# Старт нельзя изменить после начала игры
encli admin-update-game -game-id 12345 start="2026-09-10T18:00:00+03:00" moderated=false
# Полная очистка игры (обнуление)
encli admin-wipe-game -game-id 67890
# Удаление игры целиком (безвозвратно; ID повторяется как подтверждение)
encli admin-delete-game -game-id 67890 67890
# Копирование игры целиком (из 12345 в 67890)
# Рекомендуется сначала admin-wipe-game на целевой
encli admin-copy-game -game-id 12345 67890
# Перестановка и клонирование уровней
encli admin-swap-levels -game-id 12345 2 5
encli admin-insert-level -game-id 12345 3 0
encli admin-clone-levels -game-id 12345 2 1
# Обновление бонуса и подсказки
encli admin-update-bonus -game-id 12345 1 <bonus-id> name="Новое имя" answers="код1,код2"
# Заменить бонусное время на 3 минуты, сохранив остальные поля:
encli admin-update-bonus -game-id 12345 1 <bonus-id> award_hours=0 award_minutes=3 award_seconds=0
# Штрафной бонус: negative=true; обычный бонус: negative=false.
# Время задаётся компонентами; пропущенные компоненты при обновлении сохраняются.
# LLM: admin_create_bonus и admin_update_bonus принимают эти же поля времени.
encli admin-update-hint -game-id 12345 1 <hint-id> text="Новый текст" delay=0:45:00
# Удаление задания
encli admin-delete-task -game-id 12345 1 <task-id>
# Жизненный цикл игры
encli admin-deliver -game-id 12345
encli admin-not-deliver -game-id 12345
encli admin-award-points -game-id 12345
encli admin-end-ratings -game-id 12345
encli admin-calc-ik -game-id 12345
encli admin-action-monitor -game-id 12345encli встраивает PicoClaw с tool-calling: агент читает состояние игры, вызывает admin-команды, ищет факты в Википедии и читает локальные файлы со сценарием. Один runtime используется в CLI (--llm), локальном Web UI (-web) и Docker (через -e).
По умолчанию используется OpenRouter (https://openrouter.ai/api/v1) и бесплатная модель openai/gpt-oss-120b:free. Чтобы использовать свой ключ и другую модель, задайте переменные окружения (или передайте их в docker run -e ...):
| Переменная | Алиас | Назначение |
|---|---|---|
LLM_API_KEY |
OPENROUTER_API_KEY |
API-ключ OpenRouter (получить) |
LLM_MODEL |
OPENROUTER_MODEL |
Идентификатор модели в каталоге OpenRouter, например anthropic/claude-sonnet-4 или google/gemini-2.5-pro-preview |
LLM_BASE_URL |
OPENROUTER_BASE_URL |
Base URL OpenAI-совместимого API (если не OpenRouter) |
Приоритет: сначала LLM_*, затем OPENROUTER_*. Для localhost (127.0.0.1 / localhost в URL) ключ не обязателен — удобно для локального прокси.
Всё то же самое настраивается мышкой в Web UI — см. Настройка LLM в Web UI.
Вместо ключа агент умеет работать на подписке ChatGPT — через тот же backend, что использует Codex CLI. Токенов по прайсу OpenRouter это не тратит: в отчёте о выполнении стоимость показывается как $0 (подписка ChatGPT).
encli codex-login # откроет браузер и дождётся редиректа
encli codex-login -device # headless-хост: код вводится на chatgpt.com
encli codex-login -no-browser # напечатает ссылку, ждёт вставки redirect URL
encli codex-status # аккаунт, срок действия токена, путь к файлу
encli codex-logout # удалить сохранённую учёткуУчётка (access- и refresh-токен) лежит в ~/.config/encli/codex/auth.json с правами 0600; путь переопределяется через ENCLI_CODEX_AUTH_FILE. Отдельный подкаталог не случаен: -web считает каждый *.json в ~/.config/encli/ сессией домена Encounter, и учётка в общем каталоге показалась бы там фиктивным доменом с кнопкой Logout, которая её удаляет. Access-токен обновляется автоматически за 5 минут до истечения, обновлённое значение сразу пишется обратно в файл — повторный codex-login нужен, только если истёк refresh-токен.
Как выбирается транспорт:
| Условие | Транспорт |
|---|---|
-llm-auth codex или LLM_AUTH=codex |
Подписка ChatGPT (ошибка, если codex-login не выполнен) |
-llm-auth gigachat или LLM_AUTH=gigachat |
GigaChat (ошибка, если не задан GIGACHAT_CREDENTIALS) |
-llm-auth apikey или LLM_AUTH=apikey |
Только API-ключ, сохранённая учётка игнорируется |
Ничего не задано, LLM_API_KEY задан |
API-ключ (явный ключ всегда в приоритете) |
Ничего не задано, задан LLM_BASE_URL / OPENROUTER_BASE_URL |
Указанный endpoint (в том числе локальный прокси без ключа) |
| Ничего не задано в окружении, есть настройки из Web UI | То, что сохранено в ~/.config/encli/llm/settings.json |
| Ничего не задано, ключа и endpoint нет, учётка ChatGPT есть | Подписка ChatGPT |
Ничего не задано, ключа и endpoint нет, задан GIGACHAT_CREDENTIALS |
GigaChat |
Автовыбор подписки или GigaChat срабатывает, только когда не задано вообще ничего:
и ключ, и base URL — это явно выраженное намерение, поэтому оставшаяся с прошлого раза
учётка не уводит локальный прокси на chatgpt.com. Чтобы использовать подписку
при заданном ключе или endpoint, укажите -llm-auth codex явно. Учётка ChatGPT имеет
приоритет над GIGACHAT_CREDENTIALS; выбрать GigaChat при обеих настройках можно
через -llm-auth gigachat.
Модель по умолчанию для подписки — gpt-5.5; LLM_MODEL её переопределяет, но только
именем, которое backend действительно обслуживает: префикс openai/ отбрасывается,
принимаются семейства gpt-*, o3*, o4*. Любое другое имя (в том числе забытое в шелле
openrouter/free или claude-sonnet-4) заменяется на дефолтную модель с предупреждением
в stderr — иначе backend подменил бы её молча, а в отчёте стояла бы запрошенная модель,
а не та, что отвечала. Флаг -llm-auth доступен в --llm, -chat и -web.
# Разовая настройка
encli codex-login
# Дальше ключ не нужен ни в одном из режимов
encli -game-id 12345 --llm "проверь ответы на уровне 3"
encli -game-id 12345 -chat
encli -web
# Явный выбор, когда в окружении есть и LLM_API_KEY
encli -llm-auth codex -game-id 12345 --llm "покажи статус игры"Агент умеет работать на GigaChat API.
Ключ авторизации (Authorization key — base64 от Client ID:Client Secret) берётся в
личном кабинете Сбера, в проекте GigaChat API; по нему клиент сам получает access-токен,
живущий 30 минут, и обновляет его за 2 минуты до истечения.
export GIGACHAT_CREDENTIALS=<Ключ авторизации из личного кабинета>
encli -game-id 12345 --llm "покажи уровни"
# Явный выбор, когда в окружении есть и LLM_API_KEY
encli -llm-auth gigachat -game-id 12345 --llm "покажи статус игры"| Переменная | Назначение |
|---|---|
GIGACHAT_CREDENTIALS |
Ключ авторизации (обязательна) |
GIGACHAT_SCOPE |
Версия API: GIGACHAT_API_PERS (по умолчанию), GIGACHAT_API_B2B, GIGACHAT_API_CORP |
GIGACHAT_MODEL |
Модель (по умолчанию: GigaChat-2-Max); при отсутствии берётся LLM_MODEL |
GIGACHAT_BASE_URL |
Base URL API (по умолчанию: https://api.giga.chat/v1) |
GIGACHAT_AUTH_URL |
Endpoint OAuth (по умолчанию: https://ngw.devices.sberbank.ru:9443/api/v2/oauth) |
GIGACHAT_CA_BUNDLE |
PEM-файл с «Российским доверенным корневым УЦ» — включает проверку сертификата |
GIGACHAT_INSECURE |
0 — проверять сертификат; 1 — не проверять (по умолчанию не проверяется) |
Две площадки и модели. По умолчанию используется https://api.giga.chat/v1 — там живут
GigaChat-2, GigaChat-2-Pro, GigaChat-2-Max и третье поколение: GigaChat-3-Lightning,
GigaChat-3-Pro, GigaChat-3-Ultra. Старые имена (GigaChat, GigaChat-Pro, GigaChat-Max,
*-preview) обслуживает только legacy-хост — для них задайте
GIGACHAT_BASE_URL=https://gigachat.devices.sberbank.ru/api/v1. Если модели на площадке нет,
API отвечает 404 No such model; encli дополняет эту ошибку списком моделей, которые площадка
реально отдаёт, и подсказкой про legacy-хост.
LLM_BASE_URL для GigaChat НЕ читается — это намеренно. Переменная означает «адрес
OpenAI-совместимого endpoint», и если бы она действовала здесь, забытый в шелле
LLM_BASE_URL=https://openrouter.ai/api/v1 отправил бы OAuth-токен GigaChat в заголовке
Authorization постороннему хосту. Адрес переопределяется только через GIGACHAT_BASE_URL.
Сертификат. GigaChat отдаётся под сертификатом Минцифры, которого нет ни в одном
системном хранилище по умолчанию. Проверка, которая падает всегда, — это не защита, а
поломка, поэтому по умолчанию сертификат GigaChat не проверяется (один раз за запуск
об этом печатается предупреждение в stderr). Чтобы включить проверку, скачайте
корневой сертификат Минцифры
и укажите путь в GIGACHAT_CA_BUNDLE — он добавляется к системному пулу, а не заменяет
его. Если корень уже установлен в системе, достаточно GIGACHAT_INSECURE=0.
# Просто работает, без проверки сертификата
export GIGACHAT_CREDENTIALS=<ключ>
encli -game-id 12345 --llm "проверь ответы на уровне 3"
# Третье поколение
GIGACHAT_MODEL=GigaChat-3-Ultra encli -domain demo.en.cx --llm "покажи игры на домене"
# С проверкой сертификата
export GIGACHAT_CA_BUNDLE=~/.config/encli/russian_trusted_root_ca.pem
export GIGACHAT_MODEL=GigaChat-2-Pro
encli -game-id 12345 --llm "проверь ответы на уровне 3"Инструменты передаются GigaChat в его собственном формате (functions / function_call),
а не в OpenAI-совместимом tools / tool_calls: OpenAI-образный запрос GigaChat принимает
без ошибки, но молча теряет все инструменты, и агент остаётся без единого действия.
За один ход GigaChat вызывает не больше одной функции — цикл агента от этого просто
становится длиннее. Стоимость в отчёте о выполнении не показывается: GigaChat тарифицируется
в собственных единицах с предоплаченного баланса, а не в долларах.
Примеры:
# Разовый запуск с другой моделью
LLM_API_KEY=sk-or-v1-XXXX LLM_MODEL=anthropic/claude-sonnet-4 \
encli -game-id 12345 --llm "создай уровень с бонусом"
# Постоянная настройка в shell
export LLM_API_KEY=sk-or-v1-XXXX
export LLM_MODEL=google/gemini-2.5-flash-preview
encli -game-id 12345 --llm "проверь ответы на уровне 3"
# Старые имена переменных (обратная совместимость)
export OPENROUTER_API_KEY=sk-or-v1-XXXX
export OPENROUTER_MODEL=openai/gpt-4o
encli -game-id 12345 --llm "покажи статус"
# Локальный OpenAI-совместимый сервер (Ollama, LiteLLM, Cursor proxy и т.п.)
export LLM_BASE_URL=http://127.0.0.1:8317/v1
export LLM_MODEL=my-local-model
encli -game-id 12345 --llm "покажи уровни"Список моделей и цены: openrouter.ai/models. В конце сессии агент печатает отчёт: модель, число запросов к LLM, токены и ориентировочная стоимость (для OpenRouter — по прайсу из /models).
encli -game-id 12345 --llm "скопируй игру 82033 в 82034"
encli -debug -game-id 12345 --llm "покажи статус игры" # подробный лог в stderr
encli -readonly -game-id 12345 --llm "аудит всех уровней" # без изменений в игреДля review-запросов (проверь ответы, найди ошибки) агент не применяет правки сразу: сначала предлагает исправления, затем CLI (или Web UI) спрашивает подтверждение по каждому пункту.
Поведение агента:
- Вызовы инструментов выводятся в читаемом виде (например,
[admin_level_content] Читаю содержимое уровня 3). - Большие ответы tool-call'ов сжимаются перед отправкой обратно в модель.
- При 429 и 502–504 — до 3 повторов с нарастающей задержкой.
- После создания или изменения уровней агент проверяет результат (коды, тайминги, подсказки, задания).
Локальный чат с тем же агентом, историей диалогов и переключателем режима безопасности (только чтение / с подтверждением / полный доступ):
export LLM_API_KEY=sk-or-v1-...
export LLM_MODEL=anthropic/claude-sonnet-4
encli -web
# по умолчанию http://127.0.0.1:8787 — откроется в браузере
encli -web -web-addr 0.0.0.0:8787 # слушать на всех интерфейсах
# или: ENCLI_WEB_ADDR=0.0.0.0:8787 encli -web
-web-addr 0.0.0.0открывает доступ, а не только просмотр. API не аутентифицирован — предполагается, что слушает петлевой интерфейс. Открыв его наружу, вы отдаёте не только чаты и вход в en.cx:PUT /api/v1/llm/settingsзадаёт base URL провайдера, а агент шлёт на этот адресAuthorization: Bearerс вашим ключом — в том числе с ключом из переменной окружения, которую панель не может перезаписать. То есть подмена base URL превращается в кражу ключа. Открывайте порт только в доверенной сети и лучше за обратным прокси с авторизацией; по той же причине не оставляйте-webоткрытым наружу надолго.
В шапке UI отображается текущая модель. Сессии Encounter логинятся через UI; чаты сохраняются в ~/.config/encli/web/chats/.
Ничего не настроено — и первый же encli -web встречает мастером вместо пустого чата,
который не может ответить. После приветствия — два шага: модель → вход в en.cx.
- Модель. Polza.AI для пользователей без готового доступа, существующая подписка ChatGPT или свой OpenAI-совместимый провайдер. Для Polza приложение подключает аккаунт через браузер и проверяет доступный баланс; для других провайдеров проверка настроек показывает выбранный транспорт и модель, но не подтверждает ответ внешнего API.
- Вход в en.cx. Домен, логин, пароль. Кнопка «Войти и завершить» проверяет данные на сервере Encounter. Без успешного входа завершить настройку нельзя.
Движок домена определяется автоматически. Ручной выбор доступен через
-engine, ENCX_ENGINE и API настроек движка.
Факт прохождения хранится в ~/.config/encli/onboarding/state.json
(переопределяется через ENCLI_ONBOARDING_FILE), так что мастер не повторяется на каждом
старте. Открыть мастер снова —
кнопка «Мастер настройки» (◈) в шапке; чтобы он снова появился при следующем запуске,
удалите файл состояния или вызовите POST /api/v1/onboarding/reset.
Файлы настроек и состояния лежат в подкаталогах, а не прямо в ~/.config/encli/, по той же
причине, что и учётка ChatGPT: -web показывает каждый *.json из корня этого каталога как
сессию домена Encounter, и кнопка «Выйти» рядом с такой «сессией» удалила бы файл.
Приоритет источников тот же, что и у настроек LLM: флаг CLI → переменная окружения → сохранённые настройки → дефолт. Если поле перекрыто переменной или флагом, мастер показывает это рядом с полем, а не делает вид, что сохранение подействовало.
Эндпоинты: GET /api/v1/onboarding, POST /api/v1/onboarding/complete,
POST /api/v1/onboarding/reset, GET/PUT/DELETE /api/v1/engine/settings,
GET /api/v1/engine/probe?domain=....
В мастере и панели LLM есть отдельный вариант Polza.AI. Новый пользователь открывает «Зарегистрироваться», создаёт аккаунт на сайте Polza и возвращается в encli. Кнопка регистрации использует партнёрскую ссылку приложения. Регистрацию и оплату пользователь выполняет на стороне Polza; приложение не принимает деньги.
«Подключить аккаунт» запускает OAuth PKCE:
пользователь разрешает доступ на сайте Polza, а encli получает и сохраняет API-ключ.
Адрес https://polza.ai/api/v1 подставляется автоматически. Модель выбирается из
актуального каталога текстовых моделей с поддержкой инструментов. По умолчанию предлагается
deepseek/deepseek-v4-flash-0731, если она есть в каталоге. Резервный способ — вставить ключ вручную.
Если браузер находится на другой машине, можно вставить полный callback URL в encli.
Баланс проверяется отдельным авторизованным запросом
GET /api/v2/balance.
Показывается доступная к расходованию сумма (available), учитывающая лимиты ключа.
Нулевой остаток не означает ошибочный ключ: нужно пополнить баланс на сайте Polza или
проверить лимит расходов, затем повторить проверку. Получение каталога /models
не используется как доказательство действительности ключа или наличия средств.
Ключ хранится в обычном файле настроек LLM с правами 0600, в браузер из настроек
возвращается только маска. Для агента это существующий транспорт apikey, поэтому
подключение работает и в Web UI, и в CLI/TUI. Если переменные окружения или флаг
перекрывают выбранное подключение, приложение сообщает об этом.
Партнёрская регистрация и OAuth — отдельные действия: недокументированные параметры реферала к OAuth URL не добавляются. Факт начисления партнёрского вознаграждения определяется Polza и самим приложением не проверяется.
Переменные окружения задавать не обязательно: кнопка «Настройки LLM» в шапке открывает панель, где настраивается подключение к модели.
- Подписка ChatGPT. Кнопка «Войти через ChatGPT» запускает тот же OAuth-поток, что и
encli codex-login, но целиком из браузера: encli открывает страницу авторизации, ждёт редирект наhttp://localhost:1455/auth/callbackи сохраняет учётку в~/.config/encli/codex/auth.json. Если браузер живёт на другой машине, в панели есть поле для ручной вставки redirect URL. Порт 1455 не выбирается произвольно: OpenAI сверяет redirect URI с зарегистрированным для клиента Codex, поэтому при занятом порте вход честно падает с ошибкой вместо молчаливого отказа на стороне провайдера. - OpenAI-совместимый провайдер. Поля Base URL, API-ключ и модель. Сохраняются в
~/.config/encli/llm/settings.json(права0600, атомарная запись); путь переопределяется черезENCLI_LLM_SETTINGS_FILE. Файл лежит в подкаталоге по той же причине, что и учётка ChatGPT:-webпоказывает каждый*.jsonиз~/.config/encli/как сессию домена Encounter. Через API ключ наружу не отдаётся — только маска вида••••1234.
Приоритет источников: флаг CLI → переменная окружения → сохранённые настройки → дефолт. Окружение намеренно старше файла: ключ, выставленный в шелле или в контейнере, — это голос деплоя, и его не должно перебивать значение, введённое в UI когда-то давно. Чтобы это не выглядело как «сохранил, а ничего не изменилось», панель рядом с таким полем показывает плашку с именем переменной, которая его перекрывает, и внизу — что реально уйдёт агенту.
Настройки читаются на каждом запросе, так что менять их можно не перезапуская encli -web.
Соответствие эндпоинтов: GET/PUT/DELETE /api/v1/llm/settings, POST /api/v1/llm/codex/login
(+ GET .../login/{id} для статуса, POST .../login/{id}/code для ручной вставки),
GET /api/v1/llm/codex/status, POST /api/v1/llm/codex/logout.
На Windows Web UI стартует и без флага: если encli.exe запущен без команды и без терминала (двойной клик в проводнике, ярлык), вместо мелькнувшей справки поднимается Web UI и открывается браузер. Запуск из cmd.exe или PowerShell не меняется — encli.exe без аргументов там по-прежнему печатает справку, а любая команда (encli.exe game-list) выполняется как обычно.
Консольный аналог Web UI: полноэкранный чат в терминале с тем же агентом, той же историей (~/.config/encli/web/chats/ — чаты видны и в -web), теми же сессиями Encounter и режимами безопасности per-chat.
export LLM_API_KEY=sk-or-v1-...
encli -chat -domain tech.en.cx
encli -chat -readonly # по умолчанию режим approveСлева — список чатов, справа — переписка с инлайновым логом вызовов инструментов; подтверждения изменений запрашиваются прямо в интерфейсе (y / n / q). Слэш-команды: /help, /new, /chats, /domain, /game, /games, /mode readonly|approve|full, /login, /logout, /auth, /delete, /export [md|json], /quit. Клавиши: Enter — отправить, Tab — фокус на список чатов, Ctrl+B — показать/скрыть список, Esc — отменить текущий запрос, PgUp/PgDn — прокрутка.
Для файла экспорта Encounter GameScenario агент использует inspect_scenario_file
(название и точные счётчики), затем admin_import_scenario с ID целевой игры.
Импортёр переносит уровни и содержимое напрямую из файла и сверяет результат со
свежим экспортом. admin_verify_scenario выполняет такую сверку без изменений.
Повторный импорт выравнивает содержимое существующих уровней; лишние уровни не
удаляются автоматически и попадают в отчёт о расхождениях.
Большие результаты старых инструментов сокращаются только в запросе к модели; полная история сохраняется. Пустые и незавершённые ответы модели выводятся как ошибки, а в Web UI ошибки остаются в переписке после обновления страницы.
Агент может читать сценарии с диска, открывать ссылки и сверять факты:
| Инструмент | Назначение |
|---|---|
read_local_file |
Прочитать текстовый файл |
list_local_dir |
Список файлов в каталоге |
search_local_files |
Поиск по содержимому / glob |
wikipedia_search |
Поиск статей |
wikipedia_article |
Краткое содержание статьи |
fetch_url |
Загрузить внешнюю страницу по ссылке как текст |
osm_route_map |
Схема дохода (пешком) или доезда (на машине) из OpenStreetMap в PNG |
fetch_url принимает только http/https, конвертирует HTML в читаемый текст
(содержимое <script> и <style> отбрасывается) и отдаёт длинные страницы
частями через offset. Адреса, которые резолвятся в loopback, приватные или
link-local сети, отклоняются на этапе подключения — включая редиректы, — поэтому
ссылкой из чата нельзя дотянуться до внутренних сервисов хоста. Для доменов
Encounter и настроенного домена сайта используется текущая сессия пользователя,
если она есть. Bearer-токен нового движка отправляется только на настроенный
API-адрес; внешние домены не получают данные сессии, в том числе при редиректах.
admin_upload_image загружает PNG, JPEG, GIF или WebP (до 20 МиБ) в файлы
указанной игры на старом или новом движке и возвращает прямой URL и готовый тег
<img>. Агент может взять изображение из прикреплённого файла чата или из
LLM_FILES_ROOT. Для вставки в задание затем используется admin_create_task
или admin_update_task. По умолчанию агент загружает файл под случайным именем
(не менее 128 бит случайности), чтобы адрес нельзя было подобрать до открытия
уровня. Параметр name используется только если пользователь явно указал
точное имя файла в запросе, например «имя файла: clue.png». Существующий файл
с тем же именем не перезаписывается.
osm_route_map рисует PNG-схему по данным OpenStreetMap в стиле уровней
Encounter: красная метка — старт, чёрная — финиш, между ними маршрут.
Различаются два вида схем, и агент обязан явно выбрать profile:
- схема дохода (
profile: foot) — пешком: между локациями на пешеходных играх или от парковки до локации на автомобильных; маршрут — фиолетовый пунктир; - схема доезда / парковки (
profile: car) — на машине, для автомобильных игр; маршрут — сплошная линия.
Для уровня существующей игры достаточно передать game_id и level_number:
тулза одним запросом читает сценарий, берёт финиш из первых координат этого
уровня (<span class="coords">, иначе пары широта, долгота), а старт — из
последних координат предыдущих уровней, то есть с того места, где команда
находилась. Если эта точка ближе 15 м к финишу (схема внутри локации),
рисуется только финиш, а в ответе появляется start_skipped. Явные to/from
перекрывают точки из сценария и рисуются как заданы; to нужен, если в самом
уровне координат нет. Точки задаются как широта,долгота или адресом; адрес геокодируется через
Nominatim (не чаще раза в секунду), маршрут строит FOSSGIS OSRM
(routing.openstreetmap.de), подложка — тайлы tile.openstreetmap.org. Кадр
плотно охватывает маршрут (масштаб до 18) и сам выбирает портретную или
альбомную ориентацию по форме маршрута; zoom, width и height (256–1280 px)
можно задать явно. Результат содержит путь к PNG, длину и время маршрута,
finish_coords и task_html — готовый текст уровня («Пройдите от красной точки
на карте до чёрной…» с координатами финиша), где IMAGE_URL заменяется на адрес
загруженной картинки. Файл сохраняется в каталог загрузок чата (maps/),
поэтому его сразу можно передать в admin_upload_image. Имя локальной схемы
тоже случайное, если пользователь явно не указал точное имя. На картинке всегда есть
подпись «© OpenStreetMap contributors» — этого требуют правила использования
OSM, не обрезайте её. Если сервис маршрутов недоступен, карта строится только с
метками и в ответе появляется route_error; не загрузившиеся тайлы остаются
пустым фоном и считаются в tile_errors. В обоих случаях в ответе есть
warning, и агент сначала сообщает о неполной схеме пользователю.
Агент рисует схему и вставляет её в уровень только по явной просьбе пользователя: при создании, импорте, копировании или проверке игры он схем не добавляет, а может лишь предложить их текстом.
Та же генерация доступна из командной строки без этих ограничений — команда всегда рисует схему в файл и никогда не меняет игру:
# схема дохода к уровню 8: точки берутся из сценария игры
encli route-map -game-id 32055 level=8 out=level8.png
# схема доезда от вокзала до парковки
encli route-map to="57.618854, 39.872352" from="Ярославль, Московский вокзал" profile=carКлючи: level, to, from, profile (foot по умолчанию или car),
zoom, width, height, out (по умолчанию ./dohod-XXXX.png или
./doezd-XXXX.png, существующий файл out перезаписывается); с -json
команда печатает тот же JSON, что и тулза.
Корень для локальных путей — LLM_FILES_ROOT (по умолчанию текущая рабочая директория); файлы вне этого каталога недоступны.
export LLM_FILES_ROOT=~/quests/my-scenario
encli -game-id 12345 --llm "прочитай levels.md и создай уровни по сценарию"| Команда | Что делает |
|---|---|
login |
Логинится и сохраняет сессию |
logout |
Чистит сохраненную сессию |
codex-login |
Авторизует агента по подписке ChatGPT (OAuth, без API-ключа) |
codex-logout |
Удаляет сохранённую учётку ChatGPT |
codex-status |
Показывает сохранённую учётку ChatGPT (аккаунт, срок, путь) |
games |
Показывает список игр через HTML-страницу домена |
game-list |
Показывает список игр через JSON API |
status |
Показывает текущее состояние игры |
level |
Печатает текст текущего задания |
levels |
Показывает все уровни с прогрессом |
bonuses |
Показывает бонусы текущего уровня |
hints |
Показывает подсказки (обычные и штрафные) |
sectors |
Показывает секторы текущего уровня |
log |
Показывает лог пробитий кодов |
messages |
Показывает сообщения от организаторов |
enter |
Подает заявку на вход в игру |
send-code |
Отправляет код уровня/сектора через LevelAction.Answer |
send-bonus |
Отправляет бонусный код через BonusAction.Answer |
hint |
Запрашивает штрафную подсказку |
game-stats |
Показывает статистику игры (уровни, команды, результаты) |
profile |
Показывает профиль текущего пользователя (ранг, очки, домен) |
import-scenario |
Импортирует Game scenario.html в игру (уровни, задания, подсказки, ответы) |
--llm <prompt> |
Естественно-языковая команда через LLM-агента (OpenRouter или совместимый API) |
-web |
Локальный Web UI для агента (чат, история, режимы безопасности) |
-chat |
Полноэкранный TUI-чат с агентом в терминале (общая история с -web) |
-readonly |
Запретить агенту инструменты, изменяющие игру (для --llm, -web и -chat) |
-llm-auth |
Транспорт агента: apikey (по умолчанию) или codex — подписка ChatGPT |
-v |
Показывает версию |
Admin-команды (требуют прав редактора игры):
| Команда | Что делает |
|---|---|
admin-games |
Показывает список авторских игр |
admin-levels |
Показывает все уровни с их ID (админка) |
admin-create-levels |
Создаёт указанное количество новых уровней |
admin-delete-level |
Удаляет уровень по номеру |
admin-rename-level |
Переименовывает уровень |
admin-set-autopass |
Устанавливает таймер автоперехода |
admin-set-block |
Настраивает блокировку ответов |
admin-create-bonus |
Создаёт бонус на уровне |
admin-delete-bonus |
Удаляет бонус по ID |
admin-create-sector |
Создаёт сектор на уровне |
admin-delete-sector |
Удаляет сектор по ID |
admin-update-sector |
Обновляет сектор по ID |
admin-create-hint |
Создаёт подсказку на уровне |
admin-delete-hint |
Удаляет подсказку по ID |
admin-update-hint |
Обновляет подсказку по ID |
admin-create-task |
Создаёт задание на уровне |
admin-update-task |
Обновляет задание по ID |
admin-set-comment |
Устанавливает название и комментарий уровня |
admin-teams |
Показывает команды в игре |
admin-corrections |
Показывает начисления бонусного/штрафного времени |
admin-add-correction |
Добавляет начисление времени |
admin-delete-correction |
Удаляет начисление по ID |
admin-level-content |
Читает содержимое уровня (задание, секторы, бонусы, подсказки, настройки) |
admin-create-message |
Создаёт игровое сообщение |
admin-messages |
Показывает сообщения уровня |
admin-update-message |
Обновляет сообщение по ID |
admin-delete-message |
Удаляет сообщение по ID |
admin-create-game |
Создаёт новую игру (key=value: title, start, finish обязательны) |
admin-game-info |
Показывает информацию об игре (название, авторы, описание, дата) |
admin-update-game |
Обновляет настройки игры (key=value: title, authors, description, prize, start, finish, request_last_date, moderated) |
admin-deliver |
Помечает игру как состоявшуюся |
admin-award-points |
Начисляет очки участникам |
admin-end-ratings |
Завершает приём оценок |
admin-calc-ik |
Считает игровой коэффициент |
admin-wipe-game |
Полностью обнуляет игру (удаляет всё содержимое) |
admin-copy-game |
Копирует всю игру (уровни, настройки, бонусы, секторы, подсказки) в другую |
admin-delete-game |
Удаляет игру целиком, без возврата (ID повторяется вторым аргументом как подтверждение) |
admin-swap-levels |
Меняет местами два уровня по номеру |
admin-insert-level |
Перемещает уровень на новую позицию |
admin-clone-levels |
Клонирует N уровней с настроек существующего |
admin-delete-task |
Удаляет задание по ID |
admin-update-bonus |
Обновляет бонус по ID (key=value) |
admin-update-hint |
Обновляет подсказку по ID (key=value) |
admin-action-monitor |
Показывает монитор действий в игре |
admin-not-deliver |
Помечает игру как несостоявшуюся |
import-scenario загружает экспорт страницы Game scenario.html (Encounter), удаляет текущие уровни в целевой игре и создаёт новые:
Запросы нового REST API по умолчанию отправляются с интервалом не меньше 40 мс. Заголовки X-Ratelimit-Limit и X-Ratelimit-Remaining могут дополнительно замедлить запросы: клиент распределяет остаток бюджета с запасом 5% (минимум один запрос), а при малом остатке ждёт минуту. Без заголовка сброса минута — консервативное предположение клиента. Retry-After также учитывается. Бюджет общий для клиентов одного API-хоста внутри процесса; другие процессы и устройства за общим IP учитываются только косвенно через ответы сервера. Ожидание не расходует сетевой таймаут, но подчиняется контексту операции. Для изменения минимального интервала: -api-request-interval 200ms (5 запросов/с); серверный бюджет приоритетнее этого параметра. При --sync-missing -har журнал сохраняется и при ошибке импорта.
- названия уровней
- автопереходы
- задания
- подсказки с задержками
- ответы (как секторы)
Для ссылок на локальные изображения (./..._files/...) команда встраивает файлы как data: URL прямо в HTML текста заданий/подсказок.
Если Encounter включает anti-spam (NotHumanRequest.aspx) или запрос попадает в таймаут, import-scenario не падает: CLI возьмёт ссылку на Login.aspx со страницы проверки и попробует автоматически ввести -login/-password (или ENCX_*), затем JSON sign-in; если не выйдет — попросит завершить проверку в браузере и повторит шаг.
Флаг --sync-missing приводит существующие уровни в соответствие со сценарием: при расхождении задания, подсказки и секторы на уровне пересоздаются по экспорту (источник истины — HTML), без полного wipe игры. Недостающие уровни создаются целиком.
encli import-scenario -game-id 82307 "/Users/svk/Downloads/moscow.en.cx __ Game scenario.html"
# Проверка без записи в игру
encli import-scenario -game-id 82307 --dry-run "/Users/svk/Downloads/moscow.en.cx __ Game scenario.html"
# Сверка и выравнивание уровней по сценарию (без полного wipe)
encli import-scenario -game-id 82307 --sync-missing "/Users/svk/Downloads/moscow.en.cx __ Game scenario.html"Почти все можно передавать либо через флаги, либо через env. Удобно, если гоняете команды часто.
| Флаг | Env-переменная | Описание |
|---|---|---|
-domain |
ENCX_DOMAIN |
Домен Encounter (по умолчанию: tech.en.cx) |
-login |
ENCX_LOGIN |
Логин |
-password |
ENCX_PASSWORD |
Пароль |
-game-id |
ENCX_GAME_ID |
ID игры |
-insecure |
ENCX_INSECURE |
Пропустить проверку TLS-сертификата |
-http |
— | Использовать HTTP вместо HTTPS |
-engine |
ENCX_ENGINE |
Движок Encounter: auto (по умолчанию), legacy, new |
-api-base-url |
ENCX_API_BASE_URL |
Хост нового движка (по умолчанию выводится из домена) |
-json |
— | Выводить результат в формате JSON |
-debug |
ENCX_DEBUG |
Включить отладочный вывод в stderr |
-har |
ENCX_HAR |
Записывать HTTP-трафик в HAR 1.2 |
-har-out |
ENCX_HAR_OUT |
Путь экспорта HAR (файл или каталог) |
-web-addr |
ENCLI_WEB_ADDR |
Адрес Web UI (по умолчанию: 127.0.0.1:8787) |
-readonly |
— | Блокировать у агента инструменты записи (см. также режим в Web UI) |
| — | LLM_BASE_URL |
Base URL OpenAI-совместимого API (по умолчанию: https://openrouter.ai/api/v1) |
| — | OPENROUTER_BASE_URL |
Алиас для LLM_BASE_URL |
| — | LLM_API_KEY |
API-ключ для --llm и -web (не нужен для localhost) |
| — | LLM_MODEL |
Модель для агента (по умолчанию: openai/gpt-oss-120b:free) |
-llm-auth |
LLM_AUTH |
Транспорт агента: apikey (по умолчанию), codex — подписка ChatGPT, gigachat — GigaChat API |
| — | ENCLI_CODEX_AUTH_FILE |
Путь к учётке ChatGPT (по умолчанию: ~/.config/encli/codex/auth.json) |
| — | ENCLI_LLM_SETTINGS_FILE |
Путь к настройкам LLM из Web UI (по умолчанию: ~/.config/encli/llm/settings.json) |
| — | ENCLI_ENGINE_SETTINGS_FILE |
Путь к настройкам движка из Web UI (по умолчанию: ~/.config/encli/engine/settings.json) |
| — | ENCLI_ONBOARDING_FILE |
Путь к состоянию мастера первого запуска (по умолчанию: ~/.config/encli/onboarding/state.json) |
| — | GIGACHAT_CREDENTIALS |
Ключ авторизации GigaChat (base64 от Client ID:Client Secret) |
| — | GIGACHAT_SCOPE |
Версия GigaChat API (по умолчанию: GIGACHAT_API_PERS) |
| — | GIGACHAT_MODEL |
Модель GigaChat (по умолчанию: GigaChat-2-Max) |
| — | GIGACHAT_BASE_URL |
Base URL GigaChat (по умолчанию: https://api.giga.chat/v1) |
| — | GIGACHAT_AUTH_URL |
Endpoint OAuth GigaChat (по умолчанию: https://ngw.devices.sberbank.ru:9443/api/v2/oauth) |
| — | GIGACHAT_CA_BUNDLE |
PEM с корневым сертификатом Минцифры — включает проверку TLS для GigaChat |
| — | GIGACHAT_INSECURE |
0 — проверять TLS-сертификат GigaChat (по умолчанию не проверяется) |
| — | LLM_FILES_ROOT |
Корень каталога для read_local_file / search_local_files (по умолчанию: cwd) |
| — | OPENROUTER_API_KEY |
Алиас для LLM_API_KEY |
| — | OPENROUTER_MODEL |
Алиас для LLM_MODEL |
Пример:
export ENCX_DOMAIN=tech.en.cx
export ENCX_LOGIN=my_login
export ENCX_PASSWORD=my_password
export ENCX_GAME_ID=12345
export ENCX_DEBUG=1
# LLM (OpenRouter)
export LLM_API_KEY=sk-or-v1-...
export LLM_MODEL=anthropic/claude-sonnet-4
encli login -insecure
encli status
encli -debug status
encli -game-id 12345 --llm "покажи уровни"В -debug режиме encli пишет в stderr полный разбор аргументов, шаги LLM-агента, запуск и завершение tool-call'ов, а также HTTP-запросы encx с таймингами. Вывод не обрезается — данные показываются целиком для полноценной диагностики.
Подробнее про агента, смену модели и Web UI — в разделе LLM-агент и OpenRouter.
go build -o encli ./cmd/encli/Ниже краткая шпаргалка по основным методам, которые уже завернуты в клиент. Endpoint'ы в таблице — старого движка; чем каждый метод обслуживается на новом, перечислено в матрице паритета.
| Метод | Endpoint | Описание |
|---|---|---|
Login |
POST /login/signin |
Авторизация |
GetGameModel |
POST /gameengines/encounter/play/{id} |
Состояние игры |
GetGameModelLevel |
GET /gameengines/encounter/play/{id}?level={number} |
Уровень, выбранный игроком в штурмовой игре |
SendCode |
POST /gameengines/encounter/play/{id} |
Отправка кода (LevelAction.Answer) |
GetPenaltyHint |
GET /gameengines/encounter/play/{id} |
Запрос штрафной подсказки |
GetGameList |
GET /home/?json=1 |
Список игр (JSON, с пагинацией) |
GetDomainGames |
GET m.{domain}/ |
Список игр (HTML) |
GetGameStatistics |
GET /gamestatistics/full/{id}?json=1 |
Полная статистика игры |
GetTimeoutToGame |
GET m.{domain}/gameengines/encounter/play/{id} |
Таймер до начала |
EnterGame |
GET /MakeGameFee.aspx?confirm=yes&gid={id} (fallback: POST …/makefee/Login.aspx) |
Подать заявку / вступить в игру |
GetGameDetails |
GET /GameDetails.aspx?gid={id} |
Детали игры (HTML) |
GetTeamDetails |
GET /Teams/TeamDetails.aspx?tid={id} |
Информация о команде |
AcceptTeamInvitation |
GET /Teams/TeamDetails.aspx?action=accept_invitation&tid={id} |
Принять приглашение |
Admin API (требует прав редактора):
| Метод | Endpoint | Описание |
|---|---|---|
AdminGetLevels |
GET /Administration/Games/LevelManager.aspx |
Список уровней (ID, названия) |
AdminGetLevelSequence |
GET /Administration/Games/LevelManager.aspx |
Режим выдачи уровней |
AdminSetLevelSequence |
GET /Administration/Games/LevelManager.aspx?sequences=change |
Сменить режим выдачи (3 = штурмовая) |
AdminCreateLevels |
GET /Administration/Games/LevelManager.aspx?levels=create |
Создание уровней |
AdminDeleteLevel |
GET /Administration/Games/LevelManager.aspx?levels=delete |
Удаление уровня |
AdminRenameLevels |
POST /Administration/Games/LevelManager.aspx?level_names=update |
Переименование уровней |
AdminUpdateAutopass |
POST /Administration/Games/LevelEditor.aspx |
Настройка автоперехода |
AdminUpdateAnswerBlock |
POST /Administration/Games/LevelEditor.aspx |
Настройка блокировки ответов |
AdminCreateBonus |
POST /Administration/Games/BonusEdit.aspx?action=save |
Создание бонуса |
AdminDeleteBonus |
GET /Administration/Games/BonusEdit.aspx?action=delete |
Удаление бонуса |
AdminCreateSector |
POST /Administration/Games/LevelEditor.aspx |
Создание сектора |
AdminDeleteSector |
GET /Administration/Games/LevelEditor.aspx?delsector={id} |
Удаление сектора |
AdminCreateHint |
POST /Administration/Games/PromptEdit.aspx |
Создание подсказки |
AdminDeleteHint |
GET /Administration/Games/PromptEdit.aspx?action=PromptDelete |
Удаление подсказки |
AdminCreateTask |
POST /Administration/Games/TaskEdit.aspx |
Создание задания |
AdminUpdateComment |
POST /Administration/Games/NameCommentEdit.aspx |
Обновление названия/комментария |
AdminGetTeams |
GET /Administration/Games/TaskEdit.aspx |
Список команд |
AdminGetCorrections |
GET /GameBonusPenaltyTime.aspx |
Список начислений времени |
AdminAddCorrection |
POST /GameBonusPenaltyTime.aspx?action=save |
Добавление начисления |
AdminDeleteCorrection |
GET /GameBonusPenaltyTime.aspx?action=delete |
Удаление начисления |
AdminGetLevelSettings |
GET /Administration/Games/LevelEditor.aspx |
Чтение настроек уровня (автопереход, блокировка) |
AdminGetBonusIds |
GET /Administration/Games/LevelEditor.aspx |
Список ID бонусов на уровне |
AdminGetBonus |
GET /Administration/Games/BonusEdit.aspx?action=edit |
Чтение деталей бонуса |
AdminGetHintIds |
GET /Administration/Games/LevelEditor.aspx |
Список ID подсказок на уровне |
AdminGetHint |
GET /Administration/Games/PromptEdit.aspx?action=PromptEdit |
Чтение деталей подсказки (обычной и штрафной) |
AdminGetTaskIds |
GET /Administration/Games/LevelEditor.aspx |
Список ID заданий на уровне |
AdminGetTask |
GET /Administration/Games/TaskEdit.aspx?action=TaskEdit |
Чтение деталей задания |
AdminGetComment |
GET /Administration/Games/NameCommentEdit.aspx |
Чтение названия и комментария уровня |
AdminGetSectorAnswers |
GET /ALoader/LevelInfo.aspx |
Чтение секторов и ответов уровня |
AdminGetGameInfo |
GET /Administration/Games/GameEditor.aspx |
Чтение настроек игры (название, авторы, описание, приз, дата) |
AdminUpdateGameInfo |
POST /Administration/Games/GameEditor.aspx |
Обновление настроек игры |
AdminWipeGame |
(комбинированный) | Полная очистка игры (удаление всего содержимого) |
AdminCopyGame |
(комбинированный) | Полное копирование игры (уровни, настройки, бонусы, секторы, подсказки) |
Полная неофициальная (полученная методом реверс-инжиниринга) спецификация API в формате OpenAPI 3.1: openapi.yaml.
Поддерживаемые домены: *.en.cx, *.encounter.cx, *.encounter.ru. Домен quest.ua deprecated — мигрирован в {city}questua.en.cx (напр. kharkov.quest.ua -> kharkovquestua.en.cx).
Для тестирования собственных разработок предусмотрен специализированный домен tech.en.cx. Чтобы получить на нём права создания игр (исключительно в технологических целях) — напишите в техподдержку сети.
Интеграционные тесты ходят в tech.en.cx:
ENCX_INTEGRATION=1 go test ./encx/ -v -count=1Если переменную ENCX_INTEGRATION не задавать, запустятся только юнит-тесты.
Пакет e2e/ содержит сквозные тесты библиотеки encx и CLI encli
против реального домена Encounter. Они исключены из обычной сборки
билд-тегом e2e:
go test -tags e2e ./e2e -v -timeout 15mКонфигурация через переменные окружения:
| Переменная | Назначение | По умолчанию |
|---|---|---|
ENCX_E2E_DOMAIN |
Домен Encounter | svk.en.cx |
ENCX_E2E_LOGIN |
Логин аккаунта | skrashevich |
ENCX_E2E_PASSWORD |
Пароль аккаунта | — |
ENCX_E2E_GAME_ID |
ID игры-песочницы | 82448 |
Аккаунт должен быть автором игры-песочницы. Тесты создают собственные
уровни в конце игры, проверяют контент и удаляют всё созданное; существующие
уровни не изменяются. Если игра-песочница завершилась, тесты сами продлевают
дату её окончания. CLI-тесты собирают encli во временный каталог и работают
с изолированным HOME, не трогая сессии в ~/.config/encli.






