Skip to content

Repository files navigation

encx-cli

GitHub stars Last commit License Ask DeepWiki Download binaries

encx-cli — это Go-клиент и CLI для API движка городских квестов Encounter (en.cx).

Если короче: здесь есть и библиотека для встраивания в свой код, и консольная утилита, которой можно быстро залогиниться, посмотреть игру, прочитать задания и отправить код без браузера.

  • Модуль: github.com/skrashevich/encx-cli
  • CLI: cmd/encli
  • Пакет-библиотека: encx
  • Тестовый домен для примеров и интеграционных тестов: tech.en.cx

Скриншоты

Снимки автоматически пересоздаются при изменении web-ui. Локальная генерация и CI.

Общий вид: боковая панель с историей чатов, выбор домена и игры, авторизация, поле ввода и режим агента (только чтение / с согласованием / полный доступ). В левом верхнем углу — текущая LLM-модель.

Общий вид Web UI encli

Чат с агентом: запрос на естественном языке и пошаговые вызовы инструментов (admin API, чтение уровней и т.д.).

Чат с агентом и логом инструментов

Тёмная тема (переключатель ◐ в верхней панели).

Web UI encli в тёмной теме

Что внутри

Проект пригодится в двух сценариях:

  • хотите написать свой тулинг поверх 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 не покажет предупреждение о недоверенном разработчике.

CLI

go install github.com/skrashevich/encx-cli/cmd/encli@latest
go install github.com/skrashevich/encx-cli/cmd/encx-mock@latest

Mock-сервер

Для локальной разработки и ручной проверки CLI в репозитории есть encx-mock — небольшой HTTP-сервер, который имитирует домен Encounter без похода в реальный tech.en.cx.

encx-mock: локальный сервер и encli

encx-mock: сценарий из HTML и эмуляция потери сети (PZDC)

Скрипты 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

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.

iOS

Нативное приложение вынесено в отдельный репозиторий enkapp. Здесь остаётся только gomobile-библиотека и сборка Encx.xcframework.

Пакет mobile/encxmobile — gomobile-обёртка над encx. API возвращает JSON-строки, которые декодируются в Swift через JSONDecoder.

Сборка Encx.xcframework

# из корня репозитория
./mobile/bind-ios.sh

# результат: mobile/build/Encx.xcframework

Скрипт устанавливает gomobile, прогоняет тесты и собирает фреймворк для устройства и симулятора.

Подключение в Xcode

См. также enkapp — готовый Xcode-проект с этим фреймворком.

  1. Перетащите Encx.xcframework в проект (Embed & Sign).
  2. Добавьте в Info.plist разрешение на сеть (если ещё нет):
<key>NSAppTransportSecurity</key>
<dict>
  <key>NSAllowsArbitraryLoads</key>
  <true/>
</dict>

Для production лучше настроить ATS exceptions только для нужных доменов (*.en.cx).

Пример (Swift)

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 Тексты ошибок

PHP

Тот же пакет 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.

CLI: encli

encli полезен, когда нужно быстро дернуть API руками и не городить под это отдельный код.

encli: логин, список игр, статус и отправка кода

Демо использует публичный mock-сервер encounter.exe.xyz. GIF обновляются автоматически через GitHub Pages.

encli: уровни, секторы, бонусы, подсказки и лог пробитий

Типичный поток такой:

  1. залогиниться;
  2. выбрать игру;
  3. смотреть статус, задания и сообщения;
  4. отправлять коды и запрашивать подсказки.
# Авторизация (интерактивный ввод пароля)
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 12345

LLM-агент и OpenRouter

encli встраивает PicoClaw с tool-calling: агент читает состояние игры, вызывает admin-команды, ищет факты в Википедии и читает локальные файлы со сценарием. Один runtime используется в CLI (--llm), локальном Web UI (-web) и Docker (через -e).

API-ключ и модель

По умолчанию используется 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 вместо API-ключа

Вместо ключа агент умеет работать на подписке 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 (Сбер)

Агент умеет работать на 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).

Режим --llm

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 повторов с нарастающей задержкой.
  • После создания или изменения уровней агент проверяет результат (коды, тайминги, подсказки, задания).

Web UI (-web)

Локальный чат с тем же агентом, историей диалогов и переключателем режима безопасности (только чтение / с подтверждением / полный доступ):

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=....

Polza.AI: доступ без аккаунта ChatGPT

В мастере и панели 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 в Web UI

Переменные окружения задавать не обязательно: кнопка «Настройки 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) выполняется как обычно.

TUI-чат (-chat)

Консольный аналог 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 — прокрутка.

Импорт HTML-сценария в чате

Для файла экспорта 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 и создай уровни по сценарию"

Команды CLI

Команда Что делает
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 Помечает игру как несостоявшуюся

Импорт сценария из HTML

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/

API

Ниже краткая шпаргалка по основным методам, которые уже завернуты в клиент. 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-тесты

Пакет 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.

About

Go client library and CLI for the Encounter (en.cx) quest engine API

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages