UNPKG

claude-gemini-multimodal-bridge

Version:

Enterprise-grade AI integration bridge connecting Claude Code, Gemini CLI, and Google AI Studio with intelligent routing and advanced multimodal processing capabilities

248 lines (247 loc) 8.79 kB
import { config } from 'dotenv'; import { existsSync } from 'fs'; import { dirname, join } from 'path'; import { fileURLToPath } from 'url'; import { execSync } from 'child_process'; import { logger } from './logger.js'; export class SmartEnvLoader { static instance; isLoaded = false; loadResult = { success: false, foundFiles: [], errors: [] }; constructor() { } static getInstance() { if (!SmartEnvLoader.instance) { SmartEnvLoader.instance = new SmartEnvLoader(); } return SmartEnvLoader.instance; } async loadEnvironment(options = {}) { if (this.isLoaded && !options.forceReload) { return this.loadResult; } const { verbose = false } = options; const searchPaths = options.searchPaths || await this.getDefaultSearchPaths(); this.loadResult = { success: false, foundFiles: [], errors: [] }; if (verbose) { logger.debug('Starting smart environment loading', { searchPaths: searchPaths.length, cwd: process.cwd() }); } for (const searchPath of searchPaths) { const envPath = join(searchPath, '.env'); if (verbose) { logger.debug('Checking for .env file', { path: envPath }); } if (existsSync(envPath)) { this.loadResult.foundFiles.push(envPath); try { const result = config({ path: envPath }); if (result.error) { this.loadResult.errors.push(`Failed to load ${envPath}: ${result.error.message}`); if (verbose) { logger.warn('Failed to load .env file', { path: envPath, error: result.error.message }); } } else { this.loadResult.success = true; this.loadResult.loadedFrom = envPath; if (verbose) { logger.info('Successfully loaded .env file', { path: envPath, variablesLoaded: Object.keys(result.parsed || {}).length }); } break; } } catch (error) { const errorMsg = `Error loading ${envPath}: ${error.message}`; this.loadResult.errors.push(errorMsg); if (verbose) { logger.error('Error loading .env file', { path: envPath, error }); } } } } if (!this.loadResult.success) { const hasRequiredEnvVars = this.checkEnvironmentVariables(); if (hasRequiredEnvVars) { this.loadResult.success = true; this.loadResult.loadedFrom = 'environment variables'; if (verbose) { logger.info('Using environment variables (no .env file needed)', { hasGeminiKey: !!process.env.GEMINI_API_KEY, hasClaudeKey: !!process.env.CLAUDE_API_KEY }); } } } this.isLoaded = true; if (verbose || this.loadResult.errors.length > 0) { logger.info('Environment loading completed', { success: this.loadResult.success, loadedFrom: this.loadResult.loadedFrom, foundFiles: this.loadResult.foundFiles.length, errors: this.loadResult.errors.length }); } return this.loadResult; } async getDefaultSearchPaths() { const paths = []; paths.push(process.cwd()); const projectRoot = await this.findProjectRoot(); if (projectRoot && projectRoot !== process.cwd()) { paths.push(projectRoot); } try { const currentFileUrl = import.meta.url; const currentFilePath = fileURLToPath(currentFileUrl); const projectFromFile = this.findProjectRootFromPath(currentFilePath); if (projectFromFile && !paths.includes(projectFromFile)) { paths.push(projectFromFile); } } catch (error) { } try { const globalDir = await this.findGlobalNpmInstallation(); if (globalDir && !paths.includes(globalDir)) { paths.push(globalDir); } } catch (error) { } try { const homeDir = process.env.HOME || process.env.USERPROFILE; if (homeDir) { const cgmbHome = join(homeDir, '.cgmb'); paths.push(cgmbHome); } } catch (error) { } return paths; } async findProjectRoot(startPath = process.cwd()) { let currentPath = startPath; while (currentPath !== dirname(currentPath)) { const packageJsonPath = join(currentPath, 'package.json'); if (existsSync(packageJsonPath)) { try { const pkg = await import(packageJsonPath, { assert: { type: 'json' } }); if (pkg.default?.name === 'claude-gemini-multimodal-bridge' || pkg.default?.bin?.cgmb) { return currentPath; } } catch { } } currentPath = dirname(currentPath); } return null; } findProjectRootFromPath(filePath) { let currentPath = dirname(filePath); while (currentPath !== dirname(currentPath)) { const packageJsonPath = join(currentPath, 'package.json'); if (existsSync(packageJsonPath)) { return currentPath; } currentPath = dirname(currentPath); } return null; } async findGlobalNpmInstallation() { try { const npmRoot = execSync('npm root -g', { encoding: 'utf8', timeout: 5000, stdio: 'pipe' }).trim(); const cgmbGlobalPath = join(npmRoot, 'claude-gemini-multimodal-bridge'); if (existsSync(cgmbGlobalPath)) { return cgmbGlobalPath; } } catch (error) { } try { const cgmbPath = execSync('which cgmb', { encoding: 'utf8', timeout: 5000, stdio: 'pipe' }).trim(); if (cgmbPath) { const binDir = dirname(cgmbPath); const possibleProjectRoot = dirname(binDir); if (existsSync(join(possibleProjectRoot, 'package.json'))) { return possibleProjectRoot; } } } catch (error) { } return null; } checkEnvironmentVariables() { const requiredVars = [ 'GEMINI_API_KEY', 'GOOGLE_AI_STUDIO_API_KEY', 'CLAUDE_API_KEY' ]; return requiredVars.some(varName => !!process.env[varName]); } getLoadResult() { return { ...this.loadResult }; } isEnvironmentLoaded() { return this.isLoaded && this.loadResult.success; } async reload(options = {}) { this.isLoaded = false; return this.loadEnvironment({ ...options, forceReload: true }); } getEnvironmentStatus() { const importantVars = [ 'GEMINI_API_KEY', 'GOOGLE_AI_STUDIO_API_KEY', 'CLAUDE_API_KEY', 'CLAUDE_CODE_PATH', 'GEMINI_CLI_PATH', 'LOG_LEVEL' ]; const availableVars = {}; importantVars.forEach(varName => { availableVars[varName] = !!process.env[varName]; }); return { loaded: this.isLoaded && this.loadResult.success, source: this.loadResult.loadedFrom || null, availableVars, foundFiles: [...this.loadResult.foundFiles], errors: [...this.loadResult.errors] }; } } export async function loadEnvironmentSmart(options = {}) { const loader = SmartEnvLoader.getInstance(); return loader.loadEnvironment(options); } export function getEnvironmentStatus() { const loader = SmartEnvLoader.getInstance(); return loader.getEnvironmentStatus(); }