UNPKG

claude-keys-manager

Version:

Claude Code API Key manager with template and repository management tools

314 lines (256 loc) 7.83 kB
const fs = require('fs-extra'); const path = require('path'); const os = require('os'); const chalk = require('chalk'); const ora = require('ora'); const fetch = require('node-fetch'); class KeyManager { constructor() { this.configDir = path.join(os.homedir(), '.claude-code-manager'); this.keysFile = path.join(this.configDir, 'keys.json'); this.configFile = path.join(this.configDir, 'config.json'); this.keys = []; this.currentKey = null; } async init() { await fs.ensureDir(this.configDir); await this.loadKeys(); await this.loadConfig(); } async loadKeys() { try { if (await fs.pathExists(this.keysFile)) { this.keys = await fs.readJson(this.keysFile); } } catch (error) { console.error(chalk.red(`加载keys失败: ${error.message}`)); this.keys = []; } } async saveKeys() { try { await fs.writeJson(this.keysFile, this.keys, { spaces: 2 }); } catch (error) { console.error(chalk.red(`保存keys失败: ${error.message}`)); } } async loadConfig() { try { if (await fs.pathExists(this.configFile)) { const config = await fs.readJson(this.configFile); this.currentKey = config.currentKey; } } catch (error) { console.error(chalk.red(`加载配置失败: ${error.message}`)); } } async saveConfig() { try { const config = { currentKey: this.currentKey, lastUpdated: new Date().toISOString() }; await fs.writeJson(this.configFile, config, { spaces: 2 }); } catch (error) { console.error(chalk.red(`保存配置失败: ${error.message}`)); } } async addKey(keyData) { const { name, key, description = '' } = keyData; if (!name || !key) { throw new Error('Key名称和值不能为空'); } // 检查是否已存在 const existing = this.keys.find(k => k.name === name); if (existing) { throw new Error(`Key "${name}" 已存在`); } const newKey = { id: Date.now().toString(), name, key: this.encryptKey(key), description, created: new Date().toISOString(), lastUsed: null, usageCount: 0, isValid: null }; this.keys.push(newKey); await this.saveKeys(); // 如果是第一个key,设为当前key if (this.keys.length === 1) { this.currentKey = newKey.id; await this.saveConfig(); } return newKey; } async removeKey(keyId) { const index = this.keys.findIndex(k => k.id === keyId); if (index === -1) { throw new Error('Key不存在'); } const removedKey = this.keys.splice(index, 1)[0]; // 如果删除的是当前key,重置 if (this.currentKey === keyId) { this.currentKey = this.keys.length > 0 ? this.keys[0].id : null; } await this.saveKeys(); await this.saveConfig(); return removedKey; } async switchKey(keyId) { const key = this.keys.find(k => k.id === keyId); if (!key) { throw new Error('Key不存在'); } this.currentKey = keyId; await this.saveConfig(); return key; } getCurrentKey() { if (!this.currentKey) return null; return this.keys.find(k => k.id === this.currentKey); } async validateKey(keyId) { const key = this.keys.find(k => k.id === keyId); if (!key) { throw new Error('Key不存在'); } const spinner = ora('验证API Key...').start(); try { const decryptedKey = this.decryptKey(key.key); // 测试API调用 const isValid = await this.testKeyValidity(decryptedKey); // 更新key状态 key.isValid = isValid; key.lastValidated = new Date().toISOString(); await this.saveKeys(); if (isValid) { spinner.succeed(`Key "${key.name}" 验证成功`); } else { spinner.fail(`Key "${key.name}" 验证失败`); } return isValid; } catch (error) { key.isValid = false; key.lastValidated = new Date().toISOString(); await this.saveKeys(); spinner.fail(`Key验证失败: ${error.message}`); return false; } } async testKeyValidity(apiKey) { try { // 这里应该调用Claude API来验证key // 由于我们没有实际的Claude API,这里使用模拟验证 // 检查key格式 (假设Claude key以特定格式开头) if (!apiKey.startsWith('sk-') && !apiKey.startsWith('claude-')) { return false; } // 模拟API调用 await new Promise(resolve => setTimeout(resolve, 1000)); // 简单的key格式验证 return apiKey.length >= 32; } catch (error) { return false; } } async testKey(keyId, testPrompt = "Hello, Claude!") { const key = this.keys.find(k => k.id === keyId); if (!key) { throw new Error('Key不存在'); } const decryptedKey = this.decryptKey(key.key); const spinner = ora('测试API Key功能...').start(); try { // 模拟API测试 const result = await this.simulateApiCall(decryptedKey, testPrompt); // 更新使用统计 key.lastUsed = new Date().toISOString(); key.usageCount++; await this.saveKeys(); spinner.succeed('API Key测试成功'); return result; } catch (error) { spinner.fail(`API Key测试失败: ${error.message}`); throw error; } } async simulateApiCall(apiKey, prompt) { // 模拟API调用延迟 await new Promise(resolve => setTimeout(resolve, 2000)); return { success: true, response: `模拟响应: 收到提示 "${prompt}"`, usage: { prompt_tokens: prompt.length, completion_tokens: 20, total_tokens: prompt.length + 20 }, timestamp: new Date().toISOString() }; } listKeys() { return this.keys.map(key => ({ ...key, key: '***' + key.key.slice(-4), // 隐藏key内容,只显示后4位 isCurrent: key.id === this.currentKey })); } getKeyStats() { const total = this.keys.length; const valid = this.keys.filter(k => k.isValid === true).length; const invalid = this.keys.filter(k => k.isValid === false).length; const untested = this.keys.filter(k => k.isValid === null).length; const totalUsage = this.keys.reduce((sum, k) => sum + k.usageCount, 0); return { total, valid, invalid, untested, totalUsage, currentKey: this.getCurrentKey()?.name || 'None' }; } // 简单的加密/解密 (实际应用中应使用更安全的方法) encryptKey(key) { return Buffer.from(key).toString('base64'); } decryptKey(encryptedKey) { return Buffer.from(encryptedKey, 'base64').toString('utf8'); } async exportKeys(outputPath) { const exportData = { version: '1.0.0', exported: new Date().toISOString(), keys: this.keys.map(key => ({ ...key, key: this.decryptKey(key.key) // 导出时解密 })) }; await fs.writeJson(outputPath, exportData, { spaces: 2 }); return outputPath; } async importKeys(inputPath) { const importData = await fs.readJson(inputPath); if (!importData.keys || !Array.isArray(importData.keys)) { throw new Error('无效的导入文件格式'); } let imported = 0; let skipped = 0; for (const keyData of importData.keys) { try { await this.addKey({ name: keyData.name, key: keyData.key, description: keyData.description || '' }); imported++; } catch (error) { skipped++; } } return { imported, skipped }; } } module.exports = KeyManager;