makaron-cli 0.13.0 → 0.13.2

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.2",
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.2",
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:
@@ -95,7 +101,7 @@ npx makaron-cli chat --project auto --image img1.jpg --image img2.jpg --json -b
95
101
  npx makaron-cli chat --project auto --agent-model deepseek-v4-pro --image-model qwen "design a product poster"
96
102
  ```
97
103
 
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|sonnet-4.6|sonnet-5|opus-4.8|grok-4.5|deepseek-v4-pro`. `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.
104
+ 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
105
 
100
106
  Returns immediately:
101
107
  ```json
@@ -451,8 +457,23 @@ Admin commands require an API key with admin privileges. Ask your admin to run `
451
457
 
452
458
  ```bash
453
459
  npx makaron-cli admin skills
460
+ npx makaron-cli admin skills --json
461
+ ```
462
+
463
+ The human-readable list includes assigned categories plus title/prompt locale completeness.
464
+
465
+ ### Manage marketplace categories
466
+
467
+ ```bash
468
+ npx makaron-cli admin skill-categories
469
+ npx makaron-cli admin skill-categories --json
470
+ npx makaron-cli admin skill-categories add '{"id":"portrait-effects","labels":{"en":"Portrait Effects","zh":"人像特效","zh-Hant":"人像特效","ja":"ポートレート効果"},"icon":"✨","sort_order":10}'
471
+ npx makaron-cli admin skill-categories update portrait-effects '{"icon":"📸"}'
472
+ npx makaron-cli admin skill-categories delete portrait-effects
454
473
  ```
455
474
 
475
+ Deleting a category is blocked while any marketplace skill still uses it.
476
+
456
477
  ### Upload assets to Storage
457
478
 
458
479
  ```bash
@@ -475,9 +496,10 @@ Storage paths follow this convention:
475
496
 
476
497
  ```bash
477
498
  npx makaron-cli admin skills add '{
478
- "labels": {"zh": "中文名", "en": "English Name"},
499
+ "labels": {"en": "English Name", "zh": "中文名", "zh-Hant": "繁體名稱", "ja": "日本語名"},
479
500
  "image": "https://sdyrtztrjgmmpnirswxt.supabase.co/storage/v1/object/public/images/marketplace/covers/skill-name.jpg",
480
- "prompt": "Default prompt shown to users",
501
+ "prompts": {"en": "Default prompt", "zh": "默认提示词", "zh-Hant": "預設提示詞", "ja": "デフォルトプロンプト"},
502
+ "categories": ["portrait-effects"],
481
503
  "skill_path": "https://sdyrtztrjgmmpnirswxt.supabase.co/storage/v1/object/public/images/marketplace/skills/skill-name.zip",
482
504
  "image_count": 2,
483
505
  "sort_order": 10,
@@ -486,6 +508,8 @@ npx makaron-cli admin skills add '{
486
508
  }'
487
509
  ```
488
510
 
511
+ 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.
512
+
489
513
  ### Update / delete a skill
490
514
 
491
515
  ```bash
@@ -520,7 +544,7 @@ npx makaron-cli admin fetch-skill https://www.makaron.app/s/4c4cbd57
520
544
  ```
521
545
  6. **Add to marketplace**:
522
546
  ```bash
523
- 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}'
547
+ 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}'
524
548
  ```
525
549
  7. **Verify** — visit https://www.makaron.app/home and confirm the skill appears, can be clicked, and works
526
550
 
package/bin/makaron.mjs CHANGED
@@ -27,7 +27,8 @@ 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', 'sonnet-4.6', 'sonnet-5', 'opus-4.8', 'grok-4.5', 'deepseek-v4-pro'];
30
+ const AGENT_MODELS = ['auto', 'gpt-5.6-terra', 'gpt-5.6-sol', 'gpt-5.6-luna', 'grok-4.5', 'deepseek-v4-pro'];
31
+ const AGENT_WAIT_TIMEOUT_SECONDS = Math.max(900, Number(process.env.MAKARON_AGENT_WAIT_TIMEOUT_SECONDS || 10_800));
31
32
 
32
33
  // Public anon key (safe to embed — only enables auth, not data access)
33
34
  const SUPABASE_URL = 'https://sdyrtztrjgmmpnirswxt.supabase.co';
@@ -292,7 +293,7 @@ Options:
292
293
  --skill <id|label|name> Use an installed skill or auto-install a matched marketplace skill.
293
294
  --image-model <name> Image model: gemini, gemini-lite, qwen, openai, pony, or wai.
294
295
  --video-model <name> Preferred video model: seedance-fast, seedance-mini, seedance, kling, grok, or google-omni.
295
- --agent-model <name> Agent model: auto, sonnet-4.6, sonnet-5, opus-4.8, grok-4.5, or deepseek-v4-pro.
296
+ --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
297
  --video-resolution <res> Video resolution: auto, 480p, 720p, 1080p, or 4k.
297
298
  --background, -b Submit and print a runId.
298
299
  --json Output structured JSON.
@@ -363,6 +364,8 @@ async function streamAgent(baseUrl, headers, projectId, prompt, opts = {}) {
363
364
  ...(opts.videoModel ? { videoModel: opts.videoModel } : {}),
364
365
  ...(opts.videoResolution ? { videoResolution: opts.videoResolution } : {}),
365
366
  ...(opts.agentModel && opts.agentModel !== 'auto' ? { agentModel: opts.agentModel } : {}),
367
+ ...(opts.uploadedVideoCount ? { uploadedVideoCount: opts.uploadedVideoCount } : {}),
368
+ ...(opts.turnMediaCount ? { turnMediaCount: opts.turnMediaCount } : {}),
366
369
  }),
367
370
  signal: controller.signal,
368
371
  });
@@ -476,6 +479,8 @@ async function submitRun(baseUrl, headers, projectId, prompt, opts = {}) {
476
479
  if (opts.currentSnapshotIndex != null) body.currentSnapshotIndex = opts.currentSnapshotIndex;
477
480
  if (opts.isNsfw) body.isNsfw = opts.isNsfw;
478
481
  if (opts.audioAttachments?.length) body.audioAttachments = opts.audioAttachments;
482
+ if (opts.uploadedVideoCount) body.uploadedVideoCount = opts.uploadedVideoCount;
483
+ if (opts.turnMediaCount) body.turnMediaCount = opts.turnMediaCount;
479
484
 
480
485
  const res = await fetch(`${baseUrl}/api/agent/run`, {
481
486
  method: 'POST',
@@ -519,7 +524,7 @@ async function pollRun(baseUrl, headers, runId, opts = {}) {
519
524
  try {
520
525
  const res = await fetch(`${baseUrl}/api/agent/run/${runId}?${params}`, { headers });
521
526
  if (!res.ok) {
522
- if (elapsed > 800) { process.stderr.write(`\n❌ Timeout after ${elapsed}s\n`); process.exit(1); }
527
+ if (elapsed > AGENT_WAIT_TIMEOUT_SECONDS) { process.stderr.write(`\n❌ Timeout after ${elapsed}s\n`); process.exit(1); }
523
528
  continue;
524
529
  }
525
530
  data = await res.json();
@@ -569,6 +574,13 @@ async function pollRun(baseUrl, headers, runId, opts = {}) {
569
574
  case 'music_task':
570
575
  process.stderr.write(`\n🎵 Music submitted: ${ev.data?.taskId}\n`);
571
576
  break;
577
+ case 'studio_run': {
578
+ const stage = ev.data?.currentStage || ev.data?.current_stage || 'complete';
579
+ const recipe = ev.data?.recipe || 'studio';
580
+ const status = ev.data?.status || 'running';
581
+ process.stderr.write(`\nStudio Run: ${recipe} / ${stage} / ${status}\n`);
582
+ break;
583
+ }
572
584
  case 'error':
573
585
  process.stderr.write(`\n❌ Error: ${ev.data?.message}\n`);
574
586
  break;
@@ -580,6 +592,7 @@ async function pollRun(baseUrl, headers, runId, opts = {}) {
580
592
 
581
593
  // Check terminal status
582
594
  if (data.status === 'completed' || data.status === 'failed' || data.status === 'aborted') {
595
+ normalizeRunResponse(data);
583
596
  if (printedText && !json) process.stdout.write('\n');
584
597
  if (data.status === 'completed' && exportCompositions) {
585
598
  data = await exportAnimatedCompositionsFromRun(baseUrl, headers, data, {
@@ -611,7 +624,7 @@ async function pollRun(baseUrl, headers, runId, opts = {}) {
611
624
  process.stderr.write(`🔗 ${APP_URL}/projects/${data.projectId}\n`);
612
625
  }
613
626
 
614
- if (data.status === 'failed') process.exit(1);
627
+ if (data.status === 'failed' || data.status === 'aborted') process.exit(1);
615
628
  return data;
616
629
  }
617
630
  }
@@ -620,11 +633,15 @@ async function pollRun(baseUrl, headers, runId, opts = {}) {
620
633
  // ─── Pick Helper ────────────────────────────────────────────────────────────
621
634
 
622
635
  function applyPick(data, field) {
636
+ const videoUrls = [...new Set([
637
+ ...(data.output || []).filter(o => o.type === 'video' && o.url).map(o => o.url),
638
+ ...(data.result?.videos || []).filter(v => v.videoUrl).map(v => v.videoUrl),
639
+ ])];
623
640
  switch (field) {
624
641
  case 'first_image_url': return data.output?.find(o => o.type === 'image')?.url || null;
625
642
  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);
643
+ case 'first_video_url': return videoUrls[0] || null;
644
+ case 'video_urls': return videoUrls;
628
645
  case 'first_design_url': return data.output?.find(o => o.type === 'design')?.url || null;
629
646
  case 'design_urls': return (data.output || []).filter(o => o.type === 'design' && o.url).map(o => o.url);
630
647
  case 'first_music_url': return data.output?.find(o => o.type === 'music' && o.url)?.url || null;
@@ -635,6 +652,8 @@ function applyPick(data, field) {
635
652
  description: action.description,
636
653
  source: action.source,
637
654
  }));
655
+ case 'studio_run': return [...(data.output || [])].reverse().find(o => o.type === 'studio_run') || null;
656
+ case 'studio_recipe': return [...(data.output || [])].reverse().find(o => o.type === 'studio_run')?.recipe || null;
638
657
  case 'project_url': return data.project_url || data.projectUrl || null;
639
658
  case 'output': return data.output || [];
640
659
  case 'text': return data.output?.find(o => o.type === 'text')?.content || null;
@@ -656,7 +675,7 @@ async function watchRun(baseUrl, headers, runId, opts = {}) {
656
675
  try {
657
676
  const res = await fetch(`${baseUrl}/api/agent/run/${runId}`, { headers });
658
677
  if (!res.ok) {
659
- if (elapsed > 800) { process.stderr.write(`Timeout after ${elapsed}s\n`); process.exit(2); }
678
+ if (elapsed > AGENT_WAIT_TIMEOUT_SECONDS) { process.stderr.write(`Timeout after ${elapsed}s\n`); process.exit(2); }
660
679
  await new Promise(r => setTimeout(r, interval));
661
680
  continue;
662
681
  }
@@ -990,6 +1009,17 @@ function timeSince(date) {
990
1009
 
991
1010
  // ─── Skill Marketplace ──────────────────────────────────────────────────────
992
1011
 
1012
+ const MARKETPLACE_LOCALES = ['en', 'zh', 'zh-Hant', 'ja'];
1013
+
1014
+ function localizedValues(value) {
1015
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return [];
1016
+ return Object.values(value).filter(item => typeof item === 'string' && item.trim());
1017
+ }
1018
+
1019
+ function localizedCoverage(value) {
1020
+ return MARKETPLACE_LOCALES.filter(locale => typeof value?.[locale] === 'string' && value[locale].trim()).length;
1021
+ }
1022
+
993
1023
  function getSkillLabel(skill) {
994
1024
  return skill.labels?.en || skill.labels?.zh || skill.label || skill.name || skill.id;
995
1025
  }
@@ -1031,16 +1061,43 @@ async function fetchMarketplaceSkills(baseUrl, opts = {}) {
1031
1061
  return skills.map(normalizeMarketplaceSkill);
1032
1062
  }
1033
1063
 
1064
+ async function fetchBuiltInSkills(baseUrl) {
1065
+ const res = await fetch(`${baseUrl}/api/skills?include=internal`);
1066
+ if (!res.ok) {
1067
+ process.stderr.write(`Error ${res.status}: ${await res.text()}\n`);
1068
+ process.exit(1);
1069
+ }
1070
+ const data = await res.json();
1071
+ return (data.skills || []).filter(skill => skill.builtIn);
1072
+ }
1073
+
1074
+ function printBuiltInSkills(skills) {
1075
+ if (!skills.length) {
1076
+ console.log('No built-in skills found.');
1077
+ return;
1078
+ }
1079
+ console.log(`Built-in skills: ${skills.length}\n`);
1080
+ for (const skill of skills) {
1081
+ const recipe = skill.studioRunRecipe ? ` [Studio Run: ${skill.studioRunRecipe}]` : '';
1082
+ const source = skill.sourceMediaRequired ? ' [source media required]' : '';
1083
+ const adapter = skill.sourceProject === 'openmontage'
1084
+ ? ` [OpenMontage: ${skill.supportLevel || 'adapted'}${skill.canonicalSkill && skill.canonicalSkill !== skill.name ? ` -> ${skill.canonicalSkill}` : ''}]`
1085
+ : '';
1086
+ console.log(` ${skill.name}${recipe}${source}${adapter}`);
1087
+ if (skill.description) console.log(` ${String(skill.description).replace(/\s+/g, ' ').trim()}`);
1088
+ }
1089
+ }
1090
+
1034
1091
  function marketplaceSearchText(skill) {
1092
+ const localized = [...localizedValues(skill.labels), ...localizedValues(skill.prompts)];
1035
1093
  return [
1036
1094
  skill.id,
1037
1095
  skill.label,
1038
- skill.labels?.en,
1039
- skill.labels?.zh,
1096
+ ...localized,
1040
1097
  skill.prompt,
1098
+ ...(Array.isArray(skill.categories) ? skill.categories : []),
1041
1099
  slugifySkill(skill.label),
1042
- slugifySkill(skill.labels?.en),
1043
- slugifySkill(skill.labels?.zh),
1100
+ ...localized.map(slugifySkill),
1044
1101
  ].filter(Boolean).join(' ').toLowerCase();
1045
1102
  }
1046
1103
 
@@ -1048,9 +1105,10 @@ function marketplaceSkillTokens(skill) {
1048
1105
  return [
1049
1106
  skill.id,
1050
1107
  skill.label,
1051
- skill.labels?.en,
1052
- skill.labels?.zh,
1108
+ ...localizedValues(skill.labels),
1109
+ ...localizedValues(skill.prompts),
1053
1110
  skill.prompt,
1111
+ ...(Array.isArray(skill.categories) ? skill.categories : []),
1054
1112
  ].filter(Boolean).map(value => String(value).toLowerCase());
1055
1113
  }
1056
1114
 
@@ -1098,8 +1156,13 @@ function printMarketplaceSkill(skill) {
1098
1156
  console.log(`${skill.label}`);
1099
1157
  console.log(`ID: ${skill.id}`);
1100
1158
  console.log(`Type: ${skill.hasSkill ? 'installable skill' : 'prompt template'}`);
1101
- if (skill.labels?.zh && skill.labels.zh !== skill.label) console.log(`ZH: ${skill.labels.zh}`);
1102
- if (skill.prompt) console.log(`Prompt: ${skill.prompt}`);
1159
+ for (const locale of MARKETPLACE_LOCALES) {
1160
+ const label = skill.labels?.[locale];
1161
+ if (label && label !== skill.label) console.log(`${locale}: ${label}`);
1162
+ }
1163
+ if (Array.isArray(skill.categories) && skill.categories.length) console.log(`Categories: ${skill.categories.join(', ')}`);
1164
+ console.log(`i18n: titles ${localizedCoverage(skill.labels)}/4 · prompts ${localizedCoverage(skill.prompts)}/4`);
1165
+ if (skill.prompts?.en || skill.prompt) console.log(`Prompt (en): ${skill.prompts?.en || skill.prompt}`);
1103
1166
  if (skill.image) console.log(`Cover: ${skill.image}`);
1104
1167
  if (skill.hasSkill) console.log(`Install: makaron skills install ${skill.id}`);
1105
1168
  }
@@ -1537,6 +1600,7 @@ Commands:
1537
1600
  register --verify --challenge-id <id> --answer <n> Verify and save API key
1538
1601
  claim Get claim URL for human to link account
1539
1602
  login Log in to Makaron (human interactive)
1603
+ credits Show current credit balance
1540
1604
  list (ls) List all projects
1541
1605
  project media <projectId> --json List timeline media for a project
1542
1606
  create --image <file> Create project from local image
@@ -1544,7 +1608,7 @@ Commands:
1544
1608
  create --title "name" Create empty project (text-to-image)
1545
1609
 
1546
1610
  chat --project <id> "message" Chat (non-blocking, polls for result)
1547
- chat --project <id> --skill <id> Use or auto-install a marketplace skill
1611
+ chat --project <id> --skill <id> Use a built-in or marketplace skill
1548
1612
  chat --project <id> --video <file> Attach video to conversation
1549
1613
  chat --project <id> --audio <file> Attach song/beat/voice reference
1550
1614
  chat --project <id> -b "message" Background: submit and print runId
@@ -1561,7 +1625,7 @@ Commands:
1561
1625
  Export editable Remotion composition to MP4
1562
1626
  responses list --project <id> List runs for a project
1563
1627
  abort <runId> Abort a running Agent
1564
- skills list|search|show|install Browse and install marketplace skills
1628
+ skills list|search|show|install Browse built-in and marketplace skills
1565
1629
 
1566
1630
  edit [--image <file>] "prompt" AI image edit / text-to-image
1567
1631
  analyze --video <file|url> Analyze video content
@@ -1571,8 +1635,8 @@ Commands:
1571
1635
  admin Admin commands (skills, upload, set-admin)
1572
1636
 
1573
1637
  Model selection:
1574
- --agent-model <name> Reasoning/tool model: auto, sonnet-4.6, sonnet-5,
1575
- opus-4.8, grok-4.5, or deepseek-v4-pro
1638
+ --agent-model <name> Reasoning/tool model: auto, gpt-5.6-terra, gpt-5.6-sol,
1639
+ gpt-5.6-luna, grok-4.5, or deepseek-v4-pro
1576
1640
  --image-model <name> Image model: gemini, gemini-lite, qwen, openai,
1577
1641
  pony, or wai
1578
1642
  --video-model <name> Video model: seedance-fast, seedance-mini, seedance,
@@ -1580,7 +1644,7 @@ Model selection:
1580
1644
 
1581
1645
  Examples:
1582
1646
  makaron chat --project auto --agent-model deepseek-v4-pro "plan a launch poster"
1583
- makaron chat --project <id> --agent-model sonnet-5 --image-model qwen "make it cinematic"
1647
+ makaron chat --project <id> --agent-model gpt-5.6-terra --image-model qwen "make it cinematic"
1584
1648
  makaron chat --project <id> --video-model seedance-fast "turn this into a short video"
1585
1649
 
1586
1650
  Run makaron <command> --help for command-specific options.
@@ -1660,6 +1724,8 @@ function printHelp(topic, subtopic) {
1660
1724
  `);
1661
1725
  } else if (topic === 'list' || topic === 'ls') {
1662
1726
  console.log('Usage: makaron list');
1727
+ } else if (topic === 'credits' || topic === 'credit' || topic === 'balance') {
1728
+ console.log('Usage: makaron credits [--json]');
1663
1729
  } else if (topic === 'project' || topic === 'projects') {
1664
1730
  if (subtopic === 'media') console.log('Usage: makaron project media <projectId> [--json]');
1665
1731
  else console.log(`Project commands:
@@ -1672,13 +1738,17 @@ function printHelp(topic, subtopic) {
1672
1738
  } else if (topic === 'install-skill') {
1673
1739
  console.log('Usage: makaron install-skill [--global] [--agent <agent>] [--yes]');
1674
1740
  } else if (topic === 'skills') {
1675
- if (subtopic === 'list') console.log('Usage: makaron skills list [--json]');
1741
+ if (subtopic === 'list') console.log('Usage: makaron skills list [--built-in] [--json]');
1676
1742
  else if (subtopic === 'search') console.log('Usage: makaron skills search <query> [--json]');
1677
1743
  else if (subtopic === 'show') console.log('Usage: makaron skills show <marketplace-id|label> [--json]');
1678
1744
  else if (subtopic === 'install') console.log('Usage: makaron skills install <marketplace-id|label> [--json]');
1679
- else console.log(`Skill marketplace commands:
1745
+ else console.log(`Skill commands:
1746
+ skills list --built-in List all built-in Makaron skills and Studio Run recipes
1747
+ skills list --built-in --openmontage
1748
+ List OpenMontage-native adapters only
1680
1749
  skills list List marketplace skills
1681
1750
  skills search <query> Search marketplace skills
1751
+ skills show <id|label> --built-in Show a built-in skill
1682
1752
  skills show <id|label> Show a marketplace skill
1683
1753
  skills install <id|label> Install a marketplace skill to your workspace
1684
1754
 
@@ -1719,7 +1789,8 @@ Use with chat:
1719
1789
  music status <taskId> Check music status
1720
1790
  `);
1721
1791
  } else if (topic === 'admin') {
1722
- if (subtopic === 'skills') console.log('Usage: makaron admin skills [add|update|delete] ...');
1792
+ if (subtopic === 'skills') console.log('Usage: makaron admin skills [--json|add|update|delete] ...');
1793
+ else if (subtopic === 'skill-categories') console.log('Usage: makaron admin skill-categories [--json|add|update|delete] ...');
1723
1794
  else if (subtopic === 'upload') console.log('Usage: makaron admin upload <local-file> <storage-path>');
1724
1795
  else if (subtopic === 'fetch-skill') console.log('Usage: makaron admin fetch-skill <share-code|url>');
1725
1796
  else if (subtopic === 'set-admin') console.log('Usage: makaron admin set-admin <email>');
@@ -1728,6 +1799,10 @@ Use with chat:
1728
1799
  admin skills add '<json>' Add a new skill
1729
1800
  admin skills update <id> '<json>' Update a skill
1730
1801
  admin skills delete <id> Delete a skill
1802
+ admin skill-categories List marketplace categories
1803
+ admin skill-categories add '<json>' Add a category
1804
+ admin skill-categories update <id> '<json>'
1805
+ admin skill-categories delete <id> Delete a category
1731
1806
  admin upload <file> <storage-path> Upload file to Storage
1732
1807
  admin fetch-skill <code|url> Download skill from share link
1733
1808
  admin set-admin <email> Grant admin access to a user
@@ -1761,6 +1836,29 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
1761
1836
  installAgentSkill(args.slice(1));
1762
1837
  } else if (command === 'login') {
1763
1838
  await login();
1839
+ } else if (command === 'credits' || command === 'credit' || command === 'balance') {
1840
+ const { headers, baseUrl } = getAuth();
1841
+ const res = await fetch(`${baseUrl}/api/billing/credits`, { headers });
1842
+ const data = await res.json().catch(() => null);
1843
+ if (!res.ok || !data) {
1844
+ const message = data?.error || data?.message || (res.ok ? 'Invalid response' : `HTTP ${res.status}`);
1845
+ process.stderr.write(`Failed to get credits: ${message}\n`);
1846
+ process.exit(1);
1847
+ }
1848
+ if (args.includes('--json')) {
1849
+ console.log(JSON.stringify(data));
1850
+ } else {
1851
+ console.log(`Credits: ${data.balance ?? 0}`);
1852
+ console.log(`Lifetime purchased: ${data.lifetimePurchased ?? 0}`);
1853
+ console.log(`Lifetime used: ${data.lifetimeUsed ?? 0}`);
1854
+ if (data.subscription) {
1855
+ const plan = data.subscription.planId || 'unknown';
1856
+ const status = data.subscription.status ? ` (${data.subscription.status})` : '';
1857
+ console.log(`Subscription: ${plan}${status}`);
1858
+ } else {
1859
+ console.log('Subscription: none');
1860
+ }
1861
+ }
1764
1862
  } else if (command === 'create') {
1765
1863
  const { headers, baseUrl } = getAuth();
1766
1864
  const opts = { images: [], imageUrls: [] };
@@ -1866,6 +1964,9 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
1866
1964
  process.exit(1);
1867
1965
  }
1868
1966
 
1967
+ let uploadedTurnMediaCount = 0;
1968
+ let uploadedTurnVideoCount = 0;
1969
+
1869
1970
  // --project auto: create a new project (with images/videos if provided)
1870
1971
  if (!projectId || projectId === 'auto') {
1871
1972
  // Create an empty project first, then attach media by URL. Local images use
@@ -1903,6 +2004,7 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
1903
2004
  process.exit(1);
1904
2005
  }
1905
2006
  process.stderr.write(`📤 Added ${addedCount} image(s) to project\n`);
2007
+ uploadedTurnMediaCount += addedCount;
1906
2008
  } else {
1907
2009
  process.stderr.write(`❌ Failed to add images: ${await res.text()}\n`);
1908
2010
  process.exit(1);
@@ -1994,19 +2096,25 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
1994
2096
  const data = await res.json();
1995
2097
  const videoSnaps = (data.snapshots || []).filter(s => s.type === 'video');
1996
2098
  process.stderr.write(`📹 Added ${videoSnaps.length} video(s) to timeline\n`);
2099
+ uploadedTurnVideoCount += videoSnaps.length;
2100
+ uploadedTurnMediaCount += videoSnaps.length;
1997
2101
  } else {
1998
2102
  process.stderr.write(`⚠️ Failed to add videos: ${await res.text()}\n`);
1999
2103
  }
2000
2104
  }
2001
2105
 
2002
- // Inject hint so Agent knows videos are available
2003
- const hint = `[User uploaded ${chatVideos.length === 1 ? 'a video' : `${chatVideos.length} videos`}. Use analyze_video to understand the content.]`;
2004
- finalPrompt = `${finalPrompt}\n\n${hint}`;
2005
2106
  }
2006
2107
 
2007
2108
  if (useStream) {
2008
2109
  // Legacy SSE mode
2009
- const { results } = await streamAgent(baseUrl, headers, projectId, finalPrompt, { videoModel, videoResolution, preferredModel, agentModel });
2110
+ const { results } = await streamAgent(baseUrl, headers, projectId, finalPrompt, {
2111
+ videoModel,
2112
+ videoResolution,
2113
+ preferredModel,
2114
+ agentModel,
2115
+ uploadedVideoCount: uploadedTurnVideoCount,
2116
+ turnMediaCount: uploadedTurnMediaCount,
2117
+ });
2010
2118
  process.stderr.write('\n━━━ Results ━━━\n');
2011
2119
  for (const img of results.images) process.stderr.write(`🖼️ Image: ${img.imageUrl}\n`);
2012
2120
  for (const d of results.designs) process.stderr.write(`🎨 ${d.desc}\n`);
@@ -2015,7 +2123,15 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2015
2123
  for (const task of results.musicTasks) await pollMusic(baseUrl, headers, task.taskId);
2016
2124
  } else {
2017
2125
  // Default: fire-and-forget + poll
2018
- const { runId } = await submitRun(baseUrl, headers, projectId, finalPrompt, { videoModel, videoResolution, preferredModel, agentModel, audioAttachments });
2126
+ const { runId } = await submitRun(baseUrl, headers, projectId, finalPrompt, {
2127
+ videoModel,
2128
+ videoResolution,
2129
+ preferredModel,
2130
+ agentModel,
2131
+ audioAttachments,
2132
+ uploadedVideoCount: uploadedTurnVideoCount,
2133
+ turnMediaCount: uploadedTurnMediaCount,
2134
+ });
2019
2135
  if (background) {
2020
2136
  // Just print runId and exit
2021
2137
  if (jsonOutput) {
@@ -2203,8 +2319,13 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2203
2319
  const jsonOutput = args.includes('--json');
2204
2320
 
2205
2321
  if (sub === 'list') {
2206
- const skills = await fetchMarketplaceSkills(baseUrl);
2322
+ const builtIn = args.includes('--built-in');
2323
+ let skills = builtIn ? await fetchBuiltInSkills(baseUrl) : await fetchMarketplaceSkills(baseUrl);
2324
+ if (builtIn && args.includes('--openmontage')) {
2325
+ skills = skills.filter(skill => skill.sourceProject === 'openmontage');
2326
+ }
2207
2327
  if (jsonOutput) console.log(JSON.stringify({ skills }, null, 2));
2328
+ else if (builtIn) printBuiltInSkills(skills);
2208
2329
  else printMarketplaceSkills(skills);
2209
2330
  } else if (sub === 'search') {
2210
2331
  const query = args.filter((arg, index) => index > 1 && arg !== '--json').join(' ').trim();
@@ -2221,11 +2342,15 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2221
2342
  else printMarketplaceSkills(skills);
2222
2343
  } else if (sub === 'show') {
2223
2344
  const identifier = args[2];
2224
- if (!identifier) { console.error('Usage: makaron skills show <marketplace-id|label> [--json]'); process.exit(1); }
2225
- const skills = await fetchMarketplaceSkills(baseUrl);
2226
- const skill = findMarketplaceSkill(skills, identifier);
2345
+ if (!identifier) { console.error('Usage: makaron skills show <id|label> [--built-in] [--json]'); process.exit(1); }
2346
+ const builtIn = args.includes('--built-in');
2347
+ const skills = builtIn ? await fetchBuiltInSkills(baseUrl) : await fetchMarketplaceSkills(baseUrl);
2348
+ const skill = builtIn
2349
+ ? skills.find(candidate => candidate.name === identifier || candidate.label?.toLowerCase() === identifier.toLowerCase())
2350
+ : findMarketplaceSkill(skills, identifier);
2227
2351
  if (!skill) { console.error(`Skill not found: ${identifier}`); process.exit(1); }
2228
2352
  if (jsonOutput) console.log(JSON.stringify(skill, null, 2));
2353
+ else if (builtIn) printBuiltInSkills([skill]);
2229
2354
  else printMarketplaceSkill(skill);
2230
2355
  } else if (sub === 'install') {
2231
2356
  const identifier = args[2];
@@ -2238,9 +2363,13 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2238
2363
  if (jsonOutput) console.log(JSON.stringify({ ...data, marketplaceId: skill.id, label: skill.label }, null, 2));
2239
2364
  else console.log(data.skillName);
2240
2365
  } else {
2241
- console.log(`Skill marketplace commands:
2366
+ console.log(`Skill commands:
2367
+ skills list --built-in List all built-in Makaron skills and Studio Run recipes
2368
+ skills list --built-in --openmontage
2369
+ List OpenMontage-native adapters only
2242
2370
  skills list List marketplace skills
2243
2371
  skills search <query> Search marketplace skills
2372
+ skills show <id|label> --built-in Show a built-in skill
2244
2373
  skills show <id|label> Show a marketplace skill
2245
2374
  skills install <id|label> Install a marketplace skill to your workspace
2246
2375
  `);
@@ -2492,18 +2621,23 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2492
2621
  const sub = args[1];
2493
2622
 
2494
2623
  if (sub === 'skills') {
2495
- const action = args[2]; // add, update, delete, or none (list)
2624
+ const action = args[2] === '--json' ? null : args[2]; // add, update, delete, or none (list)
2496
2625
  if (!action) {
2497
2626
  // List all skills
2498
2627
  const res = await fetch(`${baseUrl}/api/admin/home-skills`, { headers });
2499
2628
  if (!res.ok) { console.error(`Error ${res.status}:`, await res.text()); process.exit(1); }
2500
2629
  const skills = await res.json();
2630
+ if (args.includes('--json')) {
2631
+ console.log(JSON.stringify(skills, null, 2));
2632
+ process.exit(0);
2633
+ }
2501
2634
  console.log(`📋 ${skills.length} skills\n`);
2502
2635
  for (const s of skills) {
2503
2636
  const label = s.labels?.en || s.labels?.zh || '(no label)';
2504
2637
  const active = s.is_active ? '✅' : '❌';
2505
2638
  const hasZip = s.skill_path ? '📦' : ' ';
2506
- console.log(` ${active} ${hasZip} ${String(s.sort_order).padStart(3)} ${s.id} ${label}`);
2639
+ const categories = Array.isArray(s.categories) && s.categories.length ? s.categories.join(',') : 'uncategorized';
2640
+ console.log(` ${active} ${hasZip} ${String(s.sort_order).padStart(3)} ${s.id} ${label} [${categories}] title ${localizedCoverage(s.labels)}/4 · prompt ${localizedCoverage(s.prompts)}/4`);
2507
2641
  }
2508
2642
  } else if (action === 'add') {
2509
2643
  const json = args.slice(3).join(' ');
@@ -2541,6 +2675,59 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2541
2675
  process.exit(1);
2542
2676
  }
2543
2677
 
2678
+ } else if (sub === 'skill-categories') {
2679
+ const action = args[2] === '--json' ? null : args[2];
2680
+ if (!action) {
2681
+ const res = await fetch(`${baseUrl}/api/admin/skill-categories`, { headers });
2682
+ if (!res.ok) { console.error(`Error ${res.status}:`, await res.text()); process.exit(1); }
2683
+ const categories = await res.json();
2684
+ if (args.includes('--json')) {
2685
+ console.log(JSON.stringify(categories, null, 2));
2686
+ process.exit(0);
2687
+ }
2688
+ console.log(`📂 ${categories.length} skill categories\n`);
2689
+ for (const category of categories) {
2690
+ const label = category.labels?.en || category.labels?.zh || category.id;
2691
+ const active = category.is_active ? '✅' : '❌';
2692
+ const icon = category.icon || ' ';
2693
+ console.log(` ${active} ${icon} ${String(category.sort_order).padStart(3)} ${category.id} ${label} title ${localizedCoverage(category.labels)}/4`);
2694
+ }
2695
+ } else if (action === 'add') {
2696
+ const json = args.slice(3).join(' ');
2697
+ if (!json) { console.error('Usage: makaron admin skill-categories add \'{"id":"...","labels":{...}}\''); process.exit(1); }
2698
+ let body;
2699
+ try { body = JSON.parse(json); } catch { console.error('Invalid JSON'); process.exit(1); }
2700
+ const res = await fetch(`${baseUrl}/api/admin/skill-categories`, {
2701
+ method: 'POST', headers: { 'Content-Type': 'application/json', ...headers }, body: JSON.stringify(body),
2702
+ });
2703
+ if (!res.ok) { console.error(`Error ${res.status}:`, await res.text()); process.exit(1); }
2704
+ const data = await res.json();
2705
+ console.log(`✅ Skill category created: ${data.id}`);
2706
+ } else if (action === 'update') {
2707
+ const id = args[3];
2708
+ const json = args.slice(4).join(' ');
2709
+ if (!id || !json) { console.error('Usage: makaron admin skill-categories update <id> \'{"field":"value"}\''); process.exit(1); }
2710
+ let body;
2711
+ try { body = JSON.parse(json); } catch { console.error('Invalid JSON'); process.exit(1); }
2712
+ body.id = id;
2713
+ const res = await fetch(`${baseUrl}/api/admin/skill-categories`, {
2714
+ method: 'PUT', headers: { 'Content-Type': 'application/json', ...headers }, body: JSON.stringify(body),
2715
+ });
2716
+ if (!res.ok) { console.error(`Error ${res.status}:`, await res.text()); process.exit(1); }
2717
+ console.log(`✅ Skill category ${id} updated`);
2718
+ } else if (action === 'delete') {
2719
+ const id = args[3];
2720
+ if (!id) { console.error('Usage: makaron admin skill-categories delete <id>'); process.exit(1); }
2721
+ const res = await fetch(`${baseUrl}/api/admin/skill-categories`, {
2722
+ method: 'DELETE', headers: { 'Content-Type': 'application/json', ...headers }, body: JSON.stringify({ id }),
2723
+ });
2724
+ if (!res.ok) { console.error(`Error ${res.status}:`, await res.text()); process.exit(1); }
2725
+ console.log(`✅ Skill category ${id} deleted`);
2726
+ } else {
2727
+ console.error(`Unknown action: ${action}. Use: add, update, delete, or omit to list.`);
2728
+ process.exit(1);
2729
+ }
2730
+
2544
2731
  } else if (sub === 'upload') {
2545
2732
  const filePath = args[2];
2546
2733
  const storagePath = args[3];
@@ -2600,6 +2787,10 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2600
2787
  admin skills add '<json>' Add a new skill
2601
2788
  admin skills update <id> '<json>' Update a skill
2602
2789
  admin skills delete <id> Delete a skill
2790
+ admin skill-categories List marketplace categories
2791
+ admin skill-categories add '<json>' Add a category
2792
+ admin skill-categories update <id> '<json>'
2793
+ admin skill-categories delete <id> Delete a category
2603
2794
  admin upload <file> <storage-path> Upload file to Storage
2604
2795
  admin fetch-skill <code|url> Download skill from share link
2605
2796
  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.13.0",
3
+ "version": "0.13.2",
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
@@ -82,7 +88,7 @@ npx makaron-cli chat --project auto --image img1.jpg --image img2.jpg --json -b
82
88
  npx makaron-cli chat --project auto --agent-model deepseek-v4-pro --image-model qwen "design a product poster"
83
89
  ```
84
90
 
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|sonnet-4.6|sonnet-5|opus-4.8|grok-4.5|deepseek-v4-pro`. `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.
91
+ 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
92
 
87
93
  Returns immediately:
88
94
  ```json
@@ -126,7 +132,9 @@ npx makaron-cli chat --project auto \
126
132
  -b "make this with the selected skill"
127
133
  ```
128
134
 
129
- `--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.
135
+ `--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.
136
+
137
+ 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.
130
138
 
131
139
  ### With additional images (existing project)
132
140