Constructor Pattern fix replacing v0.47-v0.48 patch series. The "is the
user interactive?" logic was previously duplicated across 15+ places:
bootstrap.sh x4 ([ -t 0 ] gates on profile/onboard/launch/etc)
install.sh x1 (PATH wiring decision)
install/lib-hooks.sh (activate-hooks prompt)
install/lib-plan.sh (auto-confirm gate)
install/lib-menu.sh (skip-menu gate)
install/lib-wizard.sh (sleep-wizard gate)
install/lib-onboarding.sh x2 (onboarding_should_run + preflight retry)
install/lib-preflight.sh (install-tool prompt)
Every duplicated check was a chance to get curl|bash semantics wrong.
v0.47 used `[ -t 1 ]` (broke under tee'd stdout). v0.48 used `[ -t 0 ]`
(broke under curl pipe stdin). Each fix was a patch on top of the same
architectural defect: scattered truth.
ARCHITECTURAL FIX (Rule Zero — 1 cube = 1 responsibility):
scripts/kei-prompt.sh (NEW, ~110 LOC, public API):
kei_is_interactive → 0 if user is at a terminal, 1 if headless
kei_prompt Q [DEFAULT] → answer or default to stdout
kei_prompt_yn Q [Y|N] → exit 0=yes, 1=no, with [Y/n] hint
kei_prompt_secret Q → no-echo input (tokens, keys)
Truth signal: /dev/tty accessibility, with [ -t 0 ] as second-choice
fallback. KEI_NONINTERACTIVE=1 for CI override. Same contract as the
inline rules — now in ONE place.
bootstrap.sh + install.sh: source the cube at the top, with self-
contained inline fallback (mirrors the kei_is_interactive contract
only) so they remain self-bootable even if scripts/ is missing.
All 15+ inline gates replaced with `kei_is_interactive` calls.
All 3 `read -r -p` prompts in installer cubes replaced with
`kei_prompt` / `kei_prompt_yn`.
Existing copy_pet_scripts() in lib-scaffold.sh installs scripts/*.sh
into ~/.claude/scripts/ automatically — no install logic change needed.
WHAT THIS PREVENTS:
- Next time someone writes a prompt in installer code, the only path
is `kei_prompt`. They CANNOT accidentally type `[ -t 0 ]` because
there is no `[ -t 0 ]` to copy-paste anymore (except inside the
cube itself).
- The v2 tty-interactivity-gate-guard.sh hook (added 2026-05-27)
becomes a regression net rather than the first line of defence.
- Two real install incidents this month (May 2026, 7 prompts each)
do not happen a third time.
VERIFIED:
- Syntax check passes on all 9 modified files + new cube.
- Primitive functions smoke-tested across 8 cases: headless,
KEI_NONINTERACTIVE override, default fallback, yn convenience,
re-source guard, /dev/tty available, /dev/tty open() fails.
- 2 remaining [ -t 0 ] in tree: BOTH inside kei_is_interactive
fallback in bootstrap.sh + install.sh (single-source contract,
not patches).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirror of keigit 3aff0029. lib-hooks.sh apostrophe-in-jq closed the bash single-quote and broke every install (fixed). cortex-ui removed from profiles+menu; forgejo/zoekt source lib-launchd; portable kei-message id (BSD date fallback); non-TTY default minimal; install stamps .kei-profile; README hook count 54; web-install warns before discarding local edits in the managed clone.
PATH-wiring (~/.claude/bin, home of the `kei` entry-point) was gated on
`[ -t 0 ] && [ -t 1 ]`; curl|bash tees stdout so -t 1 is false → pathway skipped
→ `kei: command not found`. Same tee/-t1 trap as onboarding (this one in the
top-level install.sh). Eradicated across tree: install.sh, lib-menu.sh,
lib-wizard.sh all gate on [ -t 0 ]. Verified piped-under-pty.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- preflight failure handling: вместо `|| true` (молчаливое продолжение
при упавшем preflight) — явный prompt «продолжить? [y/N]» с return 1
при отказе. Без TTY печатает warning и продолжает. Это закрывает
HIGH bug-9: «.onboarded флаг выставляется при нерабочей конфигурации».
- lib-preflight.sh::preflight_check_cli — общий helper (command -v +
offer-install + version echo). Убирает 6-file boilerplate (хотя сами
per-provider файлы пока не переписаны под него — это отдельный шаг).
- onboarding_fallback_providers: расширен с 3 до 14 провайдеров,
покрывает все 7 транспортов. Был дрейф vs providers.toml (14 vs 3),
юзер без submodule видел только anthropic+openai+ollama.
- STR_PICK_PROVIDER plural mismatch: whiptail и plain ветки теперь
используют один fallback "Provider within" (раньше plain имел
"Providers within", whiptail — "Provider within").
- STR_DONE_NEXT удалён из en.sh + ru.sh (мёртвый ключ).
- Новые ключи: STR_MENU_* (для lib-menu.sh) + STR_PREFLIGHT_FAILED +
STR_PREFLIGHT_CONTINUE. lib-menu.sh начал использовать
STR_MENU_TITLE / STR_MENU_SUBSTRATE (частичная локализация, остальное
меню — отдельной задачей).
Тесты: bash -n чисто, i18n round-trip EN/RU работает, non-TTY smoke
install --no-execute проходит.