#!/usr/bin/env bash # # DawAPI AI Tools Installer # 支持 macOS / Linux / WSL2 # 一键安装 Claude Code、Gemini CLI、Codex CLI # set -e # ============================================================================ # 颜色和样式定义 # ============================================================================ if [[ -t 1 ]] && [[ -z "${NO_COLOR:-}" ]]; then RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m' BLUE='\033[0;34m' MAGENTA='\033[0;35m' CYAN='\033[0;36m' WHITE='\033[1;37m' GRAY='\033[0;90m' BOLD='\033[1m' DIM='\033[2m' RESET='\033[0m' else RED='' GREEN='' YELLOW='' BLUE='' MAGENTA='' CYAN='' WHITE='' GRAY='' BOLD='' DIM='' RESET='' fi # ============================================================================ # 全局变量 # ============================================================================ OS_TYPE="" PKG_MANAGER="" SHELL_RC="" INSTALL_CLAUDE=false INSTALL_GEMINI=false INSTALL_CODEX=false INSTALL_DAWLINE=false API_KEY="" GEMINI_MODEL="" CODEX_MODEL="" CONFIG_DIR="$HOME/.config/ai-tools" ENV_FILE="$CONFIG_DIR/env" # DawLine 相关 DAWLINE_REPO="https://gitee.com/ZwGood/daw-line.git" DAWLINE_DIR="$HOME/.claude/dawline" # 默认模型(来自 DawAPI 文档) DEFAULT_GEMINI_MODEL="gemini-3-pro-preview" DEFAULT_CODEX_MODEL="gpt-5.1-codex" # npm 镜像源(使用国内镜像加速) NPM_REGISTRY="https://registry.npmmirror.com" # ============================================================================ # 工具函数 # ============================================================================ print_banner() { echo "" echo -e "${MAGENTA}${BOLD}" cat << 'EOF' ____ ___ ____ ____ / __ \____ __ __/ | / __ \/ _/ / / / / __ `/ | /| / / /| | / /_/ // / / /_/ / /_/ /| |/ |/ / ___ |/ ____// / /_____/\__,_/ |__/|__/_/ |_/_/ /___/ EOF echo -e "${RESET}" echo -e "${CYAN}${BOLD}AI 开发工具一键安装脚本${RESET}" echo -e "${GRAY}支持 Claude Code / Gemini CLI / Codex CLI${RESET}" echo "" echo -e "${GRAY}────────────────────────────────────────────────────${RESET}" echo "" } log_info() { echo -e "${BLUE}[INFO]${RESET} $1" } log_success() { echo -e "${GREEN}[ OK]${RESET} $1" } log_warn() { echo -e "${YELLOW}[WARN]${RESET} $1" } log_error() { echo -e "${RED}[ ERR]${RESET} $1" } log_step() { echo "" echo -e "${MAGENTA}${BOLD}▶ 步骤 $1: $2${RESET}" echo -e "${GRAY}──────────────────────────────────────${RESET}" } # Spinner 动画 spinner() { local pid=$1 local delay=0.1 local spinstr='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏' while ps -p "$pid" > /dev/null 2>&1; do for i in $(seq 0 9); do printf "\r${CYAN}[%s]${RESET} %s" "${spinstr:$i:1}" "$2" sleep $delay done done printf "\r" } # 带 spinner 执行命令 run_with_spinner() { local msg="$1" shift "$@" > /dev/null 2>&1 & local pid=$! spinner $pid "$msg" wait $pid local status=$? if [[ $status -eq 0 ]]; then echo -e "${GREEN}[ OK]${RESET} $msg" else echo -e "${RED}[ ERR]${RESET} $msg" return $status fi } # 用户确认 confirm() { local prompt="$1" local default="${2:-y}" local yn if [[ "$default" == "y" ]]; then prompt="$prompt [Y/n]: " else prompt="$prompt [y/N]: " fi echo -ne "${CYAN}[????]${RESET} $prompt" read -r yn < /dev/tty yn="${yn:-$default}" case "$yn" in [Yy]*) return 0 ;; *) return 1 ;; esac } # 读取用户输入 prompt_input() { local prompt="$1" local default="$2" local result if [[ -n "$default" ]]; then echo -ne "${CYAN}[????]${RESET} $prompt [${default}]: " >&2 read -r result < /dev/tty echo "${result:-$default}" else echo -ne "${CYAN}[????]${RESET} $prompt: " >&2 read -r result < /dev/tty echo "$result" fi } # 读取密码输入(隐藏) prompt_secret() { local prompt="$1" local result echo -ne "${CYAN}[🔑]${RESET} $prompt: " >&2 read -r -s result < /dev/tty echo "" >&2 echo "$result" } # 选择菜单 select_option() { local prompt="$1" shift local options=("$@") local selected=0 local key echo -e "${CYAN}[????]${RESET} $prompt" echo "" for i in "${!options[@]}"; do if [[ $i -eq $selected ]]; then echo -e " ${GREEN}▶ [$((i+1))] ${options[$i]}${RESET}" else echo -e " [$((i+1))] ${options[$i]}" fi done echo "" echo -ne "${GRAY}请输入数字选择 (1-${#options[@]}): ${RESET}" local choice read -r choice < /dev/tty if [[ "$choice" =~ ^[0-9]+$ ]] && [[ "$choice" -ge 1 ]] && [[ "$choice" -le "${#options[@]}" ]]; then echo "$((choice-1))" else echo "0" fi } # ============================================================================ # 系统检测 # ============================================================================ detect_os() { log_step "1" "检测系统环境" local os_name os_name="$(uname -s)" case "$os_name" in Darwin) OS_TYPE="macos" log_success "检测到 macOS 系统" # 检测 macOS 版本 local macos_version macos_version=$(sw_vers -productVersion 2>/dev/null || echo "未知") log_info "macOS 版本: $macos_version" # 检测芯片类型 local chip chip=$(uname -m) if [[ "$chip" == "arm64" ]]; then log_info "芯片类型: Apple Silicon (M系列)" else log_info "芯片类型: Intel" fi ;; Linux) # 检测是否是 WSL if grep -qi microsoft /proc/version 2>/dev/null; then OS_TYPE="wsl" log_success "检测到 WSL2 (Windows Subsystem for Linux)" else OS_TYPE="linux" log_success "检测到 Linux 系统" fi # 检测 Linux 发行版 if [[ -f /etc/os-release ]]; then source /etc/os-release log_info "发行版: ${PRETTY_NAME:-$NAME}" fi ;; *) log_error "不支持的操作系统: $os_name" exit 1 ;; esac # 检测 Shell local current_shell current_shell=$(basename "$SHELL") log_info "当前 Shell: $current_shell" case "$current_shell" in zsh) SHELL_RC="$HOME/.zshrc" ;; bash) if [[ "$OS_TYPE" == "macos" ]]; then SHELL_RC="$HOME/.bash_profile" else SHELL_RC="$HOME/.bashrc" fi ;; *) SHELL_RC="$HOME/.profile" ;; esac log_info "Shell 配置文件: $SHELL_RC" } detect_package_manager() { log_info "检测包管理器..." case "$OS_TYPE" in macos) if command -v brew &> /dev/null; then PKG_MANAGER="brew" log_success "检测到 Homebrew" else PKG_MANAGER="none" log_warn "未检测到 Homebrew,稍后将引导安装" fi ;; linux|wsl) if command -v apt-get &> /dev/null; then PKG_MANAGER="apt" log_success "检测到 APT 包管理器" elif command -v dnf &> /dev/null; then PKG_MANAGER="dnf" log_success "检测到 DNF 包管理器" elif command -v yum &> /dev/null; then PKG_MANAGER="yum" log_success "检测到 YUM 包管理器" elif command -v pacman &> /dev/null; then PKG_MANAGER="pacman" log_success "检测到 Pacman 包管理器" elif command -v apk &> /dev/null; then PKG_MANAGER="apk" log_success "检测到 APK 包管理器" elif command -v zypper &> /dev/null; then PKG_MANAGER="zypper" log_success "检测到 Zypper 包管理器" else PKG_MANAGER="none" log_warn "未检测到支持的包管理器" fi ;; esac } # ============================================================================ # Node.js 安装 # ============================================================================ check_nodejs() { log_step "2" "检查 Node.js 环境" # 检查是否有 nvm local has_nvm=false if command -v nvm &> /dev/null || [[ -s "$HOME/.nvm/nvm.sh" ]]; then has_nvm=true # 加载 nvm(如果尚未加载) if [[ -s "$HOME/.nvm/nvm.sh" ]]; then source "$HOME/.nvm/nvm.sh" fi log_success "检测到 nvm" fi if command -v node &> /dev/null; then local node_version node_version=$(node --version) log_success "Node.js 已安装: $node_version" # 检查版本是否至少为 v20 local major_version major_version=$(echo "$node_version" | sed 's/v//' | cut -d. -f1) if [[ "$major_version" -lt 20 ]]; then log_warn "Node.js 版本过低,需要 v20 或更高版本" if [[ "$has_nvm" == true ]]; then log_info "将使用 nvm 安装 Node.js v20..." install_nodejs_with_nvm else if confirm "是否要升级 Node.js?"; then install_nodejs else log_error "Node.js 版本不满足要求,无法继续安装" exit 1 fi fi fi # 检查 npm if command -v npm &> /dev/null; then local npm_version npm_version=$(npm --version) log_success "npm 已安装: v$npm_version" else log_error "npm 未安装" install_nodejs fi else log_warn "Node.js 未安装" if [[ "$has_nvm" == true ]]; then log_info "将使用 nvm 安装 Node.js..." install_nodejs_with_nvm else log_info "正在安装 Node.js..." install_nodejs fi fi } install_nodejs_with_nvm() { log_info "使用 nvm 安装 Node.js v20..." # 确保 nvm 已加载 if [[ -s "$HOME/.nvm/nvm.sh" ]]; then source "$HOME/.nvm/nvm.sh" fi # 安装 Node.js v20 (LTS) nvm install 20 nvm use 20 nvm alias default 20 # 验证安装 if command -v node &> /dev/null; then local node_version node_version=$(node --version) log_success "Node.js 安装成功: $node_version" else log_error "Node.js 安装失败" exit 1 fi } install_nodejs() { log_info "开始安装 Node.js v20..." case "$OS_TYPE" in macos) if [[ "$PKG_MANAGER" == "brew" ]]; then run_with_spinner "通过 Homebrew 安装 Node.js" brew install node@20 # 链接 node@20 brew link --overwrite node@20 2>/dev/null || true else # 先安装 Homebrew log_info "正在安装 Homebrew..." /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" PKG_MANAGER="brew" # 添加 Homebrew 到 PATH(Apple Silicon) if [[ -f "/opt/homebrew/bin/brew" ]]; then eval "$(/opt/homebrew/bin/brew shellenv)" fi run_with_spinner "通过 Homebrew 安装 Node.js" brew install node@20 brew link --overwrite node@20 2>/dev/null || true fi ;; linux|wsl) case "$PKG_MANAGER" in apt) log_info "添加 NodeSource 仓库 (Node.js 20.x)..." curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - > /dev/null 2>&1 run_with_spinner "安装 Node.js" sudo apt-get install -y nodejs ;; dnf) curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash - > /dev/null 2>&1 run_with_spinner "安装 Node.js" sudo dnf install -y nodejs ;; yum) curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash - > /dev/null 2>&1 run_with_spinner "安装 Node.js" sudo yum install -y nodejs ;; pacman) run_with_spinner "安装 Node.js" sudo pacman -S --noconfirm nodejs npm ;; apk) run_with_spinner "安装 Node.js" sudo apk add nodejs npm ;; zypper) run_with_spinner "安装 Node.js" sudo zypper install -y nodejs20 npm20 ;; *) log_error "无法自动安装 Node.js,请手动安装后重试" log_info "访问 https://nodejs.org/ 下载安装 v20 或更高版本" exit 1 ;; esac ;; esac # 验证安装 if command -v node &> /dev/null; then local node_version node_version=$(node --version) local major_version major_version=$(echo "$node_version" | sed 's/v//' | cut -d. -f1) if [[ "$major_version" -ge 20 ]]; then log_success "Node.js 安装成功: $node_version" else log_warn "安装的 Node.js 版本 ($node_version) 低于 v20" log_info "建议手动安装 Node.js v20: https://nodejs.org/" fi else log_error "Node.js 安装失败" exit 1 fi } # ============================================================================ # 工具选择 # ============================================================================ select_tools() { log_step "3" "选择要安装/配置的 AI 工具" # 检测已安装的工具 local claude_installed=false local gemini_installed=false local codex_installed=false local dawline_installed=false if command -v claude &> /dev/null; then claude_installed=true fi if command -v gemini &> /dev/null; then gemini_installed=true fi if command -v codex &> /dev/null; then codex_installed=true fi if [[ -f "$DAWLINE_DIR/dawline" ]]; then dawline_installed=true fi # 检查是否需要修复 DawLine(Claude Code 被卸载但 DawLine 还在) if [[ "$dawline_installed" == true ]] && [[ "$claude_installed" == false ]]; then echo "" echo -e "${YELLOW}${BOLD}⚠️ 检测到异常状态${RESET}" echo -e "${YELLOW}DawLine 已安装,但 Claude Code 未安装${RESET}" echo "" echo -e " ${CYAN}[1]${RESET} 修复 - 重新安装 Claude Code" echo -e " ${CYAN}[2]${RESET} 卸载 - 移除 DawLine" echo -e " ${CYAN}[3]${RESET} 忽略 - 继续正常安装流程" echo "" local repair_choice echo -ne "${CYAN}[????]${RESET} 请选择操作: " read -r repair_choice < /dev/tty case "$repair_choice" in 1) log_info "将重新安装 Claude Code" INSTALL_CLAUDE=true ;; 2) log_info "正在卸载 DawLine..." uninstall_dawline dawline_installed=false log_success "DawLine 已卸载" ;; 3) log_info "继续正常安装流程" ;; esac echo "" fi # 显示安装状态 echo "" echo -e "${WHITE}AI 开发工具状态:${RESET}" echo "" if [[ "$claude_installed" == true ]]; then local ver=$(claude --version 2>/dev/null || echo "") echo -e " ${CYAN}[1]${RESET} Claude Code ${GREEN}✓ 已安装${RESET} ${GRAY}$ver${RESET}" else echo -e " ${CYAN}[1]${RESET} Claude Code ${YELLOW}○ 未安装${RESET}" fi if [[ "$gemini_installed" == true ]]; then local ver=$(gemini --version 2>/dev/null || echo "") echo -e " ${CYAN}[2]${RESET} Gemini CLI ${GREEN}✓ 已安装${RESET} ${GRAY}$ver${RESET}" else echo -e " ${CYAN}[2]${RESET} Gemini CLI ${YELLOW}○ 未安装${RESET}" fi if [[ "$codex_installed" == true ]]; then local ver=$(codex --version 2>/dev/null || echo "") echo -e " ${CYAN}[3]${RESET} Codex CLI ${GREEN}✓ 已安装${RESET} ${GRAY}$ver${RESET}" else echo -e " ${CYAN}[3]${RESET} Codex CLI ${YELLOW}○ 未安装${RESET}" fi # 只有 Claude Code 已安装时才显示 DawLine 选项 if [[ "$claude_installed" == true ]]; then if [[ "$dawline_installed" == true ]]; then echo -e " ${CYAN}[4]${RESET} DawLine 状态栏 ${GREEN}✓ 已安装${RESET} ${GRAY}(Claude Code 增强)${RESET}" echo -e " ${CYAN}[5]${RESET} ${RED}卸载 DawLine${RESET} ${GRAY}(移除状态栏)${RESET}" else echo -e " ${CYAN}[4]${RESET} DawLine 状态栏 ${YELLOW}○ 未安装${RESET} ${GRAY}(Claude Code 增强)${RESET}" fi fi echo "" echo -e "${GRAY}提示: 选择的工具将进行安装(如未安装)和配置(API地址、密钥等)${RESET}" if [[ "$claude_installed" == true ]]; then if [[ "$dawline_installed" == true ]]; then echo -e "${GRAY}输入数字选择,多选用逗号分隔 (如: 1,2,3,4),输入 'all' 全选${RESET}" echo -e "${GRAY}选择 5 可卸载 DawLine${RESET}" else echo -e "${GRAY}输入数字选择,多选用逗号分隔 (如: 1,2,3,4),输入 'all' 全选${RESET}" fi else echo -e "${GRAY}输入数字选择,多选用逗号分隔 (如: 1,2,3),输入 'all' 全选${RESET}" echo -e "${GRAY}注意: 安装 Claude Code 后可选择安装 DawLine 增强状态栏${RESET}" fi echo "" local choice echo -ne "${CYAN}[????]${RESET} 请选择要安装/配置的工具: " read -r choice < /dev/tty if [[ "$choice" == "all" ]] || [[ "$choice" == "ALL" ]]; then INSTALL_CLAUDE=true INSTALL_GEMINI=true INSTALL_CODEX=true # 只有 Claude Code 已安装时,all 才包含 DawLine if [[ "$claude_installed" == true ]]; then INSTALL_DAWLINE=true fi else IFS=',' read -ra selections <<< "$choice" for sel in "${selections[@]}"; do sel=$(echo "$sel" | tr -d ' ') case "$sel" in 1) INSTALL_CLAUDE=true ;; 2) INSTALL_GEMINI=true ;; 3) INSTALL_CODEX=true ;; 4) # 只有 Claude Code 已安装时才允许选择 DawLine if [[ "$claude_installed" == true ]]; then INSTALL_DAWLINE=true else log_warn "DawLine 需要先安装 Claude Code,安装完成后将询问是否安装" fi ;; 5) # 卸载 DawLine if [[ "$claude_installed" == true ]] && [[ "$dawline_installed" == true ]]; then if confirm "确定要卸载 DawLine 吗?"; then uninstall_dawline log_success "DawLine 已卸载" fi fi ;; esac done fi # 显示选择结果 echo "" log_info "已选择的工具:" [[ "$INSTALL_CLAUDE" == true ]] && echo -e " ${GREEN}✓${RESET} Claude Code" [[ "$INSTALL_GEMINI" == true ]] && echo -e " ${GREEN}✓${RESET} Gemini CLI" [[ "$INSTALL_CODEX" == true ]] && echo -e " ${GREEN}✓${RESET} Codex CLI" [[ "$INSTALL_DAWLINE" == true ]] && echo -e " ${GREEN}✓${RESET} DawLine 状态栏" if [[ "$INSTALL_CLAUDE" == false ]] && [[ "$INSTALL_GEMINI" == false ]] && [[ "$INSTALL_CODEX" == false ]] && [[ "$INSTALL_DAWLINE" == false ]]; then log_error "未选择任何工具,退出安装" exit 1 fi } # ============================================================================ # 模型选择 # ============================================================================ select_models() { log_step "4" "配置默认模型" # Gemini 模型配置 if [[ "$INSTALL_GEMINI" == true ]]; then echo "" echo -e "${WHITE}Gemini CLI 默认模型配置:${RESET}" echo -e "${GRAY}默认模型: ${DEFAULT_GEMINI_MODEL}${RESET}" echo -e "${GRAY}提示: 直接按回车使用默认值,或输入自定义模型名${RESET}" echo "" local input echo -ne "${CYAN}[????]${RESET} 输入 Gemini 模型名 [${DEFAULT_GEMINI_MODEL}]: " read -r input < /dev/tty GEMINI_MODEL="${input:-$DEFAULT_GEMINI_MODEL}" log_success "Gemini 模型: $GEMINI_MODEL" fi # Codex 模型配置 if [[ "$INSTALL_CODEX" == true ]]; then echo "" echo -e "${WHITE}Codex CLI 默认模型配置:${RESET}" echo -e "${GRAY}默认模型: ${DEFAULT_CODEX_MODEL}${RESET}" echo -e "${GRAY}提示: 直接按回车使用默认值,或输入自定义模型名${RESET}" echo "" local input echo -ne "${CYAN}[????]${RESET} 输入 Codex 模型名 [${DEFAULT_CODEX_MODEL}]: " read -r input < /dev/tty CODEX_MODEL="${input:-$DEFAULT_CODEX_MODEL}" log_success "Codex 模型: $CODEX_MODEL" fi # Claude Code 不需要配置模型 if [[ "$INSTALL_CLAUDE" == true ]] && [[ "$INSTALL_GEMINI" == false ]] && [[ "$INSTALL_CODEX" == false ]]; then echo "" log_info "Claude Code 无需配置默认模型" fi } # ============================================================================ # API Key 配置 # ============================================================================ configure_api_key() { log_step "5" "配置 API 密钥" # 如果只选了 DawLine,尝试从已有配置读取 API Key if [[ "$INSTALL_DAWLINE" == true ]] && [[ "$INSTALL_CLAUDE" == false ]] && [[ "$INSTALL_GEMINI" == false ]] && [[ "$INSTALL_CODEX" == false ]]; then log_info "检测已有的 API Key 配置..." # 尝试从 Claude Code config.json 读取 local claude_config="$HOME/.claude/config.json" if [[ -f "$claude_config" ]]; then local existing_key existing_key=$(grep -o '"primaryApiKey"[[:space:]]*:[[:space:]]*"[^"]*"' "$claude_config" 2>/dev/null | sed 's/.*"primaryApiKey"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/') if [[ -n "$existing_key" ]] && [[ "$existing_key" =~ ^sk- ]]; then API_KEY="$existing_key" log_success "已从 Claude Code 配置读取 API Key" return fi fi # 尝试从环境变量读取 if [[ -n "$ANTHROPIC_AUTH_TOKEN" ]] && [[ "$ANTHROPIC_AUTH_TOKEN" =~ ^sk- ]]; then API_KEY="$ANTHROPIC_AUTH_TOKEN" log_success "已从环境变量读取 API Key" return fi # 尝试从 DawLine 已有配置读取 local dawapi_key_file="$DAWLINE_DIR/dawapi_key.txt" if [[ -f "$dawapi_key_file" ]]; then local existing_key existing_key=$(cat "$dawapi_key_file" 2>/dev/null) if [[ -n "$existing_key" ]] && [[ "$existing_key" =~ ^sk- ]]; then API_KEY="$existing_key" log_success "已从 DawLine 配置读取 API Key" return fi fi log_warn "未找到已有的 API Key,需要重新输入" fi echo "" echo -e "${WHITE}DawAPI 统一使用一个 API Key 访问所有模型${RESET}" echo -e "${GRAY}请在 DawAPI 控制台获取你的 API Key${RESET}" echo "" while true; do API_KEY=$(prompt_input "请输入你的 API Key") # 去除前后空格 API_KEY=$(echo "$API_KEY" | tr -d '[:space:]') if [[ -z "$API_KEY" ]]; then log_error "API Key 不能为空" continue fi break done log_success "API Key 已配置" } # ============================================================================ # 安装工具 # ============================================================================ install_tools() { log_step "6" "安装 AI 工具" log_info "使用 npm 镜像源: $NPM_REGISTRY" echo "" if [[ "$INSTALL_CLAUDE" == true ]]; then if command -v claude &> /dev/null; then local version version=$(claude --version 2>/dev/null || echo "已安装") log_success "Claude Code 已安装: $version" else run_with_spinner "安装 Claude Code" npm install -g @anthropic-ai/claude-code --registry="$NPM_REGISTRY" if command -v claude &> /dev/null; then log_success "Claude Code 安装成功" else log_error "Claude Code 安装失败" fi fi fi if [[ "$INSTALL_GEMINI" == true ]]; then if command -v gemini &> /dev/null; then local version version=$(gemini --version 2>/dev/null || echo "已安装") log_success "Gemini CLI 已安装: $version" else run_with_spinner "安装 Gemini CLI" npm install -g @google/gemini-cli --registry="$NPM_REGISTRY" if command -v gemini &> /dev/null; then log_success "Gemini CLI 安装成功" else log_error "Gemini CLI 安装失败" fi fi fi if [[ "$INSTALL_CODEX" == true ]]; then if command -v codex &> /dev/null; then local version version=$(codex --version 2>/dev/null || echo "已安装") log_success "Codex CLI 已安装: $version" else run_with_spinner "安装 Codex CLI" npm install -g @openai/codex@latest --registry="$NPM_REGISTRY" if command -v codex &> /dev/null; then log_success "Codex CLI 安装成功" else log_error "Codex CLI 安装失败" fi fi fi } # ============================================================================ # DawLine 状态栏安装 # ============================================================================ uninstall_dawline() { log_info "正在卸载 DawLine..." # 删除 DawLine 目录 if [[ -d "$DAWLINE_DIR" ]]; then rm -rf "$DAWLINE_DIR" fi # 从 settings.json 移除 statusLine 配置 local settings_file="$HOME/.claude/settings.json" if [[ -f "$settings_file" ]]; then # 简单处理:如果文件只包含 statusLine 配置,删除整个文件 # 否则保留文件但提示用户手动移除 if grep -q '"statusLine"' "$settings_file"; then log_info "请手动从 $settings_file 中移除 statusLine 配置" fi fi log_success "DawLine 已卸载" } check_rust() { if command -v cargo &> /dev/null; then local rust_version rust_version=$(rustc --version 2>/dev/null || echo "已安装") log_success "Rust 已安装: $rust_version" return 0 else return 1 fi } install_rust() { log_info "正在安装 Rust..." echo "" echo -e "${YELLOW}Rust 安装程序将启动,请按照提示操作${RESET}" echo -e "${GRAY}推荐选择默认安装选项 (按 1 然后回车)${RESET}" echo "" # 使用 rustup 安装 curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y # 加载 cargo 环境 if [[ -f "$HOME/.cargo/env" ]]; then source "$HOME/.cargo/env" fi # 验证安装 if command -v cargo &> /dev/null; then log_success "Rust 安装成功" return 0 else log_error "Rust 安装失败,请手动安装后重试" log_info "访问 https://rustup.rs/ 获取安装说明" return 1 fi } ask_dawline() { # 只有安装了 Claude Code 才询问 if [[ "$INSTALL_CLAUDE" != true ]]; then return fi echo "" echo -e "${WHITE}╭─────────────────────────────────────────────────────────╮${RESET}" echo -e "${WHITE}│ DawLine - DawAPI 增强状态栏 │${RESET}" echo -e "${WHITE}╰─────────────────────────────────────────────────────────╯${RESET}" echo "" echo -e "${GRAY}DawLine 是专为 Claude Code 设计的高性能状态栏工具${RESET}" echo -e "${GRAY}功能特性:${RESET}" echo -e " ${CYAN}•${RESET} 实时显示 DawAPI 账户余额" echo -e " ${CYAN}•${RESET} 显示模型、目录、Git 分支等信息" echo -e " ${CYAN}•${RESET} 多种主题可选,支持自定义" echo -e " ${CYAN}•${RESET} Rust 编写,启动速度极快" echo "" # 检查是否已安装 if [[ -f "$DAWLINE_DIR/dawline" ]]; then echo -e "${GREEN}✓${RESET} DawLine 已安装" if confirm "是否重新安装/更新 DawLine?"; then INSTALL_DAWLINE=true fi else if confirm "是否安装 DawLine 状态栏?"; then INSTALL_DAWLINE=true fi fi } install_dawline() { if [[ "$INSTALL_DAWLINE" != true ]]; then return fi log_step "6.5" "安装 DawLine 状态栏" # 检查 Rust 环境 log_info "检查 Rust 环境..." if ! check_rust; then log_warn "Rust 未安装,DawLine 需要 Rust 来编译" if confirm "是否安装 Rust?"; then if ! install_rust; then log_error "无法安装 Rust,跳过 DawLine 安装" INSTALL_DAWLINE=false return fi else log_warn "跳过 DawLine 安装" INSTALL_DAWLINE=false return fi fi # 确保设置了默认 toolchain log_info "检查 Rust toolchain..." if ! rustup default 2>/dev/null | grep -q "stable\|nightly\|beta"; then log_info "设置默认 Rust toolchain (首次需要下载,可能需要几分钟)..." rustup default stable log_success "Rust toolchain 已设置" fi # 检查 git if ! command -v git &> /dev/null; then log_error "Git 未安装,无法克隆 DawLine 仓库" INSTALL_DAWLINE=false return fi # 创建临时目录 local temp_dir temp_dir=$(mktemp -d) log_info "克隆 DawLine 仓库..." if ! git clone --depth 1 "$DAWLINE_REPO" "$temp_dir/daw-line" 2>/dev/null; then log_error "克隆仓库失败" rm -rf "$temp_dir" INSTALL_DAWLINE=false return fi log_info "编译 DawLine (这可能需要几分钟)..." cd "$temp_dir/daw-line" # 编译 if cargo build --release 2>/dev/null; then log_success "DawLine 编译成功" else log_error "DawLine 编译失败" cd - > /dev/null rm -rf "$temp_dir" INSTALL_DAWLINE=false return fi # 安装 log_info "安装 DawLine..." mkdir -p "$DAWLINE_DIR" cp target/release/dawline "$DAWLINE_DIR/" chmod +x "$DAWLINE_DIR/dawline" # 初始化配置 log_info "初始化 DawLine 配置..." "$DAWLINE_DIR/dawline" --init 2>/dev/null || true # 复制主题 if [[ -f "$DAWLINE_DIR/themes/cometix.toml" ]]; then cp "$DAWLINE_DIR/themes/cometix.toml" "$DAWLINE_DIR/config.toml" fi # 清理 cd - > /dev/null rm -rf "$temp_dir" log_success "DawLine 安装成功" # 检测 Nerd Font check_nerd_font } check_nerd_font() { log_info "检测 Nerd Font 字体..." local has_nerd_font=false # 使用 fc-list 检测(macOS/Linux 通用) if command -v fc-list &> /dev/null; then if fc-list 2>/dev/null | grep -qi "nerd"; then has_nerd_font=true fi fi # macOS 额外检查系统字体目录 if [[ "$OS_TYPE" == "macos" ]] && [[ "$has_nerd_font" == false ]]; then if ls ~/Library/Fonts/*[Nn]erd* 2>/dev/null | grep -q . || \ ls /Library/Fonts/*[Nn]erd* 2>/dev/null | grep -q .; then has_nerd_font=true fi fi if [[ "$has_nerd_font" == true ]]; then log_success "检测到 Nerd Font 字体" else echo "" echo -e "${YELLOW}${BOLD}⚠️ 未检测到 Nerd Font 字体${RESET}" echo -e "${YELLOW}DawLine 需要 Nerd Font 才能正确显示图标${RESET}" echo "" echo -e "${WHITE}安装方式:${RESET}" echo -e " ${CYAN}1.${RESET} 在 Claude Code 中输入: ${CYAN}帮我安装 JetBrainsMono Nerd Font 字体${RESET}" echo -e " ${CYAN}2.${RESET} 手动下载: ${CYAN}https://www.nerdfonts.com/font-downloads${RESET}" echo "" echo -e "${GRAY}推荐字体: JetBrainsMono Nerd Font、FiraCode Nerd Font${RESET}" echo "" fi } configure_dawline() { if [[ "$INSTALL_DAWLINE" != true ]] && [[ ! -f "$DAWLINE_DIR/dawline" ]]; then return fi # 如果 DawLine 已安装,配置 API Key if [[ -f "$DAWLINE_DIR/dawline" ]]; then log_info "配置 DawLine API Key..." "$DAWLINE_DIR/dawline" --set-dawapi-key "$API_KEY" 2>/dev/null || true log_success "DawLine API Key 已配置" # 更新 Claude Code 的 settings.json 以启用状态栏 local settings_file="$HOME/.claude/settings.json" if [[ -f "$settings_file" ]]; then # 检查是否已有 statusLine 配置 if grep -q '"statusLine"' "$settings_file"; then log_info "settings.json 已有状态栏配置" else # 尝试在 JSON 末尾添加 statusLine 配置 log_info "更新 Claude Code settings.json..." # 备份原文件 cp "$settings_file" "${settings_file}.bak" # 使用 sed 在最后一个 } 前插入配置 if command -v python3 &> /dev/null; then python3 -c " import json with open('$settings_file', 'r') as f: data = json.load(f) data['statusLine'] = { 'type': 'command', 'command': '$DAWLINE_DIR/dawline' } with open('$settings_file', 'w') as f: json.dump(data, f, indent=2) " 2>/dev/null && log_success "settings.json 已更新" || log_warn "无法自动更新 settings.json,请手动添加 statusLine 配置" else log_warn "无法自动更新 settings.json,请手动添加 statusLine 配置" fi fi else # 创建新的 settings.json log_info "创建 Claude Code settings.json..." cat > "$settings_file" << EOF { "statusLine": { "type": "command", "command": "$DAWLINE_DIR/dawline" } } EOF log_success "settings.json 已创建" fi fi } # ============================================================================ # 配置写入 # ============================================================================ write_config() { log_step "7" "写入配置文件" # 创建配置目录 mkdir -p "$CONFIG_DIR" mkdir -p "$HOME/.claude" mkdir -p "$HOME/.codex" # 写入统一环境变量文件 log_info "写入环境变量配置..." cat > "$ENV_FILE" << EOF # >>> DawAPI AI Tools Configuration >>> # 由 DawAPI 安装脚本自动生成 # 生成时间: $(date '+%Y-%m-%d %H:%M:%S') EOF if [[ "$INSTALL_CLAUDE" == true ]]; then cat >> "$ENV_FILE" << EOF # Claude Code 配置 export ANTHROPIC_BASE_URL="https://dawclaudecode.com" export ANTHROPIC_AUTH_TOKEN="$API_KEY" EOF fi if [[ "$INSTALL_GEMINI" == true ]]; then cat >> "$ENV_FILE" << EOF # Gemini CLI 配置 export GOOGLE_GEMINI_BASE_URL="https://dawclaudecode.com" export GEMINI_API_KEY="$API_KEY" export GEMINI_MODEL="$GEMINI_MODEL" EOF fi if [[ "$INSTALL_CODEX" == true ]]; then cat >> "$ENV_FILE" << EOF # Codex CLI 配置 (环境变量部分) export OPENAI_API_KEY="$API_KEY" export OPENAI_BASE_URL="https://dawclaudecode.com/v1" EOF fi cat >> "$ENV_FILE" << EOF # <<< DawAPI AI Tools Configuration <<< EOF log_success "环境变量文件已写入: $ENV_FILE" # 更新 Shell RC 文件 log_info "更新 Shell 配置..." local source_line="source \"$ENV_FILE\"" local marker_start="# >>> DawAPI AI Tools >>>" local marker_end="# <<< DawAPI AI Tools <<<" # 检查是否已存在配置 if [[ -f "$SHELL_RC" ]] && grep -q "$marker_start" "$SHELL_RC"; then # 替换现有配置 sed -i.bak "/$marker_start/,/$marker_end/d" "$SHELL_RC" rm -f "${SHELL_RC}.bak" fi # 添加新配置 cat >> "$SHELL_RC" << EOF $marker_start if [[ -f "$ENV_FILE" ]]; then source "$ENV_FILE" fi $marker_end EOF log_success "Shell 配置已更新: $SHELL_RC" # 写入 Claude Code 配置 if [[ "$INSTALL_CLAUDE" == true ]]; then log_info "写入 Claude Code 配置..." cat > "$HOME/.claude/config.json" << EOF { "primaryApiKey": "$API_KEY", "apiBaseUrl": "https://dawclaudecode.com" } EOF chmod 600 "$HOME/.claude/config.json" log_success "Claude Code 配置已写入" fi # 写入 Codex 配置 if [[ "$INSTALL_CODEX" == true ]]; then log_info "写入 Codex CLI 配置..." cat > "$HOME/.codex/config.toml" << EOF # DawAPI Codex 配置 # 生成时间: $(date '+%Y-%m-%d %H:%M:%S') model_provider = "daw" model = "$CODEX_MODEL" model_reasoning_effort = "high" model_reasoning_summary = "detailed" approval_policy = "suggest" sandbox_mode = "read-only" network_access = true preferred_auth_method = "apikey" [shell_environment_policy] inherit = "all" ignore_default_excludes = false [model_providers.daw] name = "daw" base_url = "https://dawclaudecode.com/v1" wire_api = "responses" requires_openai_auth = true [features] web_search_request = true EOF cat > "$HOME/.codex/auth.json" << EOF { "OPENAI_API_KEY": "$API_KEY" } EOF chmod 600 "$HOME/.codex/config.toml" chmod 600 "$HOME/.codex/auth.json" log_success "Codex CLI 配置已写入" fi } # ============================================================================ # 安装总结 # ============================================================================ print_summary() { log_step "8" "安装完成" echo "" echo -e "${GREEN}${BOLD}╔════════════════════════════════════════════════════════════╗${RESET}" echo -e "${GREEN}${BOLD}║ 🎉 安装成功完成! ║${RESET}" echo -e "${GREEN}${BOLD}╚════════════════════════════════════════════════════════════╝${RESET}" echo "" echo -e "${WHITE}已安装的工具:${RESET}" if [[ "$INSTALL_CLAUDE" == true ]]; then local version version=$(claude --version 2>/dev/null || echo "") echo -e " ${GREEN}✓${RESET} Claude Code ${GRAY}$version${RESET}" fi if [[ "$INSTALL_GEMINI" == true ]]; then local version version=$(gemini --version 2>/dev/null || echo "") echo -e " ${GREEN}✓${RESET} Gemini CLI ${GRAY}$version${RESET}" echo -e " ${GRAY}默认模型: $GEMINI_MODEL${RESET}" fi if [[ "$INSTALL_CODEX" == true ]]; then local version version=$(codex --version 2>/dev/null || echo "") echo -e " ${GREEN}✓${RESET} Codex CLI ${GRAY}$version${RESET}" echo -e " ${GRAY}默认模型: $CODEX_MODEL${RESET}" fi if [[ "$INSTALL_DAWLINE" == true ]] || [[ -f "$DAWLINE_DIR/dawline" ]]; then echo -e " ${GREEN}✓${RESET} DawLine 状态栏" echo -e " ${GRAY}位置: $DAWLINE_DIR/dawline${RESET}" fi echo "" echo -e "${WHITE}配置文件位置:${RESET}" echo -e " ${GRAY}环境变量: $ENV_FILE${RESET}" echo -e " ${GRAY}Shell RC: $SHELL_RC${RESET}" [[ "$INSTALL_CLAUDE" == true ]] && echo -e " ${GRAY}Claude: $HOME/.claude/config.json${RESET}" [[ "$INSTALL_CODEX" == true ]] && echo -e " ${GRAY}Codex: $HOME/.codex/config.toml${RESET}" [[ "$INSTALL_DAWLINE" == true || -f "$DAWLINE_DIR/dawline" ]] && echo -e " ${GRAY}DawLine: $DAWLINE_DIR/config.toml${RESET}" echo "" echo -e "${YELLOW}${BOLD}⚠️ 重要提示:${RESET}" echo -e "${YELLOW}请运行以下命令使配置生效,或重新打开终端:${RESET}" echo "" echo -e " ${CYAN}source $SHELL_RC${RESET}" echo "" echo -e "${WHITE}开始使用:${RESET}" [[ "$INSTALL_CLAUDE" == true ]] && echo -e " ${CYAN}claude${RESET} - 启动 Claude Code" [[ "$INSTALL_GEMINI" == true ]] && echo -e " ${CYAN}gemini${RESET} - 启动 Gemini CLI" [[ "$INSTALL_CODEX" == true ]] && echo -e " ${CYAN}codex${RESET} - 启动 Codex CLI" echo "" echo -e "${GRAY}────────────────────────────────────────────────────${RESET}" echo -e "${GRAY}DawAPI - 让 AI 开发更简单${RESET}" echo -e "${GRAY}文档: https://docx.dawclaudecode.com${RESET}" echo "" } # ============================================================================ # 主函数 # ============================================================================ main() { print_banner # 检查是否是 root 用户 if [[ $EUID -eq 0 ]]; then log_warn "检测到 root 用户,建议使用普通用户运行此脚本" if ! confirm "是否继续?"; then exit 0 fi fi detect_os detect_package_manager check_nodejs select_tools select_models configure_api_key install_tools ask_dawline install_dawline write_config configure_dawline print_summary } # 运行主函数 main "$@"