makaron-cli 0.12.2 → 0.13.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.11.5",
3
+ "version": "0.13.1",
4
4
  "description": "AI image editing, video generation, music creation, and marketplace skill workflows via CLI. Agents can self-register, install skills, create projects, and produce creative media.",
5
5
  "author": {
6
6
  "name": "Makaron AI",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "makaron-cli",
3
- "version": "0.11.5",
3
+ "version": "0.13.1",
4
4
  "description": "AI image editing, video generation, music creation, and marketplace skill workflows via CLI. Agents can self-register, install skills, create projects, and produce creative media.",
5
5
  "displayName": "Makaron",
6
6
  "shortDescription": "AI image/video/music creation from the terminal",
package/README.md CHANGED
@@ -90,8 +90,13 @@ npx makaron-cli chat --project <id> --json -b "<prompt>"
90
90
  # Auto-create project (with or without images)
91
91
  npx makaron-cli chat --project auto --image photo.jpg --json -b "make it cinematic"
92
92
  npx makaron-cli chat --project auto --image img1.jpg --image img2.jpg --json -b "combine these"
93
+
94
+ # Choose each model role explicitly
95
+ npx makaron-cli chat --project auto --agent-model deepseek-v4-pro --image-model qwen "design a product poster"
93
96
  ```
94
97
 
98
+ Model flags are role-specific: `--agent-model` controls reasoning and tool use, `--image-model` controls image generation/editing, and `--video-model` controls video generation. `--agent-model` accepts `auto|gpt-5.6-terra|gpt-5.6-sol|gpt-5.6-luna|grok-4.5|deepseek-v4-pro`. `auto` currently uses GPT-5.6 Terra. `MAKARON_AGENT_MODEL` can set the default for automation; the command flag takes precedence. The legacy `--model` flag remains temporarily supported with a deprecation warning.
99
+
95
100
  Returns immediately:
96
101
  ```json
97
102
  {"runId": "xxx", "projectId": "...", "projectUrl": "https://www.makaron.app/projects/...", "status": "running"}
@@ -280,7 +285,7 @@ npx makaron-cli edit --image photo.jpg --ref style.jpg "match this style"
280
285
  npx makaron-cli edit --image photo.jpg --out result.jpg "make it dramatic"
281
286
  ```
282
287
 
283
- Options: `--image`, `--model gemini|gemini-lite|qwen|openai|pony|wai`, `--ref <file>` (up to 3), `--aspect <ratio>`, `--out <path>`
288
+ Options: `--image`, `--image-model gemini|gemini-lite|qwen|openai|pony|wai`, `--ref <file>` (up to 3), `--aspect <ratio>`, `--out <path>`
284
289
 
285
290
  ### `video` — Standalone video tools (no project timeline)
286
291
 
@@ -292,13 +297,16 @@ npx makaron-cli video script --image img1.jpg "cinematic story"
292
297
  npx makaron-cli analyze --video input.mp4 "describe the key actions and pacing"
293
298
 
294
299
  # 3a. Submit image-to-video rendering (images must be public URLs from step 1 or uploaded)
295
- npx makaron-cli video create --script "Shot 1 (5s): <<<image_1>>> ..." --image https://...jpg --duration 5 --model kling
296
- npx makaron-cli video create --script "Shot 1 (5s): <<<image_1>>> slow cinematic push-in with native ambience" --image https://...jpg --duration 5 --model grok
297
- 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 --model seedance-mini --video-resolution 480p --aspect 9:16
300
+ npx makaron-cli video create --script "Shot 1 (5s): <<<image_1>>> ..." --image https://...jpg --duration 5 --video-model kling
301
+ 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
302
+ 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
303
+
304
+ # 3b. Native SeeDance text-to-video (no image required)
305
+ 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
298
306
 
299
- # 3b. Edit a video from a local file or public URL
300
- npx makaron-cli video create --script "make it funny" --video input.mp4 --duration 5 --model seedance-fast
301
- npx makaron-cli video create --script "make it warmer and cinematic" --video https://example.com/input.mp4 --duration 5 --model seedance --video-resolution 1080p
307
+ # 3c. Edit a video from a local file or public URL
308
+ npx makaron-cli video create --script "make it funny" --video input.mp4 --duration 5 --video-model seedance-fast
309
+ 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
302
310
 
303
311
  # 4. Check status
304
312
  npx makaron-cli video status <taskId>
@@ -310,9 +318,9 @@ For project/timeline video editing, use:
310
318
  npx makaron-cli chat --project <id|auto> --video input.mp4 -b "make it funny"
311
319
  ```
312
320
 
313
- Options for `video create`: `--script "..."`, `--script-file <path>`, `--image <url>` (repeatable, up to 7), `--video <file|url>`, `--duration <seconds>`, `--aspect 9:16|16:9|1:1`, `--model seedance-fast|seedance-mini|seedance|kling|grok|google-omni`, `--video-resolution auto|480p|720p|1080p|4k`. Default model is `seedance-fast`. SeeDance accepts integer output duration 4-15s (default 5s); `seedance-mini` supports 480p/720p and is best for cheaper drafts/multi-size tests; 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. For `--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.
321
+ Options for `video create`: `--script "..."`, `--script-file <path>`, `--image <url>` (repeatable, up to 7), `--video <file|url>`, `--duration <seconds>`, `--aspect 9:16|16:9|1:1`, `--video-model seedance-fast|seedance-mini|seedance|kling|grok|google-omni`, `--video-resolution auto|480p|720p|1080p|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; 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.
314
322
 
315
- Video edit model behavior: `--model kling --video` uses Kling base/direct edit internally; `--model seedance-fast --video`, `--model seedance-mini --video`, or `--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. `--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.
323
+ 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 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.
316
324
 
317
325
  ### `music` — Music generation
318
326
 
@@ -443,8 +451,23 @@ Admin commands require an API key with admin privileges. Ask your admin to run `
443
451
 
444
452
  ```bash
445
453
  npx makaron-cli admin skills
454
+ npx makaron-cli admin skills --json
455
+ ```
456
+
457
+ The human-readable list includes assigned categories plus title/prompt locale completeness.
458
+
459
+ ### Manage marketplace categories
460
+
461
+ ```bash
462
+ npx makaron-cli admin skill-categories
463
+ npx makaron-cli admin skill-categories --json
464
+ npx makaron-cli admin skill-categories add '{"id":"portrait-effects","labels":{"en":"Portrait Effects","zh":"人像特效","zh-Hant":"人像特效","ja":"ポートレート効果"},"icon":"✨","sort_order":10}'
465
+ npx makaron-cli admin skill-categories update portrait-effects '{"icon":"📸"}'
466
+ npx makaron-cli admin skill-categories delete portrait-effects
446
467
  ```
447
468
 
469
+ Deleting a category is blocked while any marketplace skill still uses it.
470
+
448
471
  ### Upload assets to Storage
449
472
 
450
473
  ```bash
@@ -467,9 +490,10 @@ Storage paths follow this convention:
467
490
 
468
491
  ```bash
469
492
  npx makaron-cli admin skills add '{
470
- "labels": {"zh": "中文名", "en": "English Name"},
493
+ "labels": {"en": "English Name", "zh": "中文名", "zh-Hant": "繁體名稱", "ja": "日本語名"},
471
494
  "image": "https://sdyrtztrjgmmpnirswxt.supabase.co/storage/v1/object/public/images/marketplace/covers/skill-name.jpg",
472
- "prompt": "Default prompt shown to users",
495
+ "prompts": {"en": "Default prompt", "zh": "默认提示词", "zh-Hant": "預設提示詞", "ja": "デフォルトプロンプト"},
496
+ "categories": ["portrait-effects"],
473
497
  "skill_path": "https://sdyrtztrjgmmpnirswxt.supabase.co/storage/v1/object/public/images/marketplace/skills/skill-name.zip",
474
498
  "image_count": 2,
475
499
  "sort_order": 10,
@@ -478,6 +502,8 @@ npx makaron-cli admin skills add '{
478
502
  }'
479
503
  ```
480
504
 
505
+ New skills require all four localized titles, all four localized default prompts, and at least one existing category. Partial `labels` or `prompts` updates are merged with existing locales, so updating one language does not erase the others.
506
+
481
507
  ### Update / delete a skill
482
508
 
483
509
  ```bash
@@ -512,7 +538,7 @@ npx makaron-cli admin fetch-skill https://www.makaron.app/s/4c4cbd57
512
538
  ```
513
539
  6. **Add to marketplace**:
514
540
  ```bash
515
- npx makaron-cli admin skills add '{"labels":{"zh":"...","en":"..."},"image":"<cover_url>","skill_path":"<zip_url>","before_images":["<before_url>"],"image_count":2,"sort_order":10,"is_active":true}'
541
+ npx makaron-cli admin skills add '{"labels":{"en":"...","zh":"...","zh-Hant":"...","ja":"..."},"prompts":{"en":"...","zh":"...","zh-Hant":"...","ja":"..."},"categories":["portrait-effects"],"image":"<cover_url>","skill_path":"<zip_url>","before_images":["<before_url>"],"image_count":2,"sort_order":10,"is_active":true}'
516
542
  ```
517
543
  7. **Verify** — visit https://www.makaron.app/home and confirm the skill appears, can be clicked, and works
518
544
 
package/bin/makaron.mjs CHANGED
@@ -27,6 +27,7 @@ const APP_URL = process.env.MAKARON_APP_URL || DEFAULT_URL;
27
27
  const NPM_PACKAGE_NAME = 'makaron-cli';
28
28
  const UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
29
29
  const UPDATE_CHECK_TIMEOUT_MS = 400;
30
+ const AGENT_MODELS = ['auto', 'gpt-5.6-terra', 'gpt-5.6-sol', 'gpt-5.6-luna', 'grok-4.5', 'deepseek-v4-pro'];
30
31
 
31
32
  // Public anon key (safe to embed — only enables auth, not data access)
32
33
  const SUPABASE_URL = 'https://sdyrtztrjgmmpnirswxt.supabase.co';
@@ -50,6 +51,18 @@ const SEEDANCE_MAX_VIDEO_SIDE = 6000;
50
51
  const SEEDANCE_MIN_VIDEO_ASPECT = 0.4;
51
52
  const SEEDANCE_MAX_VIDEO_ASPECT = 2.5;
52
53
 
54
+ function warnLegacyModelFlag(replacement) {
55
+ process.stderr.write(`⚠️ --model is deprecated here; use ${replacement}.\n`);
56
+ }
57
+
58
+ function validateAgentModel(value) {
59
+ if (!AGENT_MODELS.includes(value)) {
60
+ process.stderr.write(`❌ Unknown agent model: ${value}\nChoose one of: ${AGENT_MODELS.join(', ')}\n`);
61
+ process.exit(1);
62
+ }
63
+ return value;
64
+ }
65
+
53
66
  function getCliVersion() {
54
67
  try {
55
68
  const pkg = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
@@ -277,8 +290,9 @@ Options:
277
290
  --video <file|url> Attach a video to the project timeline. Repeatable.
278
291
  --audio <file|url> Attach a song, beat, or voice reference. MP3/WAV, repeatable.
279
292
  --skill <id|label|name> Use an installed skill or auto-install a matched marketplace skill.
280
- --model <name> Preferred image/model route.
293
+ --image-model <name> Image model: gemini, gemini-lite, qwen, openai, pony, or wai.
281
294
  --video-model <name> Preferred video model: seedance-fast, seedance-mini, seedance, kling, grok, or google-omni.
295
+ --agent-model <name> Agent model: auto, gpt-5.6-terra, gpt-5.6-sol, gpt-5.6-luna, grok-4.5, or deepseek-v4-pro.
282
296
  --video-resolution <res> Video resolution: auto, 480p, 720p, 1080p, or 4k.
283
297
  --background, -b Submit and print a runId.
284
298
  --json Output structured JSON.
@@ -333,7 +347,7 @@ async function abortRun(baseUrl, headers, runId) {
333
347
  } catch { /* best effort */ }
334
348
  }
335
349
 
336
- async function streamAgent(baseUrl, headers, projectId, prompt) {
350
+ async function streamAgent(baseUrl, headers, projectId, prompt, opts = {}) {
337
351
  const controller = new AbortController();
338
352
  const res = await fetch(`${baseUrl}/api/agent`, {
339
353
  method: 'POST',
@@ -345,6 +359,10 @@ async function streamAgent(baseUrl, headers, projectId, prompt) {
345
359
  projectId,
346
360
  prompt,
347
361
  headless: true,
362
+ ...(opts.preferredModel ? { preferredModel: opts.preferredModel } : {}),
363
+ ...(opts.videoModel ? { videoModel: opts.videoModel } : {}),
364
+ ...(opts.videoResolution ? { videoResolution: opts.videoResolution } : {}),
365
+ ...(opts.agentModel && opts.agentModel !== 'auto' ? { agentModel: opts.agentModel } : {}),
348
366
  }),
349
367
  signal: controller.signal,
350
368
  });
@@ -452,6 +470,7 @@ async function streamAgent(baseUrl, headers, projectId, prompt) {
452
470
  async function submitRun(baseUrl, headers, projectId, prompt, opts = {}) {
453
471
  const body = { projectId, prompt };
454
472
  if (opts.preferredModel) body.preferredModel = opts.preferredModel;
473
+ if (opts.agentModel && opts.agentModel !== 'auto') body.agentModel = opts.agentModel;
455
474
  if (opts.videoModel) body.videoModel = opts.videoModel;
456
475
  if (opts.videoResolution) body.videoResolution = opts.videoResolution;
457
476
  if (opts.currentSnapshotIndex != null) body.currentSnapshotIndex = opts.currentSnapshotIndex;
@@ -971,6 +990,17 @@ function timeSince(date) {
971
990
 
972
991
  // ─── Skill Marketplace ──────────────────────────────────────────────────────
973
992
 
993
+ const MARKETPLACE_LOCALES = ['en', 'zh', 'zh-Hant', 'ja'];
994
+
995
+ function localizedValues(value) {
996
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return [];
997
+ return Object.values(value).filter(item => typeof item === 'string' && item.trim());
998
+ }
999
+
1000
+ function localizedCoverage(value) {
1001
+ return MARKETPLACE_LOCALES.filter(locale => typeof value?.[locale] === 'string' && value[locale].trim()).length;
1002
+ }
1003
+
974
1004
  function getSkillLabel(skill) {
975
1005
  return skill.labels?.en || skill.labels?.zh || skill.label || skill.name || skill.id;
976
1006
  }
@@ -1013,15 +1043,15 @@ async function fetchMarketplaceSkills(baseUrl, opts = {}) {
1013
1043
  }
1014
1044
 
1015
1045
  function marketplaceSearchText(skill) {
1046
+ const localized = [...localizedValues(skill.labels), ...localizedValues(skill.prompts)];
1016
1047
  return [
1017
1048
  skill.id,
1018
1049
  skill.label,
1019
- skill.labels?.en,
1020
- skill.labels?.zh,
1050
+ ...localized,
1021
1051
  skill.prompt,
1052
+ ...(Array.isArray(skill.categories) ? skill.categories : []),
1022
1053
  slugifySkill(skill.label),
1023
- slugifySkill(skill.labels?.en),
1024
- slugifySkill(skill.labels?.zh),
1054
+ ...localized.map(slugifySkill),
1025
1055
  ].filter(Boolean).join(' ').toLowerCase();
1026
1056
  }
1027
1057
 
@@ -1029,9 +1059,10 @@ function marketplaceSkillTokens(skill) {
1029
1059
  return [
1030
1060
  skill.id,
1031
1061
  skill.label,
1032
- skill.labels?.en,
1033
- skill.labels?.zh,
1062
+ ...localizedValues(skill.labels),
1063
+ ...localizedValues(skill.prompts),
1034
1064
  skill.prompt,
1065
+ ...(Array.isArray(skill.categories) ? skill.categories : []),
1035
1066
  ].filter(Boolean).map(value => String(value).toLowerCase());
1036
1067
  }
1037
1068
 
@@ -1079,8 +1110,13 @@ function printMarketplaceSkill(skill) {
1079
1110
  console.log(`${skill.label}`);
1080
1111
  console.log(`ID: ${skill.id}`);
1081
1112
  console.log(`Type: ${skill.hasSkill ? 'installable skill' : 'prompt template'}`);
1082
- if (skill.labels?.zh && skill.labels.zh !== skill.label) console.log(`ZH: ${skill.labels.zh}`);
1083
- if (skill.prompt) console.log(`Prompt: ${skill.prompt}`);
1113
+ for (const locale of MARKETPLACE_LOCALES) {
1114
+ const label = skill.labels?.[locale];
1115
+ if (label && label !== skill.label) console.log(`${locale}: ${label}`);
1116
+ }
1117
+ if (Array.isArray(skill.categories) && skill.categories.length) console.log(`Categories: ${skill.categories.join(', ')}`);
1118
+ console.log(`i18n: titles ${localizedCoverage(skill.labels)}/4 · prompts ${localizedCoverage(skill.prompts)}/4`);
1119
+ if (skill.prompts?.en || skill.prompt) console.log(`Prompt (en): ${skill.prompts?.en || skill.prompt}`);
1084
1120
  if (skill.image) console.log(`Cover: ${skill.image}`);
1085
1121
  if (skill.hasSkill) console.log(`Install: makaron skills install ${skill.id}`);
1086
1122
  }
@@ -1551,9 +1587,26 @@ Commands:
1551
1587
 
1552
1588
  admin Admin commands (skills, upload, set-admin)
1553
1589
 
1590
+ Model selection:
1591
+ --agent-model <name> Reasoning/tool model: auto, gpt-5.6-terra, gpt-5.6-sol,
1592
+ gpt-5.6-luna, grok-4.5, or deepseek-v4-pro
1593
+ --image-model <name> Image model: gemini, gemini-lite, qwen, openai,
1594
+ pony, or wai
1595
+ --video-model <name> Video model: seedance-fast, seedance-mini, seedance,
1596
+ kling, grok, or google-omni
1597
+
1598
+ Examples:
1599
+ makaron chat --project auto --agent-model deepseek-v4-pro "plan a launch poster"
1600
+ makaron chat --project <id> --agent-model gpt-5.6-terra --image-model qwen "make it cinematic"
1601
+ makaron chat --project <id> --video-model seedance-fast "turn this into a short video"
1602
+
1603
+ Run makaron <command> --help for command-specific options.
1604
+ The legacy --model flag is deprecated; use the role-specific flags above.
1605
+
1554
1606
  Environment:
1555
- MAKARON_API_KEY API key (mk_live_xxx) — recommended for agents
1556
- MAKARON_URL API base (default: ${DEFAULT_URL})
1607
+ MAKARON_API_KEY API key (mk_live_xxx) — recommended for agents
1608
+ MAKARON_URL API base (default: ${DEFAULT_URL})
1609
+ MAKARON_AGENT_MODEL Default Agent model; --agent-model takes precedence
1557
1610
  `);
1558
1611
  }
1559
1612
 
@@ -1660,17 +1713,18 @@ Use with chat:
1660
1713
  composition status <jobId> [--wait] [--json]
1661
1714
  `);
1662
1715
  } else if (topic === 'edit') {
1663
- console.log('Usage: makaron edit [--image <file|url>] [--model gemini|gemini-lite|qwen|openai|pony|wai] [--skill enhance|creative|wild|captions] [--ref <file>] [--out <file>] "prompt"');
1716
+ 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"');
1664
1717
  } else if (topic === 'analyze') {
1665
1718
  console.log('Usage: makaron analyze --video <file|url> ["question"]');
1666
1719
  } else if (topic === 'video') {
1667
1720
  if (subtopic === 'script') console.log('Usage: makaron video script --image <file> [--image <file>] [--lang en|zh] "direction"');
1668
- else if (subtopic === 'create') console.log('Usage: makaron video create --script "..." (--image <url> | --video <public-url>) [--duration 10] [--aspect 9:16] [--model seedance-fast|seedance-mini|seedance|kling|grok|google-omni] [--video-resolution auto|480p|720p|1080p|4k] [--keep-original-sound]');
1721
+ else if (subtopic === 'create') console.log('Usage: makaron video create --script "..." [--image <url> | --video <public-url>] [--duration 10] [--aspect 9:16] [--video-model seedance-fast|seedance-mini|seedance|kling|grok|google-omni] [--video-resolution auto|480p|720p|1080p|4k] [--keep-original-sound]');
1669
1722
  else if (subtopic === 'status') console.log('Usage: makaron video status <taskId> | --snapshot <snapshotId> [--wait]');
1670
1723
  else console.log(`Video commands:
1671
1724
  video script --image <file> [--image <file>] "direction" Write video script
1725
+ video create --script "..." --video-model seedance-fast Native text-to-video (no image required)
1672
1726
  video create --script "..." --image <url> [--duration 10] Submit video task
1673
- video create --script "..." --video <public-url> [--model seedance-fast|seedance-mini|seedance|kling|google-omni] Edit a video (standalone; Grok does not support video refs)
1727
+ video create --script "..." --video <public-url> [--video-model seedance-fast|seedance-mini|seedance|kling|google-omni] Edit a video (standalone; Grok does not support video refs)
1674
1728
  video status <taskId> Check video status
1675
1729
  video status --snapshot <snapshotId> [--wait] Check v2 video snapshot
1676
1730
  `);
@@ -1682,7 +1736,8 @@ Use with chat:
1682
1736
  music status <taskId> Check music status
1683
1737
  `);
1684
1738
  } else if (topic === 'admin') {
1685
- if (subtopic === 'skills') console.log('Usage: makaron admin skills [add|update|delete] ...');
1739
+ if (subtopic === 'skills') console.log('Usage: makaron admin skills [--json|add|update|delete] ...');
1740
+ else if (subtopic === 'skill-categories') console.log('Usage: makaron admin skill-categories [--json|add|update|delete] ...');
1686
1741
  else if (subtopic === 'upload') console.log('Usage: makaron admin upload <local-file> <storage-path>');
1687
1742
  else if (subtopic === 'fetch-skill') console.log('Usage: makaron admin fetch-skill <share-code|url>');
1688
1743
  else if (subtopic === 'set-admin') console.log('Usage: makaron admin set-admin <email>');
@@ -1691,6 +1746,10 @@ Use with chat:
1691
1746
  admin skills add '<json>' Add a new skill
1692
1747
  admin skills update <id> '<json>' Update a skill
1693
1748
  admin skills delete <id> Delete a skill
1749
+ admin skill-categories List marketplace categories
1750
+ admin skill-categories add '<json>' Add a category
1751
+ admin skill-categories update <id> '<json>'
1752
+ admin skill-categories delete <id> Delete a category
1694
1753
  admin upload <file> <storage-path> Upload file to Storage
1695
1754
  admin fetch-skill <code|url> Download skill from share link
1696
1755
  admin set-admin <email> Grant admin access to a user
@@ -1756,6 +1815,7 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
1756
1815
  let videoModel = undefined;
1757
1816
  let videoResolution = undefined;
1758
1817
  let preferredModel = undefined;
1818
+ let agentModel = process.env.MAKARON_AGENT_MODEL || 'auto';
1759
1819
  for (let i = 1; i < args.length; i++) {
1760
1820
  if (args[i] === '--project' && args[i + 1]) projectId = args[++i];
1761
1821
  else if (args[i] === '--image' && args[i + 1]) chatImages.push(args[++i]);
@@ -1768,7 +1828,12 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
1768
1828
  else if (args[i] === '--json') jsonOutput = true;
1769
1829
  else if (args[i] === '--video-model' && args[i + 1]) videoModel = args[++i];
1770
1830
  else if (args[i] === '--video-resolution' && args[i + 1]) videoResolution = args[++i];
1771
- else if (args[i] === '--model' && args[i + 1]) preferredModel = args[++i];
1831
+ else if (args[i] === '--image-model' && args[i + 1]) preferredModel = args[++i];
1832
+ else if (args[i] === '--agent-model' && args[i + 1]) agentModel = args[++i];
1833
+ else if (args[i] === '--model' && args[i + 1]) {
1834
+ warnLegacyModelFlag('--image-model');
1835
+ preferredModel = args[++i];
1836
+ }
1772
1837
  else promptParts.push(args[i]);
1773
1838
  }
1774
1839
  const prompt = promptParts.join(' ');
@@ -1777,6 +1842,7 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
1777
1842
  console.error('Run: makaron chat --help');
1778
1843
  process.exit(1);
1779
1844
  }
1845
+ agentModel = validateAgentModel(agentModel);
1780
1846
  const { headers, baseUrl } = getAuth();
1781
1847
  // Split images into URLs vs local files
1782
1848
  const imageUrlList = chatImages.filter(p => p.startsWith('http://') || p.startsWith('https://'));
@@ -1962,7 +2028,7 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
1962
2028
 
1963
2029
  if (useStream) {
1964
2030
  // Legacy SSE mode
1965
- const { results } = await streamAgent(baseUrl, headers, projectId, finalPrompt);
2031
+ const { results } = await streamAgent(baseUrl, headers, projectId, finalPrompt, { videoModel, videoResolution, preferredModel, agentModel });
1966
2032
  process.stderr.write('\n━━━ Results ━━━\n');
1967
2033
  for (const img of results.images) process.stderr.write(`🖼️ Image: ${img.imageUrl}\n`);
1968
2034
  for (const d of results.designs) process.stderr.write(`🎨 ${d.desc}\n`);
@@ -1971,7 +2037,7 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
1971
2037
  for (const task of results.musicTasks) await pollMusic(baseUrl, headers, task.taskId);
1972
2038
  } else {
1973
2039
  // Default: fire-and-forget + poll
1974
- const { runId } = await submitRun(baseUrl, headers, projectId, finalPrompt, { videoModel, videoResolution, preferredModel, audioAttachments });
2040
+ const { runId } = await submitRun(baseUrl, headers, projectId, finalPrompt, { videoModel, videoResolution, preferredModel, agentModel, audioAttachments });
1975
2041
  if (background) {
1976
2042
  // Just print runId and exit
1977
2043
  if (jsonOutput) {
@@ -2238,7 +2304,11 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2238
2304
  let outputPath = null;
2239
2305
  for (let i = 1; i < args.length; i++) {
2240
2306
  if (args[i] === '--image' && args[i + 1]) editArgs.image = imageToArg(args[++i]);
2241
- else if (args[i] === '--model' && args[i + 1]) editArgs.model = args[++i];
2307
+ else if (args[i] === '--image-model' && args[i + 1]) editArgs.model = args[++i];
2308
+ else if (args[i] === '--model' && args[i + 1]) {
2309
+ warnLegacyModelFlag('--image-model');
2310
+ editArgs.model = args[++i];
2311
+ }
2242
2312
  else if (args[i] === '--skill' && args[i + 1]) editArgs.skill = args[++i];
2243
2313
  else if (args[i] === '--ref' && args[i + 1]) {
2244
2314
  editArgs.referenceImages = editArgs.referenceImages || [];
@@ -2249,7 +2319,7 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2249
2319
  else promptParts.push(args[i]);
2250
2320
  }
2251
2321
  editArgs.editPrompt = promptParts.join(' ');
2252
- if (!editArgs.editPrompt) { console.error('Usage: makaron edit [--image <file|url>] [--model gemini|gemini-lite|qwen|openai|pony|wai] [--ref <file>] [--out <file>] "prompt"'); process.exit(1); }
2322
+ 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); }
2253
2323
  process.stderr.write('🎨 Generating...\n');
2254
2324
  const result = await callMcpTool(baseUrl, headers, 'makaron_edit_image', editArgs);
2255
2325
  saveMcpImage(result, outputPath);
@@ -2294,7 +2364,11 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2294
2364
  else if (args[i] === '--script-file' && args[i + 1]) script = fs.readFileSync(args[++i], 'utf-8');
2295
2365
  else if (args[i] === '--duration' && args[i + 1]) duration = Number(args[++i]);
2296
2366
  else if (args[i] === '--aspect' && args[i + 1]) aspectRatio = args[++i];
2297
- else if (args[i] === '--model' && args[i + 1]) videoModel = args[++i];
2367
+ else if (args[i] === '--video-model' && args[i + 1]) videoModel = args[++i];
2368
+ else if (args[i] === '--model' && args[i + 1]) {
2369
+ warnLegacyModelFlag('--video-model');
2370
+ videoModel = args[++i];
2371
+ }
2298
2372
  else if (args[i] === '--video-resolution' && args[i + 1]) videoResolution = args[++i];
2299
2373
  else if (args[i] === '--resolution' && args[i + 1]) videoResolution = args[++i];
2300
2374
  else if (args[i] === '--keep-original-sound') keepOriginalSound = true;
@@ -2304,8 +2378,10 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2304
2378
  }
2305
2379
  else if (args[i] === '--wait') wait = true;
2306
2380
  }
2307
- if ((!images.length && !video) || !script) {
2308
- console.error('Usage: makaron video create --script "..." (--image <url> | --video <public-url>) [--duration 10] [--aspect 9:16] [--model seedance-fast|seedance-mini|seedance|kling|grok|google-omni] [--video-resolution auto|480p|720p|1080p|4k] [--keep-original-sound]');
2381
+ const selectedVideoModel = videoModel || 'seedance-fast';
2382
+ const isSeedanceModel = selectedVideoModel === 'seedance-fast' || selectedVideoModel === 'seedance-mini' || selectedVideoModel === 'seedance';
2383
+ if (!script || (!images.length && !video && !isSeedanceModel)) {
2384
+ console.error('Usage: makaron video create --script "..." [--image <url> | --video <public-url>] [--duration 10] [--aspect 9:16] [--video-model seedance-fast|seedance-mini|seedance|kling|grok|google-omni] [--video-resolution auto|480p|720p|1080p|4k] [--keep-original-sound]');
2309
2385
  process.exit(1);
2310
2386
  }
2311
2387
 
@@ -2316,7 +2392,6 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2316
2392
 
2317
2393
  let videoUrl = isHttpUrl(video) ? video : null;
2318
2394
  let inputVideoMeta = null;
2319
- const selectedVideoModel = videoModel || 'seedance-fast';
2320
2395
  if (videoUrl) {
2321
2396
  process.stderr.write(`📹 Assuming public video URL already matches provider reference limits. Seedance requires ≤${MAX_VIDEO_PROVIDER_REFERENCE_DURATION}s, ≤50MB, sides 300-6000px, frame pixels 409,600-${MAX_VIDEO_FRAME_PIXELS}; Kling requires ≤200MB and ≤2K; Google Omni accepts one reference video in Makaron; Grok does not support video references.\n`);
2322
2397
  }
@@ -2391,8 +2466,9 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2391
2466
  } else {
2392
2467
  console.log(`Video commands:
2393
2468
  video script --image <file> [--image <file>] "direction" Write video script
2469
+ video create --script "..." --video-model seedance-fast Native text-to-video (no image required)
2394
2470
  video create --script "..." --image <url> [--duration 10] Submit video task
2395
- video create --script "..." --video <public-url> [--model seedance-fast|seedance-mini|seedance|kling|google-omni] Edit a video (standalone; Grok does not support video refs)
2471
+ video create --script "..." --video <public-url> [--video-model seedance-fast|seedance-mini|seedance|kling|google-omni] Edit a video (standalone; Grok does not support video refs)
2396
2472
  video status <taskId> Check video status
2397
2473
  video status --snapshot <snapshotId> [--wait] Check v2 video snapshot
2398
2474
  `);
@@ -2438,18 +2514,23 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2438
2514
  const sub = args[1];
2439
2515
 
2440
2516
  if (sub === 'skills') {
2441
- const action = args[2]; // add, update, delete, or none (list)
2517
+ const action = args[2] === '--json' ? null : args[2]; // add, update, delete, or none (list)
2442
2518
  if (!action) {
2443
2519
  // List all skills
2444
2520
  const res = await fetch(`${baseUrl}/api/admin/home-skills`, { headers });
2445
2521
  if (!res.ok) { console.error(`Error ${res.status}:`, await res.text()); process.exit(1); }
2446
2522
  const skills = await res.json();
2523
+ if (args.includes('--json')) {
2524
+ console.log(JSON.stringify(skills, null, 2));
2525
+ process.exit(0);
2526
+ }
2447
2527
  console.log(`📋 ${skills.length} skills\n`);
2448
2528
  for (const s of skills) {
2449
2529
  const label = s.labels?.en || s.labels?.zh || '(no label)';
2450
2530
  const active = s.is_active ? '✅' : '❌';
2451
2531
  const hasZip = s.skill_path ? '📦' : ' ';
2452
- console.log(` ${active} ${hasZip} ${String(s.sort_order).padStart(3)} ${s.id} ${label}`);
2532
+ const categories = Array.isArray(s.categories) && s.categories.length ? s.categories.join(',') : 'uncategorized';
2533
+ console.log(` ${active} ${hasZip} ${String(s.sort_order).padStart(3)} ${s.id} ${label} [${categories}] title ${localizedCoverage(s.labels)}/4 · prompt ${localizedCoverage(s.prompts)}/4`);
2453
2534
  }
2454
2535
  } else if (action === 'add') {
2455
2536
  const json = args.slice(3).join(' ');
@@ -2487,6 +2568,59 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2487
2568
  process.exit(1);
2488
2569
  }
2489
2570
 
2571
+ } else if (sub === 'skill-categories') {
2572
+ const action = args[2] === '--json' ? null : args[2];
2573
+ if (!action) {
2574
+ const res = await fetch(`${baseUrl}/api/admin/skill-categories`, { headers });
2575
+ if (!res.ok) { console.error(`Error ${res.status}:`, await res.text()); process.exit(1); }
2576
+ const categories = await res.json();
2577
+ if (args.includes('--json')) {
2578
+ console.log(JSON.stringify(categories, null, 2));
2579
+ process.exit(0);
2580
+ }
2581
+ console.log(`📂 ${categories.length} skill categories\n`);
2582
+ for (const category of categories) {
2583
+ const label = category.labels?.en || category.labels?.zh || category.id;
2584
+ const active = category.is_active ? '✅' : '❌';
2585
+ const icon = category.icon || ' ';
2586
+ console.log(` ${active} ${icon} ${String(category.sort_order).padStart(3)} ${category.id} ${label} title ${localizedCoverage(category.labels)}/4`);
2587
+ }
2588
+ } else if (action === 'add') {
2589
+ const json = args.slice(3).join(' ');
2590
+ if (!json) { console.error('Usage: makaron admin skill-categories add \'{"id":"...","labels":{...}}\''); process.exit(1); }
2591
+ let body;
2592
+ try { body = JSON.parse(json); } catch { console.error('Invalid JSON'); process.exit(1); }
2593
+ const res = await fetch(`${baseUrl}/api/admin/skill-categories`, {
2594
+ method: 'POST', headers: { 'Content-Type': 'application/json', ...headers }, body: JSON.stringify(body),
2595
+ });
2596
+ if (!res.ok) { console.error(`Error ${res.status}:`, await res.text()); process.exit(1); }
2597
+ const data = await res.json();
2598
+ console.log(`✅ Skill category created: ${data.id}`);
2599
+ } else if (action === 'update') {
2600
+ const id = args[3];
2601
+ const json = args.slice(4).join(' ');
2602
+ if (!id || !json) { console.error('Usage: makaron admin skill-categories update <id> \'{"field":"value"}\''); process.exit(1); }
2603
+ let body;
2604
+ try { body = JSON.parse(json); } catch { console.error('Invalid JSON'); process.exit(1); }
2605
+ body.id = id;
2606
+ const res = await fetch(`${baseUrl}/api/admin/skill-categories`, {
2607
+ method: 'PUT', headers: { 'Content-Type': 'application/json', ...headers }, body: JSON.stringify(body),
2608
+ });
2609
+ if (!res.ok) { console.error(`Error ${res.status}:`, await res.text()); process.exit(1); }
2610
+ console.log(`✅ Skill category ${id} updated`);
2611
+ } else if (action === 'delete') {
2612
+ const id = args[3];
2613
+ if (!id) { console.error('Usage: makaron admin skill-categories delete <id>'); process.exit(1); }
2614
+ const res = await fetch(`${baseUrl}/api/admin/skill-categories`, {
2615
+ method: 'DELETE', headers: { 'Content-Type': 'application/json', ...headers }, body: JSON.stringify({ id }),
2616
+ });
2617
+ if (!res.ok) { console.error(`Error ${res.status}:`, await res.text()); process.exit(1); }
2618
+ console.log(`✅ Skill category ${id} deleted`);
2619
+ } else {
2620
+ console.error(`Unknown action: ${action}. Use: add, update, delete, or omit to list.`);
2621
+ process.exit(1);
2622
+ }
2623
+
2490
2624
  } else if (sub === 'upload') {
2491
2625
  const filePath = args[2];
2492
2626
  const storagePath = args[3];
@@ -2546,6 +2680,10 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2546
2680
  admin skills add '<json>' Add a new skill
2547
2681
  admin skills update <id> '<json>' Update a skill
2548
2682
  admin skills delete <id> Delete a skill
2683
+ admin skill-categories List marketplace categories
2684
+ admin skill-categories add '<json>' Add a category
2685
+ admin skill-categories update <id> '<json>'
2686
+ admin skill-categories delete <id> Delete a category
2549
2687
  admin upload <file> <storage-path> Upload file to Storage
2550
2688
  admin fetch-skill <code|url> Download skill from share link
2551
2689
  admin set-admin <email> Grant admin access to a user
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "makaron-cli",
3
- "version": "0.12.2",
3
+ "version": "0.13.1",
4
4
  "description": "Talk to Makaron Agent from the terminal — create projects, edit images, generate videos",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -77,8 +77,13 @@ npx makaron-cli chat --project <id> --json -b "<prompt>"
77
77
  # Auto-create project (with or without images)
78
78
  npx makaron-cli chat --project auto --image photo.jpg --json -b "make it cinematic"
79
79
  npx makaron-cli chat --project auto --image img1.jpg --image img2.jpg --json -b "combine these"
80
+
81
+ # Choose each model role explicitly
82
+ npx makaron-cli chat --project auto --agent-model deepseek-v4-pro --image-model qwen "design a product poster"
80
83
  ```
81
84
 
85
+ Model flags are role-specific: `--agent-model` controls reasoning and tool use, `--image-model` controls image generation/editing, and `--video-model` controls video generation. `--agent-model` accepts `auto|gpt-5.6-terra|gpt-5.6-sol|gpt-5.6-luna|grok-4.5|deepseek-v4-pro`. `auto` currently uses GPT-5.6 Terra. `MAKARON_AGENT_MODEL` can set the default for automation; the command flag takes precedence. The legacy `--model` flag remains temporarily supported with a deprecation warning.
86
+
82
87
  Returns immediately:
83
88
  ```json
84
89
  {"runId": "xxx", "projectId": "...", "projectUrl": "https://www.makaron.app/projects/...", "status": "running"}
@@ -121,7 +126,9 @@ npx makaron-cli chat --project auto \
121
126
  -b "make this with the selected skill"
122
127
  ```
123
128
 
124
- `--skill` accepts an installed skill name, a marketplace UUID, or a unique marketplace label. If a marketplace skill is matched, the CLI installs or reuses it and sends `[Active skill: <installed-skill-name>]` to Makaron Agent. Do not call admin skill commands for ordinary users. There is intentionally no user-facing CLI delete command for marketplace skills.
129
+ `--skill` accepts an installed skill name, a marketplace UUID, or a unique marketplace label. Marketplace search matches every localized title, every localized default prompt, and category ids. If a marketplace skill is matched, the CLI installs or reuses it and sends `[Active skill: <installed-skill-name>]` to Makaron Agent. Do not call admin skill commands for ordinary users. There is intentionally no user-facing CLI delete command for marketplace skills.
130
+
131
+ For an explicitly authorized marketplace administrator, `admin skills` shows category assignment and 4-locale title/prompt completeness, while `admin skill-categories` supports list/add/update/delete. New skills must include `en`, `zh`, `zh-Hant`, and `ja` in both `labels` and `prompts`, plus at least one existing `categories` id. Partial localized updates are safely merged server-side.
125
132
 
126
133
  ### With additional images (existing project)
127
134
 
@@ -238,7 +245,7 @@ npx makaron-cli edit --image photo.jpg --ref style.jpg "match this style"
238
245
  npx makaron-cli edit --image photo.jpg --out result.jpg "make it dramatic"
239
246
  ```
240
247
 
241
- Options: `--image`, `--model gemini|gemini-lite|qwen|openai|pony|wai`, `--ref <file>` (up to 3), `--aspect <ratio>`, `--out <path>`
248
+ Options: `--image`, `--image-model gemini|gemini-lite|qwen|openai|pony|wai`, `--ref <file>` (up to 3), `--aspect <ratio>`, `--out <path>`
242
249
 
243
250
  ### `video` — Standalone video tools (no project timeline)
244
251
 
@@ -250,12 +257,15 @@ npx makaron-cli video script --image img1.jpg "cinematic story"
250
257
  npx makaron-cli analyze --video input.mp4 "describe the key actions and pacing"
251
258
 
252
259
  # 3a. Submit image-to-video rendering (images must be public URLs from step 1 or uploaded)
253
- npx makaron-cli video create --script "Shot 1 (5s): <<<image_1>>> ..." --image https://...jpg --duration 5 --model kling
254
- 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 --model seedance-mini --video-resolution 480p --aspect 9:16
260
+ npx makaron-cli video create --script "Shot 1 (5s): <<<image_1>>> ..." --image https://...jpg --duration 5 --video-model kling
261
+ 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
262
+
263
+ # 3b. Native SeeDance text-to-video (no image required)
264
+ 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
255
265
 
256
- # 3b. Edit a video from a local file or public URL
257
- npx makaron-cli video create --script "make it funny" --video input.mp4 --duration 5 --model seedance-fast
258
- npx makaron-cli video create --script "make it warmer and cinematic" --video https://example.com/input.mp4 --duration 5 --model seedance --video-resolution 1080p
266
+ # 3c. Edit a video from a local file or public URL
267
+ npx makaron-cli video create --script "make it funny" --video input.mp4 --duration 5 --video-model seedance-fast
268
+ 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
259
269
 
260
270
  # 4. Check status
261
271
  npx makaron-cli video status <taskId>
@@ -267,9 +277,9 @@ npx makaron-cli video status <taskId>
267
277
  npx makaron-cli chat --project <id|auto> --video input.mp4 -b "make it funny"
268
278
  ```
269
279
 
270
- Options for `video create`: `--script "..."`, `--script-file <path>`, `--image <url>` (repeatable, up to 7), `--video <file|url>`, `--duration <seconds>`, `--aspect 9:16|16:9|1:1`, `--model seedance-fast|seedance-mini|seedance|kling|grok|google-omni`, `--video-resolution auto|480p|720p|1080p|4k`. Default model is `seedance-fast`. SeeDance accepts integer output duration 4-15s (default 5s); `seedance-mini` supports 480p/720p and is best for cheaper drafts/multi-size tests; 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. For `--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.
280
+ Options for `video create`: `--script "..."`, `--script-file <path>`, `--image <url>` (repeatable, up to 7), `--video <file|url>`, `--duration <seconds>`, `--aspect 9:16|16:9|1:1`, `--video-model seedance-fast|seedance-mini|seedance|kling|grok|google-omni`, `--video-resolution auto|480p|720p|1080p|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; 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.
271
281
 
272
- Video edit model behavior: `--model kling --video` uses Kling base/direct edit internally; `--model seedance-fast --video`, `--model seedance-mini --video`, or `--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. `--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.
282
+ 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 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.
273
283
 
274
284
  ### `music` — Music generation
275
285