Handbooks
Справочник

Переменные окружения

Каждая переменная, которую читает Handbooks, правило именования, которое их порождает, каскад .env и те, что никогда не должны попадать в файл конфигурации.

Правило именования

У каждой настройки в реестре один ключ в camelCase. Из него одним и тем же преобразованием выводятся три имени:

ПоверхностьИз readWorkersС областью видимости generate
Флаг--read-workers <n>
ОкружениеHANDBOOK_READ_WORKERSHANDBOOK_GENERATE_READ_WORKERS
Ключ в файле конфигурацииreadWorkersgenerateReadWorkers или вложенно generate: { readWorkers: }

Форма с областью видимости всегда побеждает плоскую. Именно это позволяет сказать «повествование по-китайски, но только при генерации», ничего больше не трогая.

export HANDBOOK_NARRATE_LANG=en              # everywhere
export HANDBOOK_GENERATE_NARRATE_LANG=zh     # …except generate

Несколько настроек существуют только в форме с областью видимости, потому что их смысл меняется от команды к команде: --out (HANDBOOK_RENDER_OUT, HANDBOOK_SKILL_OUT, HANDBOOK_PLAN_OUT), --handbook (HANDBOOK_SKILL_HANDBOOK, HANDBOOK_PLAN_HANDBOOK), --lang у skill (HANDBOOK_SKILL_BODY_LANG) и --detail / --narrate-lang у resync (HANDBOOK_RESYNC_CARD_DETAIL, HANDBOOK_RESYNC_PROSE_LANG).

Вендорские псевдонимы

Семь настроек принимают также те имена, которые у людей уже экспортированы:

НастройкаПсевдоним
llmApiKeyOPENAI_API_KEY
llmProviderOPENAI_PROVIDER
llmModelOPENAI_MODEL
llmBaseUrlOPENAI_BASE_URL
llmMaxTokensOPENAI_MAX_TOKENS
llmTimeoutOPENAI_TIMEOUT
llmExtraBodyOPENAI_EXTRA_BODY

Порядок поиска такой: с областью видимости HANDBOOK_<CMD>_<KEY> → плоская HANDBOOK_<KEY> → вендорский псевдоним.

Переменные начальной загрузки

Три настройки разрешаются раньше всего остального, потому что всё остальное от них зависит. Ни одну из них нельзя задать тем, что она загружает — ключу --env внутри handbook.config.yaml было бы уже некому его прочитать.

Переменная / флагЧто делает
HANDBOOK_ENV / --env <name>Выбирает каскад .env для конкретного окружения и предпочитает handbook.config.<name>.yaml
--env-file <path> / HANDBOOK_ENV_FILEЗагружает ровно этот один файл, минуя каскад. Отсутствующий файл — громкая ошибка. Предпочитайте переменную: у Node >= 20.6 тоже есть --env-file, и он сканирует командную строку заранее, поэтому отсутствующий путь умирает как node: <path>: not found (код 9) ещё до запуска Handbooks. Флаг побеждает переменную, когда заданы оба
--config <path>Называет один конкретный файл конфигурации, минуя поиск

Каскад .env

Без --env-file CLI загружает каскад файлов .env* из текущей директории, в порядке убывания приоритета:

#ФайлКтоОбластьВ коммите?
1окружение шеллавсегда побеждает
2.env.<name>.localличноетолько это окружениенет (в gitignore)
3.env.<name>командноетолько это окружениеда
4.env.localличноекаждое окружениенет (в gitignore)
5.envкомандноебазовый уровеньда

Строки 2 и 3 применяются только тогда, когда --env/HANDBOOK_ENV называет окружение. Если не задано ни то ни другое, загружаются только строки 4 и 5.

Весь каскад — это «вызови их в таком порядке, побеждает тот, кто записал первым», потому что загрузка файла никогда не перекрывает уже заданный ключ. Именно это одно правило и удерживает шелл выше любого файла, безо всякой дополнительной логики где бы то ни было.

handbook generate --env prod --source ~/code/api --work work/api
# loads .env.prod.local → .env.prod → .env.local → .env
# and prefers handbook.config.prod.yaml over handbook.config.yaml

Каскад работает только в текущем каталоге

В отличие от handbook.config.yaml, который находится подъёмом вверх до корня git, файлы .env читаются из той директории, в которой вы запускаете команду. .env означает «эта машина, прямо сейчас». Запускайте команды с LLM из корня репозитория или передавайте --env-file.

Что принимает парсер .env

KEY=value, необязательный префикс export , пустые строки, строки-комментарии с #, значения в одинарных и двойных кавычках (кавычки снимаются) и завершающий встроенный комментарий # у значения без кавычек. Работают переводы строк CRLF, LF и одиночный CR. Многострочных значений нет.

Пустое значение читается как незаданноеHANDBOOK_TITLE= не даст вам руководство без заголовка.

Секреты

В реестре как secret помечены две настройки — llmApiKey / OPENAI_API_KEY и llmExtraBody / OPENAI_EXTRA_BODY. Для обеих это значит:

  • она никогда не является флагом командной строки (флаги попадают в историю шелла и в вывод ps);
  • она отклоняется, если появляется в файле конфигурации, с сообщением о причине — файлы конфигурации попадают в коммиты;
  • она маскируется в выводе handbook config.

llmExtraBody — секрет потому, что форма у него произвольная. Он подмешивает всё, что вы в него положили, в тело каждого запроса, а шлюзы вполне принимают аутентификацию в теле, — так что инструментарий не может ни перечислить, что там лежит, ни отличить поле тонкой настройки от учётных данных. Флага у него нет вовсе; пользуйтесь переменной окружения.

llmBaseUrl секретом намеренно не является: если вся команда направляет каждый свой клон на один общий шлюз, закоммитить его — законное желание. Отклоняется в файле конфигурации только URL со встроенными учётными данными (https://user:pass@gw.internal/v1) — там и больше нигде.

handbook.config.yaml: llmApiKey must not appear in a config file (it gets committed)
— use .env or the shell environment instead

Docker

В образ зашиты HANDBOOK_SOURCE=/src и HANDBOOK_WORK=/work, поэтому вам остаётся только примонтировать тома:

docker run --rm -v "$PWD:/src:ro" -v handbook-work:/work handbook:local analyze

Собственный --env-file у Docker накладывается поверх загрузки .env инструментарием — работают оба, и переменная OPENAI_*, переданная таким образом, видна ровно так же, как была бы видна при экспорте в шелле. Файлы .env* никогда не запекаются в образ; см. .dockerignore.

Как увидеть, что на самом деле разрешилось

handbook config --command generate

печатает активное окружение, каждый файл .env, который загрузил каскад, найденный файл конфигурации и по одной строке на настройку с её происхождением — flag, env, file или default.

handbook config --check    # exit 2 on the first invalid or missing value

Поставьте --check в CI

Переменная с опечаткой раньше означала «молча отработали на значении по умолчанию». Теперь это сбой, и переменная названа в сообщении — а найти это в CI намного дешевле, чем на сороковой минуте прогона генерации.

Полный список

Каждая переменная, с её типом, значением по умолчанию и описанием, есть на странице Справочник конфигурации — которая генерируется из того же реестра, что читает CLI, поэтому разойтись с ним она не может.

Файл .env.example в корне репозитория тоже генерируется из этого реестра. Каждая строка в нём начинается закомментированной, поэтому копировать файл целиком безопасно.

На этой странице