UNPKG

claude-key-manager

Version:

🚀 Claude Key Manager 4.1.3 - 修复密钥验证问题,支持更多密钥格式

428 lines (372 loc) 12.2 kB
const inquirer = require('inquirer'); const chalk = require('chalk'); const boxen = require('boxen'); const ora = require('ora'); const VersionManager = require('./version-manager'); class InteractiveMenu { constructor(keyManager) { this.keyManager = keyManager; this.versionManager = new VersionManager(); } async start() { console.clear(); console.log(boxen( chalk.bold.blue('🚀 Claude Key Manager 4.0\n') + chalk.gray('纯Node.js重构版 - 交互式菜单模式'), { padding: 1, borderColor: 'blue', borderStyle: 'round' } )); while (true) { try { const action = await this.showMainMenu(); if (action === 'exit') break; await this.handleAction(action); // 操作完成后暂停,让用户看到结果 await inquirer.prompt([{ type: 'input', name: 'continue', message: chalk.gray('按回车键继续...') }]); } catch (error) { console.log(chalk.red('\n❌ 错误:'), error.message); await inquirer.prompt([{ type: 'input', name: 'continue', message: chalk.gray('按回车键继续...') }]); } } console.log(chalk.green('\n👋 再见!')); } async showMainMenu() { // 获取当前状态信息 const currentKey = await this.keyManager.getCurrentKey(); const allKeys = await this.keyManager.listKeys(); const statusInfo = [ `📊 状态信息:`, ` 密钥总数: ${allKeys.length}`, ` 当前密钥: ${currentKey ? chalk.green(currentKey.name) : chalk.yellow('未设置')}`, '' ].join('\n'); console.log('\n' + boxen(statusInfo, { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'gray' })); const choices = [ { name: '📝 添加新的API密钥', value: 'add', short: 'Add' }, { name: `📋 列出所有密钥 (${allKeys.length}个)`, value: 'list', short: 'List' }, { name: '🔄 切换当前使用的密钥', value: 'switch', short: 'Switch', disabled: allKeys.length === 0 ? '没有可用密钥' : false }, { name: '🗑️ 删除密钥 (你要的功能!)', value: 'delete', short: 'Delete', disabled: allKeys.length === 0 ? '没有可删除的密钥' : false }, { name: '👁️ 查看当前密钥详情', value: 'current', short: 'Current', disabled: !currentKey ? '没有当前密钥' : false }, { name: '🔧 高级选项...', value: 'advanced', short: 'Advanced' }, new inquirer.Separator(), { name: '📋 版本信息和更新检查', value: 'version', short: 'Version' }, { name: '🔄 检查并更新到最新版本', value: 'update', short: 'Update' }, { name: '❓ 显示帮助信息', value: 'help', short: 'Help' }, { name: '🚪 退出', value: 'exit', short: 'Exit' } ]; const { action } = await inquirer.prompt([{ type: 'list', name: 'action', message: '选择要执行的操作:', choices: choices, pageSize: 12 }]); return action; } async handleAction(action) { switch (action) { case 'add': await this.handleAddKey(); break; case 'list': await this.handleListKeys(); break; case 'switch': await this.handleSwitchKey(); break; case 'delete': await this.handleDeleteKey(); break; case 'current': await this.handleShowCurrent(); break; case 'advanced': await this.handleAdvanced(); break; case 'version': await this.handleVersion(); break; case 'update': await this.handleUpdate(); break; case 'help': await this.handleHelp(); break; } } async handleAddKey() { console.log(chalk.blue('\n📝 添加新的API密钥\n')); const answers = await inquirer.prompt([ { type: 'input', name: 'key', message: '请输入Claude API密钥:', validate: (input) => { if (!input.trim()) return '密钥不能为空'; if (!input.startsWith('sk-ant-')) return '密钥应该以 sk-ant- 开头'; if (input.length < 20) return '密钥长度太短'; return true; } }, { type: 'input', name: 'name', message: '为此密钥设置名称:', default: 'default', validate: (input) => input.trim() ? true : '名称不能为空' }, { type: 'input', name: 'description', message: '密钥描述 (可选):', default: '' }, { type: 'input', name: 'apiUrl', message: 'API Base URL (按回车使用默认):', default: 'https://api.anthropic.com', validate: (input) => { if (!input.trim()) return true; // 允许空值,会使用默认 try { new URL(input.trim()); return true; } catch { return '请输入有效的URL格式 (例如: https://api.anthropic.com)'; } } } ]); const spinner = ora('正在保存密钥...').start(); try { await this.keyManager.addKey( answers.key.trim(), answers.name.trim(), answers.description.trim(), answers.apiUrl.trim() ); spinner.succeed(chalk.green('✅ 密钥添加成功!')); } catch (error) { spinner.fail(chalk.red('❌ 添加失败')); throw error; } } async handleListKeys() { console.log(chalk.blue('\n📋 所有已保存的密钥\n')); const spinner = ora('正在加载密钥列表...').start(); const keys = await this.keyManager.listKeys(true); spinner.stop(); if (keys.length === 0) { console.log(chalk.yellow('⚠️ 没有找到任何密钥')); return; } console.log(boxen( keys.map(k => { const status = k.active ? chalk.green('👉 当前') : ' '; const name = chalk.bold(k.name); const desc = k.description ? chalk.gray(` - ${k.description}`) : ''; const created = chalk.gray(`\n 创建: ${new Date(k.createdAt).toLocaleString()}`); const apiUrl = chalk.blue(`\n API: ${k.apiUrl}`); const preview = k.preview ? chalk.gray(`\n 预览: ${k.preview}`) : ''; return `${status} ${name}${desc}${created}${apiUrl}${preview}`; }).join('\n\n'), { padding: 1, borderColor: 'blue', title: `📋 密钥列表 (${keys.length}个)`, titleAlignment: 'center' } )); } async handleSwitchKey() { console.log(chalk.blue('\n🔄 切换当前使用的密钥\n')); const keys = await this.keyManager.listKeys(); const activeKey = await this.keyManager.getCurrentKey(); const choices = keys.map(k => ({ name: `${k.name}${k.description ? ` - ${k.description}` : ''}${k.active ? chalk.green(' (当前)') : ''}`, value: k.name, disabled: k.active ? '已经是当前密钥' : false })); if (choices.filter(c => !c.disabled).length === 0) { console.log(chalk.yellow('⚠️ 没有可切换的密钥')); return; } const { selectedKey } = await inquirer.prompt([{ type: 'list', name: 'selectedKey', message: '选择要切换到的密钥:', choices: choices }]); const spinner = ora(`正在切换到 ${selectedKey}...`).start(); try { await this.keyManager.switchKey(selectedKey); spinner.succeed(chalk.green(`✅ 已切换到密钥: ${selectedKey}`)); } catch (error) { spinner.fail(chalk.red('❌ 切换失败')); throw error; } } async handleDeleteKey() { console.log(chalk.blue('\n🗑️ 删除密钥\n')); const keys = await this.keyManager.listKeys(); const choices = keys.map(k => ({ name: `${k.name}${k.description ? ` - ${k.description}` : ''}${k.active ? chalk.red(' (当前使用中!)') : ''}`, value: k.name })); const { keyToDelete } = await inquirer.prompt([{ type: 'list', name: 'keyToDelete', message: '选择要删除的密钥:', choices: choices }]); // 二次确认 - 特别重要! const { confirmDelete } = await inquirer.prompt([{ type: 'confirm', name: 'confirmDelete', message: chalk.red(`⚠️ 确定要删除密钥 '${keyToDelete}' 吗?此操作不可撤销!`), default: false }]); if (!confirmDelete) { console.log(chalk.yellow('❌ 删除操作已取消')); return; } const spinner = ora(`正在删除 ${keyToDelete}...`).start(); try { await this.keyManager.deleteKey(keyToDelete, true); // 强制删除,因为已经确认过了 spinner.succeed(chalk.green(`✅ 密钥 '${keyToDelete}' 删除成功`)); } catch (error) { spinner.fail(chalk.red('❌ 删除失败')); throw error; } } async handleShowCurrent() { console.log(chalk.blue('\n👁️ 当前密钥详情\n')); const current = await this.keyManager.getCurrentKey(); const info = [ `🔑 名称: ${chalk.bold(current.name)}`, `📝 描述: ${current.description || chalk.gray('无')}`, `🔍 预览: ${current.preview}`, `⏰ 完整密钥长度: ${current.key.length} 字符` ].join('\n'); console.log(boxen(info, { padding: 1, borderColor: 'green', title: '当前密钥', titleAlignment: 'center' })); } async handleAdvanced() { const { advancedAction } = await inquirer.prompt([{ type: 'list', name: 'advancedAction', message: '高级选项:', choices: [ { name: '🔍 密钥验证和测试', value: 'validate' }, { name: '📊 使用统计', value: 'stats' }, { name: '🔧 清理无效密钥', value: 'cleanup' }, { name: '↩️ 返回主菜单', value: 'back' } ] }]); if (advancedAction === 'back') return; // 实现高级功能... console.log(chalk.yellow(`🚧 功能 "${advancedAction}" 正在开发中...`)); } async handleVersion() { console.log(chalk.blue('\n📋 版本信息和更新检查\n')); await this.versionManager.showVersionInfo(); } async handleUpdate() { console.log(chalk.blue('\n🔄 检查并更新到最新版本\n')); await this.versionManager.updateHelper(); } async handleHelp() { const helpText = ` ${chalk.bold('🚀 Claude Key Manager 4.0 使用指南')} ${chalk.blue('基本命令:')} claude-key add <key> 添加新密钥 claude-key list 列出所有密钥 claude-key switch <name> 切换密钥 claude-key delete <name> 删除密钥 claude-key current 显示当前密钥 ${chalk.blue('版本管理:')} claude-key version 显示版本信息并检查更新 claude-key update 更新到最新版本 ${chalk.blue('交互模式:')} claude-key 启动后输入 / 进入交互模式 claude-key interactive 直接进入交互模式 ${chalk.blue('别名:')} ck claude-key 的简短别名 ${chalk.blue('特色功能:')} • 🔒 安全的系统密钥存储 (keytar) • 🎯 直观的交互式菜单 • 🗑️ 安全删除 (二次确认) • 📊 密钥使用统计 • 🔄 自动版本检查和更新 • 🚀 纯Node.js,无需编译 ${chalk.gray('提示: 在任何界面输入 Ctrl+C 退出')} `; console.log(boxen(helpText, { padding: 1, borderColor: 'cyan', borderStyle: 'round' })); } } module.exports = InteractiveMenu;