Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 36 additions & 67 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
{
Expand Down Expand Up @@ -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

## 🚀 快速开始
## 开发指南

### 安装依赖

Expand Down Expand Up @@ -122,7 +121,7 @@ pnpm run build
pnpm run clean
```

## 📁 项目结构
### 项目结构

```
auo/
Expand Down Expand Up @@ -159,47 +158,17 @@ 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**: 包含调试信息
## 构建和发布

### 发布流程

1. 更新版本号:`pnpm version [major|minor|patch]`
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`
4. 推送分支:`git push origin feature/amazing-feature`
62 changes: 50 additions & 12 deletions src/cli/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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':
Expand All @@ -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;
Expand Down Expand Up @@ -66,7 +89,8 @@ Basic Commands:
auo --help, -h # Show this help message

Configuration Management:
auo --next # Switch to the next configuration
auo --use <index> # Switch to configuration at specified index
auo --remove <index> # Remove configuration at specified index
auo --list # List all configurations
auo --add # Add a new configuration (interactive)
auo --config-path # Show config file path
Expand All @@ -75,24 +99,38 @@ 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`);
}

/**
* 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;
}

Expand Down
61 changes: 44 additions & 17 deletions src/config/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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;
}
}
}
6 changes: 4 additions & 2 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
45 changes: 40 additions & 5 deletions tests/cli/commands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand All @@ -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', () => {
Expand Down
Loading