diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml deleted file mode 100644 index 51ca8bb..0000000 --- a/.github/workflows/pr-check.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: PR Check - -on: - pull_request: - # branches: - types: [opened, synchronize, reopened] - -jobs: - check: - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: "1.25.1" - - - name: Cache Go modules - uses: actions/cache@v3 - with: - path: | - ~/.cache/go-build - ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - - name: Download dependencies - run: make mod - - - name: Run tests - run: make test - - - name: Run checks - run: make check - - - name: Check for breaking changes - run: | - # Check if there are changes to internal packages or cmd - if git diff --name-only origin/main...HEAD | grep -E "^(internal/|cmd/)"; then - echo "⚠️ Potential breaking changes detected in internal/ or cmd/ directories" - echo "Please review these changes carefully:" - git diff --name-only origin/main...HEAD | grep -E "^(internal/|cmd/)" - fi - - - name: Check commit messages - run: | - # Get all commits in the PR - commits=$(git log --format=%s origin/main..HEAD) - - # Check if commits follow conventional commit format - for commit in $commits; do - if [[ ! "$commit" =~ ^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?: .+ ]]; then - echo "⚠️ Commit message does not follow conventional commit format: $commit" - echo "Expected format: [optional scope]: " - echo "Types: feat, fix, docs, style, refactor, test, chore" - fi - done - - - name: Build - run: make build diff --git a/README.md b/README.md index 0bcd221..de2ca4b 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ **A unified CLI tool for managing multiple AI tools and model providers** -English | [简体中文](README_CN.md) +English | [简体中文](readme_cn.md) @@ -28,6 +28,12 @@ AIM (AI Model Manager) is a powerful command-line tool designed to simplify the ⚠️ **This project is currently in the design phase.** Core functionality is being implemented. + +## 🐛 Known Issues + +- **`aim setup install codex`** - This command is still under development and actively being updated. Not recommended for production use at this time. +- **`aim setup install cc`** - This command is available and functional, but please note that once configured, it may cause `aim run cc` to fail. We are actively working on resolving this issue. + ## 💭 Foreword After many years in software development, the pace of AI development in recent years has consistently exceeded my imagination. I never thought years ago that AI would so profoundly change our development methods and lifestyles. @@ -42,12 +48,12 @@ Welcome to experience this tool completed through human "manual labor" and AI "m **One-line install:** ```bash -curl -fsSL https://raw.githubusercontent.com/fakecore/aim/main/scripts/setup-tool.sh | bash -s -- --version v1.1.0-rc1|curl -fsSL https://raw.githubusercontent.com/fakecore/aim/main/scripts/setup-tool.sh | bash -s -- --version v1.1.0-rc1 +curl -fsSL https://raw.githubusercontent.com/fakecore/aim/main/scripts/setup-tool.sh | bash ``` **User installation (no sudo):** ```bash -curl -fsSL https://raw.githubusercontent.com/fakecore/aim/main/scripts/setup-tool.sh | bash -s -- --version v1.1.0-rc1|curl -fsSL https://raw.githubusercontent.com/fakecore/aim/main/scripts/setup-tool.sh | bash -s -- --version v1.1.0-rc1 --user +curl -fsSL https://raw.githubusercontent.com/fakecore/aim/main/scripts/setup-tool.sh | bash -s -- --user ``` ### Basic Usage @@ -85,9 +91,9 @@ aim run codex --key another-key ## 📚 Documentation -- **[CI/CD Complete Guide](docs/cicd/CI_CD_EN.md)** - Continuous integration and deployment reference -- **[Local Development Setup](docs/development-guide/LOCAL_DEV_EN.md)** - Local development environment configuration guide -- **[TUI Interface Design](docs/tui-interface/TUI_DESIGN_EN.md)** - Terminal user interface design documentation +- **[CI/CD Complete Guide](docs/cicd/ci_cd.md)** - Continuous integration and deployment reference +- **[Local Development Setup](docs/development-guide/local_dev.md)** - Local development environment configuration guide +- **[TUI Interface Design](docs/tui-interface/tui_design.md)** - Terminal user interface design documentation ## 🎯 Supported Providers @@ -150,7 +156,7 @@ source test/local-dev-setup/dev-setup.fish # Fish ## 🤝 Contributing -We welcome contributions! Please see our [Development Guide](docs/development-guide/LOCAL_DEV_EN.md) for details. +We welcome contributions! Please see our [Development Guide](docs/development-guide/local_dev.md) for details. 1. **Fork this repository** 2. **Create a feature branch** (`git checkout -b feature/amazing-feature`) diff --git a/README_CN.md b/README_CN.md index 26c01c0..a1f1ca4 100644 --- a/README_CN.md +++ b/README_CN.md @@ -28,6 +28,12 @@ AIM (AI Model Manager) 是一个强大的命令行工具,旨在简化多个 AI ⚠️ **本项目目前处于设计阶段。**核心功能正在实现中。 + +## 🐛 已知问题 + +- **`aim setup install codex`** - 此命令仍在开发中,正在积极更新。目前不建议在生产环境中使用。 +- **`aim setup install cc`** - 此命令可用且功能正常,但请注意,一旦配置完成,可能会导致 `aim run cc` 失败。我们正在积极解决此问题。 + ## 💭 写在前面 从事软件开发多年,这两年 AI 的发展速度一直超出我的想象。几年前从未想过 AI 会如此深刻地改变我们的开发方式和生活方式。 @@ -42,13 +48,13 @@ AIM (AI Model Manager) 是一个强大的命令行工具,旨在简化多个 AI **一行命令安装:** ```bash -curl -fsSL https://raw.githubusercontent.com/fakecore/aim/main/scripts/setup-tool.sh | bash -s -- --version v1.1.0-rc1|curl -fsSL https://raw.githubusercontent.com/fakecore/aim/main/scripts/setup-tool.sh | bash -s -- --version v1.1.0-rc1| bash +curl -fsSL https://raw.githubusercontent.com/fakecore/aim/main/scripts/setup-tool.sh | bash ``` **用户目录安装(无需 sudo):** ```bash -curl -fsSL https://raw.githubusercontent.com/fakecore/aim/main/scripts/setup-tool.sh | bash -s -- --version v1.1.0-rc1|curl -fsSL https://raw.githubusercontent.com/fakecore/aim/main/scripts/setup-tool.sh | bash -s -- --version v1.1.0-rc1| bash -s -- --user +curl -fsSL https://raw.githubusercontent.com/fakecore/aim/main/scripts/setup-tool.sh | bash -s -- --user ``` ### 基础用法 @@ -86,9 +92,9 @@ aim run codex --key another-key ## 📚 文档 -- **[CI/CD 完整指南](docs/cicd/CI_CD.md)** - 持续集成和部署的权威参考 -- **[本地开发环境设置](docs/development-guide/LOCAL_DEV.md)** - 本地开发环境配置指南 -- **[TUI 界面设计](docs/tui-interface/TUI_DESIGN.md)** - 终端用户界面设计文档 +- **[CI/CD 完整指南](docs/cicd/ci_cd_cn.md)** - 持续集成和部署的权威参考 +- **[本地开发环境设置](docs/development-guide/local_dev_cn.md)** - 本地开发环境配置指南 +- **[TUI 界面设计](docs/tui-interface/tui_design_cn.md)** - 终端用户界面设计文档 ## 🎯 支持的提供商 @@ -151,7 +157,7 @@ source test/local-dev-setup/dev-setup.fish # Fish ## 🤝 贡献 -我们欢迎贡献!请参阅我们的[开发指南](docs/development-guide/LOCAL_DEV.md)了解详情。 +我们欢迎贡献!请参阅我们的[开发指南](docs/development-guide/local_dev_cn.md)了解详情。 1. **Fork 本仓库** 2. **创建功能分支** (`git checkout -b feature/amazing-feature`) @@ -168,7 +174,7 @@ source test/local-dev-setup/dev-setup.fish # Fish - **问题反馈**:[GitHub Issues](https://github.com/fakecore/aim/issues) - **讨论区**:[GitHub Discussions](https://github.com/fakecore/aim/discussions) -- **文档**:[docs/](docs/) +- **文档**:[docs/](docs)
diff --git a/VERSION b/VERSION index 8376431..9084fa2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1.0-rc1 +1.1.0 diff --git a/configs/default.yaml b/configs/default.yaml index 330b4df..ee43f01 100644 --- a/configs/default.yaml +++ b/configs/default.yaml @@ -10,6 +10,8 @@ settings: default_provider: deepseek # Default provider timeout: 60000 # Default timeout (milliseconds) +keys: + # Global Provider Configuration (OpenAI Compatible Endpoints) providers: deepseek: @@ -29,36 +31,14 @@ providers: kimi: base_url: https://api.moonshot.cn/v1 - model: moonshot-v1-8k + model: kimi-k2-turbo-preview timeout: 60000 qwen: base_url: https://dashscope.aliyuncs.com/compatible-mode/v1 - model: qwen-plus + model: qwen3-max timeout: 60000 -# API Key Management -keys: - deepseek-key: - provider: deepseek - key: ${DEEPSEEK_API_KEY} - description: "DeepSeek API Key" - - glm-key: - provider: glm - key: ${GLM_API_KEY} - description: "GLM API Key" - - kimi-key: - provider: kimi - key: ${KIMI_API_KEY} - description: "KIMI API Key" - - qwen-key: - provider: qwen - key: ${QWEN_API_KEY} - description: "Qwen API Key" - # Tool Configuration tools: claude-code: diff --git a/docs/cicd/CI_CD.md b/docs/cicd/CI_CD.md index 3a08078..312ae20 100644 --- a/docs/cicd/CI_CD.md +++ b/docs/cicd/CI_CD.md @@ -1,81 +1,81 @@ -# AIM 项目 CI/CD 精简指南 +# AIM Project CI/CD Concise Guide -本指南为 AIM 项目的单人开发者提供快速、实用的 CI/CD 操作指南。 +This guide provides a quick and practical CI/CD operation guide for solo developers of the AIM project. -## 🚀 快速开始 +## 🚀 Quick Start -### 环境检查 +### Environment Check ```bash -# 检查基础环境 +# Check basic environment git config --list | grep -E "(user\.name|user\.email)" go version make help ``` -### 常用命令速查 +### Common Commands Quick Reference -| 操作 | 命令 | -|------|------| -| 检查当前版本 | `make version` | -| 创建 RC 版本 | `make rc` | -| 创建正式版本 | `make release` | -| 运行测试 | `make test` | -| 代码检查 | `make check` | -| 构建所有平台 | `make build-all` | +| Operation | Command | +|-----------|---------| +| Check current version | `make version` | +| Create RC version | `make rc` | +| Create official release | `make release` | +| Run tests | `make test` | +| Code checks | `make check` | +| Build for all platforms | `make build-all` | -## 📦 版本发布流程 +## 📦 Version Release Process -### 1. 修复发布 (Patch Release) -用于向后兼容的问题修正。 +### 1. Patch Release +Used for backward-compatible bug fixes. ```bash -# 1. 创建 hotfix 分支 +# 1. Create hotfix branch git checkout -b hotfix/v0.1.0 -# 2. 修复问题并提交 +# 2. Fix issues and commit git add . git commit -m "fix: resolve crash when loading invalid config" -# 3. 运行测试 +# 3. Run tests make test -# 4. 合并到 main +# 4. Merge to main git checkout main git merge --no-ff hotfix/v0.1.0 -# 5. 创建版本标签 +# 5. Create version tag git tag -a v0.1.0 -m "Release v0.1.0 Bug fixes: - Fix crash when loading invalid config - Improve error handling for missing files" -# 6. 推送标签触发自动发布 +# 6. Push tag to trigger automatic release git push origin v0.1.0 -# 7. 删除 hotfix 分支 +# 7. Delete hotfix branch git branch -d hotfix/v0.1.0 ``` -### 2. 功能发布 (Minor Release) -用于添加向后兼容的新功能。 +### 2. Minor Release +Used for adding backward-compatible new features. ```bash -# 1. 创建 release 分支 +# 1. Create release branch git checkout -b release/v0.2.0 -# 2. 更新版本信息和文档 -# ... 进行必要的更新 ... +# 2. Update version information and documentation +# ... make necessary updates ... -# 3. 提交更改 +# 3. Commit changes git add . git commit -m "feat: prepare for v0.2.0 release" -# 4. 创建 RC 版本(可选) +# 4. Create RC version (optional) git tag -a v0.2.0-rc.1 -m "Release candidate v0.2.0-rc.1" git push origin v0.2.0-rc.1 -# 5. 测试 RC 版本后,创建正式版本 +# 5. After testing RC version, create official release git tag -a v0.2.0 -m "Release v0.2.0 New features: @@ -83,38 +83,38 @@ New features: - Add configuration validation - Add interactive setup wizard" -# 6. 合并到 main +# 6. Merge to main git checkout main git merge --no-ff release/v0.2.0 -# 7. 推送标签触发自动发布 +# 7. Push tag to trigger automatic release git push origin v0.2.0 -# 8. 删除 release 分支 +# 8. Delete release branch git branch -d release/v0.2.0 ``` -### 3. 主要发布 (Major Release) -用于包含不兼容变更的重要版本更新。 +### 3. Major Release +Used for significant version updates that include incompatible changes. ```bash -# 1. 创建 release 分支 +# 1. Create release branch git checkout -b release/v1.0.0 -# 2. 进行重大变更和文档更新 -# ... 进行代码修改和文档更新 ... +# 2. Make major changes and update documentation +# ... make code changes and documentation updates ... -# 3. 提交更改 +# 3. Commit changes git add . git commit -m "feat!: prepare for v1.0.0 major release BREAKING CHANGE: Update CLI command structure" -# 4. 创建 RC 版本 +# 4. Create RC version git tag -a v1.0.0-rc.1 -m "Release candidate v1.0.0-rc.1" git push origin v1.0.0-rc.1 -# 5. 测试后创建正式版本 +# 5. Create official release after testing git tag -a v1.0.0 -m "Release v1.0.0 Major changes: @@ -127,169 +127,169 @@ Breaking Changes: - Configuration format updated - Deprecated features removed" -# 6. 合并到 main +# 6. Merge to main git checkout main git merge --no-ff release/v1.0.0 -# 7. 推送标签触发自动发布 +# 7. Push tag to trigger automatic release git push origin v1.0.0 -# 8. 删除 release 分支 +# 8. Delete release branch git branch -d release/v1.0.0 ``` -## 🔧 构建触发条件 +## 🔧 Build Trigger Conditions -### 自动触发场景 +### Automatic Trigger Scenarios -| 触发条件 | 工作流 | 执行时间 | 主要操作 | -|---------|--------|----------|----------| -| 推送到 main 分支 | CI | ~15 分钟 | 测试、检查、构建 | -| 推送版本标签 (v*.*.*) | Release | ~30 分钟 | 多平台构建、创建 Release | -| 推送 RC 标签 (v*.*.*-rc.*) | Pre-release | ~30 分钟 | 多平台构建、创建预发布 | -| 创建/更新 PR | PR Check | ~15 分钟 | 测试、检查、变更分析 | +| Trigger Condition | Workflow | Execution Time | Main Operations | +|-------------------|----------|----------------|-----------------| +| Push to main branch | CI | ~15 minutes | Tests, checks, build | +| Push version tag (v*.*.*) | Release | ~30 minutes | Multi-platform build, create Release | +| Push RC tag (v*.*.*-rc.*) | Pre-release | ~30 minutes | Multi-platform build, create pre-release | +| Create/update PR | PR Check | ~15 minutes | Tests, checks, change analysis | -### 避免不必要构建 +### Avoiding Unnecessary Builds ```bash -# 1. 使用 Draft PR +# 1. Use Draft PR gh pr create --draft --title "WIP: Feature" --body "Work in progress" -# 2. 跳过 CI +# 2. Skip CI git commit -m "docs: update README [ci skip]" -# 3. 批量提交 +# 3. Batch commits git rebase -i HEAD~3 ``` -## 📋 发布前检查清单 +## 📋 Pre-release Checklist -### 代码质量 -- [ ] 所有代码检查通过 (`make check`) -- [ ] 所有单元测试通过 (`make test`) -- [ ] 测试覆盖率符合要求 (>80%) -- [ ] 无安全漏洞 -- [ ] 代码格式符合规范 +### Code Quality +- [ ] All code checks pass (`make check`) +- [ ] All unit tests pass (`make test`) +- [ ] Test coverage meets requirements (>80%) +- [ ] No security vulnerabilities +- [ ] Code formatting follows standards -### 功能验证 -- [ ] 新功能按预期工作 -- [ ] 现有功能未受影响 -- [ ] 边界情况测试通过 -- [ ] 错误处理正确 -- [ ] 性能符合预期 +### Functionality Verification +- [ ] New features work as expected +- [ ] Existing features are not affected +- [ ] Edge case tests pass +- [ ] Error handling is correct +- [ ] Performance meets expectations -### 文档更新 -- [ ] README.md 更新 -- [ ] CHANGELOG.md 更新 -- [ ] API 文档更新(如需要) -- [ ] 用户指南更新(如需要) -- [ ] 迁移指南(如需要) +### Documentation Updates +- [ ] README.md updated +- [ ] CHANGELOG.md updated +- [ ] API documentation updated (if needed) +- [ ] User guide updated (if needed) +- [ ] Migration guide (if needed) -## 🛠️ 常用命令参考 +## 🛠️ Common Commands Reference -### Make 命令 +### Make Commands ```bash -# 构建相关 -make build # 构建当前平台 -make build-all # 构建所有平台 -make clean # 清理构建产物 - -# 测试相关 -make test # 运行测试 -make coverage # 生成覆盖率报告 -make check # 运行所有检查 - -# 代码质量 -make fmt # 格式化代码 -make vet # 静态分析 -make lint # 代码审查 - -# 依赖管理 -make mod # 更新依赖 - -# 开发环境 -make dev-setup # 设置开发环境 -make dev-install # 安装到开发环境 -make dev-test # 测试开发环境 +# Build related +make build # Build for current platform +make build-all # Build for all platforms +make clean # Clean build artifacts + +# Test related +make test # Run tests +make coverage # Generate coverage report +make check # Run all checks + +# Code quality +make fmt # Format code +make vet # Static analysis +make lint # Code review + +# Dependency management +make mod # Update dependencies + +# Development environment +make dev-setup # Set up development environment +make dev-install # Install to development environment +make dev-test # Test development environment ``` -## 🔍 故障排除 +## 🔍 Troubleshooting -### 发布失败 +### Release Failures -**问题**: GitHub Actions 工作流失败 +**Problem**: GitHub Actions workflow fails -**解决方案**: +**Solution**: ```bash -# 1. 修复代码问题 -# ... 修复代码 ... +# 1. Fix code issues +# ... fix code ... -# 2. 重新运行测试 +# 2. Rerun tests make test -# 3. 删除失败的 Release(如果已创建) -# 在 GitHub 页面上手动删除 +# 3. Delete failed Release (if created) +# Manually delete on GitHub page -# 4. 删除错误的标签 +# 4. Delete incorrect tag git tag -d v0.1.0 git push origin :refs/tags/v0.1.0 -# 5. 重新创建标签 +# 5. Recreate tag git tag -a v0.1.0 -m "Release v0.1.0 (fixed)" git push origin v0.1.0 ``` -### 版本号冲突 +### Version Number Conflicts -**问题**: 版本号已存在 +**Problem**: Version number already exists -**解决方案**: +**Solution**: ```bash -# 1. 列出所有标签 +# 1. List all tags git tag -l -# 2. 删除错误的本地标签 +# 2. Delete incorrect local tag git tag -d v0.1.0 -# 3. 删除错误的远程标签 +# 3. Delete incorrect remote tag git push origin :refs/tags/v0.1.0 -# 4. 创建正确的标签 +# 4. Create correct tag git tag -a v0.1.2 -m "Release v0.1.2" git push origin v0.1.2 ``` -### 构建产物不完整 +### Incomplete Build Artifacts -**问题**: 某些平台的二进制文件缺失 +**Problem**: Binary files for some platforms are missing -**解决方案**: +**Solution**: ```bash -# 1. 本地测试构建 +# 1. Test build locally make build-all VERSION=v0.1.0 -# 2. 检查构建产物 +# 2. Check build artifacts ls -la bin/ -# 3. 验证二进制文件 +# 3. Verify binary files ./bin/aim-linux-amd64 version ``` -## 📊 版本管理策略 +## 📊 Version Management Strategy -### 版本号格式 -- **正式版本**: `MAJOR.MINOR.PATCH` (例如: `v0.1.0`, `v1.2.3`) -- **预发布版本**: `MAJOR.MINOR.PATCH-rc.N` (例如: `v0.1.0-rc.1`) +### Version Number Format +- **Official Release**: `MAJOR.MINOR.PATCH` (e.g., `v0.1.0`, `v1.2.3`) +- **Pre-release**: `MAJOR.MINOR.PATCH-rc.N` (e.g., `v0.1.0-rc.1`) -### 版本号递增规则 -- **MAJOR**: 不兼容的 API 变更 -- **MINOR**: 向后兼容的功能新增 -- **PATCH**: 向后兼容的问题修正 +### Version Number Increment Rules +- **MAJOR**: Incompatible API changes +- **MINOR**: Backward-compatible feature additions +- **PATCH**: Backward-compatible bug fixes -### 分支策略 +### Branch Strategy ```mermaid graph TD - A[main] --> B[feature/功能名] + A[main] --> B[feature/feature-name] A --> C[release/vX.Y.Z] A --> D[hotfix/vX.Y.Z] B --> A @@ -297,35 +297,35 @@ graph TD D --> A ``` -## 💡 最佳实践 +## 💡 Best Practices -### 版本管理 -- 保持版本号递增,永远不要递减版本号 -- 使用语义化版本,严格遵循 SemVer 规范 -- 完成发布后立即创建标签 -- 编写清晰的发布说明,详细说明每个版本的变更 -- 尽量避免破坏性变更 +### Version Management +- Keep version numbers incrementing, never decrement +- Use semantic versioning, strictly follow SemVer specification +- Create tags immediately after release completion +- Write clear release notes detailing changes in each version +- Avoid breaking changes whenever possible -### 分支管理 -- 保持 main 分支稳定,只合并经过测试的代码 -- 功能分支应该短期存在并及时合并 -- 定期清理已合并的分支 -- 使用描述性分支名,清楚表达其用途 +### Branch Management +- Keep main branch stable, only merge tested code +- Feature branches should be short-lived and merged promptly +- Regularly clean up merged branches +- Use descriptive branch names that clearly express their purpose -### 发布流程 -- 确保所有测试通过 -- 在不同平台上测试二进制文件 -- 确保文档与代码同步更新 -- 及时通知用户新版本发布 +### Release Process +- Ensure all tests pass +- Test binary files on different platforms +- Ensure documentation is updated synchronously with code +- Notify users of new releases promptly -## 📚 相关文档 +## 📚 Related Documentation -- [CI/CD 设计方案](AIM_CI_CD_DESIGN.md) - 详细的 CI/CD 设计方案 -- [版本管理策略](VERSION_MANAGEMENT_STRATEGY.md) - 完整的版本管理策略 -- [发布流程](RELEASE_PROCESS.md) - 详细的发布流程文档 -- [构建触发条件](BUILD_TRIGGERS.md) - 构建触发条件详解 -- [快速发布指南](QUICK_RELEASE_GUIDE.md) - 快速参考指南 +- [CI/CD Design Plan](AIM_CI_CD_DESIGN.md) - Detailed CI/CD design plan +- [Version Management Strategy](VERSION_MANAGEMENT_STRATEGY.md) - Complete version management strategy +- [Release Process](RELEASE_PROCESS.md) - Detailed release process documentation +- [Build Trigger Conditions](BUILD_TRIGGERS.md) - Detailed explanation of build trigger conditions +- [Quick Release Guide](QUICK_RELEASE_GUIDE.md) - Quick reference guide --- -通过遵循本指南,AIM 项目的版本发布将更加规范、可靠和高效,同时保持单人开发的灵活性和简洁性。 \ No newline at end of file +By following this guide, the AIM project's version releases will be more standardized, reliable, and efficient, while maintaining the flexibility and simplicity of solo development. \ No newline at end of file diff --git a/docs/cicd/CI_CD_EN.md b/docs/cicd/CI_CD_EN.md deleted file mode 100644 index 312ae20..0000000 --- a/docs/cicd/CI_CD_EN.md +++ /dev/null @@ -1,331 +0,0 @@ -# AIM Project CI/CD Concise Guide - -This guide provides a quick and practical CI/CD operation guide for solo developers of the AIM project. - -## 🚀 Quick Start - -### Environment Check -```bash -# Check basic environment -git config --list | grep -E "(user\.name|user\.email)" -go version -make help -``` - -### Common Commands Quick Reference - -| Operation | Command | -|-----------|---------| -| Check current version | `make version` | -| Create RC version | `make rc` | -| Create official release | `make release` | -| Run tests | `make test` | -| Code checks | `make check` | -| Build for all platforms | `make build-all` | - -## 📦 Version Release Process - -### 1. Patch Release -Used for backward-compatible bug fixes. - -```bash -# 1. Create hotfix branch -git checkout -b hotfix/v0.1.0 - -# 2. Fix issues and commit -git add . -git commit -m "fix: resolve crash when loading invalid config" - -# 3. Run tests -make test - -# 4. Merge to main -git checkout main -git merge --no-ff hotfix/v0.1.0 - -# 5. Create version tag -git tag -a v0.1.0 -m "Release v0.1.0 - -Bug fixes: -- Fix crash when loading invalid config -- Improve error handling for missing files" - -# 6. Push tag to trigger automatic release -git push origin v0.1.0 - -# 7. Delete hotfix branch -git branch -d hotfix/v0.1.0 -``` - -### 2. Minor Release -Used for adding backward-compatible new features. - -```bash -# 1. Create release branch -git checkout -b release/v0.2.0 - -# 2. Update version information and documentation -# ... make necessary updates ... - -# 3. Commit changes -git add . -git commit -m "feat: prepare for v0.2.0 release" - -# 4. Create RC version (optional) -git tag -a v0.2.0-rc.1 -m "Release candidate v0.2.0-rc.1" -git push origin v0.2.0-rc.1 - -# 5. After testing RC version, create official release -git tag -a v0.2.0 -m "Release v0.2.0 - -New features: -- Add support for OpenAI API -- Add configuration validation -- Add interactive setup wizard" - -# 6. Merge to main -git checkout main -git merge --no-ff release/v0.2.0 - -# 7. Push tag to trigger automatic release -git push origin v0.2.0 - -# 8. Delete release branch -git branch -d release/v0.2.0 -``` - -### 3. Major Release -Used for significant version updates that include incompatible changes. - -```bash -# 1. Create release branch -git checkout -b release/v1.0.0 - -# 2. Make major changes and update documentation -# ... make code changes and documentation updates ... - -# 3. Commit changes -git add . -git commit -m "feat!: prepare for v1.0.0 major release - -BREAKING CHANGE: Update CLI command structure" - -# 4. Create RC version -git tag -a v1.0.0-rc.1 -m "Release candidate v1.0.0-rc.1" -git push origin v1.0.0-rc.1 - -# 5. Create official release after testing -git tag -a v1.0.0 -m "Release v1.0.0 - -Major changes: -- Redesigned CLI command structure -- Updated configuration file format -- Added migration guide for v0.x users - -Breaking Changes: -- CLI commands restructured -- Configuration format updated -- Deprecated features removed" - -# 6. Merge to main -git checkout main -git merge --no-ff release/v1.0.0 - -# 7. Push tag to trigger automatic release -git push origin v1.0.0 - -# 8. Delete release branch -git branch -d release/v1.0.0 -``` - -## 🔧 Build Trigger Conditions - -### Automatic Trigger Scenarios - -| Trigger Condition | Workflow | Execution Time | Main Operations | -|-------------------|----------|----------------|-----------------| -| Push to main branch | CI | ~15 minutes | Tests, checks, build | -| Push version tag (v*.*.*) | Release | ~30 minutes | Multi-platform build, create Release | -| Push RC tag (v*.*.*-rc.*) | Pre-release | ~30 minutes | Multi-platform build, create pre-release | -| Create/update PR | PR Check | ~15 minutes | Tests, checks, change analysis | - -### Avoiding Unnecessary Builds - -```bash -# 1. Use Draft PR -gh pr create --draft --title "WIP: Feature" --body "Work in progress" - -# 2. Skip CI -git commit -m "docs: update README [ci skip]" - -# 3. Batch commits -git rebase -i HEAD~3 -``` - -## 📋 Pre-release Checklist - -### Code Quality -- [ ] All code checks pass (`make check`) -- [ ] All unit tests pass (`make test`) -- [ ] Test coverage meets requirements (>80%) -- [ ] No security vulnerabilities -- [ ] Code formatting follows standards - -### Functionality Verification -- [ ] New features work as expected -- [ ] Existing features are not affected -- [ ] Edge case tests pass -- [ ] Error handling is correct -- [ ] Performance meets expectations - -### Documentation Updates -- [ ] README.md updated -- [ ] CHANGELOG.md updated -- [ ] API documentation updated (if needed) -- [ ] User guide updated (if needed) -- [ ] Migration guide (if needed) - -## 🛠️ Common Commands Reference - -### Make Commands -```bash -# Build related -make build # Build for current platform -make build-all # Build for all platforms -make clean # Clean build artifacts - -# Test related -make test # Run tests -make coverage # Generate coverage report -make check # Run all checks - -# Code quality -make fmt # Format code -make vet # Static analysis -make lint # Code review - -# Dependency management -make mod # Update dependencies - -# Development environment -make dev-setup # Set up development environment -make dev-install # Install to development environment -make dev-test # Test development environment -``` - -## 🔍 Troubleshooting - -### Release Failures - -**Problem**: GitHub Actions workflow fails - -**Solution**: -```bash -# 1. Fix code issues -# ... fix code ... - -# 2. Rerun tests -make test - -# 3. Delete failed Release (if created) -# Manually delete on GitHub page - -# 4. Delete incorrect tag -git tag -d v0.1.0 -git push origin :refs/tags/v0.1.0 - -# 5. Recreate tag -git tag -a v0.1.0 -m "Release v0.1.0 (fixed)" -git push origin v0.1.0 -``` - -### Version Number Conflicts - -**Problem**: Version number already exists - -**Solution**: -```bash -# 1. List all tags -git tag -l - -# 2. Delete incorrect local tag -git tag -d v0.1.0 - -# 3. Delete incorrect remote tag -git push origin :refs/tags/v0.1.0 - -# 4. Create correct tag -git tag -a v0.1.2 -m "Release v0.1.2" -git push origin v0.1.2 -``` - -### Incomplete Build Artifacts - -**Problem**: Binary files for some platforms are missing - -**Solution**: -```bash -# 1. Test build locally -make build-all VERSION=v0.1.0 - -# 2. Check build artifacts -ls -la bin/ - -# 3. Verify binary files -./bin/aim-linux-amd64 version -``` - -## 📊 Version Management Strategy - -### Version Number Format -- **Official Release**: `MAJOR.MINOR.PATCH` (e.g., `v0.1.0`, `v1.2.3`) -- **Pre-release**: `MAJOR.MINOR.PATCH-rc.N` (e.g., `v0.1.0-rc.1`) - -### Version Number Increment Rules -- **MAJOR**: Incompatible API changes -- **MINOR**: Backward-compatible feature additions -- **PATCH**: Backward-compatible bug fixes - -### Branch Strategy -```mermaid -graph TD - A[main] --> B[feature/feature-name] - A --> C[release/vX.Y.Z] - A --> D[hotfix/vX.Y.Z] - B --> A - C --> A - D --> A -``` - -## 💡 Best Practices - -### Version Management -- Keep version numbers incrementing, never decrement -- Use semantic versioning, strictly follow SemVer specification -- Create tags immediately after release completion -- Write clear release notes detailing changes in each version -- Avoid breaking changes whenever possible - -### Branch Management -- Keep main branch stable, only merge tested code -- Feature branches should be short-lived and merged promptly -- Regularly clean up merged branches -- Use descriptive branch names that clearly express their purpose - -### Release Process -- Ensure all tests pass -- Test binary files on different platforms -- Ensure documentation is updated synchronously with code -- Notify users of new releases promptly - -## 📚 Related Documentation - -- [CI/CD Design Plan](AIM_CI_CD_DESIGN.md) - Detailed CI/CD design plan -- [Version Management Strategy](VERSION_MANAGEMENT_STRATEGY.md) - Complete version management strategy -- [Release Process](RELEASE_PROCESS.md) - Detailed release process documentation -- [Build Trigger Conditions](BUILD_TRIGGERS.md) - Detailed explanation of build trigger conditions -- [Quick Release Guide](QUICK_RELEASE_GUIDE.md) - Quick reference guide - ---- - -By following this guide, the AIM project's version releases will be more standardized, reliable, and efficient, while maintaining the flexibility and simplicity of solo development. \ No newline at end of file diff --git a/docs/cicd/ci_cd_cn.md b/docs/cicd/ci_cd_cn.md new file mode 100644 index 0000000..3a08078 --- /dev/null +++ b/docs/cicd/ci_cd_cn.md @@ -0,0 +1,331 @@ +# AIM 项目 CI/CD 精简指南 + +本指南为 AIM 项目的单人开发者提供快速、实用的 CI/CD 操作指南。 + +## 🚀 快速开始 + +### 环境检查 +```bash +# 检查基础环境 +git config --list | grep -E "(user\.name|user\.email)" +go version +make help +``` + +### 常用命令速查 + +| 操作 | 命令 | +|------|------| +| 检查当前版本 | `make version` | +| 创建 RC 版本 | `make rc` | +| 创建正式版本 | `make release` | +| 运行测试 | `make test` | +| 代码检查 | `make check` | +| 构建所有平台 | `make build-all` | + +## 📦 版本发布流程 + +### 1. 修复发布 (Patch Release) +用于向后兼容的问题修正。 + +```bash +# 1. 创建 hotfix 分支 +git checkout -b hotfix/v0.1.0 + +# 2. 修复问题并提交 +git add . +git commit -m "fix: resolve crash when loading invalid config" + +# 3. 运行测试 +make test + +# 4. 合并到 main +git checkout main +git merge --no-ff hotfix/v0.1.0 + +# 5. 创建版本标签 +git tag -a v0.1.0 -m "Release v0.1.0 + +Bug fixes: +- Fix crash when loading invalid config +- Improve error handling for missing files" + +# 6. 推送标签触发自动发布 +git push origin v0.1.0 + +# 7. 删除 hotfix 分支 +git branch -d hotfix/v0.1.0 +``` + +### 2. 功能发布 (Minor Release) +用于添加向后兼容的新功能。 + +```bash +# 1. 创建 release 分支 +git checkout -b release/v0.2.0 + +# 2. 更新版本信息和文档 +# ... 进行必要的更新 ... + +# 3. 提交更改 +git add . +git commit -m "feat: prepare for v0.2.0 release" + +# 4. 创建 RC 版本(可选) +git tag -a v0.2.0-rc.1 -m "Release candidate v0.2.0-rc.1" +git push origin v0.2.0-rc.1 + +# 5. 测试 RC 版本后,创建正式版本 +git tag -a v0.2.0 -m "Release v0.2.0 + +New features: +- Add support for OpenAI API +- Add configuration validation +- Add interactive setup wizard" + +# 6. 合并到 main +git checkout main +git merge --no-ff release/v0.2.0 + +# 7. 推送标签触发自动发布 +git push origin v0.2.0 + +# 8. 删除 release 分支 +git branch -d release/v0.2.0 +``` + +### 3. 主要发布 (Major Release) +用于包含不兼容变更的重要版本更新。 + +```bash +# 1. 创建 release 分支 +git checkout -b release/v1.0.0 + +# 2. 进行重大变更和文档更新 +# ... 进行代码修改和文档更新 ... + +# 3. 提交更改 +git add . +git commit -m "feat!: prepare for v1.0.0 major release + +BREAKING CHANGE: Update CLI command structure" + +# 4. 创建 RC 版本 +git tag -a v1.0.0-rc.1 -m "Release candidate v1.0.0-rc.1" +git push origin v1.0.0-rc.1 + +# 5. 测试后创建正式版本 +git tag -a v1.0.0 -m "Release v1.0.0 + +Major changes: +- Redesigned CLI command structure +- Updated configuration file format +- Added migration guide for v0.x users + +Breaking Changes: +- CLI commands restructured +- Configuration format updated +- Deprecated features removed" + +# 6. 合并到 main +git checkout main +git merge --no-ff release/v1.0.0 + +# 7. 推送标签触发自动发布 +git push origin v1.0.0 + +# 8. 删除 release 分支 +git branch -d release/v1.0.0 +``` + +## 🔧 构建触发条件 + +### 自动触发场景 + +| 触发条件 | 工作流 | 执行时间 | 主要操作 | +|---------|--------|----------|----------| +| 推送到 main 分支 | CI | ~15 分钟 | 测试、检查、构建 | +| 推送版本标签 (v*.*.*) | Release | ~30 分钟 | 多平台构建、创建 Release | +| 推送 RC 标签 (v*.*.*-rc.*) | Pre-release | ~30 分钟 | 多平台构建、创建预发布 | +| 创建/更新 PR | PR Check | ~15 分钟 | 测试、检查、变更分析 | + +### 避免不必要构建 + +```bash +# 1. 使用 Draft PR +gh pr create --draft --title "WIP: Feature" --body "Work in progress" + +# 2. 跳过 CI +git commit -m "docs: update README [ci skip]" + +# 3. 批量提交 +git rebase -i HEAD~3 +``` + +## 📋 发布前检查清单 + +### 代码质量 +- [ ] 所有代码检查通过 (`make check`) +- [ ] 所有单元测试通过 (`make test`) +- [ ] 测试覆盖率符合要求 (>80%) +- [ ] 无安全漏洞 +- [ ] 代码格式符合规范 + +### 功能验证 +- [ ] 新功能按预期工作 +- [ ] 现有功能未受影响 +- [ ] 边界情况测试通过 +- [ ] 错误处理正确 +- [ ] 性能符合预期 + +### 文档更新 +- [ ] README.md 更新 +- [ ] CHANGELOG.md 更新 +- [ ] API 文档更新(如需要) +- [ ] 用户指南更新(如需要) +- [ ] 迁移指南(如需要) + +## 🛠️ 常用命令参考 + +### Make 命令 +```bash +# 构建相关 +make build # 构建当前平台 +make build-all # 构建所有平台 +make clean # 清理构建产物 + +# 测试相关 +make test # 运行测试 +make coverage # 生成覆盖率报告 +make check # 运行所有检查 + +# 代码质量 +make fmt # 格式化代码 +make vet # 静态分析 +make lint # 代码审查 + +# 依赖管理 +make mod # 更新依赖 + +# 开发环境 +make dev-setup # 设置开发环境 +make dev-install # 安装到开发环境 +make dev-test # 测试开发环境 +``` + +## 🔍 故障排除 + +### 发布失败 + +**问题**: GitHub Actions 工作流失败 + +**解决方案**: +```bash +# 1. 修复代码问题 +# ... 修复代码 ... + +# 2. 重新运行测试 +make test + +# 3. 删除失败的 Release(如果已创建) +# 在 GitHub 页面上手动删除 + +# 4. 删除错误的标签 +git tag -d v0.1.0 +git push origin :refs/tags/v0.1.0 + +# 5. 重新创建标签 +git tag -a v0.1.0 -m "Release v0.1.0 (fixed)" +git push origin v0.1.0 +``` + +### 版本号冲突 + +**问题**: 版本号已存在 + +**解决方案**: +```bash +# 1. 列出所有标签 +git tag -l + +# 2. 删除错误的本地标签 +git tag -d v0.1.0 + +# 3. 删除错误的远程标签 +git push origin :refs/tags/v0.1.0 + +# 4. 创建正确的标签 +git tag -a v0.1.2 -m "Release v0.1.2" +git push origin v0.1.2 +``` + +### 构建产物不完整 + +**问题**: 某些平台的二进制文件缺失 + +**解决方案**: +```bash +# 1. 本地测试构建 +make build-all VERSION=v0.1.0 + +# 2. 检查构建产物 +ls -la bin/ + +# 3. 验证二进制文件 +./bin/aim-linux-amd64 version +``` + +## 📊 版本管理策略 + +### 版本号格式 +- **正式版本**: `MAJOR.MINOR.PATCH` (例如: `v0.1.0`, `v1.2.3`) +- **预发布版本**: `MAJOR.MINOR.PATCH-rc.N` (例如: `v0.1.0-rc.1`) + +### 版本号递增规则 +- **MAJOR**: 不兼容的 API 变更 +- **MINOR**: 向后兼容的功能新增 +- **PATCH**: 向后兼容的问题修正 + +### 分支策略 +```mermaid +graph TD + A[main] --> B[feature/功能名] + A --> C[release/vX.Y.Z] + A --> D[hotfix/vX.Y.Z] + B --> A + C --> A + D --> A +``` + +## 💡 最佳实践 + +### 版本管理 +- 保持版本号递增,永远不要递减版本号 +- 使用语义化版本,严格遵循 SemVer 规范 +- 完成发布后立即创建标签 +- 编写清晰的发布说明,详细说明每个版本的变更 +- 尽量避免破坏性变更 + +### 分支管理 +- 保持 main 分支稳定,只合并经过测试的代码 +- 功能分支应该短期存在并及时合并 +- 定期清理已合并的分支 +- 使用描述性分支名,清楚表达其用途 + +### 发布流程 +- 确保所有测试通过 +- 在不同平台上测试二进制文件 +- 确保文档与代码同步更新 +- 及时通知用户新版本发布 + +## 📚 相关文档 + +- [CI/CD 设计方案](AIM_CI_CD_DESIGN.md) - 详细的 CI/CD 设计方案 +- [版本管理策略](VERSION_MANAGEMENT_STRATEGY.md) - 完整的版本管理策略 +- [发布流程](RELEASE_PROCESS.md) - 详细的发布流程文档 +- [构建触发条件](BUILD_TRIGGERS.md) - 构建触发条件详解 +- [快速发布指南](QUICK_RELEASE_GUIDE.md) - 快速参考指南 + +--- + +通过遵循本指南,AIM 项目的版本发布将更加规范、可靠和高效,同时保持单人开发的灵活性和简洁性。 \ No newline at end of file diff --git a/docs/development-guide/LOCAL_DEV.md b/docs/development-guide/LOCAL_DEV.md index e801cc4..663b22f 100644 --- a/docs/development-guide/LOCAL_DEV.md +++ b/docs/development-guide/LOCAL_DEV.md @@ -1,276 +1,275 @@ -# 本地测试环境使用指南 +# Local Development Environment Guide -## 快速开始 +## Quick Start ```bash -# 1. 初始化测试环境(默认在 aim-local-dev/ 目录) +# 1. Initialize test environment (default in aim-local-dev/ directory) ./local-dev.sh -# 2. 加载环境变量 +# 2. Load environment variables source aim-local-dev/env.sh # Bash/Zsh/Sh source aim-local-dev/env.fish # Fish -# 3. 使用 aim 命令 +# 3. Use aim commands aim version aim provider list aim keys list -# 4. 运行测试 +# 4. Run tests aim-local-dev/test.sh ``` -## 更新和重建 +## Updates and Rebuilding -修改代码后,快速重建和更新二进制文件: +Quickly rebuild and update binaries after modifying code: ```bash -# 使用 rebuild 或 update 命令 +# Use rebuild or update command ./local-dev.sh rebuild -# 更新后重新加载环境变量 +# Reload environment variables after update source aim-local-dev/env.sh -# 验证更新 -aim version # 查看 Built 时间 +# Verify update +aim version # Check Built time ``` -**rebuild/update 的智能检测:** -- 如果已加载环境(`$AIM_HOME` 存在),自动更新到该环境 -- 否则更新到默认位置 `aim-local-dev/` +**Smart detection for rebuild/update:** +- If environment is loaded (`$AIM_HOME` exists), automatically update to that environment +- Otherwise update to default location `aim-local-dev/` -## 特点 +## Features -- ✅ 同时支持 Bash/Zsh/Fish shell -- ✅ 测试环境在项目目录下 (`aim-local-dev/`) -- ✅ PATH 优先级最高,不影响系统安装 -- ✅ 完全隔离的 AIM_HOME、配置和缓存 -- ✅ 自动构建最新代码 -- ✅ 包含测试 API keys -- ✅ 支持快速重建和更新 +- ✅ Supports Bash/Zsh/Fish shells simultaneously +- ✅ Test environment in project directory (`aim-local-dev/`) +- ✅ Highest PATH priority, doesn't affect system installation +- ✅ Completely isolated AIM_HOME, configuration and cache +- ✅ Automatically builds latest code +- ✅ Includes test API keys +- ✅ Supports quick rebuild and update -## 使用自定义路径 +## Using Custom Paths ```bash -# 指定其他路径 +# Specify other paths ./local-dev.sh ~/my-test ./local-dev.sh /tmp/aim-test -# 加载自定义环境 +# Load custom environment source ~/my-test/env.sh -# 更新(自动检测 $AIM_HOME) +# Update (automatically detects $AIM_HOME) ./local-dev.sh rebuild ``` -## 开发工作流 +## Development Workflow -典型的开发测试流程: +Typical development and testing workflow: ```bash -# 1. 初始化一次 +# 1. Initialize once ./local-dev.sh -# 2. 加载环境 +# 2. Load environment source aim-local-dev/env.sh -# 3. 修改代码 +# 3. Modify code vim internal/keys/editor.go -# 4. 快速重建 +# 4. Quick rebuild ./local-dev.sh rebuild -# 5. 重新加载环境 +# 5. Reload environment source aim-local-dev/env.sh -# 6. 测试新功能 +# 6. Test new features aim keys edit deepseek -# 继续开发循环... +# Continue development cycle... ``` -## PATH 优先级 +## PATH Priority -通过 `export PATH="$AIM_HOME/bin:$PATH"` 方式,测试环境的优先级高于系统安装: +Through `export PATH="$AIM_HOME/bin:$PATH"`, the test environment has higher priority than system installation: ```bash source aim-local-dev/env.sh -# 验证优先级 +# Verify priority which aim -# 输出: /Users/dylan/code/aim/aim-local-dev/bin/aim +# Output: /Users/dylan/code/aim/aim-local-dev/bin/aim echo $PATH | tr ':' '\n' | head -1 -# 输出: /Users/dylan/code/aim/aim-local-dev/bin +# Output: /Users/dylan/code/aim/aim-local-dev/bin ``` -这样可以: -- 测试环境的 aim/aix 优先于系统安装 -- 不影响系统全局安装 -- AIM_HOME 指向测试目录,配置和缓存完全隔离 +This allows: +- Test environment's aim/aix takes priority over system installation +- Doesn't affect global system installation +- AIM_HOME points to test directory, configuration and cache are completely isolated -## 配置 API Keys +## Configuring API Keys -### 方法 1:修改环境文件 +### Method 1: Modify Environment File ```bash -# Bash/Zsh 用户 +# For Bash/Zsh users vim aim-local-dev/env.sh export DEEPSEEK_API_KEY="sk-your-real-key" -# Fish 用户 +# For Fish users vim aim-local-dev/env.fish set -gx DEEPSEEK_API_KEY "sk-your-real-key" ``` -### 方法 2:环境变量覆盖 +### Method 2: Environment Variable Override ```bash -# 先设置真实 key +# First set real key export DEEPSEEK_API_KEY="sk-your-real-key" -# 再加载环境(会保留已存在的 key) +# Then load environment (will preserve existing key) source aim-local-dev/env.sh ``` -### 方法 3:使用 aim keys 命令 +### Method 3: Using aim keys Command ```bash source aim-local-dev/env.sh aim keys add deepseek -# 按提示输入 key +# Follow prompts to enter key ``` -## 测试场景 +## Testing Scenarios -### 测试 Provider 管理 +### Testing Provider Management ```bash source aim-local-dev/env.sh -# 列出所有 providers +# List all providers aim provider list -# 查看特定 provider 信息 +# View specific provider information aim provider info deepseek -# 添加自定义 provider +# Add custom provider aim provider add my-ai \ --display-name "My AI" \ --env-var MY_AI_KEY \ --key-prefix "myai-" ``` -### 测试 Key 管理 +### Testing Key Management ```bash source aim-local-dev/env.sh -# 列出所有 keys +# List all keys aim keys list -# 测试特定 provider 的 key +# Test key for specific provider aim keys test deepseek -# 测试所有已配置的 keys +# Test all configured keys aim keys test -# 使用编辑器编辑 key +# Edit key using editor aim keys edit deepseek ``` -## 多环境测试 +## Multi-Environment Testing -可以创建多个测试环境用于不同场景: +Create multiple test environments for different scenarios: ```bash -# 开发环境 +# Development environment ./local-dev.sh ~/aim-dev -# 测试环境 +# Testing environment ./local-dev.sh ~/aim-test -# 使用不同环境 -source ~/aim-dev/env.sh # 开发 -source ~/aim-test/env.sh # 测试 +# Use different environments +source ~/aim-dev/env.sh # Development +source ~/aim-test/env.sh # Testing ``` -## 清理 +## Cleanup ```bash -# 删除测试环境 +# Delete test environment rm -rf aim-local-dev ``` -## 与系统安装隔离 +## Isolation from System Installation ```bash -# 测试环境 +# Test environment source aim-local-dev/env.sh which aim # aim-local-dev/bin/aim echo $AIM_HOME # aim-local-dev -# 新终端(未加载环境) -which aim # /usr/local/bin/aim (系统安装) -echo $AIM_HOME # (空或系统设置) +# New terminal (environment not loaded) +which aim # /usr/local/bin/aim (system installation) +echo $AIM_HOME # (empty or system setting) ``` -## 故障排除 +## Troubleshooting -### 找不到 aim 命令 +### aim Command Not Found ```bash -# 确认已加载环境 +# Confirm environment is loaded source aim-local-dev/env.sh -# 检查 PATH +# Check PATH echo $PATH | grep local-dev -# 检查二进制文件 +# Check binary files ls -la aim-local-dev/bin/ ``` -### 代码修改后未生效 +### Code Changes Not Taking Effect ```bash -# 重建并重新加载 +# Rebuild and reload ./local-dev.sh rebuild source aim-local-dev/env.sh -# 验证版本 -aim version # 检查 Built 时间 +# Verify version +aim version # Check Built time ``` -### 权限被拒绝 +### Permission Denied ```bash -# 确保脚本可执行 +# Ensure scripts are executable chmod +x aim-local-dev/test.sh chmod +x aim-local-dev/bin/* ``` -### 重新初始化 +### Reinitialization ```bash -# 脚本会询问是否重新初始化 +# Script will ask whether to reinitialize ./local-dev.sh -# 或强制删除后重建 +# Or force delete and rebuild rm -rf aim-local-dev && ./local-dev.sh ``` -## 命令参考 +## Command Reference ```bash -# 初始化 -./local-dev.sh # 默认位置 aim-local-dev/ -./local-dev.sh ~/custom-path # 自定义位置 +# Initialization +./local-dev.sh # Default location aim-local-dev/ +./local-dev.sh ~/custom-path # Custom location -# 更新 -./local-dev.sh rebuild # 重建(自动检测环境) -./local-dev.sh update # 同 rebuild +# Updates +./local-dev.sh rebuild # Rebuild (automatically detects environment) +./local-dev.sh update # Same as rebuild -# 使用 +# Usage source aim-local-dev/env.sh # Bash/Zsh source aim-local-dev/env.fish # Fish -aim-local-dev/test.sh # 快速测试 -``` +aim-local-dev/test.sh # Quick test \ No newline at end of file diff --git a/docs/development-guide/LOCAL_DEV_EN.md b/docs/development-guide/LOCAL_DEV_EN.md deleted file mode 100644 index 663b22f..0000000 --- a/docs/development-guide/LOCAL_DEV_EN.md +++ /dev/null @@ -1,275 +0,0 @@ -# Local Development Environment Guide - -## Quick Start - -```bash -# 1. Initialize test environment (default in aim-local-dev/ directory) -./local-dev.sh - -# 2. Load environment variables -source aim-local-dev/env.sh # Bash/Zsh/Sh -source aim-local-dev/env.fish # Fish - -# 3. Use aim commands -aim version -aim provider list -aim keys list - -# 4. Run tests -aim-local-dev/test.sh -``` - -## Updates and Rebuilding - -Quickly rebuild and update binaries after modifying code: - -```bash -# Use rebuild or update command -./local-dev.sh rebuild - -# Reload environment variables after update -source aim-local-dev/env.sh - -# Verify update -aim version # Check Built time -``` - -**Smart detection for rebuild/update:** -- If environment is loaded (`$AIM_HOME` exists), automatically update to that environment -- Otherwise update to default location `aim-local-dev/` - -## Features - -- ✅ Supports Bash/Zsh/Fish shells simultaneously -- ✅ Test environment in project directory (`aim-local-dev/`) -- ✅ Highest PATH priority, doesn't affect system installation -- ✅ Completely isolated AIM_HOME, configuration and cache -- ✅ Automatically builds latest code -- ✅ Includes test API keys -- ✅ Supports quick rebuild and update - -## Using Custom Paths - -```bash -# Specify other paths -./local-dev.sh ~/my-test -./local-dev.sh /tmp/aim-test - -# Load custom environment -source ~/my-test/env.sh - -# Update (automatically detects $AIM_HOME) -./local-dev.sh rebuild -``` - -## Development Workflow - -Typical development and testing workflow: - -```bash -# 1. Initialize once -./local-dev.sh - -# 2. Load environment -source aim-local-dev/env.sh - -# 3. Modify code -vim internal/keys/editor.go - -# 4. Quick rebuild -./local-dev.sh rebuild - -# 5. Reload environment -source aim-local-dev/env.sh - -# 6. Test new features -aim keys edit deepseek - -# Continue development cycle... -``` - -## PATH Priority - -Through `export PATH="$AIM_HOME/bin:$PATH"`, the test environment has higher priority than system installation: - -```bash -source aim-local-dev/env.sh - -# Verify priority -which aim -# Output: /Users/dylan/code/aim/aim-local-dev/bin/aim - -echo $PATH | tr ':' '\n' | head -1 -# Output: /Users/dylan/code/aim/aim-local-dev/bin -``` - -This allows: -- Test environment's aim/aix takes priority over system installation -- Doesn't affect global system installation -- AIM_HOME points to test directory, configuration and cache are completely isolated - -## Configuring API Keys - -### Method 1: Modify Environment File - -```bash -# For Bash/Zsh users -vim aim-local-dev/env.sh -export DEEPSEEK_API_KEY="sk-your-real-key" - -# For Fish users -vim aim-local-dev/env.fish -set -gx DEEPSEEK_API_KEY "sk-your-real-key" -``` - -### Method 2: Environment Variable Override - -```bash -# First set real key -export DEEPSEEK_API_KEY="sk-your-real-key" - -# Then load environment (will preserve existing key) -source aim-local-dev/env.sh -``` - -### Method 3: Using aim keys Command - -```bash -source aim-local-dev/env.sh -aim keys add deepseek -# Follow prompts to enter key -``` - -## Testing Scenarios - -### Testing Provider Management - -```bash -source aim-local-dev/env.sh - -# List all providers -aim provider list - -# View specific provider information -aim provider info deepseek - -# Add custom provider -aim provider add my-ai \ - --display-name "My AI" \ - --env-var MY_AI_KEY \ - --key-prefix "myai-" -``` - -### Testing Key Management - -```bash -source aim-local-dev/env.sh - -# List all keys -aim keys list - -# Test key for specific provider -aim keys test deepseek - -# Test all configured keys -aim keys test - -# Edit key using editor -aim keys edit deepseek -``` - -## Multi-Environment Testing - -Create multiple test environments for different scenarios: - -```bash -# Development environment -./local-dev.sh ~/aim-dev - -# Testing environment -./local-dev.sh ~/aim-test - -# Use different environments -source ~/aim-dev/env.sh # Development -source ~/aim-test/env.sh # Testing -``` - -## Cleanup - -```bash -# Delete test environment -rm -rf aim-local-dev -``` - -## Isolation from System Installation - -```bash -# Test environment -source aim-local-dev/env.sh -which aim # aim-local-dev/bin/aim -echo $AIM_HOME # aim-local-dev - -# New terminal (environment not loaded) -which aim # /usr/local/bin/aim (system installation) -echo $AIM_HOME # (empty or system setting) -``` - -## Troubleshooting - -### aim Command Not Found - -```bash -# Confirm environment is loaded -source aim-local-dev/env.sh - -# Check PATH -echo $PATH | grep local-dev - -# Check binary files -ls -la aim-local-dev/bin/ -``` - -### Code Changes Not Taking Effect - -```bash -# Rebuild and reload -./local-dev.sh rebuild -source aim-local-dev/env.sh - -# Verify version -aim version # Check Built time -``` - -### Permission Denied - -```bash -# Ensure scripts are executable -chmod +x aim-local-dev/test.sh -chmod +x aim-local-dev/bin/* -``` - -### Reinitialization - -```bash -# Script will ask whether to reinitialize -./local-dev.sh - -# Or force delete and rebuild -rm -rf aim-local-dev && ./local-dev.sh -``` - -## Command Reference - -```bash -# Initialization -./local-dev.sh # Default location aim-local-dev/ -./local-dev.sh ~/custom-path # Custom location - -# Updates -./local-dev.sh rebuild # Rebuild (automatically detects environment) -./local-dev.sh update # Same as rebuild - -# Usage -source aim-local-dev/env.sh # Bash/Zsh -source aim-local-dev/env.fish # Fish -aim-local-dev/test.sh # Quick test \ No newline at end of file diff --git a/docs/development-guide/local_dev_cn.md b/docs/development-guide/local_dev_cn.md new file mode 100644 index 0000000..e801cc4 --- /dev/null +++ b/docs/development-guide/local_dev_cn.md @@ -0,0 +1,276 @@ +# 本地测试环境使用指南 + +## 快速开始 + +```bash +# 1. 初始化测试环境(默认在 aim-local-dev/ 目录) +./local-dev.sh + +# 2. 加载环境变量 +source aim-local-dev/env.sh # Bash/Zsh/Sh +source aim-local-dev/env.fish # Fish + +# 3. 使用 aim 命令 +aim version +aim provider list +aim keys list + +# 4. 运行测试 +aim-local-dev/test.sh +``` + +## 更新和重建 + +修改代码后,快速重建和更新二进制文件: + +```bash +# 使用 rebuild 或 update 命令 +./local-dev.sh rebuild + +# 更新后重新加载环境变量 +source aim-local-dev/env.sh + +# 验证更新 +aim version # 查看 Built 时间 +``` + +**rebuild/update 的智能检测:** +- 如果已加载环境(`$AIM_HOME` 存在),自动更新到该环境 +- 否则更新到默认位置 `aim-local-dev/` + +## 特点 + +- ✅ 同时支持 Bash/Zsh/Fish shell +- ✅ 测试环境在项目目录下 (`aim-local-dev/`) +- ✅ PATH 优先级最高,不影响系统安装 +- ✅ 完全隔离的 AIM_HOME、配置和缓存 +- ✅ 自动构建最新代码 +- ✅ 包含测试 API keys +- ✅ 支持快速重建和更新 + +## 使用自定义路径 + +```bash +# 指定其他路径 +./local-dev.sh ~/my-test +./local-dev.sh /tmp/aim-test + +# 加载自定义环境 +source ~/my-test/env.sh + +# 更新(自动检测 $AIM_HOME) +./local-dev.sh rebuild +``` + +## 开发工作流 + +典型的开发测试流程: + +```bash +# 1. 初始化一次 +./local-dev.sh + +# 2. 加载环境 +source aim-local-dev/env.sh + +# 3. 修改代码 +vim internal/keys/editor.go + +# 4. 快速重建 +./local-dev.sh rebuild + +# 5. 重新加载环境 +source aim-local-dev/env.sh + +# 6. 测试新功能 +aim keys edit deepseek + +# 继续开发循环... +``` + +## PATH 优先级 + +通过 `export PATH="$AIM_HOME/bin:$PATH"` 方式,测试环境的优先级高于系统安装: + +```bash +source aim-local-dev/env.sh + +# 验证优先级 +which aim +# 输出: /Users/dylan/code/aim/aim-local-dev/bin/aim + +echo $PATH | tr ':' '\n' | head -1 +# 输出: /Users/dylan/code/aim/aim-local-dev/bin +``` + +这样可以: +- 测试环境的 aim/aix 优先于系统安装 +- 不影响系统全局安装 +- AIM_HOME 指向测试目录,配置和缓存完全隔离 + +## 配置 API Keys + +### 方法 1:修改环境文件 + +```bash +# Bash/Zsh 用户 +vim aim-local-dev/env.sh +export DEEPSEEK_API_KEY="sk-your-real-key" + +# Fish 用户 +vim aim-local-dev/env.fish +set -gx DEEPSEEK_API_KEY "sk-your-real-key" +``` + +### 方法 2:环境变量覆盖 + +```bash +# 先设置真实 key +export DEEPSEEK_API_KEY="sk-your-real-key" + +# 再加载环境(会保留已存在的 key) +source aim-local-dev/env.sh +``` + +### 方法 3:使用 aim keys 命令 + +```bash +source aim-local-dev/env.sh +aim keys add deepseek +# 按提示输入 key +``` + +## 测试场景 + +### 测试 Provider 管理 + +```bash +source aim-local-dev/env.sh + +# 列出所有 providers +aim provider list + +# 查看特定 provider 信息 +aim provider info deepseek + +# 添加自定义 provider +aim provider add my-ai \ + --display-name "My AI" \ + --env-var MY_AI_KEY \ + --key-prefix "myai-" +``` + +### 测试 Key 管理 + +```bash +source aim-local-dev/env.sh + +# 列出所有 keys +aim keys list + +# 测试特定 provider 的 key +aim keys test deepseek + +# 测试所有已配置的 keys +aim keys test + +# 使用编辑器编辑 key +aim keys edit deepseek +``` + +## 多环境测试 + +可以创建多个测试环境用于不同场景: + +```bash +# 开发环境 +./local-dev.sh ~/aim-dev + +# 测试环境 +./local-dev.sh ~/aim-test + +# 使用不同环境 +source ~/aim-dev/env.sh # 开发 +source ~/aim-test/env.sh # 测试 +``` + +## 清理 + +```bash +# 删除测试环境 +rm -rf aim-local-dev +``` + +## 与系统安装隔离 + +```bash +# 测试环境 +source aim-local-dev/env.sh +which aim # aim-local-dev/bin/aim +echo $AIM_HOME # aim-local-dev + +# 新终端(未加载环境) +which aim # /usr/local/bin/aim (系统安装) +echo $AIM_HOME # (空或系统设置) +``` + +## 故障排除 + +### 找不到 aim 命令 + +```bash +# 确认已加载环境 +source aim-local-dev/env.sh + +# 检查 PATH +echo $PATH | grep local-dev + +# 检查二进制文件 +ls -la aim-local-dev/bin/ +``` + +### 代码修改后未生效 + +```bash +# 重建并重新加载 +./local-dev.sh rebuild +source aim-local-dev/env.sh + +# 验证版本 +aim version # 检查 Built 时间 +``` + +### 权限被拒绝 + +```bash +# 确保脚本可执行 +chmod +x aim-local-dev/test.sh +chmod +x aim-local-dev/bin/* +``` + +### 重新初始化 + +```bash +# 脚本会询问是否重新初始化 +./local-dev.sh + +# 或强制删除后重建 +rm -rf aim-local-dev && ./local-dev.sh +``` + +## 命令参考 + +```bash +# 初始化 +./local-dev.sh # 默认位置 aim-local-dev/ +./local-dev.sh ~/custom-path # 自定义位置 + +# 更新 +./local-dev.sh rebuild # 重建(自动检测环境) +./local-dev.sh update # 同 rebuild + +# 使用 +source aim-local-dev/env.sh # Bash/Zsh +source aim-local-dev/env.fish # Fish +aim-local-dev/test.sh # 快速测试 +``` diff --git a/docs/tui-interface/TUI_DESIGN.md b/docs/tui-interface/TUI_DESIGN.md index 9ed5597..36ed085 100644 --- a/docs/tui-interface/TUI_DESIGN.md +++ b/docs/tui-interface/TUI_DESIGN.md @@ -1,63 +1,63 @@ -# AIM TUI 设计文档 +# AIM TUI Design Documentation -## 概述 +## Overview -基于 [Bubble Tea](https://github.com/charmbracelet/bubbletea) 框架,为 AIM 提供友好的终端交互界面(TUI)。 +A friendly terminal user interface (TUI) for AIM based on the [Bubble Tea](https://github.com/charmbracelet/bubbletea) framework. -## 技术栈 +## Technology Stack -### 核心库 -- **[bubbletea](https://github.com/charmbracelet/bubbletea)** - TUI 框架(Elm Architecture) -- **[bubbles](https://github.com/charmbracelet/bubbles)** - 预制组件(list, textinput, spinner, etc.) -- **[lipgloss](https://github.com/charmbracelet/lipgloss)** - 样式和布局 +### Core Libraries +- **[bubbletea](https://github.com/charmbracelet/bubbletea)** - TUI framework (Elm Architecture) +- **[bubbles](https://github.com/charmbracelet/bubbles)** - Pre-built components (list, textinput, spinner, etc.) +- **[lipgloss](https://github.com/charmbracelet/lipgloss)** - Styling and layout -### 依赖安装 +### Dependency Installation ```bash go get github.com/charmbracelet/bubbletea go get github.com/charmbracelet/bubbles go get github.com/charmbracelet/lipgloss ``` -## TUI 命令设计 +## TUI Command Design -### 1. 交互式模式命令 +### 1. Interactive Mode Commands ```bash -# 启动交互式配置向导 -aim init # 交互式初始化(默认 TUI) -aim init --interactive # 明确指定交互式 -aim init --no-tui # 非交互式(原始方式) +# Start interactive configuration wizard +aim init # Interactive initialization (default TUI) +aim init --interactive # Explicitly specify interactive +aim init --no-tui # Non-interactive (original method) -# 交互式选择模型 -aim use # 不带参数,进入 TUI 选择器 -aim use --interactive # 交互式选择 +# Interactive model selection +aim use # Without parameters, enter TUI selector +aim use --interactive # Interactive selection -# 交互式测试界面 -aim test --interactive # TUI 测试界面,实时显示进度 +# Interactive test interface +aim test --interactive # TUI test interface with real-time progress -# 交互式配置编辑 -aim config --interactive # TUI 配置编辑器 +# Interactive configuration editing +aim config --interactive # TUI configuration editor ``` -### 2. 命令优先级 +### 2. Command Priority ``` -明确的标志 > TUI 模式(无参数时)> 非交互模式(有参数时) +Explicit flags > TUI mode (no parameters) > Non-interactive mode (with parameters) ``` -**示例**: +**Examples**: ```bash -aim use # → TUI 选择器 -aim use deepseek # → 直接切换(非交互) -aim use --interactive # → TUI 选择器(强制) -aim use deepseek --no-tui # → 直接切换(禁用 TUI) +aim use # → TUI selector +aim use deepseek # → Direct switch (non-interactive) +aim use --interactive # → TUI selector (forced) +aim use deepseek --no-tui # → Direct switch (disable TUI) ``` -## TUI 界面设计 +## TUI Interface Design -### 1. 初始化向导 (init) +### 1. Initialization Wizard (init) -#### 界面布局 +#### Interface Layout ``` ┌─────────────────────────────────────────────────────────┐ │ AIM Setup Wizard │ @@ -78,7 +78,7 @@ aim use deepseek --no-tui # → 直接切换(禁用 TUI) └─────────────────────────────────────────────────────────┘ ``` -#### 步骤流程 +#### Step Flow ``` Step 1: Language Selection Step 2: Default Tool Selection @@ -87,7 +87,7 @@ Step 4: API Keys Setup Step 5: Summary & Confirmation ``` -#### 实现代码框架 +#### Implementation Code Framework ```go // internal/tui/init.go package tui @@ -149,14 +149,14 @@ func (m InitModel) View() string { func (m InitModel) nextStep() (tea.Model, tea.Cmd) { m.step++ - // 切换到下一步的 UI + // Switch to next step UI return m, nil } ``` -### 2. 模型选择器 (use) +### 2. Model Selector (use) -#### 界面布局 +#### Interface Layout ``` ┌─────────────────────────────────────────────────────────┐ │ Select AI Model Provider │ @@ -184,16 +184,16 @@ func (m InitModel) nextStep() (tea.Model, tea.Cmd) { ↑/↓: navigate • enter: select • /: filter • t: test • q: quit ``` -#### 功能特性 -- **实时筛选**: 输入 `/` 进入过滤模式 -- **状态指示**: 显示连接状态和延迟 -- **快速测试**: 按 `t` 快速测试选中的提供商 -- **颜色编码**: - - 绿色 ✓: 可用 - - 红色 ✗: 不可用 - - 黄色 ⚠: 警告 +#### Features +- **Real-time Filtering**: Press `/` to enter filter mode +- **Status Indicators**: Show connection status and latency +- **Quick Test**: Press `t` to quickly test selected provider +- **Color Coding**: + - Green ✓: Available + - Red ✗: Unavailable + - Yellow ⚠: Warning -#### 实现代码框架 +#### Implementation Code Framework ```go // internal/tui/selector.go package tui @@ -259,7 +259,7 @@ func (m SelectorModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case "enter": m.filtering = false m.filter.Blur() - // 应用过滤 + // Apply filter return m, m.applyFilter() } } else { @@ -269,10 +269,10 @@ func (m SelectorModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.filter.Focus() return m, nil case "t": - // 测试选中的提供商 + // Test selected provider return m, m.testProvider() case "enter": - // 选择并应用 + // Select and apply return m, m.selectProvider() } } @@ -290,9 +290,9 @@ func (m SelectorModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } ``` -### 3. 测试界面 (test) +### 3. Test Interface (test) -#### 界面布局 +#### Interface Layout ``` ┌─────────────────────────────────────────────────────────┐ │ Testing Provider Connections │ @@ -320,13 +320,13 @@ func (m SelectorModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { └─────────────────────────────────────────────────────────┘ ``` -#### 功能特性 -- **实时进度**: 显示测试进度条 -- **并发测试**: 多个提供商并行测试 -- **动画效果**: 测试中的 spinner 动画 -- **详细报告**: 可查看失败详情 +#### Features +- **Real-time Progress**: Display test progress bar +- **Concurrent Testing**: Multiple providers tested in parallel +- **Animation Effects**: Spinner animation during testing +- **Detailed Reports**: View failure details -#### 实现代码框架 +#### Implementation Code Framework ```go // internal/tui/test.go package tui @@ -383,9 +383,9 @@ func (m TestModel) Init() tea.Cmd { func (m TestModel) startTests() tea.Cmd { return func() tea.Msg { - // 启动异步测试 - // 使用 goroutine 测试每个提供商 - // 完成后发送 testCompleteMsg + // Start async testing + // Use goroutine to test each provider + // Send testCompleteMsg when complete return testCompleteMsg{} } } @@ -413,14 +413,14 @@ func (m TestModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } func (m TestModel) View() string { - // 渲染测试进度界面 + // Render test progress interface return "" } ``` -### 4. 配置编辑器 (config) +### 4. Configuration Editor (config) -#### 界面布局 +#### Interface Layout ``` ┌─────────────────────────────────────────────────────────┐ │ Configuration Editor │ @@ -450,29 +450,29 @@ func (m TestModel) View() string { tab: next field • shift+tab: prev • enter: confirm • esc: cancel ``` -## 项目结构更新 +## Project Structure Updates -### 新增目录和文件 +### New Directories and Files ``` aim/ ├── internal/ -│ ├── tui/ # TUI 模块 -│ │ ├── tui.go # TUI 工具函数 -│ │ ├── init.go # 初始化向导 -│ │ ├── selector.go # 模型选择器 -│ │ ├── test.go # 测试界面 -│ │ ├── config.go # 配置编辑器 -│ │ ├── styles.go # 样式定义 -│ │ └── components/ # 自定义组件 -│ │ ├── header.go # 头部组件 -│ │ ├── footer.go # 底部组件 -│ │ └── statusbar.go # 状态栏组件 +│ ├── tui/ # TUI module +│ │ ├── tui.go # TUI utility functions +│ │ ├── init.go # Initialization wizard +│ │ ├── selector.go # Model selector +│ │ ├── test.go # Test interface +│ │ ├── config.go # Configuration editor +│ │ ├── styles.go # Style definitions +│ │ └── components/ # Custom components +│ │ ├── header.go # Header component +│ │ ├── footer.go # Footer component +│ │ └── statusbar.go # Status bar component ``` -## 样式系统设计 +## Style System Design -### 配色方案 +### Color Scheme ```go // internal/tui/styles.go @@ -481,15 +481,15 @@ package tui import "github.com/charmbracelet/lipgloss" var ( - // 颜色定义 - colorPrimary = lipgloss.Color("#7B68EE") // 主色 - colorSuccess = lipgloss.Color("#00D787") // 成功/绿色 - colorWarning = lipgloss.Color("#FFAF00") // 警告/黄色 - colorError = lipgloss.Color("#FF5F87") // 错误/红色 - colorMuted = lipgloss.Color("#6C7086") // 次要文字 - colorBorder = lipgloss.Color("#45475A") // 边框 - - // 样式定义 + // Color definitions + colorPrimary = lipgloss.Color("#7B68EE") // Primary color + colorSuccess = lipgloss.Color("#00D787") // Success/green + colorWarning = lipgloss.Color("#FFAF00") // Warning/yellow + colorError = lipgloss.Color("#FF5F87") // Error/red + colorMuted = lipgloss.Color("#6C7086") // Muted text + colorBorder = lipgloss.Color("#45475A") // Border + + // Style definitions titleStyle = lipgloss.NewStyle(). Bold(true). Foreground(colorPrimary). @@ -518,7 +518,7 @@ var ( mutedStyle = lipgloss.NewStyle(). Foreground(colorMuted) - // 布局样式 + // Layout styles boxStyle = lipgloss.NewStyle(). Border(lipgloss.RoundedBorder()). BorderForeground(colorBorder). @@ -529,7 +529,7 @@ var ( Foreground(colorPrimary) ) -// 状态图标 +// Status icons const ( IconSuccess = "✓" IconError = "✗" @@ -540,9 +540,9 @@ const ( ) ``` -## 命令集成 +## Command Integration -### 更新命令定义 +### Update Command Definitions ```go // internal/cmd/init.go @@ -571,12 +571,12 @@ func runInit(cmd *cobra.Command, args []string) error { interactive, _ := cmd.Flags().GetBool("interactive") noTUI, _ := cmd.Flags().GetBool("no-tui") - // 判断是否使用 TUI + // Determine whether to use TUI if interactive && !noTUI && isTerminal() { return runInitTUI() } - // 否则使用传统方式 + // Otherwise use traditional method return runInitTraditional() } @@ -587,7 +587,7 @@ func runInitTUI() error { } ``` -### use 命令 +### use Command ```go // internal/cmd/use.go @@ -611,12 +611,12 @@ func runUse(cmd *cobra.Command, args []string) error { interactive, _ := cmd.Flags().GetBool("interactive") noTUI, _ := cmd.Flags().GetBool("no-tui") - // 无参数且未禁用 TUI,进入交互模式 + // No parameters and TUI not disabled, enter interactive mode if len(args) == 0 && !noTUI && isTerminal() { return runUseTUI() } - // 有参数,直接切换 + // Has parameters, switch directly if len(args) > 0 { return switchModel(args[0]) } @@ -625,23 +625,23 @@ func runUse(cmd *cobra.Command, args []string) error { } func runUseTUI() error { - // 加载可用的提供商 + // Load available providers providers := loadAvailableProviders() - // 启动选择器 + // Start selector p := tea.NewProgram(tui.NewSelectorModel(providers)) m, err := p.Run() if err != nil { return err } - // 获取选择结果 + // Get selection result selected := m.(tui.SelectorModel).GetSelected() return switchModel(selected) } ``` -### test 命令 +### test Command ```go // internal/cmd/test.go @@ -665,12 +665,12 @@ func runTest(cmd *cobra.Command, args []string) error { noTUI, _ := cmd.Flags().GetBool("no-tui") all, _ := cmd.Flags().GetBool("all") - // 交互模式或测试所有提供商时使用 TUI + // Use TUI when in interactive mode or testing all providers if (interactive || all) && !noTUI && isTerminal() { return runTestTUI(all) } - // 传统测试方式 + // Traditional test method return runTestTraditional(args) } @@ -688,9 +688,9 @@ func runTestTUI(testAll bool) error { } ``` -## 辅助功能 +## Helper Functions -### 终端检测 +### Terminal Detection ```go // internal/tui/tui.go @@ -701,24 +701,24 @@ import ( "golang.org/x/term" ) -// isTerminal 检测是否在终端环境 +// isTerminal detects if running in terminal environment func isTerminal() bool { return term.IsTerminal(int(os.Stdout.Fd())) } -// getTerminalSize 获取终端尺寸 +// getTerminalSize gets terminal dimensions func getTerminalSize() (width, height int, err error) { return term.GetSize(int(os.Stdout.Fd())) } -// 检测是否支持颜色 +// Detect if color is supported func supportsColor() bool { term := os.Getenv("TERM") return term != "dumb" && term != "" } ``` -### 响应式布局 +### Responsive Layout ```go // internal/tui/components/layout.go @@ -736,7 +736,7 @@ func NewLayout(width, height int) Layout { } func (l Layout) Box(content string) string { - boxWidth := l.width - 4 // 留出边框和 padding + boxWidth := l.width - 4 // Leave space for border and padding style := lipgloss.NewStyle(). Width(boxWidth). @@ -757,9 +757,9 @@ func (l Layout) Center(content string) string { } ``` -## 配置选项 +## Configuration Options -### 添加 TUI 配置 +### Add TUI Configuration ```yaml # configs/default.yaml @@ -768,36 +768,36 @@ settings: default_tool: claude-code # ... - # TUI 设置 + # TUI settings tui: - enabled: true # 启用 TUI + enabled: true # Enable TUI color_scheme: auto # auto/light/dark - animations: true # 启用动画 - confirm_actions: true # 确认重要操作 + animations: true # Enable animations + confirm_actions: true # Confirm important actions ``` -## 用户体验增强 +## User Experience Enhancements -### 键盘快捷键 +### Keyboard Shortcuts -全局快捷键: -- `↑/k`: 向上 -- `↓/j`: 向下 -- `enter`: 确认/选择 -- `esc`: 返回/取消 -- `q/ctrl+c`: 退出 -- `?`: 帮助 +Global shortcuts: +- `↑/k`: Up +- `↓/j`: Down +- `enter`: Confirm/Select +- `esc`: Back/Cancel +- `q/ctrl+c`: Quit +- `?`: Help -特定场景: -- `/`: 过滤/搜索 -- `t`: 快速测试 -- `e`: 编辑 -- `r`: 刷新 +Specific scenarios: +- `/`: Filter/Search +- `t`: Quick test +- `e`: Edit +- `r`: Refresh -### 动画效果 +### Animation Effects ```go -// 使用 spinner +// Using spinner import "github.com/charmbracelet/bubbles/spinner" s := spinner.New() @@ -805,18 +805,18 @@ s.Spinner = spinner.Dot s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205")) ``` -### 进度反馈 +### Progress Feedback ```go -// 使用 progress bar +// Using progress bar import "github.com/charmbracelet/bubbles/progress" p := progress.New(progress.WithDefaultGradient()) ``` -## 测试 +## Testing -### TUI 测试策略 +### TUI Testing Strategy ```go // internal/tui/selector_test.go @@ -835,40 +835,40 @@ func TestSelectorModel(t *testing.T) { m := NewSelectorModel(providers) - // 测试初始状态 + // Test initial state if m.selected != 0 { t.Errorf("Expected selected=0, got %d", m.selected) } - // 模拟按键 + // Simulate key press m, _ = m.Update(tea.KeyMsg{Type: tea.KeyDown}) - // 验证状态变化 + // Verify state change // ... } ``` -## 实施计划更新 +## Implementation Plan Updates -### 阶段 2.5: TUI 集成(新增,Week 2-3 之间) +### Phase 2.5: TUI Integration (New, Between Week 2-3) -**任务**: -- [ ] 添加 Bubble Tea 依赖 -- [ ] 创建基础 TUI 框架 -- [ ] 实现初始化向导 TUI -- [ ] 实现模型选择器 TUI -- [ ] 实现测试界面 TUI -- [ ] 更新所有命令支持 TUI -- [ ] 编写 TUI 单元测试 +**Tasks**: +- [ ] Add Bubble Tea dependencies +- [ ] Create basic TUI framework +- [ ] Implement initialization wizard TUI +- [ ] Implement model selector TUI +- [ ] Implement test interface TUI +- [ ] Update all commands to support TUI +- [ ] Write TUI unit tests -**验收标准**: +**Acceptance Criteria**: ```bash -aim init # 启动 TUI 向导 -aim use # 启动 TUI 选择器 -aim test --interactive # 启动 TUI 测试界面 +aim init # Start TUI wizard +aim use # Start TUI selector +aim test --interactive # Start TUI test interface ``` -## 依赖更新 +## Dependency Updates ### go.mod @@ -882,7 +882,7 @@ require ( github.com/spf13/viper v1.18.0 gopkg.in/yaml.v3 v3.0.1 - // TUI 依赖 + // TUI dependencies github.com/charmbracelet/bubbletea v0.25.0 github.com/charmbracelet/bubbles v0.18.0 github.com/charmbracelet/lipgloss v0.9.1 @@ -890,26 +890,26 @@ require ( ) ``` -## 最佳实践 +## Best Practices -### 1. 优雅降级 -如果终端不支持 TUI,自动回退到传统命令行模式 +### 1. Graceful Degradation +If terminal doesn't support TUI, automatically fall back to traditional command-line mode -### 2. 响应式设计 -根据终端尺寸调整布局 +### 2. Responsive Design +Adjust layout based on terminal size -### 3. 键盘优先 -所有操作都可以通过键盘完成 +### 3. Keyboard First +All operations can be completed via keyboard -### 4. 即时反馈 -提供实时的视觉反馈和状态更新 +### 4. Instant Feedback +Provide real-time visual feedback and status updates -### 5. 可访问性 -支持屏幕阅读器(通过 `--no-tui` 标志) +### 5. Accessibility +Support screen readers (via `--no-tui` flag) -## 参考资源 +## Reference Resources - [Bubble Tea Documentation](https://github.com/charmbracelet/bubbletea) - [Bubbles Components](https://github.com/charmbracelet/bubbles) - [Lipgloss Styling](https://github.com/charmbracelet/lipgloss) -- [Bubble Tea Examples](https://github.com/charmbracelet/bubbletea/tree/master/examples) +- [Bubble Tea Examples](https://github.com/charmbracelet/bubbletea/tree/master/examples) \ No newline at end of file diff --git a/docs/tui-interface/TUI_DESIGN_EN.md b/docs/tui-interface/tui_design_cn.md similarity index 76% rename from docs/tui-interface/TUI_DESIGN_EN.md rename to docs/tui-interface/tui_design_cn.md index 36ed085..9ed5597 100644 --- a/docs/tui-interface/TUI_DESIGN_EN.md +++ b/docs/tui-interface/tui_design_cn.md @@ -1,63 +1,63 @@ -# AIM TUI Design Documentation +# AIM TUI 设计文档 -## Overview +## 概述 -A friendly terminal user interface (TUI) for AIM based on the [Bubble Tea](https://github.com/charmbracelet/bubbletea) framework. +基于 [Bubble Tea](https://github.com/charmbracelet/bubbletea) 框架,为 AIM 提供友好的终端交互界面(TUI)。 -## Technology Stack +## 技术栈 -### Core Libraries -- **[bubbletea](https://github.com/charmbracelet/bubbletea)** - TUI framework (Elm Architecture) -- **[bubbles](https://github.com/charmbracelet/bubbles)** - Pre-built components (list, textinput, spinner, etc.) -- **[lipgloss](https://github.com/charmbracelet/lipgloss)** - Styling and layout +### 核心库 +- **[bubbletea](https://github.com/charmbracelet/bubbletea)** - TUI 框架(Elm Architecture) +- **[bubbles](https://github.com/charmbracelet/bubbles)** - 预制组件(list, textinput, spinner, etc.) +- **[lipgloss](https://github.com/charmbracelet/lipgloss)** - 样式和布局 -### Dependency Installation +### 依赖安装 ```bash go get github.com/charmbracelet/bubbletea go get github.com/charmbracelet/bubbles go get github.com/charmbracelet/lipgloss ``` -## TUI Command Design +## TUI 命令设计 -### 1. Interactive Mode Commands +### 1. 交互式模式命令 ```bash -# Start interactive configuration wizard -aim init # Interactive initialization (default TUI) -aim init --interactive # Explicitly specify interactive -aim init --no-tui # Non-interactive (original method) +# 启动交互式配置向导 +aim init # 交互式初始化(默认 TUI) +aim init --interactive # 明确指定交互式 +aim init --no-tui # 非交互式(原始方式) -# Interactive model selection -aim use # Without parameters, enter TUI selector -aim use --interactive # Interactive selection +# 交互式选择模型 +aim use # 不带参数,进入 TUI 选择器 +aim use --interactive # 交互式选择 -# Interactive test interface -aim test --interactive # TUI test interface with real-time progress +# 交互式测试界面 +aim test --interactive # TUI 测试界面,实时显示进度 -# Interactive configuration editing -aim config --interactive # TUI configuration editor +# 交互式配置编辑 +aim config --interactive # TUI 配置编辑器 ``` -### 2. Command Priority +### 2. 命令优先级 ``` -Explicit flags > TUI mode (no parameters) > Non-interactive mode (with parameters) +明确的标志 > TUI 模式(无参数时)> 非交互模式(有参数时) ``` -**Examples**: +**示例**: ```bash -aim use # → TUI selector -aim use deepseek # → Direct switch (non-interactive) -aim use --interactive # → TUI selector (forced) -aim use deepseek --no-tui # → Direct switch (disable TUI) +aim use # → TUI 选择器 +aim use deepseek # → 直接切换(非交互) +aim use --interactive # → TUI 选择器(强制) +aim use deepseek --no-tui # → 直接切换(禁用 TUI) ``` -## TUI Interface Design +## TUI 界面设计 -### 1. Initialization Wizard (init) +### 1. 初始化向导 (init) -#### Interface Layout +#### 界面布局 ``` ┌─────────────────────────────────────────────────────────┐ │ AIM Setup Wizard │ @@ -78,7 +78,7 @@ aim use deepseek --no-tui # → Direct switch (disable TUI) └─────────────────────────────────────────────────────────┘ ``` -#### Step Flow +#### 步骤流程 ``` Step 1: Language Selection Step 2: Default Tool Selection @@ -87,7 +87,7 @@ Step 4: API Keys Setup Step 5: Summary & Confirmation ``` -#### Implementation Code Framework +#### 实现代码框架 ```go // internal/tui/init.go package tui @@ -149,14 +149,14 @@ func (m InitModel) View() string { func (m InitModel) nextStep() (tea.Model, tea.Cmd) { m.step++ - // Switch to next step UI + // 切换到下一步的 UI return m, nil } ``` -### 2. Model Selector (use) +### 2. 模型选择器 (use) -#### Interface Layout +#### 界面布局 ``` ┌─────────────────────────────────────────────────────────┐ │ Select AI Model Provider │ @@ -184,16 +184,16 @@ func (m InitModel) nextStep() (tea.Model, tea.Cmd) { ↑/↓: navigate • enter: select • /: filter • t: test • q: quit ``` -#### Features -- **Real-time Filtering**: Press `/` to enter filter mode -- **Status Indicators**: Show connection status and latency -- **Quick Test**: Press `t` to quickly test selected provider -- **Color Coding**: - - Green ✓: Available - - Red ✗: Unavailable - - Yellow ⚠: Warning +#### 功能特性 +- **实时筛选**: 输入 `/` 进入过滤模式 +- **状态指示**: 显示连接状态和延迟 +- **快速测试**: 按 `t` 快速测试选中的提供商 +- **颜色编码**: + - 绿色 ✓: 可用 + - 红色 ✗: 不可用 + - 黄色 ⚠: 警告 -#### Implementation Code Framework +#### 实现代码框架 ```go // internal/tui/selector.go package tui @@ -259,7 +259,7 @@ func (m SelectorModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case "enter": m.filtering = false m.filter.Blur() - // Apply filter + // 应用过滤 return m, m.applyFilter() } } else { @@ -269,10 +269,10 @@ func (m SelectorModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.filter.Focus() return m, nil case "t": - // Test selected provider + // 测试选中的提供商 return m, m.testProvider() case "enter": - // Select and apply + // 选择并应用 return m, m.selectProvider() } } @@ -290,9 +290,9 @@ func (m SelectorModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } ``` -### 3. Test Interface (test) +### 3. 测试界面 (test) -#### Interface Layout +#### 界面布局 ``` ┌─────────────────────────────────────────────────────────┐ │ Testing Provider Connections │ @@ -320,13 +320,13 @@ func (m SelectorModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { └─────────────────────────────────────────────────────────┘ ``` -#### Features -- **Real-time Progress**: Display test progress bar -- **Concurrent Testing**: Multiple providers tested in parallel -- **Animation Effects**: Spinner animation during testing -- **Detailed Reports**: View failure details +#### 功能特性 +- **实时进度**: 显示测试进度条 +- **并发测试**: 多个提供商并行测试 +- **动画效果**: 测试中的 spinner 动画 +- **详细报告**: 可查看失败详情 -#### Implementation Code Framework +#### 实现代码框架 ```go // internal/tui/test.go package tui @@ -383,9 +383,9 @@ func (m TestModel) Init() tea.Cmd { func (m TestModel) startTests() tea.Cmd { return func() tea.Msg { - // Start async testing - // Use goroutine to test each provider - // Send testCompleteMsg when complete + // 启动异步测试 + // 使用 goroutine 测试每个提供商 + // 完成后发送 testCompleteMsg return testCompleteMsg{} } } @@ -413,14 +413,14 @@ func (m TestModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } func (m TestModel) View() string { - // Render test progress interface + // 渲染测试进度界面 return "" } ``` -### 4. Configuration Editor (config) +### 4. 配置编辑器 (config) -#### Interface Layout +#### 界面布局 ``` ┌─────────────────────────────────────────────────────────┐ │ Configuration Editor │ @@ -450,29 +450,29 @@ func (m TestModel) View() string { tab: next field • shift+tab: prev • enter: confirm • esc: cancel ``` -## Project Structure Updates +## 项目结构更新 -### New Directories and Files +### 新增目录和文件 ``` aim/ ├── internal/ -│ ├── tui/ # TUI module -│ │ ├── tui.go # TUI utility functions -│ │ ├── init.go # Initialization wizard -│ │ ├── selector.go # Model selector -│ │ ├── test.go # Test interface -│ │ ├── config.go # Configuration editor -│ │ ├── styles.go # Style definitions -│ │ └── components/ # Custom components -│ │ ├── header.go # Header component -│ │ ├── footer.go # Footer component -│ │ └── statusbar.go # Status bar component +│ ├── tui/ # TUI 模块 +│ │ ├── tui.go # TUI 工具函数 +│ │ ├── init.go # 初始化向导 +│ │ ├── selector.go # 模型选择器 +│ │ ├── test.go # 测试界面 +│ │ ├── config.go # 配置编辑器 +│ │ ├── styles.go # 样式定义 +│ │ └── components/ # 自定义组件 +│ │ ├── header.go # 头部组件 +│ │ ├── footer.go # 底部组件 +│ │ └── statusbar.go # 状态栏组件 ``` -## Style System Design +## 样式系统设计 -### Color Scheme +### 配色方案 ```go // internal/tui/styles.go @@ -481,15 +481,15 @@ package tui import "github.com/charmbracelet/lipgloss" var ( - // Color definitions - colorPrimary = lipgloss.Color("#7B68EE") // Primary color - colorSuccess = lipgloss.Color("#00D787") // Success/green - colorWarning = lipgloss.Color("#FFAF00") // Warning/yellow - colorError = lipgloss.Color("#FF5F87") // Error/red - colorMuted = lipgloss.Color("#6C7086") // Muted text - colorBorder = lipgloss.Color("#45475A") // Border - - // Style definitions + // 颜色定义 + colorPrimary = lipgloss.Color("#7B68EE") // 主色 + colorSuccess = lipgloss.Color("#00D787") // 成功/绿色 + colorWarning = lipgloss.Color("#FFAF00") // 警告/黄色 + colorError = lipgloss.Color("#FF5F87") // 错误/红色 + colorMuted = lipgloss.Color("#6C7086") // 次要文字 + colorBorder = lipgloss.Color("#45475A") // 边框 + + // 样式定义 titleStyle = lipgloss.NewStyle(). Bold(true). Foreground(colorPrimary). @@ -518,7 +518,7 @@ var ( mutedStyle = lipgloss.NewStyle(). Foreground(colorMuted) - // Layout styles + // 布局样式 boxStyle = lipgloss.NewStyle(). Border(lipgloss.RoundedBorder()). BorderForeground(colorBorder). @@ -529,7 +529,7 @@ var ( Foreground(colorPrimary) ) -// Status icons +// 状态图标 const ( IconSuccess = "✓" IconError = "✗" @@ -540,9 +540,9 @@ const ( ) ``` -## Command Integration +## 命令集成 -### Update Command Definitions +### 更新命令定义 ```go // internal/cmd/init.go @@ -571,12 +571,12 @@ func runInit(cmd *cobra.Command, args []string) error { interactive, _ := cmd.Flags().GetBool("interactive") noTUI, _ := cmd.Flags().GetBool("no-tui") - // Determine whether to use TUI + // 判断是否使用 TUI if interactive && !noTUI && isTerminal() { return runInitTUI() } - // Otherwise use traditional method + // 否则使用传统方式 return runInitTraditional() } @@ -587,7 +587,7 @@ func runInitTUI() error { } ``` -### use Command +### use 命令 ```go // internal/cmd/use.go @@ -611,12 +611,12 @@ func runUse(cmd *cobra.Command, args []string) error { interactive, _ := cmd.Flags().GetBool("interactive") noTUI, _ := cmd.Flags().GetBool("no-tui") - // No parameters and TUI not disabled, enter interactive mode + // 无参数且未禁用 TUI,进入交互模式 if len(args) == 0 && !noTUI && isTerminal() { return runUseTUI() } - // Has parameters, switch directly + // 有参数,直接切换 if len(args) > 0 { return switchModel(args[0]) } @@ -625,23 +625,23 @@ func runUse(cmd *cobra.Command, args []string) error { } func runUseTUI() error { - // Load available providers + // 加载可用的提供商 providers := loadAvailableProviders() - // Start selector + // 启动选择器 p := tea.NewProgram(tui.NewSelectorModel(providers)) m, err := p.Run() if err != nil { return err } - // Get selection result + // 获取选择结果 selected := m.(tui.SelectorModel).GetSelected() return switchModel(selected) } ``` -### test Command +### test 命令 ```go // internal/cmd/test.go @@ -665,12 +665,12 @@ func runTest(cmd *cobra.Command, args []string) error { noTUI, _ := cmd.Flags().GetBool("no-tui") all, _ := cmd.Flags().GetBool("all") - // Use TUI when in interactive mode or testing all providers + // 交互模式或测试所有提供商时使用 TUI if (interactive || all) && !noTUI && isTerminal() { return runTestTUI(all) } - // Traditional test method + // 传统测试方式 return runTestTraditional(args) } @@ -688,9 +688,9 @@ func runTestTUI(testAll bool) error { } ``` -## Helper Functions +## 辅助功能 -### Terminal Detection +### 终端检测 ```go // internal/tui/tui.go @@ -701,24 +701,24 @@ import ( "golang.org/x/term" ) -// isTerminal detects if running in terminal environment +// isTerminal 检测是否在终端环境 func isTerminal() bool { return term.IsTerminal(int(os.Stdout.Fd())) } -// getTerminalSize gets terminal dimensions +// getTerminalSize 获取终端尺寸 func getTerminalSize() (width, height int, err error) { return term.GetSize(int(os.Stdout.Fd())) } -// Detect if color is supported +// 检测是否支持颜色 func supportsColor() bool { term := os.Getenv("TERM") return term != "dumb" && term != "" } ``` -### Responsive Layout +### 响应式布局 ```go // internal/tui/components/layout.go @@ -736,7 +736,7 @@ func NewLayout(width, height int) Layout { } func (l Layout) Box(content string) string { - boxWidth := l.width - 4 // Leave space for border and padding + boxWidth := l.width - 4 // 留出边框和 padding style := lipgloss.NewStyle(). Width(boxWidth). @@ -757,9 +757,9 @@ func (l Layout) Center(content string) string { } ``` -## Configuration Options +## 配置选项 -### Add TUI Configuration +### 添加 TUI 配置 ```yaml # configs/default.yaml @@ -768,36 +768,36 @@ settings: default_tool: claude-code # ... - # TUI settings + # TUI 设置 tui: - enabled: true # Enable TUI + enabled: true # 启用 TUI color_scheme: auto # auto/light/dark - animations: true # Enable animations - confirm_actions: true # Confirm important actions + animations: true # 启用动画 + confirm_actions: true # 确认重要操作 ``` -## User Experience Enhancements +## 用户体验增强 -### Keyboard Shortcuts +### 键盘快捷键 -Global shortcuts: -- `↑/k`: Up -- `↓/j`: Down -- `enter`: Confirm/Select -- `esc`: Back/Cancel -- `q/ctrl+c`: Quit -- `?`: Help +全局快捷键: +- `↑/k`: 向上 +- `↓/j`: 向下 +- `enter`: 确认/选择 +- `esc`: 返回/取消 +- `q/ctrl+c`: 退出 +- `?`: 帮助 -Specific scenarios: -- `/`: Filter/Search -- `t`: Quick test -- `e`: Edit -- `r`: Refresh +特定场景: +- `/`: 过滤/搜索 +- `t`: 快速测试 +- `e`: 编辑 +- `r`: 刷新 -### Animation Effects +### 动画效果 ```go -// Using spinner +// 使用 spinner import "github.com/charmbracelet/bubbles/spinner" s := spinner.New() @@ -805,18 +805,18 @@ s.Spinner = spinner.Dot s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205")) ``` -### Progress Feedback +### 进度反馈 ```go -// Using progress bar +// 使用 progress bar import "github.com/charmbracelet/bubbles/progress" p := progress.New(progress.WithDefaultGradient()) ``` -## Testing +## 测试 -### TUI Testing Strategy +### TUI 测试策略 ```go // internal/tui/selector_test.go @@ -835,40 +835,40 @@ func TestSelectorModel(t *testing.T) { m := NewSelectorModel(providers) - // Test initial state + // 测试初始状态 if m.selected != 0 { t.Errorf("Expected selected=0, got %d", m.selected) } - // Simulate key press + // 模拟按键 m, _ = m.Update(tea.KeyMsg{Type: tea.KeyDown}) - // Verify state change + // 验证状态变化 // ... } ``` -## Implementation Plan Updates +## 实施计划更新 -### Phase 2.5: TUI Integration (New, Between Week 2-3) +### 阶段 2.5: TUI 集成(新增,Week 2-3 之间) -**Tasks**: -- [ ] Add Bubble Tea dependencies -- [ ] Create basic TUI framework -- [ ] Implement initialization wizard TUI -- [ ] Implement model selector TUI -- [ ] Implement test interface TUI -- [ ] Update all commands to support TUI -- [ ] Write TUI unit tests +**任务**: +- [ ] 添加 Bubble Tea 依赖 +- [ ] 创建基础 TUI 框架 +- [ ] 实现初始化向导 TUI +- [ ] 实现模型选择器 TUI +- [ ] 实现测试界面 TUI +- [ ] 更新所有命令支持 TUI +- [ ] 编写 TUI 单元测试 -**Acceptance Criteria**: +**验收标准**: ```bash -aim init # Start TUI wizard -aim use # Start TUI selector -aim test --interactive # Start TUI test interface +aim init # 启动 TUI 向导 +aim use # 启动 TUI 选择器 +aim test --interactive # 启动 TUI 测试界面 ``` -## Dependency Updates +## 依赖更新 ### go.mod @@ -882,7 +882,7 @@ require ( github.com/spf13/viper v1.18.0 gopkg.in/yaml.v3 v3.0.1 - // TUI dependencies + // TUI 依赖 github.com/charmbracelet/bubbletea v0.25.0 github.com/charmbracelet/bubbles v0.18.0 github.com/charmbracelet/lipgloss v0.9.1 @@ -890,26 +890,26 @@ require ( ) ``` -## Best Practices +## 最佳实践 -### 1. Graceful Degradation -If terminal doesn't support TUI, automatically fall back to traditional command-line mode +### 1. 优雅降级 +如果终端不支持 TUI,自动回退到传统命令行模式 -### 2. Responsive Design -Adjust layout based on terminal size +### 2. 响应式设计 +根据终端尺寸调整布局 -### 3. Keyboard First -All operations can be completed via keyboard +### 3. 键盘优先 +所有操作都可以通过键盘完成 -### 4. Instant Feedback -Provide real-time visual feedback and status updates +### 4. 即时反馈 +提供实时的视觉反馈和状态更新 -### 5. Accessibility -Support screen readers (via `--no-tui` flag) +### 5. 可访问性 +支持屏幕阅读器(通过 `--no-tui` 标志) -## Reference Resources +## 参考资源 - [Bubble Tea Documentation](https://github.com/charmbracelet/bubbletea) - [Bubbles Components](https://github.com/charmbracelet/bubbles) - [Lipgloss Styling](https://github.com/charmbracelet/lipgloss) -- [Bubble Tea Examples](https://github.com/charmbracelet/bubbletea/tree/master/examples) \ No newline at end of file +- [Bubble Tea Examples](https://github.com/charmbracelet/bubbletea/tree/master/examples) diff --git a/internal/cmd/config.go b/internal/cmd/config.go index 76a8727..c29a878 100644 --- a/internal/cmd/config.go +++ b/internal/cmd/config.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "os/exec" + "strconv" "github.com/fakecore/aim/internal/config" "github.com/spf13/cobra" @@ -173,7 +174,7 @@ func runConfigShow(cmd *cobra.Command, args []string) error { fmt.Printf(" Enabled: true\n") } if len(tool.Profiles) > 0 { - fmt.Printf(" Profiles: %s\n", getProfileList(tool.Profiles)) + fmt.Printf(" Profiles: %s\n", config.GetProfileList(tool.Profiles)) // Show detailed profile information for profileName, profile := range tool.Profiles { fmt.Printf(" %s:\n", profileName) @@ -235,8 +236,10 @@ func runConfigSet(cmd *cobra.Command, args []string) error { case "default-key": cfg.Settings.DefaultKey = value case "timeout": - // TODO: Parse int - return + if timeout, err := strconv.Atoi(value); err == nil { + cfg.Settings.Timeout = timeout + } + // If invalid, keep the existing value (silently ignore) case "language": cfg.Settings.Language = value default: @@ -383,23 +386,3 @@ func runConfigEdit(cmd *cobra.Command, args []string) error { return nil } -// getProfileList returns a comma-separated list of profile names -func getProfileList(profiles map[string]*config.ToolProfile) string { - var names []string - for name := range profiles { - names = append(names, name) - } - - if len(names) == 1 { - return names[0] - } - - result := "" - for i, name := range names { - if i > 0 { - result += ", " - } - result += name - } - return result -} diff --git a/internal/config/loader.go b/internal/config/loader.go index 1e681dd..5435ba6 100644 --- a/internal/config/loader.go +++ b/internal/config/loader.go @@ -52,18 +52,18 @@ func (l *Loader) GetLocalPath() string { // Load loads and merges configuration from all sources func (l *Loader) Load() (*Config, error) { - // 1. Start with default configuration - cfg := DefaultConfig() - - // 2. Load global configuration + // 1. Load global configuration (this should be the main config file) global, err := l.loadGlobal() - if err == nil { - cfg = l.mergeConfigs(cfg, global) - } else if !os.IsNotExist(err) { + if err != nil { + if os.IsNotExist(err) { + return nil, fmt.Errorf("global configuration file not found at %s. Please run 'aim config init' to create it", l.globalPath) + } return nil, fmt.Errorf("failed to load global config: %w", err) } - // 3. Load local/project configuration + cfg := global + + // 2. Load local/project configuration (if exists) local, err := l.loadLocal() if err == nil { cfg = l.mergeConfigs(cfg, local) @@ -71,13 +71,13 @@ func (l *Loader) Load() (*Config, error) { return nil, fmt.Errorf("failed to load local config: %w", err) } - // 4. Apply environment variable overrides + // 3. Apply environment variable overrides cfg = l.applyEnvOverrides(cfg) - // 5. Expand environment variable references in config + // 4. Expand environment variable references in config cfg = l.expandEnvVars(cfg) - // 6. Validate final configuration + // 5. Validate final configuration if err := cfg.Validate(); err != nil { return nil, fmt.Errorf("invalid configuration: %w", err) } @@ -192,11 +192,16 @@ func (l *Loader) InitGlobal() error { // InitGlobalSilent initializes the global configuration file without checking if it exists // This is used for automatic initialization func (l *Loader) InitGlobalSilent() error { - // Create default v2.0 config + // Start with base configuration from default.yaml cfg := DefaultConfig() + // Add any additional builtin providers/tools using merge logic (won't override existing ones) cfg = l.addBuiltinProviders(cfg) - cfg = l.addBuiltinTools(cfg) - cfg = l.addBuiltinAliases(cfg) + + var err error + cfg, err = l.addBuiltinTools(cfg) + if err != nil { + return err + } return l.SaveGlobal(cfg) } @@ -215,21 +220,23 @@ func (l *Loader) InitLocal() error { return fmt.Errorf("local config already exists at %s", localPath) } - // Create minimal local config + // Create minimal local config (local configs should be minimal by design) cfg := &Config{ Version: "1.0", Settings: Settings{ DefaultProvider: "deepseek", }, - Keys: make(map[string]*Key), - Tools: make(map[string]*ToolConfig), + Keys: make(map[string]*Key), // Local config starts with empty keys + Tools: make(map[string]*ToolConfig), // Local config starts with empty tools Aliases: make(map[string]string), } - // Add built-in tools and providers + // Add built-in tools and providers from default config (for reference, but with minimal profiles) cfg = l.addBuiltinProviders(cfg) - cfg = l.addBuiltinTools(cfg) - cfg = l.addBuiltinAliases(cfg) + cfg, err = l.addBuiltinTools(cfg) + if err != nil { + return err + } return l.SaveLocal(cfg) } @@ -373,7 +380,7 @@ func (l *Loader) expandEnvVars(cfg *Config) *Config { return &result } -// addBuiltinProviders adds built-in provider configurations +// addBuiltinProviders adds built-in provider configurations using merge logic // This adds OpenAI-compatible API endpoints for use with codex, aider, and other OpenAI-compatible tools func (l *Loader) addBuiltinProviders(cfg *Config) *Config { if cfg.Providers == nil { @@ -415,10 +422,13 @@ func (l *Loader) addBuiltinProviders(cfg *Config) *Config { configName = providerName + endpoint.Suffix } - cfg.Providers[configName] = &Provider{ - BaseURL: toolCfg.BaseURL, - Model: toolCfg.Model, - Timeout: toolCfg.Timeout, + // Merge logic: only add if provider doesn't already exist + if _, exists := cfg.Providers[configName]; !exists { + cfg.Providers[configName] = &Provider{ + BaseURL: toolCfg.BaseURL, + Model: toolCfg.Model, + Timeout: toolCfg.Timeout, + } } } } @@ -426,40 +436,40 @@ func (l *Loader) addBuiltinProviders(cfg *Config) *Config { return cfg } -// addBuiltinTools adds built-in tool configurations with proper profiles -func (l *Loader) addBuiltinTools(cfg *Config) *Config { +// addBuiltinTools adds built-in tool configurations using merge logic +// This is primarily used for InitLocal() where we need minimal tool references +func (l *Loader) addBuiltinTools(cfg *Config) (*Config, error) { if cfg.Tools == nil { cfg.Tools = make(map[string]*ToolConfig) } - // Load default tool configurations from embedded default config - defaultTools := l.loadDefaultTools() + // For InitLocal: if no tools exist, add basic tool references from default config + // For InitGlobalSilent: this won't add anything since DefaultConfig() already loaded complete tools + if len(cfg.Tools) == 0 { + // Load default tool configurations from embedded default config + defaultTools, err := l.loadDefaultTools() + if err != nil { + return nil, err + } - // Merge default tools with existing configuration - for toolName, defaultTool := range defaultTools { - // If tool already exists, preserve it but merge with default - if existingTool, exists := cfg.Tools[toolName]; exists { - // Merge existing tool with default tool - mergedTool := l.mergeToolConfig(existingTool, defaultTool) - cfg.Tools[toolName] = mergedTool - } else { - // Add new tool from defaults + // Add basic tool references (minimal versions) + for toolName, defaultTool := range defaultTools { cfg.Tools[toolName] = defaultTool } } - return cfg + return cfg, nil } // loadDefaultTools loads tool configurations from embedded default config -func (l *Loader) loadDefaultTools() map[string]*ToolConfig { +func (l *Loader) loadDefaultTools() (map[string]*ToolConfig, error) { // Use embedded config data directly var defaultConfig Config if err := yaml.Unmarshal(configs.DefaultConfigData, &defaultConfig); err != nil { - panic(fmt.Sprintf("Failed to unmarshal embedded default config: %v", err)) + return nil, fmt.Errorf("failed to unmarshal embedded default config: %w", err) } - return defaultConfig.Tools + return defaultConfig.Tools, nil } // mergeToolConfig merges existing tool configuration with default tool configuration @@ -498,12 +508,45 @@ func (l *Loader) mergeToolConfig(existing, defaultTool *ToolConfig) *ToolConfig return &merged } -// addBuiltinAliases adds built-in aliases -// DISABLED: Alias functionality temporarily disabled -func (l *Loader) addBuiltinAliases(cfg *Config) *Config { - // Temporarily disable adding built-in aliases - return cfg +// mergeToolConfigMinimal merges existing tool configuration with minimal tool configuration +func (l *Loader) mergeToolConfigMinimal(existing, minimalTool *ToolConfig) *ToolConfig { + merged := *existing // Copy existing tool + // Keep existing profiles, don't merge with minimal (empty) profiles + if merged.Profiles == nil { + merged.Profiles = make(map[string]*ToolProfile) + } + + // Use minimal field mapping if existing has none + if len(merged.FieldMapping) == 0 && minimalTool.FieldMapping != nil { + merged.FieldMapping = make(map[string]string) + for k, v := range minimalTool.FieldMapping { + merged.FieldMapping[k] = v + } + } + + // Use minimal defaults if existing has none + if merged.Defaults == nil && minimalTool.Defaults != nil { + merged.Defaults = &ToolDefaults{ + Timeout: minimalTool.Defaults.Timeout, + } + if minimalTool.Defaults.Env != nil { + merged.Defaults.Env = make(map[string]string) + for k, v := range minimalTool.Defaults.Env { + merged.Defaults.Env[k] = v + } + } + } + + // Use minimal command and enabled settings if not set + if merged.Command == "" { + merged.Command = minimalTool.Command + } + if !merged.Enabled { + merged.Enabled = minimalTool.Enabled + } + + return &merged } // copyStringMap creates a deep copy of a string map diff --git a/internal/config/types.go b/internal/config/types.go index 7dfdf5e..42ea575 100644 --- a/internal/config/types.go +++ b/internal/config/types.go @@ -1,6 +1,11 @@ package config -import "time" +import ( + "time" + + "github.com/fakecore/aim/configs" + "gopkg.in/yaml.v3" +) // Config represents the complete v1.0 configuration structure type Config struct { @@ -38,11 +43,11 @@ type Provider struct { // ToolProfile represents a tool-specific provider configuration type ToolProfile struct { - Provider string `yaml:"provider"` - BaseURL string `yaml:"base_url,omitempty"` - Model string `yaml:"model,omitempty"` - Timeout int `yaml:"timeout,omitempty"` - Env map[string]string `yaml:"env,omitempty"` + Provider string `yaml:"provider"` + BaseURL string `yaml:"base_url,omitempty"` + Model string `yaml:"model,omitempty"` + Timeout int `yaml:"timeout,omitempty"` + Env map[string]string `yaml:"env,omitempty"` FieldMapping map[string]string `yaml:"field_mapping,omitempty"` } @@ -54,19 +59,19 @@ type ToolDefaults struct { // ToolConfig represents a tool configuration type ToolConfig struct { - Command string `yaml:"command"` - Enabled bool `yaml:"enabled,omitempty"` - Defaults *ToolDefaults `yaml:"defaults,omitempty"` - FieldMapping map[string]string `yaml:"field_mapping,omitempty"` - Profiles map[string]*ToolProfile `yaml:"profiles"` + Command string `yaml:"command"` + Enabled bool `yaml:"enabled,omitempty"` + Defaults *ToolDefaults `yaml:"defaults,omitempty"` + FieldMapping map[string]string `yaml:"field_mapping,omitempty"` + Profiles map[string]*ToolProfile `yaml:"profiles"` } // RuntimeConfig represents the final resolved configuration at runtime type RuntimeConfig struct { Tool string Key string - Profile string // Profile name used - Provider string // Actual provider name that the profile points to + Profile string // Profile name used + Provider string // Actual provider name that the profile points to APIKey string BaseURL string Model string @@ -76,19 +81,24 @@ type RuntimeConfig struct { // DefaultConfig returns the default v1.0 configuration func DefaultConfig() *Config { - return &Config{ - Version: "1.0", - Settings: Settings{ - DefaultTool: "claude-code", - DefaultProvider: "deepseek", - Timeout: 60000, - Language: "en", - }, - Keys: make(map[string]*Key), - Providers: make(map[string]*Provider), - Tools: make(map[string]*ToolConfig), - Aliases: make(map[string]string), + var cfg Config + if err := yaml.Unmarshal(configs.DefaultConfigData, &cfg); err != nil { + // Fallback to minimal config if default YAML fails to load + return &Config{ + Version: "1.0", + Settings: Settings{ + DefaultTool: "claude-code", + DefaultProvider: "deepseek", + Timeout: 60000, + Language: "en", + }, + Keys: make(map[string]*Key), + Providers: make(map[string]*Provider), + Tools: make(map[string]*ToolConfig), + Aliases: make(map[string]string), + } } + return &cfg } // Validate validates the v1.0 configuration @@ -104,18 +114,10 @@ func (c *Config) Validate() error { } // ResolveAlias resolves a tool alias to its actual name -// DISABLED: Alias functionality temporarily disabled +// TODO: Re-enable alias functionality when needed func (c *Config) ResolveAlias(name string) string { // Temporarily disable alias functionality - return name as-is return name - - // Original alias resolution code (disabled): - /* - if alias, ok := c.Aliases[name]; ok { - return alias - } - return name - */ } // GetKey retrieves a key configuration by name diff --git a/internal/constants/constants.go b/internal/constants/constants.go new file mode 100644 index 0000000..673aef7 --- /dev/null +++ b/internal/constants/constants.go @@ -0,0 +1,22 @@ +package constants + +import "time" + +// File permissions +const ( + ConfigFileMode = 0644 + ConfigDirMode = 0755 +) + +// Timeout constants +const ( + DefaultTimeout = 60 * time.Second + GLMTimeout = 50 * time.Minute // 3,000,000ms + GLMCodingTimeout = 5 * time.Minute // 300,000ms +) + +// Timeout in milliseconds for configuration +const ( + DefaultTimeoutMS = 60000 + GLMTimeoutMS = 300000 +) \ No newline at end of file diff --git a/internal/provider/builtin.go b/internal/provider/builtin.go index bdc87e9..0cc909c 100644 --- a/internal/provider/builtin.go +++ b/internal/provider/builtin.go @@ -3,6 +3,8 @@ package provider import ( "fmt" "strings" + + "github.com/fakecore/aim/internal/constants" ) // BuiltinProviderInfo represents builtin provider information with multiple endpoints @@ -48,7 +50,7 @@ var builtinProviders = map[string]BuiltinProviderInfo{ "claude-code": { BaseURL: "https://api.deepseek.com/anthropic", Model: "deepseek-chat", - Timeout: 60000, + Timeout: int(constants.DefaultTimeoutMS), Env: map[string]string{ "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1", }, @@ -75,9 +77,9 @@ var builtinProviders = map[string]BuiltinProviderInfo{ Description: "Default configuration", Tools: map[string]ToolConfig{ "claude-code": { - BaseURL: "https://api.moonshot.cn/v1/anthropic", + BaseURL: "https://api.moonshot.cn/anthropic", Model: "kimi-k2-turbo-preview", - Timeout: 60000, + Timeout: int(constants.DefaultTimeoutMS), Env: map[string]string{ "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1", }, @@ -107,7 +109,7 @@ var builtinProviders = map[string]BuiltinProviderInfo{ "claude-code": { BaseURL: "https://open.bigmodel.cn/api/anthropic", Model: "glm-4.6", - Timeout: 3000000, + Timeout: int(constants.GLMTimeout.Milliseconds()), Env: map[string]string{ "ANTHROPIC_DEFAULT_HAIKU_MODEL": "glm-4.5-air", "ANTHROPIC_DEFAULT_SONNET_MODEL": "glm-4.6", @@ -132,7 +134,7 @@ var builtinProviders = map[string]BuiltinProviderInfo{ "claude-code": { BaseURL: "https://open.bigmodel.cn/api/anthropic", Model: "glm-4.6", - Timeout: 3000000, + Timeout: int(constants.GLMTimeout.Milliseconds()), Env: map[string]string{ "ANTHROPIC_DEFAULT_HAIKU_MODEL": "glm-4.5-air", "ANTHROPIC_DEFAULT_SONNET_MODEL": "glm-4.6", @@ -143,7 +145,7 @@ var builtinProviders = map[string]BuiltinProviderInfo{ "codex": { BaseURL: "https://open.bigmodel.cn/api/coding/paas/v4", Model: "glm-4.6", - Timeout: 300000, + Timeout: int(constants.GLMCodingTimeout.Milliseconds()), EnvKeyName: "GLM_API_KEY", }, }, @@ -164,7 +166,7 @@ var builtinProviders = map[string]BuiltinProviderInfo{ "claude-code": { BaseURL: "https://dashscope.aliyuncs.com/api/v2/apps/claude-code-proxy", Model: "qwen3-max", - Timeout: 60000, + Timeout: int(constants.DefaultTimeoutMS), Env: map[string]string{ "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1", }, diff --git a/internal/setup/formatters.go b/internal/setup/formatters.go index c28343e..2ac185f 100644 --- a/internal/setup/formatters.go +++ b/internal/setup/formatters.go @@ -6,6 +6,31 @@ import ( "strings" ) +var shellReplacer = strings.NewReplacer( + `"`, `\"`, + `$`, `\$`, + "`", "\\`", + `\`, `\\`, + `!`, `\!`, + `&`, `\&`, + `|`, `\|`, + `(`, `\(`, + `)`, `\)`, + `[`, `\[`, + `]`, `\]`, + `{`, `\{`, + `}`, `\}`, + `;`, `\;`, + `<`, `\<`, + `>`, `\>`, + ` `, `\ `, +) + +// EscapeShellValue escapes special characters in shell values +func EscapeShellValue(value string) string { + return shellReplacer.Replace(value) +} + // ZshFormatter Zsh environment variable formatter type ZshFormatter struct{} @@ -21,11 +46,7 @@ func (f *ZshFormatter) FormatEnv(result *SetupResult) string { var output strings.Builder for key, value := range result.EnvVars { - // Escape special characters - escapedValue := strings.ReplaceAll(value, `"`, `\"`) - escapedValue = strings.ReplaceAll(escapedValue, `$`, `\$`) - escapedValue = strings.ReplaceAll(escapedValue, "`", "\\`") - + escapedValue := EscapeShellValue(value) output.WriteString(fmt.Sprintf("export %s=\"%s\"\n", key, escapedValue)) } @@ -47,11 +68,7 @@ func (f *BashFormatter) FormatEnv(result *SetupResult) string { var output strings.Builder for key, value := range result.EnvVars { - // Escape special characters - escapedValue := strings.ReplaceAll(value, `"`, `\"`) - escapedValue = strings.ReplaceAll(escapedValue, `$`, `\$`) - escapedValue = strings.ReplaceAll(escapedValue, "`", "\\`") - + escapedValue := EscapeShellValue(value) output.WriteString(fmt.Sprintf("export %s=\"%s\"\n", key, escapedValue)) } @@ -220,7 +237,7 @@ func (f *SimpleCommandFormatter) FormatCommand(result *SetupResult) string { if len(parts) == 0 { return "" } - + // The last part is the actual command return parts[len(parts)-1] } diff --git a/internal/setup/installers.go b/internal/setup/installers.go index 69c25d9..bcd32a1 100644 --- a/internal/setup/installers.go +++ b/internal/setup/installers.go @@ -4,22 +4,121 @@ import ( "bytes" "encoding/json" "fmt" - "io/ioutil" "os" "path/filepath" "strings" "time" "github.com/BurntSushi/toml" + "github.com/fakecore/aim/internal/constants" ) +// ConfigFormat represents configuration format +type ConfigFormat int + +const ( + JSON ConfigFormat = iota + TOML +) + +// BaseInstaller provides common functionality for tool installers +type BaseInstaller struct{} + +// ConfigParser defines how to parse configuration data +type ConfigParser interface { + Parse(data []byte, target *map[string]interface{}) error + Marshal(config interface{}) ([]byte, error) +} + +// JSONParser implements JSON parsing +type JSONParser struct{} + +func (p *JSONParser) Parse(data []byte, target *map[string]interface{}) error { + return json.Unmarshal(data, target) +} + +func (p *JSONParser) Marshal(config interface{}) ([]byte, error) { + return json.MarshalIndent(config, "", " ") +} + +// TOMLParser implements TOML parsing +type TOMLParser struct{} + +func (p *TOMLParser) Parse(data []byte, target *map[string]interface{}) error { + return toml.Unmarshal(data, target) +} + +func (p *TOMLParser) Marshal(config interface{}) ([]byte, error) { + var buf bytes.Buffer + if err := toml.NewEncoder(&buf).Encode(config); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + // ClaudeCodeInstaller Claude Code installer -type ClaudeCodeInstaller struct{} +type ClaudeCodeInstaller struct { + BaseInstaller +} func NewClaudeCodeInstaller() *ClaudeCodeInstaller { return &ClaudeCodeInstaller{} } +// checkManagedByAIM通用实现 +func (b *BaseInstaller) checkManagedByAIM(configPath string, parser ConfigParser) (bool, map[string]interface{}, error) { + // Check if config file exists + if _, err := os.Stat(configPath); os.IsNotExist(err) { + return false, nil, nil + } + + // Read existing configuration + data, err := os.ReadFile(configPath) + if err != nil { + return false, nil, fmt.Errorf("failed to read config file: %w", err) + } + + // Parse configuration + var existingConfig map[string]interface{} + if err := parser.Parse(data, &existingConfig); err != nil { + return false, nil, fmt.Errorf("failed to parse config file: %w", err) + } + + // Check if there's a managed_by_aim field + if managedByAIM, exists := existingConfig["managed_by_aim"]; exists { + if managedMap, ok := managedByAIM.(map[string]interface{}); ok { + // Check if there's backup information + if hasBackup, ok := managedMap["backup"].(bool); ok && hasBackup { + return true, existingConfig, nil + } + } + } + + return false, existingConfig, nil +} + +// installAIMManaged通用实现 +func (b *BaseInstaller) installAIMManaged(req *InstallRequest, configPath string, parser ConfigParser, convertConfig func(*InstallRequest) (interface{}, error)) error { + // Convert configuration + config, err := convertConfig(req) + if err != nil { + return fmt.Errorf("failed to convert config: %w", err) + } + + // Serialize configuration + configData, err := parser.Marshal(config) + if err != nil { + return fmt.Errorf("failed to marshal config: %w", err) + } + + // Directly overwrite the config file + if err := os.WriteFile(configPath, configData, constants.ConfigFileMode); err != nil { + return fmt.Errorf("failed to write config file: %w", err) + } + + return nil +} + func (i *ClaudeCodeInstaller) Install(req *InstallRequest) error { // Get config path configPath, err := i.GetConfigPath() @@ -29,7 +128,7 @@ func (i *ClaudeCodeInstaller) Install(req *InstallRequest) error { // Ensure config directory exists configDir := filepath.Dir(configPath) - if err := os.MkdirAll(configDir, 0755); err != nil { + if err := os.MkdirAll(configDir, constants.ConfigDirMode); err != nil { return fmt.Errorf("failed to create config directory: %w", err) } @@ -70,12 +169,12 @@ func (i *ClaudeCodeInstaller) Backup(req *InstallRequest) error { } // Copy file - data, err := ioutil.ReadFile(configPath) + data, err := os.ReadFile(configPath) if err != nil { return fmt.Errorf("failed to read config file: %w", err) } - if err := ioutil.WriteFile(backupPath, data, 0644); err != nil { + if err := os.WriteFile(backupPath, data, constants.ConfigFileMode); err != nil { return fmt.Errorf("failed to write backup file: %w", err) } @@ -98,7 +197,7 @@ func (i *ClaudeCodeInstaller) ValidateConfig(path string) error { } // Try to parse JSON - data, err := ioutil.ReadFile(path) + data, err := os.ReadFile(path) if err != nil { return fmt.Errorf("failed to read config file: %w", err) } @@ -150,7 +249,9 @@ func (i *ClaudeCodeInstaller) ConvertConfig(req *InstallRequest) (interface{}, e } // CodexInstaller Codex installer -type CodexInstaller struct{} +type CodexInstaller struct { + BaseInstaller +} func NewCodexInstaller() *CodexInstaller { return &CodexInstaller{} @@ -165,7 +266,7 @@ func (i *CodexInstaller) Install(req *InstallRequest) error { // Ensure config directory exists configDir := filepath.Dir(configPath) - if err := os.MkdirAll(configDir, 0755); err != nil { + if err := os.MkdirAll(configDir, constants.ConfigDirMode); err != nil { return fmt.Errorf("failed to create config directory: %w", err) } @@ -206,12 +307,12 @@ func (i *CodexInstaller) Backup(req *InstallRequest) error { } // Copy file - data, err := ioutil.ReadFile(configPath) + data, err := os.ReadFile(configPath) if err != nil { return fmt.Errorf("failed to read config file: %w", err) } - if err := ioutil.WriteFile(backupPath, data, 0644); err != nil { + if err := os.WriteFile(backupPath, data, constants.ConfigFileMode); err != nil { return fmt.Errorf("failed to write backup file: %w", err) } @@ -224,7 +325,7 @@ func (i *CodexInstaller) GetConfigPath() (string, error) { return "", fmt.Errorf("failed to get home directory: %w", err) } - return filepath.Join(home, ".codex", "config.toml"), nil + return filepath.Join(home, ".codex", "constants.toml"), nil } func (i *CodexInstaller) ValidateConfig(path string) error { @@ -234,7 +335,7 @@ func (i *CodexInstaller) ValidateConfig(path string) error { } // Try to parse TOML - data, err := ioutil.ReadFile(path) + data, err := os.ReadFile(path) if err != nil { return fmt.Errorf("failed to read config file: %w", err) } @@ -281,56 +382,12 @@ func (i *CodexInstaller) ConvertConfig(req *InstallRequest) (interface{}, error) // checkManagedByAIM checks if the configuration is managed by AIM func (i *ClaudeCodeInstaller) checkManagedByAIM(configPath string) (bool, map[string]interface{}, error) { - // Check if config file exists - if _, err := os.Stat(configPath); os.IsNotExist(err) { - return false, nil, nil - } - - // Read existing configuration - data, err := ioutil.ReadFile(configPath) - if err != nil { - return false, nil, fmt.Errorf("failed to read config file: %w", err) - } - - // Parse JSON - var existingConfig map[string]interface{} - if err := json.Unmarshal(data, &existingConfig); err != nil { - return false, nil, fmt.Errorf("failed to parse config file: %w", err) - } - - // Check if there's a managed_by_aim field - if managedByAIM, exists := existingConfig["managed_by_aim"]; exists { - if managedMap, ok := managedByAIM.(map[string]interface{}); ok { - // Check if there's backup information - if hasBackup, ok := managedMap["backup"].(bool); ok && hasBackup { - return true, existingConfig, nil - } - } - } - - return false, existingConfig, nil + return i.BaseInstaller.checkManagedByAIM(configPath, &JSONParser{}) } // installAIMManaged installs to existing AIM-managed configuration func (i *ClaudeCodeInstaller) installAIMManaged(req *InstallRequest, configPath string) error { - // Convert configuration - claudeConfig, err := i.ConvertConfig(req) - if err != nil { - return fmt.Errorf("failed to convert config: %w", err) - } - - // Serialize configuration - configData, err := json.MarshalIndent(claudeConfig, "", " ") - if err != nil { - return fmt.Errorf("failed to marshal config: %w", err) - } - - // Directly overwrite the config file - if err := ioutil.WriteFile(configPath, configData, 0644); err != nil { - return fmt.Errorf("failed to write config file: %w", err) - } - - return nil + return i.BaseInstaller.installAIMManaged(req, configPath, &JSONParser{}, i.ConvertConfig) } // installNonAIMManaged installs to non-AIM managed configuration @@ -363,7 +420,7 @@ func (i *ClaudeCodeInstaller) installNonAIMManaged(req *InstallRequest, configPa } // Write configuration file - if err := ioutil.WriteFile(configPath, configData, 0644); err != nil { + if err := os.WriteFile(configPath, configData, constants.ConfigFileMode); err != nil { return fmt.Errorf("failed to write config file: %w", err) } @@ -385,7 +442,7 @@ func (i *ClaudeCodeInstaller) installNew(req *InstallRequest, configPath string) } // Write configuration file - if err := ioutil.WriteFile(configPath, configData, 0644); err != nil { + if err := os.WriteFile(configPath, configData, constants.ConfigFileMode); err != nil { return fmt.Errorf("failed to write config file: %w", err) } @@ -394,57 +451,12 @@ func (i *ClaudeCodeInstaller) installNew(req *InstallRequest, configPath string) // checkManagedByAIM checks if the configuration is managed by AIM func (i *CodexInstaller) checkManagedByAIM(configPath string) (bool, map[string]interface{}, error) { - // Check if config file exists - if _, err := os.Stat(configPath); os.IsNotExist(err) { - return false, nil, nil - } - - // Read existing configuration - data, err := ioutil.ReadFile(configPath) - if err != nil { - return false, nil, fmt.Errorf("failed to read config file: %w", err) - } - - // Parse TOML - var existingConfig map[string]interface{} - if err := toml.Unmarshal(data, &existingConfig); err != nil { - return false, nil, fmt.Errorf("failed to parse config file: %w", err) - } - - // Check if there's a managed_by_aim field - if managedByAIM, exists := existingConfig["managed_by_aim"]; exists { - if managedMap, ok := managedByAIM.(map[string]interface{}); ok { - // Check if there's backup information - if hasBackup, ok := managedMap["backup"].(bool); ok && hasBackup { - return true, existingConfig, nil - } - } - } - - return false, existingConfig, nil + return i.BaseInstaller.checkManagedByAIM(configPath, &TOMLParser{}) } // installAIMManaged installs to existing AIM-managed configuration func (i *CodexInstaller) installAIMManaged(req *InstallRequest, configPath string) error { - // Convert configuration - codexConfig, err := i.ConvertConfig(req) - if err != nil { - return fmt.Errorf("failed to convert config: %w", err) - } - - // Serialize configuration to TOML format - var buf bytes.Buffer - if err := toml.NewEncoder(&buf).Encode(codexConfig); err != nil { - return fmt.Errorf("failed to marshal config: %w", err) - } - configData := buf.Bytes() - - // Directly overwrite the config file - if err := ioutil.WriteFile(configPath, configData, 0644); err != nil { - return fmt.Errorf("failed to write config file: %w", err) - } - - return nil + return i.BaseInstaller.installAIMManaged(req, configPath, &TOMLParser{}, i.ConvertConfig) } // installNonAIMManaged installs to non-AIM managed configuration @@ -478,7 +490,7 @@ func (i *CodexInstaller) installNonAIMManaged(req *InstallRequest, configPath st configData := buf.Bytes() // Write configuration file - if err := ioutil.WriteFile(configPath, configData, 0644); err != nil { + if err := os.WriteFile(configPath, configData, constants.ConfigFileMode); err != nil { return fmt.Errorf("failed to write config file: %w", err) } @@ -501,7 +513,7 @@ func (i *CodexInstaller) installNew(req *InstallRequest, configPath string) erro configData := buf.Bytes() // Write configuration file - if err := ioutil.WriteFile(configPath, configData, 0644); err != nil { + if err := os.WriteFile(configPath, configData, constants.ConfigFileMode); err != nil { return fmt.Errorf("failed to write config file: %w", err) } diff --git a/internal/setup/manager.go b/internal/setup/manager.go index c462653..165520a 100644 --- a/internal/setup/manager.go +++ b/internal/setup/manager.go @@ -3,13 +3,13 @@ package setup import ( "context" "fmt" - "io/ioutil" "os" "path/filepath" "strings" "time" "github.com/fakecore/aim/internal/config" + "github.com/fakecore/aim/internal/constants" "github.com/fakecore/aim/internal/tool" ) @@ -435,17 +435,8 @@ func (sm *SetupManager) buildCommandWithEnv(toolName string, runtime *config.Run var envExports []string for key, value := range envVars { // Escape special characters - escapedValue := strings.ReplaceAll(value, `"`, `\"`) - escapedValue = strings.ReplaceAll(escapedValue, `$`, `\$`) - escapedValue = strings.ReplaceAll(escapedValue, "`", "\\`") - escapedValue = strings.ReplaceAll(escapedValue, `&`, `\&`) - escapedValue = strings.ReplaceAll(escapedValue, `;`, `\;`) - escapedValue = strings.ReplaceAll(escapedValue, `|`, `\|`) - escapedValue = strings.ReplaceAll(escapedValue, `>`, `\>`) - escapedValue = strings.ReplaceAll(escapedValue, `<`, `\<`) - escapedValue = strings.ReplaceAll(escapedValue, `(`, `\(`) - escapedValue = strings.ReplaceAll(escapedValue, `)`, `\)`) - escapedValue = strings.ReplaceAll(escapedValue, ` `, `\ `) + escapedValue := EscapeShellValue(value) + // Handle tab and newline characters specifically for command context escapedValue = strings.ReplaceAll(escapedValue, `\t`, `\t`) escapedValue = strings.ReplaceAll(escapedValue, `\n`, `\n`) @@ -575,13 +566,13 @@ func (sm *SetupManager) findLatestBackup(configPath string) (string, error) { // restoreFromBackup restores configuration from backup file func (sm *SetupManager) restoreFromBackup(installer ToolInstaller, backupPath, configPath string) error { // Read backup file - data, err := ioutil.ReadFile(backupPath) + data, err := os.ReadFile(backupPath) if err != nil { return fmt.Errorf("failed to read backup file: %w", err) } // Write to configuration file - if err := ioutil.WriteFile(configPath, data, 0644); err != nil { + if err := os.WriteFile(configPath, data, constants.ConfigFileMode); err != nil { return fmt.Errorf("failed to write config file: %w", err) } diff --git a/scripts/release.sh b/scripts/release.sh index 1e1a09c..32e9574 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -167,8 +167,14 @@ increment_version() { case "$type" in "rc") - # Always create RC based on current version (remove -dev if present) - echo "${major}.${minor}.${patch}-rc1" + if [[ -n "$rc" ]]; then + # Increment RC number (v1.1.0-rc1 -> v1.1.0-rc2) + local rc_num=${rc#-rc} + echo "${major}.${minor}.${patch}-rc$((rc_num + 1))" + else + # Create first RC from non-RC version (remove -dev if present) + echo "${major}.${minor}.${patch}-rc1" + fi ;; "release") if [[ -n "$rc" ]]; then @@ -224,7 +230,7 @@ push_to_remote() { fi print_info "Pushing commits to remote..." - git push origin main + git push origin print_info "Pushing tags to remote..." git push origin --tags @@ -246,38 +252,6 @@ update_version_file() { echo "$version" > "$version_file" } -# Update version in README files -update_readme_files() { - local version=$1 - local readme_md="$PROJECT_ROOT/README.md" - local readme_cn="$PROJECT_ROOT/README_CN.md" - - if [[ "$DRY_RUN" == "true" ]]; then - print_info "DRY RUN: Would update version in README files to $version" - return - fi - - # Update README.md - if [[ -f "$readme_md" ]]; then - print_info "Updating version in README.md" - # Update version in installation examples - sed -i.bak -E "s/VERSION=v[0-9]+\.[0-9]+\.[0-9]+(-rc[0-9]+)?(-dev)?/VERSION=v$version/g" "$readme_md" - sed -i.bak -E "s/curl -fsSL https:\/\/raw.githubusercontent.com\/fakecore\/aim\/main\/scripts\/setup-tool.sh | bash -s -- --version v[0-9]+\.[0-9]+\.[0-9]+(-rc[0-9]+)?(-dev)?/curl -fsSL https:\/\/raw.githubusercontent.com\/fakecore\/aim\/main\/scripts\/setup-tool.sh | bash -s -- --version v$version/g" "$readme_md" - sed -i.bak -E "s/https:\/\/github.com\/fakecore\/aim\/releases\/download\/v[0-9]+\.[0-9]+\.[0-9]+(-rc[0-9]+)?(-dev)?/https:\/\/github.com\/fakecore\/aim\/releases\/download\/v$version/g" "$readme_md" - rm -f "$readme_md.bak" - fi - - # Update README_CN.md - if [[ -f "$readme_cn" ]]; then - print_info "Updating version in README_CN.md" - # Update version in installation examples - sed -i.bak -E "s/VERSION=v[0-9]+\.[0-9]+\.[0-9]+(-rc[0-9]+)?(-dev)?/VERSION=v$version/g" "$readme_cn" - sed -i.bak -E "s/curl -fsSL https:\/\/raw.githubusercontent.com\/fakecore\/aim\/main\/scripts\/setup-tool.sh | bash -s -- --version v[0-9]+\.[0-9]+\.[0-9]+(-rc[0-9]+)?(-dev)?/curl -fsSL https:\/\/raw.githubusercontent.com\/fakecore\/aim\/main\/scripts\/setup-tool.sh | bash -s -- --version v$version/g" "$readme_cn" - sed -i.bak -E "s/https:\/\/github.com\/fakecore\/aim\/releases\/download\/v[0-9]+\.[0-9]+\.[0-9]+(-rc[0-9]+)?(-dev)?/https:\/\/github.com\/fakecore\/aim\/releases\/download\/v$version/g" "$readme_cn" - rm -f "$readme_cn.bak" - fi -} - # Interactive confirmation confirm_action() { local message=$1 @@ -334,9 +308,6 @@ create_release() { # Update VERSION file update_version_file "$NEW_VERSION" - # Update README files - update_readme_files "$NEW_VERSION" - # Commit changes if [[ "$DRY_RUN" == "true" ]]; then print_info "DRY RUN: Would commit changes and create tag"