UNPKG

git-contextor

Version:

A code context tool with vector search and real-time monitoring, with optional Git integration.

308 lines (281 loc) 11.5 kB
const ConfigManager = require('../../core/ConfigManager'); const logger = require('../utils/logger'); const ora = require('ora'); const inquirer = require('inquirer'); const fs = require('fs').promises; const { isGitRepository } = require('../../utils/git'); const chalk = require('chalk'); async function init(options) { const spinner = ora('Checking repository status...').start(); // Await the isGitRepository check, as it is async const isGitRepo = await require('../../utils/git').isGitRepository(process.cwd()); if (!isGitRepo) { spinner.fail('Not a git repository.'); console.log('Error: Not a git repository.'); console.log('Please run Git Contextor from the root of a git repository.'); logger.error('Please run Git Contextor from the root of a git repository.'); process.exit(1); } const configManager = new ConfigManager(process.cwd()); try { await fs.access(configManager.configFile); if (!options.force) { spinner.info('Git Contextor already initialized. Use --force to reinitialize.'); console.log('Git Contextor already initialized. Use --force to reinitialize.'); return; } } catch (error) { if (error.code !== 'ENOENT') { spinner.fail('Error checking initialization status.'); console.log('Error: Unable to check initialization status.'); logger.error(error.message); process.exit(1); } // Not initialized, which is the expected case, so we continue. } spinner.stop(); try { let userConfig; // Check for environment variables to skip questions if (process.env.GEMINI_API_KEY) { spinner.info('Found GEMINI_API_KEY in environment. Configuring with Google Gemini.'); userConfig = { embedding: { provider: 'gemini', apiKey: process.env.GEMINI_API_KEY, model: 'text-embedding-004', dimensions: 768, }, chat: { provider: 'gemini', model: 'gemini-1.5-flash', apiKey: process.env.GEMINI_API_KEY, }, }; } else if (process.env.OPENAI_API_KEY) { spinner.info('Found OPENAI_API_KEY in environment. Configuring with OpenAI.'); userConfig = { embedding: { provider: 'openai', apiKey: process.env.OPENAI_API_KEY, model: 'text-embedding-3-small', dimensions: 1536, }, chat: { provider: 'openai', model: 'gpt-4o-mini', apiKey: process.env.OPENAI_API_KEY, }, }; } if (!userConfig) { console.log('Welcome to Git Contextor initialization!'); const questions = [ { type: 'list', name: 'embeddingProvider', message: 'Choose your embedding provider:', choices: [ { name: 'Local (runs on your machine, no key needed)', value: 'local' }, { name: 'OpenAI', value: 'openai' }, { name: 'Google Gemini', value: 'gemini' }, ], default: 'local', }, { type: 'password', name: 'embeddingApiKey', message: 'Enter your OpenAI API Key:', mask: '*', when: (answers) => answers.embeddingProvider === 'openai' && !process.env.OPENAI_API_KEY, validate: (input) => !!input || 'API Key cannot be empty.', }, { type: 'password', name: 'embeddingApiKey', message: 'Enter your Google Gemini API Key:', mask: '*', when: (answers) => answers.embeddingProvider === 'gemini' && !process.env.GOOGLE_API_KEY, validate: (input) => !!input || 'API Key cannot be empty.', }, { type: 'list', name: 'chatProvider', message: 'Choose your chat model provider (for conversational search):', choices: [ { name: 'OpenAI', value: 'openai' }, { name: 'Google Gemini', value: 'gemini' }, ], default: 'openai', }, { type: 'input', name: 'chatModel', message: 'Enter the chat model to use:', default: (answers) => (answers.chatProvider === 'openai' ? 'gpt-4o-mini' : 'gemini-1.5-flash'), }, { type: 'confirm', name: 'useSameApiKey', message: (answers) => `Use the same API key for the chat provider (${answers.chatProvider})?`, default: true, when: (answers) => answers.embeddingProvider === answers.chatProvider && answers.embeddingProvider !== 'local', }, { type: 'password', name: 'chatApiKey', message: 'Enter your OpenAI API Key for chat:', mask: '*', when: (answers) => answers.chatProvider === 'openai' && !answers.useSameApiKey && // if they said no to using the same key !process.env.OPENAI_API_KEY, validate: (input) => !!input || 'API Key cannot be empty.', }, { type: 'password', name: 'chatApiKey', message: 'Enter your Google Gemini API Key for chat:', mask: '*', when: (answers) => answers.chatProvider === 'gemini' && !answers.useSameApiKey && !process.env.GOOGLE_API_KEY, validate: (input) => !!input || 'API Key cannot be empty.', }, { type: 'confirm', name: 'setupTunneling', message: 'Would you like to set up tunneling for sharing your repository with AI agents?', default: true, }, ]; const answers = await inquirer.prompt(questions); // Handle tunnel setup if (answers.setupTunneling) { console.log('\n' + chalk.blue('🚇 Tunnel Setup')); console.log('Git Contextor can create secure tunnels to share your repository with AI agents.'); console.log('We recommend using tunnel.corrently.cloud for enterprise-grade security.'); console.log('\nTo get your CORRENTLY_TUNNEL_API_KEY:'); console.log(' 1. Visit ' + chalk.cyan('https://tunnel.corrently.cloud/') + ' in your browser.'); console.log(' 2. Sign up or log in to your Corrently Tunnel account.'); console.log(' 3. Go to your dashboard and copy your API key.'); console.log(' 4. Paste it below when prompted, or set it later using the frontend or by exporting CORRENTLY_TUNNEL_API_KEY.'); console.log(' 5. You can always manage this key via the web UI (Config > Tunnel Configuration).'); console.log(''); const tunnelQuestions = [ { type: 'rawlist', name: 'tunnelProvider', message: 'Choose your tunnel provider:', choices: [ { name: 'tunnel.corrently.cloud (Recommended - Enterprise-grade, secure)', value: 'corrently' }, { name: 'localtunnel (Free but less reliable)', value: 'localtunnel' }, { name: 'Skip for now', value: 'skip' }, ], default: 1, }, ]; const tunnelAnswers = await inquirer.prompt(tunnelQuestions); if (tunnelAnswers.tunnelProvider === 'corrently') { console.log('\n' + chalk.green('🔑 Getting your tunnel.corrently.cloud API Key')); console.log('To use tunnel.corrently.cloud, you need an API key:'); console.log('1. Visit: ' + chalk.cyan('https://tunnel.corrently.cloud/')); console.log('2. Sign up or log in to your account'); console.log('3. Navigate to your dashboard'); console.log('4. Copy your API key'); console.log('5. Paste it below or set it as an environment variable later\n'); const apiKeyQuestions = [ { type: 'password', name: 'tunnelApiKey', message: 'Enter your tunnel.corrently.cloud API Key (or press Enter to skip):', mask: '*', validate: (input) => { if (!input) { return 'You can set this later with: export CORRENTLY_TUNNEL_API_KEY=your_key'; } return true; }, }, ]; const apiKeyAnswers = await inquirer.prompt(apiKeyQuestions); Object.assign(answers, tunnelAnswers, apiKeyAnswers); } else { Object.assign(answers, tunnelAnswers); } } // Construct config from answers userConfig = { embedding: { provider: answers.embeddingProvider, apiKey: answers.embeddingApiKey, }, chat: { provider: answers.chatProvider, model: answers.chatModel, apiKey: answers.useSameApiKey ? answers.embeddingApiKey : answers.chatApiKey, }, }; // Add tunnel configuration if set up if (answers.setupTunneling && answers.tunnelProvider && answers.tunnelProvider !== 'skip') { userConfig.tunneling = { provider: answers.tunnelProvider, }; if (answers.tunnelProvider === 'corrently') { userConfig.tunneling.corrently = { serverUrl: 'https://tunnel.corrently.cloud', apiKey: answers.tunnelApiKey || process.env.CORRENTLY_TUNNEL_API_KEY || null, description: 'Git Contextor Share', }; } } // Set model and dimensions based on provider if (answers.embeddingProvider === 'openai') { userConfig.embedding.model = 'text-embedding-3-small'; userConfig.embedding.dimensions = 1536; } else if (answers.embeddingProvider === 'gemini') { userConfig.embedding.model = 'text-embedding-004'; userConfig.embedding.dimensions = 768; } // Local defaults are already set in ConfigManager } // Add tunnel configuration from environment if not already set if (!userConfig.tunneling && process.env.CORRENTLY_TUNNEL_API_KEY) { userConfig.tunneling = { provider: 'corrently', corrently: { serverUrl: 'https://tunnel.corrently.cloud', apiKey: process.env.CORRENTLY_TUNNEL_API_KEY, description: 'Git Contextor Share', }, }; } spinner.start('Applying configuration...'); await configManager.init(userConfig, options.force); spinner.succeed('Git Contextor initialized successfully.'); console.log('Git Contextor initialized successfully'); logger.info(`Configuration file saved to ${configManager.configFile}`); // Display tunnel setup summary if (userConfig.tunneling) { console.log('\n' + chalk.blue('🚇 Tunnel Configuration:')); console.log(`Provider: ${userConfig.tunneling.provider}`); if (userConfig.tunneling.provider === 'corrently') { if (userConfig.tunneling.corrently.apiKey) { console.log('✅ API key configured'); console.log('Test your connection: ' + chalk.cyan('npx git-contextor tunnel test')); } else { console.log('⚠️ API key not set'); console.log('Set it with: ' + chalk.cyan('export CORRENTLY_TUNNEL_API_KEY=your_key')); } } } logger.info('\nRun "npx git-contextor start" to begin monitoring and indexing the repository.'); } catch (error) { spinner.fail('Initialization failed.'); console.log('Error: Initialization failed.'); logger.error(error.message); process.exit(1); } } module.exports = init;