Переменные окружения
Каждая переменная, которую читает Handbooks, правило именования, которое их порождает, каскад .env и те, что никогда не должны попадать в файл конфигурации.
Правило именования
У каждой настройки в реестре один ключ в camelCase. Из него одним и тем же преобразованием выводятся три имени:
| Поверхность | Из readWorkers | С областью видимости generate |
|---|---|---|
| Флаг | --read-workers <n> | — |
| Окружение | HANDBOOK_READ_WORKERS | HANDBOOK_GENERATE_READ_WORKERS |
| Ключ в файле конфигурации | readWorkers | generateReadWorkers или вложенно 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).
Вендорские псевдонимы
Семь настроек принимают также те имена, которые у людей уже экспортированы:
| Настройка | Псевдоним |
|---|---|
llmApiKey | OPENAI_API_KEY |
llmProvider | OPENAI_PROVIDER |
llmModel | OPENAI_MODEL |
llmBaseUrl | OPENAI_BASE_URL |
llmMaxTokens | OPENAI_MAX_TOKENS |
llmTimeout | OPENAI_TIMEOUT |
llmExtraBody | OPENAI_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 insteadDocker
В образ зашиты 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 в корне репозитория тоже генерируется из этого реестра. Каждая
строка в нём начинается закомментированной, поэтому копировать файл целиком безопасно.