@fionoble/image-gen-cli 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 +91 -0
- package/index.js +231 -0
- package/package.json +34 -0
- package/skill/SKILL.md +71 -0
package/README.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# @fionoble/image-gen-cli
|
|
2
|
+
|
|
3
|
+
A CLI tool for generating and editing images using OpenAI's `gpt-image-1` model. Comes with a [Claude Code](https://claude.com/claude-code) skill for seamless AI-assisted image generation.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @fionoble/image-gen-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or use without installing:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx @fionoble/image-gen-cli -p "A cat in a spacesuit" -o cat.png
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### Prerequisites
|
|
18
|
+
|
|
19
|
+
- Node.js 18+
|
|
20
|
+
- An [OpenAI API key](https://platform.openai.com/api-keys) set as `OPENAI_API_KEY`
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
export OPENAI_API_KEY="sk-..."
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## CLI Usage
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
image-gen [options]
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Options
|
|
33
|
+
|
|
34
|
+
| Flag | Description | Default |
|
|
35
|
+
|------|-------------|---------|
|
|
36
|
+
| `-p, --prompt <text>` | Image prompt (required) | - |
|
|
37
|
+
| `-i, --image <path>` | Input image for editing | - |
|
|
38
|
+
| `-o, --output <path>` | Output file path | `generated-<timestamp>.png` |
|
|
39
|
+
| `-s, --size <size>` | `1024x1024`, `1024x1536`, `1536x1024`, `auto` | `auto` |
|
|
40
|
+
| `-q, --quality <quality>` | `low`, `medium`, `high`, `auto` | `auto` |
|
|
41
|
+
| `-n, --count <n>` | Number of images to generate | `1` |
|
|
42
|
+
| `-m, --model <model>` | OpenAI model | `gpt-image-1` |
|
|
43
|
+
| `--install-skill` | Install the Claude Code skill | - |
|
|
44
|
+
| `--uninstall-skill` | Remove the Claude Code skill | - |
|
|
45
|
+
|
|
46
|
+
### Examples
|
|
47
|
+
|
|
48
|
+
Generate an image:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
image-gen -p "A watercolor painting of a Japanese garden" -q high -o garden.png
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Edit an existing image:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
image-gen -p "Make the sky a dramatic sunset" -i photo.png -o photo-sunset.png
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Generate a logo:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
image-gen -p "Minimalist coffee shop logo" -q high -s 1024x1024 -o logo.png
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Claude Code Skill
|
|
67
|
+
|
|
68
|
+
Install the skill so Claude Code can generate images for you:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
image-gen --install-skill
|
|
72
|
+
# or
|
|
73
|
+
npx @fionoble/image-gen-cli --install-skill
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Once installed, the `/image-gen` skill is available in Claude Code. You can either:
|
|
77
|
+
|
|
78
|
+
- Invoke it directly: `/image-gen create a logo for my app`
|
|
79
|
+
- Or just ask naturally: "generate an image of a cat in space"
|
|
80
|
+
|
|
81
|
+
Claude will craft a detailed prompt, pick appropriate settings, generate the image, and display the result.
|
|
82
|
+
|
|
83
|
+
To remove the skill:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
image-gen --uninstall-skill
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## License
|
|
90
|
+
|
|
91
|
+
ISC
|
package/index.js
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const OpenAI = require("openai").default;
|
|
6
|
+
|
|
7
|
+
function printUsage() {
|
|
8
|
+
console.log(`Usage: image-gen [options]
|
|
9
|
+
|
|
10
|
+
Generate or edit images using OpenAI's gpt-image-1 model.
|
|
11
|
+
|
|
12
|
+
Options:
|
|
13
|
+
-p, --prompt <text> Image prompt (required)
|
|
14
|
+
-i, --image <path> Input image for editing (optional)
|
|
15
|
+
-o, --output <path> Output file path (default: generated-<timestamp>.png)
|
|
16
|
+
-s, --size <size> Image size: 1024x1024, 1024x1536, 1536x1024, auto (default: auto)
|
|
17
|
+
-q, --quality <quality> Quality: low, medium, high, auto (default: auto)
|
|
18
|
+
-n, --count <n> Number of images to generate (default: 1)
|
|
19
|
+
-m, --model <model> Model: gpt-image-1 (default: gpt-image-1)
|
|
20
|
+
--install-skill Install the Claude Code skill to ~/.claude/skills/
|
|
21
|
+
--uninstall-skill Remove the Claude Code skill
|
|
22
|
+
-h, --help Show this help message
|
|
23
|
+
|
|
24
|
+
Environment:
|
|
25
|
+
OPENAI_API_KEY Required. Your OpenAI API key.
|
|
26
|
+
|
|
27
|
+
Examples:
|
|
28
|
+
image-gen -p "A cat in a spacesuit"
|
|
29
|
+
image-gen -p "Make the background blue" -i photo.png
|
|
30
|
+
image-gen -p "A logo for a coffee shop" -s 1024x1024 -q high -o logo.png
|
|
31
|
+
npx @fionoble/image-gen-cli --install-skill
|
|
32
|
+
`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function installSkill() {
|
|
36
|
+
const os = require("os");
|
|
37
|
+
const skillDir = path.join(os.homedir(), ".claude", "skills", "image-gen");
|
|
38
|
+
const source = path.join(__dirname, "skill", "SKILL.md");
|
|
39
|
+
|
|
40
|
+
if (!fs.existsSync(source)) {
|
|
41
|
+
console.error("Error: SKILL.md not found in package. Try reinstalling.");
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
46
|
+
fs.copyFileSync(source, path.join(skillDir, "SKILL.md"));
|
|
47
|
+
console.log(`Claude Code skill installed to ${skillDir}/SKILL.md`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function uninstallSkill() {
|
|
51
|
+
const os = require("os");
|
|
52
|
+
const skillDir = path.join(os.homedir(), ".claude", "skills", "image-gen");
|
|
53
|
+
const skillFile = path.join(skillDir, "SKILL.md");
|
|
54
|
+
|
|
55
|
+
if (fs.existsSync(skillFile)) {
|
|
56
|
+
fs.unlinkSync(skillFile);
|
|
57
|
+
fs.rmdirSync(skillDir);
|
|
58
|
+
console.log("Claude Code skill removed.");
|
|
59
|
+
} else {
|
|
60
|
+
console.log("Skill not installed, nothing to remove.");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function parseArgs(argv) {
|
|
65
|
+
const args = {
|
|
66
|
+
prompt: null,
|
|
67
|
+
image: null,
|
|
68
|
+
output: null,
|
|
69
|
+
size: "auto",
|
|
70
|
+
quality: "auto",
|
|
71
|
+
count: 1,
|
|
72
|
+
model: "gpt-image-1",
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
for (let i = 2; i < argv.length; i++) {
|
|
76
|
+
const arg = argv[i];
|
|
77
|
+
switch (arg) {
|
|
78
|
+
case "-p":
|
|
79
|
+
case "--prompt":
|
|
80
|
+
args.prompt = argv[++i];
|
|
81
|
+
break;
|
|
82
|
+
case "-i":
|
|
83
|
+
case "--image":
|
|
84
|
+
args.image = argv[++i];
|
|
85
|
+
break;
|
|
86
|
+
case "-o":
|
|
87
|
+
case "--output":
|
|
88
|
+
args.output = argv[++i];
|
|
89
|
+
break;
|
|
90
|
+
case "-s":
|
|
91
|
+
case "--size":
|
|
92
|
+
args.size = argv[++i];
|
|
93
|
+
break;
|
|
94
|
+
case "-q":
|
|
95
|
+
case "--quality":
|
|
96
|
+
args.quality = argv[++i];
|
|
97
|
+
break;
|
|
98
|
+
case "-n":
|
|
99
|
+
case "--count":
|
|
100
|
+
args.count = parseInt(argv[++i], 10);
|
|
101
|
+
break;
|
|
102
|
+
case "-m":
|
|
103
|
+
case "--model":
|
|
104
|
+
args.model = argv[++i];
|
|
105
|
+
break;
|
|
106
|
+
case "--install-skill":
|
|
107
|
+
installSkill();
|
|
108
|
+
process.exit(0);
|
|
109
|
+
case "--uninstall-skill":
|
|
110
|
+
uninstallSkill();
|
|
111
|
+
process.exit(0);
|
|
112
|
+
case "-h":
|
|
113
|
+
case "--help":
|
|
114
|
+
printUsage();
|
|
115
|
+
process.exit(0);
|
|
116
|
+
default:
|
|
117
|
+
if (!args.prompt) {
|
|
118
|
+
args.prompt = arg;
|
|
119
|
+
} else {
|
|
120
|
+
console.error(`Unknown option: ${arg}`);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return args;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function resolveOutputPath(output, index, count) {
|
|
130
|
+
if (output && count === 1) {
|
|
131
|
+
return path.resolve(output);
|
|
132
|
+
}
|
|
133
|
+
const timestamp = Date.now();
|
|
134
|
+
const base = output
|
|
135
|
+
? path.parse(output)
|
|
136
|
+
: { dir: ".", name: `generated-${timestamp}`, ext: ".png" };
|
|
137
|
+
if (count > 1) {
|
|
138
|
+
return path.resolve(base.dir, `${base.name}-${index + 1}${base.ext || ".png"}`);
|
|
139
|
+
}
|
|
140
|
+
return path.resolve(base.dir, `${base.name}${base.ext || ".png"}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function generateImage(client, args) {
|
|
144
|
+
console.log(`Generating image with prompt: "${args.prompt}"`);
|
|
145
|
+
|
|
146
|
+
const params = {
|
|
147
|
+
model: args.model,
|
|
148
|
+
prompt: args.prompt,
|
|
149
|
+
n: args.count,
|
|
150
|
+
size: args.size,
|
|
151
|
+
quality: args.quality,
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const result = await client.images.generate(params);
|
|
155
|
+
|
|
156
|
+
const paths = [];
|
|
157
|
+
for (let i = 0; i < result.data.length; i++) {
|
|
158
|
+
const outputPath = resolveOutputPath(args.output, i, args.count);
|
|
159
|
+
const imageBytes = Buffer.from(result.data[i].b64_json, "base64");
|
|
160
|
+
fs.writeFileSync(outputPath, imageBytes);
|
|
161
|
+
paths.push(outputPath);
|
|
162
|
+
console.log(`Saved: ${outputPath}`);
|
|
163
|
+
}
|
|
164
|
+
return paths;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function editImage(client, args) {
|
|
168
|
+
const imagePath = path.resolve(args.image);
|
|
169
|
+
if (!fs.existsSync(imagePath)) {
|
|
170
|
+
console.error(`Input image not found: ${imagePath}`);
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
console.log(`Editing image "${imagePath}" with prompt: "${args.prompt}"`);
|
|
175
|
+
|
|
176
|
+
const imageFile = fs.createReadStream(imagePath);
|
|
177
|
+
|
|
178
|
+
const params = {
|
|
179
|
+
model: args.model,
|
|
180
|
+
prompt: args.prompt,
|
|
181
|
+
image: imageFile,
|
|
182
|
+
n: args.count,
|
|
183
|
+
size: args.size,
|
|
184
|
+
quality: args.quality,
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const result = await client.images.edit(params);
|
|
188
|
+
|
|
189
|
+
const paths = [];
|
|
190
|
+
for (let i = 0; i < result.data.length; i++) {
|
|
191
|
+
const outputPath = resolveOutputPath(args.output, i, args.count);
|
|
192
|
+
const imageBytes = Buffer.from(result.data[i].b64_json, "base64");
|
|
193
|
+
fs.writeFileSync(outputPath, imageBytes);
|
|
194
|
+
paths.push(outputPath);
|
|
195
|
+
console.log(`Saved: ${outputPath}`);
|
|
196
|
+
}
|
|
197
|
+
return paths;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function main() {
|
|
201
|
+
const args = parseArgs(process.argv);
|
|
202
|
+
|
|
203
|
+
if (!args.prompt) {
|
|
204
|
+
console.error("Error: --prompt is required");
|
|
205
|
+
printUsage();
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (!process.env.OPENAI_API_KEY) {
|
|
210
|
+
console.error("Error: OPENAI_API_KEY environment variable is not set");
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const client = new OpenAI();
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
if (args.image) {
|
|
218
|
+
await editImage(client, args);
|
|
219
|
+
} else {
|
|
220
|
+
await generateImage(client, args);
|
|
221
|
+
}
|
|
222
|
+
} catch (err) {
|
|
223
|
+
console.error(`Error: ${err.message}`);
|
|
224
|
+
if (err.status) {
|
|
225
|
+
console.error(`Status: ${err.status}`);
|
|
226
|
+
}
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fionoble/image-gen-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI tool for OpenAI image generation and editing, with a Claude Code skill",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"image-gen": "./index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"index.js",
|
|
11
|
+
"skill/SKILL.md"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"openai",
|
|
18
|
+
"image-generation",
|
|
19
|
+
"gpt-image",
|
|
20
|
+
"cli",
|
|
21
|
+
"claude-code",
|
|
22
|
+
"ai"
|
|
23
|
+
],
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/Fionoble/image-gen-cli.git"
|
|
27
|
+
},
|
|
28
|
+
"author": "Fionoble",
|
|
29
|
+
"license": "ISC",
|
|
30
|
+
"type": "commonjs",
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"openai": "^6.25.0"
|
|
33
|
+
}
|
|
34
|
+
}
|
package/skill/SKILL.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: image-gen
|
|
3
|
+
description: Generate or edit images using OpenAI's gpt-image-1 model. Use when the user asks to generate, create, or edit images using AI.
|
|
4
|
+
allowed-tools: Bash, Read
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# image-gen
|
|
8
|
+
|
|
9
|
+
Generate or edit images using OpenAI's gpt-image-1 model via the `image-gen` CLI tool.
|
|
10
|
+
|
|
11
|
+
## Tool Location
|
|
12
|
+
|
|
13
|
+
Run with `image-gen` (if globally installed via `npm link`) or `npx image-gen-cli`.
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
image-gen [options]
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Options
|
|
22
|
+
|
|
23
|
+
| Flag | Description | Default |
|
|
24
|
+
|------|-------------|---------|
|
|
25
|
+
| `-p, --prompt <text>` | Image prompt (required) | - |
|
|
26
|
+
| `-i, --image <path>` | Input image path for editing | - |
|
|
27
|
+
| `-o, --output <path>` | Output file path | `generated-<timestamp>.png` |
|
|
28
|
+
| `-s, --size <size>` | `1024x1024`, `1024x1536`, `1536x1024`, `auto` | `auto` |
|
|
29
|
+
| `-q, --quality <quality>` | `low`, `medium`, `high`, `auto` | `auto` |
|
|
30
|
+
| `-n, --count <n>` | Number of images | `1` |
|
|
31
|
+
| `-m, --model <model>` | OpenAI model | `gpt-image-1` |
|
|
32
|
+
|
|
33
|
+
### Environment
|
|
34
|
+
|
|
35
|
+
Requires `OPENAI_API_KEY` to be set in the environment.
|
|
36
|
+
|
|
37
|
+
## Instructions
|
|
38
|
+
|
|
39
|
+
When the user asks you to generate or edit an image:
|
|
40
|
+
|
|
41
|
+
1. **Craft a detailed prompt** from the user's request. Good prompts are specific about style, composition, colors, lighting, and mood. Expand brief requests into rich descriptions.
|
|
42
|
+
|
|
43
|
+
2. **Choose appropriate settings:**
|
|
44
|
+
- Use `-q high` for logos, artwork, or when quality matters
|
|
45
|
+
- Use `-q low` for quick drafts or iterations
|
|
46
|
+
- Use `-s 1024x1536` for portraits/vertical images
|
|
47
|
+
- Use `-s 1536x1024` for landscapes/horizontal images
|
|
48
|
+
- Use `-s 1024x1024` for square images (icons, profile pictures, logos)
|
|
49
|
+
|
|
50
|
+
3. **For image editing** (`-i` flag): When the user provides an existing image and wants modifications, use the edit mode. The input image must be a valid PNG file.
|
|
51
|
+
|
|
52
|
+
4. **Output path**: Always use `-o` to save to a descriptive filename in the current working directory unless the user specifies otherwise.
|
|
53
|
+
|
|
54
|
+
5. **After generation**: Read and display the generated image to the user using the Read tool so they can see the result. Ask if they want any changes.
|
|
55
|
+
|
|
56
|
+
## Examples
|
|
57
|
+
|
|
58
|
+
Generate a new image:
|
|
59
|
+
```bash
|
|
60
|
+
image-gen -p "A watercolor painting of a serene Japanese garden with cherry blossoms, koi pond, and a small wooden bridge, soft morning light" -q high -s 1536x1024 -o japanese-garden.png
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Edit an existing image:
|
|
64
|
+
```bash
|
|
65
|
+
image-gen -p "Change the sky to a dramatic sunset with orange and purple clouds" -i photo.png -q high -o photo-sunset.png
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Generate a logo:
|
|
69
|
+
```bash
|
|
70
|
+
image-gen -p "Minimalist logo for a coffee shop called 'Bean & Brew', clean lines, modern design, coffee cup icon" -q high -s 1024x1024 -o logo.png
|
|
71
|
+
```
|