diff --git a/install.sh b/install.sh index 3c14a4b..ac24bb5 100755 --- a/install.sh +++ b/install.sh @@ -45,6 +45,8 @@ source "$LIB_DIR/lib-menu.sh" source "$LIB_DIR/lib-i18n.sh" # Загружаем английский словарь по умолчанию — welcome banner идёт до выбора языка. i18n_load_default +# shellcheck source=install/lib-preflight.sh +source "$LIB_DIR/lib-preflight.sh" # shellcheck source=install/lib-onboarding.sh source "$LIB_DIR/lib-onboarding.sh" # shellcheck source=install/lib-plan.sh diff --git a/install/lib-onboarding.sh b/install/lib-onboarding.sh index e133a82..d6328ca 100644 --- a/install/lib-onboarding.sh +++ b/install/lib-onboarding.sh @@ -319,6 +319,11 @@ onboarding_run() { onboarding_pick_transport onboarding_pick_provider onboarding_pick_model + # Preflight — проверка CLI/daemon до сбора ключей. + # Для direct-api провайдеров файла preflight нет → silent pass. + if command -v preflight_run >/dev/null 2>&1; then + preflight_run "$ONBOARDING_PROVIDER" || true + fi onboarding_collect_auth onboarding_write_secrets onboarding_write_config diff --git a/install/lib-preflight.sh b/install/lib-preflight.sh new file mode 100644 index 0000000..9588d7e --- /dev/null +++ b/install/lib-preflight.sh @@ -0,0 +1,66 @@ +# shellcheck shell=bash +# lib-preflight.sh — диспетчер preflight-проверок CLI. +# +# Контракт: +# preflight_run +# 1. Ищет файл install/preflight/.sh +# 2. Если есть — source'ит и вызывает `preflight_check_` +# 3. Функция возвращает 0 (ok) / 1 (missing, инструкция напечатана) +# 4. Если файла нет — провайдеру CLI не нужен, тихо exit 0 +# +# Файл per-provider должен экспортировать ОДНУ функцию: +# preflight_check_() — печатает инструкцию в stderr, exit 0/1 +# +# Sanitize: dashes в id заменяются на underscores для имени функции +# (bash не любит dashes в идентификаторах). + +PREFLIGHT_DIR="${LIB_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}/preflight" + +# Печатает инструкцию по установке, спрашивает действие. +# Аргументы: $1 — имя CLI, $2 — команда установки. +preflight_offer_install() { + local cli="$1" + local install_cmd="$2" + echo "" >&2 + echo " ⚠ $cli не найден." >&2 + echo " Установить: $install_cmd" >&2 + echo "" >&2 + if [ -t 0 ] && [ -t 1 ]; then + read -r -p " Поставить сейчас? [y/N/skip] " ans + case "$ans" in + y|Y|yes) + eval "$install_cmd" + return $? + ;; + skip|s|S) + echo " пропускаю — поставите вручную позже." >&2 + return 0 + ;; + *) + echo " пропуск (по умолчанию)." >&2 + return 1 + ;; + esac + else + # non-TTY: только печатаем инструкцию. + return 1 + fi +} + +# Главный диспетчер. Вызывается из onboarding между pick_model и collect_auth. +preflight_run() { + local provider="$1" + [ -z "$provider" ] && return 0 + local script="$PREFLIGHT_DIR/${provider}.sh" + if [ ! -f "$script" ]; then + return 0 # CLI не нужен — direct-api, ключ собирается ниже + fi + # shellcheck disable=SC1090 + source "$script" + local fn="preflight_check_${provider//-/_}" + if command -v "$fn" >/dev/null 2>&1; then + "$fn" + return $? + fi + return 0 +} diff --git a/install/preflight/anthropic-bedrock.sh b/install/preflight/anthropic-bedrock.sh new file mode 100644 index 0000000..1250e9a --- /dev/null +++ b/install/preflight/anthropic-bedrock.sh @@ -0,0 +1,26 @@ +# shellcheck shell=bash +# preflight/anthropic-bedrock.sh — AWS CLI + Bedrock региональный доступ. + +preflight_check_anthropic_bedrock() { + if ! command -v aws >/dev/null 2>&1; then + local cmd + case "$(uname -s)" in + Darwin) cmd="brew install awscli" ;; + Linux) cmd="curl 'https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip' -o /tmp/awscliv2.zip && unzip -q /tmp/awscliv2.zip -d /tmp && sudo /tmp/aws/install" ;; + *) cmd="см. https://aws.amazon.com/cli/" ;; + esac + preflight_offer_install "aws CLI" "$cmd" || return 1 + fi + # Проверяем что credentials хоть как-то настроены (env, ~/.aws/credentials, IAM role). + if ! aws sts get-caller-identity >/dev/null 2>&1; then + echo "" >&2 + echo " ⚠ AWS credentials не настроены." >&2 + echo " Запустите: aws configure" >&2 + echo " Или экспортируйте AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY + AWS_REGION." >&2 + echo "" >&2 + return 1 + fi + echo " ✓ aws CLI: $(aws --version 2>&1 | head -1)" >&2 + echo " ✓ identity: $(aws sts get-caller-identity --query Arn --output text 2>&1)" >&2 + return 0 +} diff --git a/install/preflight/codex.sh b/install/preflight/codex.sh new file mode 100644 index 0000000..5eb7c97 --- /dev/null +++ b/install/preflight/codex.sh @@ -0,0 +1,29 @@ +# shellcheck shell=bash +# preflight/codex.sh — OpenAI Codex CLI через ChatGPT OAuth. + +preflight_check_codex() { + if ! command -v codex >/dev/null 2>&1; then + if ! command -v npm >/dev/null 2>&1; then + echo "" >&2 + echo " ⚠ npm требуется для установки codex." >&2 + echo " Сначала: brew install node (macOS) или apt install nodejs npm (Linux)" >&2 + echo "" >&2 + return 1 + fi + preflight_offer_install "codex CLI" "npm install -g @openai/codex" || return 1 + fi + # Проверяем что OAuth активен. + local status + status="$(codex login status 2>&1 || true)" + if ! echo "$status" | grep -qiE "logged.in|active"; then + echo "" >&2 + echo " ⚠ codex не залогинен в ChatGPT." >&2 + echo " Запустите: codex login" >&2 + echo " (требуется ChatGPT Plus/Pro/Team подписка)" >&2 + echo "" >&2 + return 1 + fi + echo " ✓ codex CLI: $(codex --version 2>&1 | head -1)" >&2 + echo " ✓ OAuth: $status" >&2 + return 0 +} diff --git a/install/preflight/google-vertex.sh b/install/preflight/google-vertex.sh new file mode 100644 index 0000000..3e4a1ff --- /dev/null +++ b/install/preflight/google-vertex.sh @@ -0,0 +1,28 @@ +# shellcheck shell=bash +# preflight/google-vertex.sh — gcloud CLI + service-account JSON. + +preflight_check_google_vertex() { + if ! command -v gcloud >/dev/null 2>&1; then + local cmd + case "$(uname -s)" in + Darwin) cmd="brew install --cask google-cloud-sdk" ;; + Linux) cmd="curl https://sdk.cloud.google.com | bash" ;; + *) cmd="см. https://cloud.google.com/sdk/docs/install" ;; + esac + preflight_offer_install "gcloud CLI" "$cmd" || return 1 + fi + # Проверяем что выбран project. + local project + project="$(gcloud config get-value project 2>/dev/null)" + if [ -z "$project" ] || [ "$project" = "(unset)" ]; then + echo "" >&2 + echo " ⚠ GCP project не выбран." >&2 + echo " Запустите: gcloud auth login && gcloud config set project YOUR_PROJECT_ID" >&2 + echo " Также установите GOOGLE_APPLICATION_CREDENTIALS на путь к service-account JSON." >&2 + echo "" >&2 + return 1 + fi + echo " ✓ gcloud CLI: $(gcloud --version 2>&1 | head -1)" >&2 + echo " ✓ project: $project" >&2 + return 0 +} diff --git a/install/preflight/lmstudio-local.sh b/install/preflight/lmstudio-local.sh new file mode 100644 index 0000000..c1bef68 --- /dev/null +++ b/install/preflight/lmstudio-local.sh @@ -0,0 +1,16 @@ +# shellcheck shell=bash +# preflight/lmstudio-local.sh — LM Studio desktop GUI на 127.0.0.1:1234. + +preflight_check_lmstudio_local() { + # LM Studio это desktop-приложение, не CLI — проверяем только порт. + if ! curl -fsS --max-time 3 http://127.0.0.1:1234/v1/models >/dev/null 2>&1; then + echo "" >&2 + echo " ⚠ LM Studio сервер не запущен на 1234." >&2 + echo " Скачайте: https://lmstudio.ai/" >&2 + echo " В GUI: Local Server → Start Server (порт 1234 по умолчанию)" >&2 + echo "" >&2 + return 1 + fi + echo " ✓ LM Studio: 127.0.0.1:1234 отвечает" >&2 + return 0 +} diff --git a/install/preflight/mlx-local.sh b/install/preflight/mlx-local.sh new file mode 100644 index 0000000..e10c89d --- /dev/null +++ b/install/preflight/mlx-local.sh @@ -0,0 +1,23 @@ +# shellcheck shell=bash +# preflight/mlx-local.sh — MLX inference server (Apple silicon). + +preflight_check_mlx_local() { + if [ "$(uname -s)" != "Darwin" ] || [ "$(uname -m)" != "arm64" ]; then + echo "" >&2 + echo " ⚠ MLX доступен только на Apple silicon (arm64 macOS)." >&2 + echo " Текущая платформа: $(uname -s) $(uname -m)" >&2 + return 1 + fi + if ! command -v mlx_lm.server >/dev/null 2>&1; then + preflight_offer_install "mlx_lm" "pip install mlx-lm" || return 1 + fi + if ! curl -fsS --max-time 3 http://127.0.0.1:8080/v1/models >/dev/null 2>&1; then + echo "" >&2 + echo " ⚠ MLX server не запущен на 8080." >&2 + echo " Запустите: mlx_lm.server --model mlx-community/Qwen2.5-Coder-32B-Instruct-4bit" >&2 + echo "" >&2 + return 1 + fi + echo " ✓ mlx_lm.server: 127.0.0.1:8080 отвечает" >&2 + return 0 +} diff --git a/install/preflight/ollama-local.sh b/install/preflight/ollama-local.sh new file mode 100644 index 0000000..d949290 --- /dev/null +++ b/install/preflight/ollama-local.sh @@ -0,0 +1,25 @@ +# shellcheck shell=bash +# preflight/ollama-local.sh — Ollama daemon на 127.0.0.1:11434. + +preflight_check_ollama_local() { + if ! command -v ollama >/dev/null 2>&1; then + local cmd + case "$(uname -s)" in + Darwin) cmd="brew install ollama" ;; + Linux) cmd="curl -fsSL https://ollama.com/install.sh | sh" ;; + *) cmd="см. https://ollama.com/download" ;; + esac + preflight_offer_install "ollama" "$cmd" || return 1 + fi + # Проверяем что daemon запущен. + if ! curl -fsS --max-time 3 http://127.0.0.1:11434/api/tags >/dev/null 2>&1; then + echo "" >&2 + echo " ⚠ ollama daemon не запущен." >&2 + echo " Запустите: ollama serve (или brew services start ollama на macOS)" >&2 + echo "" >&2 + return 1 + fi + echo " ✓ ollama: $(ollama --version 2>&1 | head -1)" >&2 + echo " ✓ daemon: 127.0.0.1:11434 отвечает" >&2 + return 0 +}