dzzzr-cli

Go-библиотека и консольная утилита для движка городской игры «Дозор Классик»
(classic.dzzzr.ru). Позволяет играть и администрировать игру без браузера, а также
встраивать клиент движка в свой код.
В комплекте: библиотека, CLI, mock-сервер движка для разработки, инструменты для
LLM-агентов с MCP-сервером и gomobile-биндинги для мобильного приложения игрока.
Проект повторяет по структуре и функциям
encx-cli — такой же клиент для движка
Encounter.
Что внутри
| Каталог |
Назначение |
dzzzr/ |
клиент движка: игрок и организатор |
cmd/dzzzr/ |
консольная утилита |
cmd/dzzzr-mock/ |
mock-сервер движка, см. README |
agenttools/ |
каталог инструментов движка для LLM-агентов с политиками доступа |
agentmcp/ |
MCP-сервер над этим каталогом |
agentloop/ |
общее ядро LLM-агента для CLI/web/WASM; протокол и нативные провайдеры PicoClaw |
agentfiles/ |
чтение локальных файлов и сохранение новых JSON-документов агентом |
gamesource/, pdfsource/ |
импорт сценария игры из JSON и PDF |
mobile/dzzzrmobile/ |
gomobile-обёртка для iOS и Android |
e2e/ |
сквозные тесты: собранная утилита против mock-сервера |
Установка
go install github.com/skrashevich/dzzzr-cli/cmd/dzzzr@latest
go install github.com/skrashevich/dzzzr-cli/cmd/dzzzr-mock@latest
Библиотека:
go get github.com/skrashevich/dzzzr-cli/dzzzr
Docker:
docker run --rm ghcr.io/skrashevich/dzzzr-cli version
docker run --rm -p 18090:18090 -e DZZZR_MOCK_ADDR=0.0.0.0:18090 ghcr.io/skrashevich/dzzzr-mock
Быстрый старт
По умолчанию используется https://classic.dzzzr.ru/moscow/. Флаг -city
меняет город в пути; -base-url или DZZZR_BASE_URL задаёт адрес движка целиком.
# вход: спросит недостающие логин и пароль
dzzzr login -city moscow
# что происходит в игре
dzzzr status
# текст задания и подсказки
dzzzr level
dzzzr hints
# отправить код
dzzzr send-code "(112)D45R92"
Всё это можно попробовать против mock-сервера, ничего не задев на боевом движке:
dzzzr-mock & # слушает 0.0.0.0:18090
export DZZZR_BASE_URL=http://127.0.0.1:18090/moscow/
dzzzr login -login demo -password demo -captain demo -pin 1234
dzzzr status
dzzzr send-code 1
Отправка кода завершается кодом выхода 0, если движок код принял, и 2, если
отклонил, — удобно в скриптах:
if dzzzr send-code "$code"; then echo принят; else echo мимо; fi
Полный список команд игрока и организатора, флагов и переменных окружения —
в docs/cli.md.

Ещё записи — прохождение уровня, mock-сервер, организаторская часть — на
GitHub Pages. Тапки VHS лежат в
docs/vhs/, собрать локально: ./docs/vhs/render.sh (нужны vhs,
ttyd, ffmpeg).
Авторизация
- Аккаунт сайта — логин и пароль.
API/login.php меняет их на токен сессии.
Этого достаточно, чтобы играть.
- Заголовок HTTP Basic — движок требует, чтобы он просто был. Утилита
подставляет
{город}_{логин} с пустым паролем; логин капитана и PIN
(-captain, -pin) нужны только там, где организатор их выдал.
- Учётная запись организатора — отдельные логин и пароль от админки, с игровыми
никак не связаны.
Утилита хранит всё это в ~/.config/dzzzr/<город>.json с правами 600
($XDG_CONFIG_HOME/dzzzr, если переменная задана; на Windows — %AppData%\dzzzr,
где права файла заменяет наследуемый доступ к профилю). Каталог целиком
переопределяется переменной DZZZR_CONFIG_DIR. Файл содержит секреты — не кладите
его в общий каталог.
ИИ-агент
Движок можно отдать LLM-агенту как каталог инструментов — по MCP или встроенным
циклом прямо из утилиты:
dzzzr mcp # MCP-сервер, только чтение
export DZZZR_LLM_API_KEY=sk-…
dzzzr agent "на каком мы уровне и сколько кодов осталось"
dzzzr chat # полноэкранный диалог в терминале
dzzzr web # тот же агент в браузере, рядом — редактор игры
Политика доступа (readonly / approve / full) задаётся флагом -security.
Агент умеет импортировать сценарий игры из PDF и читать публичные веб-страницы
инструментом fetch_url — HTML приводится к тексту, кодировка распознаётся,
непубличные адреса (localhost, локальная сеть, служебные диапазоны) не читаются.
Содержимое страницы — данные, а не инструкции. Подробно — в
docs/agent.md.
Настройка модели
Переменные окружения не обязательны: при первом запуске dzzzr web или
dzzzr editor открывается мастер настройки. Он проходит два шага: подключает
модель и выполняет вход в Дозор игроком или организатором. Способы подключения
модели:
- Polza.ai — вход в аккаунт или API-ключ, выбор модели с поддержкой
инструментов, проверка баланса;
- подписка ChatGPT — вход через браузер; если уже выполнен вход в Codex CLI
(
~/.codex/auth.json), dzzzr использует его;
- OpenAI-совместимый провайдер — адрес API, ключ и модель (по умолчанию
OpenRouter).
Редактору модель не нужна, поэтому её настройку можно отложить кнопкой
«Настроить позже». Позже настройки меняются кнопкой ⚙ рядом с названием модели,
а мастер открывается снова кнопкой ◈.
Настройки сохраняются в <каталог настроек>/llm/settings.json, вход ChatGPT —
в codex/auth.json рядом. Оба файла доступны только владельцу. Их читают и веб,
и dzzzr chat, и dzzzr agent. Выход из аккаунта игрока их не удаляет.
Переменные окружения (DZZZR_LLM_PROVIDER, DZZZR_LLM_API_KEY,
DZZZR_LLM_BASE_URL, DZZZR_LLM_MODEL и их варианты LLM_*, OPENROUTER_*)
важнее сохранённого. Панель показывает, какая переменная перекрывает значение.
Веб-интерфейс
dzzzr web поднимает локальный сервер (по умолчанию 127.0.0.1:8788) с тем же
агентом, что и dzzzr chat: переписка нескольких чатов, потоковый ответ,
согласование действий, вход на сайт города и файлы, которые агент сохранил за
время чата. Наружу ничего не публикуется, слушает только петлю.
Статистика по выгрузке журнала открывается на вкладке «Статистика» рядом
с чатом и редактором (#/stats) или отдельной командой (учётные данные движка и LLM не требуются):
dzzzr log-stat # выбрать XLSX/JSON/CSV в браузере
dzzzr log-stat /path/to/log-1566.xlsx # сразу открыть выбранный журнал
dzzzr log-stat /path/to/log-1566.json # JSON-выгрузка журнала
dzzzr log-stat /path/to/log-1566.xlsx -json > statistics.json
Автор исходного алгоритма расчёта статистики —
Sergey <sergey@luberg.me> Luberg.
Алгоритм портирован на Go;
Пакет gamestats на Go читает XLSX/XLSM, JSON и CSV/TSV (UTF-8 или Windows-1251),
восстанавливает прохождение, таймауты, отказы и зачёты организатора, считает
штрафы, бонусы, места и отставания. Браузер отображает таблицу в формате DozoR,
детализацию команд и уровней и отправляет изменения параметров на пересчёт
в бэкенд. Экспорт XLSX с листами «Статистика» и «Детали» также выполняется в Go.
Файл обрабатывается локальным сервером. Просмотр статистики не отправляет журнал
в движок или LLM; при обсуждении агент получает запрошенные им результаты и события.
Максимальный размер файла — 32 МБ.
JSON принимается как объект с номерами строк ({"1":["Время",...],"2":[...]})
или массив строк ([["Время",...],[...]]), со столбцами в том же порядке, что в XLSX.
Числовые ключи сортируются по номеру строки; пустые ячейки могут быть null.
Лимиты восстанавливаются из событий, неизвестные параметры можно исправить
в «Параметрах подсчёта». Размеры бонусов в журнале отсутствуют. Для игры 1566
сохранён набор параметров из предоставленного HTML-образца, с пояснением
предположений в интерфейсе; к другим играм этот набор не применяется.
В общем web UI журнал и параметры сохраняются в <каталог настроек>/web/statistics.
Ссылка /?stats=<log_id>#/stats восстанавливает расчёт после перезагрузки.
В отдельной команде log-stat журнал не сохраняется на диск: после перезагрузки
его нужно выбрать заново, параметры восстанавливаются из браузера.
Кнопка «Обсудить с агентом» открывает чат с идентификатором текущего журнала.
Агент работает с тем же расчётом: stats_load получает полный журнал из движка
по game_id или читает локальный XLSX/JSON/CSV; stats_report возвращает итоги,
ячейки и замечания; stats_search ищет исходные события по команде, уровню,
игроку, действию, тексту и времени с пагинацией; stats_configure меняет параметры
локального расчёта, не затрагивая движок. Поддерживаются и вложения текущего чата.
При возвращении во вкладку статистики (или по кнопке «Обновить от агента») UI
читает актуальные параметры. Конфликтующие версии не перезаписываются.
В CLI/MCP журналы инструментов хранятся в .dzzzr-statistics внутри DZZZR_FILES_ROOT.
Замечания об аномалиях — поводы проверить исходные события, не доказательства нарушений.
Время трактуется как часы движка, независимо от часового пояса компьютера.
Кнопка «скачать .html для офлайна» сохраняет самостоятельный интерактивный отчёт.
В просмотрщиках с отключёнными скриптами доступны заранее рассчитанная таблица,
подробности команд, события и параметры уровней. Изменять расчёт там нельзя.
В современном браузере без сервера и интернета доступны подробности
команд и уровней, замечания по журналу, изменение лимитов, подсказок, бонусов и
штрафов, скрытие команд/уровней и экспорт XLSX. HTML содержит полный исходный журнал
и выбранные параметры. Повторный экспорт HTML сохраняет новый вариант расчёта;
«Сбросить параметры» возвращает исходные настройки алгоритма.
Для офлайн-пересчёта тот же пакет gamestats компилируется в WebAssembly; формулы
не дублируются в JavaScript. Обычный web UI продолжает рассчитывать статистику
на Go-бэкенде. Встроенный модуль хранится в репозитории, поэтому готовый бинарник
не требует установленного Go. При изменении gamestats, зависимостей или версии Go
пересоберите его: go generate ./internal/statsoffline. Тест проверяет актуальность
исходников и, при наличии Node.js, совпадение результатов WASM и обычного Go.
Рядом с чатом — переключатель «Редактор» (dzzzr editor открывает браузер
сразу в нём): ручная заливка игры для автора,
которому проще заполнить форму, чем диктовать уровень модели. Тот же сервер,
тот же клиент движка, та же организаторская сессия. Формы игры и задания
рисуются по той же JSON Schema, которую принимают инструменты агента
admin_create_game и admin_create_level, поэтому поле, добавленное агенту,
появляется и в форме: тест не даст им разойтись. В боковой панели — игры
организатора и задания выбранной игры с добавлением, удалением и
перестановкой; в главной области — поля по группам, таблицы основных,
бонусных, ложных и спойлерных кодов со вставкой списком, локальная проверка
перед записью, выгрузка и заливка сценария целиком и кнопка «Спросить агента»,
открывающая постоянный чат общего черновика игры.
Лист кодов вставляется не только из буфера: «Из файла» читает .txt, .csv
или .tsv — windows-1251 распознаётся сама, — и кладёт прочитанное в то же
поле вставки, чтобы автор увидел разбор до записи. Непрочитанное разбор
называет вслух, а не подставляет вместо него ноль или пустую сложность.
Поля автоматически сохраняются в локальный черновик. Переключение в чат
передаёт агенту актуальные несохранённые правки, а при возврате в редактор
виден результат его работы. Повторный переход открывает тот же чат.
Новые задания и черновик восстанавливаются после перезагрузки страницы.
Разные поля объединяются автоматически; при одновременной правке одного поля
интерфейс предлагает выбрать версию. В движок поля записываются только кнопкой
«Сохранить в игру» для открытой игры или задания. В привязанном чате агент
использует draft_*; инструменты изменения движка ему не выдаются.
Задание, подсказки и легенда — то, что движок показывает игрокам как HTML, —
правятся с панелью форматирования и переключателем на исходную разметку;
картинка кнопкой загружается в файлы самой игры, а относительные ссылки движка
разрешаются только для показа, так что на движок уходит ровно то, что в поле.
Организатором входят в самом редакторе: логин и пароль проверяются на админке и
сохраняются в тот же файл сессии, где их оставил бы -admin-login. Этот вход —
общий: после него в чате становятся доступны инструменты admin_*, потому что
набор инструментов собирается заново на каждый ход из того же клиента. Ни один
из двух видов не главный. Игровая сессия редактору не нужна — запуск
dzzzr -admin-login … -admin-password … web открывает только организаторскую
часть и сразу в редакторе. Адрес страницы (#/editor/game/4242/level/901)
называет открытую игру и задание, так что перезагрузка и «назад» возвращают
туда же, а без адреса страница открывает вид, в котором работали последним.
Подробно — в docs/webui.md.
На Windows запуск dzzzr.exe без команды из Проводника открывает браузер
с редактором организатора. Из cmd.exe, PowerShell и других терминалов запуск
остаётся консольным: без команды выводится справка. Чтобы открыть редактор
из терминала явно, используйте dzzzr editor.
Способ запуска определяется по родительскому процессу; для неизвестных
запускателей используется число процессов в консоли. Если автоматически
запущенный сервер завершился с ошибкой, окно удерживается до нажатия Enter.
Любое непустое значение DZZZR_NO_AUTO_WEB выключает автозапуск —
в том числе DZZZR_NO_AUTO_WEB=0.
|
|
 |
 |
| Переписка, вызовы инструментов, выбор прав агента |
Тёмная тема |
 |
 |
| Статус хода, инструменты, потоковый ответ |
Согласование действия под политикой approve |
 |
 |
| Файлы, сохранённые агентом за чат |
Вход на сайт одного города |
 |
 |
| Задания игры и форма уровня по группам полей |
Тёмная тема |
 |
|
| Лист кодов вставляется одним блоком |
|
Скриншоты и GIF ниже пересобираются в CI
(.github/workflows/media.yml) из
docs/shots/ и docs/vhs/ и публикуются на
GitHub Pages.
Мобильные биндинги
mobile/dzzzrmobile — gomobile-обёртка: методы возвращают JSON-строки, все целые
int64, контекста нет.
./mobile/bind-ios.sh # → mobile/build/dzzzr.xcframework
./mobile/bind-android.sh # → mobile/build/dzzzr.aar (нужны ANDROID_HOME и NDK)
Разработка
go build ./...
go vet ./...
go test ./... -count=1
go test -race ./... -count=1
go test -tags e2e ./e2e -count=1 # собранная утилита против mock-сервера
golangci-lint run ./... # нужен golangci-lint v2
Живые тесты против боевого движка включаются переменными DZZZR_INTEGRATION=1,
DZZZR_E2E_CITY, DZZZR_E2E_LOGIN, DZZZR_E2E_PASSWORD, DZZZR_E2E_CAPTAIN,
DZZZR_E2E_PIN. Без них они пропускаются.
Демо-медиа для README собираются локально теми же скриптами, что и в CI:
./docs/vhs/render.sh # GIF CLI → docs/gifs/ (нужны vhs, ttyd, ffmpeg)
./docs/shots/render.sh # скриншоты web-ui → docs/screenshots/ (нужен Node)
Оба каталога в .gitignore: workflow
media.yml пересобирает их при изменениях в
docs/vhs/, docs/shots/, cmd/dzzzr/ или cmd/dzzzr-mock/ и публикует на
GitHub Pages. Скриншоты снимаются с настоящего dzzzr web поверх dzzzr-mock;
переписка в чатах — фикстуры из docs/shots/fixtures/.
Документация
Лицензия
Apache 2.0, см. LICENSE.
Браузерная версия (WebAssembly)
Открыть приложение на GitHub Pages.
Тот же Go-код работает в браузере: статистика XLSX/JSON/CSV, поиск событий,
HTML/XLSX-экспорт, извлечение и индексирование PDF, применение PDF mapping,
валидация полных сценариев и пакетов уровней.
Раздел «Редактор» использует формы нативного web-ui: игра, задания, HTML-тексты,
подсказки, основные/бонусные/ложные коды, спойлеры, секторы и координаты.
Можно создавать и копировать игры, добавлять, удалять и переставлять уровни,
импортировать и экспортировать полный сценарий с вложениями. JSON-экспорт
включает актуальные черновики, сохраняет исходный HTML, source и extensions.
Кнопка «Сохранить локально» записывает документ в локальную игру.
Агент работает через общее с CLI и нативным web-ui ядро agentloop: до 200 шагов,
повторные попытки, таймаут каждого запроса, отмена, проверка аргументов инструментов,
сохранение исходного контекста PDF, события инструментов и отчёт о токенах/времени.
«Спросить агента» связывает диалог с открытым черновиком. draft_read,
draft_open_level, draft_update, draft_add_level читают и меняют те же документы,
что формы; проверка revision защищает правки автора. scenario_create,
scenario_import, scenario_list, scenario_read, scenario_export работают
с локальными играми. Доступны режимы чтения, локального редактирования и подтверждения
каждого изменения. В настройках LLM задаются лимит шагов, таймаут и дополнительные
поля OpenAI-совместимого запроса.
LLM подключается напрямую к указанному HTTPS API; провайдер должен разрешать CORS.
API key хранится только в памяти вкладки. Модель получает сообщения и результаты
выбранных инструментов. Игры, черновики и текст диалогов сохраняются в IndexedDB;
рабочее пространство можно скачать и восстановить из JSON. Загруженные файлы,
журнал и ключ нужно загрузить заново после перезагрузки. Очистка данных сайта удаляет
локальные игры, поэтому экспортируйте сценарии или резервную копию.
После первого успешного кэширования редактор и локальные операции доступны офлайн.
Для LLM нужен интернет. OCR, вход и публикация в движок, MCP-сервер, shell
и доступ к произвольным файлам ОС доступны в нативном CLI/web UI.
Сборка: go run ./cmd/build-browser -out dist/browser. Для локальной проверки
раздайте каталог через HTTP, например python3 -m http.server --directory dist/browser.
Workflow Demo media публикует приложение в /app/, сохраняя демостраницу и медиа.
В артефакт включаются только явно перечисленные ресурсы, без пользовательских журналов,
сценариев, настроек или ключей. Формулы статистики на JavaScript не дублируются.