Studio — веб-интерфейс
Весь тулчейн во вкладке браузера, с живыми логами и откатом в один клик. Только localhost — намеренно.
handbook studio # → http://127.0.0.1:4860
handbook studio --port 5000 # or: pnpm studio --port 5000Те же кодовые пути, что и у CLI, то же разрешение конфигурации, те же артефакты на диске — просто другой способ управления.
Нулевой шаг сборки. UI — это один написанный вручную HTML-файл со встроенным CSS и ванильным JS. Ни бандлера, ни фреймворка, ничего с CDN. Он загружается мгновенно и работает с выдернутым сетевым кабелем.
Что в нём можно делать
| Область | Что она делает |
|---|---|
| Репозитории | Зарегистрировать дерево исходников + рабочий каталог под URL-безопасным именем |
| Генерация | Полный набор параметров, живая трансляция логов по SSE, отмена посреди запуска |
| Просмотр handbook | Читать отрендеренный handbook на месте |
| Граф влияния | Какие файлы принадлежат этапу, кто вызывает его и кого вызывает он |
| Просмотр исходников | Открыть реальный файл за любой карточкой, на процитированной строке |
| Планирование | Ввести запрос, наблюдать за работой read-only-агента, прочитать план |
| Применение / откат | Пробный прогон, применение, список всех резервных копий, откат в один клик |
| Resync | Прокатить handbook вперёд по живому дереву — не собирая каталог кейса |
| История | Эволюция по каждому репозиторию: что изменил каждый запуск и когда |
Задачи
Генерация, планирование и resync выполняются как фоновые задачи с захваченным логом, отдаваемым по Server-Sent Events.
- Одна задача на репозиторий одновременно. Артефакты конвейера небезопасны для конкурентных писателей в одном рабочем каталоге; второй запуск отклоняется с внятным сообщением.
- Отменяемо. У каждой задачи есть
AbortController, чей сигнал доходит до LLM-запросов в полёте. Отмена означает отмену, а не «перестань показывать лог». - Статусы:
running→succeeded|failed|cancelled. Полный лог сохраняется, так что можно прочитать, что произошло, уже после завершения.
Конфигурация
Studio разрешает свои настройки из тех же уровней, что и любая другая команда, —
флаги, окружение, каскад .env, handbook.config.yaml, значения по умолчанию:
handbook studio --model gpt-4o --base-url https://my-proxy/v1 --port 5000
handbook --env prod studioЗадача генерации, запущенная из UI, видит тот же слой конфигурационного файла, что и CLI,
поэтому detail, narrateLang, readWorkers и всё остальное работают из YAML.
Модель безопасности
Studio — локальный инструмент. Он не укреплён для внешнего доступа и не притворяется укреплённым.
- По умолчанию слушает
127.0.0.1. - CSRF-защита проверяет заголовок запроса
Host, а не сокет. Проходят только loopback-имена хостов. POSTтребуетapplication/json, что блокирует классическую кросс-доменную атаку через HTML-форму.- Имена репозиториев валидируются по
^[A-Za-z0-9][A-Za-z0-9._-]*$до того, как коснутся файловой системы, а пути нормализуются через realpath. - Раздача файлов исходников и handbook заперта в песочнице зарегистрированных корней.
В контейнере
pnpm run docker:studio # docker compose up --build studioКонтейнер обязан слушать 0.0.0.0, чтобы опубликованный порт вообще был достижим
(HANDBOOK_STUDIO_HOST=0.0.0.0 в docker-compose.yml).
Работает только http://localhost:4860
Не IP из LAN и не имя контейнера. Обращение из браузера на хосте по-прежнему отправляет Host: localhost:4860 и проходит; запрос с IP из LAN или именем хоста контейнера отклоняется с 403 — так
задумано. Удалённый доступ — намеренно нереализованная отдельная возможность (ей понадобился бы явный
список разрешённых), а не пробел в этой защите.
Состояние
~/.handbook-studio/
studio.json the repository registry (schema-validated on read)
work/<name>/ auto-created work dirs for repos that did not bring their own--state-dir перемещает его. Всё остальное — артефакты handbook, история эволюции —
живёт в рабочем каталоге каждого репозитория, поэтому удаление каталога состояния теряет
реестр и ничего по-настоящему важного.
Скриптование поверх него
UI — просто клиент. HTTP API достаточно стабилен, чтобы писать скрипты:
curl -s http://localhost:4860/api/repos | jq
curl -s -X POST http://localhost:4860/api/repos \
-H 'content-type: application/json' \
-d '{"name":"api","sourceRoot":"/Users/me/code/api","workDir":"/Users/me/work/api"}'
curl -s -X POST http://localhost:4860/api/repos/api \
-H 'content-type: application/json' \
-d '{"action":"analyze"}'
curl -N http://localhost:4860/api/jobs/<job-id> # SSE log streamПолная таблица маршрутов — в README пакета.