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