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
JavaScript
/**
* 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;
;