fix(onboarding): no crash on text input, Claude Code default, explanations (#34)
1. CRASH on word input (e.g. "claude") at any menu: $((ans-1)) under set -u → "unbound variable" → install died. Added _onb_read_choice validation (all 4 menus). 2. Claude Code default: added claude-code subscription provider (kei-registries submodule c559065→b904993), subscription default transport, claude-code default provider — Enter,Enter,Enter → Claude Code, no API key. 3. install died at line 178 for no-key providers (claude-code/codex/local): onboarding_run ended on a false `&&` → returned 1 → set -e. Added `return 0`. Plus en+ru explanations and auto-select single-option steps. Verified piped-under-pty. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f79f28e68a
commit
54aee697cf
6 changed files with 59 additions and 12 deletions
|
|
@ -1 +1 @@
|
||||||
Subproject commit c5590658ee636398c512ce2b25d8aedb3212a9b6
|
Subproject commit b90499311ed09cbf88ced93aaa9d890dcf861fac
|
||||||
|
|
@ -19,7 +19,7 @@ STR_TR_AZURE_OPENAI="Azure OpenAI (deployment+key)"
|
||||||
STR_TR_GOOGLE_VERTEX="Google Vertex AI (GCP)"
|
STR_TR_GOOGLE_VERTEX="Google Vertex AI (GCP)"
|
||||||
STR_TR_LOCAL="Local (Ollama/MLX/LMStudio)"
|
STR_TR_LOCAL="Local (Ollama/MLX/LMStudio)"
|
||||||
STR_TR_PROXY="Proxy (LiteLLM/OpenRouter)"
|
STR_TR_PROXY="Proxy (LiteLLM/OpenRouter)"
|
||||||
STR_TR_SUBSCRIPTION="OAuth subscription (ChatGPT)"
|
STR_TR_SUBSCRIPTION="Subscription login (Claude Code / ChatGPT — no API key)"
|
||||||
|
|
||||||
# Auth collection
|
# Auth collection
|
||||||
STR_AUTH_INTRO="Auth for"
|
STR_AUTH_INTRO="Auth for"
|
||||||
|
|
@ -40,3 +40,9 @@ STR_MENU_CONFIRM="Confirm selection?"
|
||||||
# Preflight warnings
|
# Preflight warnings
|
||||||
STR_PREFLIGHT_FAILED="Preflight failed — provider may not work."
|
STR_PREFLIGHT_FAILED="Preflight failed — provider may not work."
|
||||||
STR_PREFLIGHT_CONTINUE="Continue anyway? [y/N]"
|
STR_PREFLIGHT_CONTINUE="Continue anyway? [y/N]"
|
||||||
|
|
||||||
|
# Wizard explanations + input validation
|
||||||
|
STR_PICK_INVALID="please type one of the numbers shown"
|
||||||
|
STR_EXPLAIN_TRANSPORT="How the agents reach the AI. subscription = log in with your plan, no API key (Claude Code is option 1); direct-api = your own API key. Press Enter for the default."
|
||||||
|
STR_EXPLAIN_PROVIDER="Which AI service. Option 1 is the recommended default — press Enter."
|
||||||
|
STR_EXPLAIN_MODEL="Default model the agents use. Option 1 is the recommended default — press Enter."
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ STR_TR_AZURE_OPENAI="Azure OpenAI (deployment+ключ)"
|
||||||
STR_TR_GOOGLE_VERTEX="Google Vertex AI (GCP)"
|
STR_TR_GOOGLE_VERTEX="Google Vertex AI (GCP)"
|
||||||
STR_TR_LOCAL="Локально (Ollama/MLX/LMStudio)"
|
STR_TR_LOCAL="Локально (Ollama/MLX/LMStudio)"
|
||||||
STR_TR_PROXY="Прокси (LiteLLM/OpenRouter)"
|
STR_TR_PROXY="Прокси (LiteLLM/OpenRouter)"
|
||||||
STR_TR_SUBSCRIPTION="OAuth-подписка (ChatGPT)"
|
STR_TR_SUBSCRIPTION="Вход по подписке (Claude Code / ChatGPT — без API-ключа)"
|
||||||
|
|
||||||
# Сбор ключей
|
# Сбор ключей
|
||||||
STR_AUTH_INTRO="Аутентификация для"
|
STR_AUTH_INTRO="Аутентификация для"
|
||||||
|
|
@ -40,3 +40,9 @@ STR_MENU_CONFIRM="Подтвердить выбор?"
|
||||||
# Preflight-предупреждения
|
# Preflight-предупреждения
|
||||||
STR_PREFLIGHT_FAILED="Preflight упал — провайдер может не работать."
|
STR_PREFLIGHT_FAILED="Preflight упал — провайдер может не работать."
|
||||||
STR_PREFLIGHT_CONTINUE="Продолжить всё равно? [y/N]"
|
STR_PREFLIGHT_CONTINUE="Продолжить всё равно? [y/N]"
|
||||||
|
|
||||||
|
# Пояснения мастера + валидация ввода
|
||||||
|
STR_PICK_INVALID="введите один из показанных номеров"
|
||||||
|
STR_EXPLAIN_TRANSPORT="Как агенты обращаются к ИИ. subscription = вход по подписке, без API-ключа (Claude Code — вариант 1); direct-api = свой API-ключ. Нажми Enter для варианта по умолчанию."
|
||||||
|
STR_EXPLAIN_PROVIDER="Какой ИИ-сервис. Вариант 1 — рекомендуемый по умолчанию, нажми Enter."
|
||||||
|
STR_EXPLAIN_MODEL="Модель, которую используют агенты. Вариант 1 — рекомендуемый по умолчанию, нажми Enter."
|
||||||
|
|
|
||||||
|
|
@ -45,12 +45,20 @@ onboarding_fallback_providers() {
|
||||||
printf "lmstudio-local\tlocal\tLM Studio (local)\t_\n"
|
printf "lmstudio-local\tlocal\tLM Studio (local)\t_\n"
|
||||||
printf "litellm-proxy\tproxy\tLiteLLM proxy (keisei.app)\tKEI_LITELLM_KEY\n"
|
printf "litellm-proxy\tproxy\tLiteLLM proxy (keisei.app)\tKEI_LITELLM_KEY\n"
|
||||||
printf "openrouter\tproxy\tOpenRouter\tOPENROUTER_API_KEY\n"
|
printf "openrouter\tproxy\tOpenRouter\tOPENROUTER_API_KEY\n"
|
||||||
|
printf "claude-code\tsubscription\tClaude Code (subscription — your claude CLI, no API key)\t_\n"
|
||||||
printf "codex\tsubscription\tOpenAI Codex (ChatGPT OAuth)\t_\n"
|
printf "codex\tsubscription\tOpenAI Codex (ChatGPT OAuth)\t_\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Уникальные транспорты — для первого экрана выбора.
|
# Уникальные транспорты — для первого экрана выбора.
|
||||||
|
# Claude-Code-native kit → выводим subscription + direct-api ПЕРВЫМИ, чтобы
|
||||||
|
# рекомендованный путь (Claude Code, опция 1) был дефолтом. Остальные следом.
|
||||||
onboarding_list_transports() {
|
onboarding_list_transports() {
|
||||||
onboarding_list_providers | awk -F'\t' '{print $2}' | sort -u
|
local all; all="$(onboarding_list_providers | awk -F'\t' '{print $2}' | sort -u)"
|
||||||
|
local t
|
||||||
|
for t in subscription direct-api; do
|
||||||
|
printf '%s\n' "$all" | grep -qx "$t" && echo "$t"
|
||||||
|
done
|
||||||
|
printf '%s\n' "$all" | grep -vxE 'subscription|direct-api' || true
|
||||||
}
|
}
|
||||||
|
|
||||||
# Провайдеры внутри транспорта.
|
# Провайдеры внутри транспорта.
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,23 @@
|
||||||
# - lib-i18n.sh: STR_* словарь + i18n_available_languages + i18n_load_lang
|
# - lib-i18n.sh: STR_* словарь + i18n_available_languages + i18n_load_lang
|
||||||
# - lib-onboarding-registry.sh: списки провайдеров/моделей
|
# - 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
|
||||||
|
}
|
||||||
|
|
||||||
onboarding_pick_language() {
|
onboarding_pick_language() {
|
||||||
local langs
|
local langs
|
||||||
langs="$(i18n_available_languages 2>/dev/null)"
|
langs="$(i18n_available_languages 2>/dev/null)"
|
||||||
|
|
@ -43,8 +60,7 @@ onboarding_pick_language() {
|
||||||
printf " %2d) %s — %s\n" "$i" "$code" "$name" >&2
|
printf " %2d) %s — %s\n" "$i" "$code" "$name" >&2
|
||||||
i=$((i+1))
|
i=$((i+1))
|
||||||
done <<< "$langs"
|
done <<< "$langs"
|
||||||
read -r -p "[1-${#codes[@]}, default 1=en]: " ans
|
ans="$(_onb_read_choice "${#codes[@]}" "[1-${#codes[@]}, default 1=en]: ")"
|
||||||
ans="${ans:-1}"
|
|
||||||
ONBOARDING_LANG="${codes[$((ans-1))]:-en}"
|
ONBOARDING_LANG="${codes[$((ans-1))]:-en}"
|
||||||
fi
|
fi
|
||||||
command -v i18n_load_lang >/dev/null 2>&1 && i18n_load_lang "$ONBOARDING_LANG"
|
command -v i18n_load_lang >/dev/null 2>&1 && i18n_load_lang "$ONBOARDING_LANG"
|
||||||
|
|
@ -76,6 +92,7 @@ onboarding_pick_transport() {
|
||||||
else
|
else
|
||||||
echo "" >&2
|
echo "" >&2
|
||||||
echo "$prompt" >&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
|
local i=1
|
||||||
declare -a opts=()
|
declare -a opts=()
|
||||||
while IFS= read -r tr; do
|
while IFS= read -r tr; do
|
||||||
|
|
@ -83,8 +100,7 @@ onboarding_pick_transport() {
|
||||||
echo " $i) $tr" >&2
|
echo " $i) $tr" >&2
|
||||||
i=$((i+1))
|
i=$((i+1))
|
||||||
done <<< "$transports"
|
done <<< "$transports"
|
||||||
read -r -p "[1-${#opts[@]}, default 1]: " ans
|
ans="$(_onb_read_choice "${#opts[@]}" "[1-${#opts[@]}, default 1]: ")"
|
||||||
ans="${ans:-1}"
|
|
||||||
ONBOARDING_TRANSPORT="${opts[$((ans-1))]:-direct-api}"
|
ONBOARDING_TRANSPORT="${opts[$((ans-1))]:-direct-api}"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
@ -111,6 +127,7 @@ onboarding_pick_provider() {
|
||||||
else
|
else
|
||||||
echo "" >&2
|
echo "" >&2
|
||||||
echo "${STR_PICK_PROVIDER:-Provider within} $ONBOARDING_TRANSPORT:" >&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=()
|
declare -a ids=()
|
||||||
local i=1
|
local i=1
|
||||||
while IFS=$'\t' read -r id dn ae; do
|
while IFS=$'\t' read -r id dn ae; do
|
||||||
|
|
@ -118,8 +135,7 @@ onboarding_pick_provider() {
|
||||||
echo " $i) $id — $dn" >&2
|
echo " $i) $id — $dn" >&2
|
||||||
i=$((i+1))
|
i=$((i+1))
|
||||||
done <<< "$rows"
|
done <<< "$rows"
|
||||||
read -r -p "[1-${#ids[@]}, default 1]: " ans
|
ans="$(_onb_read_choice "${#ids[@]}" "[1-${#ids[@]}, default 1]: ")"
|
||||||
ans="${ans:-1}"
|
|
||||||
ONBOARDING_PROVIDER="${ids[$((ans-1))]:-${ids[0]}}"
|
ONBOARDING_PROVIDER="${ids[$((ans-1))]:-${ids[0]}}"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
@ -135,6 +151,12 @@ onboarding_pick_model() {
|
||||||
local rows; rows=$(onboarding_models_for_provider "$lookup")
|
local rows; rows=$(onboarding_models_for_provider "$lookup")
|
||||||
[ -z "$rows" ] && rows=$(printf "claude-sonnet-4-6\tClaude Sonnet 4.6 (fallback)\n")
|
[ -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
|
if command -v whiptail >/dev/null 2>&1; then
|
||||||
local args=()
|
local args=()
|
||||||
while IFS=$'\t' read -r id dn; do
|
while IFS=$'\t' read -r id dn; do
|
||||||
|
|
@ -146,6 +168,7 @@ onboarding_pick_model() {
|
||||||
else
|
else
|
||||||
echo "" >&2
|
echo "" >&2
|
||||||
echo "${STR_PICK_MODEL:-Models for} $lookup:" >&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=()
|
declare -a ids=()
|
||||||
local i=1
|
local i=1
|
||||||
while IFS=$'\t' read -r id dn; do
|
while IFS=$'\t' read -r id dn; do
|
||||||
|
|
@ -153,8 +176,7 @@ onboarding_pick_model() {
|
||||||
echo " $i) $id — $dn" >&2
|
echo " $i) $id — $dn" >&2
|
||||||
i=$((i+1))
|
i=$((i+1))
|
||||||
done <<< "$rows"
|
done <<< "$rows"
|
||||||
read -r -p "[1-${#ids[@]}, default 1]: " ans
|
ans="$(_onb_read_choice "${#ids[@]}" "[1-${#ids[@]}, default 1]: ")"
|
||||||
ans="${ans:-1}"
|
|
||||||
ONBOARDING_MODEL="${ids[$((ans-1))]:-${ids[0]}}"
|
ONBOARDING_MODEL="${ids[$((ans-1))]:-${ids[0]}}"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -95,4 +95,9 @@ onboarding_run() {
|
||||||
say " ${STR_DONE_CONFIG:-config:} $ONBOARDING_CONFIG"
|
say " ${STR_DONE_CONFIG:-config:} $ONBOARDING_CONFIG"
|
||||||
[ "${#ONBOARDING_AUTH_ENV_KEYS[@]}" -gt 0 ] && say " ${STR_DONE_SECRETS:-secrets:} $SECRETS_ENV (chmod 600)"
|
[ "${#ONBOARDING_AUTH_ENV_KEYS[@]}" -gt 0 ] && say " ${STR_DONE_SECRETS:-secrets:} $SECRETS_ENV (chmod 600)"
|
||||||
fi
|
fi
|
||||||
|
# MUST end on success: the last statement above is a short-circuit `&&` that
|
||||||
|
# evaluates false when the provider has no auth keys (claude-code / codex /
|
||||||
|
# local) → function would return 1 → `set -e` aborts the whole install at the
|
||||||
|
# onboarding_run call. Subscription/local providers are exactly the no-key case.
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue