#!/usr/bin/env bash # Sundy Handbook · Codex / Claude Code / Gemini CLI restore script for macOS / Linux. # # Recommended: # bash <(curl -fsSL https://sundy.tumpai.site/uninstall.sh) # # Options: # --codex Restore only Codex configuration # --claude Restore only Claude Code configuration # --gemini Restore only Gemini CLI configuration # --all Restore Codex, Claude Code and Gemini CLI configuration (default) # --remove-cli Also try to uninstall npm-installed CLIs # -y, --yes Skip confirmation set -e TARGET="${SUNDY_UNINSTALL_TARGET:-all}" ASSUME_YES="${SUNDY_UNINSTALL_YES:-0}" REMOVE_CLI="${SUNDY_REMOVE_CLI:-0}" for arg in "$@"; do case "$arg" in --codex) TARGET="codex" ;; --claude) TARGET="claude" ;; --gemini) TARGET="gemini" ;; --all) TARGET="all" ;; --remove-cli) REMOVE_CLI=1 ;; -y|--yes) ASSUME_YES=1 ;; -h|--help) cat <&2; exit 1 ;; esac done case "$TARGET" in all|codex|claude|gemini) ;; *) echo "Invalid target: $TARGET" >&2; exit 1 ;; esac if [ -t 1 ]; then C_RED=$'\033[31m'; C_GRN=$'\033[32m'; C_YLW=$'\033[33m' C_BLU=$'\033[34m'; C_BLD=$'\033[1m'; C_RST=$'\033[0m' else C_RED=""; C_GRN=""; C_YLW=""; C_BLU=""; C_BLD=""; C_RST="" fi ok() { echo "${C_GRN}OK${C_RST} $*"; } info() { echo "${C_BLU}INFO${C_RST} $*"; } warn() { echo "${C_YLW}WARN${C_RST} $*"; } title() { echo; echo "${C_BLD}${C_BLU}==> $*${C_RST}"; } OS="$(uname -s)" case "$OS" in Darwin) PLATFORM="mac" ;; Linux) PLATFORM="linux" ;; *) echo "Unsupported OS: $OS (Windows uses uninstall.ps1)" >&2; exit 1 ;; esac echo cat <>> $marker >>>" "$rc" 2>/dev/null || return 0 local bak="$rc.sundy-uninstall-bak-$(timestamp)" cp "$rc" "$bak" if [ "$PLATFORM" = "mac" ]; then sed -i '' "/# >>> $marker >>>/,/# <<< $marker <</dev/null || true else sed -i "/# >>> $marker >>>/,/# <<< $marker <</dev/null || true fi ok "已清理 $rc 中的 $marker 配置块(备份: $bak)" } latest_codex_backup() { local dir="$HOME/.codex" [ -d "$dir" ] || return 0 find "$dir" -maxdepth 1 -name 'config.toml.bak-*' -type f -print 2>/dev/null | sort | tail -n 1 } codex_uses_sundy() { local cfg_file="$HOME/.codex/config.toml" [ -f "$cfg_file" ] && grep -Eq 'model_providers\.sundy|sundy-api\.tumpai\.site:2053|Sundy Codex CLI' "$cfg_file" } restore_codex() { title "恢复 Codex" while IFS= read -r rc; do remove_marked_block "$rc" "codex-sundy" done < <(rc_files) unset SUNDY_API_KEY local cfg_dir="$HOME/.codex" local cfg_file="$cfg_dir/config.toml" if [ -f "$cfg_file" ]; then if grep -Eq 'model_providers\.sundy|sundy-api\.tumpai\.site:2053|Sundy Codex CLI' "$cfg_file"; then local removed="$cfg_file.sundy-uninstall-bak-$(timestamp)" cp "$cfg_file" "$removed" local backup backup="$(latest_codex_backup || true)" if [ -n "$backup" ] && [ -f "$backup" ]; then cp "$backup" "$cfg_file" ok "已从备份恢复 Codex 配置: $backup" else rm -f "$cfg_file" ok "已移除 Sundy 生成的 Codex 配置: $cfg_file" fi ok "当前 Codex 配置已备份到: $removed" else info "Codex 配置看起来不是 Sundy 生成,已保留: $cfg_file" fi else info "未发现 Codex 配置: $cfg_file" fi if [ "$REMOVE_CLI" = "1" ]; then if command -v npm >/dev/null 2>&1; then if npm uninstall -g @openai/codex; then ok "已尝试卸载 @openai/codex" elif command -v sudo >/dev/null 2>&1 && sudo npm uninstall -g @openai/codex; then ok "已使用 sudo 尝试卸载 @openai/codex" else warn "卸载 @openai/codex 失败,请手动检查 npm 全局权限" fi else warn "未找到 npm,跳过 Codex CLI 卸载" fi fi } edit_claude_settings() { local file="$1" if command -v node >/dev/null 2>&1; then node - "$file" <<'NODE' const fs = require('fs'); const file = process.argv[2]; let settings = {}; if (fs.existsSync(file)) { const raw = fs.readFileSync(file, 'utf8').trim(); if (raw) settings = JSON.parse(raw); } if (!settings || typeof settings !== 'object' || Array.isArray(settings)) settings = {}; const permissions = settings.permissions; if (permissions && typeof permissions === 'object' && !Array.isArray(permissions)) { if (permissions.defaultMode === 'bypassPermissions') delete permissions.defaultMode; if (permissions.skipDangerousModePermissionPrompt === true) delete permissions.skipDangerousModePermissionPrompt; if (Object.keys(permissions).length === 0) delete settings.permissions; } fs.writeFileSync(file, JSON.stringify(settings, null, 2) + '\n'); NODE return 0 fi if command -v python3 >/dev/null 2>&1; then python3 - "$file" <<'PY' import json import os import sys file = sys.argv[1] settings = {} if os.path.exists(file): raw = open(file, encoding="utf-8").read().strip() if raw: settings = json.loads(raw) if not isinstance(settings, dict): settings = {} permissions = settings.get("permissions") if isinstance(permissions, dict): if permissions.get("defaultMode") == "bypassPermissions": permissions.pop("defaultMode", None) if permissions.get("skipDangerousModePermissionPrompt") is True: permissions.pop("skipDangerousModePermissionPrompt", None) if not permissions: settings.pop("permissions", None) with open(file, "w", encoding="utf-8") as f: json.dump(settings, f, ensure_ascii=False, indent=2) f.write("\n") PY return 0 fi return 1 } restore_claude() { title "恢复 Claude Code" while IFS= read -r rc; do remove_marked_block "$rc" "claude-sundy" done < <(rc_files) if ! codex_uses_sundy; then unset SUNDY_API_KEY fi unset ANTHROPIC_AUTH_TOKEN unset ANTHROPIC_BASE_URL unset ANTHROPIC_DEFAULT_OPUS_MODEL unset ANTHROPIC_DEFAULT_SONNET_MODEL unset ANTHROPIC_DEFAULT_HAIKU_MODEL unset ANTHROPIC_MODEL unset ANTHROPIC_SMALL_FAST_MODEL unset CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY local cache_file="$HOME/.claude/cache/gateway-models.json" if [ -f "$cache_file" ]; then rm -f "$cache_file" ok "已清理 Claude Code 网关模型缓存" fi local settings_file="$HOME/.claude/settings.json" if [ -f "$settings_file" ]; then local bak="$settings_file.sundy-uninstall-bak-$(timestamp)" cp "$settings_file" "$bak" if edit_claude_settings "$settings_file"; then ok "已移除 Claude Code 的 Sundy YOLO 权限设置(备份: $bak)" else warn "未找到 node/python3,无法自动编辑 $settings_file;已保留备份 $bak" fi else info "未发现 Claude Code settings.json" fi if [ "$REMOVE_CLI" = "1" ]; then if command -v npm >/dev/null 2>&1; then npm uninstall -g @anthropic-ai/claude-code || warn "npm 卸载 @anthropic-ai/claude-code 失败或未安装" else warn "未找到 npm,跳过 Claude Code npm 包卸载" fi warn "官方 Claude Code 安装器的本地安装路径可能因版本而异;本脚本不删除整个 ~/.claude,避免误删用户设置。" fi } edit_gemini_settings() { local file="$1" if command -v node >/dev/null 2>&1; then node - "$file" <<'NODE' const fs = require('fs'); const file = process.argv[2]; let settings = {}; if (fs.existsSync(file)) { const raw = fs.readFileSync(file, 'utf8').trim(); if (raw) settings = JSON.parse(raw); } if (!settings || typeof settings !== 'object' || Array.isArray(settings)) settings = {}; const security = settings.security; if (security && typeof security === 'object' && !Array.isArray(security)) { const auth = security.auth; if (auth && typeof auth === 'object' && !Array.isArray(auth)) { if (auth.selectedType === 'gemini-api-key') delete auth.selectedType; if (auth.enforcedType === 'gemini-api-key') delete auth.enforcedType; if (Object.keys(auth).length === 0) delete security.auth; } if (Object.keys(security).length === 0) delete settings.security; } fs.writeFileSync(file, JSON.stringify(settings, null, 2) + '\n'); NODE return 0 fi if command -v python3 >/dev/null 2>&1; then python3 - "$file" <<'PY' import json import os import sys file = sys.argv[1] settings = {} if os.path.exists(file): raw = open(file, encoding="utf-8").read().strip() if raw: settings = json.loads(raw) if not isinstance(settings, dict): settings = {} security = settings.get("security") if isinstance(security, dict): auth = security.get("auth") if isinstance(auth, dict): if auth.get("selectedType") == "gemini-api-key": auth.pop("selectedType", None) if auth.get("enforcedType") == "gemini-api-key": auth.pop("enforcedType", None) if not auth: security.pop("auth", None) if not security: settings.pop("security", None) with open(file, "w", encoding="utf-8") as f: json.dump(settings, f, ensure_ascii=False, indent=2) f.write("\n") PY return 0 fi return 1 } restore_gemini() { title "恢复 Gemini CLI" while IFS= read -r rc; do remove_marked_block "$rc" "gemini-sundy" done < <(rc_files) if ! codex_uses_sundy; then unset SUNDY_API_KEY fi unset GEMINI_API_KEY unset GOOGLE_GEMINI_BASE_URL unset GOOGLE_GENAI_API_VERSION unset GEMINI_API_KEY_AUTH_MECHANISM unset GEMINI_MODEL unset GEMINI_SUNDY_MODE local settings_file="$HOME/.gemini/settings.json" if [ -f "$settings_file" ]; then local bak="$settings_file.sundy-uninstall-bak-$(timestamp)" cp "$settings_file" "$bak" if edit_gemini_settings "$settings_file"; then ok "已移除 Gemini CLI 的 Sundy 认证设置(备份: $bak)" else warn "未找到 node/python3,无法自动编辑 $settings_file;已保留备份 $bak" fi else info "未发现 Gemini CLI settings.json" fi if [ "$REMOVE_CLI" = "1" ]; then if command -v npm >/dev/null 2>&1; then npm uninstall -g @google/gemini-cli || warn "npm 卸载 @google/gemini-cli 失败或未安装" else warn "未找到 npm,跳过 Gemini CLI npm 包卸载" fi fi } case "$TARGET" in all) restore_codex restore_claude restore_gemini ;; codex) restore_codex ;; claude) restore_claude ;; gemini) restore_gemini ;; esac echo cat <