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
245 lines (244 loc) • 8.73 kB
JavaScript
import { config } from 'dotenv';
import { existsSync } from 'fs';
import { findExecutable } from './platformUtils.js';
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 = findExecutable('cgmb');
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();
}