用 AI 写了个 codex-switch 一键切换 Codex 多套 API 配置

2026-04-11 08:311阅读0评论SEO问题
  • 内容介绍
  • 文章标签
  • 相关推荐
问题描述:

天天给我的 codex 换各种不同的中转 API 有点麻烦,让 codex 给我写了一个快速命令行切换的命令,分享给各位佬,请自行修改使用。

  • 输入 codex-switch 即可进入交互式选择
  • 支持粘贴大量文本(如配置json)自动解析添加配置

CleanShot 2026-04-05 at 15.22.35@2x990×342 77.4 KB

添加新的配置:
CleanShot 2026-04-05 at 15.23.35@2x1170×406 75.5 KB

脚本如下,直接粘贴给你的 codex 或者其他 AI 工具来帮你安装、改动、调整:

#!/bin/bash # codex-switch 一键安装脚本 # 用法: bash install-codex-switch.sh # 或: curl -fsSL <url> | bash set -e CODEX_DIR="$HOME/.codex" SCRIPT_PATH="$CODEX_DIR/switch-api.sh" PROFILES_DIR="$CODEX_DIR/profiles" echo "" echo " ┌──────────────────────────────────────┐" echo " │ codex-switch 安装程序 │" echo " │ Codex API 多配置快速切换工具 │" echo " └──────────────────────────────────────┘" echo "" # 检查 codex 是否已安装 if [[ ! -d "$CODEX_DIR" ]]; then echo " ❌ 未检测到 ~/.codex 目录,请先安装 OpenAI Codex CLI" exit 1 fi # 创建 profiles 目录 mkdir -p "$PROFILES_DIR" # 备份旧脚本 if [[ -f "$SCRIPT_PATH" ]]; then cp "$SCRIPT_PATH" "${SCRIPT_PATH}.bak.$(date +%Y%m%d%H%M%S)" echo " 📦 已备份旧版本" fi # 写入主脚本 cat > "$SCRIPT_PATH" << 'MAINSCRIPT' #!/bin/zsh # Codex API 快速切换脚本 # 用法: codex-switch — 方向键交互选择 # codex-switch <名称> — 直接切换 # codex-switch add — 添加新配置 # codex-switch rm <名称> — 删除配置 # codex-switch list — 列出所有配置 CODEX_DIR="$HOME/.codex" PROFILES_DIR="$CODEX_DIR/profiles" # ── 工具函数 ───────────────────────────────── load_profile() { local file="$1" unset desc auth_mode api_key config source "$file" } get_profile_names() { local files=("$PROFILES_DIR"/*.conf(N)) for f in "${files[@]}"; do echo "${f:t:r}" done } current_profile() { local names=($(get_profile_names)) for name in "${names[@]}"; do load_profile "$PROFILES_DIR/$name.conf" if [[ "$auth_mode" == "chatgpt" ]]; then grep -q 'model_provider' "$CODEX_DIR/config.toml" 2>/dev/null || { echo "$name"; return; } else local url url=$(echo "$config" | grep 'base_url' | head -1 | sed 's/.*= *"//;s/".*//') [[ -n "$url" ]] && grep -q "$url" "$CODEX_DIR/config.toml" 2>/dev/null && { echo "$name"; return; } fi done echo "unknown" } get_tail() { sed -n '/^\[projects\./,$p' "$CODEX_DIR/config.toml" } # ── 方向键交互菜单 ─────────────────────────── arrow_menu() { local names=($(get_profile_names)) local count=${#names[@]} if (( count == 0 )); then echo "❌ 没有配置文件,请先运行: codex-switch add" return 1 fi local current current="$(current_profile)" local -a descs local sel=1 for (( i=1; i<=count; i++ )); do load_profile "$PROFILES_DIR/${names[$i]}.conf" descs[$i]="$desc" [[ "${names[$i]}" == "$current" ]] && sel=$i done tput civis 2>/dev/null draw_menu() { (( ${1:-0} )) && printf "\033[%dA" "$count" for (( i=1; i<=count; i++ )); do local marker=" " local style="\033[0m" [[ "${names[$i]}" == "$current" ]] && marker="✦ " if (( i == sel )); then style="\033[1;36m" printf "\r\033[K${style} ▸ %-14s %s${marker:+ \033[2m%s\033[0m}\033[0m\n" "${names[$i]}" "${descs[$i]}" "$( [[ "${names[$i]}" == "$current" ]] && echo '当前' )" else printf "\r\033[K${style} %-14s %s${marker:+ \033[2m%s\033[0m}\033[0m\n" "${names[$i]}" "${descs[$i]}" "$( [[ "${names[$i]}" == "$current" ]] && echo '当前' )" fi done } echo "" echo " \033[1mCodex API 配置切换\033[0m (↑↓ 选择, Enter 确认, q 退出)" echo " ─────────────────────────────────────────────" echo "" draw_menu 0 while true; do read -rsk1 key case "$key" in $'\e') read -rsk1 -t 0.1 key2 read -rsk1 -t 0.1 key3 case "$key2$key3" in '[A') (( sel > 1 )) && (( sel-- )) ;; '[B') (( sel < count )) && (( sel++ )) ;; esac draw_menu 1 ;; k) (( sel > 1 )) && (( sel-- )); draw_menu 1 ;; j) (( sel < count )) && (( sel++ )); draw_menu 1 ;; $'\n'|'') tput cnorm 2>/dev/null echo "" switch_to "${names[$sel]}" return ;; q|Q) tput cnorm 2>/dev/null echo "" echo " 已取消" return 1 ;; esac done } # ── 切换逻辑 ───────────────────────────────── switch_to() { local name="$1" local file="$PROFILES_DIR/$name.conf" if [[ ! -f "$file" ]]; then echo "❌ 配置不存在: $name" echo " 可用: $(get_profile_names | tr '\n' ' ')" return 1 fi load_profile "$file" local tail tail="$(get_tail)" printf '%s\n\n%s\n' "$config" "$tail" > "$CODEX_DIR/config.toml" if [[ "$auth_mode" == "apikey" && -n "$api_key" ]]; then cat > "$CODEX_DIR/auth.json" <<AUTHEOF { "auth_mode": "apikey", "OPENAI_API_KEY": "$api_key" } AUTHEOF else if [[ -f "$CODEX_DIR/auth.json" ]] && command -v jq &>/dev/null; then local cur cur="$(cat "$CODEX_DIR/auth.json")" echo "$cur" | jq '.auth_mode = "chatgpt" | .OPENAI_API_KEY = null' > "$CODEX_DIR/auth.json" else echo '{"auth_mode":"chatgpt","OPENAI_API_KEY":null}' > "$CODEX_DIR/auth.json" fi if ! jq -e '.tokens.refresh_token' "$CODEX_DIR/auth.json" &>/dev/null; then echo "⚠️ 需要重新登录: codex --login" fi fi echo "✅ 已切换到: $desc" } # ── 添加新配置 ─────────────────────────────── parse_pasted_text() { local text="$1" _parsed_url=$(echo "$text" | grep -oE 'base_url\s*=\s*"[^"]+"' | head -1 | sed 's/.*= *"//;s/"//') [[ -z "$_parsed_url" ]] && _parsed_url=$(echo "$text" | grep -oE 'https?://[A-Za-z0-9._~:/?#@!$&()*+,;=-]+' | head -1) _parsed_url="${_parsed_url%[,;'\"\}\)]}" _parsed_key=$(echo "$text" | grep -oE '(sk-[A-Za-z0-9_-]{20,}|key-[A-Za-z0-9_-]{20,})' | head -1) if [[ -z "$_parsed_key" ]]; then _parsed_key=$(echo "$text" | grep -oiE '(OPENAI_API_KEY|api_key)["\s:=]+\s*"?([^",\s}]+)' | head -1 | sed 's/.*["= :]//') _parsed_key="${_parsed_key%[\"'\},]}" fi _parsed_model=$(echo "$text" | grep -E '^\s*model\s*=' | grep -v 'model_provider\|model_reasoning\|model_context\|model_auto\|review_model' | head -1 | sed 's/.*= *"//;s/".*//') _parsed_wire=$(echo "$text" | grep -oE 'wire_api\s*=\s*"[^"]+"' | head -1 | sed 's/.*= *"//;s/"//') _parsed_name=$(echo "$text" | grep -E '^\s*name\s*=' | head -1 | sed 's/.*= *"//;s/".*//') if [[ -z "$_parsed_name" && -n "$_parsed_url" ]]; then _parsed_name=$(echo "$_parsed_url" | sed 's|https\?://||;s|/.*||;s|^api\.||;s|^www\.||;s|\.com$||;s|\.net$||;s|\.io$||;s|\.ai$||;s|\.org$||;s|\.cn$||' | tr '.' '-') fi } add_profile() { echo "" echo " \033[1m添加新的 API 配置\033[0m" echo " ─────────────────" echo "" echo " 可以直接粘贴包含 URL / Key 的文本(如配置片段、JSON 等)" echo " 粘贴完成后按两次 Enter 结束;或直接按 Enter 进入手动输入模式" echo "" printf " ▸ 粘贴或输入: " local pasted="" line="" empty_count=0 while true; do IFS= read -r line if [[ -z "$line" ]]; then (( empty_count++ )) (( empty_count >= 2 )) && break [[ -z "$pasted" ]] && break pasted+=$'\n' else empty_count=0 pasted+="${line}"$'\n' fi done local pname="" pdesc="" purl="" pkey="" pmodel="" pwire="" if [[ -n "$pasted" ]]; then parse_pasted_text "$pasted" echo "" echo " \033[1m已识别:\033[0m" [[ -n "$_parsed_name" ]] && echo " 名称: $_parsed_name" [[ -n "$_parsed_url" ]] && echo " URL: $_parsed_url" [[ -n "$_parsed_key" ]] && echo " Key: ${_parsed_key:0:12}...${_parsed_key: -4}" [[ -n "$_parsed_model" ]] && echo " 模型: $_parsed_model" [[ -n "$_parsed_wire" ]] && echo " Wire: $_parsed_wire" echo "" pname="$_parsed_name" purl="$_parsed_url" pkey="$_parsed_key" pmodel="$_parsed_model" pwire="$_parsed_wire" fi printf " 配置名称 [${pname:-myapi}]: " read -r input; [[ -n "$input" ]] && pname="$input" pname="${pname:-myapi}" pname="${pname// /-}" if [[ -f "$PROFILES_DIR/$pname.conf" ]]; then printf " ⚠️ \"$pname\" 已存在,覆盖? [y/N]: " read -rk1 yn; echo [[ "$yn" != [yY] ]] && { echo " 已取消"; return 1; } fi printf " 描述 [${pname} API]: " read -r input; pdesc="${input:-${pname} API}" printf " API Base URL [${purl:-}]: " read -r input; [[ -n "$input" ]] && purl="$input" if [[ -z "$purl" ]]; then echo " ❌ URL 不能为空"; return 1 fi if [[ -n "$pkey" ]]; then printf " API Key [${pkey:0:12}...${pkey: -4}]: " else printf " API Key: " fi read -r input; [[ -n "$input" ]] && pkey="$input" if [[ -z "$pkey" ]]; then echo " ❌ API Key 不能为空"; return 1 fi printf " 模型名称 [${pmodel:-gpt-5.4}]: " read -r input; [[ -n "$input" ]] && pmodel="$input" pmodel="${pmodel:-gpt-5.4}" printf " Wire API (responses/chat) [${pwire:-responses}]: " read -r input; [[ -n "$input" ]] && pwire="$input" pwire="${pwire:-responses}" cat > "$PROFILES_DIR/$pname.conf" <<PROFEOF desc="$pdesc" auth_mode="apikey" api_key="$pkey" config='model_provider = "OpenAI" model = "$pmodel" review_model = "$pmodel" model_reasoning_effort = "xhigh" disable_response_storage = true network_access = "enabled" windows_wsl_setup_acknowledged = true model_context_window = 1000000 model_auto_compact_token_limit = 900000 personality = "pragmatic" approvals_reviewer = "user" service_tier = "fast" [model_providers.OpenAI] name = "OpenAI" base_url = "$purl" wire_api = "$pwire" requires_openai_auth = true' PROFEOF echo "" echo " ✅ 配置 \"$pname\" 已保存" printf " 立即切换到此配置? [Y/n]: " read -rk1 sw; echo [[ "$sw" != [nN] ]] && switch_to "$pname" } # ── 删除配置 ───────────────────────────────── rm_profile() { local name="$1" if [[ -z "$name" ]]; then echo "用法: codex-switch rm <配置名>" return 1 fi local file="$PROFILES_DIR/$name.conf" if [[ ! -f "$file" ]]; then echo "❌ 配置不存在: $name" return 1 fi printf " 确认删除 \"$name\"? [y/N]: " read -rk1 yn; echo if [[ "$yn" == [yY] ]]; then rm "$file" echo "✅ 已删除: $name" else echo "已取消" fi } # ── 列出配置 ───────────────────────────────── list_profiles() { local names=($(get_profile_names)) local current current="$(current_profile)" echo "" echo " \033[1m已有配置\033[0m ($PROFILES_DIR/)" echo " ─────────────────" for name in "${names[@]}"; do load_profile "$PROFILES_DIR/$name.conf" local mark="" [[ "$name" == "$current" ]] && mark=" ← 当前" printf " %-14s %s\033[2m%s\033[0m\n" "$name" "$desc" "$mark" done echo "" } # ── 帮助 ───────────────────────────────────── show_help() { local current current="$(current_profile)" echo "" echo " \033[1mcodex-switch\033[0m — Codex API 多配置快速切换" echo "" echo " \033[1m用法:\033[0m" echo " codex-switch 交互式选择(↑↓ 方向键)" echo " codex-switch <名称> 直接切换到指定配置" echo " codex-switch add 添加新配置(支持粘贴自动识别)" echo " codex-switch rm <名称> 删除配置" echo " codex-switch list 列出所有配置" echo " codex-switch help 显示此帮助" echo "" echo " \033[1m当前:\033[0m $current" echo " \033[1m配置目录:\033[0m $PROFILES_DIR/" echo "" } # ── 入口 ───────────────────────────────────── case "${1:-}" in add) add_profile ;; rm|remove) rm_profile "$2" ;; list|ls) list_profiles ;; help|-h|--help) show_help ;; "") arrow_menu ;; *) switch_to "$1" ;; esac MAINSCRIPT chmod +x "$SCRIPT_PATH" echo " ✅ 脚本已安装到 $SCRIPT_PATH" # 创建 profiles 目录 mkdir -p "$PROFILES_DIR" # 创建官方配置(如果不存在) if [[ ! -f "$PROFILES_DIR/official.conf" ]]; then cat > "$PROFILES_DIR/official.conf" << 'OFFCONF' desc="官方 OpenAI (ChatGPT 认证)" auth_mode="chatgpt" config='model = "gpt-5.4" model_reasoning_effort = "xhigh" personality = "pragmatic" approvals_reviewer = "user" service_tier = "fast"' OFFCONF echo " 📄 已创建默认配置: official" fi # 设置 shell alias setup_alias() { local shell_rc="" if [[ -n "$ZSH_VERSION" ]] || [[ "$SHELL" == */zsh ]]; then shell_rc="$HOME/.zshrc" elif [[ -n "$BASH_VERSION" ]] || [[ "$SHELL" == */bash ]]; then shell_rc="$HOME/.bashrc" fi if [[ -z "$shell_rc" ]]; then echo " ⚠️ 无法检测 shell 类型,请手动添加 alias:" echo " alias codex-switch='source ~/.codex/switch-api.sh'" return fi local alias_line="alias codex-switch='source ~/.codex/switch-api.sh'" if grep -qF 'codex-switch' "$shell_rc" 2>/dev/null; then echo " ✅ alias 已存在于 $shell_rc" else echo "" >> "$shell_rc" echo "# codex-switch: Codex API 多配置切换" >> "$shell_rc" echo "$alias_line" >> "$shell_rc" echo " ✅ alias 已添加到 $shell_rc" fi } setup_alias echo "" echo " ┌────────────────────────────────────────┐" echo " │ 安装完成! │" echo " │ │" echo " │ 重新打开终端,或运行: │" echo " │ source ~/.zshrc │" echo " │ │" echo " │ 然后: │" echo " │ codex-switch 交互式选择 │" echo " │ codex-switch add 添加新配置 │" echo " │ codex-switch rm <名> 删除配置 │" echo " │ codex-switch list 查看所有配置 │" echo " │ codex-switch help 帮助信息 │" echo " └────────────────────────────────────────┘" echo "" 网友解答:


--【壹】--:

一直在用 正版 cc 订阅,所以一直没敢用 cc switch 看来我重复造轮子了,现在马上去下。


--【贰】--:

ccswitch可以直接切


--【叁】--:

感谢大佬了。


--【肆】--:

cc switch不是有这个功能嘛


--【伍】--:

我也vibe了一个tui的,自用还是比较舒适的


--【陆】--:

我前几天写了一个类似于ccswitch的命令行切换工具(https://github.com/realihang/cc-switch-linux.git)跟你这个思路差不多 需要的友友欢迎star​!

问题描述:

天天给我的 codex 换各种不同的中转 API 有点麻烦,让 codex 给我写了一个快速命令行切换的命令,分享给各位佬,请自行修改使用。

  • 输入 codex-switch 即可进入交互式选择
  • 支持粘贴大量文本(如配置json)自动解析添加配置

CleanShot 2026-04-05 at 15.22.35@2x990×342 77.4 KB

添加新的配置:
CleanShot 2026-04-05 at 15.23.35@2x1170×406 75.5 KB

脚本如下,直接粘贴给你的 codex 或者其他 AI 工具来帮你安装、改动、调整:

#!/bin/bash # codex-switch 一键安装脚本 # 用法: bash install-codex-switch.sh # 或: curl -fsSL <url> | bash set -e CODEX_DIR="$HOME/.codex" SCRIPT_PATH="$CODEX_DIR/switch-api.sh" PROFILES_DIR="$CODEX_DIR/profiles" echo "" echo " ┌──────────────────────────────────────┐" echo " │ codex-switch 安装程序 │" echo " │ Codex API 多配置快速切换工具 │" echo " └──────────────────────────────────────┘" echo "" # 检查 codex 是否已安装 if [[ ! -d "$CODEX_DIR" ]]; then echo " ❌ 未检测到 ~/.codex 目录,请先安装 OpenAI Codex CLI" exit 1 fi # 创建 profiles 目录 mkdir -p "$PROFILES_DIR" # 备份旧脚本 if [[ -f "$SCRIPT_PATH" ]]; then cp "$SCRIPT_PATH" "${SCRIPT_PATH}.bak.$(date +%Y%m%d%H%M%S)" echo " 📦 已备份旧版本" fi # 写入主脚本 cat > "$SCRIPT_PATH" << 'MAINSCRIPT' #!/bin/zsh # Codex API 快速切换脚本 # 用法: codex-switch — 方向键交互选择 # codex-switch <名称> — 直接切换 # codex-switch add — 添加新配置 # codex-switch rm <名称> — 删除配置 # codex-switch list — 列出所有配置 CODEX_DIR="$HOME/.codex" PROFILES_DIR="$CODEX_DIR/profiles" # ── 工具函数 ───────────────────────────────── load_profile() { local file="$1" unset desc auth_mode api_key config source "$file" } get_profile_names() { local files=("$PROFILES_DIR"/*.conf(N)) for f in "${files[@]}"; do echo "${f:t:r}" done } current_profile() { local names=($(get_profile_names)) for name in "${names[@]}"; do load_profile "$PROFILES_DIR/$name.conf" if [[ "$auth_mode" == "chatgpt" ]]; then grep -q 'model_provider' "$CODEX_DIR/config.toml" 2>/dev/null || { echo "$name"; return; } else local url url=$(echo "$config" | grep 'base_url' | head -1 | sed 's/.*= *"//;s/".*//') [[ -n "$url" ]] && grep -q "$url" "$CODEX_DIR/config.toml" 2>/dev/null && { echo "$name"; return; } fi done echo "unknown" } get_tail() { sed -n '/^\[projects\./,$p' "$CODEX_DIR/config.toml" } # ── 方向键交互菜单 ─────────────────────────── arrow_menu() { local names=($(get_profile_names)) local count=${#names[@]} if (( count == 0 )); then echo "❌ 没有配置文件,请先运行: codex-switch add" return 1 fi local current current="$(current_profile)" local -a descs local sel=1 for (( i=1; i<=count; i++ )); do load_profile "$PROFILES_DIR/${names[$i]}.conf" descs[$i]="$desc" [[ "${names[$i]}" == "$current" ]] && sel=$i done tput civis 2>/dev/null draw_menu() { (( ${1:-0} )) && printf "\033[%dA" "$count" for (( i=1; i<=count; i++ )); do local marker=" " local style="\033[0m" [[ "${names[$i]}" == "$current" ]] && marker="✦ " if (( i == sel )); then style="\033[1;36m" printf "\r\033[K${style} ▸ %-14s %s${marker:+ \033[2m%s\033[0m}\033[0m\n" "${names[$i]}" "${descs[$i]}" "$( [[ "${names[$i]}" == "$current" ]] && echo '当前' )" else printf "\r\033[K${style} %-14s %s${marker:+ \033[2m%s\033[0m}\033[0m\n" "${names[$i]}" "${descs[$i]}" "$( [[ "${names[$i]}" == "$current" ]] && echo '当前' )" fi done } echo "" echo " \033[1mCodex API 配置切换\033[0m (↑↓ 选择, Enter 确认, q 退出)" echo " ─────────────────────────────────────────────" echo "" draw_menu 0 while true; do read -rsk1 key case "$key" in $'\e') read -rsk1 -t 0.1 key2 read -rsk1 -t 0.1 key3 case "$key2$key3" in '[A') (( sel > 1 )) && (( sel-- )) ;; '[B') (( sel < count )) && (( sel++ )) ;; esac draw_menu 1 ;; k) (( sel > 1 )) && (( sel-- )); draw_menu 1 ;; j) (( sel < count )) && (( sel++ )); draw_menu 1 ;; $'\n'|'') tput cnorm 2>/dev/null echo "" switch_to "${names[$sel]}" return ;; q|Q) tput cnorm 2>/dev/null echo "" echo " 已取消" return 1 ;; esac done } # ── 切换逻辑 ───────────────────────────────── switch_to() { local name="$1" local file="$PROFILES_DIR/$name.conf" if [[ ! -f "$file" ]]; then echo "❌ 配置不存在: $name" echo " 可用: $(get_profile_names | tr '\n' ' ')" return 1 fi load_profile "$file" local tail tail="$(get_tail)" printf '%s\n\n%s\n' "$config" "$tail" > "$CODEX_DIR/config.toml" if [[ "$auth_mode" == "apikey" && -n "$api_key" ]]; then cat > "$CODEX_DIR/auth.json" <<AUTHEOF { "auth_mode": "apikey", "OPENAI_API_KEY": "$api_key" } AUTHEOF else if [[ -f "$CODEX_DIR/auth.json" ]] && command -v jq &>/dev/null; then local cur cur="$(cat "$CODEX_DIR/auth.json")" echo "$cur" | jq '.auth_mode = "chatgpt" | .OPENAI_API_KEY = null' > "$CODEX_DIR/auth.json" else echo '{"auth_mode":"chatgpt","OPENAI_API_KEY":null}' > "$CODEX_DIR/auth.json" fi if ! jq -e '.tokens.refresh_token' "$CODEX_DIR/auth.json" &>/dev/null; then echo "⚠️ 需要重新登录: codex --login" fi fi echo "✅ 已切换到: $desc" } # ── 添加新配置 ─────────────────────────────── parse_pasted_text() { local text="$1" _parsed_url=$(echo "$text" | grep -oE 'base_url\s*=\s*"[^"]+"' | head -1 | sed 's/.*= *"//;s/"//') [[ -z "$_parsed_url" ]] && _parsed_url=$(echo "$text" | grep -oE 'https?://[A-Za-z0-9._~:/?#@!$&()*+,;=-]+' | head -1) _parsed_url="${_parsed_url%[,;'\"\}\)]}" _parsed_key=$(echo "$text" | grep -oE '(sk-[A-Za-z0-9_-]{20,}|key-[A-Za-z0-9_-]{20,})' | head -1) if [[ -z "$_parsed_key" ]]; then _parsed_key=$(echo "$text" | grep -oiE '(OPENAI_API_KEY|api_key)["\s:=]+\s*"?([^",\s}]+)' | head -1 | sed 's/.*["= :]//') _parsed_key="${_parsed_key%[\"'\},]}" fi _parsed_model=$(echo "$text" | grep -E '^\s*model\s*=' | grep -v 'model_provider\|model_reasoning\|model_context\|model_auto\|review_model' | head -1 | sed 's/.*= *"//;s/".*//') _parsed_wire=$(echo "$text" | grep -oE 'wire_api\s*=\s*"[^"]+"' | head -1 | sed 's/.*= *"//;s/"//') _parsed_name=$(echo "$text" | grep -E '^\s*name\s*=' | head -1 | sed 's/.*= *"//;s/".*//') if [[ -z "$_parsed_name" && -n "$_parsed_url" ]]; then _parsed_name=$(echo "$_parsed_url" | sed 's|https\?://||;s|/.*||;s|^api\.||;s|^www\.||;s|\.com$||;s|\.net$||;s|\.io$||;s|\.ai$||;s|\.org$||;s|\.cn$||' | tr '.' '-') fi } add_profile() { echo "" echo " \033[1m添加新的 API 配置\033[0m" echo " ─────────────────" echo "" echo " 可以直接粘贴包含 URL / Key 的文本(如配置片段、JSON 等)" echo " 粘贴完成后按两次 Enter 结束;或直接按 Enter 进入手动输入模式" echo "" printf " ▸ 粘贴或输入: " local pasted="" line="" empty_count=0 while true; do IFS= read -r line if [[ -z "$line" ]]; then (( empty_count++ )) (( empty_count >= 2 )) && break [[ -z "$pasted" ]] && break pasted+=$'\n' else empty_count=0 pasted+="${line}"$'\n' fi done local pname="" pdesc="" purl="" pkey="" pmodel="" pwire="" if [[ -n "$pasted" ]]; then parse_pasted_text "$pasted" echo "" echo " \033[1m已识别:\033[0m" [[ -n "$_parsed_name" ]] && echo " 名称: $_parsed_name" [[ -n "$_parsed_url" ]] && echo " URL: $_parsed_url" [[ -n "$_parsed_key" ]] && echo " Key: ${_parsed_key:0:12}...${_parsed_key: -4}" [[ -n "$_parsed_model" ]] && echo " 模型: $_parsed_model" [[ -n "$_parsed_wire" ]] && echo " Wire: $_parsed_wire" echo "" pname="$_parsed_name" purl="$_parsed_url" pkey="$_parsed_key" pmodel="$_parsed_model" pwire="$_parsed_wire" fi printf " 配置名称 [${pname:-myapi}]: " read -r input; [[ -n "$input" ]] && pname="$input" pname="${pname:-myapi}" pname="${pname// /-}" if [[ -f "$PROFILES_DIR/$pname.conf" ]]; then printf " ⚠️ \"$pname\" 已存在,覆盖? [y/N]: " read -rk1 yn; echo [[ "$yn" != [yY] ]] && { echo " 已取消"; return 1; } fi printf " 描述 [${pname} API]: " read -r input; pdesc="${input:-${pname} API}" printf " API Base URL [${purl:-}]: " read -r input; [[ -n "$input" ]] && purl="$input" if [[ -z "$purl" ]]; then echo " ❌ URL 不能为空"; return 1 fi if [[ -n "$pkey" ]]; then printf " API Key [${pkey:0:12}...${pkey: -4}]: " else printf " API Key: " fi read -r input; [[ -n "$input" ]] && pkey="$input" if [[ -z "$pkey" ]]; then echo " ❌ API Key 不能为空"; return 1 fi printf " 模型名称 [${pmodel:-gpt-5.4}]: " read -r input; [[ -n "$input" ]] && pmodel="$input" pmodel="${pmodel:-gpt-5.4}" printf " Wire API (responses/chat) [${pwire:-responses}]: " read -r input; [[ -n "$input" ]] && pwire="$input" pwire="${pwire:-responses}" cat > "$PROFILES_DIR/$pname.conf" <<PROFEOF desc="$pdesc" auth_mode="apikey" api_key="$pkey" config='model_provider = "OpenAI" model = "$pmodel" review_model = "$pmodel" model_reasoning_effort = "xhigh" disable_response_storage = true network_access = "enabled" windows_wsl_setup_acknowledged = true model_context_window = 1000000 model_auto_compact_token_limit = 900000 personality = "pragmatic" approvals_reviewer = "user" service_tier = "fast" [model_providers.OpenAI] name = "OpenAI" base_url = "$purl" wire_api = "$pwire" requires_openai_auth = true' PROFEOF echo "" echo " ✅ 配置 \"$pname\" 已保存" printf " 立即切换到此配置? [Y/n]: " read -rk1 sw; echo [[ "$sw" != [nN] ]] && switch_to "$pname" } # ── 删除配置 ───────────────────────────────── rm_profile() { local name="$1" if [[ -z "$name" ]]; then echo "用法: codex-switch rm <配置名>" return 1 fi local file="$PROFILES_DIR/$name.conf" if [[ ! -f "$file" ]]; then echo "❌ 配置不存在: $name" return 1 fi printf " 确认删除 \"$name\"? [y/N]: " read -rk1 yn; echo if [[ "$yn" == [yY] ]]; then rm "$file" echo "✅ 已删除: $name" else echo "已取消" fi } # ── 列出配置 ───────────────────────────────── list_profiles() { local names=($(get_profile_names)) local current current="$(current_profile)" echo "" echo " \033[1m已有配置\033[0m ($PROFILES_DIR/)" echo " ─────────────────" for name in "${names[@]}"; do load_profile "$PROFILES_DIR/$name.conf" local mark="" [[ "$name" == "$current" ]] && mark=" ← 当前" printf " %-14s %s\033[2m%s\033[0m\n" "$name" "$desc" "$mark" done echo "" } # ── 帮助 ───────────────────────────────────── show_help() { local current current="$(current_profile)" echo "" echo " \033[1mcodex-switch\033[0m — Codex API 多配置快速切换" echo "" echo " \033[1m用法:\033[0m" echo " codex-switch 交互式选择(↑↓ 方向键)" echo " codex-switch <名称> 直接切换到指定配置" echo " codex-switch add 添加新配置(支持粘贴自动识别)" echo " codex-switch rm <名称> 删除配置" echo " codex-switch list 列出所有配置" echo " codex-switch help 显示此帮助" echo "" echo " \033[1m当前:\033[0m $current" echo " \033[1m配置目录:\033[0m $PROFILES_DIR/" echo "" } # ── 入口 ───────────────────────────────────── case "${1:-}" in add) add_profile ;; rm|remove) rm_profile "$2" ;; list|ls) list_profiles ;; help|-h|--help) show_help ;; "") arrow_menu ;; *) switch_to "$1" ;; esac MAINSCRIPT chmod +x "$SCRIPT_PATH" echo " ✅ 脚本已安装到 $SCRIPT_PATH" # 创建 profiles 目录 mkdir -p "$PROFILES_DIR" # 创建官方配置(如果不存在) if [[ ! -f "$PROFILES_DIR/official.conf" ]]; then cat > "$PROFILES_DIR/official.conf" << 'OFFCONF' desc="官方 OpenAI (ChatGPT 认证)" auth_mode="chatgpt" config='model = "gpt-5.4" model_reasoning_effort = "xhigh" personality = "pragmatic" approvals_reviewer = "user" service_tier = "fast"' OFFCONF echo " 📄 已创建默认配置: official" fi # 设置 shell alias setup_alias() { local shell_rc="" if [[ -n "$ZSH_VERSION" ]] || [[ "$SHELL" == */zsh ]]; then shell_rc="$HOME/.zshrc" elif [[ -n "$BASH_VERSION" ]] || [[ "$SHELL" == */bash ]]; then shell_rc="$HOME/.bashrc" fi if [[ -z "$shell_rc" ]]; then echo " ⚠️ 无法检测 shell 类型,请手动添加 alias:" echo " alias codex-switch='source ~/.codex/switch-api.sh'" return fi local alias_line="alias codex-switch='source ~/.codex/switch-api.sh'" if grep -qF 'codex-switch' "$shell_rc" 2>/dev/null; then echo " ✅ alias 已存在于 $shell_rc" else echo "" >> "$shell_rc" echo "# codex-switch: Codex API 多配置切换" >> "$shell_rc" echo "$alias_line" >> "$shell_rc" echo " ✅ alias 已添加到 $shell_rc" fi } setup_alias echo "" echo " ┌────────────────────────────────────────┐" echo " │ 安装完成! │" echo " │ │" echo " │ 重新打开终端,或运行: │" echo " │ source ~/.zshrc │" echo " │ │" echo " │ 然后: │" echo " │ codex-switch 交互式选择 │" echo " │ codex-switch add 添加新配置 │" echo " │ codex-switch rm <名> 删除配置 │" echo " │ codex-switch list 查看所有配置 │" echo " │ codex-switch help 帮助信息 │" echo " └────────────────────────────────────────┘" echo "" 网友解答:


--【壹】--:

一直在用 正版 cc 订阅,所以一直没敢用 cc switch 看来我重复造轮子了,现在马上去下。


--【贰】--:

ccswitch可以直接切


--【叁】--:

感谢大佬了。


--【肆】--:

cc switch不是有这个功能嘛


--【伍】--:

我也vibe了一个tui的,自用还是比较舒适的


--【陆】--:

我前几天写了一个类似于ccswitch的命令行切换工具(https://github.com/realihang/cc-switch-linux.git)跟你这个思路差不多 需要的友友欢迎star​!