@yohei1996/nanobanana 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # @yohei1996/nanobanana
2
+
3
+ šŸŒ Nano Banana - Gemini Pro Image Generation CLI
4
+
5
+ High-quality image generation powered by Google's Gemini Pro with 4K support.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install -g @yohei1996/nanobanana
11
+ ```
12
+
13
+ Or use directly with npx:
14
+
15
+ ```bash
16
+ npx @yohei1996/nanobanana "A cat playing piano"
17
+ ```
18
+
19
+ ## Setup
20
+
21
+ Set your Gemini API key:
22
+
23
+ ```bash
24
+ export GEMINI_API_KEY=your-api-key
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ```bash
30
+ # Basic usage
31
+ nanobanana "A cat playing piano in watercolor style"
32
+
33
+ # With 4K resolution
34
+ nanobanana "Landscape photo" --resolution 4k
35
+
36
+ # With aspect ratio
37
+ nanobanana "Logo design" --aspect 1:1 --output ./logo.png
38
+
39
+ # All options
40
+ nanobanana "prompt" --aspect 16:9 --resolution 4k --output ./image.png
41
+ ```
42
+
43
+ ## Options
44
+
45
+ | Option | Description | Example |
46
+ |--------|-------------|---------|
47
+ | `--aspect <ratio>` | Aspect ratio | `1:1`, `16:9`, `9:16`, `4:3` |
48
+ | `--resolution <r>` | Resolution | `4k`, `2k`, `1k` |
49
+ | `--output <path>` | Output file path | `./image.png` |
50
+ | `--api-key <key>` | Gemini API key | |
51
+
52
+ ## Output
53
+
54
+ Images are saved to `~/nanobanana-images/` by default.
55
+
56
+ ## License
57
+
58
+ MIT
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ const { generateImage } = require('../src/index.js');
3
+
4
+ const args = process.argv.slice(2);
5
+
6
+ if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
7
+ console.log(`
8
+ šŸŒ Nano Banana - Gemini Pro Image Generator
9
+
10
+ Usage: nanobanana <prompt> [options]
11
+
12
+ Options:
13
+ --aspect <ratio> Aspect ratio (1:1, 16:9, 9:16, 4:3, 3:4, etc.)
14
+ --resolution <r> Resolution (4k, 2k, 1k)
15
+ --output <path> Output file path
16
+ --api-key <key> Gemini API key (or set GEMINI_API_KEY env var)
17
+ --help, -h Show this help message
18
+
19
+ Examples:
20
+ nanobanana "A cat playing piano in watercolor style"
21
+ nanobanana "Landscape photo" --resolution 4k
22
+ nanobanana "Logo design" --aspect 1:1 --output ./logo.png
23
+
24
+ Environment:
25
+ GEMINI_API_KEY Your Gemini API key
26
+ GOOGLE_API_KEY Alternative API key variable
27
+ `);
28
+ process.exit(0);
29
+ }
30
+
31
+ const options = { prompt: '', model: 'pro' };
32
+
33
+ for (let i = 0; i < args.length; i++) {
34
+ if (args[i] === '--aspect' && args[i + 1]) {
35
+ options.aspectRatio = args[++i];
36
+ } else if (args[i] === '--resolution' && args[i + 1]) {
37
+ options.resolution = args[++i];
38
+ } else if (args[i] === '--output' && args[i + 1]) {
39
+ options.outputPath = args[++i];
40
+ } else if (args[i] === '--api-key' && args[i + 1]) {
41
+ process.env.GEMINI_API_KEY = args[++i];
42
+ } else if (!args[i].startsWith('--')) {
43
+ options.prompt = args[i];
44
+ }
45
+ }
46
+
47
+ if (!options.prompt) {
48
+ console.error('Error: Please provide a prompt');
49
+ process.exit(1);
50
+ }
51
+
52
+ generateImage(options)
53
+ .then((paths) => console.log('\n✨ Generated:', paths.join(', ')))
54
+ .catch((err) => {
55
+ console.error('Error:', err.message);
56
+ process.exit(1);
57
+ });
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@yohei1996/nanobanana",
3
+ "version": "1.0.0",
4
+ "description": "Gemini Pro image generation CLI - High quality image generation with 4K support",
5
+ "main": "src/index.js",
6
+ "bin": {
7
+ "nanobanana": "./bin/nanobanana.js"
8
+ },
9
+ "scripts": {
10
+ "test": "echo \"No tests yet\"",
11
+ "lint": "echo \"No linting configured\""
12
+ },
13
+ "keywords": [
14
+ "gemini",
15
+ "image-generation",
16
+ "ai",
17
+ "cli",
18
+ "4k"
19
+ ],
20
+ "author": "yohei1996",
21
+ "license": "MIT",
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://github.com/yohei1996/biz-agent-skills-packages",
25
+ "directory": "packages/nanobanana"
26
+ },
27
+ "engines": {
28
+ "node": ">=18.0.0"
29
+ }
30
+ }
package/src/index.js ADDED
@@ -0,0 +1,145 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const MODELS = {
5
+ flash: 'gemini-2.0-flash-exp-image-generation',
6
+ pro: 'gemini-3-pro-image-preview',
7
+ };
8
+
9
+ function getGeminiApiKey() {
10
+ // 1. Check environment variables
11
+ const envKey = process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY;
12
+ if (envKey) return envKey;
13
+
14
+ // 2. Try to read from VSCode/Cursor settings (for backward compatibility)
15
+ const os = require('os');
16
+ const settingsPaths = [
17
+ path.join(os.homedir(), 'Library/Application Support/Code/User/settings.json'),
18
+ path.join(os.homedir(), 'Library/Application Support/Cursor/User/settings.json'),
19
+ ];
20
+
21
+ for (const settingsPath of settingsPaths) {
22
+ if (fs.existsSync(settingsPath)) {
23
+ try {
24
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
25
+ const apiKey = settings['bizAgent-task-kanban.geminiApiKey'];
26
+ if (apiKey) return apiKey;
27
+ } catch {
28
+ // Ignore parsing errors
29
+ }
30
+ }
31
+ }
32
+
33
+ throw new Error(
34
+ 'Gemini API key not found. Set GEMINI_API_KEY environment variable or use --api-key option.'
35
+ );
36
+ }
37
+
38
+ async function generateImage(options) {
39
+ const { prompt, aspectRatio, outputPath, model = 'pro', resolution } = options;
40
+
41
+ const apiKey = getGeminiApiKey();
42
+
43
+ const modelId = MODELS[model] || MODELS.pro;
44
+ const modelLabel = model === 'pro' ? 'šŸš€ Pro' : '⚔ Flash';
45
+
46
+ console.log(`${modelLabel} Generating: "${prompt.substring(0, 50)}${prompt.length > 50 ? '...' : ''}"`);
47
+ if (aspectRatio) console.log(`šŸ“ Aspect ratio: ${aspectRatio}`);
48
+ if (resolution) console.log(`šŸ“ Resolution: ${resolution}`);
49
+
50
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/${modelId}:generateContent?key=${apiKey}`;
51
+
52
+ // Enhance prompt for Pro model
53
+ let enhancedPrompt = prompt;
54
+ if (model === 'pro') {
55
+ if (prompt.length < 50) {
56
+ enhancedPrompt = `Create a high-quality, detailed image: ${prompt}. Pay attention to composition, lighting, and fine details.`;
57
+ }
58
+ if (resolution === '4k') {
59
+ enhancedPrompt += ' Render at maximum 4K quality with exceptional detail.';
60
+ }
61
+ }
62
+
63
+ const requestBody = {
64
+ contents: [{ parts: [{ text: enhancedPrompt }] }],
65
+ generationConfig: {
66
+ responseModalities: ['TEXT', 'IMAGE'],
67
+ },
68
+ };
69
+
70
+ // Configure aspect ratio and resolution
71
+ if (aspectRatio || resolution) {
72
+ requestBody.generationConfig.imageConfig = {};
73
+ if (aspectRatio) {
74
+ requestBody.generationConfig.imageConfig.aspectRatio = aspectRatio;
75
+ }
76
+ if (resolution) {
77
+ const sizeMap = { '4k': '4K', '2k': '2K', '1k': '1K', high: '1K' };
78
+ requestBody.generationConfig.imageConfig.imageSize = sizeMap[resolution.toLowerCase()] || '1K';
79
+ }
80
+ }
81
+
82
+ const response = await fetch(url, {
83
+ method: 'POST',
84
+ headers: { 'Content-Type': 'application/json' },
85
+ body: JSON.stringify(requestBody),
86
+ });
87
+
88
+ if (!response.ok) {
89
+ const errorText = await response.text();
90
+ throw new Error(`API Error: ${response.status} - ${errorText}`);
91
+ }
92
+
93
+ const result = await response.json();
94
+
95
+ if (!result.candidates || !result.candidates[0]?.content?.parts) {
96
+ throw new Error('No image generated');
97
+ }
98
+
99
+ const images = [];
100
+ for (const part of result.candidates[0].content.parts) {
101
+ if (part.inlineData && part.inlineData.mimeType?.startsWith('image/')) {
102
+ images.push({
103
+ data: Buffer.from(part.inlineData.data, 'base64'),
104
+ mimeType: part.inlineData.mimeType,
105
+ });
106
+ }
107
+ }
108
+
109
+ if (images.length === 0) {
110
+ throw new Error('No image data in response');
111
+ }
112
+
113
+ // Determine output directory
114
+ const outputDir =
115
+ outputPath && fs.statSync(outputPath, { throwIfNoEntry: false })?.isDirectory()
116
+ ? outputPath
117
+ : outputPath
118
+ ? path.dirname(outputPath)
119
+ : path.join(process.env.HOME || process.cwd(), 'nanobanana-images');
120
+
121
+ if (!fs.existsSync(outputDir)) {
122
+ fs.mkdirSync(outputDir, { recursive: true });
123
+ }
124
+
125
+ const savedPaths = [];
126
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
127
+ const prefix = model === 'pro' ? 'pro_' : '';
128
+
129
+ for (let i = 0; i < images.length; i++) {
130
+ const ext = images[i].mimeType === 'image/png' ? 'png' : 'jpg';
131
+ const filename =
132
+ outputPath && !fs.statSync(outputPath, { throwIfNoEntry: false })?.isDirectory()
133
+ ? path.basename(outputPath)
134
+ : `${prefix}image_${timestamp}_${i + 1}.${ext}`;
135
+ const fullPath = path.join(outputDir, filename);
136
+
137
+ fs.writeFileSync(fullPath, images[i].data);
138
+ savedPaths.push(fullPath);
139
+ console.log(`āœ… Saved: ${fullPath}`);
140
+ }
141
+
142
+ return savedPaths;
143
+ }
144
+
145
+ module.exports = { generateImage, getGeminiApiKey, MODELS };