claude-keys-manager
Version:
Claude Code API Key manager with template and repository management tools
245 lines (195 loc) • 7.5 kB
JavaScript
const fs = require('fs-extra');
const path = require('path');
const chalk = require('chalk');
const ora = require('ora');
const fetch = require('node-fetch');
const yauzl = require('yauzl');
class ClaudeCodeManager {
constructor() {
this.projectDir = process.cwd();
this.templatesDir = path.join(__dirname, 'templates');
}
async downloadRepository(repoUrl, targetDir = null) {
if (!repoUrl) {
throw new Error('仓库URL不能为空');
}
const spinner = ora('下载GitHub仓库...').start();
try {
const { owner, repo } = this.parseGitHubUrl(repoUrl);
const repoName = targetDir ? path.basename(targetDir) : repo;
const downloadDir = targetDir || path.join(this.projectDir, repoName);
// 检查目录是否已存在
if (await fs.pathExists(downloadDir)) {
spinner.warn(`目录 ${downloadDir} 已存在,跳过下载`);
return downloadDir;
}
// 下载ZIP文件
const zipUrl = `https://github.com/${owner}/${repo}/archive/refs/heads/main.zip`;
spinner.text = '正在下载仓库文件...';
const response = await fetch(zipUrl);
if (!response.ok) {
// 尝试master分支
const masterZipUrl = `https://github.com/${owner}/${repo}/archive/refs/heads/master.zip`;
const masterResponse = await fetch(masterZipUrl);
if (!masterResponse.ok) {
throw new Error(`无法下载仓库: ${response.statusText}`);
}
response = masterResponse;
}
const zipBuffer = await response.buffer();
const tempZipPath = path.join(require('os').tmpdir(), `${repo}.zip`);
await fs.writeFile(tempZipPath, zipBuffer);
// 解压ZIP文件
spinner.text = '正在解压文件...';
await this.extractZip(tempZipPath, downloadDir, repo);
// 清理临时文件
await fs.remove(tempZipPath);
spinner.succeed(`仓库下载完成: ${downloadDir}`);
return downloadDir;
} catch (error) {
spinner.fail(`仓库下载失败: ${error.message}`);
throw error;
}
}
parseGitHubUrl(url) {
// 支持多种GitHub URL格式
const patterns = [
/github\.com\/([^\/]+)\/([^\/]+?)(?:\.git)?(?:\/.*)?$/,
/^([^\/]+)\/([^\/]+)$/ // 简化格式: owner/repo
];
for (const pattern of patterns) {
const match = url.match(pattern);
if (match) {
return { owner: match[1], repo: match[2] };
}
}
throw new Error('无效的GitHub仓库URL格式');
}
extractZip(zipPath, targetDir, repoName) {
return new Promise((resolve, reject) => {
yauzl.open(zipPath, { lazyEntries: true }, (err, zipfile) => {
if (err) return reject(err);
zipfile.readEntry();
zipfile.on('entry', async (entry) => {
// 跳过顶级目录 (repo-main/ 或 repo-master/)
const relativePath = entry.fileName.split('/').slice(1).join('/');
if (!relativePath) {
zipfile.readEntry();
return;
}
const outputPath = path.join(targetDir, relativePath);
if (/\/$/.test(entry.fileName)) {
// 目录
await fs.ensureDir(outputPath);
zipfile.readEntry();
} else {
// 文件
await fs.ensureDir(path.dirname(outputPath));
zipfile.openReadStream(entry, (err, readStream) => {
if (err) return reject(err);
const writeStream = fs.createWriteStream(outputPath);
readStream.pipe(writeStream);
writeStream.on('close', () => zipfile.readEntry());
writeStream.on('error', reject);
});
}
});
zipfile.on('end', resolve);
zipfile.on('error', reject);
});
});
}
async createTemplate(templateName, sourceDir) {
const spinner = ora('创建代码模板...').start();
try {
await fs.ensureDir(this.templatesDir);
const templateDir = path.join(this.templatesDir, templateName);
if (await fs.pathExists(templateDir)) {
spinner.warn(`模板 ${templateName} 已存在`);
return templateDir;
}
await fs.copy(sourceDir, templateDir);
// 创建模板配置文件
const config = {
name: templateName,
created: new Date().toISOString(),
source: sourceDir
};
await fs.writeJson(path.join(templateDir, '.template.json'), config, { spaces: 2 });
spinner.succeed(`模板创建完成: ${templateName}`);
return templateDir;
} catch (error) {
spinner.fail(`模板创建失败: ${error.message}`);
throw error;
}
}
async useTemplate(templateName, targetDir) {
const spinner = ora('应用代码模板...').start();
try {
const templateDir = path.join(this.templatesDir, templateName);
if (!await fs.pathExists(templateDir)) {
throw new Error(`模板 ${templateName} 不存在`);
}
const finalTargetDir = targetDir || path.join(this.projectDir, templateName);
if (await fs.pathExists(finalTargetDir)) {
spinner.warn(`目录 ${finalTargetDir} 已存在`);
return finalTargetDir;
}
// 复制模板文件,排除配置文件
await fs.copy(templateDir, finalTargetDir, {
filter: (src) => !src.endsWith('.template.json')
});
spinner.succeed(`模板应用完成: ${finalTargetDir}`);
return finalTargetDir;
} catch (error) {
spinner.fail(`模板应用失败: ${error.message}`);
throw error;
}
}
async listTemplates() {
try {
if (!await fs.pathExists(this.templatesDir)) {
return [];
}
const templates = [];
const items = await fs.readdir(this.templatesDir);
for (const item of items) {
const templateDir = path.join(this.templatesDir, item);
const configPath = path.join(templateDir, '.template.json');
if (await fs.pathExists(configPath)) {
const config = await fs.readJson(configPath);
templates.push({
name: item,
...config
});
}
}
return templates;
} catch (error) {
return [];
}
}
async run(repoUrl, options = {}) {
try {
console.log(chalk.blue.bold('Claude Code Manager 启动中...'));
if (repoUrl) {
const projectDir = await this.downloadRepository(repoUrl, options.targetDir);
console.log(chalk.green.bold('✅ 下载完成!'));
console.log(chalk.yellow(`项目目录: ${projectDir}`));
// 检查是否有package.json,提示安装依赖
const packageJsonPath = path.join(projectDir, 'package.json');
if (await fs.pathExists(packageJsonPath)) {
console.log(chalk.cyan('💡 检测到package.json,可以运行: cd ' + path.basename(projectDir) + ' && npm install'));
}
return projectDir;
} else {
console.log(chalk.green.bold('✅ Claude Code Manager 准备就绪!'));
}
} catch (error) {
console.error(chalk.red.bold(`❌ 错误: ${error.message}`));
throw error;
}
}
}
module.exports = ClaudeCodeManager;