UNPKG

git-contextor

Version:

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

155 lines (138 loc) 6.4 kB
const logger = require('./logger'); const ora = require('ora'); const inquirer = require('inquirer'); const { execSync } = require('child_process'); const path = require('path'); const fs = require('fs'); /** * Checks if the Qdrant service is available at the configured URL. * @param {string} host * @param {number} port * @returns {Promise<boolean>} */ async function isQdrantReady(host, port) { const url = `http://${host}:${port}`; const isInteractive = process.stdout.isTTY; const spinner = isInteractive ? ora(`Checking Qdrant connection at ${url}...`).start() : null; try { const response = await fetch(url, { signal: AbortSignal.timeout(2000) }); if (response.ok) { if (spinner) { spinner.succeed(`Qdrant connection successful at ${url}`); } else { logger.debug(`Qdrant connection successful at ${url}`); } return true; } const message = `Qdrant at ${url} responded with status ${response.status}`; if (spinner) { spinner.fail(message); } else { logger.warn(message); } return false; } catch (error) { let message; if (error.name === 'AbortError' || error.code === 'ECONNREFUSED') { message = `Could not connect to Qdrant at ${url}.`; if (spinner) spinner.fail(message); else logger.warn(message); } else { message = `An unexpected error occurred while checking Qdrant: ${error.message}`; if (spinner) spinner.fail(message); else logger.error(message); } return false; } } /** * Checks for a running Qdrant instance and provides interactive prompts if not found. * @param {import('../../core/ConfigManager')} configManager - The application's config manager. * @returns {Promise<void>} */ async function checkQdrant(configManager) { const { provider, qdrant } = configManager.config.vectorStore; let { host: qdrantHost, port: qdrantPort } = qdrant; // Only check Qdrant if it's the configured provider or auto-selected with a host if (provider === 'memory' || (provider === 'auto' && !qdrantHost)) { return; } if (qdrantHost && await isQdrantReady(qdrantHost, qdrantPort)) { return; } // Do not prompt if not in an interactive terminal if (!process.stdout.isTTY) { const errorMsg = 'Qdrant connection failed. Run in an interactive terminal to configure or start Qdrant.'; logger.error(errorMsg); throw new Error(errorMsg); } const { action } = await inquirer.prompt([ { type: 'list', name: 'action', message: 'Qdrant connection failed. What would you like to do?', choices: [ { name: 'Enter a different host and port', value: 'enter_new' }, { name: 'Attempt to start Qdrant via Docker (requires Docker & docker-compose)', value: 'start_docker' }, { name: 'Abort', value: 'abort' }, ], }, ]); if (action === 'abort') { logger.info('Aborting operation.'); throw new Error('User aborted Qdrant setup.'); } if (action === 'enter_new') { const { newHost, newPort } = await inquirer.prompt([ { type: 'input', name: 'newHost', message: 'Enter Qdrant host:', default: qdrantHost || 'localhost' }, { type: 'input', name: 'newPort', message: 'Enter Qdrant port:', default: qdrantPort, validate: input => !isNaN(parseInt(input, 10)) || 'Please enter a valid port number.' }, ]); qdrantHost = newHost; qdrantPort = parseInt(newPort, 10); if (await isQdrantReady(qdrantHost, qdrantPort)) { // Explicitly set provider to 'qdrant' since user is providing details for it. await configManager.updateConfig({ vectorStore: { provider: 'qdrant', qdrant: { host: qdrantHost, port: qdrantPort } } }); logger.success(`Configuration updated to use Qdrant at: ${qdrantHost}:${qdrantPort}`); return; } else { const errorMsg = 'Still unable to connect with the new details. Aborting.'; logger.error(errorMsg); throw new Error(errorMsg); } } if (action === 'start_docker') { const spinner = ora('Attempting to start Qdrant Docker container...').start(); try { let packagePath; try { // This works when git-contextor is an installed dependency packagePath = path.dirname(require.resolve('git-contextor/package.json')); } catch (e) { // This is a fallback for local development packagePath = path.resolve(__dirname, '../../..'); } const composeFile = path.join(packagePath, 'docker', 'docker-compose.yml'); if (!fs.existsSync(composeFile)) { spinner.fail('Could not find docker-compose.yml file. Please start Qdrant manually.'); const errorMsg = 'You can usually start it by running: docker-compose -f /path/to/git-contextor/docker/docker-compose.yml up -d'; logger.info(errorMsg); throw new Error('docker-compose.yml not found.'); } execSync(`docker-compose -f "${composeFile}" up -d`, { stdio: 'ignore' }); spinner.succeed('Docker command executed. Waiting 5 seconds for Qdrant to start...'); await new Promise(resolve => setTimeout(resolve, 5000)); if (await isQdrantReady(qdrantHost, qdrantPort)) { return; } else { spinner.fail('Qdrant container was started, but the service is still not responding.'); const errorMsg = 'Please check the Docker container logs and try again.'; logger.error(errorMsg); throw new Error('Qdrant from Docker is not responding.'); } } catch (error) { spinner.fail('Failed to start Qdrant via Docker.'); logger.error(error.message); logger.info('Please ensure Docker and docker-compose are installed and running.'); throw error; // re-throw original or new error } } } module.exports = { checkQdrant };