一个面向中文用户的一键安装脚本,用来检测、安装和更新常用 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
脚本默认把 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







