diff --git a/cli/index.js b/cli/index.js index 996ae5d8..972ed0d7 100644 --- a/cli/index.js +++ b/cli/index.js @@ -45,218 +45,12 @@ program .action(async () => { await initialize(); - const { action } = await inquirer.prompt({ - type: 'list', - 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 createInitialAdmin(); 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 @@ -304,47 +98,6 @@ program 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() { const spinner = ora('正在获取 Claude 账户...').start(); @@ -756,11 +204,7 @@ program.parse(); if (!process.argv.slice(2).length) { console.log(styles.title('🚀 Claude Relay Service CLI\n')); console.log('使用以下命令管理服务:\n'); - 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 admin - 创建初始管理员账户'); 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 查看详细帮助信息'); } \ No newline at end of file