refactor: 简化CLI工具管理命令

- 移除复杂的API Key管理和账户管理命令
- 简化admin命令直接创建初始管理员
- 保留核心的status命令用于系统状态查看
- 减少CLI工具的复杂度,专注于核心功能

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
shaw
2025-07-16 16:17:07 +08:00
parent 48c09b1286
commit 63906c9fa3

View File

@@ -45,218 +45,12 @@ program
.action(async () => { .action(async () => {
await initialize(); await initialize();
const { action } = await inquirer.prompt({ // 直接执行创建初始管理员
type: 'list', await createInitialAdmin();
name: 'action',
message: '选择操作:',
choices: [
{ name: '🔑 设置管理员密码', value: 'set-password' },
{ name: '👤 创建初始管理员', value: 'create-admin' },
{ name: '🔄 重置管理员密码', value: 'reset-password' },
{ name: '📊 查看管理员信息', value: 'view-admin' }
]
});
switch (action) {
case 'set-password':
await setAdminPassword();
break;
case 'create-admin':
await createInitialAdmin();
break;
case 'reset-password':
await resetAdminPassword();
break;
case 'view-admin':
await viewAdminInfo();
break;
}
await redis.disconnect(); await redis.disconnect();
}); });
// 🔑 API Key 管理
program
.command('keys')
.description('API Key 管理')
.action(async () => {
await initialize();
// 尝试兼容不同版本的inquirer
let prompt = inquirer.prompt || inquirer.default?.prompt || inquirer;
if (typeof prompt !== 'function') {
prompt = (await import('inquirer')).default;
}
const { action } = await prompt({
type: 'list',
name: 'action',
message: '选择操作:',
choices: [
{ name: '📋 列出所有 API Keys', value: 'list' },
{ name: '🔑 创建新的 API Key', value: 'create' },
{ name: '📝 更新 API Key', value: 'update' },
{ name: '🗑️ 删除 API Key', value: 'delete' },
{ name: '📊 查看使用统计', value: 'stats' },
{ name: '🧹 重置所有统计数据', value: 'reset-stats' }
]
});
switch (action) {
case 'list':
await listApiKeys();
break;
case 'create':
await createApiKey();
break;
case 'update':
await updateApiKey();
break;
case 'delete':
await deleteApiKey();
break;
case 'stats':
await viewApiKeyStats();
break;
case 'reset-stats':
await resetAllApiKeyStats();
break;
}
await redis.disconnect();
});
// 🏢 Claude 账户管理
program
.command('accounts')
.description('Claude 账户管理')
.action(async () => {
await initialize();
const { action } = await inquirer.prompt({
type: 'list',
name: 'action',
message: '选择操作:',
choices: [
{ name: '📋 列出所有账户', value: 'list' },
{ name: '🏢 创建新账户', value: 'create' },
{ name: '📝 更新账户', value: 'update' },
{ name: '🗑️ 删除账户', value: 'delete' },
{ name: '🔄 刷新 Token', value: 'refresh' },
{ name: '🧪 测试账户', value: 'test' }
]
});
switch (action) {
case 'list':
await listClaudeAccounts();
break;
case 'create':
await createClaudeAccount();
break;
case 'update':
await updateClaudeAccount();
break;
case 'delete':
await deleteClaudeAccount();
break;
case 'refresh':
await refreshAccountToken();
break;
case 'test':
await testClaudeAccount();
break;
}
await redis.disconnect();
});
// 🧹 重置统计数据命令
program
.command('reset-stats')
.description('重置所有API Key的统计数据')
.option('--force', '跳过确认直接重置')
.option('--debug', '显示详细的Redis键调试信息')
.action(async (options) => {
await initialize();
console.log(styles.title('\n🧹 重置所有API Key统计数据\n'));
// 如果启用调试显示当前Redis键
if (options.debug) {
console.log(styles.info('🔍 调试模式: 检查Redis中的实际键...\n'));
try {
const usageKeys = await redis.getClient().keys('usage:*');
const apiKeyKeys = await redis.getClient().keys('apikey:*');
console.log(styles.dim('API Key 键:'));
apiKeyKeys.forEach(key => console.log(` ${key}`));
console.log(styles.dim('\nUsage 键:'));
usageKeys.forEach(key => console.log(` ${key}`));
// 检查今日统计键
const today = new Date().toISOString().split('T')[0];
const dailyKeys = await redis.getClient().keys(`usage:daily:*:${today}`);
console.log(styles.dim(`\n今日统计键 (${today}):`));
dailyKeys.forEach(key => console.log(` ${key}`));
console.log('');
} catch (error) {
console.error(styles.error('调试信息获取失败:', error.message));
}
}
// 显示警告信息
console.log(styles.warning('⚠️ 警告: 此操作将删除所有API Key的使用统计数据!'));
console.log(styles.dim(' 包括: Token使用量、请求数量、每日/每月统计、最后使用时间等'));
console.log(styles.dim(' 此操作不可逆,请谨慎操作!\n'));
if (!options.force) {
console.log(styles.info('如需强制执行,请使用: npm run cli reset-stats -- --force\n'));
console.log(styles.error('操作已取消 - 请添加 --force 参数确认重置'));
await redis.disconnect();
return;
}
// 获取当前统计概览
const spinner = ora('正在获取当前统计数据...').start();
try {
const apiKeys = await apiKeyService.getAllApiKeys();
const totalTokens = apiKeys.reduce((sum, key) => sum + (key.usage?.total?.tokens || 0), 0);
const totalRequests = apiKeys.reduce((sum, key) => sum + (key.usage?.total?.requests || 0), 0);
spinner.succeed('统计数据获取完成');
console.log(styles.info('\n📊 当前统计概览:'));
console.log(` API Keys 数量: ${apiKeys.length}`);
console.log(` 总 Token 使用量: ${totalTokens.toLocaleString()}`);
console.log(` 总请求数量: ${totalRequests.toLocaleString()}\n`);
// 执行重置操作
const resetSpinner = ora('正在重置所有API Key统计数据...').start();
const stats = await redis.resetAllUsageStats();
resetSpinner.succeed('所有统计数据重置完成');
// 显示重置结果
console.log(styles.success('\n✅ 重置操作完成!\n'));
console.log(styles.info('📊 重置详情:'));
console.log(` 重置的API Key数量: ${stats.resetApiKeys}`);
console.log(` 删除的总体统计: ${stats.deletedKeys}`);
console.log(` 删除的每日统计: ${stats.deletedDailyKeys}`);
console.log(` 删除的每月统计: ${stats.deletedMonthlyKeys}`);
console.log(styles.warning('\n💡 提示: API Key本身未被删除只是清空了使用统计数据'));
} catch (error) {
spinner.fail('重置操作失败');
console.error(styles.error(error.message));
}
await redis.disconnect();
});
// 📊 系统状态 // 📊 系统状态
program program
@@ -304,47 +98,6 @@ program
await redis.disconnect(); await redis.disconnect();
}); });
// 🧹 清理命令
program
.command('cleanup')
.description('清理过期数据')
.action(async () => {
await initialize();
const { confirm } = await inquirer.prompt({
type: 'confirm',
name: 'confirm',
message: '确定要清理过期数据吗?',
default: false
});
if (!confirm) {
console.log(styles.warning('操作已取消'));
await redis.disconnect();
return;
}
const spinner = ora('正在清理过期数据...').start();
try {
const [expiredKeys, errorAccounts] = await Promise.all([
apiKeyService.cleanupExpiredKeys(),
claudeAccountService.cleanupErrorAccounts()
]);
await redis.cleanup();
spinner.succeed('清理完成');
console.log(`${styles.success('✅')} 清理了 ${expiredKeys} 个过期 API Key`);
console.log(`${styles.success('✅')} 重置了 ${errorAccounts} 个错误账户`);
} catch (error) {
spinner.fail('清理失败');
console.error(styles.error(error.message));
}
await redis.disconnect();
});
// 实现具体功能函数 // 实现具体功能函数
@@ -397,315 +150,10 @@ async function createInitialAdmin() {
} }
} }
async function setAdminPassword() {
console.log(styles.title('\n🔑 设置管理员密码\n'));
const passwordData = await inquirer.prompt([
{
type: 'password',
name: 'newPassword',
message: '新密码:',
validate: input => input.length >= 8 || '密码至少8个字符'
},
{
type: 'password',
name: 'confirmPassword',
message: '确认密码:',
validate: (input, answers) => input === answers.newPassword || '密码不匹配'
}
]);
const spinner = ora('正在更新密码...').start();
try {
const adminData = await redis.getSession('admin_credentials');
if (!adminData || Object.keys(adminData).length === 0) {
spinner.fail('未找到管理员账户');
console.log(styles.warning('请先创建初始管理员账户'));
return;
}
const passwordHash = await bcrypt.hash(passwordData.newPassword, 12);
adminData.passwordHash = passwordHash;
adminData.updatedAt = new Date().toISOString();
await redis.setSession('admin_credentials', adminData, 0);
spinner.succeed('密码更新成功');
console.log(`${styles.success('✅')} 管理员密码已更新`);
} catch (error) {
spinner.fail('密码更新失败');
console.error(styles.error(error.message));
}
}
async function listApiKeys() {
const spinner = ora('正在获取 API Keys...').start();
try {
const apiKeys = await apiKeyService.getAllApiKeys();
spinner.succeed(`找到 ${apiKeys.length} 个 API Key`);
if (apiKeys.length === 0) {
console.log(styles.warning('没有找到任何 API Key'));
return;
}
const tableData = [
['ID', '名称', '状态', 'Token使用', '请求数', '创建时间']
];
apiKeys.forEach(key => {
tableData.push([
key.id.substring(0, 8) + '...',
key.name,
key.isActive ? '🟢 活跃' : '🔴 停用',
key.usage?.total?.tokens?.toLocaleString() || '0',
key.usage?.total?.requests?.toLocaleString() || '0',
new Date(key.createdAt).toLocaleDateString()
]);
});
console.log('\n📋 API Keys 列表:\n');
console.log(table(tableData));
} catch (error) {
spinner.fail('获取 API Keys 失败');
console.error(styles.error(error.message));
}
}
async function createApiKey() {
console.log(styles.title('\n🔑 创建新的 API Key\n'));
const keyData = await inquirer.prompt([
{
type: 'input',
name: 'name',
message: 'API Key 名称:',
validate: input => input.length > 0 || '名称不能为空'
},
{
type: 'input',
name: 'description',
message: '描述 (可选):'
},
{
type: 'number',
name: 'tokenLimit',
message: 'Token 限制 (0=无限制):',
default: 1000000
},
]);
const spinner = ora('正在创建 API Key...').start();
try {
const newKey = await apiKeyService.generateApiKey(keyData);
spinner.succeed('API Key 创建成功');
console.log(`${styles.success('✅')} API Key: ${styles.warning(newKey.apiKey)}`);
console.log(`${styles.info('')} 请妥善保管此 API Key它只会显示一次`);
} catch (error) {
spinner.fail('创建 API Key 失败');
console.error(styles.error(error.message));
}
}
async function resetAllApiKeyStats() {
console.log(styles.title('\n🧹 重置所有API Key统计数据\n'));
// 显示警告信息
console.log(styles.warning('⚠️ 警告: 此操作将删除所有API Key的使用统计数据!'));
console.log(styles.dim(' 包括: Token使用量、请求数量、每日/每月统计、最后使用时间等'));
console.log(styles.dim(' 此操作不可逆,请谨慎操作!\n'));
// 第一次确认
const { firstConfirm } = await inquirer.prompt({
type: 'confirm',
name: 'firstConfirm',
message: '您确定要重置所有API Key的统计数据吗',
default: false
});
if (!firstConfirm) {
console.log(styles.info('操作已取消'));
return;
}
// 获取当前统计概览
const spinner = ora('正在获取当前统计数据...').start();
try {
const apiKeys = await apiKeyService.getAllApiKeys();
const totalTokens = apiKeys.reduce((sum, key) => sum + (key.usage?.total?.tokens || 0), 0);
const totalRequests = apiKeys.reduce((sum, key) => sum + (key.usage?.total?.requests || 0), 0);
spinner.succeed('统计数据获取完成');
console.log(styles.info('\n📊 当前统计概览:'));
console.log(` API Keys 数量: ${apiKeys.length}`);
console.log(` 总 Token 使用量: ${totalTokens.toLocaleString()}`);
console.log(` 总请求数量: ${totalRequests.toLocaleString()}\n`);
// 第二次确认(需要输入"RESET"
const { confirmation } = await inquirer.prompt({
type: 'input',
name: 'confirmation',
message: '请输入 "RESET" 来确认重置操作:',
validate: input => input === 'RESET' || '请输入正确的确认文本 "RESET"'
});
if (confirmation !== 'RESET') {
console.log(styles.info('操作已取消'));
return;
}
// 执行重置操作
const resetSpinner = ora('正在重置所有API Key统计数据...').start();
const stats = await redis.resetAllUsageStats();
resetSpinner.succeed('所有统计数据重置完成');
// 显示重置结果
console.log(styles.success('\n✅ 重置操作完成!\n'));
console.log(styles.info('📊 重置详情:'));
console.log(` 重置的API Key数量: ${stats.resetApiKeys}`);
console.log(` 删除的总体统计: ${stats.deletedKeys}`);
console.log(` 删除的每日统计: ${stats.deletedDailyKeys}`);
console.log(` 删除的每月统计: ${stats.deletedMonthlyKeys}`);
console.log(styles.warning('\n💡 提示: API Key本身未被删除只是清空了使用统计数据'));
} catch (error) {
spinner.fail('重置操作失败');
console.error(styles.error(error.message));
}
}
async function viewApiKeyStats() {
console.log(styles.title('\n📊 API Key 使用统计\n'));
const spinner = ora('正在获取统计数据...').start();
try {
const apiKeys = await apiKeyService.getAllApiKeys();
if (apiKeys.length === 0) {
spinner.succeed('获取完成');
console.log(styles.warning('没有找到任何 API Key'));
return;
}
spinner.succeed(`找到 ${apiKeys.length} 个 API Key 的统计数据`);
const tableData = [
['名称', 'Token总量', '输入Token', '输出Token', '请求数', '最后使用']
];
let totalTokens = 0;
let totalRequests = 0;
apiKeys.forEach(key => {
const usage = key.usage?.total || {};
const tokens = usage.tokens || 0;
const inputTokens = usage.inputTokens || 0;
const outputTokens = usage.outputTokens || 0;
const requests = usage.requests || 0;
totalTokens += tokens;
totalRequests += requests;
tableData.push([
key.name,
tokens.toLocaleString(),
inputTokens.toLocaleString(),
outputTokens.toLocaleString(),
requests.toLocaleString(),
key.lastUsedAt ? new Date(key.lastUsedAt).toLocaleDateString() : '从未使用'
]);
});
console.log(table(tableData));
console.log(styles.info('\n📈 总计统计:'));
console.log(`总 Token 使用量: ${styles.success(totalTokens.toLocaleString())}`);
console.log(`总请求数量: ${styles.success(totalRequests.toLocaleString())}`);
} catch (error) {
spinner.fail('获取统计数据失败');
console.error(styles.error(error.message));
}
}
async function updateApiKey() {
console.log(styles.title('\n📝 更新 API Key\n'));
console.log(styles.warning('功能开发中...'));
}
async function deleteApiKey() {
console.log(styles.title('\n🗑 删除 API Key\n'));
console.log(styles.warning('功能开发中...'));
}
async function resetAdminPassword() {
console.log(styles.title('\n🔄 重置管理员密码\n'));
console.log(styles.warning('功能开发中...'));
}
async function viewAdminInfo() {
console.log(styles.title('\n👤 管理员信息\n'));
const spinner = ora('正在获取管理员信息...').start();
try {
const adminData = await redis.getSession('admin_credentials');
if (!adminData || Object.keys(adminData).length === 0) {
spinner.fail('未找到管理员账户');
console.log(styles.warning('请先创建初始管理员账户'));
return;
}
spinner.succeed('管理员信息获取成功');
console.log(`用户名: ${styles.info(adminData.username)}`);
console.log(`创建时间: ${styles.dim(new Date(adminData.createdAt).toLocaleString())}`);
console.log(`最后登录: ${adminData.lastLogin ? styles.dim(new Date(adminData.lastLogin).toLocaleString()) : '从未登录'}`);
} catch (error) {
spinner.fail('获取管理员信息失败');
console.error(styles.error(error.message));
}
}
async function createClaudeAccount() {
console.log(styles.title('\n🏢 创建 Claude 账户\n'));
console.log(styles.warning('功能开发中... 请使用Web界面创建OAuth账户'));
}
async function updateClaudeAccount() {
console.log(styles.title('\n📝 更新 Claude 账户\n'));
console.log(styles.warning('功能开发中...'));
}
async function deleteClaudeAccount() {
console.log(styles.title('\n🗑 删除 Claude 账户\n'));
console.log(styles.warning('功能开发中...'));
}
async function refreshAccountToken() {
console.log(styles.title('\n🔄 刷新账户 Token\n'));
console.log(styles.warning('功能开发中...'));
}
async function testClaudeAccount() {
console.log(styles.title('\n🧪 测试 Claude 账户\n'));
console.log(styles.warning('功能开发中...'));
}
async function listClaudeAccounts() { async function listClaudeAccounts() {
const spinner = ora('正在获取 Claude 账户...').start(); const spinner = ora('正在获取 Claude 账户...').start();
@@ -756,11 +204,7 @@ program.parse();
if (!process.argv.slice(2).length) { if (!process.argv.slice(2).length) {
console.log(styles.title('🚀 Claude Relay Service CLI\n')); console.log(styles.title('🚀 Claude Relay Service CLI\n'));
console.log('使用以下命令管理服务:\n'); console.log('使用以下命令管理服务:\n');
console.log(' claude-relay-cli admin - 管理员账户操作'); console.log(' claude-relay-cli admin - 创建初始管理员账户');
console.log(' claude-relay-cli keys - API Key 管理 (包含重置统计数据)');
console.log(' claude-relay-cli accounts - Claude 账户管理');
console.log(' claude-relay-cli status - 查看系统状态'); console.log(' claude-relay-cli status - 查看系统状态');
console.log(' claude-relay-cli cleanup - 清理过期数据');
console.log(' claude-relay-cli reset-stats - 重置所有API Key统计数据');
console.log('\n使用 --help 查看详细帮助信息'); console.log('\n使用 --help 查看详细帮助信息');
} }