makaron-cli 0.13.9 → 0.14.1

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.1",
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.1",
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
@@ -98,11 +98,12 @@ npx makaron-cli chat --project auto --image photo.jpg --json -b "make it cinemat
98
98
  npx makaron-cli chat --project auto --image img1.jpg --image img2.jpg --json -b "combine these"
99
99
  ```
100
100
 
101
- `chat` routes image and video models automatically, but you may select the Agent LLM with `--agent-model`. Accepted values are exactly `auto`, `gpt-5.6-terra`, `gpt-5.6-sol`, `gpt-5.6-luna`, `grok-4.5`, and `deepseek-v4-pro`; `auto` currently resolves to `gpt-5.6-terra`. This flag changes only the reasoning/tool-calling Agent LLM. The CLI still rejects `--image-model`, `--video-model`, and the legacy `--model` flag before starting a chat run, so image/video model names cannot be mistaken for Agent models.
101
+ `chat` routes image and video models automatically, but you may select the Agent LLM with `--agent-model`. Accepted values are `auto`, the base model IDs (`gpt-5.6-terra`, `gpt-5.6-sol`, `gpt-5.6-luna`, `grok-4.6`, `deepseek-v4-pro`), and the personal-plan routes (`gpt-5.6-terra-codex-subscription`, `gpt-5.6-sol-codex-subscription`, `gpt-5.6-luna-codex-subscription`). For the configured owner, `auto` resolves to GPT-5.6 Terra through the personal Codex plan. Base GPT-5.6 IDs select Azure API; the suffixed IDs select the personal plan explicitly. This flag changes only the reasoning/tool-calling Agent LLM.
102
102
 
103
103
  ```bash
104
104
  # Explicit lower-cost Agent LLM for a controlled comparison
105
105
  npx makaron-cli chat --project auto --agent-model deepseek-v4-pro --json -b "make a 20s badminton video"
106
+ npx makaron-cli chat --project auto --agent-model gpt-5.6-sol-codex-subscription --json -b "reply with the active model"
106
107
  ```
107
108
 
108
109
  Returns immediately:
@@ -170,26 +171,37 @@ npx makaron-cli project media <projectId> --json
170
171
 
171
172
  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
173
 
173
- External source ranges can be added without uploading either the original video or a derivative clip:
174
+ Typed external images and video ranges can be added without uploading the original media:
174
175
 
175
176
  ```bash
176
177
  npx makaron-cli project media add <projectId> \
178
+ --type image \
179
+ --source-url "https://cdn.example.com/product.jpg" \
180
+ --description "Hero product image"
181
+
182
+ npx makaron-cli project media add <projectId> \
183
+ --type video \
177
184
  --source-url "https://cdn.example.com/source.mp4" \
178
185
  --start 12.5 --end 19 \
179
186
  --description "Racket frame molding"
180
187
 
181
188
  # Batch form: a JSON array or {"clips": [...]}
182
- npx makaron-cli project media add <projectId> --input ranges.json --json
189
+ npx makaron-cli project media add <projectId> --input media.json --json
183
190
  ```
184
191
 
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.
192
+ Every item must declare `type` as `image` or `video`. Images contain
193
+ `source_url + type + description` and do not have a time range. Videos contain
194
+ `source_url + type + start + end + description`; their ranges remain
195
+ non-destructive metadata, and Remotion must trim the original URL to those exact
196
+ source bounds. The returned `<<<media_N>>>` is immediately usable by a later
197
+ Agent run.
186
198
  Use `description` as the provider-neutral media-understanding field. Put any
187
199
  already-known summary, editorial purpose, concrete scene evidence, confidence,
188
200
  and limitations there. Makaron exposes the full description in Media List
189
201
  context so the Agent can edit from it without repeating image/video analysis
190
202
  unless a required detail is missing or uncertain.
191
203
 
192
- For an agent-to-agent handoff, create the project, import the external ranges,
204
+ For an agent-to-agent handoff, create the project, import the typed external media,
193
205
  and start the Agent in one command:
194
206
 
195
207
  ```bash
@@ -200,9 +212,9 @@ npx makaron-cli chat --project auto \
200
212
  ```
201
213
 
202
214
  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
215
+ item declares `type`. Images omit `start` and `end`; videos require both values
216
+ in seconds. Array order is edit order. It is validated before project creation
217
+ and supports up to 20 media items for one Makaron task. Batch planning remains
206
218
  the upstream orchestrator's responsibility: convert each plan into one manifest
207
219
  and start one independent Makaron task.
208
220
 
@@ -341,9 +353,12 @@ npx makaron-cli edit --image photo.jpg --ref style.jpg "match this style"
341
353
 
342
354
  # Output to file
343
355
  npx makaron-cli edit --image photo.jpg --out result.jpg "make it dramatic"
356
+
357
+ # Strict transparent PNG/WebP output through GPT Image 2 (fails rather than returning opaque)
358
+ npx makaron-cli edit --image-model openai --background transparent --out sticker.png "a magenta star sticker"
344
359
  ```
345
360
 
346
- Options: `--image`, `--image-model gemini|gemini-lite|qwen|openai|pony|wai`, `--ref <file>` (up to 3), `--aspect <ratio>`, `--out <path>`
361
+ 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
362
 
348
363
  ### `video` — Standalone video tools (no project timeline)
349
364
 
@@ -354,18 +369,21 @@ npx makaron-cli video script --image img1.jpg "cinematic story"
354
369
  # 2. Analyze a video (standalone, no timeline write)
355
370
  npx makaron-cli analyze --video input.mp4 "describe the key actions and pacing"
356
371
 
357
- # 3a. Submit image-to-video rendering (images must be public URLs from step 1 or uploaded)
372
+ # 3a. Submit reference-to-video rendering (images must be public URLs from step 1 or uploaded)
358
373
  npx makaron-cli video create --script "Shot 1 (5s): <<<image_1>>> ..." --image https://...jpg --duration 5 --video-model kling
359
374
  npx makaron-cli video create --script "Shot 1 (5s): <<<image_1>>> slow cinematic push-in with native ambience" --image https://...jpg --duration 5 --video-model grok
375
+ npx makaron-cli video create --script "Keep both subjects recognizable as they enter the same studio" --image https://...jpg --image https://...webp --duration 5 --video-model grok --video-resolution 720p
360
376
  npx makaron-cli video create --script "Shot 1 (15s): <<<image_1>>> and <<<image_2>>> build a neon one-person studio" --image https://...jpg --image https://...webp --duration 15 --video-model seedance-mini --video-resolution 480p --aspect 9:16
361
377
 
362
- # 3b. Native SeeDance or MiniMax H3 text-to-video (no image required)
378
+ # 3b. Native SeeDance, Wan 3.0, or MiniMax H3 text-to-video (no image required)
363
379
  npx makaron-cli video create --script "Shot 1 (5s): A neon one-person studio wakes at dawn" --duration 5 --video-model seedance-fast --aspect 16:9
364
380
  npx makaron-cli video create --script "Shot 1 (15s): A premium creative editor comes alive" --duration 15 --video-model minimax-h3 --aspect 16:9
365
381
 
366
382
  # 3c. Edit a video from a local file or public URL
367
383
  npx makaron-cli video create --script "make it funny" --video input.mp4 --duration 5 --video-model seedance-fast
368
384
  npx makaron-cli video create --script "make it warmer and cinematic" --video https://example.com/input.mp4 --duration 5 --video-model seedance --video-resolution 1080p
385
+ npx makaron-cli video create --script "turn the light warm gold but preserve the action" --video input.mp4 --video-model grok --operation edit
386
+ npx makaron-cli video create --script "continue the camera move into the next beat" --video input.mp4 --video-model grok --operation extend --duration 4
369
387
 
370
388
  # 4. Check status
371
389
  npx makaron-cli video status <taskId>
@@ -377,11 +395,13 @@ For project/timeline video editing, use:
377
395
  npx makaron-cli chat --project <id|auto> --video input.mp4 -b "make it funny"
378
396
  ```
379
397
 
380
- Options for `video create`: `--script "..."`, `--script-file <path>`, `--image <url>` (repeatable, up to the selected model limit), `--video <file|url>` and `--audio <file|url>` (repeatable where supported), `--duration <seconds>`, `--aspect 9:16|16:9|1:1`, `--video-model seedance-fast|seedance-mini|seedance|seedance-2.5|kling|grok|google-omni|minimax-h3|sync-lipsync-v3`, `--video-resolution auto|480p|720p|768p|1080p|2k|4k`. Default model is `seedance-fast`. SeeDance accepts native text-to-video with no image and integer output duration 4-15s (default 5s); `seedance-mini` supports 480p/720p and is best for cheaper drafts/multi-size tests; MiniMax H3 accepts native text-to-video, 4-15s output, public 768p/2k resolution, and up to 9 image, up to 3 video, and up to 3 audio references through Makaron Agent/chat. `sync-lipsync-v3` requires exactly one video plus one MP3/WAV and preserves that replacement audio while aligning the mouth. H3 defaults to 768p; request 2k explicitly for maximum/final quality. Kling supports 5-15s; Grok 1.5 supports 1-15s single-image-to-video only; Gemini Omni supports 3-10s fast 720p image/video generation and editing with native generated audio, including up to 6 image references when no video reference is provided. For `--video-model grok`, forced `--aspect` is ignored to avoid xAI stretching the source image; pad/create the image at the target shape first or use another model.
398
+ Options for `video create`: `--script "..."`, `--script-file <path>`, `--image <url>` (repeatable, up to the selected model limit), `--video <file|url>` and `--audio <file|url>` (repeatable where supported), `--voice <xai-preset-id>` (repeatable, Grok only), `--duration <seconds>`, `--aspect 9:16|16:9|1:1`, `--video-model seedance-fast|seedance-mini|seedance|seedance-2.5|wan-3.0|wan-3.0-pro|kling|grok|google-omni|minimax-h3|sync-lipsync-v3`, `--video-resolution auto|480p|720p|768p|1080p|2k|4k`. Default model is `seedance-fast`. SeeDance accepts native text-to-video with no image and integer output duration 4-15s (default 5s); every Seedance image input is submitted through reference-to-video, including one image. `seedance-mini` supports 480p/720p and is best for cheaper drafts/multi-size tests. Wan 3.0 Standard supports 480p/720p/1080p and Wan 3.0 Pro supports 1080p/2K/4K through MuleRouter. MiniMax H3 accepts native text-to-video, 4-15s output, public 768p/2k resolution, and up to 9 image, up to 3 video, and up to 3 audio feature references through Makaron Agent/chat; a single image keeps the `reference_image` role. `sync-lipsync-v3` requires exactly one video plus one MP3/WAV and preserves that replacement audio while aligning the mouth. H3 defaults to 768p; request 2k explicitly for maximum/final quality. Kling supports 5-15s. Grok generation uses `grok-imagine-video-1.5`: text-only generation supports 480p/720p/1080p, while any 1-7 image or preset voice input uses reference-to-video and is capped at 720p. Grok edit/extend uses `grok-imagine-video` internally under the same `grok` selector. Gemini Omni supports 3-10s fast image/video generation and editing with native generated audio; every image-only generation request uses `reference_to_video`, including a single image, with up to 6 images when no video reference is provided.
381
399
 
382
400
  Seedance 2.5: use `--video-model seedance-2.5` for 4-30 second output at 480p/720p. `--image` accepts local files or URLs (up to 30), while repeatable `--video` and `--audio` accept up to 10 each. Use `--video-operation generate|edit|extend`, `--extend-direction forward|backward`, `--output-format mp4|mov`, `--web-search`, `--generated-audio` / `--no-generated-audio`, and `--relaxed-content-filter`. Edit/extend require a video reference. The Evolink route does not currently expose 4K output.
383
401
 
384
- Video edit model behavior: `--video-model kling --video` uses Kling base/direct edit internally; `--video-model seedance-fast --video`, `--video-model seedance-mini --video`, or `--video-model seedance --video` uses the SeeDance video-reference path and requires target <=15s, <=50MB, width/height 300-6000px, aspect ratio 0.4-2.5, and frame pixels 409,600-2,086,876. `--video-model minimax-h3 --video` uses H3 feature/reference mode: up to 3 video references totaling <=15s, each <=50MB with width/height 256-5760px and aspect ratio 0.4-2.5. `--video-model google-omni --video` uses Gemini Omni direct video editing and accepts one reference video in Makaron. Output duration is clamped to 3-10s. Grok does not support video references.
402
+ Wan 3.0: use `--video-model wan-3.0` for MuleRouter Standard at 480p/720p/1080p, or `--video-model wan-3.0-pro` for MuleRouter Pro super-resolution at 1080p/2K/4K. Both support 2-30 second generation with up to 10 images, 5 videos, and 5 audio references. Use generation mode with feature references; typed edit/extend and `--relaxed-content-filter` are not supported.
403
+
404
+ Video edit model behavior: `--video-model kling --video` uses Kling base/direct edit internally; `--video-model seedance-fast --video`, `--video-model seedance-mini --video`, or `--video-model seedance --video` uses the SeeDance video-reference path and requires target <=15s, <=50MB, width/height 300-6000px, aspect ratio 0.4-2.5, and frame pixels 409,600-2,086,876. `--video-model minimax-h3 --video` uses H3 feature/reference mode: up to 3 video references totaling <=15s, each <=50MB with width/height 256-5760px and aspect ratio 0.4-2.5. `--video-model google-omni --video` uses Gemini Omni direct video editing and accepts one reference video in Makaron. Output duration is clamped to 3-10s. `--video-model grok --video --operation edit` accepts one MP4 up to 8.7s, retains duration/aspect, and caps output at 720p. `--operation extend` accepts one 2-15s MP4 and adds 2-10s (default 6s); the returned result includes the original plus extension.
385
405
 
386
406
  ### `music` — Music generation
387
407
 
@@ -497,7 +517,7 @@ send_message "All done!"
497
517
  - One project = one conversation thread. All history is preserved.
498
518
  - One active Agent Run at a time per project. A new message received while it is active is appended to that same Agent Run and processed at a durable work-unit boundary; it does not interrupt the execution or create a second owner for an in-progress Studio workflow.
499
519
  - Multi-image: `create --image a.jpg --image b.jpg` or `chat --image ref.jpg`.
500
- - Provider-generated videos can take 3-5 minutes; Grok is usually around 30-40 seconds; Gemini Omni is usually around 30-70 seconds plus Storage handoff. Remotion compositions should be converted with `materialize` / `responses get --materialize`, and timing should be read from `duration_seconds`, `render_seconds`, and `realtime_ratio`.
520
+ - Provider-generated videos can take 3-5 minutes; current Grok generation/edit probes are usually around 15-60 seconds; Gemini Omni is usually around 30-70 seconds plus Storage handoff. Remotion compositions should be converted with `materialize` / `responses get --materialize`, and timing should be read from `duration_seconds`, `render_seconds`, and `realtime_ratio`.
501
521
  - Music takes ~60 seconds. Appears in output when done.
502
522
  - Images are typically ready in 15-30 seconds.
503
523
  - stdout is always machine-readable JSON/text. Human-friendly logs go to stderr.
package/bin/makaron.mjs CHANGED
@@ -33,7 +33,10 @@ const CHAT_AGENT_MODELS = [
33
33
  'gpt-5.6-terra',
34
34
  'gpt-5.6-sol',
35
35
  'gpt-5.6-luna',
36
- 'grok-4.5',
36
+ 'gpt-5.6-terra-codex-subscription',
37
+ 'gpt-5.6-sol-codex-subscription',
38
+ 'gpt-5.6-luna-codex-subscription',
39
+ 'grok-4.6',
37
40
  'deepseek-v4-pro',
38
41
  ];
39
42
 
@@ -62,6 +65,8 @@ const SEEDANCE_MIN_VIDEO_ASPECT = 0.4;
62
65
  const SEEDANCE_MAX_VIDEO_ASPECT = 2.5;
63
66
  const MINIMAX_H3_MIN_VIDEO_SIDE = 256;
64
67
  const MINIMAX_H3_MAX_VIDEO_SIDE = 5760;
68
+ const MULEROUTER_WAN_MAX_VIDEO_SIDE = 4096;
69
+ const MULEROUTER_WAN_MAX_VIDEO_FILE_SIZE = 100 * 1024 * 1024;
65
70
 
66
71
  function warnLegacyModelFlag(replacement) {
67
72
  process.stderr.write(`⚠️ --model is deprecated here; use ${replacement}.\n`);
@@ -174,25 +179,19 @@ function readJsonInput(filePath) {
174
179
  return JSON.parse(raw);
175
180
  }
176
181
 
177
- const MAX_MEDIA_MANIFEST_RANGES = 20;
182
+ const MAX_MEDIA_MANIFEST_ITEMS = 20;
178
183
 
179
184
  function normalizeMediaManifest(input) {
180
185
  const manifest = Array.isArray(input) ? { clips: input } : input;
181
186
  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;
187
+ throw new Error('Media manifest must be a JSON object or an array of media items.');
188
+ }
189
+ const rawRanges = Array.isArray(manifest.clips) ? manifest.clips : null;
191
190
  if (!rawRanges?.length) {
192
191
  throw new Error('Media manifest must contain a non-empty clips array.');
193
192
  }
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.`);
193
+ if (rawRanges.length > MAX_MEDIA_MANIFEST_ITEMS) {
194
+ throw new Error(`Media manifest supports at most ${MAX_MEDIA_MANIFEST_ITEMS} media items per Makaron task.`);
196
195
  }
197
196
 
198
197
  const sourceRanges = rawRanges.map((raw, index) => {
@@ -207,8 +206,19 @@ function normalizeMediaManifest(input) {
207
206
  if (!['http:', 'https:'].includes(parsed.protocol)) {
208
207
  throw new Error(`clips[${index}].source_url must use HTTP or HTTPS.`);
209
208
  }
210
- const start = Number(raw.start ?? raw.start_sec);
211
- const end = Number(raw.end ?? raw.end_sec);
209
+ const declaredType = raw.type;
210
+ if (declaredType !== 'image' && declaredType !== 'video') {
211
+ throw new Error(`clips[${index}].type must be image or video.`);
212
+ }
213
+ const description = typeof raw.description === 'string' ? raw.description.trim() : '';
214
+ if (declaredType === 'image') {
215
+ if (raw.start !== undefined || raw.end !== undefined || raw.start_sec !== undefined || raw.end_sec !== undefined) {
216
+ throw new Error(`clips[${index}] image items must not include start or end.`);
217
+ }
218
+ return { source_url: sourceUrl, type: 'image', description };
219
+ }
220
+ const start = Number(raw.start);
221
+ const end = Number(raw.end);
212
222
  if (!Number.isFinite(start) || start < 0) {
213
223
  throw new Error(`clips[${index}].start must be a finite number >= 0.`);
214
224
  }
@@ -217,9 +227,10 @@ function normalizeMediaManifest(input) {
217
227
  }
218
228
  return {
219
229
  source_url: sourceUrl,
230
+ type: declaredType,
220
231
  start,
221
232
  end,
222
- description: typeof raw.description === 'string' ? raw.description.trim() : '',
233
+ description,
223
234
  };
224
235
  });
225
236
 
@@ -447,17 +458,19 @@ Options:
447
458
  --image <file|url> Attach a reference image or screenshot. Repeatable.
448
459
  --video <file|url> Attach a video to the project timeline. Repeatable.
449
460
  --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.
461
+ --media-manifest <file|-> Import typed image/video media before this run.
451
462
  --skill <id|label|name> Use an installed skill or auto-install a matched marketplace skill.
452
463
  --agent-model <id> Agent LLM only: auto, gpt-5.6-terra, gpt-5.6-sol,
453
- gpt-5.6-luna, grok-4.5, or deepseek-v4-pro.
464
+ gpt-5.6-luna, grok-4.6, deepseek-v4-pro, or a
465
+ gpt-5.6-*-codex-subscription personal-plan route.
454
466
  --background, -b Submit and print a runId.
455
467
  --json Output structured JSON.
456
468
  --stream Legacy live SSE stream.
457
469
  --help, -h Show this help.
458
470
 
459
- Agent LLM defaults to auto (currently gpt-5.6-terra). Image/video model routing
460
- stays automatic in chat; --image-model, --video-model, and --model are rejected.
471
+ Agent LLM defaults to auto (GPT-5.6 Terra; the account owner uses the personal
472
+ Codex plan). Base GPT-5.6 ids select Azure API; append -codex-subscription to
473
+ select the personal plan explicitly. Image/video model routing stays automatic in chat.
461
474
 
462
475
  What you can ask:
463
476
  Image edit
@@ -489,6 +502,9 @@ What you can ask:
489
502
  Compare Agent LLMs with identical inputs
490
503
  makaron chat --project auto --agent-model deepseek-v4-pro -b --json "make a 20s badminton video"
491
504
 
505
+ Force the personal Codex plan
506
+ makaron chat --project auto --agent-model gpt-5.6-sol-codex-subscription -b --json "reply with the active model"
507
+
492
508
  Music
493
509
  makaron chat --project <id> "add calm piano background music"
494
510
 
@@ -1031,7 +1047,10 @@ async function addProjectMediaSourceRanges(baseUrl, headers, projectId, ranges,
1031
1047
  }
1032
1048
  if (!opts.silent) {
1033
1049
  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)'}`);
1050
+ const range = item.type === 'video' && Number.isFinite(item.start_sec) && Number.isFinite(item.end_sec)
1051
+ ? ` ${formatSeconds(item.start_sec)}-${formatSeconds(item.end_sec)}s`
1052
+ : '';
1053
+ console.log(`${item.ref} [${item.type || 'media'}] ${item.source_url}${range}${item.created ? '' : ' (existing)'}`);
1035
1054
  }
1036
1055
  }
1037
1056
  return data;
@@ -1787,7 +1806,22 @@ function saveMcpImage(result, outputPath) {
1787
1806
  const imageBlock = content.find(c => c.type === 'image');
1788
1807
  if (textBlock) process.stderr.write(`${textBlock.text}\n`);
1789
1808
  if (imageBlock) {
1790
- const out = outputPath || `makaron-output-${Date.now()}.jpg`;
1809
+ const extension = imageBlock.mimeType === 'image/png'
1810
+ ? 'png'
1811
+ : imageBlock.mimeType === 'image/webp'
1812
+ ? 'webp'
1813
+ : 'jpg';
1814
+ let out = outputPath || `makaron-output-${Date.now()}.${extension}`;
1815
+ if (outputPath) {
1816
+ const requestedExtension = path.extname(outputPath).slice(1).toLowerCase();
1817
+ const compatible = extension === 'jpg'
1818
+ ? ['jpg', 'jpeg'].includes(requestedExtension)
1819
+ : requestedExtension === extension;
1820
+ if (!compatible) {
1821
+ out = `${outputPath.slice(0, outputPath.length - path.extname(outputPath).length)}.${extension}`;
1822
+ process.stderr.write(`Output is ${imageBlock.mimeType}; saving as ${out} so bytes and filename agree.\n`);
1823
+ }
1824
+ }
1791
1825
  fs.writeFileSync(out, Buffer.from(imageBlock.data, 'base64'));
1792
1826
  console.log(out);
1793
1827
  return out;
@@ -1838,8 +1872,10 @@ Commands:
1838
1872
  credits Show current credit balance
1839
1873
  list (ls) List all projects
1840
1874
  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
1875
+ project media add <projectId> --type image --source-url <url>
1876
+ Add an external image without uploading it
1877
+ project media add <projectId> --type video --source-url <url> --start <n> --end <n>
1878
+ Add an external video range without uploading it
1843
1879
  create --image <file> Create project from local image
1844
1880
  create --image-url <url> Create project from URL
1845
1881
  create --title "name" Create empty project (text-to-image)
@@ -1851,7 +1887,7 @@ Commands:
1851
1887
  chat --project <id> --video <file> Attach video to conversation
1852
1888
  chat --project <id> --audio <file> Attach song/beat/voice reference
1853
1889
  chat --project auto --media-manifest <file> "message"
1854
- Create, import external ranges, and run Agent
1890
+ Create, import typed external media, and run Agent
1855
1891
  chat --project <id> -b "message" Background: submit and print runId
1856
1892
  chat --project <id> --stream "msg" Legacy: stream SSE in real-time
1857
1893
  chat --project <id> --json "msg" Output structured JSON result
@@ -1961,11 +1997,12 @@ function printHelp(topic, subtopic) {
1961
1997
  console.log('Usage: makaron credits [--json]');
1962
1998
  } else if (topic === 'project' || topic === 'projects') {
1963
1999
  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]`);
2000
+ makaron project media add <projectId> --type image --source-url <url> [--description <text>] [--json]
2001
+ makaron project media add <projectId> --type video --source-url <url> --start <n> --end <n> [--description <text>] [--json]
2002
+ makaron project media add <projectId> --input <media.json> [--json]`);
1966
2003
  else console.log(`Project commands:
1967
2004
  project media <projectId> --json List timeline media for a project
1968
- project media add <projectId> ... Add external source_url + start + end media
2005
+ project media add <projectId> ... Add typed external image/video media
1969
2006
  `);
1970
2007
  } else if (topic === 'abort') {
1971
2008
  console.log('Usage: makaron abort <runId>');
@@ -2005,19 +2042,21 @@ Not sure which built-in skill to use? Start with:
2005
2042
  composition status <jobId> [--wait] [--json]
2006
2043
  `);
2007
2044
  } 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"');
2045
+ 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
2046
  } else if (topic === 'analyze') {
2010
2047
  console.log('Usage: makaron analyze --video <file|url> ["question"]');
2011
2048
  } else if (topic === 'video') {
2012
2049
  if (subtopic === 'script') console.log('Usage: makaron video script --image <file> [--image <file>] [--lang en|zh] "direction"');
2013
- else if (subtopic === 'create') console.log('Usage: makaron video create --script "..." [--image <url> ...] [--video <url> ...] [--audio <url> ...] [--duration 10] [--aspect 9:16] [--video-model seedance-fast|seedance-mini|seedance|seedance-2.5|kling|grok|google-omni|minimax-h3|sync-lipsync-v3] [--operation generate|edit|extend] [--video-resolution auto|480p|720p|768p|1080p|2k|4k] [--keep-original-sound]');
2050
+ else if (subtopic === 'create') console.log('Usage: makaron video create --script "..." [--image <url> ...] [--video <url> ...] [--audio <url> ...] [--voice <xai-preset-id> ...] [--duration 10] [--aspect 9:16] [--video-model seedance-fast|seedance-mini|seedance|seedance-2.5|wan-3.0|wan-3.0-pro|kling|grok|google-omni|minimax-h3|sync-lipsync-v3] [--operation generate|edit|extend] [--video-resolution auto|480p|720p|768p|1080p|2k|4k] [--keep-original-sound]');
2014
2051
  else if (subtopic === 'status') console.log('Usage: makaron video status <taskId> | --snapshot <snapshotId> [--wait]');
2015
2052
  else console.log(`Video commands:
2016
2053
  video script --image <file> [--image <file>] "direction" Write video script
2017
2054
  video create --script "..." --video-model seedance-fast Native text-to-video (no image required)
2055
+ video create --script "..." --video-model wan-3.0 Wan 3.0 Standard via MuleRouter
2056
+ video create --script "..." --video-model wan-3.0-pro Wan 3.0 Pro super-resolution via MuleRouter
2018
2057
  video create --script "..." --video-model minimax-h3 MiniMax H3 text-to-video (default 768P)
2019
2058
  video create --script "..." --image <url> [--duration 10] Submit video task
2020
- video create --script "..." --video <public-url> [--video-model seedance-fast|seedance-mini|seedance|seedance-2.5|kling|google-omni|minimax-h3] Edit/reference a video (Grok does not support video refs)
2059
+ video create --script "..." --video <file|url> --video-model grok [--operation edit|extend] Edit or extend one MP4 with Grok
2021
2060
  video create --script "Use the supplied audio" --video <url> --audio <url> --video-model sync-lipsync-v3 Lip-sync exact replacement audio
2022
2061
  video status <taskId> Check video status
2023
2062
  video status --snapshot <snapshotId> [--wait] Check v2 video snapshot
@@ -2269,12 +2308,12 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2269
2308
  );
2270
2309
  importedManifestMedia = imported.media || [];
2271
2310
  if (importedManifestMedia.length !== mediaManifest.sourceRanges.length) {
2272
- process.stderr.write(`❌ Imported ${importedManifestMedia.length}/${mediaManifest.sourceRanges.length} source range(s); aborting run.\n`);
2311
+ process.stderr.write(`❌ Imported ${importedManifestMedia.length}/${mediaManifest.sourceRanges.length} media item(s); aborting run.\n`);
2273
2312
  process.exit(1);
2274
2313
  }
2275
2314
  uploadedTurnMediaCount += importedManifestMedia.length;
2276
- uploadedTurnVideoCount += importedManifestMedia.length;
2277
- process.stderr.write(`📎 Imported ${importedManifestMedia.length} external source range(s) from media manifest\n`);
2315
+ uploadedTurnVideoCount += importedManifestMedia.filter(item => item.type === 'video').length;
2316
+ process.stderr.write(`📎 Imported ${importedManifestMedia.length} typed external media item(s) from media manifest\n`);
2278
2317
  }
2279
2318
  // Upload additional images to existing project
2280
2319
  if (imageFileList.length > 0 || imageUrlList.length > 0) {
@@ -2432,9 +2471,12 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2432
2471
  ...(importedManifestMedia.length ? {
2433
2472
  importedMedia: importedManifestMedia.map(item => ({
2434
2473
  ref: item.ref,
2474
+ type: item.type,
2435
2475
  source_url: item.source_url,
2436
- start_sec: item.start_sec,
2437
- end_sec: item.end_sec,
2476
+ ...(item.type === 'video' ? {
2477
+ start_sec: item.start_sec,
2478
+ end_sec: item.end_sec,
2479
+ } : {}),
2438
2480
  })),
2439
2481
  } : {}),
2440
2482
  }));
@@ -2699,7 +2741,7 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2699
2741
  const jsonOutput = args.includes('--json');
2700
2742
  if (args[2] === 'add') {
2701
2743
  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); }
2744
+ if (!projectId) { console.error('Usage: makaron project media add <projectId> --type <image|video> --source-url <url> [--start <n> --end <n>]'); process.exit(1); }
2703
2745
  const readOption = (name) => {
2704
2746
  const index = args.indexOf(name);
2705
2747
  return index >= 0 ? args[index + 1] : undefined;
@@ -2707,26 +2749,25 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2707
2749
  const inputPath = readOption('--input');
2708
2750
  let ranges;
2709
2751
  if (inputPath) {
2710
- const input = readJsonInput(inputPath);
2711
- ranges = Array.isArray(input) ? input : input.clips || input.source_ranges || input.sourceRanges;
2752
+ const normalized = normalizeMediaManifest(readJsonInput(inputPath));
2753
+ ranges = normalized.sourceRanges;
2712
2754
  } else {
2713
2755
  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>.');
2756
+ const type = readOption('--type');
2757
+ const start = readOption('--start');
2758
+ const end = readOption('--end');
2759
+ if (!sourceUrl || (type !== 'image' && type !== 'video')) {
2760
+ console.error('Provide --type <image|video> and --source-url <url>, or --input <manifest.json>.');
2718
2761
  process.exit(1);
2719
2762
  }
2720
- ranges = [{
2763
+ const normalized = normalizeMediaManifest([{
2721
2764
  source_url: sourceUrl,
2722
- start,
2723
- end,
2765
+ type,
2766
+ ...(start !== undefined ? { start } : {}),
2767
+ ...(end !== undefined ? { end } : {}),
2724
2768
  ...(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);
2769
+ }]);
2770
+ ranges = normalized.sourceRanges;
2730
2771
  }
2731
2772
  await addProjectMediaSourceRanges(baseUrl, headers, projectId, ranges, { json: jsonOutput });
2732
2773
  } else {
@@ -2737,7 +2778,7 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2737
2778
  } else {
2738
2779
  console.log(`Project commands:
2739
2780
  project media <projectId> --json List timeline media for a project
2740
- project media add <projectId> ... Add external source_url + start + end media
2781
+ project media add <projectId> ... Add typed external image/video media
2741
2782
  `);
2742
2783
  }
2743
2784
  } else if (command === 'abort') {
@@ -2775,11 +2816,19 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2775
2816
  editArgs.referenceImages.push(imageToArg(args[++i]));
2776
2817
  }
2777
2818
  else if (args[i] === '--aspect' && args[i + 1]) editArgs.aspectRatio = args[++i];
2819
+ else if (args[i] === '--background' && args[i + 1]) {
2820
+ const background = args[++i];
2821
+ if (!['auto', 'opaque', 'transparent'].includes(background)) {
2822
+ console.error('Invalid --background. Use auto, opaque, or transparent.');
2823
+ process.exit(1);
2824
+ }
2825
+ editArgs.background = background;
2826
+ }
2778
2827
  else if (args[i] === '--out' && args[i + 1]) outputPath = args[++i];
2779
2828
  else promptParts.push(args[i]);
2780
2829
  }
2781
2830
  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); }
2831
+ 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
2832
  process.stderr.write('🎨 Generating...\n');
2784
2833
  const result = await callMcpTool(baseUrl, headers, 'makaron_edit_image', editArgs);
2785
2834
  saveMcpImage(result, outputPath);
@@ -2814,9 +2863,10 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2814
2863
  if (text) console.log(text);
2815
2864
 
2816
2865
  } else if (sub === 'create') {
2817
- const images = [];
2866
+ let images = [];
2818
2867
  const videos = [];
2819
2868
  const audios = [];
2869
+ const referenceVoices = [];
2820
2870
  let script = '', duration = undefined, aspectRatio = undefined, videoModel = undefined, videoResolution = undefined, wait = false;
2821
2871
  let keepOriginalSound = false, videoOperation = undefined, extendDirection = undefined, outputFormat = undefined;
2822
2872
  let generateAudio = undefined, contentFilter = undefined, webSearch = false;
@@ -2824,6 +2874,7 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2824
2874
  if (args[i] === '--image' && args[i + 1]) images.push(args[++i]);
2825
2875
  else if (args[i] === '--video' && args[i + 1]) videos.push(args[++i]);
2826
2876
  else if (args[i] === '--audio' && args[i + 1]) audios.push(args[++i]);
2877
+ else if (args[i] === '--voice' && args[i + 1]) referenceVoices.push(args[++i]);
2827
2878
  else if (args[i] === '--script' && args[i + 1]) script = args[++i];
2828
2879
  else if (args[i] === '--script-file' && args[i + 1]) script = fs.readFileSync(args[++i], 'utf-8');
2829
2880
  else if (args[i] === '--duration' && args[i + 1]) duration = Number(args[++i]);
@@ -2849,27 +2900,43 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2849
2900
  }
2850
2901
  else if (args[i] === '--wait') wait = true;
2851
2902
  }
2852
- const selectedVideoModel = videoModel || 'seedance-fast';
2903
+ const selectedVideoModel = ['wan3', 'wan3.0', 'wan30', 'wan-3'].includes(videoModel)
2904
+ ? 'wan-3.0'
2905
+ : ['wan3-pro', 'wan3.0-pro', 'wan30-pro', 'wan-3-pro', 'berry-1.0-pro'].includes(videoModel)
2906
+ ? 'wan-3.0-pro'
2907
+ : (videoModel || 'seedance-fast');
2853
2908
  const isSeedance25 = selectedVideoModel === 'seedance-2.5';
2909
+ const isWan30 = selectedVideoModel === 'wan-3.0' || selectedVideoModel === 'wan-3.0-pro';
2854
2910
  const isSeedanceModel = selectedVideoModel === 'seedance-fast' || selectedVideoModel === 'seedance-mini' || selectedVideoModel === 'seedance' || isSeedance25;
2855
2911
  const isMinimaxH3 = selectedVideoModel === 'minimax-h3';
2912
+ const isGrok = selectedVideoModel === 'grok';
2913
+ const isGoogleOmni = selectedVideoModel === 'google-omni';
2856
2914
  const isSyncLipsync = selectedVideoModel === 'sync-lipsync-v3';
2857
- const supportsNativeTextToVideo = isSeedanceModel || isMinimaxH3;
2858
- if (!script || (!images.length && !videos.length && !audios.length && !supportsNativeTextToVideo)) {
2859
- console.error('Usage: makaron video create --script "..." [--image <url>] [--video <file|url>] [--audio <file|url>] [--duration 30] [--video-model seedance-2.5|minimax-h3]');
2915
+ const supportsNativeTextToVideo = isSeedanceModel || isWan30 || isMinimaxH3 || isGrok || isGoogleOmni;
2916
+ if (!script || (!images.length && !videos.length && !audios.length && !referenceVoices.length && !supportsNativeTextToVideo)) {
2917
+ console.error('Usage: makaron video create --script "..." [--image <url>] [--video <file|url>] [--audio <file|url>] [--duration 30] [--video-model seedance-2.5|wan-3.0|wan-3.0-pro|minimax-h3]');
2860
2918
  process.exit(1);
2861
2919
  }
2862
2920
  if (isSeedance25 && images.length > 30) { console.error('Seedance 2.5 supports at most 30 image references.'); process.exit(1); }
2863
2921
  if (isSeedance25 && videos.length > 10) { console.error('Seedance 2.5 supports at most 10 video references.'); process.exit(1); }
2864
2922
  if (isSeedance25 && audios.length > 10) { console.error('Seedance 2.5 supports at most 10 audio references.'); process.exit(1); }
2923
+ if (isWan30 && images.length > 10) { console.error('Wan 3.0 supports at most 10 image references.'); process.exit(1); }
2924
+ if (isWan30 && videos.length > 5) { console.error('Wan 3.0 supports at most 5 video references.'); process.exit(1); }
2925
+ if (isWan30 && audios.length > 5) { console.error('Wan 3.0 supports at most 5 audio references.'); process.exit(1); }
2865
2926
  if (isMinimaxH3 && images.length > 9) { console.error('MiniMax H3 supports at most 9 image references.'); process.exit(1); }
2866
2927
  if (isMinimaxH3 && videos.length > 3) { console.error('MiniMax H3 supports at most 3 video references.'); process.exit(1); }
2867
2928
  if (isMinimaxH3 && audios.length > 3) { console.error('MiniMax H3 supports at most 3 audio references.'); process.exit(1); }
2929
+ if (isGrok && images.length > 7) { console.error('Grok Imagine Video 1.5 supports at most 7 image references.'); process.exit(1); }
2930
+ if (isGrok && videos.length > 1) { console.error('Grok video edit/extend accepts exactly one source video.'); process.exit(1); }
2931
+ if (isGrok && referenceVoices.length > 3) { console.error('Grok Imagine Video 1.5 supports at most 3 preset voices.'); process.exit(1); }
2932
+ if (!isGrok && referenceVoices.length) { console.error('--voice is currently supported only with --video-model grok.'); process.exit(1); }
2868
2933
  if (isSyncLipsync && (images.length !== 0 || videos.length !== 1 || audios.length !== 1)) { console.error('Sync Lipsync v3 requires exactly one --video and one --audio, with no --image.'); process.exit(1); }
2869
2934
  if (isSyncLipsync && !/<<<audio_1>>>/i.test(script)) {
2870
2935
  script += '\nUse <<<audio_1>>> as the exact replacement soundtrack.';
2871
2936
  }
2872
2937
  if (videoOperation && !['generate', 'edit', 'extend'].includes(videoOperation)) { console.error('--video-operation must be generate, edit, or extend.'); process.exit(1); }
2938
+ if (isWan30 && videoOperation && videoOperation !== 'generate') { console.error('Wan 3.0 supports generation with feature references, not typed edit or extend.'); process.exit(1); }
2939
+ if (!isSeedance25 && contentFilter === false) { console.error('--relaxed-content-filter is only supported with --video-model seedance-2.5.'); process.exit(1); }
2873
2940
  if (extendDirection && !['forward', 'backward'].includes(extendDirection)) { console.error('--extend-direction must be forward or backward.'); process.exit(1); }
2874
2941
  if (outputFormat && !['mp4', 'mov'].includes(outputFormat)) { console.error('--output-format must be mp4 or mov.'); process.exit(1); }
2875
2942
 
@@ -2878,9 +2945,11 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2878
2945
  process.exit(1);
2879
2946
  }
2880
2947
 
2948
+ const inferredOperation = videoOperation || ((isSeedance25 || isGrok) && videos.length ? 'edit' : 'generate');
2881
2949
  let providerMaxDuration = isSeedance25 ? SEEDANCE25_MAX_VIDEO_REFERENCE_DURATION : MAX_VIDEO_PROVIDER_REFERENCE_DURATION;
2950
+ if (isGrok) providerMaxDuration = inferredOperation === 'edit' ? 8.7 : 15;
2882
2951
  if (isSyncLipsync) providerMaxDuration = 60;
2883
- const providerMaxPixels = isSyncLipsync || isMinimaxH3 ? Infinity : isSeedance25 ? SEEDANCE25_MAX_VIDEO_FRAME_PIXELS : MAX_VIDEO_FRAME_PIXELS;
2952
+ const providerMaxPixels = isSyncLipsync || isMinimaxH3 || isWan30 ? Infinity : isSeedance25 ? SEEDANCE25_MAX_VIDEO_FRAME_PIXELS : MAX_VIDEO_FRAME_PIXELS;
2884
2953
  const localImages = images.filter(image => !isHttpUrl(image));
2885
2954
  if (localImages.length) {
2886
2955
  const uploadedImages = await uploadImageFilesViaSignedUrl(baseUrl, headers, undefined, localImages);
@@ -2895,13 +2964,21 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2895
2964
  maxDuration: providerMaxDuration,
2896
2965
  durationTolerance: MAX_VIDEO_PROVIDER_REFERENCE_DURATION_TOLERANCE,
2897
2966
  maxFramePixels: providerMaxPixels,
2898
- maxFileSize: isSeedance25 || isSyncLipsync ? 200 * 1024 * 1024 : MAX_VIDEO_UPLOAD_FILE_SIZE,
2899
- ...(selectedVideoModel === 'minimax-h3' ? {
2967
+ maxFileSize: isWan30 ? MULEROUTER_WAN_MAX_VIDEO_FILE_SIZE : isSeedance25 || isSyncLipsync ? 200 * 1024 * 1024 : MAX_VIDEO_UPLOAD_FILE_SIZE,
2968
+ ...(isGrok ? {
2969
+ allowedExtensions: ['mp4'],
2970
+ } : selectedVideoModel === 'minimax-h3' ? {
2900
2971
  allowedExtensions: ['mp4', 'mov'],
2901
2972
  minSide: MINIMAX_H3_MIN_VIDEO_SIDE,
2902
2973
  maxSide: MINIMAX_H3_MAX_VIDEO_SIDE,
2903
2974
  minAspect: SEEDANCE_MIN_VIDEO_ASPECT,
2904
2975
  maxAspect: SEEDANCE_MAX_VIDEO_ASPECT,
2976
+ } : isWan30 ? {
2977
+ minSide: 240,
2978
+ maxSide: MULEROUTER_WAN_MAX_VIDEO_SIDE,
2979
+ minAspect: 0.125,
2980
+ maxAspect: 8,
2981
+ allowedExtensions: ['mp4', 'mov'],
2905
2982
  } : isSeedanceModel ? {
2906
2983
  minFramePixels: SEEDANCE_MIN_VIDEO_FRAME_PIXELS,
2907
2984
  minSide: SEEDANCE_MIN_VIDEO_SIDE,
@@ -2930,21 +3007,22 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2930
3007
  }
2931
3008
  // Standalone MCP tool (no project timeline write)
2932
3009
  process.stderr.write('🎬 Submitting video...\n');
2933
- const resolvedOperation = videoOperation || (isSeedance25 && videoUrls.length ? 'edit' : 'generate');
3010
+ const resolvedOperation = inferredOperation;
2934
3011
  const vArgs = isSyncLipsync
2935
3012
  ? { script, images, videoUrls, audioUrls, videoModel: selectedVideoModel, videoResolution }
2936
- : isSeedance25
2937
- ? { script, images, videoUrls, audioUrls, videoModel: selectedVideoModel, videoResolution, operation: resolvedOperation, extendDirection, outputFormat, generateAudio, contentFilter, webSearch }
3013
+ : isSeedance25 || isWan30 || isGrok
3014
+ ? { script, images, videoUrls, audioUrls, referenceVoiceIds: referenceVoices, videoModel: selectedVideoModel, videoResolution, operation: resolvedOperation, extendDirection, outputFormat, generateAudio, contentFilter, webSearch }
2938
3015
  : isMinimaxH3
2939
3016
  ? { script, images, videoUrls, audioUrls, videoModel: selectedVideoModel, videoResolution }
2940
3017
  : videoUrls[0]
2941
3018
  ? { videoUrl: videoUrls[0], editPrompt: script, images, videoModel: selectedVideoModel, videoResolution, referType: isSeedanceModel ? 'feature' : 'base' }
2942
3019
  : { script, images, videoModel: selectedVideoModel, videoResolution };
3020
+ if (inputVideoMeta?.duration) vArgs.referenceVideoDuration = inputVideoMeta.duration;
2943
3021
  const effectiveDuration = resolvedOperation === 'edit' ? undefined : duration || (inputVideoMeta?.duration ? Math.min(providerMaxDuration, Math.round(inputVideoMeta.duration)) : undefined);
2944
3022
  if (effectiveDuration) vArgs.duration = effectiveDuration;
2945
3023
  if (aspectRatio) vArgs.aspectRatio = aspectRatio;
2946
3024
  if (keepOriginalSound && videoUrls.length && !isSeedance25) vArgs.keepOriginalSound = true;
2947
- const result = await callMcpTool(baseUrl, headers, videoUrls.length && !isSeedance25 && !isMinimaxH3 && !isSyncLipsync ? 'makaron_edit_video' : 'makaron_create_video', vArgs);
3025
+ const result = await callMcpTool(baseUrl, headers, videoUrls.length && !isSeedance25 && !isWan30 && !isGrok && !isMinimaxH3 && !isSyncLipsync ? 'makaron_edit_video' : 'makaron_create_video', vArgs);
2948
3026
  const text = result?.content?.find(c => c.type === 'text')?.text;
2949
3027
  if (text) {
2950
3028
  console.log(text);
@@ -2988,9 +3066,11 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2988
3066
  console.log(`Video commands:
2989
3067
  video script --image <file> [--image <file>] "direction" Write video script
2990
3068
  video create --script "..." --video-model seedance-fast Native text-to-video (no image required)
3069
+ video create --script "..." --video-model wan-3.0 Wan 3.0 Standard via MuleRouter
3070
+ video create --script "..." --video-model wan-3.0-pro Wan 3.0 Pro super-resolution via MuleRouter
2991
3071
  video create --script "..." --video-model minimax-h3 MiniMax H3 text-to-video (default 768P)
2992
3072
  video create --script "..." --image <url> [--duration 10] Submit video task
2993
- video create --script "..." --video <public-url> [--video-model seedance-fast|seedance-mini|seedance|seedance-2.5|kling|google-omni|minimax-h3] Edit/reference a video (Grok does not support video refs)
3073
+ video create --script "..." --video <file|url> --video-model grok [--operation edit|extend] Edit or extend one MP4 with Grok
2994
3074
  video create --script "Use the supplied audio" --video <url> --audio <url> --video-model sync-lipsync-v3 Lip-sync exact replacement audio
2995
3075
  video status <taskId> Check video status
2996
3076
  video status --snapshot <snapshotId> [--wait] Check v2 video snapshot
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "makaron-cli",
3
- "version": "0.13.9",
3
+ "version": "0.14.1",
4
4
  "description": "Talk to Makaron Agent from the terminal — create projects, edit images, generate videos",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -85,10 +85,11 @@ npx makaron-cli chat --project auto --image photo.jpg --json -b "make it cinemat
85
85
  npx makaron-cli chat --project auto --image img1.jpg --image img2.jpg --json -b "combine these"
86
86
  ```
87
87
 
88
- `chat` routes image and video models automatically. Use `--agent-model` only when the user explicitly asks to select or compare the reasoning/tool-calling Agent LLM. Accepted values are exactly `auto`, `gpt-5.6-terra`, `gpt-5.6-sol`, `gpt-5.6-luna`, `grok-4.5`, and `deepseek-v4-pro`; `auto` currently resolves to `gpt-5.6-terra`. Never put an image or video model ID in `--agent-model`. The CLI rejects unknown Agent IDs plus `--image-model`, `--video-model`, and legacy `--model` before starting a chat run.
88
+ `chat` routes image and video models automatically. Use `--agent-model` only when the user explicitly asks to select or compare the reasoning/tool-calling Agent LLM. Accepted values are `auto`, the base model IDs (`gpt-5.6-terra`, `gpt-5.6-sol`, `gpt-5.6-luna`, `grok-4.6`, `deepseek-v4-pro`), and the personal-plan routes (`gpt-5.6-terra-codex-subscription`, `gpt-5.6-sol-codex-subscription`, `gpt-5.6-luna-codex-subscription`). For the configured owner, `auto` uses GPT-5.6 Terra through the personal Codex plan; base GPT-5.6 IDs select Azure API, while suffixed IDs explicitly select the personal plan. Never put an image or video model ID in `--agent-model`.
89
89
 
90
90
  ```bash
91
91
  npx makaron-cli chat --project auto --agent-model deepseek-v4-pro --json -b "make a 20s badminton video"
92
+ npx makaron-cli chat --project auto --agent-model gpt-5.6-sol-codex-subscription --json -b "reply with the active model"
92
93
  ```
93
94
 
94
95
  Returns immediately:
@@ -180,16 +181,17 @@ npx makaron-cli project media <projectId> --json
180
181
 
181
182
  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
183
 
183
- Publish an external video interval directly into that Media List without uploading the original or a derivative MP4:
184
+ Publish typed external images and video intervals directly into that Media List without uploading the original media:
184
185
 
185
186
  ```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
187
+ npx makaron-cli project media add <projectId> --type image --source-url "https://cdn.example.com/product.jpg" --description "Hero product image"
188
+ 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"
189
+ npx makaron-cli project media add <projectId> --input media.json --json
188
190
  ```
189
191
 
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.
192
+ 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
193
 
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.
194
+ 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
195
 
194
196
  ```bash
195
197
  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 +294,12 @@ npx makaron-cli edit --image photo.jpg --ref style.jpg "match this style"
292
294
 
293
295
  # Output to file
294
296
  npx makaron-cli edit --image photo.jpg --out result.jpg "make it dramatic"
297
+
298
+ # Strict transparent output through GPT Image 2
299
+ npx makaron-cli edit --image-model openai --background transparent --out sticker.png "a magenta star sticker"
295
300
  ```
296
301
 
297
- Options: `--image`, `--image-model gemini|gemini-lite|qwen|openai|pony|wai`, `--ref <file>` (up to 3), `--aspect <ratio>`, `--out <path>`
302
+ 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
303
 
299
304
  ### `video` — Standalone video tools (no project timeline)
300
305
 
@@ -305,11 +310,11 @@ npx makaron-cli video script --image img1.jpg "cinematic story"
305
310
  # 2. Analyze a video (standalone, no timeline write)
306
311
  npx makaron-cli analyze --video input.mp4 "describe the key actions and pacing"
307
312
 
308
- # 3a. Submit image-to-video rendering (images must be public URLs from step 1 or uploaded)
313
+ # 3a. Submit reference-to-video rendering (images must be public URLs from step 1 or uploaded)
309
314
  npx makaron-cli video create --script "Shot 1 (5s): <<<image_1>>> ..." --image https://...jpg --duration 5 --video-model kling
310
315
  npx makaron-cli video create --script "Shot 1 (15s): <<<image_1>>> and <<<image_2>>> build a neon one-person studio" --image https://...jpg --image https://...webp --duration 15 --video-model seedance-mini --video-resolution 480p --aspect 9:16
311
316
 
312
- # 3b. Native SeeDance or MiniMax H3 text-to-video (no image required)
317
+ # 3b. Native SeeDance, Wan 3.0, or MiniMax H3 text-to-video (no image required)
313
318
  npx makaron-cli video create --script "Shot 1 (5s): A neon one-person studio wakes at dawn" --duration 5 --video-model seedance-fast --aspect 16:9
314
319
  npx makaron-cli video create --script "Shot 1 (15s): A premium creative editor comes alive" --duration 15 --video-model minimax-h3 --aspect 16:9
315
320
 
@@ -327,11 +332,15 @@ npx makaron-cli video status <taskId>
327
332
  npx makaron-cli chat --project <id|auto> --video input.mp4 -b "make it funny"
328
333
  ```
329
334
 
330
- Options for `video create`: `--script "..."`, `--script-file <path>`, `--image <url>` (repeatable, up to the selected model limit), `--video <file|url>` and `--audio <file|url>` (repeatable where supported), `--duration <seconds>`, `--aspect 9:16|16:9|1:1`, `--video-model seedance-fast|seedance-mini|seedance|seedance-2.5|kling|grok|google-omni|minimax-h3|sync-lipsync-v3`, `--video-resolution auto|480p|720p|768p|1080p|2k|4k`. Default model is `seedance-fast`. SeeDance accepts native text-to-video with no image and integer output duration 4-15s (default 5s); `seedance-mini` supports 480p/720p and is best for cheaper drafts/multi-size tests; MiniMax H3 accepts native text-to-video, 4-15s output, public 768p/2k resolution, and up to 9 image, up to 3 video, and up to 3 audio references through Makaron Agent/chat. `sync-lipsync-v3` requires exactly one video plus one MP3/WAV and preserves that replacement audio while aligning the mouth. H3 defaults to 768p; request 2k explicitly for maximum/final quality. Kling supports 5-15s; Grok 1.5 supports 1-15s single-image-to-video only; Gemini Omni supports 3-10s fast 720p image/video generation and editing with native generated audio, including up to 6 image references when no video reference is provided. For `--video-model grok`, forced `--aspect` is ignored to avoid xAI stretching the source image; pad/create the image at the target shape first or use another model.
335
+ Options for `video create`: `--script "..."`, `--script-file <path>`, `--image <url>` (repeatable, up to the selected model limit), `--video <file|url>` and `--audio <file|url>` (repeatable where supported), `--voice <xai-preset-id>` (repeatable, Grok only), `--duration <seconds>`, `--aspect 9:16|16:9|1:1`, `--video-model seedance-fast|seedance-mini|seedance|seedance-2.5|wan-3.0|wan-3.0-pro|kling|grok|google-omni|minimax-h3|sync-lipsync-v3`, `--video-resolution auto|480p|720p|768p|1080p|2k|4k`. Default model is `seedance-fast`. SeeDance accepts native text-to-video with no image and integer output duration 4-15s (default 5s); every Seedance image input uses reference-to-video, including one image. `seedance-mini` supports 480p/720p and is best for cheaper drafts/multi-size tests. Wan 3.0 Standard supports 480p/720p/1080p and Wan 3.0 Pro supports 1080p/2K/4K through MuleRouter. MiniMax H3 accepts native text-to-video, 4-15s output, public 768p/2k resolution, and up to 9 image, up to 3 video, and up to 3 audio feature references through Makaron Agent/chat; a single image keeps the `reference_image` role. `sync-lipsync-v3` requires exactly one video plus one MP3/WAV and preserves that replacement audio while aligning the mouth. H3 defaults to 768p; request 2k explicitly for maximum/final quality. Kling supports 5-15s. Grok text-only generation supports 480p/720p/1080p; any 1-7 image or preset voice input uses reference-to-video and is capped at 720p. Grok edit/extend uses `grok-imagine-video` internally. Gemini Omni image-only generation always uses `reference_to_video`, including one image, with up to 6 images when no video reference is provided.
336
+
337
+ Provider integration contract: every image passed to video generation is a feature reference by default, even when there is exactly one image. Never infer image-to-video/first-frame mode from image count. A first-frame workflow may be added only as a separately declared model capability and an explicit caller request.
331
338
 
332
339
  Seedance 2.5 uses `--video-model seedance-2.5` and supports 4-30s at 480p/720p, up to 30 images + 10 videos + 10 audios, repeatable local/URL references, `--video-operation generate|edit|extend`, `--extend-direction`, `--output-format mp4|mov`, and `--web-search`. The Evolink route does not currently expose 4K output.
333
340
 
334
- Video edit model behavior: `--video-model kling --video` uses Kling base/direct edit internally; `--video-model seedance-fast --video`, `--video-model seedance-mini --video`, or `--video-model seedance --video` uses the SeeDance video-reference path and requires target <=15s, <=50MB, width/height 300-6000px, aspect ratio 0.4-2.5, and frame pixels 409,600-2,086,876. `--video-model minimax-h3 --video` uses H3 feature/reference mode: up to 3 video references totaling <=15s, each <=50MB with width/height 256-5760px and aspect ratio 0.4-2.5. `--video-model google-omni --video` uses Gemini Omni direct video editing and accepts one reference video in Makaron. Output duration is clamped to 3-10s. Grok does not support video references.
341
+ Wan 3.0 uses `--video-model wan-3.0` for MuleRouter Standard at 480p/720p/1080p, or `--video-model wan-3.0-pro` for MuleRouter Pro super-resolution at 1080p/2K/4K. Both support 2-30s generation, up to 10 images + 5 videos + 5 audios, and native audio. Use generation mode with feature references; typed edit/extend and the relaxed content-filter flag are not supported.
342
+
343
+ Video edit model behavior: `--video-model kling --video` uses Kling base/direct edit internally; `--video-model seedance-fast --video`, `--video-model seedance-mini --video`, or `--video-model seedance --video` uses the SeeDance video-reference path and requires target <=15s, <=50MB, width/height 300-6000px, aspect ratio 0.4-2.5, and frame pixels 409,600-2,086,876. `--video-model minimax-h3 --video` uses H3 feature/reference mode: up to 3 video references totaling <=15s, each <=50MB with width/height 256-5760px and aspect ratio 0.4-2.5. `--video-model google-omni --video` uses Gemini Omni direct video editing and accepts one reference video in Makaron. `--video-model grok --video --operation edit` accepts one MP4 up to 8.7s and caps output at 720p; `--operation extend` accepts one 2-15s MP4 and adds 2-10s.
335
344
 
336
345
  ### `music` — Music generation
337
346
 
@@ -465,7 +474,7 @@ send_message "All done!"
465
474
  - One project = one conversation thread. All history is preserved.
466
475
  - One active Agent Run at a time per project. A new message received while it is active is appended to that same Agent Run and processed at a durable work-unit boundary; it does not interrupt the execution or create a second owner for an in-progress Studio workflow.
467
476
  - Multi-image: `create --image a.jpg --image b.jpg` or `chat --image ref.jpg`.
468
- - Provider-generated videos can take 3-5 minutes; Grok is usually around 30-40 seconds; Gemini Omni is usually around 30-70 seconds plus Storage handoff. Remotion compositions should be converted with `materialize` / `responses get --materialize`, and timing should be read from `duration_seconds`, `render_seconds`, and `realtime_ratio`.
477
+ - Provider-generated videos can take 3-5 minutes; current Grok generation/edit probes are usually around 15-60 seconds; Gemini Omni is usually around 30-70 seconds plus Storage handoff. Remotion compositions should be converted with `materialize` / `responses get --materialize`, and timing should be read from `duration_seconds`, `render_seconds`, and `realtime_ratio`.
469
478
  - Music takes ~60 seconds. Appears in output when done.
470
479
  - Images are typically ready in 15-30 seconds.
471
480
  - stdout is always machine-readable JSON/text. Human-friendly logs go to stderr.