UNPKG

remcode

Version:

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

341 lines (340 loc) 14.4 kB
"use strict"; /** * One-Shot Permission Validator * Validates ALL required permissions upfront - GitHub, HuggingFace, Pinecone * * Core Principle: "All or Nothing" - Either user has ALL permissions or setup fails with clear guidance */ Object.defineProperty(exports, "__esModule", { value: true }); exports.PermissionValidator = void 0; const rest_1 = require("@octokit/rest"); const inference_1 = require("@huggingface/inference"); const pinecone_1 = require("@pinecone-database/pinecone"); const logger_1 = require("../../utils/logger"); const logger = (0, logger_1.getLogger)('PermissionValidator'); /** * Comprehensive Permission Validator * Validates all services in one shot */ class PermissionValidator { /** * Validate all required permissions across all services */ static async validateAllPermissions() { logger.info('🔍 Starting comprehensive permission validation...'); const github = await this.validateGitHubToken(); const huggingface = await this.validateHuggingFaceToken(); const pinecone = await this.validatePineconeToken(); const allValid = github.isValid && huggingface.isValid && pinecone.isValid; const result = { allValid, github, huggingface, pinecone, setupUrls: this.SETUP_URLS, message: allValid ? '✅ All permissions validated successfully' : '❌ Missing required permissions. Please complete setup first.' }; if (allValid) { logger.info('✅ All service permissions validated successfully'); } else { logger.warn('❌ Permission validation failed'); logger.warn(`GitHub: ${github.isValid ? 'PASS' : 'FAIL'}`); logger.warn(`HuggingFace: ${huggingface.isValid ? 'PASS' : 'FAIL'}`); logger.warn(`Pinecone: ${pinecone.isValid ? 'PASS' : 'FAIL'}`); } return result; } /** * Validate specific repository access (called after general validation) */ static async validateRepositoryAccess(owner, repo) { logger.info(`🔍 Validating repository access: ${owner}/${repo}`); const githubToken = process.env.GITHUB_TOKEN; if (!githubToken) { throw new Error('GitHub token not found'); } try { const octokit = new rest_1.Octokit({ auth: githubToken }); // Get repository information const { data: repository } = await octokit.rest.repos.get({ owner, repo }); const permissions = repository.permissions || {}; // Get current user const { data: user } = await octokit.rest.users.getAuthenticated(); const isOwner = repository.owner.login === user.login; const result = { owner, repo, hasAdmin: permissions.admin || false, hasPush: permissions.push || false, hasPull: permissions.pull || false, isOwner }; logger.info(`Repository access: Admin=${result.hasAdmin}, Push=${result.hasPush}, Owner=${result.isOwner}`); return result; } catch (error) { logger.error(`Repository access validation failed: ${error.message}`); throw new Error(`Cannot access repository ${owner}/${repo}: ${error.message}`); } } /** * Validate GitHub token and repository permissions */ static async validateGitHubToken() { const token = process.env.GITHUB_TOKEN; if (!token) { return { isValid: false, service: 'github', issue: 'GitHub token not found in environment variables', fixUrl: this.SETUP_URLS.github, details: 'Set GITHUB_TOKEN in your .env file' }; } try { const octokit = new rest_1.Octokit({ auth: token }); // Test basic authentication const { data: user } = await octokit.rest.users.getAuthenticated(); logger.debug(`GitHub authenticated as: ${user.login}`); // Check token scopes const scopes = await this.getTokenScopes(octokit); const missingScopes = this.REQUIRED_GITHUB_SCOPES.filter(required => !scopes.includes(required)); if (missingScopes.length > 0) { return { isValid: false, service: 'github', issue: `Missing required scopes: ${missingScopes.join(', ')}`, fixUrl: this.SETUP_URLS.github, details: `Current scopes: ${scopes.join(', ')}` }; } // Test GitHub Actions API access try { await octokit.rest.actions.listRepoWorkflows({ owner: user.login, repo: 'test' // This will 404 but tests API access }); } catch (error) { if (error.status === 404) { // Expected - just testing API access } else if (error.status === 403) { return { isValid: false, service: 'github', issue: 'Token lacks GitHub Actions API access', fixUrl: this.SETUP_URLS.github, details: 'Workflow scope required for GitHub Actions management' }; } } return { isValid: true, service: 'github', details: `Authenticated as ${user.login} with required scopes` }; } catch (error) { return { isValid: false, service: 'github', issue: `GitHub authentication failed: ${error.message}`, fixUrl: this.SETUP_URLS.github, details: 'Check if token is valid and not expired' }; } } /** * Validate HuggingFace token and model access */ static async validateHuggingFaceToken() { const token = process.env.HUGGINGFACE_TOKEN; if (!token) { return { isValid: false, service: 'huggingface', issue: 'HuggingFace token not found in environment variables', fixUrl: this.SETUP_URLS.huggingface, details: 'Set HUGGINGFACE_TOKEN in your .env file' }; } try { const hf = new inference_1.HfInference(token); // Test API connectivity with a simple embedding request const testText = 'function test() { return "hello world"; }'; try { // Try primary model first const embedding = await hf.featureExtraction({ model: this.TEST_MODELS.primary, inputs: testText }); if (embedding && Array.isArray(embedding) && embedding.length > 0) { return { isValid: true, service: 'huggingface', details: `Successfully tested embedding generation with ${this.TEST_MODELS.primary}` }; } } catch (primaryError) { logger.debug(`Primary model failed, trying fallback: ${primaryError.message}`); // Try fallback model try { const embedding = await hf.featureExtraction({ model: this.TEST_MODELS.fallback, inputs: testText }); if (embedding && Array.isArray(embedding) && embedding.length > 0) { return { isValid: true, service: 'huggingface', details: `Successfully tested with fallback model ${this.TEST_MODELS.fallback}` }; } } catch (fallbackError) { return { isValid: false, service: 'huggingface', issue: 'Cannot generate embeddings with any available model', fixUrl: this.SETUP_URLS.huggingface, details: `Primary: ${primaryError.message}, Fallback: ${fallbackError.message}` }; } } return { isValid: false, service: 'huggingface', issue: 'Embedding generation test failed', fixUrl: this.SETUP_URLS.huggingface, details: 'API responded but embedding generation unsuccessful' }; } catch (error) { return { isValid: false, service: 'huggingface', issue: `HuggingFace API authentication failed: ${error.message}`, fixUrl: this.SETUP_URLS.huggingface, details: 'Check if token is valid and has inference permissions' }; } } /** * Validate Pinecone API access and operations */ static async validatePineconeToken() { const apiKey = process.env.PINECONE_API_KEY; if (!apiKey) { return { isValid: false, service: 'pinecone', issue: 'Pinecone API key not found in environment variables', fixUrl: this.SETUP_URLS.pinecone, details: 'Set PINECONE_API_KEY in your .env file' }; } try { const pinecone = new pinecone_1.Pinecone({ apiKey }); // Test API connectivity by listing indexes const indexes = await pinecone.listIndexes(); logger.debug(`Pinecone API accessible, found ${indexes.indexes?.length || 0} indexes`); // Test basic operations capability // Note: We don't create a test index as it costs money and takes time // Instead, we verify API access and list capability which covers auth return { isValid: true, service: 'pinecone', details: `API accessible with ${indexes.indexes?.length || 0} existing indexes` }; } catch (error) { if (error.message?.includes('401') || error.message?.includes('403')) { return { isValid: false, service: 'pinecone', issue: 'Pinecone API authentication failed', fixUrl: this.SETUP_URLS.pinecone, details: 'Check if API key is valid and not expired' }; } return { isValid: false, service: 'pinecone', issue: `Pinecone API connection failed: ${error.message}`, fixUrl: this.SETUP_URLS.pinecone, details: 'Verify API key and network connectivity' }; } } /** * Get GitHub token scopes from API response */ static async getTokenScopes(octokit) { try { const response = await octokit.request('GET /user'); const scopesHeader = response.headers['x-oauth-scopes']; return scopesHeader ? scopesHeader.split(', ').map(s => s.trim()) : []; } catch (error) { logger.warn('Could not determine GitHub token scopes'); return []; } } /** * Generate comprehensive setup guidance */ static generateSetupGuidance(validation) { const actionItems = []; let markdown = `# 🔧 Remcode Setup Required\n\n`; if (!validation.allValid) { markdown += `## ❌ Missing Permissions\n\n`; if (!validation.github.isValid) { markdown += `### GitHub Token\n`; markdown += `**Issue**: ${validation.github.issue}\n`; markdown += `**Fix**: [Create new GitHub token](${validation.github.fixUrl})\n\n`; actionItems.push('Create GitHub token with required permissions'); } if (!validation.huggingface.isValid) { markdown += `### HuggingFace Token\n`; markdown += `**Issue**: ${validation.huggingface.issue}\n`; markdown += `**Fix**: [Create new HuggingFace token](${validation.huggingface.fixUrl})\n\n`; actionItems.push('Create HuggingFace token with inference permissions'); } if (!validation.pinecone.isValid) { markdown += `### Pinecone API Key\n`; markdown += `**Issue**: ${validation.pinecone.issue}\n`; markdown += `**Fix**: [Get Pinecone API key](${validation.pinecone.fixUrl})\n\n`; actionItems.push('Get valid Pinecone API key'); } markdown += `## 📋 Next Steps\n\n`; markdown += `1. Fix all issues above\n`; markdown += `2. Update your .env file with new tokens\n`; markdown += `3. Restart the MCP server\n`; markdown += `4. Try setup again\n\n`; } else { markdown += `## ✅ All Permissions Valid\n\nYou're ready to use remcode!\n`; } return { markdown, actionItems }; } } exports.PermissionValidator = PermissionValidator; PermissionValidator.REQUIRED_GITHUB_SCOPES = [ 'repo', 'workflow', 'write:repo_hook' ]; PermissionValidator.SETUP_URLS = { github: 'https://github.com/settings/tokens/new?scopes=repo,workflow,write:repo_hook,admin:repo_hook&description=Remcode%20MCP%20Tools%20-%20Full%20Access', huggingface: 'https://huggingface.co/settings/tokens/new?globalPermissions=inference&tokenType=fineGrained', pinecone: 'https://app.pinecone.io/organizations/-/projects/-/keys' }; PermissionValidator.TEST_MODELS = { primary: 'microsoft/codebert-base', fallback: 'sentence-transformers/all-MiniLM-L6-v2' };