makaron-cli 0.13.9 → 0.14.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "makaron-cli",
3
- "version": "0.13.9",
3
+ "version": "0.14.0",
4
4
  "description": "Give Claude Code a creative agent. Pass complete creative requests and source media to Makaron Chat.",
5
5
  "author": {
6
6
  "name": "Versa AI",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "makaron-cli",
3
- "version": "0.13.9",
3
+ "version": "0.14.0",
4
4
  "description": "Give Codex a creative agent. Pass complete creative requests and source media to Makaron Chat.",
5
5
  "author": {
6
6
  "name": "Versa AI",
package/README.md CHANGED
@@ -170,26 +170,37 @@ npx makaron-cli project media <projectId> --json
170
170
 
171
171
  This is project-scoped. `responses get <runId> --pick output` only returns artifacts from one run; `project media` returns the whole project timeline: original uploads, references, generated images, video snapshots, and editable compositions.
172
172
 
173
- External source ranges can be added without uploading either the original video or a derivative clip:
173
+ Typed external images and video ranges can be added without uploading the original media:
174
174
 
175
175
  ```bash
176
176
  npx makaron-cli project media add <projectId> \
177
+ --type image \
178
+ --source-url "https://cdn.example.com/product.jpg" \
179
+ --description "Hero product image"
180
+
181
+ npx makaron-cli project media add <projectId> \
182
+ --type video \
177
183
  --source-url "https://cdn.example.com/source.mp4" \
178
184
  --start 12.5 --end 19 \
179
185
  --description "Racket frame molding"
180
186
 
181
187
  # Batch form: a JSON array or {"clips": [...]}
182
- npx makaron-cli project media add <projectId> --input ranges.json --json
188
+ npx makaron-cli project media add <projectId> --input media.json --json
183
189
  ```
184
190
 
185
- The returned `<<<media_N>>>` is immediately usable by a later Agent run. Its range remains non-destructive metadata; Remotion must trim the original URL to these exact source bounds.
191
+ Every item must declare `type` as `image` or `video`. Images contain
192
+ `source_url + type + description` and do not have a time range. Videos contain
193
+ `source_url + type + start + end + description`; their ranges remain
194
+ non-destructive metadata, and Remotion must trim the original URL to those exact
195
+ source bounds. The returned `<<<media_N>>>` is immediately usable by a later
196
+ Agent run.
186
197
  Use `description` as the provider-neutral media-understanding field. Put any
187
198
  already-known summary, editorial purpose, concrete scene evidence, confidence,
188
199
  and limitations there. Makaron exposes the full description in Media List
189
200
  context so the Agent can edit from it without repeating image/video analysis
190
201
  unless a required detail is missing or uncertain.
191
202
 
192
- For an agent-to-agent handoff, create the project, import the external ranges,
203
+ For an agent-to-agent handoff, create the project, import the typed external media,
193
204
  and start the Agent in one command:
194
205
 
195
206
  ```bash
@@ -200,9 +211,9 @@ npx makaron-cli chat --project auto \
200
211
  ```
201
212
 
202
213
  The manifest is a JSON array or `{ "title": "...", "clips": [...] }`. Every
203
- clip contains exactly `source_url`, `start`, `end`, and `description`; the time
204
- values are seconds and array order is edit order. It is validated before project
205
- creation and supports up to 20 clips for one Makaron task. Batch planning remains
214
+ item declares `type`. Images omit `start` and `end`; videos require both values
215
+ in seconds. Array order is edit order. It is validated before project creation
216
+ and supports up to 20 media items for one Makaron task. Batch planning remains
206
217
  the upstream orchestrator's responsibility: convert each plan into one manifest
207
218
  and start one independent Makaron task.
208
219
 
@@ -341,9 +352,12 @@ npx makaron-cli edit --image photo.jpg --ref style.jpg "match this style"
341
352
 
342
353
  # Output to file
343
354
  npx makaron-cli edit --image photo.jpg --out result.jpg "make it dramatic"
355
+
356
+ # Strict transparent PNG/WebP output through GPT Image 2 (fails rather than returning opaque)
357
+ npx makaron-cli edit --image-model openai --background transparent --out sticker.png "a magenta star sticker"
344
358
  ```
345
359
 
346
- Options: `--image`, `--image-model gemini|gemini-lite|qwen|openai|pony|wai`, `--ref <file>` (up to 3), `--aspect <ratio>`, `--out <path>`
360
+ Options: `--image`, `--image-model gemini|gemini-lite|qwen|openai|pony|wai`, `--ref <file>` (up to 3), `--aspect <ratio>`, `--background auto|opaque|transparent`, `--out <path>`. Transparent output routes strictly to GPT Image 2 and is returned only when the provider supplies real PNG/WebP alpha.
347
361
 
348
362
  ### `video` — Standalone video tools (no project timeline)
349
363
 
package/bin/makaron.mjs CHANGED
@@ -174,25 +174,19 @@ function readJsonInput(filePath) {
174
174
  return JSON.parse(raw);
175
175
  }
176
176
 
177
- const MAX_MEDIA_MANIFEST_RANGES = 20;
177
+ const MAX_MEDIA_MANIFEST_ITEMS = 20;
178
178
 
179
179
  function normalizeMediaManifest(input) {
180
180
  const manifest = Array.isArray(input) ? { clips: input } : input;
181
181
  if (!manifest || typeof manifest !== 'object') {
182
- throw new Error('Media manifest must be a JSON object or an array of source ranges.');
183
- }
184
- const rawRanges = Array.isArray(manifest.clips)
185
- ? manifest.clips
186
- : Array.isArray(manifest.source_ranges)
187
- ? manifest.source_ranges
188
- : Array.isArray(manifest.sourceRanges)
189
- ? manifest.sourceRanges
190
- : null;
182
+ throw new Error('Media manifest must be a JSON object or an array of media items.');
183
+ }
184
+ const rawRanges = Array.isArray(manifest.clips) ? manifest.clips : null;
191
185
  if (!rawRanges?.length) {
192
186
  throw new Error('Media manifest must contain a non-empty clips array.');
193
187
  }
194
- if (rawRanges.length > MAX_MEDIA_MANIFEST_RANGES) {
195
- throw new Error(`Media manifest supports at most ${MAX_MEDIA_MANIFEST_RANGES} source ranges per Makaron task.`);
188
+ if (rawRanges.length > MAX_MEDIA_MANIFEST_ITEMS) {
189
+ throw new Error(`Media manifest supports at most ${MAX_MEDIA_MANIFEST_ITEMS} media items per Makaron task.`);
196
190
  }
197
191
 
198
192
  const sourceRanges = rawRanges.map((raw, index) => {
@@ -207,8 +201,19 @@ function normalizeMediaManifest(input) {
207
201
  if (!['http:', 'https:'].includes(parsed.protocol)) {
208
202
  throw new Error(`clips[${index}].source_url must use HTTP or HTTPS.`);
209
203
  }
210
- const start = Number(raw.start ?? raw.start_sec);
211
- const end = Number(raw.end ?? raw.end_sec);
204
+ const declaredType = raw.type;
205
+ if (declaredType !== 'image' && declaredType !== 'video') {
206
+ throw new Error(`clips[${index}].type must be image or video.`);
207
+ }
208
+ const description = typeof raw.description === 'string' ? raw.description.trim() : '';
209
+ if (declaredType === 'image') {
210
+ if (raw.start !== undefined || raw.end !== undefined || raw.start_sec !== undefined || raw.end_sec !== undefined) {
211
+ throw new Error(`clips[${index}] image items must not include start or end.`);
212
+ }
213
+ return { source_url: sourceUrl, type: 'image', description };
214
+ }
215
+ const start = Number(raw.start);
216
+ const end = Number(raw.end);
212
217
  if (!Number.isFinite(start) || start < 0) {
213
218
  throw new Error(`clips[${index}].start must be a finite number >= 0.`);
214
219
  }
@@ -217,9 +222,10 @@ function normalizeMediaManifest(input) {
217
222
  }
218
223
  return {
219
224
  source_url: sourceUrl,
225
+ type: declaredType,
220
226
  start,
221
227
  end,
222
- description: typeof raw.description === 'string' ? raw.description.trim() : '',
228
+ description,
223
229
  };
224
230
  });
225
231
 
@@ -447,7 +453,7 @@ Options:
447
453
  --image <file|url> Attach a reference image or screenshot. Repeatable.
448
454
  --video <file|url> Attach a video to the project timeline. Repeatable.
449
455
  --audio <file|url> Attach a song, beat, or voice reference. MP3/WAV, repeatable.
450
- --media-manifest <file|-> Import source_url + start + end + description clips before this run.
456
+ --media-manifest <file|-> Import typed image/video media before this run.
451
457
  --skill <id|label|name> Use an installed skill or auto-install a matched marketplace skill.
452
458
  --agent-model <id> Agent LLM only: auto, gpt-5.6-terra, gpt-5.6-sol,
453
459
  gpt-5.6-luna, grok-4.5, or deepseek-v4-pro.
@@ -1031,7 +1037,10 @@ async function addProjectMediaSourceRanges(baseUrl, headers, projectId, ranges,
1031
1037
  }
1032
1038
  if (!opts.silent) {
1033
1039
  for (const item of data.media || []) {
1034
- console.log(`${item.ref} ${item.source_url} ${formatSeconds(item.start_sec)}-${formatSeconds(item.end_sec)}s${item.created ? '' : ' (existing)'}`);
1040
+ const range = item.type === 'video' && Number.isFinite(item.start_sec) && Number.isFinite(item.end_sec)
1041
+ ? ` ${formatSeconds(item.start_sec)}-${formatSeconds(item.end_sec)}s`
1042
+ : '';
1043
+ console.log(`${item.ref} [${item.type || 'media'}] ${item.source_url}${range}${item.created ? '' : ' (existing)'}`);
1035
1044
  }
1036
1045
  }
1037
1046
  return data;
@@ -1787,7 +1796,22 @@ function saveMcpImage(result, outputPath) {
1787
1796
  const imageBlock = content.find(c => c.type === 'image');
1788
1797
  if (textBlock) process.stderr.write(`${textBlock.text}\n`);
1789
1798
  if (imageBlock) {
1790
- const out = outputPath || `makaron-output-${Date.now()}.jpg`;
1799
+ const extension = imageBlock.mimeType === 'image/png'
1800
+ ? 'png'
1801
+ : imageBlock.mimeType === 'image/webp'
1802
+ ? 'webp'
1803
+ : 'jpg';
1804
+ let out = outputPath || `makaron-output-${Date.now()}.${extension}`;
1805
+ if (outputPath) {
1806
+ const requestedExtension = path.extname(outputPath).slice(1).toLowerCase();
1807
+ const compatible = extension === 'jpg'
1808
+ ? ['jpg', 'jpeg'].includes(requestedExtension)
1809
+ : requestedExtension === extension;
1810
+ if (!compatible) {
1811
+ out = `${outputPath.slice(0, outputPath.length - path.extname(outputPath).length)}.${extension}`;
1812
+ process.stderr.write(`Output is ${imageBlock.mimeType}; saving as ${out} so bytes and filename agree.\n`);
1813
+ }
1814
+ }
1791
1815
  fs.writeFileSync(out, Buffer.from(imageBlock.data, 'base64'));
1792
1816
  console.log(out);
1793
1817
  return out;
@@ -1838,8 +1862,10 @@ Commands:
1838
1862
  credits Show current credit balance
1839
1863
  list (ls) List all projects
1840
1864
  project media <projectId> --json List timeline media for a project
1841
- project media add <projectId> --source-url <url> --start <n> --end <n>
1842
- Add an external source range without uploading video
1865
+ project media add <projectId> --type image --source-url <url>
1866
+ Add an external image without uploading it
1867
+ project media add <projectId> --type video --source-url <url> --start <n> --end <n>
1868
+ Add an external video range without uploading it
1843
1869
  create --image <file> Create project from local image
1844
1870
  create --image-url <url> Create project from URL
1845
1871
  create --title "name" Create empty project (text-to-image)
@@ -1851,7 +1877,7 @@ Commands:
1851
1877
  chat --project <id> --video <file> Attach video to conversation
1852
1878
  chat --project <id> --audio <file> Attach song/beat/voice reference
1853
1879
  chat --project auto --media-manifest <file> "message"
1854
- Create, import external ranges, and run Agent
1880
+ Create, import typed external media, and run Agent
1855
1881
  chat --project <id> -b "message" Background: submit and print runId
1856
1882
  chat --project <id> --stream "msg" Legacy: stream SSE in real-time
1857
1883
  chat --project <id> --json "msg" Output structured JSON result
@@ -1961,11 +1987,12 @@ function printHelp(topic, subtopic) {
1961
1987
  console.log('Usage: makaron credits [--json]');
1962
1988
  } else if (topic === 'project' || topic === 'projects') {
1963
1989
  if (subtopic === 'media') console.log(`Usage: makaron project media <projectId> [--json]
1964
- makaron project media add <projectId> --source-url <url> --start <n> --end <n> [--description <text>] [--json]
1965
- makaron project media add <projectId> --input <ranges.json> [--json]`);
1990
+ makaron project media add <projectId> --type image --source-url <url> [--description <text>] [--json]
1991
+ makaron project media add <projectId> --type video --source-url <url> --start <n> --end <n> [--description <text>] [--json]
1992
+ makaron project media add <projectId> --input <media.json> [--json]`);
1966
1993
  else console.log(`Project commands:
1967
1994
  project media <projectId> --json List timeline media for a project
1968
- project media add <projectId> ... Add external source_url + start + end media
1995
+ project media add <projectId> ... Add typed external image/video media
1969
1996
  `);
1970
1997
  } else if (topic === 'abort') {
1971
1998
  console.log('Usage: makaron abort <runId>');
@@ -2005,7 +2032,7 @@ Not sure which built-in skill to use? Start with:
2005
2032
  composition status <jobId> [--wait] [--json]
2006
2033
  `);
2007
2034
  } else if (topic === 'edit') {
2008
- console.log('Usage: makaron edit [--image <file|url>] [--image-model gemini|gemini-lite|qwen|openai|pony|wai] [--skill enhance|creative|wild|captions] [--ref <file>] [--out <file>] "prompt"');
2035
+ console.log('Usage: makaron edit [--image <file|url>] [--image-model gemini|gemini-lite|qwen|openai|pony|wai] [--skill enhance|creative|wild|captions] [--ref <file>] [--aspect <ratio>] [--background auto|opaque|transparent] [--out <file>] "prompt"');
2009
2036
  } else if (topic === 'analyze') {
2010
2037
  console.log('Usage: makaron analyze --video <file|url> ["question"]');
2011
2038
  } else if (topic === 'video') {
@@ -2269,12 +2296,12 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2269
2296
  );
2270
2297
  importedManifestMedia = imported.media || [];
2271
2298
  if (importedManifestMedia.length !== mediaManifest.sourceRanges.length) {
2272
- process.stderr.write(`❌ Imported ${importedManifestMedia.length}/${mediaManifest.sourceRanges.length} source range(s); aborting run.\n`);
2299
+ process.stderr.write(`❌ Imported ${importedManifestMedia.length}/${mediaManifest.sourceRanges.length} media item(s); aborting run.\n`);
2273
2300
  process.exit(1);
2274
2301
  }
2275
2302
  uploadedTurnMediaCount += importedManifestMedia.length;
2276
- uploadedTurnVideoCount += importedManifestMedia.length;
2277
- process.stderr.write(`📎 Imported ${importedManifestMedia.length} external source range(s) from media manifest\n`);
2303
+ uploadedTurnVideoCount += importedManifestMedia.filter(item => item.type === 'video').length;
2304
+ process.stderr.write(`📎 Imported ${importedManifestMedia.length} typed external media item(s) from media manifest\n`);
2278
2305
  }
2279
2306
  // Upload additional images to existing project
2280
2307
  if (imageFileList.length > 0 || imageUrlList.length > 0) {
@@ -2432,9 +2459,12 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2432
2459
  ...(importedManifestMedia.length ? {
2433
2460
  importedMedia: importedManifestMedia.map(item => ({
2434
2461
  ref: item.ref,
2462
+ type: item.type,
2435
2463
  source_url: item.source_url,
2436
- start_sec: item.start_sec,
2437
- end_sec: item.end_sec,
2464
+ ...(item.type === 'video' ? {
2465
+ start_sec: item.start_sec,
2466
+ end_sec: item.end_sec,
2467
+ } : {}),
2438
2468
  })),
2439
2469
  } : {}),
2440
2470
  }));
@@ -2699,7 +2729,7 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2699
2729
  const jsonOutput = args.includes('--json');
2700
2730
  if (args[2] === 'add') {
2701
2731
  const projectId = args[3];
2702
- if (!projectId) { console.error('Usage: makaron project media add <projectId> --source-url <url> --start <n> --end <n>'); process.exit(1); }
2732
+ if (!projectId) { console.error('Usage: makaron project media add <projectId> --type <image|video> --source-url <url> [--start <n> --end <n>]'); process.exit(1); }
2703
2733
  const readOption = (name) => {
2704
2734
  const index = args.indexOf(name);
2705
2735
  return index >= 0 ? args[index + 1] : undefined;
@@ -2707,26 +2737,25 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2707
2737
  const inputPath = readOption('--input');
2708
2738
  let ranges;
2709
2739
  if (inputPath) {
2710
- const input = readJsonInput(inputPath);
2711
- ranges = Array.isArray(input) ? input : input.clips || input.source_ranges || input.sourceRanges;
2740
+ const normalized = normalizeMediaManifest(readJsonInput(inputPath));
2741
+ ranges = normalized.sourceRanges;
2712
2742
  } else {
2713
2743
  const sourceUrl = readOption('--source-url');
2714
- const start = Number(readOption('--start') ?? readOption('--start-sec'));
2715
- const end = Number(readOption('--end') ?? readOption('--end-sec'));
2716
- if (!sourceUrl || !Number.isFinite(start) || !Number.isFinite(end)) {
2717
- console.error('Provide --source-url, --start, and --end, or --input <manifest.json>.');
2744
+ const type = readOption('--type');
2745
+ const start = readOption('--start');
2746
+ const end = readOption('--end');
2747
+ if (!sourceUrl || (type !== 'image' && type !== 'video')) {
2748
+ console.error('Provide --type <image|video> and --source-url <url>, or --input <manifest.json>.');
2718
2749
  process.exit(1);
2719
2750
  }
2720
- ranges = [{
2751
+ const normalized = normalizeMediaManifest([{
2721
2752
  source_url: sourceUrl,
2722
- start,
2723
- end,
2753
+ type,
2754
+ ...(start !== undefined ? { start } : {}),
2755
+ ...(end !== undefined ? { end } : {}),
2724
2756
  ...(readOption('--description') ? { description: readOption('--description') } : {}),
2725
- }];
2726
- }
2727
- if (!Array.isArray(ranges) || !ranges.length) {
2728
- console.error('Input must contain a non-empty clips array.');
2729
- process.exit(1);
2757
+ }]);
2758
+ ranges = normalized.sourceRanges;
2730
2759
  }
2731
2760
  await addProjectMediaSourceRanges(baseUrl, headers, projectId, ranges, { json: jsonOutput });
2732
2761
  } else {
@@ -2737,7 +2766,7 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2737
2766
  } else {
2738
2767
  console.log(`Project commands:
2739
2768
  project media <projectId> --json List timeline media for a project
2740
- project media add <projectId> ... Add external source_url + start + end media
2769
+ project media add <projectId> ... Add typed external image/video media
2741
2770
  `);
2742
2771
  }
2743
2772
  } else if (command === 'abort') {
@@ -2775,11 +2804,19 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2775
2804
  editArgs.referenceImages.push(imageToArg(args[++i]));
2776
2805
  }
2777
2806
  else if (args[i] === '--aspect' && args[i + 1]) editArgs.aspectRatio = args[++i];
2807
+ else if (args[i] === '--background' && args[i + 1]) {
2808
+ const background = args[++i];
2809
+ if (!['auto', 'opaque', 'transparent'].includes(background)) {
2810
+ console.error('Invalid --background. Use auto, opaque, or transparent.');
2811
+ process.exit(1);
2812
+ }
2813
+ editArgs.background = background;
2814
+ }
2778
2815
  else if (args[i] === '--out' && args[i + 1]) outputPath = args[++i];
2779
2816
  else promptParts.push(args[i]);
2780
2817
  }
2781
2818
  editArgs.editPrompt = promptParts.join(' ');
2782
- if (!editArgs.editPrompt) { console.error('Usage: makaron edit [--image <file|url>] [--image-model gemini|gemini-lite|qwen|openai|pony|wai] [--ref <file>] [--out <file>] "prompt"'); process.exit(1); }
2819
+ if (!editArgs.editPrompt) { console.error('Usage: makaron edit [--image <file|url>] [--image-model gemini|gemini-lite|qwen|openai|pony|wai] [--ref <file>] [--aspect <ratio>] [--background auto|opaque|transparent] [--out <file>] "prompt"'); process.exit(1); }
2783
2820
  process.stderr.write('🎨 Generating...\n');
2784
2821
  const result = await callMcpTool(baseUrl, headers, 'makaron_edit_image', editArgs);
2785
2822
  saveMcpImage(result, outputPath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "makaron-cli",
3
- "version": "0.13.9",
3
+ "version": "0.14.0",
4
4
  "description": "Talk to Makaron Agent from the terminal — create projects, edit images, generate videos",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -180,16 +180,17 @@ npx makaron-cli project media <projectId> --json
180
180
 
181
181
  This is project-scoped. `responses get <runId> --pick output` only returns artifacts from one run; `project media` returns the whole project timeline: original uploads, references, generated images, video snapshots, and editable compositions.
182
182
 
183
- Publish an external video interval directly into that Media List without uploading the original or a derivative MP4:
183
+ Publish typed external images and video intervals directly into that Media List without uploading the original media:
184
184
 
185
185
  ```bash
186
- npx makaron-cli project media add <projectId> --source-url "https://cdn.example.com/source.mp4" --start 12.5 --end 19 --description "Racket frame molding"
187
- npx makaron-cli project media add <projectId> --input ranges.json --json
186
+ npx makaron-cli project media add <projectId> --type image --source-url "https://cdn.example.com/product.jpg" --description "Hero product image"
187
+ npx makaron-cli project media add <projectId> --type video --source-url "https://cdn.example.com/source.mp4" --start 12.5 --end 19 --description "Racket frame molding"
188
+ npx makaron-cli project media add <projectId> --input media.json --json
188
189
  ```
189
190
 
190
- The JSON input may be an array or `{ "clips": [...] }`. Each clip has exactly `source_url + start + end + description`; `start` and `end` are seconds, array order is edit order, and `source_url` is opaque. Do not add or request provider-specific identity fields. Put existing media understanding (summary, editorial purpose, scene evidence, confidence, and limitations) in `description`. Makaron reads that provider-neutral Media List field before deciding whether any additional image/video analysis is needed.
191
+ The JSON input may be an array or `{ "clips": [...] }`. Every item declares `type` as `image` or `video`. Images have `source_url + type + description` and no time range. Videos have `source_url + type + start + end + description`; `start` and `end` are seconds. Array order is edit order and `source_url` is opaque. Do not add or request provider-specific identity fields. Put existing media understanding (summary, editorial purpose, scene evidence, confidence, and limitations) in `description`. Makaron reads that provider-neutral Media List field before deciding whether any additional image/video analysis is needed.
191
192
 
192
- For one-call orchestration, use `chat --project auto --media-manifest plan.json`. Makaron validates the manifest, creates the project, imports its ranges, and starts the Agent. If an upstream service returns multiple plans, the caller should start one independent Makaron task per plan instead of passing the provider-specific batch response into Makaron.
193
+ For one-call orchestration, use `chat --project auto --media-manifest plan.json`. Makaron validates the manifest, creates the project, imports its media, and starts the Agent. If an upstream service returns multiple plans, the caller should start one independent Makaron task per plan instead of passing the provider-specific batch response into Makaron.
193
194
 
194
195
  ```bash
195
196
  npx makaron-cli chat --project auto --media-manifest set-01.json --json -b "Make a 30-second 9:16 TikTok with English VO and captions"
@@ -292,9 +293,12 @@ npx makaron-cli edit --image photo.jpg --ref style.jpg "match this style"
292
293
 
293
294
  # Output to file
294
295
  npx makaron-cli edit --image photo.jpg --out result.jpg "make it dramatic"
296
+
297
+ # Strict transparent output through GPT Image 2
298
+ npx makaron-cli edit --image-model openai --background transparent --out sticker.png "a magenta star sticker"
295
299
  ```
296
300
 
297
- Options: `--image`, `--image-model gemini|gemini-lite|qwen|openai|pony|wai`, `--ref <file>` (up to 3), `--aspect <ratio>`, `--out <path>`
301
+ Options: `--image`, `--image-model gemini|gemini-lite|qwen|openai|pony|wai`, `--ref <file>` (up to 3), `--aspect <ratio>`, `--background auto|opaque|transparent`, `--out <path>`. Transparent output routes strictly to GPT Image 2 and fails instead of returning an opaque fallback.
298
302
 
299
303
  ### `video` — Standalone video tools (no project timeline)
300
304