@flatkey-ai/cli 0.3.3 → 0.3.5

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.
Files changed (3) hide show
  1. package/README.md +4 -0
  2. package/package.json +1 -1
  3. package/src/api.js +24 -6
package/README.md CHANGED
@@ -123,6 +123,10 @@ flatkey image generate \
123
123
  -o cover.png
124
124
  ```
125
125
 
126
+ Reference images (`--file` / `--image-url`) are sent as an image edit: gpt
127
+ models go through `/v1/images/edits`, Gemini models receive them as native
128
+ `fileData` parts, so the input image is preserved instead of being ignored.
129
+
126
130
  Upload a local image and get a temporary signed URL:
127
131
 
128
132
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flatkey-ai/cli",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "description": "Flatkey media generation CLI for image, video, audio, credits, status, and models.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/api.js CHANGED
@@ -44,12 +44,10 @@ export function planImageRequest(options) {
44
44
  if (mediaInputs.some((entry) => entry.kind === "video")) {
45
45
  throw new Error("Image models only accept image inputs.");
46
46
  }
47
- const imageUrls = mediaInputs
48
- .map((entry) => entry.url);
49
47
  if (model.startsWith("gpt")) {
50
48
  const responseFormat = options.response_format ?? options.responseFormat ?? "url";
51
49
  const tempUrl = options.temp_url ?? options.tempUrl;
52
- return planJsonPost(options, "/v1/images/generations", cleanObject({
50
+ const payload = cleanObject({
53
51
  model,
54
52
  prompt: options.prompt,
55
53
  size: options.size,
@@ -57,8 +55,16 @@ export function planImageRequest(options) {
57
55
  quality: options.quality,
58
56
  response_format: responseFormat,
59
57
  temp_url: tempUrl ?? true,
60
- images: imageUrls.length > 0 ? imageUrls : undefined,
61
- }));
58
+ });
59
+ if (mediaInputs.length === 0) {
60
+ return planJsonPost(options, "/v1/images/generations", payload);
61
+ }
62
+ // Reference images are only honoured on the edits endpoint; generations
63
+ // silently ignores them and runs a plain text-to-image.
64
+ return planJsonPost(options, "/v1/images/edits", {
65
+ ...payload,
66
+ images: mediaInputs.map((entry) => ({ image_url: entry.url })),
67
+ });
62
68
  }
63
69
 
64
70
  const path = `/v1beta/models/${encodeURIComponent(model)}:generateContent?key=${encodeURIComponent(options.apiKey)}`;
@@ -72,7 +78,10 @@ export function planImageRequest(options) {
72
78
  contents: [{
73
79
  parts: [
74
80
  { text: options.prompt },
75
- ...imageUrls.map((url) => ({ type: "image_url", image_url: { url } })),
81
+ // Gemini native parts: an OpenAI-style image_url part is ignored upstream.
82
+ ...mediaInputs.map((entry) => ({
83
+ fileData: { mimeType: imageMimeType(entry), fileUri: entry.url },
84
+ })),
76
85
  ],
77
86
  }],
78
87
  temp_url: true,
@@ -80,6 +89,15 @@ export function planImageRequest(options) {
80
89
  });
81
90
  }
82
91
 
92
+ function imageMimeType(entry) {
93
+ if (entry.mime) return entry.mime;
94
+ const path = String(entry.url ?? "").split(/[?#]/)[0].toLowerCase();
95
+ if (path.endsWith(".jpg") || path.endsWith(".jpeg")) return "image/jpeg";
96
+ if (path.endsWith(".webp")) return "image/webp";
97
+ if (path.endsWith(".gif")) return "image/gif";
98
+ return "image/png";
99
+ }
100
+
83
101
  export function generateVideo(options) {
84
102
  return requestJsonFromPlan(options, planVideoRequest(options));
85
103
  }