#!/bin/bash # AI 工作助手 - 自动化工作流脚本 # 版本: 1.2.1 # 官网: https://agw.bzshan.cn # 脚本版本 VERSION="1.2.1" SCRIPT_URL="work.bzshan.cn" # API服务器地址(部署后需修改为实际地址) API_BASE_URL="https://ai.bzshan.cn/api" # 颜色定义 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' GRAY='\033[0;37m' NC='\033[0m' # No Color # 配置文件路径(存储用户的选择偏好) CONFIG_PATH="$HOME/.ai-work-config" # 加载配置函数 load_config() { local key="$1" local default="$2" if [ -f "$CONFIG_PATH" ]; then local value=$(grep "^$key=" "$CONFIG_PATH" 2>/dev/null | cut -d'=' -f2-) if [ -n "$value" ]; then echo "$value" else echo "$default" fi else echo "$default" fi } # 保存配置函数 save_config() { local key="$1" local value="$2" # 如果配置文件不存在,创建它 if [ ! -f "$CONFIG_PATH" ]; then touch "$CONFIG_PATH" chmod 600 "$CONFIG_PATH" fi # 更新或添加配置项 if grep -q "^$key=" "$CONFIG_PATH" 2>/dev/null; then # 键已存在,更新值 if [[ "$OSTYPE" == "darwin"* ]]; then # macOS sed -i '' "s/^$key=.*/$key=$value/" "$CONFIG_PATH" else # Linux sed -i "s/^$key=.*/$key=$value/" "$CONFIG_PATH" fi else # 键不存在,添加新行 echo "$key=$value" >> "$CONFIG_PATH" fi } # 凭据存储路径 CREDENTIAL_PATH="$HOME/.ai-work-credentials" # ================================ # 命令行参数处理 # ================================ # 处理命令行参数 case "${1:-}" in --version|-v) echo "AI 工作助手 v${VERSION}" echo "官网: https://work.bzshan.cn" echo "脚本: ${SCRIPT_URL}" exit 0 ;; --help|-h) echo "AI 工作助手 - 自动化工作流脚本 v${VERSION}" echo "" echo "用法: $0 [选项]" echo "" echo "选项:" echo " --version, -v 显示版本信息" echo " --help, -h 显示帮助信息" echo " --update 更新脚本到最新版本" echo " --clean 清除配置和凭据" echo "" echo "示例:" echo " $0 # 启动 AI 工作助手" echo " $0 --version # 查看版本" echo " $0 --update # 更新脚本" echo "" echo "官网: https://work.bzshan.cn" exit 0 ;; --update) echo -e "${CYAN}正在检查更新...${NC}" echo "" if command -v curl &> /dev/null; then LATEST_VERSION=$(curl -sL "${SCRIPT_URL%.sh}.version" 2>/dev/null || echo "${VERSION}") if [ "$LATEST_VERSION" != "$VERSION" ]; then echo -e "${GREEN}发现新版本: ${LATEST_VERSION}${NC}" echo -e "${GRAY}当前版本: ${VERSION}${NC}" echo "" read -p "是否更新?(Y/n): " confirm if [ -z "$confirm" ] || [ "$confirm" = "Y" ] || [ "$confirm" = "y" ]; then echo -e "${CYAN}正在下载...${NC}" if curl -sL "$SCRIPT_URL" -o "$0.tmp"; then chmod +x "$0.tmp" mv "$0.tmp" "$0" echo -e "${GREEN}更新成功!${NC}" echo -e "${YELLOW}请重新运行脚本${NC}" exit 0 else echo -e "${RED}下载失败${NC}" exit 1 fi fi else echo -e "${GREEN}已是最新版本${NC}" fi else echo -e "${RED}curl 未安装,无法更新${NC}" exit 1 fi exit 0 ;; --clean) echo -e "${YELLOW}将清除以下文件:${NC}" echo " - 配置文件: $CONFIG_PATH" echo " - 凭据文件: $CREDENTIAL_PATH" echo "" read -p "确认清除?(y/N): " confirm if [ "$confirm" = "y" ] || [ "$confirm" = "Y" ]; then rm -f "$CONFIG_PATH" "$CREDENTIAL_PATH" echo -e "${GREEN}已清除${NC}" else echo -e "${GRAY}已取消${NC}" fi exit 0 ;; esac # ================================ # 环境检测 # ================================ echo -e "${CYAN}正在检测环境...${NC}" # 检测当前用户是否为 root if [ "$EUID" -eq 0 ]; then echo "" echo -e "${RED}================================${NC}" echo -e "${RED} 警告: 检测到 root 用户${NC}" echo -e "${RED}================================${NC}" echo "" echo -e "${YELLOW}不建议使用 root 权限运行本脚本!${NC}" echo "" echo -e "${CYAN}原因:${NC}" echo -e "${YELLOW} • Claude Code 等工具出于安全考虑,不允许 root 用户使用 --dangerously-skip-permissions 参数${NC}" echo -e "${YELLOW} • root 权限运行可能带来安全风险${NC}" echo "" echo -e "${CYAN}解决方案:${NC}" echo -e "${GREEN} 1. 退出 root 会话,使用普通用户重新运行${NC}" echo -e "${GREEN} 2. 如果必须使用 root,请确保以普通用户身份执行 AI 工具${NC}" echo "" read -p "是否继续以 root 身份运行?(y/N): " continue_as_root if [ -z "$continue_as_root" ] || [ "$continue_as_root" != "y" ] && [ "$continue_as_root" != "Y" ]; then echo "" echo -e "${YELLOW}请切换到普通用户后重新运行脚本${NC}" echo -e "${CYAN}例如: su - your_username${NC}" exit 1 fi echo "" echo -e "${YELLOW}⚠️ 警告: 以 root 身份运行,某些功能可能受限${NC}" echo "" fi # 辅助函数:检查 Node.js 版本是否符合要求 test_node_version() { local version="$1" # 解析版本号,格式如 "v20.10.0" if [[ "$version" =~ ^v([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then local major="${BASH_REMATCH[1]}" # 返回 true 如果主版本 >= 20 if [ "$major" -ge 20 ]; then return 0 fi fi return 1 } # 辅助函数:下载文件(带重试机制) download_file() { local url="$1" local output="$2" local max_retries=3 local attempt=1 while [ $attempt -le $max_retries ]; do echo -e "${YELLOW}下载尝试 $attempt/$max_retries...${NC}" if command -v curl &> /dev/null; then if curl -L -o "$output" "$url" 2>/dev/null; then echo -e "${GREEN}下载完成!${NC}" return 0 fi elif command -v wget &> /dev/null; then if wget -q -O "$output" "$url" 2>/dev/null; then echo -e "${GREEN}下载完成!${NC}" return 0 fi else echo -e "${RED}错误: 未检测到 curl 或 wget${NC}" return 1 fi if [ $attempt -lt $max_retries ]; then echo -e "${RED}下载失败,3 秒后重试...${NC}" sleep 3 fi attempt=$((attempt + 1)) done echo -e "${RED}下载失败${NC}" return 1 } # 检测 Node.js 是否已安装并符合版本要求 NODE_INSTALLED=false NODE_VERSION="" NODE_VERSION_OK=false if command -v node &> /dev/null; then NODE_INSTALLED=true NODE_VERSION=$(node --version 2>/dev/null) if test_node_version "$NODE_VERSION"; then echo -e " ${GREEN}检测到 Node.js $NODE_VERSION (符合要求)${NC}" NODE_VERSION_OK=true else echo -e " ${YELLOW}检测到 Node.js $NODE_VERSION (版本低于 v20,需要升级)${NC}" fi fi # 如果 Node.js 未安装或版本不符合要求 if [ "$NODE_INSTALLED" = false ] || [ "$NODE_VERSION_OK" = false ]; then echo "" echo -e "${YELLOW}================================${NC}" if [ "$NODE_INSTALLED" = false ]; then echo -e "${YELLOW} 未检测到 Node.js v20+${NC}" else echo -e "${YELLOW} Node.js 版本过低,需要 v20+${NC}" fi echo -e "${YELLOW}================================${NC}" echo "" # 检测 nvm 是否安装 NVM_INSTALLED=false if [ -s "$HOME/.nvm/nvm.sh" ]; then # 加载 nvm . "$HOME/.nvm/nvm.sh" NVM_INSTALLED=true elif command -v nvm &> /dev/null; then NVM_INSTALLED=true fi if [ "$NVM_INSTALLED" = false ]; then echo -e "${CYAN}nvm (Node Version Manager) 是 Node.js 版本管理工具,便于版本管理。${NC}" echo -e "${CYAN}建议通过 nvm 安装 Node.js,也支持直接安装。${NC}" echo "" # 加载上次的选择 LAST_INSTALL_CHOICE=$(load_config "nodejs_install_choice" "1") # 让用户选择安装方式 echo -e "${GREEN}请选择安装方式:${NC}" echo "1) 通过 nvm 安装 (推荐,支持多版本管理)" echo "2) 直接安装 Node.js (更快速)" echo "" echo -e "${GRAY}上次选择: 选项 $LAST_INSTALL_CHOICE${NC}" read -p "请输入选项 (1-2,直接回车使用上次选择): " install_choice if [ -z "$install_choice" ]; then install_choice="$LAST_INSTALL_CHOICE" fi # 保存选择 save_config "nodejs_install_choice" "$install_choice" case $install_choice in 1) # 通过 nvm 安装 echo "" echo -e "${CYAN}将自动下载并安装 nvm...${NC}" echo "" # 检测是否有 curl 或 wget if command -v curl &> /dev/null; then DOWNLOAD_CMD="curl" elif command -v wget &> /dev/null; then DOWNLOAD_CMD="wget" else echo -e "${RED}错误: 未检测到 curl 或 wget${NC}" echo -e "${YELLOW}请先安装 curl 或 wget,然后重新运行本脚本${NC}" echo "" read -p "按回车键退出..." -r exit 1 fi # 下载并安装 nvm NVM_VERSION="v0.40.1" OFFICIAL_URL="https://raw.githubusercontent.com/nvm-sh/nvm/$NVM_VERSION/install.sh" MIRROR_URL="https://ghproxy.com/raw.githubusercontent.com/nvm-sh/nvm/$NVM_VERSION/install.sh" echo -e "${CYAN}正在下载并安装 nvm $NVM_VERSION...${NC}" echo -e "${GRAY}官方源: $OFFICIAL_URL${NC}" echo -e "${GRAY}镜像源: $MIRROR_URL${NC}" echo "" # 先尝试官方源 if [ "$DOWNLOAD_CMD" = "curl" ]; then if curl -o- "$OFFICIAL_URL" | bash 2>/dev/null; then echo "" echo -e "${GREEN}nvm 安装成功!${NC}" echo "" else echo -e "${YELLOW}官方源下载失败,尝试使用国内镜像源...${NC}" if curl -o- "$MIRROR_URL" | bash 2>/dev/null; then echo "" echo -e "${GREEN}nvm 安装成功!${NC}" echo "" else echo "" echo -e "${RED}nvm 自动安装失败${NC}" echo -e "${YELLOW}请手动执行以下命令安装:${NC}" echo -e "${GREEN} curl -o- $OFFICIAL_URL | bash${NC}" echo "" read -p "按回车键退出..." -r exit 1 fi fi else if wget -qO- "$OFFICIAL_URL" | bash 2>/dev/null; then echo "" echo -e "${GREEN}nvm 安装成功!${NC}" echo "" else echo -e "${YELLOW}官方源下载失败,尝试使用国内镜像源...${NC}" if wget -qO- "$MIRROR_URL" | bash 2>/dev/null; then echo "" echo -e "${GREEN}nvm 安装成功!${NC}" echo "" else echo "" echo -e "${RED}nvm 自动安装失败${NC}" echo -e "${YELLOW}请手动执行以下命令安装:${NC}" echo -e "${GREEN} wget -qO- $OFFICIAL_URL | bash${NC}" echo "" read -p "按回车键退出..." -r exit 1 fi fi fi # 加载 nvm echo -e "${CYAN}正在加载 nvm...${NC}" export NVM_DIR="$HOME/.nvm" if [ -s "$NVM_DIR/nvm.sh" ]; then . "$NVM_DIR/nvm.sh" NVM_INSTALLED=true echo -e "${GREEN}nvm 加载成功!${NC}" echo "" else echo -e "${RED}加载 nvm 失败${NC}" echo -e "${YELLOW}请重新打开终端,然后再次运行本脚本${NC}" echo "" read -p "按回车键退出..." -r exit 1 fi ;; 2) # 直接安装 Node.js echo "" echo -e "${CYAN}将直接下载并安装 Node.js v20...${NC}" echo "" # 检测系统架构 ARCH=$(uname -m) if [[ "$ARCH" == "x86_64" ]] || [[ "$ARCH" == "amd64" ]]; then ARCH_SUFFIX="x64" elif [[ "$ARCH" == "aarch64" ]] || [[ "$ARCH" == "arm64" ]]; then ARCH_SUFFIX="arm64" else ARCH_SUFFIX="x64" fi # Node.js v20 的下载链接 NODE_VERSION="20.18.0" OFFICIAL_URL="https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-$ARCH_SUFFIX.tar.xz" MIRROR_URL="https://npmmirror.com/mirrors/node/v$NODE_VERSION/node-v$NODE_VERSION-linux-$ARCH_SUFFIX.tar.xz" TEMP_DIR=$(mktemp -d) ARCHIVE_PATH="$TEMP_DIR/node.tar.xz" echo -e "${CYAN}正在下载 Node.js v$NODE_VERSION ($ARCH_SUFFIX)...${NC}" echo -e "${GRAY}官方源: $OFFICIAL_URL${NC}" echo -e "${GRAY}镜像源: $MIRROR_URL${NC}" echo "" # 先尝试官方源 if download_file "$OFFICIAL_URL" "$ARCHIVE_PATH"; then : else echo -e "${YELLOW}官方源下载失败,尝试使用国内镜像源...${NC}" if download_file "$MIRROR_URL" "$ARCHIVE_PATH"; then : else echo -e "${RED}所有下载源均失败${NC}" echo "" echo -e "${YELLOW}请手动下载并安装:${NC}" echo -e "${CYAN}1. 访问: https://nodejs.org/dist/v$NODE_VERSION/${NC}" echo -e "${CYAN}2. 下载 node-v$NODE_VERSION-linux-$ARCH_SUFFIX.tar.xz${NC}" echo -e "${CYAN}3. 解压并安装到 /usr/local 或 ~/nodejs${NC}" echo "" read -p "按回车键退出..." -r exit 1 fi fi echo "" echo -e "${CYAN}正在解压并安装 Node.js v$NODE_VERSION...${NC}" # 创建安装目录 INSTALL_DIR="$HOME/nodejs" mkdir -p "$INSTALL_DIR" # 解压 if tar -xf "$ARCHIVE_PATH" -C "$TEMP_DIR" 2>/dev/null; then # 移动文件 local node_dir=$(find "$TEMP_DIR" -maxdepth 1 -name "node-v$NODE_VERSION-linux-*" -type d | head -n1) if [ -n "$node_dir" ]; then cp -r "$node_dir"/* "$INSTALL_DIR/" echo -e "${GREEN}Node.js 安装成功!${NC}" echo "" else echo -e "${RED}解压失败${NC}" read -p "按回车键退出..." -r exit 1 fi else echo -e "${RED}解压失败${NC}" read -p "按回车键退出..." -r exit 1 fi # 清理临时文件 rm -rf "$TEMP_DIR" # 添加到 PATH echo -e "${CYAN}正在配置环境变量...${NC}" export PATH="$INSTALL_DIR/bin:$PATH" # 添加到 shell 配置 SHELL_CONFIG="" if [ -n "$BASH_VERSION" ]; then SHELL_CONFIG="$HOME/.bashrc" elif [ -n "$ZSH_VERSION" ]; then SHELL_CONFIG="$HOME/.zshrc" fi if [ -n "$SHELL_CONFIG" ]; then if ! grep -q "export PATH=\"$INSTALL_DIR/bin:\$PATH\"" "$SHELL_CONFIG" 2>/dev/null; then echo "" >> "$SHELL_CONFIG" echo "# Node.js 安装路径" >> "$SHELL_CONFIG" echo "export PATH=\"$INSTALL_DIR/bin:\$PATH\"" >> "$SHELL_CONFIG" echo -e "${GREEN}已添加到 $SHELL_CONFIG${NC}" fi fi echo "" ;; *) echo -e "${RED}无效选项,默认通过 nvm 安装${NC}" install_choice="1" ;; esac else echo -e " ${GREEN}检测到 nvm${NC}" fi # 使用 nvm 安装 Node.js 20(如果之前选择了 nvm 路径) if [ "$NVM_INSTALLED" = true ]; then echo "" echo -e "${CYAN}正在使用 nvm 安装 Node.js 20...${NC}" echo "" # 加载 nvm(如果还未加载) export NVM_DIR="$HOME/.nvm" if [ -s "$NVM_DIR/nvm.sh" ]; then . "$NVM_DIR/nvm.sh" fi # 安装 Node.js 20 echo -e "${GRAY}执行: nvm install 20${NC}" if nvm install 20; then echo "" echo -e "${GREEN}Node.js 20 下载安装成功!${NC}" echo "" else echo "" echo -e "${RED}Node.js 20 安装失败${NC}" echo -e "${YELLOW}请手动执行以下命令:${NC}" echo -e "${GREEN} nvm install 20${NC}" echo -e "${GREEN} nvm use 20${NC}" echo "" echo -e "${YELLOW}或者选择直接安装 Node.js${NC}" echo "" read -p "按回车键退出..." -r exit 1 fi # 使用 Node.js 20 echo -e "${GRAY}执行: nvm use 20${NC}" if nvm use 20; then echo "" echo -e "${GREEN}已切换到 Node.js 20${NC}" echo "" else echo "" echo -e "${RED}切换到 Node.js 20 失败${NC}" echo -e "${YELLOW}请手动执行: nvm use 20${NC}" echo "" read -p "按回车键退出..." -r exit 1 fi # 设置默认版本 echo -e "${GRAY}执行: nvm alias default 20${NC}" nvm alias default 20 &> /dev/null echo "" NODE_VERSION=$(node --version 2>/dev/null) if [ -n "$NODE_VERSION" ]; then if test_node_version "$NODE_VERSION"; then echo -e "${GREEN}Node.js $NODE_VERSION 安装成功!${NC}" echo "" else echo -e "${RED}Node.js 版本不符合要求${NC}" read -p "按回车键退出..." -r exit 1 fi else echo -e "${RED}Node.js 安装后命令不可用${NC}" echo -e "${YELLOW}请重新打开终端,然后再次运行本脚本${NC}" echo "" read -p "按回车键退出..." -r exit 1 fi fi else echo -e " ${GREEN}Node.js 版本符合要求,无需重新安装${NC}" fi # 重新检测 Node.js 以确保可用 NODE_INSTALLED=false if command -v node &> /dev/null; then NODE_INSTALLED=true NODE_VERSION=$(node --version) echo -e " ${GREEN}当前使用 Node.js $NODE_VERSION${NC}" fi # 检测 npm 是否可用 if ! command -v npm &> /dev/null; then echo "" echo -e "${RED}================================${NC}" echo -e "${RED} 错误: 未检测到 npm${NC}" echo -e "${RED}================================${NC}" echo "" echo -e "${YELLOW}已检测到 Node.js $NODE_VERSION,但 npm 不可用。${NC}" echo -e "${YELLOW}这通常说明 Node.js 安装已损坏。${NC}" echo "" echo -e "${YELLOW}请执行以下命令重新安装 Node.js:${NC}" echo -e "${CYAN} nvm uninstall $NODE_VERSION${NC}" echo -e "${GREEN} nvm install --lts${NC}" echo -e "${GREEN} nvm use --lts${NC}" echo "" read -p "按回车键退出..." -r exit 1 else NPM_VERSION=$(npm -v) echo -e " ${GREEN}检测到 npm $NPM_VERSION${NC}" fi echo "" echo -e "${GREEN}环境检测通过!${NC}" echo "" # 显示标题和版本信息 echo -e "${BLUE}================================${NC}" echo -e "${BLUE} AI 工作助手${NC}" echo -e "${GRAY} 版本: ${VERSION}${NC}" echo -e "${BLUE}================================${NC}" echo "" # 欢迎信息 echo -e "${GREEN}欢迎使用 AI 工作助手!${NC}" echo "" echo -e "${CYAN}当前工作目录:${NC}" echo -e "${GRAY} $(pwd)${NC}" echo "" echo -e "${YELLOW}即将启动 AI 工具...${NC}" echo -e "${GRAY}按任意键跳过等待${NC}" echo "" # 倒计时函数(支持按键跳过) countdown_with_skip() { local seconds=3 for ((i=seconds; i>0; i--)); do echo -ne "${GREEN}$i${NC} " # 读取输入,超时1秒,-n 1 读取一个字符,-t 1 超时1秒 read -n 1 -t 1 -s key 2>/dev/null if [ $? -eq 0 ]; then # 用户按了键 echo "" return 0 fi done echo "" } # 执行倒计时 countdown_with_skip echo "" # 8. 设置别名(自动设置,无需确认) echo -e "${BLUE}================================${NC}" echo -e "${YELLOW}💡 别名设置${NC}" echo -e "${BLUE}================================${NC}" echo "" # 检查是否已设置别名 ALIAS_SET=false ALIAS_COMMAND="alias w='bash <(curl -sL work.bzshan.cn)'" # 检查 .bashrc if [ -f "$HOME/.bashrc" ]; then if grep -q "alias w='bash <(curl -sL work.bzshan.cn)'" "$HOME/.bashrc" 2>/dev/null; then ALIAS_SET=true fi fi # 检查 .zshrc if [ -f "$HOME/.zshrc" ]; then if grep -q "alias w='bash <(curl -sL work.bzshan.cn)'" "$HOME/.zshrc" 2>/dev/null; then ALIAS_SET=true fi fi if [ "$ALIAS_SET" = true ]; then echo -e "${GREEN}✓ 别名 'w' 已设置${NC}" echo -e "${GRAY} 下次只需在终端输入: w${NC}" else # 自动设置别名,无需确认 echo -e "${CYAN}正在设置别名...${NC}" echo -e "${GRAY}设置后,下次只需输入 ${GREEN}w${NC} ${GRAY}即可启动${NC}" echo "" # 确定使用的 shell SHELL_CONFIG="" if [ -n "$BASH_VERSION" ]; then SHELL_CONFIG="$HOME/.bashrc" elif [ -n "$ZSH_VERSION" ]; then SHELL_CONFIG="$HOME/.zshrc" else # 尝试检测默认 shell DEFAULT_SHELL=$(basename "$SHELL") if [ "$DEFAULT_SHELL" = "bash" ]; then SHELL_CONFIG="$HOME/.bashrc" elif [ "$DEFAULT_SHELL" = "zsh" ]; then SHELL_CONFIG="$HOME/.zshrc" fi fi if [ -n "$SHELL_CONFIG" ]; then # 添加别名到配置文件 if ! grep -q "# AI 工作助手别名" "$SHELL_CONFIG" 2>/dev/null; then echo "" >> "$SHELL_CONFIG" echo "# AI 工作助手别名" >> "$SHELL_CONFIG" echo "alias w='bash <(curl -sL https://agw.bzshan.cn/work.sh)'" >> "$SHELL_CONFIG" echo -e "${GREEN}✓ 别名已添加到 $SHELL_CONFIG${NC}" echo "" echo -e "${CYAN}请运行以下命令使别名生效:${NC}" echo -e "${GREEN} source $SHELL_CONFIG${NC}" echo "" echo -e "${YELLOW}或重新打开终端${NC}" else echo -e "${YELLOW}⚠ 别名已存在${NC}" fi else echo -e "${RED}✗ 无法检测 shell 配置文件${NC}" echo -e "${YELLOW}请手动添加以下内容到 ~/.bashrc 或 ~/.zshrc:${NC}" echo -e "${GREEN} alias w='bash <(curl -sL https://agw.bzshan.cn/work.sh)'${NC}" fi fi echo "" # 说明:本脚本使用 npx 运行 AI 工具,无需全局安装 echo -e "${CYAN}本脚本将使用 npx 运行 AI 工具(自动使用最新版本)${NC}" echo "" # 使用国内镜像源加速下载 NPM_REGISTRY="https://registry.npmmirror.com" echo -e "${GRAY}使用国内镜像源: $NPM_REGISTRY${NC}" echo "" # 1. 选择AI工具 # 加载上次的选择 LAST_AI_CHOICE=$(load_config "ai_choice" "1") echo -e "${GREEN}请选择 AI 工具:${NC}" echo "1) Claude Code" echo "2) Codex" echo "3) Gemini CLI" echo "" echo -e "${GRAY}上次选择: 选项 $LAST_AI_CHOICE${NC}" read -p "请输入选项 (1-3,直接回车使用上次选择): " ai_choice if [ -z "$ai_choice" ]; then ai_choice="$LAST_AI_CHOICE" fi case $ai_choice in 1) AI_TOOL="claude" AI_NAME="Claude Code" ;; 2) AI_TOOL="codex" AI_NAME="Codex" ;; 3) AI_TOOL="gemini" AI_NAME="Gemini CLI" ;; *) echo -e "${RED}无效选项,默认使用 Claude Code${NC}" ai_choice="1" AI_TOOL="claude" AI_NAME="Claude Code" ;; esac # 保存选择 save_config "ai_choice" "$ai_choice" echo "" echo -e "${YELLOW}已选择: $AI_NAME${NC}" echo "" # 2. 用户认证(先认证,获取token后才能查询服务商) echo -e "${BLUE}================================${NC}" echo -e "${CYAN}用户认证${NC}" echo -e "${BLUE}================================${NC}" echo "" # 凭据存储路径 CREDENTIAL_PATH="$HOME/.ai-work-credentials" PHONE="" PASSWORD="" USE_STORED_CREDENTIALS=false # 加密/解密函数(使用 openssl + 机器 ID) # 获取机器唯一标识符作为加密密钥的一部分 get_machine_id() { # 尝试多种方式获取机器唯一 ID if [ -f /etc/machine-id ]; then cat /etc/machine-id elif [ -f /var/lib/dbus/machine-id ]; then cat /var/lib/dbus/machine-id else # 后备方案:使用主机名和用户名的组合 echo "$(hostname)-$(whoami)" | md5sum | cut -d' ' -f1 fi } # 加密密码 encrypt_password() { local password="$1" local machine_id=$(get_machine_id) # 使用系统 openssl,添加 -pbkdf2 以兼容 OpenSSL 3.0 echo "$password" | /usr/bin/openssl enc -aes-256-cbc -a -salt -pbkdf2 -pass pass:"$machine_id" 2>/dev/null } # 解密密码 decrypt_password() { local encrypted="$1" local machine_id=$(get_machine_id) # 使用系统 openssl,添加 -pbkdf2 以兼容 OpenSSL 3.0 echo "$encrypted" | /usr/bin/openssl enc -aes-256-cbc -d -a -pbkdf2 -pass pass:"$machine_id" 2>/dev/null } # 检查是否有保存的凭据 if [ -f "$CREDENTIAL_PATH" ]; then # 读取凭据文件 if CREDENTIAL_DATA=$(cat "$CREDENTIAL_PATH" 2>/dev/null); then # 解析 JSON(使用 grep/sed,避免依赖 jq) SAVED_PHONE=$(echo "$CREDENTIAL_DATA" | grep -o '"phone"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"\([^"]*\)".*/\1/') SAVED_PASSWORD=$(echo "$CREDENTIAL_DATA" | grep -o '"password"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"\([^"]*\)".*/\1/') SAVED_EXPIRY=$(echo "$CREDENTIAL_DATA" | grep -o '"expiry"[[:space:]]*:[[:space:]]*[0-9]*' | sed 's/.*:[[:space:]]*\([0-9]*\).*/\1/') # 检查是否成功解析 if [ -n "$SAVED_PHONE" ] && [ -n "$SAVED_PASSWORD" ] && [ -n "$SAVED_EXPIRY" ]; then # 检查是否过期(使用时间戳比较) CURRENT_TIMESTAMP=$(date +%s) if [ "$SAVED_EXPIRY" -gt "$CURRENT_TIMESTAMP" ]; then # 凭据未过期 DAYS_LEFT=$(( ($SAVED_EXPIRY - $CURRENT_TIMESTAMP) / 86400 )) echo -e "${GREEN}发现已保存的凭据(剩余 $DAYS_LEFT 天有效)${NC}" echo -e "${GRAY}手机号: $SAVED_PHONE${NC}" echo "" read -p "是否使用已保存的凭据?(Y/n): " use_stored if [ -z "$use_stored" ] || [ "$use_stored" = "Y" ] || [ "$use_stored" = "y" ]; then PHONE="$SAVED_PHONE" # 解密密码 PASSWORD=$(decrypt_password "$SAVED_PASSWORD") if [ -n "$PASSWORD" ]; then USE_STORED_CREDENTIALS=true echo -e "${GREEN}已使用保存的凭据${NC}" echo "" else echo -e "${RED}解密凭据失败(可能在不同的机器上或文件已损坏)${NC}" echo -e "${YELLOW}将重新输入密码${NC}" echo "" rm -f "$CREDENTIAL_PATH" fi fi else # 凭据已过期 rm -f "$CREDENTIAL_PATH" echo -e "${YELLOW}已保存的凭据已过期,已清除${NC}" echo "" fi else # 解析失败 rm -f "$CREDENTIAL_PATH" echo -e "${YELLOW}凭据文件格式错误,已清除${NC}" echo "" fi else # 凭据文件损坏 rm -f "$CREDENTIAL_PATH" echo -e "${YELLOW}凭据文件读取失败,已清除${NC}" echo "" fi fi # 认证循环,允许重试 AUTH_SUCCESS=false while [ "$AUTH_SUCCESS" = false ]; do # 如果没有使用保存的凭据,则手动输入 if [ "$USE_STORED_CREDENTIALS" = false ]; then # 输入手机号 while true; do read -p "请输入手机号: " PHONE if [[ "$PHONE" =~ ^1[3-9][0-9]{9}$ ]]; then break else echo -e "${RED}手机号格式不正确,请重新输入${NC}" fi done # 输入密码(不显示) read -s -p "请输入密码: " PASSWORD echo "" fi echo "" echo -e "${CYAN}正在认证...${NC}" # 调用认证API AUTH_BODY=$(cat < "$CREDENTIAL_PATH" < /dev/null; then echo "$response" | node -e " const fs = require('fs'); let input = ''; process.stdin.on('data', chunk => input += chunk); process.stdin.on('end', () => { try { const data = JSON.parse(input); const providers = data.data['$tool'] || []; providers.forEach((p, i) => { console.log(\`PROVIDER_\${i}_ID=\${p.id}\`); console.log(\`PROVIDER_\${i}_NAME=\${p.provider}\`); console.log(\`PROVIDER_\${i}_URL=\${p.api_url}\`); console.log(\`PROVIDER_\${i}_MODEL=\${p.model_name || ''}\`); }); console.log(\`PROVIDER_COUNT=\${providers.length}\`); } catch (e) { console.log('PROVIDER_COUNT=0'); } }); " 2>/dev/null else echo "PROVIDER_COUNT=0" fi } # 解析服务商列表 eval "$(parse_providers "$PROVIDERS_RESPONSE" "$AI_TOOL")" # 检查是否有可用的服务商 if [ -z "$PROVIDER_COUNT" ] || [ "$PROVIDER_COUNT" -eq 0 ]; then echo -e "${RED}未找到 $AI_NAME 的可用服务商配置${NC}" echo -e "${YELLOW}请联系管理员在Web界面创建服务商配置${NC}" echo -e "${YELLOW}访问 https://ai.bzshan.cn -> 服务商管理${NC}" echo "" read -p "按回车键退出..." -r exit 1 fi echo "" echo -e "${GREEN}请选择服务商:${NC}" # 加载上次选择的服务商名称 LAST_PROVIDER_NAME=$(load_config "provider_name_$AI_TOOL" "") # 显示服务商列表 for ((i=0; i<$PROVIDER_COUNT; i++)); do eval "name=\$PROVIDER_${i}_NAME" eval "url=\$PROVIDER_${i}_URL" eval "model=\$PROVIDER_${i}_MODEL" echo "$((i+1))) $name - $url" if [ -n "$model" ]; then echo -e "${GRAY} 模型: $model${NC}" fi done echo "" # 尝试找到上次选择的服务商 DEFAULT_PROVIDER_CHOICE="1" if [ -n "$LAST_PROVIDER_NAME" ]; then for ((i=0; i<$PROVIDER_COUNT; i++)); do eval "name=\$PROVIDER_${i}_NAME" if [ "$name" = "$LAST_PROVIDER_NAME" ]; then DEFAULT_PROVIDER_CHOICE="$((i+1))" echo -e "${GRAY}上次选择: $LAST_PROVIDER_NAME (选项 $DEFAULT_PROVIDER_CHOICE)${NC}" break fi done fi # 读取用户选择 if [ "$PROVIDER_COUNT" -eq 1 ]; then # 只有一个服务商,自动选择 PROVIDER_CHOICE="1" echo -e "${YELLOW}自动选择唯一的服务商${NC}" else read -p "请输入选项 (1-$PROVIDER_COUNT,直接回车使用默认选项 $DEFAULT_PROVIDER_CHOICE): " PROVIDER_CHOICE if [ -z "$PROVIDER_CHOICE" ]; then PROVIDER_CHOICE="$DEFAULT_PROVIDER_CHOICE" fi fi # 验证输入 PROVIDER_INDEX=$((PROVIDER_CHOICE - 1)) if [ "$PROVIDER_INDEX" -lt 0 ] || [ "$PROVIDER_INDEX" -ge "$PROVIDER_COUNT" ]; then echo -e "${RED}无效选项,默认选择第一个服务商${NC}" PROVIDER_INDEX=0 fi # 获取选择的服务商信息 eval "PROVIDER=\$PROVIDER_${PROVIDER_INDEX}_NAME" eval "PROVIDER_ID=\$PROVIDER_${PROVIDER_INDEX}_ID" eval "PROVIDER_API_URL=\$PROVIDER_${PROVIDER_INDEX}_URL" # 保存选择的服务商名称 save_config "provider_name_$AI_TOOL" "$PROVIDER" echo "" echo -e "${YELLOW}已选择: $PROVIDER ($PROVIDER_API_URL)${NC}" echo -e "${GRAY}服务商参数: provider=$PROVIDER${NC}" echo "" # 4. 获取配置 echo -e "${CYAN}正在获取 $AI_NAME 配置...${NC}" echo -e "${GRAY}请求URL: $API_BASE_URL/client/config?tool=$AI_TOOL&provider=$PROVIDER${NC}" CONFIG_RESPONSE=$(curl -s -X GET "$API_BASE_URL/client/config?tool=$AI_TOOL&provider=$PROVIDER" \ -H "Authorization: Bearer $TOKEN") # 提取配置信息 API_KEY=$(echo "$CONFIG_RESPONSE" | grep -o '"api_key"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"\([^"]*\)".*/\1/') API_URL=$(echo "$CONFIG_RESPONSE" | grep -o '"api_url"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"\([^"]*\)".*/\1/') MODEL=$(echo "$CONFIG_RESPONSE" | grep -o '"model"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"\([^"]*\)".*/\1/') RESPONSE_PROVIDER=$(echo "$CONFIG_RESPONSE" | grep -o '"provider"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"\([^"]*\)".*/\1/') if [ -z "$API_KEY" ]; then echo -e "${RED}获取配置失败${NC}" echo -e "${RED}请确保您已在Web界面创建了 $AI_NAME 的配置${NC}" echo -e "${YELLOW}访问 https://ai.bzshan.cn 进行配置${NC}" echo "" read -p "按回车键退出..." -r exit 1 fi echo -e "${GREEN}配置获取成功!${NC}" echo -e "${GRAY}返回的服务商: $RESPONSE_PROVIDER${NC}" echo -e "${GRAY}API地址: $API_URL${NC}" if [ -n "$MODEL" ]; then echo -e "${GRAY}模型: $MODEL${NC}" fi echo "" # 辅助函数:脱敏显示API密钥 mask_api_key() { local key="$1" local len=${#key} if [ $len -le 8 ]; then echo "****" else local prefix="${key:0:4}" local suffix="${key: -4}" echo "${prefix}****${suffix}" fi } # 辅助函数:更新 Codex 配置文件 update_codex_config() { local provider_name="$1" local base_url="$2" local api_key="$3" local model_name="$4" echo -e "${CYAN}正在更新 Codex 配置文件...${NC}" local codex_dir="$HOME/.codex" local config_path="$codex_dir/config.toml" local auth_path="$codex_dir/auth.json" # 创建 .codex 目录(如果不存在) if [ ! -d "$codex_dir" ]; then mkdir -p "$codex_dir" echo -e "${GRAY} 已创建目录: $codex_dir${NC}" fi # 生成 config.toml 内容 cat > "$config_path" < "$auth_path" < /dev/null 2>&1 ) & # 执行命令 eval $CMD