From 1648143746df99b043c641e902b997de90b93770 Mon Sep 17 00:00:00 2001 From: JuanK Ruiz Date: Sat, 28 Feb 2026 18:46:26 -0500 Subject: [PATCH 1/2] fix(core): resolve .env loading hierarchy and strip telemetry (fixes #18)" -m " - Implement multi-level .env resolution (from project root up to ~/.gemini/.env) to mitigate MCP sandbox isolation. - Remove hardcoded mcp-debug.log and diagnostic console logging from imageGenerator.ts to sanitize production output. - Update README.md with comprehensive .env configuration instructions and recommendations. Fixes #18" --- .env.example | 7 +++ .gitignore | 7 +++ README.md | 26 ++++++++-- gemini-extension.json | 3 +- mcp-server/package-lock.json | 15 +++++- mcp-server/package.json | 3 +- mcp-server/src/imageGenerator.ts | 84 -------------------------------- mcp-server/src/index.ts | 30 ++++++++++++ 8 files changed, 83 insertions(+), 92 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e6a9a03 --- /dev/null +++ b/.env.example @@ -0,0 +1,7 @@ +# Configuración Segura Local - Nano Banana +NANOBANANA_GEMINI_API_KEY="your-api-key-here" +NANOBANANA_MODEL="gemini-3.1-flash-image-preview" + +# Fallback keys for Vertex/Gemini +# GEMINI_API_KEY="your-api-key-here" +# GOOGLE_API_KEY="your-api-key-here" diff --git a/.gitignore b/.gitignore index 58c43d9..a5b8b7b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,10 @@ dist/ node_modules/ nanobanana-output/ +tmp/ + +# Env files +.env +.env.* +!.env.example +.aiignore diff --git a/README.md b/README.md index fe3e27c..3e7db57 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,28 @@ A professional Gemini CLI extension for generating and manipulating images using For authentication setup, see the [official Gemini CLI documentation](https://github.com/google-gemini/gemini-cli/blob/main/docs/cli/authentication.md). +### Environment Variables (.env Support) + +For better security and convenience, Nano Banana supports loading your API keys and configurations directly from a `.env` file, bypassing the Gemini CLI's environment variable redaction policies. + +You can place a `.env` file in any of the following locations (the extension will automatically search for it in this exact order): + +1. **Your Current Project Directory** (e.g., `./.env` in the folder where you are running the `gemini` command) +2. **A local `.gemini` folder** (e.g., `./.gemini/.env`) +3. **Your Global Gemini configuration folder** (e.g., `~/.gemini/.env` or `%USERPROFILE%\.gemini\.env`) +4. **Your User Home Directory** (e.g., `~/.env` or `%USERPROFILE%\.env`) + +**💡 Recommended Setup:** + +Create a `.env` file in your global Gemini directory (`~/.gemini/.env` on macOS/Linux or `C:\Users\YourUser\.gemini\.env` on Windows). + +```env +NANOBANANA_GEMINI_API_KEY=AIzaSyYourSecretKeyHere... +# You can also set a specific model for the extension +NANOBANANA_MODEL=gemini-3.1-flash-image-preview +``` + +This ensures your keys are kept secure and loaded globally for Nano Banana without requiring you to manually `export` them in every terminal session. ### Key Components - **`index.ts`**: MCP server using `@modelcontextprotocol/sdk` for professional protocol handling @@ -459,10 +481,6 @@ The extension uses the official Model Context Protocol (MCP) SDK for robust clie 4. **"Image not found"**: Check that input files are in one of the searched directories (see File Search Locations above) -### Debug Mode - -The MCP server includes detailed debug logging that appears in the Gemini CLI console to help diagnose issues. - ## 📄 Legal - **License**: [Apache License 2.0](LICENSE) diff --git a/gemini-extension.json b/gemini-extension.json index 0f7e26b..8a666ff 100644 --- a/gemini-extension.json +++ b/gemini-extension.json @@ -5,8 +5,7 @@ "mcpServers": { "nanobanana": { "command": "node", - "args": ["${extensionPath}/mcp-server/dist/index.js"], - "env": {} + "args": ["${extensionPath}/mcp-server/dist/index.js"] } }, "contextFileName": "GEMINI.md" diff --git a/mcp-server/package-lock.json b/mcp-server/package-lock.json index 18b5b49..f0f6aa5 100644 --- a/mcp-server/package-lock.json +++ b/mcp-server/package-lock.json @@ -9,7 +9,8 @@ "version": "1.0.11", "dependencies": { "@google/genai": "^1.17.0", - "@modelcontextprotocol/sdk": "^1.0.0" + "@modelcontextprotocol/sdk": "^1.0.0", + "dotenv": "^16.4.5" }, "devDependencies": { "@types/node": "^22.0.0", @@ -296,6 +297,18 @@ "node": ">= 0.8" } }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", diff --git a/mcp-server/package.json b/mcp-server/package.json index fde5f05..acaacc0 100644 --- a/mcp-server/package.json +++ b/mcp-server/package.json @@ -14,7 +14,8 @@ }, "dependencies": { "@google/genai": "^1.17.0", - "@modelcontextprotocol/sdk": "^1.0.0" + "@modelcontextprotocol/sdk": "^1.0.0", + "dotenv": "^16.4.5" }, "devDependencies": { "@types/node": "^22.0.0", diff --git a/mcp-server/src/imageGenerator.ts b/mcp-server/src/imageGenerator.ts index f917b5e..67d4e0a 100644 --- a/mcp-server/src/imageGenerator.ts +++ b/mcp-server/src/imageGenerator.ts @@ -28,7 +28,6 @@ export class ImageGenerator { }); this.modelName = process.env.NANOBANANA_MODEL || ImageGenerator.DEFAULT_MODEL; - console.error(`DEBUG - Using image model: ${this.modelName}`); } private async openImagePreview(filePath: string): Promise { @@ -49,12 +48,7 @@ export class ImageGenerator { } await execAsync(command); - console.error(`DEBUG - Opened preview for: ${filePath}`); } catch (error: unknown) { - console.error( - `DEBUG - Failed to open preview for ${filePath}:`, - error instanceof Error ? error.message : String(error), - ); // Don't throw - preview failure shouldn't break image generation } } @@ -82,17 +76,10 @@ export class ImageGenerator { if (!shouldPreview || !files.length) { if (files.length > 1 && request.noPreview) { - console.error( - `DEBUG - Auto-preview disabled for ${files.length} images (--no-preview specified)`, - ); } return; } - console.error( - `DEBUG - ${request.preview ? 'Explicit' : 'Auto'}-opening ${files.length} image(s) for preview`, - ); - // Open all generated images const previewPromises = files.map((file) => this.openImagePreview(file)); await Promise.all(previewPromises); @@ -101,29 +88,21 @@ export class ImageGenerator { static validateAuthentication(): AuthConfig { const nanoGeminiKey = process.env.NANOBANANA_GEMINI_API_KEY; if (nanoGeminiKey) { - console.error('✓ Found NANOBANANA_GEMINI_API_KEY environment variable'); return { apiKey: nanoGeminiKey, keyType: 'GEMINI_API_KEY' }; } const nanoGoogleKey = process.env.NANOBANANA_GOOGLE_API_KEY; if (nanoGoogleKey) { - console.error('✓ Found NANOBANANA_GOOGLE_API_KEY environment variable'); return { apiKey: nanoGoogleKey, keyType: 'GOOGLE_API_KEY' }; } const geminiKey = process.env.GEMINI_API_KEY; if (geminiKey) { - console.error( - '✓ Found GEMINI_API_KEY environment variable (fallback)', - ); return { apiKey: geminiKey, keyType: 'GEMINI_API_KEY' }; } const googleKey = process.env.GOOGLE_API_KEY; if (googleKey) { - console.error( - '✓ Found GOOGLE_API_KEY environment variable (fallback)', - ); return { apiKey: googleKey, keyType: 'GOOGLE_API_KEY' }; } @@ -147,11 +126,6 @@ export class ImageGenerator { // Additional check: base64 image data is typically quite long if (data.length < 1000) { - console.error( - 'DEBUG - Skipping short data that may not be image:', - data.length, - 'characters', - ); return false; } @@ -245,16 +219,8 @@ export class ImageGenerator { const generatedFiles: string[] = []; const prompts = this.buildBatchPrompts(request); let firstError: string | null = null; - - console.error(`DEBUG - Generating ${prompts.length} image variation(s)`); - for (let i = 0; i < prompts.length; i++) { const currentPrompt = prompts[i]; - console.error( - `DEBUG - Generating variation ${i + 1}/${prompts.length}:`, - currentPrompt, - ); - try { // Make API call for each variation const response = await this.ai.models.generateContent({ @@ -267,8 +233,6 @@ export class ImageGenerator { ], }); - console.error('DEBUG - API Response structure for variation', i + 1); - if (response.candidates && response.candidates[0]?.content?.parts) { // Process image parts in the response for (const part of response.candidates[0].content.parts) { @@ -276,15 +240,8 @@ export class ImageGenerator { if (part.inlineData?.data) { imageBase64 = part.inlineData.data; - console.error('DEBUG - Found image data in inlineData:', { - length: imageBase64.length, - mimeType: part.inlineData.mimeType, - }); } else if (part.text && this.isValidBase64ImageData(part.text)) { imageBase64 = part.text; - console.error( - 'DEBUG - Found image data in text field (fallback)', - ); } if (imageBase64) { @@ -301,7 +258,6 @@ export class ImageGenerator { filename, ); generatedFiles.push(fullPath); - console.error('DEBUG - Image saved to:', fullPath); break; // Only process first valid image per variation } } @@ -311,10 +267,6 @@ export class ImageGenerator { if (!firstError) { firstError = errorMessage; } - console.error( - `DEBUG - Error generating variation ${i + 1}:`, - errorMessage, - ); // If auth-related, stop immediately if (errorMessage.toLowerCase().includes('authentication failed')) { @@ -344,7 +296,6 @@ export class ImageGenerator { generatedFiles, }; } catch (error: unknown) { - console.error('DEBUG - Error in generateTextToImage:', error); return { success: false, message: 'Failed to generate image', @@ -412,8 +363,6 @@ export class ImageGenerator { const transition = args?.transition || 'smooth'; let firstError: string | null = null; - console.error(`DEBUG - Generating ${steps}-step ${type} sequence`); - // Generate each step of the story/process for (let i = 0; i < steps; i++) { const stepNumber = i + 1; @@ -440,8 +389,6 @@ export class ImageGenerator { stepPrompt += `, ${transition} transition from previous step`; } - console.error(`DEBUG - Generating step ${stepNumber}: ${stepPrompt}`); - try { const response = await this.ai.models.generateContent({ model: this.modelName, @@ -475,7 +422,6 @@ export class ImageGenerator { filename, ); generatedFiles.push(fullPath); - console.error(`DEBUG - Step ${stepNumber} saved to:`, fullPath); break; } } @@ -485,10 +431,6 @@ export class ImageGenerator { if (!firstError) { firstError = errorMessage; } - console.error( - `DEBUG - Error generating step ${stepNumber}:`, - errorMessage, - ); if (errorMessage.toLowerCase().includes('authentication failed')) { return { success: false, @@ -500,16 +442,9 @@ export class ImageGenerator { // Check if this step was actually generated if (generatedFiles.length < stepNumber) { - console.error( - `DEBUG - WARNING: Step ${stepNumber} failed to generate - no valid image data received`, - ); } } - console.error( - `DEBUG - Story generation completed. Generated ${generatedFiles.length} out of ${steps} requested images`, - ); - if (generatedFiles.length === 0) { return { success: false, @@ -532,7 +467,6 @@ export class ImageGenerator { generatedFiles, }; } catch (error: unknown) { - console.error('DEBUG - Error in generateStorySequence:', error); return { success: false, message: `Failed to generate ${request.mode} sequence`, @@ -583,12 +517,6 @@ export class ImageGenerator { }, ], }); - - console.error( - 'DEBUG - Edit API Response structure:', - JSON.stringify(response, null, 2), - ); - if (response.candidates && response.candidates[0]?.content?.parts) { const generatedFiles: string[] = []; let imageFound = false; @@ -598,15 +526,8 @@ export class ImageGenerator { if (part.inlineData?.data) { resultImageBase64 = part.inlineData.data; - console.error('DEBUG - Found edited image in inlineData:', { - length: resultImageBase64.length, - mimeType: part.inlineData.mimeType, - }); } else if (part.text && this.isValidBase64ImageData(part.text)) { resultImageBase64 = part.text; - console.error( - 'DEBUG - Found edited image in text field (fallback)', - ); } if (resultImageBase64) { @@ -621,16 +542,12 @@ export class ImageGenerator { filename, ); generatedFiles.push(fullPath); - console.error('DEBUG - Edited image saved to:', fullPath); imageFound = true; break; // Only process the first valid image } } if (!imageFound) { - console.error( - 'DEBUG - No valid image data found in edit response parts', - ); } // Handle preview if requested @@ -649,7 +566,6 @@ generatedFiles.push(fullPath); error: 'No image data in response', }; } catch (error: unknown) { - console.error(`DEBUG - Error in ${request.mode}Image:`, error); return { success: false, message: `Failed to ${request.mode} image`, diff --git a/mcp-server/src/index.ts b/mcp-server/src/index.ts index 40ce958..966e8e1 100644 --- a/mcp-server/src/index.ts +++ b/mcp-server/src/index.ts @@ -12,6 +12,36 @@ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; +import { config } from 'dotenv'; +import path from 'node:path'; +import fs from 'node:fs'; +import os from 'node:os'; + +// Helper to find parent directories for .env just in case Gemini CLI changes CWD +const cwd = process.cwd(); +const homeDir = os.homedir(); +const geminiHome = process.env.GEMINI_CLI_HOME || path.join(homeDir, '.gemini'); + +// Also try to find a custom env variable from Gemini if they pass the real CWD +const realProjectDir = process.env.GEMINI_PROJECT_DIR || process.env.PWD || cwd; + +const envPaths = [ + path.join(realProjectDir, '.env'), + path.join(cwd, '.env'), + path.join(cwd, '.gemini', '.env'), + path.join(geminiHome, '.env'), + path.join(homeDir, '.env') +]; + +for (const envPath of envPaths) { + if (fs.existsSync(envPath)) { + const dotenvResult = config({ path: envPath }); + if (!dotenvResult.error) { + break; + } + } +} + import { ImageGenerator } from './imageGenerator.js'; import { ImageGenerationRequest, From 20635c70a748c71acd3b5b55ce9c90a3c8851df5 Mon Sep 17 00:00:00 2001 From: JuanK Ruiz Date: Sat, 28 Feb 2026 19:06:25 -0500 Subject: [PATCH 2/2] feat: Add hierarchical `.env` file resolution and remove debug paths, telemetry, and diagnostic logs from image generation. --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f522a1..f2dd81c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.0.12 + +- **Feature:** Added robust hierarchical `.env` file resolution support (from project root up to `~/.gemini/.env`) to load API keys securely without relying on global environment variables. +- **Fix:** Removed hardcoded local debug paths, unnecessary telemetry, and diagnostic logs from image generation flow for a cleaner production output (Fixes #18). + ## 1.0.11 - Set Nano Banana 2 (`gemini-3.1-flash-image-preview`) as the default model.