UNPKG

remcode

Version:

Turn your AI assistant into a codebase expert. Intelligent code analysis, semantic search, and software engineering guidance through MCP integration.

244 lines (243 loc) • 10.1 kB
"use strict"; /** * Token Management Utilities * * Handles collection, validation, and storage of API tokens for remcode MCP server */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TokenManager = void 0; const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const readline_1 = __importDefault(require("readline")); const logger_1 = require("./logger"); const chalk_1 = __importDefault(require("chalk")); const logger = (0, logger_1.getLogger)('TokenManager'); /** * Token Manager class for handling API tokens */ class TokenManager { constructor(projectRoot) { const root = projectRoot || process.cwd(); this.envFilePath = path_1.default.join(root, '.env'); this.gitignorePath = path_1.default.join(root, '.gitignore'); } /** * Load existing tokens from .env file */ loadExistingTokens() { try { if (!fs_1.default.existsSync(this.envFilePath)) { logger.info('No existing .env file found'); return {}; } const envContent = fs_1.default.readFileSync(this.envFilePath, 'utf8'); const tokens = {}; envContent.split('\n').forEach(line => { const trimmedLine = line.trim(); if (trimmedLine && !trimmedLine.startsWith('#')) { const [key, ...valueParts] = trimmedLine.split('='); if (key && valueParts.length > 0) { const value = valueParts.join('=').trim(); if (this.isTokenKey(key.trim())) { tokens[key.trim()] = value; } } } }); logger.info(`Loaded existing tokens: ${Object.keys(tokens).join(', ')}`); return tokens; } catch (error) { logger.warn(`Failed to load existing tokens: ${error instanceof Error ? error.message : String(error)}`); return {}; } } /** * Collect missing tokens interactively from user */ async collectMissingTokens(existingTokens, cliTokens) { const requiredTokens = { GITHUB_TOKEN: 'GitHub Personal Access Token (for repository access)', PINECONE_API_KEY: 'Pinecone API Key (for vector operations)', HUGGINGFACE_TOKEN: 'HuggingFace Token (for embedding generation)' }; const finalTokens = { ...existingTokens }; const missingTokens = []; // Check which tokens are missing for (const [key, description] of Object.entries(requiredTokens)) { if (cliTokens[key]) { finalTokens[key] = cliTokens[key]; console.log(chalk_1.default.green(`āœ“ ${key}: Provided via CLI argument`)); } else if (existingTokens[key]) { finalTokens[key] = existingTokens[key]; console.log(chalk_1.default.green(`āœ“ ${key}: Found in .env file`)); } else { missingTokens.push(key); } } // If no tokens are missing, return if (missingTokens.length === 0) { console.log(chalk_1.default.green('\nšŸŽ‰ All required tokens are available!')); return finalTokens; } console.log(chalk_1.default.yellow(`\nšŸ”‘ Missing ${missingTokens.length} required token(s). Let's collect them:`)); console.log(chalk_1.default.gray('(You can skip tokens by pressing Enter, but functionality will be limited)\n')); // Collect missing tokens interactively for (const tokenKey of missingTokens) { const description = requiredTokens[tokenKey]; const token = await this.promptForToken(tokenKey, description); if (token && token.trim()) { finalTokens[tokenKey] = token.trim(); console.log(chalk_1.default.green(`āœ“ ${tokenKey}: Collected successfully\n`)); } else { console.log(chalk_1.default.yellow(`⚠ ${tokenKey}: Skipped (functionality will be limited)\n`)); } } return finalTokens; } /** * Prompt user for a specific token */ async promptForToken(tokenKey, description) { return new Promise((resolve) => { const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout }); console.log(chalk_1.default.cyan(`šŸ“ ${tokenKey}:`)); console.log(chalk_1.default.gray(` ${description}`)); console.log(chalk_1.default.gray(` Get it from: ${this.getTokenUrl(tokenKey)}`)); rl.question(chalk_1.default.white(' Enter token: '), (answer) => { rl.close(); resolve(answer); }); }); } /** * Get the URL where users can obtain each token */ getTokenUrl(tokenKey) { const urls = { GITHUB_TOKEN: 'https://github.com/settings/tokens', PINECONE_API_KEY: 'https://app.pinecone.io/organizations/-/projects/-/keys', HUGGINGFACE_TOKEN: 'https://huggingface.co/settings/tokens' }; return urls[tokenKey] || 'Check the service documentation'; } /** * Save tokens to .env file */ async saveTokensToEnv(tokens) { try { // Read existing .env content to preserve other variables let existingOtherVars = []; if (fs_1.default.existsSync(this.envFilePath)) { const existingContent = fs_1.default.readFileSync(this.envFilePath, 'utf8'); // Extract non-token variables existingContent.split('\n').forEach(line => { const trimmedLine = line.trim(); if (trimmedLine && !trimmedLine.startsWith('#')) { const [key] = trimmedLine.split('='); if (key && !this.isTokenKey(key.trim())) { existingOtherVars.push(trimmedLine); } } else if (trimmedLine.startsWith('#')) { existingOtherVars.push(trimmedLine); } }); } // Build new .env content const envLines = []; envLines.push('# Remcode Environment Configuration'); envLines.push(''); envLines.push('# API Tokens'); if (tokens.GITHUB_TOKEN) { envLines.push(`GITHUB_TOKEN=${tokens.GITHUB_TOKEN}`); } if (tokens.PINECONE_API_KEY) { envLines.push(`PINECONE_API_KEY=${tokens.PINECONE_API_KEY}`); } if (tokens.HUGGINGFACE_TOKEN) { envLines.push(`HUGGINGFACE_TOKEN=${tokens.HUGGINGFACE_TOKEN}`); } // Add other existing variables if (existingOtherVars.length > 0) { envLines.push(''); envLines.push('# Other Configuration'); envLines.push(...existingOtherVars); } // Write to .env file const newContent = envLines.join('\n') + '\n'; fs_1.default.writeFileSync(this.envFilePath, newContent, 'utf8'); console.log(chalk_1.default.green(`āœ… Tokens saved to ${path_1.default.relative(process.cwd(), this.envFilePath)}`)); // Ensure .env is in .gitignore await this.ensureEnvInGitignore(); } catch (error) { const errorMsg = `Failed to save tokens: ${error instanceof Error ? error.message : String(error)}`; logger.error(errorMsg); throw new Error(errorMsg); } } /** * Ensure .env is added to .gitignore */ async ensureEnvInGitignore() { try { let gitignoreContent = ''; let hasEnvEntry = false; // Read existing .gitignore if (fs_1.default.existsSync(this.gitignorePath)) { gitignoreContent = fs_1.default.readFileSync(this.gitignorePath, 'utf8'); hasEnvEntry = gitignoreContent.includes('.env'); } // Add .env to .gitignore if not present if (!hasEnvEntry) { const envEntries = [ '', '# Environment variables', '.env', '.env.local', '.env.*.local' ]; if (gitignoreContent && !gitignoreContent.endsWith('\n')) { gitignoreContent += '\n'; } gitignoreContent += envEntries.join('\n') + '\n'; fs_1.default.writeFileSync(this.gitignorePath, gitignoreContent, 'utf8'); console.log(chalk_1.default.green(`āœ… Added .env to ${path_1.default.relative(process.cwd(), this.gitignorePath)}`)); } else { console.log(chalk_1.default.gray(`ℹ .env already in ${path_1.default.relative(process.cwd(), this.gitignorePath)}`)); } } catch (error) { logger.warn(`Failed to update .gitignore: ${error instanceof Error ? error.message : String(error)}`); } } /** * Check if a key is a token key */ isTokenKey(key) { const tokenKeys = ['GITHUB_TOKEN', 'PINECONE_API_KEY', 'HUGGINGFACE_TOKEN']; return tokenKeys.includes(key); } /** * Convert CLI options to token config */ static cliOptionsToTokens(options) { return { GITHUB_TOKEN: options.githubToken, PINECONE_API_KEY: options.pineconeKey, HUGGINGFACE_TOKEN: options.huggingfaceToken }; } } exports.TokenManager = TokenManager;