KeiSeiKit-1.0/install/lib-onboarding-ui.sh
KeiSei84 742822a499 feat: opt-in hook packs + stack profiles + public-prep repoint (#44)
Mirror of keigit main — Phase 2 (abae256c) + public-prep repoint (518d95df).

Phase 2: safety on by default, discipline packs opt-in; stack profiles
(minimal/web/ml/systems/mobile) pull packs + agent sets; SSoT in
_primitives/hook-packs.toml; filter+prune via lib-hooks.sh; re-runnable
via `kei configure`; 8 hooks gated via _lib/gate.sh.

Public-prep: .gitmodules + README clone + plugin homepage + web-install.sh
repointed to github.com/KeiSeiLab. ADR in DECISIONS.md 2026-05-25.
2026-05-26 13:26:09 +07:00

258 lines
10 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# shellcheck shell=bash
# lib-onboarding-ui.sh — pick_* функции мастера (whiptail / bash select).
#
# Constructor Pattern: 1 файл = UI слой. Парсеры реестров — в registry.sh,
# state-запись — в state.sh.
#
# Заполняет глобалы:
# ONBOARDING_LANG, ONBOARDING_TRANSPORT, ONBOARDING_PROVIDER, ONBOARDING_MODEL
# ONBOARDING_AUTH_ENV_KEYS[] + ONBOARDING_AUTH_ENV_VALUES[]
#
# Использует:
# - lib-i18n.sh: STR_* словарь + i18n_available_languages + i18n_load_lang
# - lib-onboarding-registry.sh: списки провайдеров/моделей
# Read a validated 1-based menu choice. Non-numeric or out-of-range input is
# rejected with a re-prompt instead of crashing: bash arithmetic $((ans-1))
# treats a non-numeric "ans" (e.g. the user typing "claude") as a variable name
# → "unbound variable" under `set -u`. $1=option count, $2=prompt.
# Echoes a number in [1,$1] on stdout; prompts/warnings go to stderr.
_onb_read_choice() {
local max="$1" prompt="$2" ans
while true; do
read -r -p "$prompt" ans
ans="${ans:-1}"
if [[ "$ans" =~ ^[0-9]+$ ]] && [ "$ans" -ge 1 ] && [ "$ans" -le "$max" ]; then
printf '%s' "$ans"; return 0
fi
printf ' ⚠ %s\n' "${STR_PICK_INVALID:-please enter a number from 1 to $max}" >&2
done
}
# Step 6 — pick a stack profile (selects which discipline hooks + agents
# install) then optionally toggle discipline packs the stack does not pull.
# Sets ONBOARDING_STACK + ONBOARDING_PACKS. Reuses _onb_read_choice + stack_packs
# (lib-packs.sh). Default = minimal (safety hooks + core agents only).
onboarding_pick_stack() {
echo "" >&2
printf '%s\n' "${STR_PICK_STACK:-Pick your stack profile (selects hooks + agents):}" >&2
local opts="minimal web ml systems mobile" i=1 o d ans
for o in $opts; do
case "$o" in
minimal) d="${STR_STACK_MINIMAL:-safety hooks + core agents only}" ;;
web) d="${STR_STACK_WEB:-TS/frontend agents + evidence, observability}" ;;
ml) d="${STR_STACK_ML:-ML/data agents + evidence, observability, epistemic}" ;;
systems) d="${STR_STACK_SYSTEMS:-Rust/Go agents + Rust-first + evidence, observability}" ;;
mobile) d="${STR_STACK_MOBILE:-Swift/Flutter agents + evidence, observability}" ;;
esac
printf ' %d) %-8s — %s\n' "$i" "$o" "$d" >&2
i=$((i+1))
done
ans="$(_onb_read_choice 5 "${STR_PICK_STACK_PROMPT:-[1-5, default 1=minimal]: }")"
ONBOARDING_STACK="$(echo "$opts" | cut -d' ' -f"$ans")"
[ -n "$ONBOARDING_STACK" ] || ONBOARDING_STACK="minimal"
# Offer discipline packs the chosen stack does not already enable.
local stackpacks p pd reply
stackpacks=" $(command -v stack_packs >/dev/null 2>&1 && stack_packs "$ONBOARDING_STACK") "
ONBOARDING_PACKS=""
printf '%s\n' "${STR_PACK_INTRO:-Optional discipline packs (safety is always on):}" >&2
for p in evidence observability epistemic orchestration git-guard; do
case "$stackpacks" in *" $p "*) continue ;; esac
case "$p" in
evidence) pd="${STR_PACK_EVIDENCE:-force evidence markers on numeric/cost claims}" ;;
observability) pd="${STR_PACK_OBS:-task timing, session dumps, agent telemetry}" ;;
epistemic) pd="${STR_PACK_EPI:-no-downgrade + alignment + recurrence reminders}" ;;
orchestration) pd="${STR_PACK_ORCH:-multi-agent fork logging + orchestrator git checks}" ;;
git-guard) pd="${STR_PACK_GIT:-block git push to github (for private-remote teams)}" ;;
esac
printf ' + %-13s — %s\n' "$p" "$pd" >&2
read -r -p " ${STR_PACK_ENABLE:-enable? [y/N]: }" reply
case "$reply" in y|Y|yes|YES) ONBOARDING_PACKS="$ONBOARDING_PACKS $p" ;; esac
done
ONBOARDING_PACKS="$(echo "$ONBOARDING_PACKS" | sed 's/^ *//;s/ *$//')"
if command -v say >/dev/null 2>&1; then
say "stack: $ONBOARDING_STACK packs: ${ONBOARDING_PACKS:-(stack defaults only)}"
fi
}
onboarding_pick_language() {
local langs
langs="$(i18n_available_languages 2>/dev/null)"
if [ -z "$langs" ]; then
langs="$(printf 'en\tEnglish\nru\tРусский\n')"
fi
if command -v whiptail >/dev/null 2>&1; then
local args=() first=1
while IFS=$'\t' read -r code name; do
[ -z "$code" ] && continue
if [ "$first" = "1" ]; then
args+=("$code" "$name" "ON"); first=0
else
args+=("$code" "$name" "OFF")
fi
done <<< "$langs"
ONBOARDING_LANG=$(whiptail --title "KeiSei · Language / Язык / 语言 / 言語 / ..." --radiolist \
"Choose interface language / Выберите язык:" 22 70 16 \
"${args[@]}" 3>&1 1>&2 2>&3) || ONBOARDING_LANG="en"
else
echo "" >&2
echo "Choose language / Выберите язык / 选择语言 / 言語選択:" >&2
declare -a codes=()
local i=1
while IFS=$'\t' read -r code name; do
[ -z "$code" ] && continue
codes+=("$code")
printf " %2d) ${KEI_BLUE:-}%s${KEI_RST:-}${KEI_GOLD:-}%s${KEI_RST:-}\n" "$i" "$code" "$name" >&2
i=$((i+1))
done <<< "$langs"
ans="$(_onb_read_choice "${#codes[@]}" "[1-${#codes[@]}, default 1=en]: ")"
ONBOARDING_LANG="${codes[$((ans-1))]:-en}"
fi
command -v i18n_load_lang >/dev/null 2>&1 && i18n_load_lang "$ONBOARDING_LANG"
}
onboarding_pick_transport() {
local transports
transports=$(onboarding_list_transports)
local prompt="${STR_PICK_TRANSPORT:-Choose connection transport:}"
if command -v whiptail >/dev/null 2>&1; then
local args=()
while IFS= read -r tr; do
local desc
case "$tr" in
direct-api) desc="${STR_TR_DIRECT_API:-Direct provider API}" ;;
aws-bedrock) desc="${STR_TR_AWS_BEDROCK:-AWS Bedrock}" ;;
azure-openai) desc="${STR_TR_AZURE_OPENAI:-Azure OpenAI}" ;;
google-vertex) desc="${STR_TR_GOOGLE_VERTEX:-Google Vertex AI}" ;;
local) desc="${STR_TR_LOCAL:-Local}" ;;
proxy) desc="${STR_TR_PROXY:-Proxy}" ;;
subscription) desc="${STR_TR_SUBSCRIPTION:-OAuth subscription}" ;;
*) desc="$tr" ;;
esac
args+=("$tr" "$desc" "OFF")
done <<< "$transports"
ONBOARDING_TRANSPORT=$(whiptail --title "KeiSei · Transport" --radiolist \
"$prompt" 18 70 7 "${args[@]}" 3>&1 1>&2 2>&3) || ONBOARDING_TRANSPORT="direct-api"
else
echo "" >&2
echo "$prompt" >&2
echo " ${STR_EXPLAIN_TRANSPORT:-How the agents reach the AI. subscription = log in with your plan (no API key); direct-api = your own API key. Default is fine for most.}" >&2
local i=1
declare -a opts=()
while IFS= read -r tr; do
opts+=("$tr")
echo " $i) $tr" >&2
i=$((i+1))
done <<< "$transports"
ans="$(_onb_read_choice "${#opts[@]}" "[1-${#opts[@]}, default 1]: ")"
ONBOARDING_TRANSPORT="${opts[$((ans-1))]:-direct-api}"
fi
}
onboarding_pick_provider() {
local rows; rows=$(onboarding_providers_in_transport "$ONBOARDING_TRANSPORT")
local count; count=$(echo "$rows" | wc -l | tr -d ' ')
# Если провайдер один на транспорт — авто-выбор.
if [ "$count" = "1" ]; then
ONBOARDING_PROVIDER=$(echo "$rows" | awk -F'\t' '{print $1}')
return
fi
if command -v whiptail >/dev/null 2>&1; then
local args=()
while IFS=$'\t' read -r id dn ae; do
args+=("$id" "$dn" "OFF")
done <<< "$rows"
local prompt="${STR_PICK_PROVIDER:-Provider within} $ONBOARDING_TRANSPORT:"
ONBOARDING_PROVIDER=$(whiptail --title "KeiSei · Provider" --radiolist \
"$prompt" 16 70 8 "${args[@]}" 3>&1 1>&2 2>&3) \
|| ONBOARDING_PROVIDER=$(echo "$rows" | head -1 | awk -F'\t' '{print $1}')
else
echo "" >&2
echo "${STR_PICK_PROVIDER:-Provider within} $ONBOARDING_TRANSPORT:" >&2
echo " ${STR_EXPLAIN_PROVIDER:-Which AI service. Option 1 is the recommended default.}" >&2
declare -a ids=()
local i=1
while IFS=$'\t' read -r id dn ae; do
ids+=("$id")
echo " $i) $id$dn" >&2
i=$((i+1))
done <<< "$rows"
ans="$(_onb_read_choice "${#ids[@]}" "[1-${#ids[@]}, default 1]: ")"
ONBOARDING_PROVIDER="${ids[$((ans-1))]:-${ids[0]}}"
fi
}
onboarding_pick_model() {
# Для AWS/Azure/Vertex модели идут под parent-провайдером — мапим.
local lookup="$ONBOARDING_PROVIDER"
case "$ONBOARDING_PROVIDER" in
anthropic-bedrock) lookup="anthropic" ;;
openai-azure) lookup="openai" ;;
google-vertex) lookup="google" ;;
esac
local rows; rows=$(onboarding_models_for_provider "$lookup")
[ -z "$rows" ] && rows=$(printf "claude-sonnet-4-6\tClaude Sonnet 4.6 (fallback)\n")
# Single model → auto-select, no dead-end prompt (mirrors provider count==1).
if [ "$(printf '%s\n' "$rows" | grep -c .)" = "1" ]; then
ONBOARDING_MODEL=$(printf '%s\n' "$rows" | head -1 | awk -F'\t' '{print $1}')
return
fi
if command -v whiptail >/dev/null 2>&1; then
local args=()
while IFS=$'\t' read -r id dn; do
args+=("$id" "$dn" "OFF")
done <<< "$rows"
ONBOARDING_MODEL=$(whiptail --title "KeiSei · Model" --radiolist \
"${STR_PICK_MODEL:-Default model:}" 16 70 8 "${args[@]}" 3>&1 1>&2 2>&3) \
|| ONBOARDING_MODEL=$(echo "$rows" | head -1 | awk -F'\t' '{print $1}')
else
echo "" >&2
echo "${STR_PICK_MODEL:-Models for} $lookup:" >&2
echo " ${STR_EXPLAIN_MODEL:-Default model the agents use. Option 1 is the recommended default.}" >&2
declare -a ids=()
local i=1
while IFS=$'\t' read -r id dn; do
ids+=("$id")
echo " $i) $id$dn" >&2
i=$((i+1))
done <<< "$rows"
ans="$(_onb_read_choice "${#ids[@]}" "[1-${#ids[@]}, default 1]: ")"
ONBOARDING_MODEL="${ids[$((ans-1))]:-${ids[0]}}"
fi
}
onboarding_collect_auth() {
ONBOARDING_AUTH_ENV_KEYS=()
ONBOARDING_AUTH_ENV_VALUES=()
local ae; ae=$(onboarding_auth_env_for_provider "$ONBOARDING_PROVIDER")
[ -z "$ae" ] || [ "$ae" = "_" ] && return # local / subscription — нет ключей
echo "" >&2
echo "${STR_AUTH_INTRO:-Auth for} $ONBOARDING_PROVIDER ($ae):" >&2
echo "${STR_AUTH_PROMPT:-Enter values (Enter — leave empty, fill later).}" >&2
local IFS_old="$IFS"; IFS=','
for key in $ae; do
IFS="$IFS_old"
local cur="${!key:-}"
local prompt_msg="$key"
[ -n "$cur" ] && prompt_msg="$key ${STR_AUTH_CURRENT_HINT:-(current: <hidden>)}"
read -r -s -p " $prompt_msg = " val
echo "" >&2
if [ -n "$val" ]; then
ONBOARDING_AUTH_ENV_KEYS+=("$key")
ONBOARDING_AUTH_ENV_VALUES+=("$val")
elif [ -n "$cur" ]; then
ONBOARDING_AUTH_ENV_KEYS+=("$key")
ONBOARDING_AUTH_ENV_VALUES+=("$cur")
fi
done
IFS="$IFS_old"
}