@yohei1996/nanobanana 1.0.0 → 1.1.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 CHANGED
@@ -1,57 +1,70 @@
1
1
  # @yohei1996/nanobanana
2
2
 
3
- 🍌 Nano Banana - Gemini Pro Image Generation CLI
3
+ 🍌 Nano Banana - Gemini Pro画像生成 CLI & Claude Codeスキル
4
4
 
5
- High-quality image generation powered by Google's Gemini Pro with 4K support.
5
+ Gemini Proを使用した高品質画像生成(4K対応)
6
6
 
7
- ## Installation
7
+ ## インストール
8
+
9
+ ### スキルとしてインストール(推奨)
8
10
 
9
11
  ```bash
10
- npm install -g @yohei1996/nanobanana
12
+ # プロジェクトディレクトリで実行
13
+ npx @yohei1996/nanobanana install
11
14
  ```
12
15
 
13
- Or use directly with npx:
16
+ これで `.claude/skills/nanobanana/` にスキルがインストールされます。
17
+
18
+ ### グローバルインストール
14
19
 
15
20
  ```bash
16
- npx @yohei1996/nanobanana "A cat playing piano"
21
+ npm install -g @yohei1996/nanobanana
17
22
  ```
18
23
 
19
- ## Setup
24
+ ## セットアップ
20
25
 
21
- Set your Gemini API key:
26
+ Gemini APIキーを設定:
22
27
 
23
28
  ```bash
24
29
  export GEMINI_API_KEY=your-api-key
25
30
  ```
26
31
 
27
- ## Usage
32
+ または VSCode/Cursor設定で `bizAgent-task-kanban.geminiApiKey` を設定。
28
33
 
29
- ```bash
30
- # Basic usage
31
- nanobanana "A cat playing piano in watercolor style"
34
+ ## 使い方
35
+
36
+ ### Claude Codeスキルとして使用
32
37
 
33
- # With 4K resolution
34
- nanobanana "Landscape photo" --resolution 4k
38
+ ```
39
+ /nanobanana 猫がピアノを弾いている水彩画
40
+ ```
41
+
42
+ ### CLIから直接実行
43
+
44
+ ```bash
45
+ # 基本
46
+ npx @yohei1996/nanobanana "A cat playing piano in watercolor style"
35
47
 
36
- # With aspect ratio
37
- nanobanana "Logo design" --aspect 1:1 --output ./logo.png
48
+ # 4K解像度
49
+ npx @yohei1996/nanobanana "Landscape photo" --resolution 4k
38
50
 
39
- # All options
40
- nanobanana "prompt" --aspect 16:9 --resolution 4k --output ./image.png
51
+ # アスペクト比指定
52
+ npx @yohei1996/nanobanana "Logo design" --aspect 1:1 --output ./logo.png
41
53
  ```
42
54
 
43
- ## Options
55
+ ## オプション
44
56
 
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 | |
57
+ | オプション | 説明 | |
58
+ |-----------|------|-----|
59
+ | `install [dir]` | スキルをインストール | `install .` |
60
+ | `--aspect <ratio>` | アスペクト比 | `1:1`, `16:9`, `9:16`, `4:3` |
61
+ | `--resolution <r>` | 解像度 | `4k`, `2k`, `1k` |
62
+ | `--output <path>` | 出力先パス | `./image.png` |
63
+ | `--api-key <key>` | Gemini APIキー | |
51
64
 
52
- ## Output
65
+ ## 出力先
53
66
 
54
- Images are saved to `~/nanobanana-images/` by default.
67
+ デフォルト: `~/nanobanana-images/`
55
68
 
56
69
  ## License
57
70
 
package/bin/nanobanana.js CHANGED
@@ -1,15 +1,38 @@
1
1
  #!/usr/bin/env node
2
- const { generateImage } = require('../src/index.js');
2
+ const { generateImage, installSkills } = require('../src/index.js');
3
3
 
4
4
  const args = process.argv.slice(2);
5
5
 
6
+ // Handle install command
7
+ if (args[0] === 'install') {
8
+ const targetDir = args[1] || process.cwd();
9
+ const force = args.includes('--force') || args.includes('-f');
10
+
11
+ installSkills(targetDir, { force })
12
+ .then((installPath) => {
13
+ console.log('\n使用方法: Claude Codeでスキルを発火してください');
14
+ console.log('例: /nanobanana 猫がピアノを弾いている');
15
+ })
16
+ .catch((err) => {
17
+ console.error('Error:', err.message);
18
+ process.exit(1);
19
+ });
20
+ return;
21
+ }
22
+
6
23
  if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
7
24
  console.log(`
8
25
  🍌 Nano Banana - Gemini Pro Image Generator
9
26
 
10
- Usage: nanobanana <prompt> [options]
27
+ Usage:
28
+ nanobanana install [dir] Install skills to .claude/skills/
29
+ nanobanana <prompt> Generate image directly
11
30
 
12
- Options:
31
+ Install Options:
32
+ install [dir] Install to specified directory (default: current dir)
33
+ --force, -f Overwrite existing files
34
+
35
+ Generate Options:
13
36
  --aspect <ratio> Aspect ratio (1:1, 16:9, 9:16, 4:3, 3:4, etc.)
14
37
  --resolution <r> Resolution (4k, 2k, 1k)
15
38
  --output <path> Output file path
@@ -17,9 +40,12 @@ Options:
17
40
  --help, -h Show this help message
18
41
 
19
42
  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
43
+ # Install skills
44
+ npx @yohei1996/nanobanana install
45
+
46
+ # Generate image directly
47
+ npx @yohei1996/nanobanana "A cat playing piano in watercolor style"
48
+ npx @yohei1996/nanobanana "Landscape photo" --resolution 4k
23
49
 
24
50
  Environment:
25
51
  GEMINI_API_KEY Your Gemini API key
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@yohei1996/nanobanana",
3
- "version": "1.0.0",
4
- "description": "Gemini Pro image generation CLI - High quality image generation with 4K support",
3
+ "version": "1.1.0",
4
+ "description": "Gemini Pro image generation CLI & Claude Code skill - High quality image generation with 4K support",
5
5
  "main": "src/index.js",
6
6
  "bin": {
7
7
  "nanobanana": "./bin/nanobanana.js"
@@ -15,7 +15,9 @@
15
15
  "image-generation",
16
16
  "ai",
17
17
  "cli",
18
- "4k"
18
+ "4k",
19
+ "claude-code",
20
+ "skill"
19
21
  ],
20
22
  "author": "yohei1996",
21
23
  "license": "MIT",
@@ -26,5 +28,10 @@
26
28
  },
27
29
  "engines": {
28
30
  "node": ">=18.0.0"
29
- }
31
+ },
32
+ "files": [
33
+ "bin/",
34
+ "src/",
35
+ "skills/"
36
+ ]
30
37
  }
@@ -0,0 +1,42 @@
1
+ ---
2
+ name: nanobanana
3
+ description: Gemini Pro画像生成(高品質・4K対応)。「画像生成」「画像作成」「イラスト作成」「〜の絵」と言われたら使用。
4
+ argument-hint: [prompt]
5
+ ---
6
+
7
+ # Nano Banana 画像生成
8
+
9
+ Gemini Proを使用して高品質な画像を生成する。
10
+
11
+ ## 実行方法
12
+
13
+ ```bash
14
+ node .claude/skills/nanobanana/scripts/generate-image.js "$ARGUMENTS"
15
+ ```
16
+
17
+ APIキーはVSCode設定(`bizAgent-task-kanban.geminiApiKey`)または環境変数から自動取得。
18
+
19
+ ## オプション
20
+
21
+ | オプション | 説明 | 例 |
22
+ |-----------|------|-----|
23
+ | --aspect | アスペクト比 | --aspect 16:9 |
24
+ | --resolution | 解像度(4k,2k,1k) | --resolution 4k |
25
+ | --output | 出力先パス | --output ./image.png |
26
+
27
+ ## 使用例
28
+
29
+ ```bash
30
+ # 基本(Proモデル)
31
+ node .claude/skills/nanobanana/scripts/generate-image.js "猫がピアノを弾いている水彩画"
32
+
33
+ # 4K高解像度
34
+ node .claude/skills/nanobanana/scripts/generate-image.js "風景写真" --resolution 4k
35
+
36
+ # アスペクト比指定
37
+ node .claude/skills/nanobanana/scripts/generate-image.js "ロゴデザイン" --aspect 1:1 --output ./logo.png
38
+ ```
39
+
40
+ ## 出力先
41
+
42
+ デフォルト: `~/nanobanana-images/`
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Gemini画像生成スクリプト
4
+ * - Flash: gemini-2.0-flash-exp-image-generation(高速)
5
+ * - Pro: gemini-2.0-flash-preview-image-generation(高品質・4K)
6
+ *
7
+ * 使用例:
8
+ * node generate-image.js "猫" --output ./cat.png
9
+ * node generate-image.js "風景" --pro --resolution 4k
10
+ */
11
+
12
+ const fs = require("fs");
13
+ const path = require("path");
14
+
15
+ const MODELS = {
16
+ flash: "gemini-2.0-flash-exp-image-generation",
17
+ pro: "gemini-3-pro-image-preview",
18
+ };
19
+
20
+ function getGeminiApiKey() {
21
+ // 1. VSCode/Cursor設定から取得
22
+ const os = require("os");
23
+ const settingsPaths = [
24
+ path.join(os.homedir(), "Library/Application Support/Code/User/settings.json"),
25
+ path.join(os.homedir(), "Library/Application Support/Cursor/User/settings.json"),
26
+ ];
27
+
28
+ for (const settingsPath of settingsPaths) {
29
+ if (fs.existsSync(settingsPath)) {
30
+ try {
31
+ const settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
32
+ const apiKey = settings["bizAgent-task-kanban.geminiApiKey"];
33
+ if (apiKey) return apiKey;
34
+ } catch {}
35
+ }
36
+ }
37
+
38
+ // 2. 環境変数から取得
39
+ const envKey = process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY;
40
+ if (envKey) return envKey;
41
+
42
+ throw new Error("Gemini APIキーが見つかりません。VSCode設定またはGEMINI_API_KEY環境変数を設定してください。");
43
+ }
44
+
45
+ async function generateImage(options) {
46
+ const { prompt, aspectRatio, outputPath, model = "flash", resolution } = options;
47
+
48
+ const apiKey = getGeminiApiKey();
49
+
50
+ const modelId = MODELS[model] || MODELS.flash;
51
+ const modelLabel = model === "pro" ? "🚀 Pro" : "⚡ Flash";
52
+
53
+ console.log(`${modelLabel} 画像生成中: "${prompt.substring(0, 50)}..."`);
54
+ if (aspectRatio) console.log(`📐 アスペクト比: ${aspectRatio}`);
55
+ if (resolution) console.log(`📏 解像度: ${resolution}`);
56
+
57
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/${modelId}:generateContent?key=${apiKey}`;
58
+
59
+ // プロンプトの強化(Proモデル用)
60
+ let enhancedPrompt = prompt;
61
+ if (model === "pro") {
62
+ if (prompt.length < 50) {
63
+ enhancedPrompt = `Create a high-quality, detailed image: ${prompt}. Pay attention to composition, lighting, and fine details.`;
64
+ }
65
+ if (resolution === "4k") {
66
+ enhancedPrompt += " Render at maximum 4K quality with exceptional detail.";
67
+ }
68
+ }
69
+
70
+ const requestBody = {
71
+ contents: [{ parts: [{ text: enhancedPrompt }] }],
72
+ generationConfig: {
73
+ responseModalities: ["TEXT", "IMAGE"],
74
+ },
75
+ };
76
+
77
+ // アスペクト比とimageConfigの設定
78
+ if (aspectRatio || resolution) {
79
+ requestBody.generationConfig.imageConfig = {};
80
+ if (aspectRatio) {
81
+ requestBody.generationConfig.imageConfig.aspectRatio = aspectRatio;
82
+ }
83
+ if (resolution) {
84
+ const sizeMap = { "4k": "4K", "2k": "2K", "1k": "1K", high: "1K" };
85
+ requestBody.generationConfig.imageConfig.imageSize = sizeMap[resolution.toLowerCase()] || "1K";
86
+ }
87
+ }
88
+
89
+ const response = await fetch(url, {
90
+ method: "POST",
91
+ headers: { "Content-Type": "application/json" },
92
+ body: JSON.stringify(requestBody),
93
+ });
94
+
95
+ if (!response.ok) {
96
+ const errorText = await response.text();
97
+ throw new Error(`API Error: ${response.status} - ${errorText}`);
98
+ }
99
+
100
+ const result = await response.json();
101
+
102
+ if (!result.candidates || !result.candidates[0]?.content?.parts) {
103
+ console.error("APIレスポンス:", JSON.stringify(result, null, 2));
104
+ throw new Error("画像が生成されませんでした");
105
+ }
106
+
107
+ const images = [];
108
+ for (const part of result.candidates[0].content.parts) {
109
+ if (part.inlineData && part.inlineData.mimeType?.startsWith("image/")) {
110
+ images.push({
111
+ data: Buffer.from(part.inlineData.data, "base64"),
112
+ mimeType: part.inlineData.mimeType,
113
+ });
114
+ }
115
+ }
116
+
117
+ if (images.length === 0) {
118
+ console.error("パーツ:", JSON.stringify(result.candidates[0].content.parts, null, 2));
119
+ throw new Error("画像が生成されませんでした(画像パーツなし)");
120
+ }
121
+
122
+ // 出力先ディレクトリの決定
123
+ const outputDir =
124
+ outputPath && fs.statSync(outputPath, { throwIfNoEntry: false })?.isDirectory()
125
+ ? outputPath
126
+ : outputPath
127
+ ? path.dirname(outputPath)
128
+ : path.join(process.env.HOME, "nanobanana-images");
129
+
130
+ if (!fs.existsSync(outputDir)) {
131
+ fs.mkdirSync(outputDir, { recursive: true });
132
+ }
133
+
134
+ const savedPaths = [];
135
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
136
+ const prefix = model === "pro" ? "pro_" : "";
137
+
138
+ for (let i = 0; i < images.length; i++) {
139
+ const ext = images[i].mimeType === "image/png" ? "png" : "jpg";
140
+ const filename =
141
+ outputPath && !fs.statSync(outputPath, { throwIfNoEntry: false })?.isDirectory()
142
+ ? path.basename(outputPath)
143
+ : `${prefix}image_${timestamp}_${i + 1}.${ext}`;
144
+ const fullPath = path.join(outputDir, filename);
145
+
146
+ fs.writeFileSync(fullPath, images[i].data);
147
+ savedPaths.push(fullPath);
148
+ console.log(`✅ 保存: ${fullPath}`);
149
+ }
150
+
151
+ return savedPaths;
152
+ }
153
+
154
+ // CLI実行
155
+ if (require.main === module) {
156
+ const args = process.argv.slice(2);
157
+ if (args.length === 0) {
158
+ console.log(`使用法: node generate-image.js <prompt> [options]
159
+
160
+ オプション:
161
+ --pro Proモデル使用(高品質・4K対応)
162
+ --aspect <ratio> アスペクト比(1:1, 16:9, 9:16, 4:3, 3:4など)
163
+ --resolution <r> 解像度(4k, 2k, 1k, high)※Proモデル推奨
164
+ --output <path> 出力先パス
165
+
166
+ 例:
167
+ node generate-image.js "猫がピアノを弾いている"
168
+ node generate-image.js "風景写真" --pro --resolution 4k
169
+ node generate-image.js "ロゴ" --aspect 1:1 --output ./logo.png
170
+ `);
171
+ process.exit(1);
172
+ }
173
+
174
+ const options = { prompt: "", model: "pro" }; // デフォルトでProモデルを使用
175
+ for (let i = 0; i < args.length; i++) {
176
+ if (args[i] === "--pro") {
177
+ options.model = "pro";
178
+ } else if (args[i] === "--aspect" && args[i + 1]) {
179
+ options.aspectRatio = args[++i];
180
+ } else if (args[i] === "--resolution" && args[i + 1]) {
181
+ options.resolution = args[++i];
182
+ } else if (args[i] === "--output" && args[i + 1]) {
183
+ options.outputPath = args[++i];
184
+ } else if (!args[i].startsWith("--")) {
185
+ options.prompt = args[i];
186
+ }
187
+ }
188
+
189
+ generateImage(options)
190
+ .then((paths) => console.log("\n生成完了:", paths.join(", ")))
191
+ .catch((err) => {
192
+ console.error("エラー:", err.message);
193
+ process.exit(1);
194
+ });
195
+ }
196
+
197
+ module.exports = { generateImage };
package/src/index.js CHANGED
@@ -142,4 +142,60 @@ async function generateImage(options) {
142
142
  return savedPaths;
143
143
  }
144
144
 
145
- module.exports = { generateImage, getGeminiApiKey, MODELS };
145
+ /**
146
+ * Install nanobanana skills to target directory
147
+ * @param {string} targetDir - Target directory (default: current working directory)
148
+ * @param {object} options - Installation options
149
+ * @param {boolean} options.force - Overwrite existing files
150
+ * @param {boolean} options.silent - Suppress output
151
+ */
152
+ async function installSkills(targetDir = process.cwd(), options = {}) {
153
+ const { force = false, silent = false } = options;
154
+
155
+ const skillsSourceDir = path.join(__dirname, '..', 'skills', 'nanobanana');
156
+ const skillsTargetDir = path.join(targetDir, '.claude', 'skills', 'nanobanana');
157
+
158
+ // Check if source exists
159
+ if (!fs.existsSync(skillsSourceDir)) {
160
+ throw new Error(`Skills source not found: ${skillsSourceDir}`);
161
+ }
162
+
163
+ // Create target directory
164
+ if (!fs.existsSync(skillsTargetDir)) {
165
+ fs.mkdirSync(skillsTargetDir, { recursive: true });
166
+ }
167
+
168
+ // Copy files recursively
169
+ const copyDir = (src, dest) => {
170
+ const entries = fs.readdirSync(src, { withFileTypes: true });
171
+
172
+ for (const entry of entries) {
173
+ const srcPath = path.join(src, entry.name);
174
+ const destPath = path.join(dest, entry.name);
175
+
176
+ if (entry.isDirectory()) {
177
+ if (!fs.existsSync(destPath)) {
178
+ fs.mkdirSync(destPath, { recursive: true });
179
+ }
180
+ copyDir(srcPath, destPath);
181
+ } else {
182
+ if (!force && fs.existsSync(destPath)) {
183
+ if (!silent) console.log(`⏭️ Skipping (exists): ${destPath}`);
184
+ continue;
185
+ }
186
+ fs.copyFileSync(srcPath, destPath);
187
+ if (!silent) console.log(`✅ Installed: ${destPath}`);
188
+ }
189
+ }
190
+ };
191
+
192
+ copyDir(skillsSourceDir, skillsTargetDir);
193
+
194
+ if (!silent) {
195
+ console.log(`\n🍌 nanobanana skills installed to: ${skillsTargetDir}`);
196
+ }
197
+
198
+ return skillsTargetDir;
199
+ }
200
+
201
+ module.exports = { generateImage, getGeminiApiKey, MODELS, installSkills };