#!/usr/bin/env bash # Sundy Handbook · Gemini CLI macOS / Linux 一键安装脚本 # # 推荐用法: # bash <(curl -fsSL https://sundy.tumpai.site/install-gemini.sh) # # 全自动(CI / 无人值守): # SUNDY_API_KEY=sk-xxx GEMINI_SUNDY_YES=1 bash <(curl -fsSL https://sundy.tumpai.site/install-gemini.sh) # # 如果已经跑过 Codex / Claude Code 安装脚本,本脚本会自动复用本机已有的 SUNDY_API_KEY。 set -e DEFAULT_BASE_URL="https://sundy-api.tumpai.site:2053/api/provider/antigravity" LEGACY_BASE_URL="https://sundy-api.tumpai.site:2053" BASE_URL="${GOOGLE_GEMINI_BASE_URL:-${GEMINI_BASE_URL:-$DEFAULT_BASE_URL}}" BASE_URL="${BASE_URL%/}" if [ "$BASE_URL" = "$LEGACY_BASE_URL" ]; then BASE_URL="$DEFAULT_BASE_URL" fi API_VERSION="${GOOGLE_GENAI_API_VERSION:-${GEMINI_API_VERSION:-v1beta}}" AUTH_MECHANISM="${GEMINI_API_KEY_AUTH_MECHANISM:-x-goog-api-key}" MODEL_DEFAULT="${GEMINI_MODEL:-auto-gemini-3}" case "$MODEL_DEFAULT" in gemini-2.5-flash|gemini-3-flash) MODEL_DEFAULT="auto-gemini-3" ;; esac # --- 参数解析 --- ASSUME_YES="${GEMINI_SUNDY_YES:-0}" for arg in "$@"; do case "$arg" in -y|--yes) ASSUME_YES=1 ;; -h|--help) cat <&2; exit 1 ;; esac done # --- 颜色 --- if [ -t 1 ]; then C_RED=$'\033[31m'; C_GRN=$'\033[32m'; C_YLW=$'\033[33m' C_BLU=$'\033[34m'; C_BLD=$'\033[1m'; C_RST=$'\033[0m' else C_RED=""; C_GRN=""; C_YLW=""; C_BLU=""; C_BLD=""; C_RST="" fi ok() { echo "${C_GRN}✔${C_RST} $*"; } info() { echo "${C_BLU}ℹ${C_RST} $*"; } warn() { echo "${C_YLW}⚠${C_RST} $*"; } fail() { echo "${C_RED}✘${C_RST} $*" >&2; exit 1; } title() { echo; echo "${C_BLD}${C_BLU}==> $*${C_RST}"; } case "$AUTH_MECHANISM" in x-goog-api-key|bearer) ;; *) fail "GEMINI_API_KEY_AUTH_MECHANISM 只能是 x-goog-api-key 或 bearer,当前为:$AUTH_MECHANISM" ;; esac # --- stdin 兜底 --- NON_INTERACTIVE=0 if [ ! -t 0 ]; then if [ -r /dev/tty ] && exec /dev/null; then : else warn "未检测到可交互终端,进入非交互模式(需提前设置 SUNDY_API_KEY 或 GEMINI_API_KEY)" NON_INTERACTIVE=1 ASSUME_YES=1 fi fi # --- 系统识别 --- OS="$(uname -s)" ARCH="$(uname -m)" case "$OS" in Darwin) PLATFORM="mac" ;; Linux) PLATFORM="linux" ;; *) fail "暂不支持的系统:$OS(Windows 请使用 install-gemini.ps1)" ;; esac echo cat </dev/null || echo '')" if [ "$cur" = "https://registry.npmjs.org/" ]; then info "未检测到代理;提示:如果 npm 安装很慢,可执行:$NPM_CMD config set registry https://registry.npmmirror.com" fi fi fi } find_npm_cmd() { local node_path node_dir node_path="$(command -v node 2>/dev/null || true)" if [ -n "$node_path" ]; then node_dir="$(dirname "$node_path")" if [ -x "$node_dir/npm" ]; then printf '%s\n' "$node_dir/npm" return 0 fi fi command -v npm 2>/dev/null || true } strip_shell_value() { sed -E "s/[[:space:]]+#.*$//; s/^[[:space:]]+//; s/[[:space:]]+$//; s/^\"//; s/\"$//; s/^'//; s/'$//" } extract_key_from_file() { local file="$1" local name line value [ -f "$file" ] || return 1 for name in SUNDY_API_KEY GEMINI_API_KEY ANTHROPIC_AUTH_TOKEN CLAUDE_API_KEY CODEX_API_KEY; do line="$(grep -E "^[[:space:]]*(export[[:space:]]+)?${name}=" "$file" 2>/dev/null | tail -n 1 || true)" if [ -n "$line" ]; then value="$(printf '%s\n' "${line#*=}" | strip_shell_value)" if [ -n "$value" ]; then API_KEY="$value" KEY_SOURCE="$file:$name" return 0 fi fi line="$(grep -E "^[[:space:]]*set[[:space:]]+-gx[[:space:]]+${name}[[:space:]]+" "$file" 2>/dev/null | tail -n 1 || true)" if [ -n "$line" ]; then value="$(printf '%s\n' "$line" | sed -E "s/^[[:space:]]*set[[:space:]]+-gx[[:space:]]+${name}[[:space:]]+//" | strip_shell_value)" if [ -n "$value" ]; then API_KEY="$value" KEY_SOURCE="$file:$name" return 0 fi fi done return 1 } discover_existing_api_key() { local files=( "$HOME/.zshenv" "$HOME/.zshrc" "$HOME/.bash_profile" "$HOME/.bashrc" "$HOME/.profile" "$HOME/.config/fish/config.fish" "$HOME/.gemini/.env" "$HOME/.env" ) local file for file in "${files[@]}"; do if extract_key_from_file "$file"; then return 0 fi done return 1 } node_major() { node -p "Number(process.versions.node.split('.')[0])" 2>/dev/null || echo 0 } install_node() { if [ "$PLATFORM" = "mac" ]; then if ! command -v brew >/dev/null 2>&1; then info "未发现 Homebrew,开始安装 Homebrew(约 2-5 分钟,可能需要输入 sudo 密码)" /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" if [ -d /opt/homebrew/bin ]; then eval "$(/opt/homebrew/bin/brew shellenv)" fi fi brew install node else if command -v apt-get >/dev/null 2>&1; then info "使用 apt 安装 Node 20" curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - sudo apt-get install -y nodejs elif command -v dnf >/dev/null 2>&1; then curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo -E bash - sudo dnf install -y nodejs elif command -v yum >/dev/null 2>&1; then curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo -E bash - sudo yum install -y nodejs else fail "无法识别的 Linux 发行版,请手动安装 Node.js 20+ 后重试:https://nodejs.org/" fi fi } title "1/6 检查 Node.js" if command -v node >/dev/null 2>&1 && [ "$(node_major)" -ge 20 ]; then ok "已安装 Node $(node -v)" else if command -v node >/dev/null 2>&1; then warn "当前 Node $(node -v) 低于 Gemini CLI 要求的 20+,开始升级..." else warn "未检测到 Node.js,开始安装 Node 20+..." fi install_node if ! command -v node >/dev/null 2>&1 || [ "$(node_major)" -lt 20 ]; then fail "Node.js 20+ 安装后仍不可用,请开新终端或手动安装 Node.js 20+" fi ok "Node 已就绪:$(node -v)" fi NPM_CMD="$(find_npm_cmd)" [ -n "$NPM_CMD" ] || fail "未找到 npm,请确认 Node.js 安装完整后重试" NODE_BIN_DIR="$(dirname "$(command -v node)")" maybe_setup_npm_mirror title "2/6 安装 Gemini CLI" if command -v gemini >/dev/null 2>&1; then ok "已安装 gemini($(gemini --version 2>/dev/null || echo unknown)),将更新到最新版" fi if [ "$PLATFORM" = "linux" ] && [ "$(id -u)" != "0" ]; then if [ ! -w "$("$NPM_CMD" prefix -g 2>/dev/null || echo /usr/lib)" ]; then info "全局 npm 目录不可写,将使用 sudo" sudo "$NPM_CMD" install -g @google/gemini-cli || fail "安装 Gemini CLI 失败" else "$NPM_CMD" install -g @google/gemini-cli || fail "安装 Gemini CLI 失败" fi else "$NPM_CMD" install -g @google/gemini-cli || fail "安装 Gemini CLI 失败" fi GEMINI_BIN="" if [ -x "$NODE_BIN_DIR/gemini" ]; then GEMINI_BIN="$NODE_BIN_DIR/gemini" else GEMINI_BIN="$(command -v gemini 2>/dev/null || true)" fi [ -n "$GEMINI_BIN" ] || fail "Gemini CLI 安装后仍找不到 gemini 命令" ok "Gemini CLI 已安装:$("$GEMINI_BIN" --version 2>/dev/null || echo '已就绪')($GEMINI_BIN)" if [ "$(command -v gemini 2>/dev/null || true)" != "$GEMINI_BIN" ]; then warn "检测到 PATH 里优先命中旧 gemini:$(command -v gemini)。脚本会把 $NODE_BIN_DIR 写到 PATH 前面。" fi title "3/6 复用 API 密钥" API_KEY="${SUNDY_API_KEY:-${GEMINI_API_KEY:-${ANTHROPIC_AUTH_TOKEN:-${CLAUDE_API_KEY:-${CODEX_API_KEY:-}}}}}" KEY_SOURCE="" if [ -n "$API_KEY" ]; then KEY_SOURCE="当前环境变量" ok "已复用本机已有密钥(来源:$KEY_SOURCE,${#API_KEY} 字符)" elif discover_existing_api_key; then ok "已复用本机已有密钥(来源:$KEY_SOURCE,${#API_KEY} 字符)" elif [ "$NON_INTERACTIVE" = "1" ]; then fail "非交互模式未找到已有 SUNDY_API_KEY / GEMINI_API_KEY;请先设置其中一个环境变量" else echo "未在本机找到已有 Sundy API Key。请把服务方发放的 Sundy API Key 粘贴进来(输入会显示在屏幕上):" echo " · 这是访问 $BASE_URL 的客户端密钥" echo " · 对 Gemini CLI 会映射为 GEMINI_API_KEY" echo " · 不会上传,只写到本机 shell、~/.gemini/settings.json 和 ~/.gemini/.env" echo while [ -z "$API_KEY" ]; do read -rp "${C_BLD}🔑 API Key: ${C_RST}" API_KEY if [ -z "$API_KEY" ]; then warn "密钥为空,请重新输入" fi done ok "已收到密钥(${#API_KEY} 字符)" fi if [ -n "${GOOGLE_API_KEY:-}" ] && [ "$GOOGLE_API_KEY" != "$API_KEY" ]; then warn "检测到当前环境里已有 GOOGLE_API_KEY。Gemini SDK 会优先使用 GOOGLE_API_KEY;如 Gemini CLI 仍走旧 key,请在该终端 unset GOOGLE_API_KEY 后重试。" fi title "4/6 确认可用模型" MODELS_FILE="$(mktemp "${TMPDIR:-/tmp}/gemini_sundy_models.XXXXXX")" MODELS_URL="$BASE_URL/$API_VERSION/models" if [ "$AUTH_MECHANISM" = "bearer" ]; then MODELS_HTTP_CODE="$(curl -s -o "$MODELS_FILE" -w "%{http_code}" --max-time 15 -H "Authorization: Bearer $API_KEY" "$MODELS_URL" 2>/dev/null || true)" else MODELS_HTTP_CODE="$(curl -s -o "$MODELS_FILE" -w "%{http_code}" --max-time 15 -H "x-goog-api-key: $API_KEY" "$MODELS_URL" 2>/dev/null || true)" fi MODELS_HTTP_CODE="${MODELS_HTTP_CODE:-000}" if [ "$MODELS_HTTP_CODE" = "200" ]; then MODEL_RESULT="$(node - "$MODELS_FILE" "$MODEL_DEFAULT" <<'NODE' const fs = require('fs'); const file = process.argv[2]; const requested = process.argv[3]; const autoRequirements = { auto: ['gemini-pro-agent', 'gemini-3-flash', 'gemini-3.1-flash-lite'], 'auto-gemini-3': ['gemini-pro-agent', 'gemini-3-flash', 'gemini-3.1-flash-lite'], 'auto-gemini-2.5': ['gemini-3.1-pro-low', 'gemini-3.1-flash-lite'] }; const preferred = [ 'auto-gemini-3', 'gemini-3-flash', 'gemini-3-pro-low', 'gemini-3-pro-high', 'gemini-3.1-flash-lite', 'gemini-3.1-pro-low', 'gemini-pro-agent' ]; try { const json = JSON.parse(fs.readFileSync(file, 'utf8')); const names = (json.models || []) .map((model) => String(model.name || '').replace(/^models\//, '')) .filter(Boolean); if (autoRequirements[requested] && autoRequirements[requested].every((name) => names.includes(name))) { console.log(`ok:${requested}`); process.exit(0); } if (names.includes(requested)) { console.log(`ok:${requested}`); process.exit(0); } const fallback = preferred.find((name) => autoRequirements[name]?.every((required) => names.includes(required)) || names.includes(name)) || names.find((name) => /^gemini/i.test(name)); if (fallback) { console.log(`fallback:${fallback}`); } else { console.log('none:'); } } catch { console.log('none:'); } NODE )" case "$MODEL_RESULT" in ok:*) ok "默认模型可用:$MODEL_DEFAULT" ;; fallback:*) OLD_MODEL="$MODEL_DEFAULT" MODEL_DEFAULT="${MODEL_RESULT#fallback:}" warn "当前 GEMINI_MODEL=$OLD_MODEL 不在服务端模型列表,已改用 $MODEL_DEFAULT" ;; *) warn "已连通模型列表,但没有找到可自动选择的 Gemini 模型;保留 $MODEL_DEFAULT" ;; esac else warn "暂时无法读取模型列表(HTTP $MODELS_HTTP_CODE),保留默认模型 $MODEL_DEFAULT;稍后会继续做连通性检查" fi rm -f "$MODELS_FILE" OWNER_FILE="$(mktemp "${TMPDIR:-/tmp}/gemini_sundy_owner.XXXXXX")" API_ROOT="$BASE_URL" case "$API_ROOT" in */api/provider/antigravity) API_ROOT="${API_ROOT%/api/provider/antigravity}" ;; esac OWNER_URL="$API_ROOT/v1/models" OWNER_HTTP_CODE="$(curl -s -o "$OWNER_FILE" -w "%{http_code}" --max-time 15 -H "Authorization: Bearer $API_KEY" "$OWNER_URL" 2>/dev/null || true)" OWNER_HTTP_CODE="${OWNER_HTTP_CODE:-000}" if [ "$OWNER_HTTP_CODE" = "200" ]; then OWNER_RESULT="$(node - "$OWNER_FILE" "$MODEL_DEFAULT" <<'NODE' const fs = require('fs'); const file = process.argv[2]; const requested = process.argv[3]; const autoRequirements = { auto: ['gemini-pro-agent', 'gemini-3-flash', 'gemini-3.1-flash-lite'], 'auto-gemini-3': ['gemini-pro-agent', 'gemini-3-flash', 'gemini-3.1-flash-lite'], 'auto-gemini-2.5': ['gemini-3.1-pro-low', 'gemini-3.1-flash-lite'] }; const preferred = [ 'auto-gemini-3', 'gemini-3-flash', 'gemini-3.1-flash-lite', 'gemini-3.1-pro-low', 'gemini-pro-agent', 'gemini-3-pro-low', 'gemini-3-pro-high' ]; try { const json = JSON.parse(fs.readFileSync(file, 'utf8')); const models = Array.isArray(json.data) ? json.data : Array.isArray(json.models) ? json.models : []; const owned = models .filter((model) => String(model.owned_by || '').toLowerCase() === 'antigravity') .map((model) => String(model.id || model.name || '').replace(/^models\//, '')) .filter((name) => /^gemini/i.test(name)); if (autoRequirements[requested] && autoRequirements[requested].every((name) => owned.includes(name))) { console.log(`ok:${requested}`); process.exit(0); } if (owned.includes(requested)) { console.log(`ok:${requested}`); process.exit(0); } const fallback = preferred.find((name) => autoRequirements[name]?.every((required) => owned.includes(required)) || owned.includes(name)) || owned[0]; if (fallback) { console.log(`fallback:${fallback}`); } else { console.log('none:'); } } catch { console.log('none:'); } NODE )" case "$OWNER_RESULT" in ok:*) case "$MODEL_DEFAULT" in auto|auto-gemini-*) ok "已确认 $MODEL_DEFAULT 只会解析到 /v1/models 中 owned_by=antigravity 的 Gemini 模型" ;; *) ok "已确认 $MODEL_DEFAULT 在 /v1/models 中 owned_by=antigravity" ;; esac ;; fallback:*) OLD_MODEL="$MODEL_DEFAULT" MODEL_DEFAULT="${OWNER_RESULT#fallback:}" warn "当前 GEMINI_MODEL=$OLD_MODEL 不是 Antigravity 所有的 Gemini 模型,已改用 $MODEL_DEFAULT" ;; *) warn "未能在 $OWNER_URL 找到 owned_by=antigravity 的 Gemini 模型;请让服务方配置 Antigravity 专属 Gemini alias" ;; esac else warn "无法读取 $OWNER_URL(HTTP $OWNER_HTTP_CODE),不能自动证明模型 owned_by=antigravity" fi rm -f "$OWNER_FILE" title "5/6 写入配置" GEMINI_DIR="$HOME/.gemini" GEMINI_SETTINGS="$GEMINI_DIR/settings.json" GEMINI_ENV="$GEMINI_DIR/.env" mkdir -p "$GEMINI_DIR" if [ -f "$GEMINI_SETTINGS" ]; then BAK="$GEMINI_SETTINGS.bak-$(date +%Y%m%d-%H%M%S)" cp "$GEMINI_SETTINGS" "$BAK" ok "已备份旧配置到 $BAK" fi node - "$GEMINI_SETTINGS" <<'NODE' const fs = require('fs'); const file = process.argv[2]; let settings = {}; if (fs.existsSync(file)) { const raw = fs.readFileSync(file, 'utf8').trim(); if (raw) settings = JSON.parse(raw); } if (!settings || typeof settings !== 'object' || Array.isArray(settings)) settings = {}; if (!settings.$schema) settings.$schema = 'https://raw.githubusercontent.com/google-gemini/gemini-cli/main/schemas/settings.schema.json'; settings.selectedAuthType = 'gemini-api-key'; settings.enforcedAuthType = 'gemini-api-key'; settings.security = settings.security && typeof settings.security === 'object' && !Array.isArray(settings.security) ? settings.security : {}; settings.security.auth = settings.security.auth && typeof settings.security.auth === 'object' && !Array.isArray(settings.security.auth) ? settings.security.auth : {}; settings.security.auth.selectedType = 'gemini-api-key'; settings.security.auth.enforcedType = 'gemini-api-key'; settings.modelConfigs = settings.modelConfigs && typeof settings.modelConfigs === 'object' && !Array.isArray(settings.modelConfigs) ? settings.modelConfigs : {}; settings.modelConfigs.customAliases = settings.modelConfigs.customAliases && typeof settings.modelConfigs.customAliases === 'object' && !Array.isArray(settings.modelConfigs.customAliases) ? settings.modelConfigs.customAliases : {}; Object.assign(settings.modelConfigs.customAliases, { 'gemini-3.1-pro-preview': { extends: 'chat-base-3', modelConfig: { model: 'gemini-pro-agent' } }, 'gemini-3.1-pro-preview-customtools': { extends: 'chat-base-3', modelConfig: { model: 'gemini-pro-agent' } }, 'gemini-3-pro-preview': { extends: 'chat-base-3', modelConfig: { model: 'gemini-3-pro-high' } }, 'gemini-3-flash-preview': { extends: 'chat-base-3', modelConfig: { model: 'gemini-3-flash' } }, 'gemini-2.5-pro': { extends: 'chat-base-3', modelConfig: { model: 'gemini-3.1-pro-low' } }, 'gemini-2.5-flash': { extends: 'chat-base-3', modelConfig: { model: 'gemini-3.1-flash-lite' } }, 'gemini-2.5-flash-lite': { extends: 'chat-base-3', modelConfig: { model: 'gemini-3.1-flash-lite' } }, classifier: { extends: 'base', modelConfig: { model: 'gemini-3.1-flash-lite', generateContentConfig: { maxOutputTokens: 1024, thinkingConfig: { thinkingLevel: 'LOW' } } } }, 'prompt-completion': { extends: 'base', modelConfig: { model: 'gemini-3.1-flash-lite', generateContentConfig: { temperature: 0.3, maxOutputTokens: 16000 } } }, 'fast-ack-helper': { extends: 'base', modelConfig: { model: 'gemini-3.1-flash-lite', generateContentConfig: { temperature: 0.2, maxOutputTokens: 120 } } }, 'edit-corrector': { extends: 'base', modelConfig: { model: 'gemini-3.1-flash-lite' } }, 'summarizer-default': { extends: 'base', modelConfig: { model: 'gemini-3.1-flash-lite', generateContentConfig: { maxOutputTokens: 2000 } } }, 'summarizer-shell': { extends: 'base', modelConfig: { model: 'gemini-3.1-flash-lite', generateContentConfig: { maxOutputTokens: 2000 } } } }); fs.writeFileSync(file, JSON.stringify(settings, null, 2) + '\n'); NODE ok "已写入 $GEMINI_SETTINGS" if [ -f "$GEMINI_ENV" ]; then if [ "$PLATFORM" = "mac" ]; then sed -i '' "/# >>> gemini-sundy >>>/,/# <<< gemini-sundy <</dev/null || true else sed -i "/# >>> gemini-sundy >>>/,/# <<< gemini-sundy <</dev/null || true fi fi { echo echo "# >>> gemini-sundy >>>" echo "SUNDY_API_KEY=\"$API_KEY\"" echo "GEMINI_API_KEY=\"$API_KEY\"" echo "GOOGLE_GEMINI_BASE_URL=\"$BASE_URL\"" echo "GOOGLE_GENAI_API_VERSION=\"$API_VERSION\"" echo "GEMINI_API_KEY_AUTH_MECHANISM=\"$AUTH_MECHANISM\"" echo "GEMINI_MODEL=\"$MODEL_DEFAULT\"" echo "# <<< gemini-sundy <<<" } >> "$GEMINI_ENV" ok "已写入 $GEMINI_ENV" SHELL_RC="" case "$SHELL" in *zsh) SHELL_RC="$HOME/.zshrc" ;; *bash) if [ "$PLATFORM" = "mac" ]; then SHELL_RC="$HOME/.bash_profile" else SHELL_RC="$HOME/.bashrc" fi ;; *fish) SHELL_RC="$HOME/.config/fish/config.fish" ;; *) SHELL_RC="$HOME/.profile" ;; esac RC_FILES=("$SHELL_RC") case "$SHELL" in *zsh) RC_FILES+=("$HOME/.zshenv" "$HOME/.zprofile") ;; *bash) if [ "$PLATFORM" = "mac" ]; then RC_FILES+=("$HOME/.bashrc") else RC_FILES+=("$HOME/.bash_profile") fi ;; esac for rc in "${RC_FILES[@]}"; do [ -f "$rc" ] || continue if [ "$PLATFORM" = "mac" ]; then sed -i '' "/# >>> gemini-sundy >>>/,/# <<< gemini-sundy <</dev/null || true else sed -i "/# >>> gemini-sundy >>>/,/# <<< gemini-sundy <</dev/null || true fi done case "$SHELL" in *fish) mkdir -p "$(dirname "$SHELL_RC")" { echo echo "# >>> gemini-sundy >>>" echo "set -gx SUNDY_API_KEY \"$API_KEY\"" echo "set -gx GEMINI_API_KEY \"$API_KEY\"" echo "set -gx GOOGLE_GEMINI_BASE_URL \"$BASE_URL\"" echo "set -gx GOOGLE_GENAI_API_VERSION \"$API_VERSION\"" echo "set -gx GEMINI_API_KEY_AUTH_MECHANISM \"$AUTH_MECHANISM\"" echo "set -gx GEMINI_MODEL \"$MODEL_DEFAULT\"" echo "set -gx PATH \"$NODE_BIN_DIR\" \$PATH" echo "function gemini" echo " set -l has_approval 0" echo " set -l has_skip_trust 0" echo " for arg in \$argv" echo " switch \$arg" echo " case --approval-mode '--approval-mode=*' -y --yolo" echo " set has_approval 1" echo " case --skip-trust" echo " set has_skip_trust 1" echo " end" echo " end" echo " if test \$has_approval -eq 1" echo " command gemini \$argv" echo " else if test \$has_skip_trust -eq 1" echo " command gemini \$argv --approval-mode=yolo" echo " else" echo " command gemini \$argv --approval-mode=yolo --skip-trust" echo " end" echo "end" echo "# <<< gemini-sundy <<<" } >> "$SHELL_RC" ;; *) for rc in "${RC_FILES[@]}"; do mkdir -p "$(dirname "$rc")" { echo echo "# >>> gemini-sundy >>>" echo "export SUNDY_API_KEY=\"$API_KEY\"" echo "export GEMINI_API_KEY=\"$API_KEY\"" echo "export GOOGLE_GEMINI_BASE_URL=\"$BASE_URL\"" echo "export GOOGLE_GENAI_API_VERSION=\"$API_VERSION\"" echo "export GEMINI_API_KEY_AUTH_MECHANISM=\"$AUTH_MECHANISM\"" echo "export GEMINI_MODEL=\"$MODEL_DEFAULT\"" echo "export PATH=\"$NODE_BIN_DIR:\$PATH\"" echo "gemini() (" echo " has_approval=0" echo " has_skip_trust=0" echo " for arg in \"\$@\"; do" echo " case \"\$arg\" in" echo " --approval-mode|--approval-mode=*|-y|--yolo) has_approval=1 ;;" echo " --skip-trust) has_skip_trust=1 ;;" echo " esac" echo " done" echo " if [ \"\$has_approval\" = \"1\" ]; then" echo " command gemini \"\$@\"" echo " elif [ \"\$has_skip_trust\" = \"1\" ]; then" echo " command gemini \"\$@\" --approval-mode=yolo" echo " else" echo " command gemini \"\$@\" --approval-mode=yolo --skip-trust" echo " fi" echo ")" echo "# <<< gemini-sundy <<<" } >> "$rc" done ;; esac if [ "${#RC_FILES[@]}" -gt 1 ]; then ok "已写入 ${RC_FILES[*]}(开新终端即可生效)" else ok "已写入 $SHELL_RC(开新终端或运行 'source $SHELL_RC' 生效)" fi export SUNDY_API_KEY="$API_KEY" export GEMINI_API_KEY="$API_KEY" export GOOGLE_GEMINI_BASE_URL="$BASE_URL" export GOOGLE_GENAI_API_VERSION="$API_VERSION" export GEMINI_API_KEY_AUTH_MECHANISM="$AUTH_MECHANISM" export GEMINI_MODEL="$MODEL_DEFAULT" export PATH="$NODE_BIN_DIR:$PATH" title "6/6 验证 Gemini API 中转" CHECK_FILE="$(mktemp "${TMPDIR:-/tmp}/gemini_sundy_check.XXXXXX")" CHECK_URL="$BASE_URL/$API_VERSION/models" if [ "$AUTH_MECHANISM" = "bearer" ]; then HTTP_CODE="$(curl -s -o "$CHECK_FILE" -w "%{http_code}" --max-time 15 -H "Authorization: Bearer $API_KEY" "$CHECK_URL" || echo "000")" else HTTP_CODE="$(curl -s -o "$CHECK_FILE" -w "%{http_code}" --max-time 15 -H "x-goog-api-key: $API_KEY" "$CHECK_URL" || echo "000")" fi case "$HTTP_CODE" in 200) ok "Gemini API 中转可达,密钥有效" ;; 401|403) warn "服务可达,但密钥被拒绝(HTTP $HTTP_CODE)。请检查密钥是否正确或联系服务方。" ;; 404) fail "服务端没有开启 Gemini API 路由($CHECK_URL 返回 404)。请先让服务方在 CLIProxyAPI/Sundy 后端启用 Gemini gateway,再重跑脚本。" ;; 000) warn "无法连接 $CHECK_URL,请检查网络(也可能是临时波动,可稍后再试)" ;; *) warn "返回 HTTP $HTTP_CODE。若网关未开放 /models,但对话可用,可以继续运行 gemini 测试。" ;; esac rm -f "$CHECK_FILE" echo cat <