claude-keys-manager
Version:
Claude Code API Key manager with template and repository management tools
314 lines (256 loc) • 7.83 kB
JavaScript
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;