Codex&Claude code&cc-switch安装脚本-zibll综合交流社区-zibll子比主题-zibll子比社区

Codex&Claude code&cc-switch安装脚本

20260614001941798-image-48

 

一个面向中文用户的一键安装脚本,用来检测、安装和更新常用 AI 命令行工具:Codex、Claude Code、cc-switch。

脚本的交互提示、菜单、状态输出、错误提示都使用中文。安装过程会尽量隐藏 npm 自带的英文输出,只在失败时给出日志路径,方便排查。

功能

  • 检测本机是否安装 Node.js 和 npm
  • Node.js 缺失或版本过低时,提示安装长期支持版
  • 检测 Codex、Claude Code、cc-switch 的本机安装状态和版本号
  • 同时获取官方 npm 源与 npmmirror 镜像源的最新版本
  • 安装和更新统一使用 包名@latest
  • 支持三种源策略:自动选择、官方源、大陆镜像源
  • 默认安装到当前用户目录,不需要 sudo
  • 提供 npm 缓存检查和清理入口

管理的工具

工具 npm 安装包 命令
Codex @openai/codex codex
Claude Code @anthropic-ai/claude-code claude
cc-switch @hobeeliu/cc-switch cc-switch

注意:这里的 cc-switch 默认指 npm 命令行版本 @hobeeliu/cc-switch。如果你要安装的是桌面版 CC Switch,需要使用它自己的发布页、Homebrew 或系统安装包。

一键使用

curl -fsSL https://raw.githubusercontent.com/DearLicy/ai-cli-installer/main/install.sh -o ai-cli-installer.sh
chmod +x ai-cli-installer.sh
./ai-cli-installer.sh

中国大陆服务器可直接使用大陆镜像源:

SOURCE_MODE=china ./ai-cli-installer.sh

也可以进入脚本菜单后选择:

5)切换源策略
3)大陆镜像源
 

源策略

策略 说明
auto 官方源可访问时使用官方源,否则回退到大陆镜像源
official 只使用官方 npm 源与 Node.js 源
china 只使用 npmmirror 镜像源

示例:

SOURCE_MODE=china NODE_MAJOR_LTS=24 ./install.sh

可配置变量:

NODE_MAJOR_LTS=24
NPM_REGISTRY_OFFICIAL=https://registry.npmjs.org
NPM_REGISTRY_CHINA=https://registry.npmmirror.com
NODE_MIRROR_OFFICIAL=https://nodejs.org/dist
NODE_MIRROR_CHINA=https://npmmirror.com/mirrors/node
NODE_INSTALL_DIR=$HOME/.local/nodejs
NPM_GLOBAL_PREFIX=$HOME/.npm-global
SOURCE_MODE=auto

为什么不建议使用 sudo

脚本默认把 Node.js 安装到:

~/.local/nodejs
 

npm 全局工具安装到:

~/.npm-global
 

这样普通用户就可以安装和更新,不容易遇到全局 npm 权限混乱,也不会覆盖系统自带的 Node.js。

运行前建议

运行任何远程脚本前,都建议先查看内容:

curl -fsSL https://raw.githubusercontent.com/DearLicy/ai-cli-installer/main/install.sh | less

确认无误后再执行。

无法链接GitHub仓库的服务器请手动创建ai-cli-installer.sh然后填入下方内容

#!/usr/bin/env bash
set -u

# AI CLI 中文安装器
# 支持:OpenAI Codex、Claude Code、cc-switch
# 适用:macOS、Linux、WSL
# 建议:使用普通用户运行,不要使用 sudo 运行本脚本。

NODE_MAJOR_LTS="${NODE_MAJOR_LTS:-24}"
NPM_REGISTRY_OFFICIAL="${NPM_REGISTRY_OFFICIAL:-https://registry.npmjs.org}"
NPM_REGISTRY_CHINA="${NPM_REGISTRY_CHINA:-https://registry.npmmirror.com}"
NODE_MIRROR_OFFICIAL="${NODE_MIRROR_OFFICIAL:-https://nodejs.org/dist}"
NODE_MIRROR_CHINA="${NODE_MIRROR_CHINA:-https://npmmirror.com/mirrors/node}"
NODE_INSTALL_DIR="${NODE_INSTALL_DIR:-$HOME/.local/nodejs}"
NPM_GLOBAL_PREFIX="${NPM_GLOBAL_PREFIX:-$HOME/.npm-global}"
SOURCE_MODE="${SOURCE_MODE:-auto}"  # 可选:auto、official、china

if [ -t 1 ]; then
  BOLD="\033[1m"; RED="\033[31m"; GREEN="\033[32m"; YELLOW="\033[33m"; BLUE="\033[34m"; RESET="\033[0m"
else
  BOLD=""; RED=""; GREEN=""; YELLOW=""; BLUE=""; RESET=""
fi

say() { printf "%b\n" "$*"; }
hr() { printf '%*s\n' "${COLUMNS:-80}" '' | tr ' ' '-'; }
has_cmd() { command -v "$1" >/dev/null 2>&1; }

source_label() {
  case "$SOURCE_MODE" in
    auto) echo "自动选择" ;;
    official) echo "官方源" ;;
    china) echo "大陆镜像源" ;;
    *) echo "未知" ;;
  esac
}

pkg_encode() {
  printf '%s' "$1" | sed 's#/#%2f#g'
}

curl_get() {
  curl -fsSL --compressed --connect-timeout 8 --max-time 30 "$1" 2>/dev/null
}

require_curl() {
  if ! has_cmd curl; then
    say "${RED}缺少 curl,请先安装 curl 后重试。${RESET}"
    say "Debian 或 Ubuntu:sudo apt-get update && sudo apt-get install -y curl ca-certificates"
    say "CentOS 或 RHEL:sudo yum install -y curl ca-certificates"
    say "macOS 通常已经自带 curl。"
    return 1
  fi
}

registry_latest() {
  local pkg="$1" reg="${2%/}" encoded json ver
  encoded="$(pkg_encode "$pkg")"
  json="$(curl_get "$reg/$encoded/latest" || true)"
  ver="$(printf '%s' "$json" | tr -d '\n' | sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n 1)"
  [ -n "$ver" ] && printf '%s\n' "$ver"
}

command_version() {
  local cmd="$1" out
  if has_cmd "$cmd"; then
    out="$($cmd --version 2>/dev/null | head -n 1 || true)"
    [ -n "$out" ] && printf '%s\n' "$out" || printf '已安装,但无法读取版本号\n'
  fi
}

npm_pkg_version() {
  local pkg="$1"
  has_cmd npm || return 0
  has_cmd node || return 0
  npm ls -g "$pkg" --depth=0 --json 2>/dev/null | node -e '
let s = "";
process.stdin.on("data", d => s += d);
process.stdin.on("end", () => {
  try {
    const pkg = process.argv[1];
    const j = JSON.parse(s || "{}");
    const v = j.dependencies && j.dependencies[pkg] && j.dependencies[pkg].version;
    if (v) console.log(v);
  } catch (_) {}
});' "$pkg"
}

node_major() {
  has_cmd node || return 1
  node -p 'Number(process.versions.node.split(".")[0])' 2>/dev/null
}

node_ok() {
  local major
  major="$(node_major 2>/dev/null || true)"
  [ -n "$major" ] && [ "$major" -ge 18 ] && has_cmd npm
}

choose_node_mirror() {
  case "$SOURCE_MODE" in
    official) printf '%s\n' "$NODE_MIRROR_OFFICIAL" ;;
    china) printf '%s\n' "$NODE_MIRROR_CHINA" ;;
    *)
      if curl_get "$NODE_MIRROR_OFFICIAL/index.json" >/dev/null; then
        printf '%s\n' "$NODE_MIRROR_OFFICIAL"
      else
        printf '%s\n' "$NODE_MIRROR_CHINA"
      fi
      ;;
  esac
}

choose_npm_registry() {
  local pkg="$1"
  case "$SOURCE_MODE" in
    official) printf '%s\n' "$NPM_REGISTRY_OFFICIAL" ;;
    china) printf '%s\n' "$NPM_REGISTRY_CHINA" ;;
    *)
      if [ -n "$(registry_latest "$pkg" "$NPM_REGISTRY_OFFICIAL")" ]; then
        printf '%s\n' "$NPM_REGISTRY_OFFICIAL"
      else
        printf '%s\n' "$NPM_REGISTRY_CHINA"
      fi
      ;;
  esac
}

platform_name() {
  local os arch
  os="$(uname -s)"
  arch="$(uname -m)"
  case "$os:$arch" in
    Linux:x86_64|Linux:amd64) echo "linux-x64" ;;
    Linux:aarch64|Linux:arm64) echo "linux-arm64" ;;
    Darwin:x86_64) echo "darwin-x64" ;;
    Darwin:arm64|Darwin:aarch64) echo "darwin-arm64" ;;
    *) return 1 ;;
  esac
}

latest_node_version() {
  local mirror="$1" json ver
  json="$(curl_get "${mirror%/}/index.json" || true)"
  ver="$(printf '%s' "$json" | grep -o "\"version\":\"v${NODE_MAJOR_LTS}\.[^\"]*\"" | head -n 1 | cut -d '"' -f 4)"
  [ -n "$ver" ] && printf '%s\n' "$ver"
}

append_path_once() {
  local rc="$1" line="$2"
  [ -f "$rc" ] || touch "$rc" 2>/dev/null || return 0
  grep -Fqs "$line" "$rc" 2>/dev/null || printf '\n%s\n' "$line" >> "$rc"
}

persist_paths() {
  local rc_line1 rc_line2 shell_name rc
  rc_line1="export PATH=\"$NODE_INSTALL_DIR/current/bin:\$PATH\""
  rc_line2="export PATH=\"$NPM_GLOBAL_PREFIX/bin:\$PATH\""

  shell_name="$(basename "${SHELL:-bash}")"
  case "$shell_name" in
    zsh) rc="$HOME/.zshrc" ;;
    fish) rc="" ;;
    *) rc="$HOME/.bashrc" ;;
  esac

  if [ -n "${rc:-}" ]; then
    append_path_once "$rc" "# 由 ai-cli-installer 添加"
    append_path_once "$rc" "$rc_line1"
    append_path_once "$rc" "$rc_line2"
    say "${GREEN}已把命令路径写入:$rc${RESET}"
  else
    say "${YELLOW}检测到 fish 终端,请手动添加下面这行配置:${RESET}"
    say "set -gx PATH $NODE_INSTALL_DIR/current/bin $NPM_GLOBAL_PREFIX/bin \$PATH"
  fi
}

install_node_lts() {
  local mirror platform version tmp file url extracted
  require_curl || return 1

  mirror="$(choose_node_mirror)"
  platform="$(platform_name || true)"
  if [ -z "$platform" ]; then
    say "${RED}暂不支持当前系统或处理器架构:$(uname -s) $(uname -m)${RESET}"
    return 1
  fi

  version="$(latest_node_version "$mirror" || true)"
  if [ -z "$version" ]; then
    say "${RED}无法获取 Node.js v$NODE_MAJOR_LTS 的最新版本。${RESET}"
    say "你可以切换为官方源或大陆镜像源后重试。"
    return 1
  fi

  say "${BLUE}准备安装 Node.js $version,系统包:$platform${RESET}"
  say "下载来源:$mirror"
  tmp="$(mktemp -d)"
  file="node-${version}-${platform}.tar.xz"
  url="${mirror%/}/${version}/${file}"

  if ! curl -fL --connect-timeout 10 --max-time 300 "$url" -o "$tmp/$file" >/dev/null 2>&1; then
    say "${RED}Node.js 下载失败。${RESET}"
    say "下载地址:$url"
    rm -rf "$tmp"
    return 1
  fi

  mkdir -p "$NODE_INSTALL_DIR"
  if ! tar -xf "$tmp/$file" -C "$NODE_INSTALL_DIR" >/dev/null 2>&1; then
    say "${RED}Node.js 解压失败。请确认系统支持解压 .xz 文件。${RESET}"
    say "Debian 或 Ubuntu 可执行:sudo apt-get install -y xz-utils"
    rm -rf "$tmp"
    return 1
  fi

  extracted="$NODE_INSTALL_DIR/node-${version}-${platform}"
  ln -sfn "$extracted" "$NODE_INSTALL_DIR/current"
  export PATH="$NODE_INSTALL_DIR/current/bin:$NPM_GLOBAL_PREFIX/bin:$PATH"
  persist_paths
  rm -rf "$tmp"

  say "${GREEN}Node.js 安装完成:$(node -v),npm:$(npm -v)${RESET}"
}

ensure_node() {
  if node_ok; then
    return 0
  fi

  if has_cmd node; then
    say "${YELLOW}检测到 Node.js $(node -v 2>/dev/null),但版本低于 18,或 npm 不可用。${RESET}"
  else
    say "${YELLOW}未检测到 Node.js 或 npm。安装这些命令行工具需要 Node.js 环境。${RESET}"
  fi

  printf "是否安装 Node.js v%s 长期支持版到当前用户目录?[Y/n] " "$NODE_MAJOR_LTS"
  read -r yn
  case "${yn:-Y}" in
    y|Y|yes|YES) install_node_lts ;;
    *) say "${RED}已取消安装 Node.js。${RESET}"; return 1 ;;
  esac
}

ensure_npm_prefix() {
  mkdir -p "$NPM_GLOBAL_PREFIX/bin"
  npm config set prefix "$NPM_GLOBAL_PREFIX" >/dev/null 2>&1
  export PATH="$NPM_GLOBAL_PREFIX/bin:$PATH"
  persist_paths
}

tool_meta() {
  case "$1" in
    codex) echo "Codex|@openai/codex|codex" ;;
    claude) echo "Claude Code|@anthropic-ai/claude-code|claude" ;;
    ccswitch) echo "cc-switch|@hobeeliu/cc-switch|cc-switch" ;;
    *) return 1 ;;
  esac
}

show_one_status() {
  local key="$1" meta name pkg cmd npmver cmdver off cn note
  meta="$(tool_meta "$key")"
  IFS='|' read -r name pkg cmd <<EOF_META
$meta
EOF_META

  npmver="$(npm_pkg_version "$pkg" | head -n 1 || true)"
  cmdver="$(command_version "$cmd" | head -n 1 || true)"
  off="$(registry_latest "$pkg" "$NPM_REGISTRY_OFFICIAL" || true)"
  cn="$(registry_latest "$pkg" "$NPM_REGISTRY_CHINA" || true)"
  note=""
  if [ -n "$off" ] && [ -n "$cn" ] && [ "$off" != "$cn" ]; then
    note=" ${YELLOW}⚠ 大陆镜像源与官方源版本不同:官方 $off / 镜像 $cn${RESET}"
  fi

  printf "%-14s " "$name"
  if [ -n "$npmver" ]; then
    printf "本机版本:%-12s " "$npmver"
  elif [ -n "$cmdver" ]; then
    printf "本机命令版本:%-18s " "$cmdver"
  else
    printf "%-24s " "本机状态:未安装"
  fi
  printf "官方最新:%-12s 镜像最新:%-12s%b\n" "${off:-无法访问}" "${cn:-无法访问}" "$note"
}

show_status() {
  require_curl || true
  hr
  say "${BOLD}AI 命令行工具安装器${RESET}  当前源策略:${BOLD}$(source_label)${RESET}"
  if has_cmd node; then
    say "Node.js:$(node -v 2>/dev/null)  npm:$(npm -v 2>/dev/null || echo 未安装)"
  else
    say "Node.js:未安装  npm:未安装"
  fi
  say "npm 全局安装目录:${NPM_GLOBAL_PREFIX}"
  hr
  show_one_status codex
  show_one_status claude
  show_one_status ccswitch
  hr
}

run_npm_install() {
  local pkg="$1" reg="$2" log
  log="$(mktemp -t ai-cli-npm.XXXXXX.log)"
  if npm install -g "${pkg}@latest" \
    --registry "$reg" \
    --include=optional \
    --ignore-scripts=false \
    --prefer-online \
    --silent >"$log" 2>&1; then
    rm -f "$log"
    return 0
  fi

  say "${RED}安装或更新失败。${RESET}"
  say "详细日志已保存到:$log"
  say "如果你在中国大陆服务器上运行,可以切换为大陆镜像源后重试。"
  return 1
}

install_or_update() {
  local key="$1" meta name pkg cmd reg latest
  meta="$(tool_meta "$key")"
  IFS='|' read -r name pkg cmd <<EOF_META
$meta
EOF_META

  ensure_node || return 1
  ensure_npm_prefix

  reg="$(choose_npm_registry "$pkg")"
  latest="$(registry_latest "$pkg" "$reg" || true)"
  say "${BLUE}准备安装或更新:$name${RESET}"
  say "安装包:${pkg}@latest"
  say "使用源:$reg"
  say "最新版本:${latest:-暂未读取到,将交给 npm 自动解析}"

  if run_npm_install "$pkg" "$reg"; then
    hash -r 2>/dev/null || true
    say "${GREEN}$name 已完成。当前版本:$(command_version "$cmd" | head -n 1 || echo 未读取到)${RESET}"
  fi
}

install_all() {
  install_or_update codex || true
  install_or_update claude || true
  install_or_update ccswitch || true
}

switch_source_mode() {
  say "请选择源策略:"
  say "1)自动选择:官方源可访问就使用官方源,否则使用大陆镜像源"
  say "2)官方源:只使用官方 npm 与 Node.js 源"
  say "3)大陆镜像源:只使用 npmmirror 镜像源"
  printf "请输入序号,当前为%s:" "$(source_label)"
  read -r s
  case "$s" in
    1) SOURCE_MODE="auto"; say "已切换为:自动选择" ;;
    2) SOURCE_MODE="official"; say "已切换为:官方源" ;;
    3) SOURCE_MODE="china"; say "已切换为:大陆镜像源" ;;
    *) say "未修改源策略。" ;;
  esac
}

clean_npm_cache() {
  ensure_node || return 1
  npm cache verify >/dev/null 2>&1 || true
  say "npm 缓存检查完成。"
  printf "是否强制清理 npm 缓存?一般只有安装到旧版本或缓存异常时才需要。[y/N] "
  read -r yn
  case "${yn:-N}" in
    y|Y|yes|YES) npm cache clean --force >/dev/null 2>&1 && say "npm 缓存已清理。" ;;
    *) say "已跳过强制清理。" ;;
  esac
}

main_menu() {
  while true; do
    show_status
    cat <<MENU
请选择操作:
  1)安装或更新 Codex
  2)安装或更新 Claude Code
  3)安装或更新 cc-switch
  4)一键安装或更新全部
  5)切换源策略
  6)安装或修复 Node.js v${NODE_MAJOR_LTS} 长期支持版
  7)检查或清理 npm 缓存
  r)刷新状态
  q)退出
MENU
    printf "请输入:"
    read -r ans
    case "$ans" in
      1) install_or_update codex ;;
      2) install_or_update claude ;;
      3) install_or_update ccswitch ;;
      4) install_all ;;
      5) switch_source_mode ;;
      6) install_node_lts ;;
      7) clean_npm_cache ;;
      r|R) : ;;
      q|Q) say "已退出。"; exit 0 ;;
      *) say "输入无效,请重新选择。" ;;
    esac
    printf "\n按回车继续……"
    read -r _
  done
}

main_menu

 

致谢

本项目的初始脚本设计、文档结构和开源仓库整理过程由 ChatGPT 协助完成。最终代码请以维护者审查和实际测试结果为准。

许可证

MIT

 

请登录后发表评论

    • DearLicy的头像-zibll子比社区代理会员DearLicy徽章-备受瞩目-zibll子比社区等级-LV10-zibll子比社区作者0