From 130f59880a218bc22980de30743d4851d4a76f06 Mon Sep 17 00:00:00 2001 From: millylee Date: Tue, 29 Jul 2025 01:51:53 +0800 Subject: [PATCH] feat: modify cli commands --- README.md | 103 ++++++++++++----------------------- src/cli/commands.ts | 62 +++++++++++++++++---- src/config/manager.ts | 61 +++++++++++++++------ src/types/index.ts | 6 +- tests/cli/commands.test.ts | 45 +++++++++++++-- tests/config/manager.test.ts | 82 ++++++++++++++++++++++++---- 6 files changed, 244 insertions(+), 115 deletions(-) diff --git a/README.md b/README.md index 757484a..19cfb96 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,46 @@ -# AUO - Claude Code Wrapper CLI +# auo [![CI/CD](https://github.com/millylee/auo/actions/workflows/ci.yml/badge.svg)](https://github.com/millylee/auo/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/millylee/auo/branch/main/graph/badge.svg)](https://codecov.io/gh/millylee/auo) [![npm version](https://badge.fury.io/js/auo.svg)](https://badge.fury.io/js/auo) [![License: BSD 2-Clause](https://img.shields.io/badge/License-BSD%202--Clause-blue.svg)](https://opensource.org/license/bsd-2-clause) -用于增强 Claude Code 的配置管理和使用体验。 +让 Claude Code 更易用,支持多配置切换。 -## 📋 前置要求 +## 前置要求 - Node.js >= 18.0.0 +## 安装与使用 + +> 首次使用时会自动判断 claude-code 是否已安装,未安装会先安装。 + +```bash +# 安装 +pnpm i -g auo + +# 列出所有配置 +auo --list + +# 切换到指定索引的配置 +auo --use 1 + +# 删除指定索引的配置 +auo --remove 2 + +# 添加新配置(交互式) +auo --add + +# 查看配置文件路径 +auo --config-path + +# 正常使用 Claude,自动使用当前选中的配置中的 baseUrl 和 authToken +auo "帮我写代码" +``` + ## 配置示例 -可以手动编辑配置文件 `~/.auo/config.json`: +可以手动编辑配置文件 `~/.auo/config.json`,但不推荐。 ```json { @@ -41,35 +68,7 @@ } ``` -## 使用方法 - -1. **列出所有配置**: - ```bash - auo --list - ``` - -2. **切换到下一个配置**: - ```bash - auo --next - ``` - -3. **添加新配置**(交互式): - ```bash - auo --add - ``` - -4. **查看配置文件路径**: - ```bash - auo --config-path - ``` - -5. **正常使用 Claude**: - ```bash - auo "帮我写代码" - ``` - 会自动使用当前选中的配置中的 baseUrl 和 authToken - -## 🚀 快速开始 +## 开发指南 ### 安装依赖 @@ -122,7 +121,7 @@ pnpm run build pnpm run clean ``` -## 📁 项目结构 +### 项目结构 ``` auo/ @@ -159,37 +158,7 @@ auo/ └── package.json # 包配置 ``` -## 🔧 开发指南 - -### Git 工作流 - -项目使用 Husky 和 lint-staged 来确保代码质量: - -- **pre-commit**: 自动运行 ESLint、Prettier 和相关测试 -- **commit-msg**: 可选的提交消息格式检查 - -### 代码规范 - -- 使用 ESLint 9 的扁平化配置 -- Prettier 进行代码格式化 -- 严格的 TypeScript 类型检查 -- 测试覆盖率要求 ≥ 80% - -### 测试策略 - -- 单元测试:使用 Vitest -- 覆盖率报告:使用 v8 provider -- 测试环境:Node.js 环境 -- Mock 支持:自动重置和清理 - -## 📦 构建和发布 - -### 构建配置 - -- **目标环境**: Node.js 18+ -- **输出格式**: ESM (`.mjs`) 和 CommonJS (`.cjs`) -- **类型定义**: 自动生成 `.d.ts` 文件 -- **Source Maps**: 包含调试信息 +## 构建和发布 ### 发布流程 @@ -197,9 +166,9 @@ auo/ 2. 推送标签:`git push --tags` 3. GitHub Actions 自动构建和发布 -## 🤝 贡献指南 +## 贡献指南 1. Fork 这个仓库 2. 创建特性分支:`git checkout -b feature/amazing-feature` 3. 提交更改:`git commit -m 'Add amazing feature'` -4. 推送分支:`git push origin feature/amazing-feature` \ No newline at end of file +4. 推送分支:`git push origin feature/amazing-feature` diff --git a/src/cli/commands.ts b/src/cli/commands.ts index 9419e68..660d1d2 100644 --- a/src/cli/commands.ts +++ b/src/cli/commands.ts @@ -14,7 +14,9 @@ declare const __PKG_DESCRIPTION__: string; export function parseArgs(args: string[]): CLIOptions { const options: CLIOptions = {}; - for (const arg of args) { + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + switch (arg) { case '--help': case '-h': @@ -24,9 +26,30 @@ export function parseArgs(args: string[]): CLIOptions { case '-v': options.version = true; break; - case '--next': - options.next = true; + case '--use': { + // Get the next argument as index + const useIndexStr = args[i + 1]; + if (useIndexStr !== undefined && !useIndexStr.startsWith('--')) { + const useIndex = parseInt(useIndexStr, 10); + if (!isNaN(useIndex)) { + options.useIndex = useIndex; + i++; // Skip next argument as it's the index + } + } + break; + } + case '--remove': { + // Get the next argument as index + const removeIndexStr = args[i + 1]; + if (removeIndexStr !== undefined && !removeIndexStr.startsWith('--')) { + const removeIndex = parseInt(removeIndexStr, 10); + if (!isNaN(removeIndex)) { + options.removeIndex = removeIndex; + i++; // Skip next argument as it's the index + } + } break; + } case '--list': options.listConfigs = true; break; @@ -66,7 +89,8 @@ Basic Commands: auo --help, -h # Show this help message Configuration Management: - auo --next # Switch to the next configuration + auo --use # Switch to configuration at specified index + auo --remove # Remove configuration at specified index auo --list # List all configurations auo --add # Add a new configuration (interactive) auo --config-path # Show config file path @@ -75,10 +99,12 @@ Notes: • Claude Code will be installed automatically on first use • Config file is stored at ~/.auo/config.json • Use config management to easily switch between different API endpoints and tokens + • Use --list to see configuration indices before using --use or --remove Examples: auo --add # Add a new API configuration - auo --next # Switch to the next configuration + auo --use 1 # Switch to configuration at index 1 + auo --remove 2 # Remove configuration at index 2 auo "Write a React component" # Ask Claude with the current config`); } @@ -86,13 +112,25 @@ Examples: * Handle configuration-related commands */ export function handleConfigCommands(options: CLIOptions, configManager: ConfigManager): boolean { - if (options.next) { - const newConfig = configManager.switchToNext(); - console.log( - `✅ Switched to config: ${newConfig.name} - ${newConfig.description || 'No description'}` - ); - console.log(` Base URL: ${newConfig.baseUrl || '(not set)'}`); - console.log(` Token: ${newConfig.authToken ? 'set' : 'not set'}`); + if (options.useIndex !== undefined) { + const config = configManager.switchToIndex(options.useIndex); + if (config) { + console.log( + `✅ Switched to config [${options.useIndex}]: ${config.name} - ${config.description || 'No description'}` + ); + console.log(` Base URL: ${config.baseUrl || '(not set)'}`); + console.log(` Token: ${config.authToken ? 'set' : 'not set'}`); + } else { + const allConfigs = configManager.getAllConfigs(); + console.error( + `❌ Invalid index ${options.useIndex}. Must be between 0 and ${allConfigs.length - 1}` + ); + } + return true; + } + + if (options.removeIndex !== undefined) { + configManager.removeConfigByIndex(options.removeIndex); return true; } diff --git a/src/config/manager.ts b/src/config/manager.ts index aca5d2f..e9c5216 100644 --- a/src/config/manager.ts +++ b/src/config/manager.ts @@ -120,23 +120,6 @@ export class ConfigManager { return defaultConfig; } - /** - * Switch to next configuration - */ - switchToNext(): ConfigItem { - const config = this.loadConfig(); - const nextIndex = (config.currentIndex + 1) % config.providers.length; - - config.currentIndex = nextIndex; - this.saveConfig(config); - - const nextConfig = config.providers[nextIndex]; - if (!nextConfig) { - throw new Error('Unable to get next configuration'); - } - return nextConfig; - } - /** * Switch to configuration at specified index */ @@ -335,4 +318,48 @@ export class ConfigManager { const config = this.loadConfig(); return [...config.providers]; } + + /** + * Delete configuration by index + */ + removeConfigByIndex(index: number): boolean { + try { + const config = this.loadConfig(); + + // Check index boundaries + if (index < 0 || index >= config.providers.length) { + console.error( + `❌ Invalid index ${index}. Must be between 0 and ${config.providers.length - 1}` + ); + return false; + } + + if (config.providers.length <= 1) { + console.error('❌ Cannot delete the last configuration'); + return false; + } + + const deletedConfig = config.providers[index]; + if (!deletedConfig) { + console.error(`❌ Configuration at index ${index} not found`); + return false; + } + + config.providers.splice(index, 1); + + // Adjust current index if necessary + if (config.currentIndex >= config.providers.length) { + config.currentIndex = config.providers.length - 1; + } else if (config.currentIndex > index) { + config.currentIndex = config.currentIndex - 1; + } + + this.saveConfig(config); + console.log(`✅ Configuration "${deletedConfig.name}" (index ${index}) deleted successfully`); + return true; + } catch (error) { + console.error('❌ Failed to delete configuration:', error); + return false; + } + } } diff --git a/src/types/index.ts b/src/types/index.ts index 6920bc1..79c5539 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -33,8 +33,10 @@ export interface CLIOptions { help?: boolean; /** Show version information */ version?: boolean; - /** Switch to the next configuration */ - next?: boolean; + /** Use configuration at specified index */ + useIndex?: number; + /** Remove configuration at specified index */ + removeIndex?: number; /** List all configurations */ listConfigs?: boolean; /** Show configuration file path */ diff --git a/tests/cli/commands.test.ts b/tests/cli/commands.test.ts index 261077a..44bf62a 100644 --- a/tests/cli/commands.test.ts +++ b/tests/cli/commands.test.ts @@ -24,9 +24,24 @@ describe('CLI Command Parsing', () => { expect(result.listConfigs).toBe(true); }); - it('should correctly parse next config parameter', () => { - const result = parseArgs(['--next']); - expect(result.next).toBe(true); + it('should correctly parse use config parameter with index', () => { + const result = parseArgs(['--use', '1']); + expect(result.useIndex).toBe(1); + }); + + it('should correctly parse remove config parameter with index', () => { + const result = parseArgs(['--remove', '2']); + expect(result.removeIndex).toBe(2); + }); + + it('should ignore invalid use index', () => { + const result = parseArgs(['--use', 'invalid']); + expect(result.useIndex).toBeUndefined(); + }); + + it('should ignore missing use index', () => { + const result = parseArgs(['--use']); + expect(result.useIndex).toBeUndefined(); }); it('should correctly parse add config parameter', () => { @@ -40,11 +55,31 @@ describe('CLI Command Parsing', () => { }); it('should correctly parse multiple parameters', () => { - const result = parseArgs(['--help', '--next', '--list']); + const result = parseArgs(['--help', '--use', '1', '--list']); expect(result.help).toBe(true); - expect(result.next).toBe(true); + expect(result.useIndex).toBe(1); expect(result.listConfigs).toBe(true); }); + + it('should correctly parse use config parameter with index', () => { + const result = parseArgs(['--use', '1']); + expect(result.useIndex).toBe(1); + }); + + it('should correctly parse remove config parameter with index', () => { + const result = parseArgs(['--remove', '2']); + expect(result.removeIndex).toBe(2); + }); + + it('should ignore invalid use index', () => { + const result = parseArgs(['--use', 'invalid']); + expect(result.useIndex).toBeUndefined(); + }); + + it('should ignore missing use index', () => { + const result = parseArgs(['--use']); + expect(result.useIndex).toBeUndefined(); + }); }); describe('CLI Commands', () => { diff --git a/tests/config/manager.test.ts b/tests/config/manager.test.ts index cba4689..3be4748 100644 --- a/tests/config/manager.test.ts +++ b/tests/config/manager.test.ts @@ -153,8 +153,8 @@ describe('ConfigManager', () => { }); }); - describe('switchToNext', () => { - it('should switch to next configuration', () => { + describe('switchToIndex', () => { + it('should switch to configuration at specified index', () => { // Add another configuration configManager.addConfig({ name: 'second', @@ -163,17 +163,68 @@ describe('ConfigManager', () => { description: 'Second configuration', }); - // Switch to next (should be 'second') - const nextConfig = configManager.switchToNext(); - expect(nextConfig.name).toBe('second'); + // Switch to index 1 (should be 'second') + const config = configManager.switchToIndex(1); + expect(config?.name).toBe('second'); // Current config should now be 'second' const currentConfig = configManager.getCurrentConfig(); expect(currentConfig.name).toBe('second'); }); - it('should cycle back to first configuration', () => { - // Add another configuration + it('should return null for invalid index', () => { + const result = configManager.switchToIndex(999); + expect(result).toBeNull(); + + const result2 = configManager.switchToIndex(-1); + expect(result2).toBeNull(); + }); + + it('should not change current config when index is invalid', () => { + const originalConfig = configManager.getCurrentConfig(); + configManager.switchToIndex(999); + const currentConfig = configManager.getCurrentConfig(); + expect(currentConfig.name).toBe(originalConfig.name); + }); + }); + + describe('removeConfigByIndex', () => { + it('should successfully remove configuration by index', () => { + // Add a configuration to remove + configManager.addConfig({ + name: 'to-remove', + baseUrl: 'https://api.remove.com', + authToken: 'remove-token', + description: 'To be removed', + }); + + const result = configManager.removeConfigByIndex(1); + expect(result).toBe(true); + + const allConfigs = configManager.getAllConfigs(); + expect(allConfigs.find((c) => c.name === 'to-remove')).toBeUndefined(); + }); + + it('should reject invalid indices', () => { + const result1 = configManager.removeConfigByIndex(-1); + expect(result1).toBe(false); + + const result2 = configManager.removeConfigByIndex(999); + expect(result2).toBe(false); + }); + + it('should prevent removing the last configuration', () => { + // Only default config exists + const result = configManager.removeConfigByIndex(0); + expect(result).toBe(false); + + // Config should still exist + const allConfigs = configManager.getAllConfigs(); + expect(allConfigs.length).toBe(1); + }); + + it('should adjust currentIndex when removing current configuration', () => { + // Add configurations configManager.addConfig({ name: 'second', baseUrl: 'https://api.second.com', @@ -181,10 +232,17 @@ describe('ConfigManager', () => { description: 'Second configuration', }); - // Switch to next twice (should cycle back to first) - configManager.switchToNext(); // Now at 'second' - const firstAgain = configManager.switchToNext(); // Should cycle back to 'default' - expect(firstAgain.name).toBe('default'); + // Switch to second configuration (index 1) + configManager.switchToIndex(1); + expect(configManager.getCurrentConfig().name).toBe('second'); + + // Remove the current configuration + const result = configManager.removeConfigByIndex(1); + expect(result).toBe(true); + + // Should now be back to default (index 0) + const currentConfig = configManager.getCurrentConfig(); + expect(currentConfig.name).toBe('default'); }); }); @@ -253,7 +311,7 @@ describe('ConfigManager', () => { }); // Switch to second configuration - configManager.switchToNext(); + configManager.switchToIndex(1); expect(configManager.getCurrentConfig().name).toBe('second'); // Delete the current configuration