#!/bin/bash # Codex Configuration Script for XCodeCLI # This script configures Codex to use your XCodeCLI instance # Automatically tests multiple API endpoints and selects the working one set -e # Color functions for output print_info() { echo -e "\033[34m[INFO]\033[0m $1" } print_success() { echo -e "\033[32m[SUCCESS]\033[0m $1" } print_warning() { echo -e "\033[33m[WARNING]\033[0m $1" } print_error() { echo -e "\033[31m[ERROR]\033[0m $1" } # Default values TEST_ONLY=false SHOW_SETTINGS=false DEFAULT_BASE_URL="https://api2.xcodecli.com" TOOL_COMMAND="codex" TOOL_PACKAGE="@openai/codex" TOOL_NAME="Codex" # ========== Shell 环境变量配置 ========== get_shell_rc() { if [ -n "${ZSH_VERSION:-}" ] || [ "${SHELL##*/}" = "zsh" ]; then echo "$HOME/.zshrc" elif [ -n "${BASH_VERSION:-}" ] || [ "${SHELL##*/}" = "bash" ]; then if [ -f "$HOME/.bashrc" ]; then echo "$HOME/.bashrc" else echo "$HOME/.bash_profile" fi else print_error "不支持当前 shell: ${SHELL##*/}" print_error "仅支持 bash 和 zsh" exit 1 fi } write_env_to_shell() { local var_name="$1" local var_value="$2" local rc_file rc_file=$(get_shell_rc) mkdir -p "$(dirname "$rc_file")" touch "$rc_file" if [ -s "$rc_file" ] && [ "$(tail -c1 "$rc_file" | wc -l)" -eq 0 ]; then echo "" >>"$rc_file" fi local export_line="export $var_name='$var_value'" local tmp_file tmp_file=$(mktemp) if [ -s "$rc_file" ]; then grep -v "^export $var_name=" "$rc_file" >"$tmp_file" 2>/dev/null || true fi echo "$export_line" >>"$tmp_file" cat "$tmp_file" >"$rc_file" rm -f "$tmp_file" export "$var_name=$var_value" } # ========== Node.js 环境检测 ========== get_node_version() { if command -v node >/dev/null 2>&1; then node --version 2>/dev/null | sed 's/v//' fi } get_node_major_version() { local version version=$(get_node_version) if [ -n "$version" ]; then echo "$version" | cut -d. -f1 fi } install_fnm() { echo "" print_info "正在安装 fnm (Fast Node Manager)..." if curl -fsSL https://fnm.vercel.app/install | bash; then export PATH="$HOME/.local/share/fnm:$PATH" if [ -f "$HOME/.local/share/fnm/fnm" ]; then eval "$(~/.local/share/fnm/fnm env)" fi if command -v fnm >/dev/null 2>&1; then print_success "fnm 安装成功!" return 0 else print_warning "fnm 可能已安装,但需要重新打开终端才能生效" print_info "请重新打开终端后再运行此脚本" return 1 fi else print_error "fnm 安装失败" return 1 fi } install_node_with_fnm() { print_info "使用 fnm 安装 Node.js 24.x..." if fnm install 24 && fnm use 24 && fnm default 24; then eval "$(fnm env)" if command -v node >/dev/null 2>&1; then local version version=$(get_node_version) print_success "Node.js v$version 安装成功!" return 0 else print_warning "Node.js 可能已安装,但需要重新打开终端才能生效" return 1 fi else print_error "Node.js 安装失败" return 1 fi } ensure_node_environment() { local version major version=$(get_node_version) if [ -n "$version" ]; then major=$(get_node_major_version) print_info "检测到 Node.js v$version" if [ "$major" -lt 20 ]; then print_warning "Node.js 版本过低 (需要 >= 20.x)" read -p "是否使用 fnm 安装 Node.js 24.x? (Y/n): " -r if [[ $REPLY =~ ^[Nn]$ ]]; then print_error "Node.js 版本不满足要求,请手动升级后重试" return 1 fi if ! command -v fnm >/dev/null 2>&1; then install_fnm || return 1 fi install_node_with_fnm || return 1 fi return 0 fi print_warning "未检测到 Node.js" print_info "将使用 fnm 安装 Node.js 24.x" read -p "是否继续? (Y/n): " -r if [[ $REPLY =~ ^[Nn]$ ]]; then return 1 fi if ! command -v fnm >/dev/null 2>&1; then install_fnm || return 1 fi install_node_with_fnm || return 1 } install_tool() { ensure_node_environment || return 1 print_info "使用 npm 安装 $TOOL_NAME..." echo " 执行: npm install -g $TOOL_PACKAGE" if npm install -g "$TOOL_PACKAGE"; then if command -v "$TOOL_COMMAND" >/dev/null 2>&1; then print_success "$TOOL_NAME 安装成功!" return 0 else print_warning "$TOOL_NAME 可能已安装,但需要重新打开终端才能生效" read -p "是否继续进行配置? (Y/n): " -r if [[ $REPLY =~ ^[Nn]$ ]]; then return 1 fi return 0 fi else print_error "安装失败" return 1 fi } # Function to validate API key format validate_api_key() { local api_key="$1" if [[ ! "$api_key" =~ ^[A-Za-z0-9_-]+$ ]]; then print_error "Invalid API key format. API key should contain only alphanumeric characters, hyphens, and underscores." return 1 fi return 0 } # Function to extract model count from API response (supports both data[] and models[]) get_model_count() { local response_file="$1" local count="0" if command -v jq >/dev/null 2>&1; then count=$(jq -r ' if (.data | type) == "array" and (.data | length) > 0 then (.data | length) elif (.models | type) == "array" and (.models | length) > 0 then (.models | length) else 0 end ' "$response_file" 2>/dev/null || echo "0") else if grep -qE '"(data|models)"[[:space:]]*:[[:space:]]*\[' "$response_file" 2>/dev/null; then count=$(grep -oE '"id"[[:space:]]*:' "$response_file" | wc -l | tr -d ' ') fi fi [ -z "$count" ] && count="0" echo "$count" } # Check for environment variable API_KEY if [ -n "$API_KEY" ]; then API_KEY_FROM_ENV="$API_KEY" else API_KEY_FROM_ENV="" fi API_KEY="" # Function to show help show_help() { cat <&2 for base_url in "${test_urls[@]}"; do print_info "Testing $base_url..." >&2 local test_endpoint="$base_url/v1/models" local response response=$(curl -s -w "%{http_code}" -o /tmp/codex_test_response \ -X GET "$test_endpoint" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $api_key" \ 2>/dev/null || echo "000") if [ "$response" = "200" ]; then local model_count model_count=$(get_model_count /tmp/codex_test_response) if [ "$model_count" -gt "0" ]; then print_success "API connection successful! Found $model_count models at $base_url" >&2 rm -f /tmp/codex_test_response echo "$base_url" return 0 else print_warning "API responded but no models found at $base_url" >&2 fi elif [ "$response" = "401" ]; then print_warning "API key authentication failed for $base_url" >&2 elif [ "$response" = "000" ]; then print_warning "Cannot connect to $base_url" >&2 else print_warning "API test failed for $base_url with HTTP status: $response" >&2 fi rm -f /tmp/codex_test_response done print_error "All API connections failed. Please check your API key and internet connection." >&2 return 1 } # Function to create Codex configuration create_codex_config() { local base_url="$1" local api_key="$2" # Create config directory if it doesn't exist mkdir -p "$HOME/.codex" # Create config.toml cat >"$HOME/.codex/config.toml" <"$HOME/.codex/auth.json" </dev/null 2>&1; then echo "" print_warning "$TOOL_NAME 未安装" read -p "是否立即安装? (Y/n): " -r if [[ $REPLY =~ ^[Nn]$ ]]; then print_info "已取消" exit 0 fi install_tool || exit 1 else print_success "$TOOL_NAME 已安装" fi # Interactive mode if no API key provided if [ -z "$API_KEY" ]; then print_info "Interactive setup mode" echo "" # Get API key while [ -z "$API_KEY" ]; do read -p "Enter your API key: " API_KEY if [ -z "$API_KEY" ]; then print_warning "API key is required" elif ! validate_api_key "$API_KEY"; then API_KEY="" fi done fi # Validate inputs if [ -z "$API_KEY" ]; then print_error "API key is required" print_info "Use --help for usage information" exit 1 fi # Validate API key format if ! validate_api_key "$API_KEY"; then exit 1 fi # Mask API key for display if [ ${#API_KEY} -gt 12 ]; then masked_key="${API_KEY:0:8}...${API_KEY: -4}" else masked_key="${API_KEY:0:4}..." fi print_info "API Key: $masked_key" echo "" # Test API connection and get working base URL local BASE_URL BASE_URL=$(test_api_connection "$API_KEY") if [ $? -ne 0 ] || [ -z "$BASE_URL" ]; then if [ "$TEST_ONLY" = true ]; then exit 1 fi read -p "All API tests failed. Continue anyway? (y/N): " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then print_info "Setup cancelled" exit 1 fi BASE_URL="$DEFAULT_BASE_URL" print_warning "Using default URL: $BASE_URL" else print_info "Selected working base URL: $BASE_URL" fi # Exit if test only if [ "$TEST_ONLY" = true ]; then print_success "API test completed successfully" exit 0 fi # Backup existing configuration backup_config # Create Codex configuration if ! create_codex_config "$BASE_URL" "$API_KEY"; then print_error "Failed to create Codex configuration" exit 1 fi echo "" print_success "Codex has been configured successfully!" print_info "You can now use Codex with your XCodeCLI API router." print_info "" print_info "Configuration file: $HOME/.codex/config.toml" print_info "Auth file: $HOME/.codex/auth.json" print_info "Environment variable: XCODECLI_OAI_KEY" # Show current settings echo "" show_current_settings echo "" print_warning "Please restart your terminal or run 'source $(get_shell_rc)' for environment variables to take effect." } # Run main function main