imgx-cli 0.6.0 → 0.7.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/CHANGELOG.md +38 -0
- package/README.md +27 -11
- package/dist/cli.bundle.js +32 -12
- package/dist/mcp.bundle.js +67 -22
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.0 (2026-02-27)
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Output format selection** — `--format` flag (CLI) and `output_format` parameter (MCP) to choose between `png`, `jpeg`, or `webp` output. Currently supported by OpenAI provider (`gpt-image-1`). Gemini provider outputs PNG regardless of format setting.
|
|
8
|
+
- `OUTPUT_FORMAT` capability added to provider capability system
|
|
9
|
+
|
|
10
|
+
## 0.6.2 (2026-02-27)
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Image preview in MCP responses** — MCP tool results now include inline image data (base64) alongside file paths. Claude Desktop and other MCP clients can display generated/edited images directly without opening files manually.
|
|
15
|
+
|
|
16
|
+
## 0.6.1 (2026-02-27)
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Default output directory changed from process cwd to `~/Pictures/imgx` — fixes images being saved to AppData when used via MCP (Claude Desktop, etc.)
|
|
21
|
+
|
|
22
|
+
## 0.6.0 (2026-02-27)
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- **OpenAI provider** — `gpt-image-1` model with generate and edit support
|
|
27
|
+
- Native `fetch` implementation (no `openai` npm dependency)
|
|
28
|
+
- Aspect ratio mapping to OpenAI size strings, resolution mapping to quality parameter
|
|
29
|
+
- Manual multipart/form-data construction for Node 18 compatibility
|
|
30
|
+
- `OPENAI_API_KEY` environment variable support
|
|
31
|
+
- `--provider` flag for `imgx config set api-key` — manage API keys per provider
|
|
32
|
+
- `imgx config set api-key <key> --provider openai`
|
|
33
|
+
- `imgx config list` now shows all configured provider keys
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- CLI and MCP server now initialize both Gemini and OpenAI providers at startup
|
|
38
|
+
- `imgx providers` and error messages updated for multi-provider context
|
|
39
|
+
- Help text updated with OpenAI provider info and env var
|
|
40
|
+
|
|
3
41
|
## 0.5.2 (2026-02-26)
|
|
4
42
|
|
|
5
43
|
### Fixed
|
package/README.md
CHANGED
|
@@ -48,16 +48,25 @@ Requires Node.js 18+.
|
|
|
48
48
|
|
|
49
49
|
## Setup
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
Set up at least one provider:
|
|
52
|
+
|
|
53
|
+
**Gemini** — get a key from [Google AI Studio](https://aistudio.google.com/apikey) (free tier available):
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
imgx config set api-key YOUR_GEMINI_API_KEY --provider gemini
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**OpenAI** — get a key from [OpenAI Platform](https://platform.openai.com/api-keys):
|
|
52
60
|
|
|
53
61
|
```bash
|
|
54
|
-
imgx config set api-key
|
|
62
|
+
imgx config set api-key YOUR_OPENAI_API_KEY --provider openai
|
|
55
63
|
```
|
|
56
64
|
|
|
57
|
-
|
|
65
|
+
Keys are stored in `~/.config/imgx/config.json` (Linux/macOS) or `%APPDATA%\imgx\config.json` (Windows). Alternatively, set environment variables:
|
|
58
66
|
|
|
59
67
|
```bash
|
|
60
68
|
export GEMINI_API_KEY="your-api-key"
|
|
69
|
+
export OPENAI_API_KEY="your-api-key"
|
|
61
70
|
```
|
|
62
71
|
|
|
63
72
|
Environment variables take precedence over the config file.
|
|
@@ -99,6 +108,7 @@ imgx edit --last -p "Crop to 16:9" -o final.png
|
|
|
99
108
|
| `--aspect-ratio` | `-a` | `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `2:3`, `3:2` |
|
|
100
109
|
| `--resolution` | `-r` | `1K`, `2K`, `4K` |
|
|
101
110
|
| `--count` | `-n` | Number of images to generate |
|
|
111
|
+
| `--format` | `-f` | Output format: `png`, `jpeg`, `webp` (OpenAI only) |
|
|
102
112
|
| `--model` | `-m` | Model name |
|
|
103
113
|
| `--provider` | | Provider name (default: `gemini`) |
|
|
104
114
|
| `--output-dir` | `-d` | Output directory |
|
|
@@ -106,7 +116,8 @@ imgx edit --last -p "Crop to 16:9" -o final.png
|
|
|
106
116
|
### Configuration
|
|
107
117
|
|
|
108
118
|
```bash
|
|
109
|
-
imgx config set api-key <key>
|
|
119
|
+
imgx config set api-key <key> --provider gemini # Save Gemini API key
|
|
120
|
+
imgx config set api-key <key> --provider openai # Save OpenAI API key
|
|
110
121
|
imgx config set model <name> # Set default model
|
|
111
122
|
imgx config set output-dir <dir> # Set default output directory
|
|
112
123
|
imgx config set aspect-ratio 16:9 # Set default aspect ratio
|
|
@@ -162,7 +173,8 @@ Environment variables override config file settings.
|
|
|
162
173
|
|
|
163
174
|
| Variable | Description |
|
|
164
175
|
|----------|-------------|
|
|
165
|
-
| `GEMINI_API_KEY` | Gemini API key
|
|
176
|
+
| `GEMINI_API_KEY` | Gemini API key |
|
|
177
|
+
| `OPENAI_API_KEY` | OpenAI API key |
|
|
166
178
|
| `IMGX_PROVIDER` | Default provider |
|
|
167
179
|
| `IMGX_MODEL` | Default model |
|
|
168
180
|
| `IMGX_OUTPUT_DIR` | Default output directory |
|
|
@@ -206,7 +218,7 @@ Add to your tool's MCP config. The `env` section is optional if you have already
|
|
|
206
218
|
"imgx": {
|
|
207
219
|
"command": "npx",
|
|
208
220
|
"args": ["--package=imgx-cli", "-y", "imgx-mcp"],
|
|
209
|
-
"env": { "GEMINI_API_KEY": "your-
|
|
221
|
+
"env": { "GEMINI_API_KEY": "your-key", "OPENAI_API_KEY": "your-key" }
|
|
210
222
|
}
|
|
211
223
|
}
|
|
212
224
|
}
|
|
@@ -224,7 +236,7 @@ Or install as a [Claude Code plugin](#install) for automatic MCP registration.
|
|
|
224
236
|
"imgx": {
|
|
225
237
|
"command": "npx",
|
|
226
238
|
"args": ["--package=imgx-cli", "-y", "imgx-mcp"],
|
|
227
|
-
"env": { "GEMINI_API_KEY": "your-
|
|
239
|
+
"env": { "GEMINI_API_KEY": "your-key", "OPENAI_API_KEY": "your-key" }
|
|
228
240
|
}
|
|
229
241
|
}
|
|
230
242
|
}
|
|
@@ -240,7 +252,7 @@ macOS / Linux:
|
|
|
240
252
|
"imgx": {
|
|
241
253
|
"command": "npx",
|
|
242
254
|
"args": ["--package=imgx-cli", "-y", "imgx-mcp"],
|
|
243
|
-
"env": { "GEMINI_API_KEY": "your-
|
|
255
|
+
"env": { "GEMINI_API_KEY": "your-key", "OPENAI_API_KEY": "your-key" }
|
|
244
256
|
}
|
|
245
257
|
}
|
|
246
258
|
}
|
|
@@ -254,7 +266,7 @@ Windows:
|
|
|
254
266
|
"imgx": {
|
|
255
267
|
"command": "cmd",
|
|
256
268
|
"args": ["/c", "npx", "--package=imgx-cli", "-y", "imgx-mcp"],
|
|
257
|
-
"env": { "GEMINI_API_KEY": "your-
|
|
269
|
+
"env": { "GEMINI_API_KEY": "your-key", "OPENAI_API_KEY": "your-key" }
|
|
258
270
|
}
|
|
259
271
|
}
|
|
260
272
|
}
|
|
@@ -270,11 +282,13 @@ Config file location: `%APPDATA%\Claude\claude_desktop_config.json` (Windows) or
|
|
|
270
282
|
[mcp_servers.imgx]
|
|
271
283
|
command = "npx"
|
|
272
284
|
args = ["--package=imgx-cli", "-y", "imgx-mcp"]
|
|
273
|
-
env = { GEMINI_API_KEY = "your-
|
|
285
|
+
env = { GEMINI_API_KEY = "your-key", OPENAI_API_KEY = "your-key" }
|
|
274
286
|
```
|
|
275
287
|
|
|
276
288
|
The same `npx` pattern works with Cursor, Windsurf, Continue.dev, Cline, Zed, and other MCP-compatible tools. On Windows, use `cmd /c npx` instead of `npx` directly.
|
|
277
289
|
|
|
290
|
+
Only include the API keys for providers you want to use. At least one is required.
|
|
291
|
+
|
|
278
292
|
## Architecture
|
|
279
293
|
|
|
280
294
|
imgx separates **model-independent** and **model-dependent** concerns:
|
|
@@ -302,12 +316,14 @@ Each provider declares its supported capabilities. The CLI dynamically enables o
|
|
|
302
316
|
| `MULTIPLE_OUTPUTS` | Generate multiple images per request |
|
|
303
317
|
| `REFERENCE_IMAGES` | Use reference images for guidance |
|
|
304
318
|
| `PERSON_CONTROL` | Control person generation in output |
|
|
319
|
+
| `OUTPUT_FORMAT` | Choose output format (PNG, JPEG, WebP) |
|
|
305
320
|
|
|
306
321
|
### Current providers
|
|
307
322
|
|
|
308
323
|
| Provider | Models | Capabilities |
|
|
309
324
|
|----------|--------|-------------|
|
|
310
|
-
| Gemini | `gemini-3-pro-image-preview`, `gemini-2.5-flash-image` | All 7 capabilities |
|
|
325
|
+
| Gemini | `gemini-3-pro-image-preview`, `gemini-2.5-flash-image` | All 7 base capabilities |
|
|
326
|
+
| OpenAI | `gpt-image-1` | Generate, edit, aspect ratio, multi-output, output format |
|
|
311
327
|
|
|
312
328
|
## Development
|
|
313
329
|
|
package/dist/cli.bundle.js
CHANGED
|
@@ -39210,8 +39210,9 @@ function getApiKeyFromEnv() {
|
|
|
39210
39210
|
|
|
39211
39211
|
// build/core/storage.js
|
|
39212
39212
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "node:fs";
|
|
39213
|
-
import { dirname, resolve as resolve2 } from "node:path";
|
|
39213
|
+
import { dirname, join as join2, resolve as resolve2 } from "node:path";
|
|
39214
39214
|
import { randomUUID } from "node:crypto";
|
|
39215
|
+
import { homedir as homedir2 } from "node:os";
|
|
39215
39216
|
var MIME_TO_EXT = {
|
|
39216
39217
|
"image/png": ".png",
|
|
39217
39218
|
"image/jpeg": ".jpg",
|
|
@@ -39224,9 +39225,17 @@ function readImageAsBase64(filePath) {
|
|
|
39224
39225
|
const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "webp" ? "image/webp" : "image/png";
|
|
39225
39226
|
return { data: buffer.toString("base64"), mimeType };
|
|
39226
39227
|
}
|
|
39228
|
+
function fallbackOutputDir(outputDir) {
|
|
39229
|
+
if (outputDir)
|
|
39230
|
+
return outputDir;
|
|
39231
|
+
const configured = resolveDefault("outputDir");
|
|
39232
|
+
if (configured)
|
|
39233
|
+
return configured;
|
|
39234
|
+
return join2(homedir2(), "Pictures", "imgx");
|
|
39235
|
+
}
|
|
39227
39236
|
function saveImage(image, outputPath, outputDir) {
|
|
39228
39237
|
const ext = MIME_TO_EXT[image.mimeType] || ".png";
|
|
39229
|
-
const filePath = outputPath ? resolve2(outputPath) : resolve2(outputDir
|
|
39238
|
+
const filePath = outputPath ? resolve2(outputPath) : resolve2(fallbackOutputDir(outputDir), `imgx-${randomUUID().slice(0, 8)}${ext}`);
|
|
39230
39239
|
mkdirSync2(dirname(filePath), { recursive: true });
|
|
39231
39240
|
writeFileSync2(filePath, image.data);
|
|
39232
39241
|
return filePath;
|
|
@@ -39243,6 +39252,7 @@ var Capability;
|
|
|
39243
39252
|
Capability2["REFERENCE_IMAGES"] = "REFERENCE_IMAGES";
|
|
39244
39253
|
Capability2["PERSON_CONTROL"] = "PERSON_CONTROL";
|
|
39245
39254
|
Capability2["STYLE_CONTROL"] = "STYLE_CONTROL";
|
|
39255
|
+
Capability2["OUTPUT_FORMAT"] = "OUTPUT_FORMAT";
|
|
39246
39256
|
})(Capability || (Capability = {}));
|
|
39247
39257
|
|
|
39248
39258
|
// build/providers/gemini/capabilities.js
|
|
@@ -39375,7 +39385,8 @@ var OPENAI_PROVIDER_INFO = {
|
|
|
39375
39385
|
Capability.TEXT_TO_IMAGE,
|
|
39376
39386
|
Capability.ASPECT_RATIO,
|
|
39377
39387
|
Capability.IMAGE_EDITING,
|
|
39378
|
-
Capability.MULTIPLE_OUTPUTS
|
|
39388
|
+
Capability.MULTIPLE_OUTPUTS,
|
|
39389
|
+
Capability.OUTPUT_FORMAT
|
|
39379
39390
|
]),
|
|
39380
39391
|
aspectRatios: ["1:1", "3:2", "2:3", "16:9", "9:16", "4:3", "3:4"],
|
|
39381
39392
|
resolutions: ["1K", "2K", "4K"]
|
|
@@ -39456,7 +39467,8 @@ var OpenAIProvider = class {
|
|
|
39456
39467
|
prompt: input.prompt,
|
|
39457
39468
|
n: input.count || 1,
|
|
39458
39469
|
size: mapSize(input.aspectRatio),
|
|
39459
|
-
quality: mapQuality(input.resolution)
|
|
39470
|
+
quality: mapQuality(input.resolution),
|
|
39471
|
+
...input.outputFormat ? { output_format: input.outputFormat } : {}
|
|
39460
39472
|
})
|
|
39461
39473
|
});
|
|
39462
39474
|
const json = await response.json();
|
|
@@ -39467,7 +39479,7 @@ var OpenAIProvider = class {
|
|
|
39467
39479
|
error: json.error?.message || `HTTP ${response.status}`
|
|
39468
39480
|
};
|
|
39469
39481
|
}
|
|
39470
|
-
return this.parseResponse(json);
|
|
39482
|
+
return this.parseResponse(json, input.outputFormat);
|
|
39471
39483
|
} catch (err) {
|
|
39472
39484
|
const msg = err instanceof Error ? err.message : String(err);
|
|
39473
39485
|
return { success: false, images: [], error: msg };
|
|
@@ -39484,7 +39496,8 @@ var OpenAIProvider = class {
|
|
|
39484
39496
|
prompt: input.prompt,
|
|
39485
39497
|
n: String(input.count || 1),
|
|
39486
39498
|
size: mapSize(input.aspectRatio),
|
|
39487
|
-
quality: mapQuality(input.resolution)
|
|
39499
|
+
quality: mapQuality(input.resolution),
|
|
39500
|
+
...input.outputFormat ? { output_format: input.outputFormat } : {}
|
|
39488
39501
|
};
|
|
39489
39502
|
const { body, contentType: ct } = buildMultipart(fields, [
|
|
39490
39503
|
{
|
|
@@ -39511,20 +39524,22 @@ var OpenAIProvider = class {
|
|
|
39511
39524
|
error: json.error?.message || `HTTP ${response.status}`
|
|
39512
39525
|
};
|
|
39513
39526
|
}
|
|
39514
|
-
return this.parseResponse(json);
|
|
39527
|
+
return this.parseResponse(json, input.outputFormat);
|
|
39515
39528
|
} catch (err) {
|
|
39516
39529
|
const msg = err instanceof Error ? err.message : String(err);
|
|
39517
39530
|
return { success: false, images: [], error: msg };
|
|
39518
39531
|
}
|
|
39519
39532
|
}
|
|
39520
|
-
parseResponse(json) {
|
|
39533
|
+
parseResponse(json, outputFormat) {
|
|
39534
|
+
const mimeMap = { png: "image/png", jpeg: "image/jpeg", webp: "image/webp" };
|
|
39535
|
+
const mimeType = mimeMap[outputFormat || "png"] || "image/png";
|
|
39521
39536
|
const images = [];
|
|
39522
39537
|
if (json.data) {
|
|
39523
39538
|
for (const item of json.data) {
|
|
39524
39539
|
if (item.b64_json) {
|
|
39525
39540
|
images.push({
|
|
39526
39541
|
data: Buffer.from(item.b64_json, "base64"),
|
|
39527
|
-
mimeType
|
|
39542
|
+
mimeType
|
|
39528
39543
|
});
|
|
39529
39544
|
}
|
|
39530
39545
|
}
|
|
@@ -39581,7 +39596,8 @@ async function runGenerate(provider, args) {
|
|
|
39581
39596
|
prompt: args.prompt,
|
|
39582
39597
|
aspectRatio: args.aspectRatio,
|
|
39583
39598
|
count: args.count,
|
|
39584
|
-
resolution: args.resolution
|
|
39599
|
+
resolution: args.resolution,
|
|
39600
|
+
outputFormat: args.outputFormat
|
|
39585
39601
|
}, args.model);
|
|
39586
39602
|
if (!result.success || result.images.length === 0) {
|
|
39587
39603
|
fail(result.error || "Generation failed");
|
|
@@ -39609,7 +39625,8 @@ async function runEdit(provider, args) {
|
|
|
39609
39625
|
inputImage: args.inputImage,
|
|
39610
39626
|
prompt: args.prompt,
|
|
39611
39627
|
aspectRatio: args.aspectRatio,
|
|
39612
|
-
resolution: args.resolution
|
|
39628
|
+
resolution: args.resolution,
|
|
39629
|
+
outputFormat: args.outputFormat
|
|
39613
39630
|
}, args.model);
|
|
39614
39631
|
if (!result.success || result.images.length === 0) {
|
|
39615
39632
|
fail(result.error || "Edit failed");
|
|
@@ -39760,7 +39777,7 @@ function showAll() {
|
|
|
39760
39777
|
}
|
|
39761
39778
|
|
|
39762
39779
|
// build/cli/index.js
|
|
39763
|
-
var VERSION2 = "0.
|
|
39780
|
+
var VERSION2 = "0.7.0";
|
|
39764
39781
|
var HELP = `imgx v${VERSION2} \u2014 AI image generation and editing CLI
|
|
39765
39782
|
|
|
39766
39783
|
Commands:
|
|
@@ -39785,6 +39802,7 @@ Options:
|
|
|
39785
39802
|
-a, --aspect-ratio <ratio> Aspect ratio (e.g., 16:9, 1:1)
|
|
39786
39803
|
-n, --count <number> Number of images to generate
|
|
39787
39804
|
-r, --resolution <size> Resolution: 1K, 2K, 4K
|
|
39805
|
+
-f, --format <type> Output format: png, jpeg, webp (OpenAI only)
|
|
39788
39806
|
-m, --model <model> Model name
|
|
39789
39807
|
--provider <name> Provider: gemini, openai (default: gemini)
|
|
39790
39808
|
-d, --output-dir <dir> Output directory
|
|
@@ -39852,6 +39870,7 @@ function main() {
|
|
|
39852
39870
|
"aspect-ratio": { type: "string", short: "a" },
|
|
39853
39871
|
count: { type: "string", short: "n" },
|
|
39854
39872
|
resolution: { type: "string", short: "r" },
|
|
39873
|
+
format: { type: "string", short: "f" },
|
|
39855
39874
|
model: { type: "string", short: "m" },
|
|
39856
39875
|
provider: { type: "string" },
|
|
39857
39876
|
"output-dir": { type: "string", short: "d" },
|
|
@@ -39886,6 +39905,7 @@ function main() {
|
|
|
39886
39905
|
outputDir: values["output-dir"] || resolveDefault("outputDir") || void 0,
|
|
39887
39906
|
aspectRatio: values["aspect-ratio"] || resolveDefault("aspectRatio") || void 0,
|
|
39888
39907
|
resolution: values.resolution || resolveDefault("resolution") || void 0,
|
|
39908
|
+
outputFormat: values.format || void 0,
|
|
39889
39909
|
model,
|
|
39890
39910
|
count: values.count ? parseInt(values.count, 10) : void 0
|
|
39891
39911
|
};
|
package/dist/mcp.bundle.js
CHANGED
|
@@ -52131,6 +52131,28 @@ function resolveApiKey(providerName) {
|
|
|
52131
52131
|
const config2 = loadConfig();
|
|
52132
52132
|
return config2.providers?.[providerName]?.apiKey;
|
|
52133
52133
|
}
|
|
52134
|
+
function loadProjectConfig() {
|
|
52135
|
+
try {
|
|
52136
|
+
const raw = readFileSync(resolve(".imgxrc"), "utf-8");
|
|
52137
|
+
return JSON.parse(raw);
|
|
52138
|
+
} catch {
|
|
52139
|
+
return {};
|
|
52140
|
+
}
|
|
52141
|
+
}
|
|
52142
|
+
function resolveDefault(key) {
|
|
52143
|
+
const envMap = {
|
|
52144
|
+
provider: process.env.IMGX_PROVIDER,
|
|
52145
|
+
model: process.env.IMGX_MODEL,
|
|
52146
|
+
outputDir: process.env.IMGX_OUTPUT_DIR
|
|
52147
|
+
};
|
|
52148
|
+
if (envMap[key])
|
|
52149
|
+
return envMap[key];
|
|
52150
|
+
const project = loadProjectConfig();
|
|
52151
|
+
if (project.defaults?.[key])
|
|
52152
|
+
return project.defaults[key];
|
|
52153
|
+
const config2 = loadConfig();
|
|
52154
|
+
return config2.defaults?.[key];
|
|
52155
|
+
}
|
|
52134
52156
|
function saveLastOutput(filePaths) {
|
|
52135
52157
|
const dir = configDir();
|
|
52136
52158
|
mkdirSync(dir, { recursive: true });
|
|
@@ -69226,8 +69248,9 @@ function getApiKeyFromEnv() {
|
|
|
69226
69248
|
|
|
69227
69249
|
// build/core/storage.js
|
|
69228
69250
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "node:fs";
|
|
69229
|
-
import { dirname, resolve as resolve2 } from "node:path";
|
|
69251
|
+
import { dirname, join as join2, resolve as resolve2 } from "node:path";
|
|
69230
69252
|
import { randomUUID } from "node:crypto";
|
|
69253
|
+
import { homedir as homedir2 } from "node:os";
|
|
69231
69254
|
var MIME_TO_EXT = {
|
|
69232
69255
|
"image/png": ".png",
|
|
69233
69256
|
"image/jpeg": ".jpg",
|
|
@@ -69240,9 +69263,17 @@ function readImageAsBase64(filePath) {
|
|
|
69240
69263
|
const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "webp" ? "image/webp" : "image/png";
|
|
69241
69264
|
return { data: buffer.toString("base64"), mimeType };
|
|
69242
69265
|
}
|
|
69266
|
+
function fallbackOutputDir(outputDir) {
|
|
69267
|
+
if (outputDir)
|
|
69268
|
+
return outputDir;
|
|
69269
|
+
const configured = resolveDefault("outputDir");
|
|
69270
|
+
if (configured)
|
|
69271
|
+
return configured;
|
|
69272
|
+
return join2(homedir2(), "Pictures", "imgx");
|
|
69273
|
+
}
|
|
69243
69274
|
function saveImage(image, outputPath, outputDir) {
|
|
69244
69275
|
const ext = MIME_TO_EXT[image.mimeType] || ".png";
|
|
69245
|
-
const filePath = outputPath ? resolve2(outputPath) : resolve2(outputDir
|
|
69276
|
+
const filePath = outputPath ? resolve2(outputPath) : resolve2(fallbackOutputDir(outputDir), `imgx-${randomUUID().slice(0, 8)}${ext}`);
|
|
69246
69277
|
mkdirSync2(dirname(filePath), { recursive: true });
|
|
69247
69278
|
writeFileSync2(filePath, image.data);
|
|
69248
69279
|
return filePath;
|
|
@@ -69259,6 +69290,7 @@ var Capability;
|
|
|
69259
69290
|
Capability2["REFERENCE_IMAGES"] = "REFERENCE_IMAGES";
|
|
69260
69291
|
Capability2["PERSON_CONTROL"] = "PERSON_CONTROL";
|
|
69261
69292
|
Capability2["STYLE_CONTROL"] = "STYLE_CONTROL";
|
|
69293
|
+
Capability2["OUTPUT_FORMAT"] = "OUTPUT_FORMAT";
|
|
69262
69294
|
})(Capability || (Capability = {}));
|
|
69263
69295
|
|
|
69264
69296
|
// build/providers/gemini/capabilities.js
|
|
@@ -69391,7 +69423,8 @@ var OPENAI_PROVIDER_INFO = {
|
|
|
69391
69423
|
Capability.TEXT_TO_IMAGE,
|
|
69392
69424
|
Capability.ASPECT_RATIO,
|
|
69393
69425
|
Capability.IMAGE_EDITING,
|
|
69394
|
-
Capability.MULTIPLE_OUTPUTS
|
|
69426
|
+
Capability.MULTIPLE_OUTPUTS,
|
|
69427
|
+
Capability.OUTPUT_FORMAT
|
|
69395
69428
|
]),
|
|
69396
69429
|
aspectRatios: ["1:1", "3:2", "2:3", "16:9", "9:16", "4:3", "3:4"],
|
|
69397
69430
|
resolutions: ["1K", "2K", "4K"]
|
|
@@ -69472,7 +69505,8 @@ var OpenAIProvider = class {
|
|
|
69472
69505
|
prompt: input.prompt,
|
|
69473
69506
|
n: input.count || 1,
|
|
69474
69507
|
size: mapSize(input.aspectRatio),
|
|
69475
|
-
quality: mapQuality(input.resolution)
|
|
69508
|
+
quality: mapQuality(input.resolution),
|
|
69509
|
+
...input.outputFormat ? { output_format: input.outputFormat } : {}
|
|
69476
69510
|
})
|
|
69477
69511
|
});
|
|
69478
69512
|
const json2 = await response.json();
|
|
@@ -69483,7 +69517,7 @@ var OpenAIProvider = class {
|
|
|
69483
69517
|
error: json2.error?.message || `HTTP ${response.status}`
|
|
69484
69518
|
};
|
|
69485
69519
|
}
|
|
69486
|
-
return this.parseResponse(json2);
|
|
69520
|
+
return this.parseResponse(json2, input.outputFormat);
|
|
69487
69521
|
} catch (err) {
|
|
69488
69522
|
const msg = err instanceof Error ? err.message : String(err);
|
|
69489
69523
|
return { success: false, images: [], error: msg };
|
|
@@ -69500,7 +69534,8 @@ var OpenAIProvider = class {
|
|
|
69500
69534
|
prompt: input.prompt,
|
|
69501
69535
|
n: String(input.count || 1),
|
|
69502
69536
|
size: mapSize(input.aspectRatio),
|
|
69503
|
-
quality: mapQuality(input.resolution)
|
|
69537
|
+
quality: mapQuality(input.resolution),
|
|
69538
|
+
...input.outputFormat ? { output_format: input.outputFormat } : {}
|
|
69504
69539
|
};
|
|
69505
69540
|
const { body, contentType: ct } = buildMultipart(fields, [
|
|
69506
69541
|
{
|
|
@@ -69527,20 +69562,22 @@ var OpenAIProvider = class {
|
|
|
69527
69562
|
error: json2.error?.message || `HTTP ${response.status}`
|
|
69528
69563
|
};
|
|
69529
69564
|
}
|
|
69530
|
-
return this.parseResponse(json2);
|
|
69565
|
+
return this.parseResponse(json2, input.outputFormat);
|
|
69531
69566
|
} catch (err) {
|
|
69532
69567
|
const msg = err instanceof Error ? err.message : String(err);
|
|
69533
69568
|
return { success: false, images: [], error: msg };
|
|
69534
69569
|
}
|
|
69535
69570
|
}
|
|
69536
|
-
parseResponse(json2) {
|
|
69571
|
+
parseResponse(json2, outputFormat) {
|
|
69572
|
+
const mimeMap = { png: "image/png", jpeg: "image/jpeg", webp: "image/webp" };
|
|
69573
|
+
const mimeType = mimeMap[outputFormat || "png"] || "image/png";
|
|
69537
69574
|
const images = [];
|
|
69538
69575
|
if (json2.data) {
|
|
69539
69576
|
for (const item of json2.data) {
|
|
69540
69577
|
if (item.b64_json) {
|
|
69541
69578
|
images.push({
|
|
69542
69579
|
data: Buffer.from(item.b64_json, "base64"),
|
|
69543
|
-
mimeType
|
|
69580
|
+
mimeType
|
|
69544
69581
|
});
|
|
69545
69582
|
}
|
|
69546
69583
|
}
|
|
@@ -69561,9 +69598,17 @@ function initOpenAI() {
|
|
|
69561
69598
|
}
|
|
69562
69599
|
|
|
69563
69600
|
// build/mcp/server.js
|
|
69601
|
+
function buildImageContent(images, paths, extra) {
|
|
69602
|
+
const content = [];
|
|
69603
|
+
for (const img of images) {
|
|
69604
|
+
content.push({ type: "image", data: img.data.toString("base64"), mimeType: img.mimeType });
|
|
69605
|
+
}
|
|
69606
|
+
content.push({ type: "text", text: JSON.stringify({ success: true, filePaths: paths, ...extra }) });
|
|
69607
|
+
return content;
|
|
69608
|
+
}
|
|
69564
69609
|
var server = new McpServer({
|
|
69565
69610
|
name: "imgx",
|
|
69566
|
-
version: "0.
|
|
69611
|
+
version: "0.7.0"
|
|
69567
69612
|
});
|
|
69568
69613
|
initGemini();
|
|
69569
69614
|
initOpenAI();
|
|
@@ -69583,6 +69628,7 @@ server.tool("generate_image", "Generate an image from a text prompt", {
|
|
|
69583
69628
|
aspect_ratio: external_exports3.enum(["1:1", "2:3", "3:2", "3:4", "4:3", "9:16", "16:9"]).optional().describe("Aspect ratio"),
|
|
69584
69629
|
resolution: external_exports3.enum(["1K", "2K", "4K"]).optional().describe("Output resolution"),
|
|
69585
69630
|
count: external_exports3.number().int().min(1).max(4).optional().describe("Number of images"),
|
|
69631
|
+
output_format: external_exports3.enum(["png", "jpeg", "webp"]).optional().describe("Output format"),
|
|
69586
69632
|
model: external_exports3.string().optional().describe("Model name"),
|
|
69587
69633
|
provider: external_exports3.string().optional().describe("Provider name")
|
|
69588
69634
|
}, async (args) => {
|
|
@@ -69592,7 +69638,8 @@ server.tool("generate_image", "Generate an image from a text prompt", {
|
|
|
69592
69638
|
prompt: args.prompt,
|
|
69593
69639
|
aspectRatio: args.aspect_ratio,
|
|
69594
69640
|
resolution: args.resolution,
|
|
69595
|
-
count: args.count
|
|
69641
|
+
count: args.count,
|
|
69642
|
+
outputFormat: args.output_format
|
|
69596
69643
|
};
|
|
69597
69644
|
const result = await prov.generate(input, args.model);
|
|
69598
69645
|
if (!result.success || result.images.length === 0) {
|
|
@@ -69605,9 +69652,7 @@ server.tool("generate_image", "Generate an image from a text prompt", {
|
|
|
69605
69652
|
paths.push(saved);
|
|
69606
69653
|
}
|
|
69607
69654
|
saveLastOutput(paths);
|
|
69608
|
-
return {
|
|
69609
|
-
content: [{ type: "text", text: JSON.stringify({ success: true, filePaths: paths }) }]
|
|
69610
|
-
};
|
|
69655
|
+
return { content: buildImageContent(result.images, paths) };
|
|
69611
69656
|
} catch (err) {
|
|
69612
69657
|
const msg = err instanceof Error ? err.message : String(err);
|
|
69613
69658
|
return { content: [{ type: "text", text: `Error: ${msg}` }] };
|
|
@@ -69620,6 +69665,7 @@ server.tool("edit_image", "Edit an existing image with text instructions", {
|
|
|
69620
69665
|
output_dir: external_exports3.string().optional().describe("Output directory"),
|
|
69621
69666
|
aspect_ratio: external_exports3.enum(["1:1", "2:3", "3:2", "3:4", "4:3", "9:16", "16:9"]).optional().describe("Aspect ratio"),
|
|
69622
69667
|
resolution: external_exports3.enum(["1K", "2K", "4K"]).optional().describe("Output resolution"),
|
|
69668
|
+
output_format: external_exports3.enum(["png", "jpeg", "webp"]).optional().describe("Output format"),
|
|
69623
69669
|
model: external_exports3.string().optional().describe("Model name"),
|
|
69624
69670
|
provider: external_exports3.string().optional().describe("Provider name")
|
|
69625
69671
|
}, async (args) => {
|
|
@@ -69634,7 +69680,8 @@ server.tool("edit_image", "Edit an existing image with text instructions", {
|
|
|
69634
69680
|
prompt: args.prompt,
|
|
69635
69681
|
inputImage: args.input,
|
|
69636
69682
|
aspectRatio: args.aspect_ratio,
|
|
69637
|
-
resolution: args.resolution
|
|
69683
|
+
resolution: args.resolution,
|
|
69684
|
+
outputFormat: args.output_format
|
|
69638
69685
|
};
|
|
69639
69686
|
const result = await prov.edit(input, args.model);
|
|
69640
69687
|
if (!result.success || result.images.length === 0) {
|
|
@@ -69642,9 +69689,7 @@ server.tool("edit_image", "Edit an existing image with text instructions", {
|
|
|
69642
69689
|
}
|
|
69643
69690
|
const saved = saveImage(result.images[0], args.output, args.output_dir);
|
|
69644
69691
|
saveLastOutput([saved]);
|
|
69645
|
-
return {
|
|
69646
|
-
content: [{ type: "text", text: JSON.stringify({ success: true, filePaths: [saved] }) }]
|
|
69647
|
-
};
|
|
69692
|
+
return { content: buildImageContent(result.images, [saved]) };
|
|
69648
69693
|
} catch (err) {
|
|
69649
69694
|
const msg = err instanceof Error ? err.message : String(err);
|
|
69650
69695
|
return { content: [{ type: "text", text: `Error: ${msg}` }] };
|
|
@@ -69656,6 +69701,7 @@ server.tool("edit_last", "Edit the last generated/edited image with new text ins
|
|
|
69656
69701
|
output_dir: external_exports3.string().optional().describe("Output directory"),
|
|
69657
69702
|
aspect_ratio: external_exports3.enum(["1:1", "2:3", "3:2", "3:4", "4:3", "9:16", "16:9"]).optional().describe("Aspect ratio"),
|
|
69658
69703
|
resolution: external_exports3.enum(["1K", "2K", "4K"]).optional().describe("Output resolution"),
|
|
69704
|
+
output_format: external_exports3.enum(["png", "jpeg", "webp"]).optional().describe("Output format"),
|
|
69659
69705
|
model: external_exports3.string().optional().describe("Model name"),
|
|
69660
69706
|
provider: external_exports3.string().optional().describe("Provider name")
|
|
69661
69707
|
}, async (args) => {
|
|
@@ -69676,7 +69722,8 @@ server.tool("edit_last", "Edit the last generated/edited image with new text ins
|
|
|
69676
69722
|
prompt: args.prompt,
|
|
69677
69723
|
inputImage: lastPaths[0],
|
|
69678
69724
|
aspectRatio: args.aspect_ratio,
|
|
69679
|
-
resolution: args.resolution
|
|
69725
|
+
resolution: args.resolution,
|
|
69726
|
+
outputFormat: args.output_format
|
|
69680
69727
|
};
|
|
69681
69728
|
const result = await prov.edit(input, args.model);
|
|
69682
69729
|
if (!result.success || result.images.length === 0) {
|
|
@@ -69684,9 +69731,7 @@ server.tool("edit_last", "Edit the last generated/edited image with new text ins
|
|
|
69684
69731
|
}
|
|
69685
69732
|
const saved = saveImage(result.images[0], args.output, args.output_dir);
|
|
69686
69733
|
saveLastOutput([saved]);
|
|
69687
|
-
return {
|
|
69688
|
-
content: [{ type: "text", text: JSON.stringify({ success: true, filePaths: [saved], inputUsed: lastPaths[0] }) }]
|
|
69689
|
-
};
|
|
69734
|
+
return { content: buildImageContent(result.images, [saved], { inputUsed: lastPaths[0] }) };
|
|
69690
69735
|
} catch (err) {
|
|
69691
69736
|
const msg = err instanceof Error ? err.message : String(err);
|
|
69692
69737
|
return { content: [{ type: "text", text: `Error: ${msg}` }] };
|