refactor(install): production-ready финальный круг
1. lib-onboarding.sh раскидан на 3 куба (Constructor Pattern <200 LOC):
- lib-onboarding-registry.sh (79 LOC) — парсеры providers/models.toml
+ onboarding_fallback_providers (14 провайдеров)
+ onboarding_auth_env_for_provider helper (был inline в collect_auth)
- lib-onboarding-ui.sh (189 LOC) — pick_language/transport/provider/model
+ collect_auth (whiptail/bash select)
- lib-onboarding-state.sh (57 LOC) — write_secrets + write_config
+ user-model-override.toml для kei-model-router
- lib-onboarding.sh (95 LOC) — тонкий оркестратор: should_run + run
Сам lib-onboarding.sh source'ит 3 подкуба автоматически. Глобалы
(ONBOARDING_*, REGISTRY_*, ONBOARDED_FLAG, etc.) объявлены в
оркестраторе, подкубы их используют через имена.
2. lib-menu.sh локализован:
- whiptail title + radiolist prompt через ${STR_MENU_TITLE} +
${STR_MENU_SUBSTRATE} + ${STR_MENU_PROFILE_PROMPT}.
- Plain heading тоже использует словарь.
- 12 коротких имён профилей (minimal/core/dev/...) — оставлены EN
как стабильные id (не переводятся).
3. _blocks/build-index.sh — детерминированная регенерация INDEX.md.
Группировка по 14 категорийным префиксам + "Прочие" для остальных.
Безопасно перезапускать. INDEX.md обновлён через этот скрипт
(минимальный diff — добавлена ссылка на build-index.sh в шапке).
Проверено: bash -n чисто, unit тесты onboarding_list_providers/
transports/models OK, non-TTY smoke ./install.sh --profile=minimal
--no-execute проходит.
This commit is contained in:
parent
6371eae98a
commit
d729a2e903
7 changed files with 430 additions and 342 deletions
|
|
@ -1,7 +1,8 @@
|
||||||
# Реестр блоков KeiSeiKit
|
# Реестр блоков KeiSeiKit
|
||||||
|
|
||||||
> SSoT для assembler. Все блоки доступные для `blocks = [...]` в `_manifests/<agent>.toml`.
|
> SSoT для assembler. Все блоки доступные для `blocks = [...]` в `_manifests/<agent>.toml`.
|
||||||
> Авто-генерируется из `_blocks/*.md`. Каждый файл = атомарный кубик (Constructor Pattern).
|
> Авто-генерируется из `_blocks/*.md` через `bash build-index.sh`.
|
||||||
|
> Каждый файл = атомарный кубик (Constructor Pattern).
|
||||||
|
|
||||||
Пример:
|
Пример:
|
||||||
```toml
|
```toml
|
||||||
|
|
@ -145,3 +146,4 @@ blocks = ["baseline", "rule-pre-dev-gate", "api-anthropic"]
|
||||||
---
|
---
|
||||||
|
|
||||||
Всего блоков: 84.
|
Всего блоков: 84.
|
||||||
|
Перегенерация: `bash _blocks/build-index.sh`.
|
||||||
|
|
|
||||||
72
_blocks/build-index.sh
Executable file
72
_blocks/build-index.sh
Executable file
|
|
@ -0,0 +1,72 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# build-index.sh — регенерация _blocks/INDEX.md из *.md.
|
||||||
|
#
|
||||||
|
# Использование:
|
||||||
|
# cd ~/Projects/KeiSeiKit-public/_blocks && bash build-index.sh
|
||||||
|
# # или из любого места:
|
||||||
|
# bash $(git rev-parse --show-toplevel)/_blocks/build-index.sh
|
||||||
|
#
|
||||||
|
# Что делает:
|
||||||
|
# 1. Сканит _blocks/*.md (исключая README.md и INDEX.md).
|
||||||
|
# 2. Группирует по префиксу (api-, auth-, ci-, db-, deploy-, ...).
|
||||||
|
# 3. Для каждого блока берёт первую H1-строку как описание.
|
||||||
|
# 4. Пишет INDEX.md с разбиением по 14 категориям + "Прочие".
|
||||||
|
#
|
||||||
|
# Безопасно перезапускать — детерминированный output.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Запускаемся всегда из _blocks/.
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
|
|
||||||
|
CATEGORIES=(api auth ci db deploy docs domain mode obs path rule scraper security stack test)
|
||||||
|
|
||||||
|
OUT="INDEX.md"
|
||||||
|
TMP="${OUT}.tmp.$$"
|
||||||
|
trap 'rm -f "$TMP"' EXIT
|
||||||
|
|
||||||
|
{
|
||||||
|
printf '# Реестр блоков KeiSeiKit\n\n'
|
||||||
|
printf '> SSoT для assembler. Все блоки доступные для `blocks = [...]` в `_manifests/<agent>.toml`.\n'
|
||||||
|
printf '> Авто-генерируется из `_blocks/*.md` через `bash build-index.sh`.\n'
|
||||||
|
printf '> Каждый файл = атомарный кубик (Constructor Pattern).\n\n'
|
||||||
|
printf 'Пример:\n```toml\nblocks = ["baseline", "rule-pre-dev-gate", "api-anthropic"]\n```\n\n'
|
||||||
|
printf '## По категориям\n\n'
|
||||||
|
|
||||||
|
for cat in "${CATEGORIES[@]}"; do
|
||||||
|
upper=$(echo "$cat" | tr '[:lower:]' '[:upper:]')
|
||||||
|
files=$(ls 2>/dev/null | grep -E "^${cat}(-|\.).*\.md$" || true)
|
||||||
|
[ -z "$files" ] && continue
|
||||||
|
printf '### %s\n\n' "$upper"
|
||||||
|
while IFS= read -r f; do
|
||||||
|
[ -z "$f" ] && continue
|
||||||
|
name="${f%.md}"
|
||||||
|
desc=$(awk '/^# / { sub(/^# /, ""); print; exit }' "$f" 2>/dev/null || true)
|
||||||
|
[ -z "$desc" ] && desc="(no title)"
|
||||||
|
printf -- '- `%s` — %s\n' "$name" "$desc"
|
||||||
|
done <<< "$files"
|
||||||
|
printf '\n'
|
||||||
|
done
|
||||||
|
|
||||||
|
printf '### Прочие (без категорийного префикса)\n\n'
|
||||||
|
while IFS= read -r f; do
|
||||||
|
name="${f%.md}"
|
||||||
|
case "$name" in
|
||||||
|
api-*|auth-*|ci-*|db-*|deploy-*|docs-*|domain-*|mode-*|obs-*|path-*|rule-*|scraper-*|security-*|stack-*|test-*|README|INDEX) continue ;;
|
||||||
|
esac
|
||||||
|
desc=$(awk '/^# / { sub(/^# /, ""); print; exit }' "$f" 2>/dev/null || true)
|
||||||
|
[ -z "$desc" ] && desc="(no title)"
|
||||||
|
printf -- '- `%s` — %s\n' "$name" "$desc"
|
||||||
|
done < <(ls *.md)
|
||||||
|
|
||||||
|
total=$(ls *.md | grep -vE '^(README|INDEX)\.md$' | wc -l | tr -d ' ')
|
||||||
|
printf '\n---\n\nВсего блоков: %d.\n' "$total"
|
||||||
|
printf 'Перегенерация: `bash _blocks/build-index.sh`.\n'
|
||||||
|
} > "$TMP"
|
||||||
|
|
||||||
|
mv "$TMP" "$OUT"
|
||||||
|
trap - EXIT
|
||||||
|
|
||||||
|
echo "✓ $OUT regenerated"
|
||||||
|
wc -l "$OUT"
|
||||||
|
|
@ -30,8 +30,8 @@ menu_should_skip() {
|
||||||
# Profile choice = how many ADDITIONAL primitive binaries to add on top.
|
# Profile choice = how many ADDITIONAL primitive binaries to add on top.
|
||||||
menu_whiptail_profile() {
|
menu_whiptail_profile() {
|
||||||
local tool="$1"
|
local tool="$1"
|
||||||
"$tool" --title "KeiSeiKit Installer — substrate always installed; profile = primitives ADDED on top" --radiolist \
|
"$tool" --title "${STR_MENU_TITLE:-KeiSeiKit Installer} — ${STR_MENU_SUBSTRATE:-substrate always installed; profile = primitives ADDED on top}" --radiolist \
|
||||||
"Choose install profile (SPACE to select, ENTER to confirm):" 28 86 12 \
|
"${STR_MENU_PROFILE_PROMPT:-Choose install profile (SPACE to select, ENTER to confirm):}" 28 86 12 \
|
||||||
"minimal" "substrate only — 0 primitives (~5s)" ON \
|
"minimal" "substrate only — 0 primitives (~5s)" ON \
|
||||||
"core" "+ 2 primitives (tomd, kei-doctor) (~5s)" OFF \
|
"core" "+ 2 primitives (tomd, kei-doctor) (~5s)" OFF \
|
||||||
"frontend" "+ 8 site tools — mock-render, visual-diff, figma-tokens" OFF \
|
"frontend" "+ 8 site tools — mock-render, visual-diff, figma-tokens" OFF \
|
||||||
|
|
@ -77,7 +77,7 @@ menu_plain_profile() {
|
||||||
echo " • 82 blocks • 16 caps • 7 roles" >&2
|
echo " • 82 blocks • 16 caps • 7 roles" >&2
|
||||||
echo " • 11 cross-tool bridges (Cursor / Copilot / Codex / Aider / …)" >&2
|
echo " • 11 cross-tool bridges (Cursor / Copilot / Codex / Aider / …)" >&2
|
||||||
echo >&2
|
echo >&2
|
||||||
echo " Profile = primitive binaries ADDED on top of substrate." >&2
|
echo " ${STR_MENU_PROFILE_PROMPT:-Profile = primitive binaries ADDED on top of substrate.}" >&2
|
||||||
echo "------------------------------------------------------------" >&2
|
echo "------------------------------------------------------------" >&2
|
||||||
echo >&2
|
echo >&2
|
||||||
echo " Standard:" >&2
|
echo " Standard:" >&2
|
||||||
|
|
|
||||||
79
install/lib-onboarding-registry.sh
Normal file
79
install/lib-onboarding-registry.sh
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
|
# lib-onboarding-registry.sh — парсеры реестров providers.toml + models.toml.
|
||||||
|
#
|
||||||
|
# Constructor Pattern: 1 файл = парсинг реестров. UI и state — в соседних кубах.
|
||||||
|
#
|
||||||
|
# Источник: $KIT_DIR/_blocks/registries/{providers,models}.toml (submodule
|
||||||
|
# kei-registries). Если файла нет — fallback на захардкоженный набор
|
||||||
|
# покрывающий все 7 транспортов.
|
||||||
|
#
|
||||||
|
# Глобалы (общие с lib-onboarding-*):
|
||||||
|
# REGISTRY_PROVIDERS — путь к providers.toml
|
||||||
|
# REGISTRY_MODELS — путь к models.toml
|
||||||
|
|
||||||
|
REGISTRY_PROVIDERS="${REGISTRY_PROVIDERS:-$KIT_DIR/_blocks/registries/providers.toml}"
|
||||||
|
REGISTRY_MODELS="${REGISTRY_MODELS:-$KIT_DIR/_blocks/registries/models.toml}"
|
||||||
|
|
||||||
|
# Парсер providers.toml. Простой awk-граббер по [[provider]] секциям.
|
||||||
|
# Печатает: <id>\t<transport>\t<display_name>\t<auth_env>
|
||||||
|
onboarding_list_providers() {
|
||||||
|
[ -f "$REGISTRY_PROVIDERS" ] || { onboarding_fallback_providers; return; }
|
||||||
|
awk '
|
||||||
|
/^\[\[provider\]\]/ { id=""; tr=""; dn=""; ae=""; next }
|
||||||
|
/^id[[:space:]]*=/ { gsub(/^id[[:space:]]*=[[:space:]]*"/, ""); gsub(/".*$/, ""); id=$0 }
|
||||||
|
/^transport[[:space:]]*=/ { gsub(/^transport[[:space:]]*=[[:space:]]*"/, ""); gsub(/".*$/, ""); tr=$0 }
|
||||||
|
/^display_name[[:space:]]*=/ { gsub(/^display_name[[:space:]]*=[[:space:]]*"/, ""); gsub(/".*$/, ""); dn=$0 }
|
||||||
|
/^auth_env[[:space:]]*=/ { gsub(/^auth_env[[:space:]]*=[[:space:]]*"/, ""); gsub(/".*$/, ""); ae=$0;
|
||||||
|
if (id && tr) print id "\t" tr "\t" dn "\t" ae }
|
||||||
|
' "$REGISTRY_PROVIDERS"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Fallback если submodule не подтянут.
|
||||||
|
# Покрывает 7 транспортов минимальными представителями. Синхронизировать
|
||||||
|
# вручную если в реестре появится новый транспорт-тип.
|
||||||
|
onboarding_fallback_providers() {
|
||||||
|
printf "anthropic\tdirect-api\tAnthropic (Direct API)\tANTHROPIC_API_KEY\n"
|
||||||
|
printf "anthropic-bedrock\taws-bedrock\tAnthropic (AWS Bedrock)\tAWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_REGION\n"
|
||||||
|
printf "openai\tdirect-api\tOpenAI (Direct API)\tOPENAI_API_KEY\n"
|
||||||
|
printf "openai-azure\tazure-openai\tOpenAI (Azure)\tAZURE_OPENAI_API_KEY,AZURE_OPENAI_ENDPOINT,AZURE_OPENAI_DEPLOYMENT\n"
|
||||||
|
printf "xai\tdirect-api\txAI\tXAI_API_KEY\n"
|
||||||
|
printf "deepseek\tdirect-api\tDeepSeek\tDEEPSEEK_API_KEY\n"
|
||||||
|
printf "google\tdirect-api\tGoogle Gemini (Direct API)\tGEMINI_API_KEY\n"
|
||||||
|
printf "google-vertex\tgoogle-vertex\tGoogle Gemini (Vertex AI)\tGOOGLE_APPLICATION_CREDENTIALS,GCP_PROJECT_ID,GCP_REGION\n"
|
||||||
|
printf "ollama-local\tlocal\tOllama (local)\t_\n"
|
||||||
|
printf "mlx-local\tlocal\tMLX (Apple silicon 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 "openrouter\tproxy\tOpenRouter\tOPENROUTER_API_KEY\n"
|
||||||
|
printf "codex\tsubscription\tOpenAI Codex (ChatGPT OAuth)\t_\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Уникальные транспорты — для первого экрана выбора.
|
||||||
|
onboarding_list_transports() {
|
||||||
|
onboarding_list_providers | awk -F'\t' '{print $2}' | sort -u
|
||||||
|
}
|
||||||
|
|
||||||
|
# Провайдеры внутри транспорта.
|
||||||
|
onboarding_providers_in_transport() {
|
||||||
|
local tr="$1"
|
||||||
|
onboarding_list_providers | awk -F'\t' -v t="$tr" '$2==t {print $1 "\t" $3 "\t" $4}'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Модели по provider_ref.
|
||||||
|
onboarding_models_for_provider() {
|
||||||
|
local pr="$1"
|
||||||
|
[ -f "$REGISTRY_MODELS" ] || { printf "claude-sonnet-4-6\tClaude Sonnet 4.6\n"; return; }
|
||||||
|
awk -v pr="$pr" '
|
||||||
|
/^\[\[model\]\]/ { id=""; pref=""; dn=""; next }
|
||||||
|
/^id[[:space:]]*=/ { gsub(/^id[[:space:]]*=[[:space:]]*"/, ""); gsub(/".*$/, ""); id=$0 }
|
||||||
|
/^provider_ref[[:space:]]*=/ { gsub(/^provider_ref[[:space:]]*=[[:space:]]*"/, ""); gsub(/".*$/, ""); pref=$0 }
|
||||||
|
/^display_name[[:space:]]*=/ { gsub(/^display_name[[:space:]]*=[[:space:]]*"/, ""); gsub(/".*$/, ""); dn=$0;
|
||||||
|
if (pref==pr) print id "\t" dn }
|
||||||
|
' "$REGISTRY_MODELS"
|
||||||
|
}
|
||||||
|
|
||||||
|
# auth_env для одного провайдера (для onboarding_collect_auth).
|
||||||
|
onboarding_auth_env_for_provider() {
|
||||||
|
local p="$1"
|
||||||
|
onboarding_list_providers | awk -F'\t' -v p="$p" '$1==p {print $4}'
|
||||||
|
}
|
||||||
57
install/lib-onboarding-state.sh
Normal file
57
install/lib-onboarding-state.sh
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
|
# lib-onboarding-state.sh — запись результата мастера на диск.
|
||||||
|
#
|
||||||
|
# Constructor Pattern: 1 файл = state-запись. UI — в ui.sh, парсеры — в registry.sh.
|
||||||
|
#
|
||||||
|
# Пишет:
|
||||||
|
# ~/.claude/.onboarded — флаг прохождения
|
||||||
|
# ~/.claude/config/onboarding.toml — выбор lang/transport/provider/model
|
||||||
|
# ~/.claude/config/user-model-override.toml — для kei-model-router (HIGH аудит-1)
|
||||||
|
# ~/.claude/secrets/.env — добавляет ключи провайдера (chmod 600)
|
||||||
|
|
||||||
|
ONBOARDED_FLAG="${ONBOARDED_FLAG:-$HOME/.claude/.onboarded}"
|
||||||
|
ONBOARDING_CONFIG="${ONBOARDING_CONFIG:-$HOME/.claude/config/onboarding.toml}"
|
||||||
|
SECRETS_ENV="${SECRETS_ENV:-$HOME/.claude/secrets/.env}"
|
||||||
|
|
||||||
|
onboarding_write_secrets() {
|
||||||
|
[ "${#ONBOARDING_AUTH_ENV_KEYS[@]}" = "0" ] && return
|
||||||
|
mkdir -p "$(dirname "$SECRETS_ENV")"
|
||||||
|
touch "$SECRETS_ENV"; chmod 600 "$SECRETS_ENV"
|
||||||
|
local i
|
||||||
|
for i in "${!ONBOARDING_AUTH_ENV_KEYS[@]}"; do
|
||||||
|
local k="${ONBOARDING_AUTH_ENV_KEYS[$i]}"
|
||||||
|
local v="${ONBOARDING_AUTH_ENV_VALUES[$i]}"
|
||||||
|
if grep -q "^${k}=" "$SECRETS_ENV" 2>/dev/null; then
|
||||||
|
grep -v "^${k}=" "$SECRETS_ENV" > "$SECRETS_ENV.tmp"
|
||||||
|
mv "$SECRETS_ENV.tmp" "$SECRETS_ENV"
|
||||||
|
fi
|
||||||
|
printf '%s=%s\n' "$k" "$v" >> "$SECRETS_ENV"
|
||||||
|
done
|
||||||
|
chmod 600 "$SECRETS_ENV"
|
||||||
|
}
|
||||||
|
|
||||||
|
onboarding_write_config() {
|
||||||
|
mkdir -p "$(dirname "$ONBOARDING_CONFIG")"
|
||||||
|
cat > "$ONBOARDING_CONFIG" <<EOF
|
||||||
|
# KeiSeiKit onboarding choices. Auto-generated by lib-onboarding.sh.
|
||||||
|
# Re-run wizard: rm ~/.claude/.onboarded && ./install.sh
|
||||||
|
language = "$ONBOARDING_LANG"
|
||||||
|
transport = "$ONBOARDING_TRANSPORT"
|
||||||
|
provider = "$ONBOARDING_PROVIDER"
|
||||||
|
default_model = "$ONBOARDING_MODEL"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Override для kei-model-router (HIGH аудит-1).
|
||||||
|
# Приоритет: --pinned flag > этот файл > agent-profiles.toml default_model_ref.
|
||||||
|
local override_path="$HOME/.claude/config/user-model-override.toml"
|
||||||
|
cat > "$override_path" <<EOF
|
||||||
|
# User-tier model override. Auto-generated by onboarding wizard.
|
||||||
|
# Format: kei-model-router::Registry::load_user_override().
|
||||||
|
# Priority: --pinned flag > этот файл > agent-profiles.toml default_model_ref.
|
||||||
|
provider = "$ONBOARDING_PROVIDER"
|
||||||
|
model = "$ONBOARDING_MODEL"
|
||||||
|
transport = "$ONBOARDING_TRANSPORT"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
: > "$ONBOARDED_FLAG"
|
||||||
|
}
|
||||||
189
install/lib-onboarding-ui.sh
Normal file
189
install/lib-onboarding-ui.sh
Normal file
|
|
@ -0,0 +1,189 @@
|
||||||
|
# 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: списки провайдеров/моделей
|
||||||
|
|
||||||
|
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) %s — %s\n" "$i" "$code" "$name" >&2
|
||||||
|
i=$((i+1))
|
||||||
|
done <<< "$langs"
|
||||||
|
read -r -p "[1-${#codes[@]}, default 1=en]: " ans
|
||||||
|
ans="${ans:-1}"
|
||||||
|
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
|
||||||
|
local i=1
|
||||||
|
declare -a opts=()
|
||||||
|
while IFS= read -r tr; do
|
||||||
|
opts+=("$tr")
|
||||||
|
echo " $i) $tr" >&2
|
||||||
|
i=$((i+1))
|
||||||
|
done <<< "$transports"
|
||||||
|
read -r -p "[1-${#opts[@]}, default 1]: " ans
|
||||||
|
ans="${ans:-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
|
||||||
|
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"
|
||||||
|
read -r -p "[1-${#ids[@]}, default 1]: " ans
|
||||||
|
ans="${ans:-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")
|
||||||
|
|
||||||
|
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
|
||||||
|
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"
|
||||||
|
read -r -p "[1-${#ids[@]}, default 1]: " ans
|
||||||
|
ans="${ans:-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"
|
||||||
|
}
|
||||||
|
|
@ -1,23 +1,21 @@
|
||||||
# shellcheck shell=bash
|
# shellcheck shell=bash
|
||||||
# lib-onboarding.sh — мастер выбора языка / транспорта / провайдера / модели.
|
# lib-onboarding.sh — мастер первичной настройки (тонкий оркестратор).
|
||||||
#
|
#
|
||||||
# Иерархия: язык → транспорт → провайдер → модель → ключи.
|
# Иерархия: язык → транспорт → провайдер → модель → preflight → ключи.
|
||||||
#
|
#
|
||||||
# Реестр: $KIT_DIR/_blocks/registries/{providers,models}.toml
|
# Constructor Pattern: этот файл — только координация. Логика по слоям:
|
||||||
# (submodule kei-registries). Если submodule не подтянут — fallback
|
# lib-onboarding-registry.sh — парсеры providers/models.toml + fallback
|
||||||
# на захардкоженный набор (anthropic direct-api + sonnet).
|
# lib-onboarding-ui.sh — pick_* функции (whiptail/bash select)
|
||||||
|
# lib-onboarding-state.sh — запись secrets/.env + onboarding.toml + флаг
|
||||||
|
# lib-preflight.sh — провайдер-специфичные CLI-проверки
|
||||||
|
# lib-i18n.sh — STR_* словарь + load_lang
|
||||||
#
|
#
|
||||||
# Состояние:
|
# Источник: $KIT_DIR/_blocks/registries/{providers,models}.toml (submodule
|
||||||
# ~/.claude/.onboarded — флаг "пройдено", skip при повторе
|
# kei-registries). Если submodule не подтянут — fallback (см. registry.sh).
|
||||||
# ~/.claude/config/onboarding.toml — выбор lang/transport/provider/model
|
|
||||||
# ~/.claude/secrets/.env — добавляет ключи провайдера
|
|
||||||
#
|
#
|
||||||
# Тулинг: whiptail > dialog > plain bash select.
|
# Skip: $ONBOARDED_FLAG, env KEISEI_SKIP_ONBOARD=1, non-TTY.
|
||||||
# Stdout-контракт: ничего значимого; запись в файлы + globals.
|
|
||||||
|
|
||||||
# ───────────────────────────────────────────────────────────────────────
|
# Глобалы заполняемые мастером.
|
||||||
# Глобалы заполняемые мастером
|
|
||||||
# ───────────────────────────────────────────────────────────────────────
|
|
||||||
ONBOARDING_LANG=""
|
ONBOARDING_LANG=""
|
||||||
ONBOARDING_TRANSPORT=""
|
ONBOARDING_TRANSPORT=""
|
||||||
ONBOARDING_PROVIDER=""
|
ONBOARDING_PROVIDER=""
|
||||||
|
|
@ -31,331 +29,24 @@ SECRETS_ENV="$HOME/.claude/secrets/.env"
|
||||||
REGISTRY_PROVIDERS="$KIT_DIR/_blocks/registries/providers.toml"
|
REGISTRY_PROVIDERS="$KIT_DIR/_blocks/registries/providers.toml"
|
||||||
REGISTRY_MODELS="$KIT_DIR/_blocks/registries/models.toml"
|
REGISTRY_MODELS="$KIT_DIR/_blocks/registries/models.toml"
|
||||||
|
|
||||||
# ───────────────────────────────────────────────────────────────────────
|
# Подкубы (sourced параллельно — функции расходятся по namespace без коллизий).
|
||||||
# Skip-логика
|
# shellcheck source=install/lib-onboarding-registry.sh
|
||||||
# ───────────────────────────────────────────────────────────────────────
|
[ -f "$LIB_DIR/lib-onboarding-registry.sh" ] && source "$LIB_DIR/lib-onboarding-registry.sh"
|
||||||
|
# shellcheck source=install/lib-onboarding-ui.sh
|
||||||
|
[ -f "$LIB_DIR/lib-onboarding-ui.sh" ] && source "$LIB_DIR/lib-onboarding-ui.sh"
|
||||||
|
# shellcheck source=install/lib-onboarding-state.sh
|
||||||
|
[ -f "$LIB_DIR/lib-onboarding-state.sh" ] && source "$LIB_DIR/lib-onboarding-state.sh"
|
||||||
|
|
||||||
|
# Skip-логика.
|
||||||
onboarding_should_run() {
|
onboarding_should_run() {
|
||||||
[ -f "$ONBOARDED_FLAG" ] && return 1 # уже пройдено
|
[ -f "$ONBOARDED_FLAG" ] && return 1
|
||||||
[ "${KEISEI_SKIP_ONBOARD:-}" = "1" ] && return 1
|
[ "${KEISEI_SKIP_ONBOARD:-}" = "1" ] && return 1
|
||||||
[ ! -t 0 ] && return 1 # не TTY → скип, профиль решит
|
[ ! -t 0 ] && return 1
|
||||||
[ ! -t 1 ] && return 1
|
[ ! -t 1 ] && return 1
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# ───────────────────────────────────────────────────────────────────────
|
# Оркестратор: 5 шагов + preflight + запись.
|
||||||
# Парсер providers.toml. Простой awk-граббер по [[provider]] секциям.
|
|
||||||
# Печатает: <id>\t<transport>\t<display_name>\t<auth_env>
|
|
||||||
# ───────────────────────────────────────────────────────────────────────
|
|
||||||
onboarding_list_providers() {
|
|
||||||
[ -f "$REGISTRY_PROVIDERS" ] || { onboarding_fallback_providers; return; }
|
|
||||||
awk '
|
|
||||||
/^\[\[provider\]\]/ { id=""; tr=""; dn=""; ae=""; next }
|
|
||||||
/^id[[:space:]]*=/ { gsub(/^id[[:space:]]*=[[:space:]]*"/, ""); gsub(/".*$/, ""); id=$0 }
|
|
||||||
/^transport[[:space:]]*=/ { gsub(/^transport[[:space:]]*=[[:space:]]*"/, ""); gsub(/".*$/, ""); tr=$0 }
|
|
||||||
/^display_name[[:space:]]*=/ { gsub(/^display_name[[:space:]]*=[[:space:]]*"/, ""); gsub(/".*$/, ""); dn=$0 }
|
|
||||||
/^auth_env[[:space:]]*=/ { gsub(/^auth_env[[:space:]]*=[[:space:]]*"/, ""); gsub(/".*$/, ""); ae=$0;
|
|
||||||
if (id && tr) print id "\t" tr "\t" dn "\t" ae }
|
|
||||||
' "$REGISTRY_PROVIDERS"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Fallback если submodule не подтянут.
|
|
||||||
# Покрывает 7 транспортов (direct-api / aws / azure / vertex / local / proxy
|
|
||||||
# / subscription) минимальными представителями. Используется только когда
|
|
||||||
# providers.toml отсутствует — синхронизировать ручно если добавится новый
|
|
||||||
# транспорт-тип в реестр.
|
|
||||||
onboarding_fallback_providers() {
|
|
||||||
printf "anthropic\tdirect-api\tAnthropic (Direct API)\tANTHROPIC_API_KEY\n"
|
|
||||||
printf "anthropic-bedrock\taws-bedrock\tAnthropic (AWS Bedrock)\tAWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_REGION\n"
|
|
||||||
printf "openai\tdirect-api\tOpenAI (Direct API)\tOPENAI_API_KEY\n"
|
|
||||||
printf "openai-azure\tazure-openai\tOpenAI (Azure)\tAZURE_OPENAI_API_KEY,AZURE_OPENAI_ENDPOINT,AZURE_OPENAI_DEPLOYMENT\n"
|
|
||||||
printf "xai\tdirect-api\txAI\tXAI_API_KEY\n"
|
|
||||||
printf "deepseek\tdirect-api\tDeepSeek\tDEEPSEEK_API_KEY\n"
|
|
||||||
printf "google\tdirect-api\tGoogle Gemini (Direct API)\tGEMINI_API_KEY\n"
|
|
||||||
printf "google-vertex\tgoogle-vertex\tGoogle Gemini (Vertex AI)\tGOOGLE_APPLICATION_CREDENTIALS,GCP_PROJECT_ID,GCP_REGION\n"
|
|
||||||
printf "ollama-local\tlocal\tOllama (local)\t_\n"
|
|
||||||
printf "mlx-local\tlocal\tMLX (Apple silicon 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 "openrouter\tproxy\tOpenRouter\tOPENROUTER_API_KEY\n"
|
|
||||||
printf "codex\tsubscription\tOpenAI Codex (ChatGPT OAuth)\t_\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Уникальные транспорты — для первого экрана выбора.
|
|
||||||
onboarding_list_transports() {
|
|
||||||
onboarding_list_providers | awk -F'\t' '{print $2}' | sort -u
|
|
||||||
}
|
|
||||||
|
|
||||||
# Провайдеры внутри транспорта.
|
|
||||||
onboarding_providers_in_transport() {
|
|
||||||
local tr="$1"
|
|
||||||
onboarding_list_providers | awk -F'\t' -v t="$tr" '$2==t {print $1 "\t" $3 "\t" $4}'
|
|
||||||
}
|
|
||||||
|
|
||||||
# Модели по provider_ref.
|
|
||||||
onboarding_models_for_provider() {
|
|
||||||
local pr="$1"
|
|
||||||
[ -f "$REGISTRY_MODELS" ] || { printf "claude-sonnet-4-6\tClaude Sonnet 4.6\n"; return; }
|
|
||||||
awk -v pr="$pr" '
|
|
||||||
/^\[\[model\]\]/ { id=""; pref=""; dn=""; next }
|
|
||||||
/^id[[:space:]]*=/ { gsub(/^id[[:space:]]*=[[:space:]]*"/, ""); gsub(/".*$/, ""); id=$0 }
|
|
||||||
/^provider_ref[[:space:]]*=/ { gsub(/^provider_ref[[:space:]]*=[[:space:]]*"/, ""); gsub(/".*$/, ""); pref=$0 }
|
|
||||||
/^display_name[[:space:]]*=/ { gsub(/^display_name[[:space:]]*=[[:space:]]*"/, ""); gsub(/".*$/, ""); dn=$0;
|
|
||||||
if (pref==pr) print id "\t" dn }
|
|
||||||
' "$REGISTRY_MODELS"
|
|
||||||
}
|
|
||||||
|
|
||||||
# ───────────────────────────────────────────────────────────────────────
|
|
||||||
# UI: язык
|
|
||||||
# ───────────────────────────────────────────────────────────────────────
|
|
||||||
onboarding_pick_language() {
|
|
||||||
# Список языков читается из lib-i18n.sh::i18n_available_languages.
|
|
||||||
# На этом шаге язык ещё не выбран — заголовок двуязычный.
|
|
||||||
local langs
|
|
||||||
langs="$(i18n_available_languages 2>/dev/null)"
|
|
||||||
if [ -z "$langs" ]; then
|
|
||||||
# Fallback если lib-i18n не подключён.
|
|
||||||
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) %s — %s\n" "$i" "$code" "$name" >&2
|
|
||||||
i=$((i+1))
|
|
||||||
done <<< "$langs"
|
|
||||||
read -r -p "[1-${#codes[@]}, default 1=en]: " ans
|
|
||||||
ans="${ans:-1}"
|
|
||||||
ONBOARDING_LANG="${codes[$((ans-1))]:-en}"
|
|
||||||
fi
|
|
||||||
# Перегружаем словарь — все последующие строки на выбранном языке.
|
|
||||||
if command -v i18n_load_lang >/dev/null 2>&1; then
|
|
||||||
i18n_load_lang "$ONBOARDING_LANG"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# ───────────────────────────────────────────────────────────────────────
|
|
||||||
# UI: транспорт
|
|
||||||
# ───────────────────────────────────────────────────────────────────────
|
|
||||||
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
|
|
||||||
local i=1
|
|
||||||
declare -a opts=()
|
|
||||||
while IFS= read -r tr; do
|
|
||||||
opts+=("$tr")
|
|
||||||
echo " $i) $tr" >&2
|
|
||||||
i=$((i+1))
|
|
||||||
done <<< "$transports"
|
|
||||||
read -r -p "[1-${#opts[@]}, default 1]: " ans
|
|
||||||
ans="${ans:-1}"
|
|
||||||
ONBOARDING_TRANSPORT="${opts[$((ans-1))]:-direct-api}"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# ───────────────────────────────────────────────────────────────────────
|
|
||||||
# UI: провайдер
|
|
||||||
# ───────────────────────────────────────────────────────────────────────
|
|
||||||
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
|
|
||||||
# Используем единый fallback что и для whiptail — устраняем plural mismatch.
|
|
||||||
echo "${STR_PICK_PROVIDER:-Provider within} $ONBOARDING_TRANSPORT:" >&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"
|
|
||||||
read -r -p "[1-${#ids[@]}, default 1]: " ans
|
|
||||||
ans="${ans:-1}"
|
|
||||||
ONBOARDING_PROVIDER="${ids[$((ans-1))]:-${ids[0]}}"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# ───────────────────────────────────────────────────────────────────────
|
|
||||||
# UI: модель
|
|
||||||
# ───────────────────────────────────────────────────────────────────────
|
|
||||||
onboarding_pick_model() {
|
|
||||||
# Для AWS/Azure/Vertex модели идут под parent-провайдером (anthropic, openai, google) —
|
|
||||||
# эти транспорты ре-используют тот же models.toml. Мапим bedrock→anthropic, azure→openai, vertex→google.
|
|
||||||
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")
|
|
||||||
|
|
||||||
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
|
|
||||||
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"
|
|
||||||
read -r -p "[1-${#ids[@]}, default 1]: " ans
|
|
||||||
ans="${ans:-1}"
|
|
||||||
ONBOARDING_MODEL="${ids[$((ans-1))]:-${ids[0]}}"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# ───────────────────────────────────────────────────────────────────────
|
|
||||||
# UI: ключи / креды по auth_env
|
|
||||||
# ───────────────────────────────────────────────────────────────────────
|
|
||||||
onboarding_collect_auth() {
|
|
||||||
ONBOARDING_AUTH_ENV_KEYS=()
|
|
||||||
ONBOARDING_AUTH_ENV_VALUES=()
|
|
||||||
local ae; ae=$(onboarding_list_providers | awk -F'\t' -v p="$ONBOARDING_PROVIDER" '$1==p {print $4}')
|
|
||||||
[ -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>)}"
|
|
||||||
# silent read — значение не светит в терминале
|
|
||||||
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"
|
|
||||||
}
|
|
||||||
|
|
||||||
# ───────────────────────────────────────────────────────────────────────
|
|
||||||
# Запись результата
|
|
||||||
# ───────────────────────────────────────────────────────────────────────
|
|
||||||
onboarding_write_secrets() {
|
|
||||||
[ "${#ONBOARDING_AUTH_ENV_KEYS[@]}" = "0" ] && return
|
|
||||||
mkdir -p "$(dirname "$SECRETS_ENV")"
|
|
||||||
touch "$SECRETS_ENV"; chmod 600 "$SECRETS_ENV"
|
|
||||||
local i
|
|
||||||
for i in "${!ONBOARDING_AUTH_ENV_KEYS[@]}"; do
|
|
||||||
local k="${ONBOARDING_AUTH_ENV_KEYS[$i]}"
|
|
||||||
local v="${ONBOARDING_AUTH_ENV_VALUES[$i]}"
|
|
||||||
# удалим старую строку с тем же ключом
|
|
||||||
if grep -q "^${k}=" "$SECRETS_ENV" 2>/dev/null; then
|
|
||||||
grep -v "^${k}=" "$SECRETS_ENV" > "$SECRETS_ENV.tmp"
|
|
||||||
mv "$SECRETS_ENV.tmp" "$SECRETS_ENV"
|
|
||||||
fi
|
|
||||||
printf '%s=%s\n' "$k" "$v" >> "$SECRETS_ENV"
|
|
||||||
done
|
|
||||||
chmod 600 "$SECRETS_ENV"
|
|
||||||
}
|
|
||||||
|
|
||||||
onboarding_write_config() {
|
|
||||||
mkdir -p "$(dirname "$ONBOARDING_CONFIG")"
|
|
||||||
cat > "$ONBOARDING_CONFIG" <<EOF
|
|
||||||
# KeiSeiKit onboarding choices. Auto-generated by lib-onboarding.sh.
|
|
||||||
# Re-run wizard: rm ~/.claude/.onboarded && ./install.sh
|
|
||||||
language = "$ONBOARDING_LANG"
|
|
||||||
transport = "$ONBOARDING_TRANSPORT"
|
|
||||||
provider = "$ONBOARDING_PROVIDER"
|
|
||||||
default_model = "$ONBOARDING_MODEL"
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# Дополнительный файл специально для kei-model-router.
|
|
||||||
# Имеет приоритет выше agent-profiles.toml default_model_ref,
|
|
||||||
# ниже --pinned flag в коде. Router читает его как user-tier override.
|
|
||||||
# Без него выбор провайдера в onboarding декоративен (HIGH аудит-1).
|
|
||||||
local override_path="$HOME/.claude/config/user-model-override.toml"
|
|
||||||
cat > "$override_path" <<EOF
|
|
||||||
# User-tier model override. Auto-generated by onboarding wizard.
|
|
||||||
# Format: kei-model-router::Registry::load_user_override().
|
|
||||||
# Priority: --pinned flag > этот файл > agent-profiles.toml default_model_ref.
|
|
||||||
provider = "$ONBOARDING_PROVIDER"
|
|
||||||
model = "$ONBOARDING_MODEL"
|
|
||||||
transport = "$ONBOARDING_TRANSPORT"
|
|
||||||
EOF
|
|
||||||
|
|
||||||
: > "$ONBOARDED_FLAG"
|
|
||||||
}
|
|
||||||
|
|
||||||
# ───────────────────────────────────────────────────────────────────────
|
|
||||||
# Оркестратор
|
|
||||||
# ───────────────────────────────────────────────────────────────────────
|
|
||||||
onboarding_run() {
|
onboarding_run() {
|
||||||
onboarding_should_run || return 0
|
onboarding_should_run || return 0
|
||||||
|
|
||||||
|
|
@ -369,13 +60,10 @@ onboarding_run() {
|
||||||
onboarding_pick_transport
|
onboarding_pick_transport
|
||||||
onboarding_pick_provider
|
onboarding_pick_provider
|
||||||
onboarding_pick_model
|
onboarding_pick_model
|
||||||
# Preflight — проверка CLI/daemon до сбора ключей.
|
|
||||||
# Для direct-api провайдеров файла preflight нет → silent pass.
|
# Preflight — провайдер-специфичная проверка CLI/daemon до сбора ключей.
|
||||||
if command -v preflight_run >/dev/null 2>&1; then
|
if command -v preflight_run >/dev/null 2>&1; then
|
||||||
if ! preflight_run "$ONBOARDING_PROVIDER"; then
|
if ! preflight_run "$ONBOARDING_PROVIDER"; then
|
||||||
# Provider preflight failed (CLI missing / daemon down / no creds).
|
|
||||||
# Не молчим — спрашиваем юзера, иначе onboarding закончится
|
|
||||||
# с .onboarded флагом для нерабочей конфигурации (HIGH аудит-9).
|
|
||||||
echo "" >&2
|
echo "" >&2
|
||||||
echo " ⚠ ${STR_PREFLIGHT_FAILED:-Preflight failed — provider may not work.}" >&2
|
echo " ⚠ ${STR_PREFLIGHT_FAILED:-Preflight failed — provider may not work.}" >&2
|
||||||
if [ -t 0 ] && [ -t 1 ]; then
|
if [ -t 0 ] && [ -t 1 ]; then
|
||||||
|
|
@ -394,6 +82,7 @@ onboarding_run() {
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
onboarding_collect_auth
|
onboarding_collect_auth
|
||||||
onboarding_write_secrets
|
onboarding_write_secrets
|
||||||
onboarding_write_config
|
onboarding_write_config
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue