makaron-cli 0.13.1 → 0.13.3

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.1",
3
+ "version": "0.13.3",
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.13.1",
3
+ "version": "0.13.3",
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
@@ -48,6 +48,12 @@ export MAKARON_API_KEY=mk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
48
48
 
49
49
  Verify: `npx makaron-cli list` should show projects.
50
50
 
51
+ Check the current credit balance and subscription:
52
+ ```bash
53
+ npx makaron-cli credits
54
+ npx makaron-cli credits --json
55
+ ```
56
+
51
57
  ### Let a human claim your account
52
58
 
53
59
  After registering, generate a link for a human to link your API key to their account:
@@ -90,12 +96,9 @@ npx makaron-cli chat --project <id> --json -b "<prompt>"
90
96
  # Auto-create project (with or without images)
91
97
  npx makaron-cli chat --project auto --image photo.jpg --json -b "make it cinematic"
92
98
  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"
96
99
  ```
97
100
 
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.
101
+ `chat` always routes agent, image, and video models automatically. Never pass `--agent-model`, `--image-model`, `--video-model`, or the legacy `--model` flag to `chat`; the CLI rejects them before starting a run. Model flags remain available only on explicit low-level commands such as `edit` and `video create`.
99
102
 
100
103
  Returns immediately:
101
104
  ```json
@@ -112,7 +115,7 @@ Returns immediately:
112
115
  | Fix one moment in a video from a screenshot | `npx makaron-cli chat --project <id> --image screenshot.png "@4 this frame should be Paris; only fix this moment"` |
113
116
  | Cut or assemble video | `npx makaron-cli chat --project <id> --video clip.mp4 "cut out the dead air and keep the best 20 seconds"` |
114
117
  | Add music | `npx makaron-cli chat --project <id> "add calm piano background music"` |
115
- | Beat-sync video from audio | `npx makaron-cli chat --project auto --audio beat.mp3 --video-model seedance-fast --video-resolution 480p "make a beat-synced video"` |
118
+ | Beat-sync video from audio | `npx makaron-cli chat --project auto --audio beat.mp3 --video-resolution 480p "make a beat-synced video"` |
116
119
  | Create motion design | `npx makaron-cli chat --project <id> "make an animated Instagram story with this image"` |
117
120
 
118
121
  ### Marketplace skills
@@ -213,7 +216,6 @@ Attach a short song, beat, or voice recording when the video should follow audio
213
216
  ```bash
214
217
  npx makaron-cli chat --project auto \
215
218
  --audio beat.mp3 \
216
- --video-model seedance-fast \
217
219
  --video-resolution 480p \
218
220
  -b "make a 15s beat-synced video"
219
221
  ```
package/bin/makaron.mjs CHANGED
@@ -27,7 +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
+ const AGENT_WAIT_TIMEOUT_SECONDS = Math.max(900, Number(process.env.MAKARON_AGENT_WAIT_TIMEOUT_SECONDS || 10_800));
31
31
 
32
32
  // Public anon key (safe to embed — only enables auth, not data access)
33
33
  const SUPABASE_URL = 'https://sdyrtztrjgmmpnirswxt.supabase.co';
@@ -55,14 +55,6 @@ function warnLegacyModelFlag(replacement) {
55
55
  process.stderr.write(`⚠️ --model is deprecated here; use ${replacement}.\n`);
56
56
  }
57
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
-
66
58
  function getCliVersion() {
67
59
  try {
68
60
  const pkg = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
@@ -290,15 +282,15 @@ Options:
290
282
  --video <file|url> Attach a video to the project timeline. Repeatable.
291
283
  --audio <file|url> Attach a song, beat, or voice reference. MP3/WAV, repeatable.
292
284
  --skill <id|label|name> Use an installed skill or auto-install a matched marketplace skill.
293
- --image-model <name> Image model: gemini, gemini-lite, qwen, openai, pony, or wai.
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.
296
285
  --video-resolution <res> Video resolution: auto, 480p, 720p, 1080p, or 4k.
297
286
  --background, -b Submit and print a runId.
298
287
  --json Output structured JSON.
299
288
  --stream Legacy live SSE stream.
300
289
  --help, -h Show this help.
301
290
 
291
+ Model routing is automatic in chat. Do not pass --agent-model, --image-model,
292
+ --video-model, or the legacy --model flag.
293
+
302
294
  What you can ask:
303
295
  Image edit
304
296
  makaron chat --project <id> --image photo.jpg "remove the person in the background"
@@ -322,7 +314,7 @@ What you can ask:
322
314
  makaron chat --project <id> "add calm piano background music"
323
315
 
324
316
  Reference audio / beat sync
325
- makaron chat --project auto --audio beat.mp3 --video-model seedance-fast --video-resolution 480p "用这个音乐做卡点视频"
317
+ makaron chat --project auto --audio beat.mp3 --video-resolution 480p "用这个音乐做卡点视频"
326
318
  makaron chat --project <id> --audio https://example.com/beat.mp3 "add this as the soundtrack"
327
319
 
328
320
  Motion design
@@ -359,10 +351,9 @@ async function streamAgent(baseUrl, headers, projectId, prompt, opts = {}) {
359
351
  projectId,
360
352
  prompt,
361
353
  headless: true,
362
- ...(opts.preferredModel ? { preferredModel: opts.preferredModel } : {}),
363
- ...(opts.videoModel ? { videoModel: opts.videoModel } : {}),
364
354
  ...(opts.videoResolution ? { videoResolution: opts.videoResolution } : {}),
365
- ...(opts.agentModel && opts.agentModel !== 'auto' ? { agentModel: opts.agentModel } : {}),
355
+ ...(opts.uploadedVideoCount ? { uploadedVideoCount: opts.uploadedVideoCount } : {}),
356
+ ...(opts.turnMediaCount ? { turnMediaCount: opts.turnMediaCount } : {}),
366
357
  }),
367
358
  signal: controller.signal,
368
359
  });
@@ -469,13 +460,12 @@ async function streamAgent(baseUrl, headers, projectId, prompt, opts = {}) {
469
460
 
470
461
  async function submitRun(baseUrl, headers, projectId, prompt, opts = {}) {
471
462
  const body = { projectId, prompt };
472
- if (opts.preferredModel) body.preferredModel = opts.preferredModel;
473
- if (opts.agentModel && opts.agentModel !== 'auto') body.agentModel = opts.agentModel;
474
- if (opts.videoModel) body.videoModel = opts.videoModel;
475
463
  if (opts.videoResolution) body.videoResolution = opts.videoResolution;
476
464
  if (opts.currentSnapshotIndex != null) body.currentSnapshotIndex = opts.currentSnapshotIndex;
477
465
  if (opts.isNsfw) body.isNsfw = opts.isNsfw;
478
466
  if (opts.audioAttachments?.length) body.audioAttachments = opts.audioAttachments;
467
+ if (opts.uploadedVideoCount) body.uploadedVideoCount = opts.uploadedVideoCount;
468
+ if (opts.turnMediaCount) body.turnMediaCount = opts.turnMediaCount;
479
469
 
480
470
  const res = await fetch(`${baseUrl}/api/agent/run`, {
481
471
  method: 'POST',
@@ -519,7 +509,7 @@ async function pollRun(baseUrl, headers, runId, opts = {}) {
519
509
  try {
520
510
  const res = await fetch(`${baseUrl}/api/agent/run/${runId}?${params}`, { headers });
521
511
  if (!res.ok) {
522
- if (elapsed > 800) { process.stderr.write(`\n❌ Timeout after ${elapsed}s\n`); process.exit(1); }
512
+ if (elapsed > AGENT_WAIT_TIMEOUT_SECONDS) { process.stderr.write(`\n❌ Timeout after ${elapsed}s\n`); process.exit(1); }
523
513
  continue;
524
514
  }
525
515
  data = await res.json();
@@ -569,6 +559,13 @@ async function pollRun(baseUrl, headers, runId, opts = {}) {
569
559
  case 'music_task':
570
560
  process.stderr.write(`\n🎵 Music submitted: ${ev.data?.taskId}\n`);
571
561
  break;
562
+ case 'studio_run': {
563
+ const stage = ev.data?.currentStage || ev.data?.current_stage || 'complete';
564
+ const recipe = ev.data?.recipe || 'studio';
565
+ const status = ev.data?.status || 'running';
566
+ process.stderr.write(`\nStudio Run: ${recipe} / ${stage} / ${status}\n`);
567
+ break;
568
+ }
572
569
  case 'error':
573
570
  process.stderr.write(`\n❌ Error: ${ev.data?.message}\n`);
574
571
  break;
@@ -580,6 +577,7 @@ async function pollRun(baseUrl, headers, runId, opts = {}) {
580
577
 
581
578
  // Check terminal status
582
579
  if (data.status === 'completed' || data.status === 'failed' || data.status === 'aborted') {
580
+ normalizeRunResponse(data);
583
581
  if (printedText && !json) process.stdout.write('\n');
584
582
  if (data.status === 'completed' && exportCompositions) {
585
583
  data = await exportAnimatedCompositionsFromRun(baseUrl, headers, data, {
@@ -611,7 +609,7 @@ async function pollRun(baseUrl, headers, runId, opts = {}) {
611
609
  process.stderr.write(`🔗 ${APP_URL}/projects/${data.projectId}\n`);
612
610
  }
613
611
 
614
- if (data.status === 'failed') process.exit(1);
612
+ if (data.status === 'failed' || data.status === 'aborted') process.exit(1);
615
613
  return data;
616
614
  }
617
615
  }
@@ -620,11 +618,15 @@ async function pollRun(baseUrl, headers, runId, opts = {}) {
620
618
  // ─── Pick Helper ────────────────────────────────────────────────────────────
621
619
 
622
620
  function applyPick(data, field) {
621
+ const videoUrls = [...new Set([
622
+ ...(data.output || []).filter(o => o.type === 'video' && o.url).map(o => o.url),
623
+ ...(data.result?.videos || []).filter(v => v.videoUrl).map(v => v.videoUrl),
624
+ ])];
623
625
  switch (field) {
624
626
  case 'first_image_url': return data.output?.find(o => o.type === 'image')?.url || null;
625
627
  case 'image_urls': return (data.output || []).filter(o => o.type === 'image' && o.url).map(o => o.url);
626
- case 'first_video_url': return data.output?.find(o => o.type === 'video' && o.url)?.url || null;
627
- case 'video_urls': return (data.output || []).filter(o => o.type === 'video' && o.url).map(o => o.url);
628
+ case 'first_video_url': return videoUrls[0] || null;
629
+ case 'video_urls': return videoUrls;
628
630
  case 'first_design_url': return data.output?.find(o => o.type === 'design')?.url || null;
629
631
  case 'design_urls': return (data.output || []).filter(o => o.type === 'design' && o.url).map(o => o.url);
630
632
  case 'first_music_url': return data.output?.find(o => o.type === 'music' && o.url)?.url || null;
@@ -635,6 +637,8 @@ function applyPick(data, field) {
635
637
  description: action.description,
636
638
  source: action.source,
637
639
  }));
640
+ case 'studio_run': return [...(data.output || [])].reverse().find(o => o.type === 'studio_run') || null;
641
+ case 'studio_recipe': return [...(data.output || [])].reverse().find(o => o.type === 'studio_run')?.recipe || null;
638
642
  case 'project_url': return data.project_url || data.projectUrl || null;
639
643
  case 'output': return data.output || [];
640
644
  case 'text': return data.output?.find(o => o.type === 'text')?.content || null;
@@ -656,7 +660,7 @@ async function watchRun(baseUrl, headers, runId, opts = {}) {
656
660
  try {
657
661
  const res = await fetch(`${baseUrl}/api/agent/run/${runId}`, { headers });
658
662
  if (!res.ok) {
659
- if (elapsed > 800) { process.stderr.write(`Timeout after ${elapsed}s\n`); process.exit(2); }
663
+ if (elapsed > AGENT_WAIT_TIMEOUT_SECONDS) { process.stderr.write(`Timeout after ${elapsed}s\n`); process.exit(2); }
660
664
  await new Promise(r => setTimeout(r, interval));
661
665
  continue;
662
666
  }
@@ -1042,6 +1046,33 @@ async function fetchMarketplaceSkills(baseUrl, opts = {}) {
1042
1046
  return skills.map(normalizeMarketplaceSkill);
1043
1047
  }
1044
1048
 
1049
+ async function fetchBuiltInSkills(baseUrl) {
1050
+ const res = await fetch(`${baseUrl}/api/skills?include=internal`);
1051
+ if (!res.ok) {
1052
+ process.stderr.write(`Error ${res.status}: ${await res.text()}\n`);
1053
+ process.exit(1);
1054
+ }
1055
+ const data = await res.json();
1056
+ return (data.skills || []).filter(skill => skill.builtIn);
1057
+ }
1058
+
1059
+ function printBuiltInSkills(skills) {
1060
+ if (!skills.length) {
1061
+ console.log('No built-in skills found.');
1062
+ return;
1063
+ }
1064
+ console.log(`Built-in skills: ${skills.length}\n`);
1065
+ for (const skill of skills) {
1066
+ const recipe = skill.studioRunRecipe ? ` [Studio Run: ${skill.studioRunRecipe}]` : '';
1067
+ const source = skill.sourceMediaRequired ? ' [source media required]' : '';
1068
+ const adapter = skill.sourceProject === 'openmontage'
1069
+ ? ` [OpenMontage: ${skill.supportLevel || 'adapted'}${skill.canonicalSkill && skill.canonicalSkill !== skill.name ? ` -> ${skill.canonicalSkill}` : ''}]`
1070
+ : '';
1071
+ console.log(` ${skill.name}${recipe}${source}${adapter}`);
1072
+ if (skill.description) console.log(` ${String(skill.description).replace(/\s+/g, ' ').trim()}`);
1073
+ }
1074
+ }
1075
+
1045
1076
  function marketplaceSearchText(skill) {
1046
1077
  const localized = [...localizedValues(skill.labels), ...localizedValues(skill.prompts)];
1047
1078
  return [
@@ -1554,6 +1585,7 @@ Commands:
1554
1585
  register --verify --challenge-id <id> --answer <n> Verify and save API key
1555
1586
  claim Get claim URL for human to link account
1556
1587
  login Log in to Makaron (human interactive)
1588
+ credits Show current credit balance
1557
1589
  list (ls) List all projects
1558
1590
  project media <projectId> --json List timeline media for a project
1559
1591
  create --image <file> Create project from local image
@@ -1561,7 +1593,7 @@ Commands:
1561
1593
  create --title "name" Create empty project (text-to-image)
1562
1594
 
1563
1595
  chat --project <id> "message" Chat (non-blocking, polls for result)
1564
- chat --project <id> --skill <id> Use or auto-install a marketplace skill
1596
+ chat --project <id> --skill <id> Use a built-in or marketplace skill
1565
1597
  chat --project <id> --video <file> Attach video to conversation
1566
1598
  chat --project <id> --audio <file> Attach song/beat/voice reference
1567
1599
  chat --project <id> -b "message" Background: submit and print runId
@@ -1578,7 +1610,7 @@ Commands:
1578
1610
  Export editable Remotion composition to MP4
1579
1611
  responses list --project <id> List runs for a project
1580
1612
  abort <runId> Abort a running Agent
1581
- skills list|search|show|install Browse and install marketplace skills
1613
+ skills list|search|show|install Browse built-in and marketplace skills
1582
1614
 
1583
1615
  edit [--image <file>] "prompt" AI image edit / text-to-image
1584
1616
  analyze --video <file|url> Analyze video content
@@ -1587,26 +1619,17 @@ Commands:
1587
1619
 
1588
1620
  admin Admin commands (skills, upload, set-admin)
1589
1621
 
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
1622
  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"
1623
+ makaron chat --project auto "plan a launch poster"
1624
+ makaron chat --project <id> "make it cinematic"
1625
+ makaron chat --project <id> "turn this into a short video"
1602
1626
 
1603
1627
  Run makaron <command> --help for command-specific options.
1604
- The legacy --model flag is deprecated; use the role-specific flags above.
1628
+ Chat chooses agent, image, and video models automatically.
1605
1629
 
1606
1630
  Environment:
1607
1631
  MAKARON_API_KEY API key (mk_live_xxx) — recommended for agents
1608
1632
  MAKARON_URL API base (default: ${DEFAULT_URL})
1609
- MAKARON_AGENT_MODEL Default Agent model; --agent-model takes precedence
1610
1633
  `);
1611
1634
  }
1612
1635
 
@@ -1677,6 +1700,8 @@ function printHelp(topic, subtopic) {
1677
1700
  `);
1678
1701
  } else if (topic === 'list' || topic === 'ls') {
1679
1702
  console.log('Usage: makaron list');
1703
+ } else if (topic === 'credits' || topic === 'credit' || topic === 'balance') {
1704
+ console.log('Usage: makaron credits [--json]');
1680
1705
  } else if (topic === 'project' || topic === 'projects') {
1681
1706
  if (subtopic === 'media') console.log('Usage: makaron project media <projectId> [--json]');
1682
1707
  else console.log(`Project commands:
@@ -1689,13 +1714,15 @@ function printHelp(topic, subtopic) {
1689
1714
  } else if (topic === 'install-skill') {
1690
1715
  console.log('Usage: makaron install-skill [--global] [--agent <agent>] [--yes]');
1691
1716
  } else if (topic === 'skills') {
1692
- if (subtopic === 'list') console.log('Usage: makaron skills list [--json]');
1717
+ if (subtopic === 'list') console.log('Usage: makaron skills list [--built-in] [--json]');
1693
1718
  else if (subtopic === 'search') console.log('Usage: makaron skills search <query> [--json]');
1694
1719
  else if (subtopic === 'show') console.log('Usage: makaron skills show <marketplace-id|label> [--json]');
1695
1720
  else if (subtopic === 'install') console.log('Usage: makaron skills install <marketplace-id|label> [--json]');
1696
- else console.log(`Skill marketplace commands:
1721
+ else console.log(`Skill commands:
1722
+ skills list --built-in List all built-in Makaron skills and Studio Run recipes
1697
1723
  skills list List marketplace skills
1698
1724
  skills search <query> Search marketplace skills
1725
+ skills show <id|label> --built-in Show a built-in skill
1699
1726
  skills show <id|label> Show a marketplace skill
1700
1727
  skills install <id|label> Install a marketplace skill to your workspace
1701
1728
 
@@ -1783,6 +1810,29 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
1783
1810
  installAgentSkill(args.slice(1));
1784
1811
  } else if (command === 'login') {
1785
1812
  await login();
1813
+ } else if (command === 'credits' || command === 'credit' || command === 'balance') {
1814
+ const { headers, baseUrl } = getAuth();
1815
+ const res = await fetch(`${baseUrl}/api/billing/credits`, { headers });
1816
+ const data = await res.json().catch(() => null);
1817
+ if (!res.ok || !data) {
1818
+ const message = data?.error || data?.message || (res.ok ? 'Invalid response' : `HTTP ${res.status}`);
1819
+ process.stderr.write(`Failed to get credits: ${message}\n`);
1820
+ process.exit(1);
1821
+ }
1822
+ if (args.includes('--json')) {
1823
+ console.log(JSON.stringify(data));
1824
+ } else {
1825
+ console.log(`Credits: ${data.balance ?? 0}`);
1826
+ console.log(`Lifetime purchased: ${data.lifetimePurchased ?? 0}`);
1827
+ console.log(`Lifetime used: ${data.lifetimeUsed ?? 0}`);
1828
+ if (data.subscription) {
1829
+ const plan = data.subscription.planId || 'unknown';
1830
+ const status = data.subscription.status ? ` (${data.subscription.status})` : '';
1831
+ console.log(`Subscription: ${plan}${status}`);
1832
+ } else {
1833
+ console.log('Subscription: none');
1834
+ }
1835
+ }
1786
1836
  } else if (command === 'create') {
1787
1837
  const { headers, baseUrl } = getAuth();
1788
1838
  const opts = { images: [], imageUrls: [] };
@@ -1812,10 +1862,7 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
1812
1862
  let background = false;
1813
1863
  let jsonOutput = false;
1814
1864
  let activeSkill = undefined;
1815
- let videoModel = undefined;
1816
1865
  let videoResolution = undefined;
1817
- let preferredModel = undefined;
1818
- let agentModel = process.env.MAKARON_AGENT_MODEL || 'auto';
1819
1866
  for (let i = 1; i < args.length; i++) {
1820
1867
  if (args[i] === '--project' && args[i + 1]) projectId = args[++i];
1821
1868
  else if (args[i] === '--image' && args[i + 1]) chatImages.push(args[++i]);
@@ -1826,13 +1873,14 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
1826
1873
  else if (args[i] === '--stream') useStream = true;
1827
1874
  else if (args[i] === '--background' || args[i] === '-b') background = true;
1828
1875
  else if (args[i] === '--json') jsonOutput = true;
1829
- else if (args[i] === '--video-model' && args[i + 1]) videoModel = args[++i];
1830
1876
  else if (args[i] === '--video-resolution' && args[i + 1]) videoResolution = 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];
1877
+ else if (
1878
+ ['--agent-model', '--image-model', '--video-model', '--model'].includes(args[i])
1879
+ || ['--agent-model=', '--image-model=', '--video-model=', '--model='].some(prefix => args[i].startsWith(prefix))
1880
+ ) {
1881
+ const flag = args[i].split('=')[0];
1882
+ process.stderr.write(`❌ makaron chat chooses agent, image, and video models automatically. Remove ${flag} and retry.\n`);
1883
+ process.exit(1);
1836
1884
  }
1837
1885
  else promptParts.push(args[i]);
1838
1886
  }
@@ -1842,7 +1890,6 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
1842
1890
  console.error('Run: makaron chat --help');
1843
1891
  process.exit(1);
1844
1892
  }
1845
- agentModel = validateAgentModel(agentModel);
1846
1893
  const { headers, baseUrl } = getAuth();
1847
1894
  // Split images into URLs vs local files
1848
1895
  const imageUrlList = chatImages.filter(p => p.startsWith('http://') || p.startsWith('https://'));
@@ -1888,6 +1935,9 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
1888
1935
  process.exit(1);
1889
1936
  }
1890
1937
 
1938
+ let uploadedTurnMediaCount = 0;
1939
+ let uploadedTurnVideoCount = 0;
1940
+
1891
1941
  // --project auto: create a new project (with images/videos if provided)
1892
1942
  if (!projectId || projectId === 'auto') {
1893
1943
  // Create an empty project first, then attach media by URL. Local images use
@@ -1925,6 +1975,7 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
1925
1975
  process.exit(1);
1926
1976
  }
1927
1977
  process.stderr.write(`📤 Added ${addedCount} image(s) to project\n`);
1978
+ uploadedTurnMediaCount += addedCount;
1928
1979
  } else {
1929
1980
  process.stderr.write(`❌ Failed to add images: ${await res.text()}\n`);
1930
1981
  process.exit(1);
@@ -2016,19 +2067,22 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2016
2067
  const data = await res.json();
2017
2068
  const videoSnaps = (data.snapshots || []).filter(s => s.type === 'video');
2018
2069
  process.stderr.write(`📹 Added ${videoSnaps.length} video(s) to timeline\n`);
2070
+ uploadedTurnVideoCount += videoSnaps.length;
2071
+ uploadedTurnMediaCount += videoSnaps.length;
2019
2072
  } else {
2020
2073
  process.stderr.write(`⚠️ Failed to add videos: ${await res.text()}\n`);
2021
2074
  }
2022
2075
  }
2023
2076
 
2024
- // Inject hint so Agent knows videos are available
2025
- const hint = `[User uploaded ${chatVideos.length === 1 ? 'a video' : `${chatVideos.length} videos`}. Use analyze_video to understand the content.]`;
2026
- finalPrompt = `${finalPrompt}\n\n${hint}`;
2027
2077
  }
2028
2078
 
2029
2079
  if (useStream) {
2030
2080
  // Legacy SSE mode
2031
- const { results } = await streamAgent(baseUrl, headers, projectId, finalPrompt, { videoModel, videoResolution, preferredModel, agentModel });
2081
+ const { results } = await streamAgent(baseUrl, headers, projectId, finalPrompt, {
2082
+ videoResolution,
2083
+ uploadedVideoCount: uploadedTurnVideoCount,
2084
+ turnMediaCount: uploadedTurnMediaCount,
2085
+ });
2032
2086
  process.stderr.write('\n━━━ Results ━━━\n');
2033
2087
  for (const img of results.images) process.stderr.write(`🖼️ Image: ${img.imageUrl}\n`);
2034
2088
  for (const d of results.designs) process.stderr.write(`🎨 ${d.desc}\n`);
@@ -2037,7 +2091,12 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2037
2091
  for (const task of results.musicTasks) await pollMusic(baseUrl, headers, task.taskId);
2038
2092
  } else {
2039
2093
  // Default: fire-and-forget + poll
2040
- const { runId } = await submitRun(baseUrl, headers, projectId, finalPrompt, { videoModel, videoResolution, preferredModel, agentModel, audioAttachments });
2094
+ const { runId } = await submitRun(baseUrl, headers, projectId, finalPrompt, {
2095
+ videoResolution,
2096
+ audioAttachments,
2097
+ uploadedVideoCount: uploadedTurnVideoCount,
2098
+ turnMediaCount: uploadedTurnMediaCount,
2099
+ });
2041
2100
  if (background) {
2042
2101
  // Just print runId and exit
2043
2102
  if (jsonOutput) {
@@ -2225,8 +2284,10 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2225
2284
  const jsonOutput = args.includes('--json');
2226
2285
 
2227
2286
  if (sub === 'list') {
2228
- const skills = await fetchMarketplaceSkills(baseUrl);
2287
+ const builtIn = args.includes('--built-in');
2288
+ const skills = builtIn ? await fetchBuiltInSkills(baseUrl) : await fetchMarketplaceSkills(baseUrl);
2229
2289
  if (jsonOutput) console.log(JSON.stringify({ skills }, null, 2));
2290
+ else if (builtIn) printBuiltInSkills(skills);
2230
2291
  else printMarketplaceSkills(skills);
2231
2292
  } else if (sub === 'search') {
2232
2293
  const query = args.filter((arg, index) => index > 1 && arg !== '--json').join(' ').trim();
@@ -2243,11 +2304,15 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2243
2304
  else printMarketplaceSkills(skills);
2244
2305
  } else if (sub === 'show') {
2245
2306
  const identifier = args[2];
2246
- if (!identifier) { console.error('Usage: makaron skills show <marketplace-id|label> [--json]'); process.exit(1); }
2247
- const skills = await fetchMarketplaceSkills(baseUrl);
2248
- const skill = findMarketplaceSkill(skills, identifier);
2307
+ if (!identifier) { console.error('Usage: makaron skills show <id|label> [--built-in] [--json]'); process.exit(1); }
2308
+ const builtIn = args.includes('--built-in');
2309
+ const skills = builtIn ? await fetchBuiltInSkills(baseUrl) : await fetchMarketplaceSkills(baseUrl);
2310
+ const skill = builtIn
2311
+ ? skills.find(candidate => candidate.name === identifier || candidate.label?.toLowerCase() === identifier.toLowerCase())
2312
+ : findMarketplaceSkill(skills, identifier);
2249
2313
  if (!skill) { console.error(`Skill not found: ${identifier}`); process.exit(1); }
2250
2314
  if (jsonOutput) console.log(JSON.stringify(skill, null, 2));
2315
+ else if (builtIn) printBuiltInSkills([skill]);
2251
2316
  else printMarketplaceSkill(skill);
2252
2317
  } else if (sub === 'install') {
2253
2318
  const identifier = args[2];
@@ -2260,9 +2325,11 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2260
2325
  if (jsonOutput) console.log(JSON.stringify({ ...data, marketplaceId: skill.id, label: skill.label }, null, 2));
2261
2326
  else console.log(data.skillName);
2262
2327
  } else {
2263
- console.log(`Skill marketplace commands:
2328
+ console.log(`Skill commands:
2329
+ skills list --built-in List all built-in Makaron skills and Studio Run recipes
2264
2330
  skills list List marketplace skills
2265
2331
  skills search <query> Search marketplace skills
2332
+ skills show <id|label> --built-in Show a built-in skill
2266
2333
  skills show <id|label> Show a marketplace skill
2267
2334
  skills install <id|label> Install a marketplace skill to your workspace
2268
2335
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "makaron-cli",
3
- "version": "0.13.1",
3
+ "version": "0.13.3",
4
4
  "description": "Talk to Makaron Agent from the terminal — create projects, edit images, generate videos",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -44,6 +44,12 @@ export MAKARON_API_KEY=mk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
44
44
 
45
45
  Verify: `npx makaron-cli list` should show projects.
46
46
 
47
+ Check the current credit balance and subscription:
48
+ ```bash
49
+ npx makaron-cli credits
50
+ npx makaron-cli credits --json
51
+ ```
52
+
47
53
  ## Core Workflow
48
54
 
49
55
  ```bash
@@ -77,12 +83,9 @@ npx makaron-cli chat --project <id> --json -b "<prompt>"
77
83
  # Auto-create project (with or without images)
78
84
  npx makaron-cli chat --project auto --image photo.jpg --json -b "make it cinematic"
79
85
  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"
83
86
  ```
84
87
 
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.
88
+ `chat` always routes agent, image, and video models automatically. Never pass `--agent-model`, `--image-model`, `--video-model`, or the legacy `--model` flag to `chat`; the CLI rejects them before starting a run. Model flags remain available only on explicit low-level commands such as `edit` and `video create`.
86
89
 
87
90
  Returns immediately:
88
91
  ```json
@@ -99,7 +102,7 @@ Returns immediately:
99
102
  | Fix one moment in a video from a screenshot | `npx makaron-cli chat --project <id> --image screenshot.png "@4 this frame should be Paris; only fix this moment"` |
100
103
  | Cut or assemble video | `npx makaron-cli chat --project <id> --video clip.mp4 "cut out the dead air and keep the best 20 seconds"` |
101
104
  | Add music | `npx makaron-cli chat --project <id> "add calm piano background music"` |
102
- | Beat-sync video from audio | `npx makaron-cli chat --project auto --audio beat.mp3 --video-model seedance-fast --video-resolution 480p "make a beat-synced video"` |
105
+ | Beat-sync video from audio | `npx makaron-cli chat --project auto --audio beat.mp3 --video-resolution 480p "make a beat-synced video"` |
103
106
  | Create motion design | `npx makaron-cli chat --project <id> "make an animated Instagram story with this image"` |
104
107
 
105
108
  ### Marketplace skills
@@ -173,7 +176,6 @@ Attach a short song, beat, or voice recording when the video should follow audio
173
176
  ```bash
174
177
  npx makaron-cli chat --project auto \
175
178
  --audio beat.mp3 \
176
- --video-model seedance-fast \
177
179
  --video-resolution 480p \
178
180
  -b "make a 15s beat-synced video"
179
181
  ```