makaron-cli 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "makaron-cli",
3
- "version": "0.9.0",
4
- "description": "AI image editing, video generation, and music creation via CLI. Agents can self-register, create projects, and produce creative media.",
3
+ "version": "0.11.0",
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",
7
7
  "url": "https://www.makaron.app"
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "makaron-cli",
3
- "version": "0.9.0",
4
- "description": "AI image editing, video generation, and music creation via CLI. Agents can self-register, create projects, and produce creative media.",
3
+ "version": "0.11.0",
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",
7
- "longDescription": "makaron.app is for humans. makaron-cli is for AI agents. Talk to Makaron Agent from the terminal — create projects, edit images, generate videos, and compose music. Zero dependencies, single file, works with npx. Agents can self-register to get an API key without human intervention.",
7
+ "longDescription": "makaron.app is for humans. makaron-cli is for AI agents. Talk to Makaron Agent from the terminal — list, search, show, and install marketplace skills; create projects; edit images; generate videos; and compose music. Zero dependencies, single file, works with npx. Agents can self-register to get an API key without human intervention.",
8
8
  "author": {
9
9
  "name": "Makaron AI",
10
10
  "url": "https://www.makaron.app"
@@ -12,9 +12,9 @@
12
12
  "homepage": "https://www.makaron.app/agent",
13
13
  "repository": "https://github.com/vegekyd/ai-image-editor",
14
14
  "license": "MIT",
15
- "keywords": ["ai", "image-editing", "video", "music", "agent", "creative"],
15
+ "keywords": ["ai", "image-editing", "video", "music", "agent", "creative", "skills"],
16
16
  "category": "Creative Tools",
17
- "capabilities": ["image-editing", "video-generation", "music-creation", "text-to-image"],
17
+ "capabilities": ["image-editing", "video-generation", "music-creation", "text-to-image", "marketplace-skills"],
18
18
  "skills": "./skills/",
19
19
  "websiteURL": "https://www.makaron.app",
20
20
  "brandColor": "#d946ef"
package/README.md CHANGED
@@ -107,6 +107,23 @@ Returns immediately:
107
107
  | Add music | `npx makaron-cli chat --project <id> "add calm piano background music"` |
108
108
  | Create motion design | `npx makaron-cli chat --project <id> "make an animated Instagram story with this image"` |
109
109
 
110
+ ### Marketplace skills
111
+
112
+ External users can browse, install, and use marketplace skills with only `MAKARON_API_KEY`:
113
+
114
+ ```bash
115
+ npx makaron-cli skills list
116
+ npx makaron-cli skills search "football"
117
+ npx makaron-cli skills search "足球"
118
+ npx makaron-cli skills show <marketplace-id-or-label>
119
+ npx makaron-cli skills install <marketplace-id-or-label>
120
+
121
+ # chat auto-installs matched marketplace skills before starting the run
122
+ npx makaron-cli chat --project auto --image selfie.jpg --skill <marketplace-id-or-label> -b "make this with the selected skill"
123
+ ```
124
+
125
+ `--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. Ordinary users do not need admin commands, and the CLI intentionally does not expose skill deletion.
126
+
110
127
  ### With additional images (existing project)
111
128
 
112
129
  ```bash
@@ -218,10 +235,11 @@ npx makaron-cli analyze --video input.mp4 "describe the key actions and pacing"
218
235
 
219
236
  # 3a. Submit image-to-video rendering (images must be public URLs from step 1 or uploaded)
220
237
  npx makaron-cli video create --script "Shot 1 (5s): <<<image_1>>> ..." --image https://...jpg --duration 5 --model kling
238
+ 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
221
239
 
222
240
  # 3b. Edit a video from a local file or public URL
223
- npx makaron-cli video create --script "make it funny" --video input.mp4 --duration 5 --model seedance
224
- npx makaron-cli video create --script "make it warmer and cinematic" --video https://example.com/input.mp4 --duration 5 --model seedance
241
+ npx makaron-cli video create --script "make it funny" --video input.mp4 --duration 5 --model seedance-fast
242
+ npx makaron-cli video create --script "make it warmer and cinematic" --video https://example.com/input.mp4 --duration 5 --model seedance --video-resolution 1080p
225
243
 
226
244
  # 4. Check status
227
245
  npx makaron-cli video status <taskId>
@@ -233,9 +251,9 @@ For project/timeline video editing, use:
233
251
  npx makaron-cli chat --project <id|auto> --video input.mp4 -b "make it funny"
234
252
  ```
235
253
 
236
- 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 kling|seedance`. SeeDance accepts integer output duration 4-15s (default 5s); Kling supports 5-15s.
254
+ 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|kling|grok`, `--video-resolution auto|480p|720p|1080p|4k`. Default model is `seedance-fast`. SeeDance accepts integer output duration 4-15s (default 5s); Kling supports 5-15s; Grok 1.5 supports 1-15s single-image-to-video only. 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.
237
255
 
238
- Video edit model behavior: `--model kling --video` uses Kling base/direct edit internally; `--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. Tiny metadata padding up to 15.5s is accepted and output duration is clamped to 15s.
256
+ Video edit model behavior: `--model kling --video` uses Kling base/direct edit internally; `--model seedance-fast --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. Tiny metadata padding up to 15.5s is accepted and output duration is clamped to 15s. Grok does not support video references.
239
257
 
240
258
  ### `music` — Music generation
241
259
 
@@ -350,7 +368,7 @@ send_message "All done!"
350
368
  - One project = one conversation thread. All history is preserved.
351
369
  - One run at a time per project. New message interrupts previous run.
352
370
  - Multi-image: `create --image a.jpg --image b.jpg` or `chat --image ref.jpg`.
353
- - Videos take 2-5 minutes to render. Use `responses get <runId> --wait --json` for the default customer-service path.
371
+ - Most videos take 3-5 minutes; Grok is usually around 30-40 seconds. Use `responses get <runId> --wait --json` for the default customer-service path.
354
372
  - Music takes ~60 seconds. Appears in output when done.
355
373
  - Images are typically ready in 15-30 seconds.
356
374
  - stdout is always machine-readable JSON/text. Human-friendly logs go to stderr.
package/bin/makaron.mjs CHANGED
@@ -274,7 +274,8 @@ Options:
274
274
  --image <file|url> Attach a reference image or screenshot. Repeatable.
275
275
  --video <file|url> Attach a video to the project timeline. Repeatable.
276
276
  --model <name> Preferred image/model route.
277
- --video-model <name> Preferred video model.
277
+ --video-model <name> Preferred video model: seedance-fast, seedance, kling, or grok.
278
+ --video-resolution <res> Video resolution: auto, 480p, 720p, 1080p, or 4k.
278
279
  --background, -b Submit and print a runId.
279
280
  --json Output structured JSON.
280
281
  --stream Legacy live SSE stream.
@@ -441,6 +442,7 @@ async function submitRun(baseUrl, headers, projectId, prompt, opts = {}) {
441
442
  const body = { projectId, prompt };
442
443
  if (opts.preferredModel) body.preferredModel = opts.preferredModel;
443
444
  if (opts.videoModel) body.videoModel = opts.videoModel;
445
+ if (opts.videoResolution) body.videoResolution = opts.videoResolution;
444
446
  if (opts.currentSnapshotIndex != null) body.currentSnapshotIndex = opts.currentSnapshotIndex;
445
447
  if (opts.isNsfw) body.isNsfw = opts.isNsfw;
446
448
 
@@ -790,6 +792,155 @@ function timeSince(date) {
790
792
  return `${Math.floor(s / 86400)}d ago`;
791
793
  }
792
794
 
795
+ // ─── Skill Marketplace ──────────────────────────────────────────────────────
796
+
797
+ function getSkillLabel(skill) {
798
+ return skill.labels?.en || skill.labels?.zh || skill.label || skill.name || skill.id;
799
+ }
800
+
801
+ function slugifySkill(value) {
802
+ return String(value || '')
803
+ .toLowerCase()
804
+ .normalize('NFKD')
805
+ .replace(/[\u0300-\u036f]/g, '')
806
+ .replace(/[^a-z0-9]+/g, '-')
807
+ .replace(/^-+|-+$/g, '');
808
+ }
809
+
810
+ function normalizeMarketplaceSkill(skill) {
811
+ return {
812
+ ...skill,
813
+ label: getSkillLabel(skill),
814
+ skillPath: skill.skillPath || skill.skill_path || null,
815
+ hasSkill: Boolean(skill.skillPath || skill.skill_path),
816
+ };
817
+ }
818
+
819
+ async function fetchMarketplaceSkills(baseUrl, opts = {}) {
820
+ let res;
821
+ try {
822
+ res = await fetch(`${baseUrl}/api/home-skills`);
823
+ } catch (err) {
824
+ if (opts.optional) return null;
825
+ process.stderr.write(`Failed to load marketplace skills: ${err.message || err}\n`);
826
+ process.exit(1);
827
+ }
828
+ if (!res.ok) {
829
+ if (opts.optional) return null;
830
+ process.stderr.write(`Error ${res.status}: ${await res.text()}\n`);
831
+ process.exit(1);
832
+ }
833
+ const data = await res.json();
834
+ const skills = Array.isArray(data) ? data : (data.skills || []);
835
+ return skills.map(normalizeMarketplaceSkill);
836
+ }
837
+
838
+ function marketplaceSearchText(skill) {
839
+ return [
840
+ skill.id,
841
+ skill.label,
842
+ skill.labels?.en,
843
+ skill.labels?.zh,
844
+ skill.prompt,
845
+ slugifySkill(skill.label),
846
+ slugifySkill(skill.labels?.en),
847
+ slugifySkill(skill.labels?.zh),
848
+ ].filter(Boolean).join(' ').toLowerCase();
849
+ }
850
+
851
+ function marketplaceSkillTokens(skill) {
852
+ return [
853
+ skill.id,
854
+ skill.label,
855
+ skill.labels?.en,
856
+ skill.labels?.zh,
857
+ skill.prompt,
858
+ ].filter(Boolean).map(value => String(value).toLowerCase());
859
+ }
860
+
861
+ function findMarketplaceSkill(skills, identifier) {
862
+ const raw = String(identifier || '').trim();
863
+ if (!raw) return null;
864
+ const lower = raw.toLowerCase();
865
+ const slug = slugifySkill(raw);
866
+ const exact = skills.filter(skill => {
867
+ const labels = Object.values(skill.labels || {}).map(v => String(v).toLowerCase());
868
+ const slugMatches = slug
869
+ ? slugifySkill(getSkillLabel(skill)) === slug ||
870
+ Object.values(skill.labels || {}).some(v => slugifySkill(v) === slug)
871
+ : false;
872
+ return skill.id === raw ||
873
+ skill.id?.toLowerCase() === lower ||
874
+ getSkillLabel(skill).toLowerCase() === lower ||
875
+ labels.includes(lower) ||
876
+ slugMatches;
877
+ });
878
+ if (exact.length === 1) return exact[0];
879
+ if (exact.length > 1) {
880
+ process.stderr.write(`Multiple marketplace skills match "${raw}". Use an id:\n`);
881
+ exact.forEach(skill => process.stderr.write(` ${skill.id} ${skill.label}\n`));
882
+ process.exit(1);
883
+ }
884
+ const prefix = skills.filter(skill => skill.id?.startsWith(raw));
885
+ if (prefix.length === 1) return prefix[0];
886
+ return null;
887
+ }
888
+
889
+ function printMarketplaceSkills(skills) {
890
+ if (!skills.length) {
891
+ console.log('No marketplace skills found.');
892
+ return;
893
+ }
894
+ console.log(`📦 ${skills.length} marketplace skills\n`);
895
+ for (const skill of skills) {
896
+ const kind = skill.hasSkill ? 'skill' : 'prompt';
897
+ console.log(` ${skill.id} ${skill.label} [${kind}]`);
898
+ }
899
+ }
900
+
901
+ function printMarketplaceSkill(skill) {
902
+ console.log(`${skill.label}`);
903
+ console.log(`ID: ${skill.id}`);
904
+ console.log(`Type: ${skill.hasSkill ? 'installable skill' : 'prompt template'}`);
905
+ if (skill.labels?.zh && skill.labels.zh !== skill.label) console.log(`ZH: ${skill.labels.zh}`);
906
+ if (skill.prompt) console.log(`Prompt: ${skill.prompt}`);
907
+ if (skill.image) console.log(`Cover: ${skill.image}`);
908
+ if (skill.hasSkill) console.log(`Install: makaron skills install ${skill.id}`);
909
+ }
910
+
911
+ async function installMarketplaceSkill(baseUrl, headers, skill, opts = {}) {
912
+ if (!skill.hasSkill) {
913
+ process.stderr.write(`"${skill.label}" is a prompt-only marketplace item and has no installable skill package.\n`);
914
+ process.exit(1);
915
+ }
916
+ if (!opts.quiet) process.stderr.write(`📦 Installing skill: ${skill.label}\n`);
917
+ const res = await fetch(`${baseUrl}/api/skills`, {
918
+ method: 'POST',
919
+ headers: { 'Content-Type': 'application/json', ...headers },
920
+ body: JSON.stringify({ skillPath: skill.skillPath, homeSkillId: skill.id }),
921
+ });
922
+ const data = await res.json().catch(() => ({}));
923
+ if (!res.ok || !data.success) {
924
+ process.stderr.write(`Install failed: ${data.error || res.status}\n`);
925
+ process.exit(1);
926
+ }
927
+ if (!opts.quiet) {
928
+ const suffix = data.alreadyInstalled ? 'already installed' : 'installed';
929
+ process.stderr.write(`✅ Skill ${suffix}: ${data.skillName}\n`);
930
+ }
931
+ return data;
932
+ }
933
+
934
+ async function resolveChatSkill(baseUrl, headers, activeSkill) {
935
+ if (!activeSkill) return undefined;
936
+ const skills = await fetchMarketplaceSkills(baseUrl, { optional: true });
937
+ if (!skills) return activeSkill;
938
+ const marketplaceSkill = findMarketplaceSkill(skills, activeSkill);
939
+ if (!marketplaceSkill) return activeSkill;
940
+ const result = await installMarketplaceSkill(baseUrl, headers, marketplaceSkill);
941
+ return result.skillName;
942
+ }
943
+
793
944
  // ─── MCP Tool Caller ─────────────────────────────────────────────────────────
794
945
 
795
946
  async function callMcpTool(baseUrl, headers, toolName, args) {
@@ -1066,6 +1217,7 @@ Commands:
1066
1217
  responses get <runId> --wait Poll until completed
1067
1218
  responses list --project <id> List runs for a project
1068
1219
  abort <runId> Abort a running Agent
1220
+ skills list|search|show|install Browse and install marketplace skills
1069
1221
 
1070
1222
  edit [--image <file>] "prompt" AI image edit / text-to-image
1071
1223
  analyze --video <file|url> Analyze video content
@@ -1107,18 +1259,32 @@ function printHelp(topic, subtopic) {
1107
1259
  `);
1108
1260
  } else if (topic === 'abort') {
1109
1261
  console.log('Usage: makaron abort <runId>');
1262
+ } else if (topic === 'skills') {
1263
+ if (subtopic === 'list') console.log('Usage: makaron skills list [--json]');
1264
+ else if (subtopic === 'search') console.log('Usage: makaron skills search <query> [--json]');
1265
+ else if (subtopic === 'show') console.log('Usage: makaron skills show <marketplace-id|label> [--json]');
1266
+ else if (subtopic === 'install') console.log('Usage: makaron skills install <marketplace-id|label> [--json]');
1267
+ else console.log(`Skill marketplace commands:
1268
+ skills list List marketplace skills
1269
+ skills search <query> Search marketplace skills
1270
+ skills show <id|label> Show a marketplace skill
1271
+ skills install <id|label> Install a marketplace skill to your workspace
1272
+
1273
+ Use with chat:
1274
+ makaron chat --project auto --skill <id|label> "your request"
1275
+ `);
1110
1276
  } else if (topic === 'edit') {
1111
1277
  console.log('Usage: makaron edit [--image <file|url>] [--model gemini|qwen|openai] [--skill enhance|creative|wild|captions] [--ref <file>] [--out <file>] "prompt"');
1112
1278
  } else if (topic === 'analyze') {
1113
1279
  console.log('Usage: makaron analyze --video <file|url> ["question"]');
1114
1280
  } else if (topic === 'video') {
1115
1281
  if (subtopic === 'script') console.log('Usage: makaron video script --image <file> [--image <file>] [--lang en|zh] "direction"');
1116
- else if (subtopic === 'create') console.log('Usage: makaron video create --script "..." (--image <url> | --video <public-url>) [--duration 10] [--aspect 9:16] [--model kling|seedance] [--keep-original-sound]');
1282
+ 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|kling|grok] [--video-resolution auto|480p|720p|1080p|4k] [--keep-original-sound]');
1117
1283
  else if (subtopic === 'status') console.log('Usage: makaron video status <taskId> | --snapshot <snapshotId> [--wait]');
1118
1284
  else console.log(`Video commands:
1119
1285
  video script --image <file> [--image <file>] "direction" Write video script
1120
1286
  video create --script "..." --image <url> [--duration 10] Submit video task
1121
- video create --script "..." --video <public-url> [--model kling|seedance] Edit a video (standalone)
1287
+ video create --script "..." --video <public-url> [--model seedance-fast|seedance|kling] Edit a video (standalone; Grok does not support video refs)
1122
1288
  video status <taskId> Check video status
1123
1289
  video status --snapshot <snapshotId> [--wait] Check v2 video snapshot
1124
1290
  `);
@@ -1197,16 +1363,19 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
1197
1363
  let jsonOutput = false;
1198
1364
  let activeSkill = undefined;
1199
1365
  let videoModel = undefined;
1366
+ let videoResolution = undefined;
1200
1367
  let preferredModel = undefined;
1201
1368
  for (let i = 1; i < args.length; i++) {
1202
1369
  if (args[i] === '--project' && args[i + 1]) projectId = args[++i];
1203
1370
  else if (args[i] === '--image' && args[i + 1]) chatImages.push(args[++i]);
1204
1371
  else if (args[i] === '--video' && args[i + 1]) chatVideos.push(args[++i]);
1205
1372
  else if (args[i] === '--skill' && args[i + 1]) activeSkill = args[++i];
1373
+ else if (args[i].startsWith('--skill=')) activeSkill = args[i].slice('--skill='.length);
1206
1374
  else if (args[i] === '--stream') useStream = true;
1207
1375
  else if (args[i] === '--background' || args[i] === '-b') background = true;
1208
1376
  else if (args[i] === '--json') jsonOutput = true;
1209
1377
  else if (args[i] === '--video-model' && args[i + 1]) videoModel = args[++i];
1378
+ else if (args[i] === '--video-resolution' && args[i + 1]) videoResolution = args[++i];
1210
1379
  else if (args[i] === '--model' && args[i + 1]) preferredModel = args[++i];
1211
1380
  else promptParts.push(args[i]);
1212
1381
  }
@@ -1293,8 +1462,10 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
1293
1462
  }
1294
1463
  }
1295
1464
 
1465
+ const resolvedSkill = await resolveChatSkill(baseUrl, headers, activeSkill);
1466
+
1296
1467
  // Upload videos to project timeline (via /api/projects/create with videoUrls)
1297
- let finalPrompt = activeSkill ? `[Active skill: ${activeSkill}]\n${prompt}` : prompt;
1468
+ let finalPrompt = resolvedSkill ? `[Active skill: ${resolvedSkill}]\n${prompt}` : prompt;
1298
1469
  if (chatVideos.length > 0) {
1299
1470
  // Upload local files via signed URL (no size limit, works with API key auth)
1300
1471
  const uploadedVideoUrls = [...prevalidatedVideoUrlList];
@@ -1352,7 +1523,7 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
1352
1523
  for (const task of results.musicTasks) await pollMusic(baseUrl, headers, task.taskId);
1353
1524
  } else {
1354
1525
  // Default: fire-and-forget + poll
1355
- const { runId } = await submitRun(baseUrl, headers, projectId, finalPrompt, { videoModel, preferredModel });
1526
+ const { runId } = await submitRun(baseUrl, headers, projectId, finalPrompt, { videoModel, videoResolution, preferredModel });
1356
1527
  if (background) {
1357
1528
  // Just print runId and exit
1358
1529
  if (jsonOutput) {
@@ -1372,10 +1543,9 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
1372
1543
  if (sub === 'get') {
1373
1544
  const runId = args[2];
1374
1545
  if (!runId) { console.error('Usage: makaron responses get <runId> [--wait] [--json] [--pick <field>]'); process.exit(1); }
1375
- let wait = false, jsonOutput = false, pick = null;
1546
+ let wait = false, pick = null;
1376
1547
  for (let i = 3; i < args.length; i++) {
1377
1548
  if (args[i] === '--wait') wait = true;
1378
- if (args[i] === '--json') jsonOutput = true;
1379
1549
  if (args[i] === '--pick' && args[i + 1]) pick = args[++i];
1380
1550
  }
1381
1551
 
@@ -1435,6 +1605,54 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
1435
1605
  } else if (command === 'list' || command === 'ls') {
1436
1606
  const { headers, baseUrl } = getAuth();
1437
1607
  await listProjects(baseUrl, headers);
1608
+ } else if (command === 'skills') {
1609
+ const sub = args[1] || 'list';
1610
+ const baseUrl = process.env.MAKARON_URL || DEFAULT_URL;
1611
+ const jsonOutput = args.includes('--json');
1612
+
1613
+ if (sub === 'list') {
1614
+ const skills = await fetchMarketplaceSkills(baseUrl);
1615
+ if (jsonOutput) console.log(JSON.stringify({ skills }, null, 2));
1616
+ else printMarketplaceSkills(skills);
1617
+ } else if (sub === 'search') {
1618
+ const query = args.filter((arg, index) => index > 1 && arg !== '--json').join(' ').trim();
1619
+ if (!query) { console.error('Usage: makaron skills search <query> [--json]'); process.exit(1); }
1620
+ const lowerQuery = query.toLowerCase();
1621
+ const slugQuery = slugifySkill(query);
1622
+ const skills = (await fetchMarketplaceSkills(baseUrl))
1623
+ .filter(skill => {
1624
+ const rawMatch = marketplaceSkillTokens(skill).some(token => token.includes(lowerQuery));
1625
+ const slugMatch = slugQuery ? marketplaceSearchText(skill).includes(slugQuery) : false;
1626
+ return rawMatch || slugMatch;
1627
+ });
1628
+ if (jsonOutput) console.log(JSON.stringify({ skills }, null, 2));
1629
+ else printMarketplaceSkills(skills);
1630
+ } else if (sub === 'show') {
1631
+ const identifier = args[2];
1632
+ if (!identifier) { console.error('Usage: makaron skills show <marketplace-id|label> [--json]'); process.exit(1); }
1633
+ const skills = await fetchMarketplaceSkills(baseUrl);
1634
+ const skill = findMarketplaceSkill(skills, identifier);
1635
+ if (!skill) { console.error(`Skill not found: ${identifier}`); process.exit(1); }
1636
+ if (jsonOutput) console.log(JSON.stringify(skill, null, 2));
1637
+ else printMarketplaceSkill(skill);
1638
+ } else if (sub === 'install') {
1639
+ const identifier = args[2];
1640
+ if (!identifier) { console.error('Usage: makaron skills install <marketplace-id|label> [--json]'); process.exit(1); }
1641
+ const { headers, baseUrl: authedBaseUrl } = getAuth();
1642
+ const skills = await fetchMarketplaceSkills(authedBaseUrl);
1643
+ const skill = findMarketplaceSkill(skills, identifier);
1644
+ if (!skill) { console.error(`Skill not found: ${identifier}`); process.exit(1); }
1645
+ const data = await installMarketplaceSkill(authedBaseUrl, headers, skill, { quiet: jsonOutput });
1646
+ if (jsonOutput) console.log(JSON.stringify({ ...data, marketplaceId: skill.id, label: skill.label }, null, 2));
1647
+ else console.log(data.skillName);
1648
+ } else {
1649
+ console.log(`Skill marketplace commands:
1650
+ skills list List marketplace skills
1651
+ skills search <query> Search marketplace skills
1652
+ skills show <id|label> Show a marketplace skill
1653
+ skills install <id|label> Install a marketplace skill to your workspace
1654
+ `);
1655
+ }
1438
1656
  } else if (command === 'project' || command === 'projects') {
1439
1657
  const { headers, baseUrl } = getAuth();
1440
1658
  const sub = args[1];
@@ -1519,7 +1737,7 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
1519
1737
 
1520
1738
  } else if (sub === 'create') {
1521
1739
  const images = [];
1522
- let script = '', duration = undefined, aspectRatio = undefined, videoModel = undefined, wait = false;
1740
+ let script = '', duration = undefined, aspectRatio = undefined, videoModel = undefined, videoResolution = undefined, wait = false;
1523
1741
  let video = null, keepOriginalSound = false;
1524
1742
  for (let i = 2; i < args.length; i++) {
1525
1743
  if (args[i] === '--image' && args[i + 1]) images.push(args[++i]);
@@ -1529,6 +1747,8 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
1529
1747
  else if (args[i] === '--duration' && args[i + 1]) duration = Number(args[++i]);
1530
1748
  else if (args[i] === '--aspect' && args[i + 1]) aspectRatio = args[++i];
1531
1749
  else if (args[i] === '--model' && args[i + 1]) videoModel = args[++i];
1750
+ else if (args[i] === '--video-resolution' && args[i + 1]) videoResolution = args[++i];
1751
+ else if (args[i] === '--resolution' && args[i + 1]) videoResolution = args[++i];
1532
1752
  else if (args[i] === '--keep-original-sound') keepOriginalSound = true;
1533
1753
  else if (args[i] === '--project') {
1534
1754
  console.error('Usage: video create no longer supports --project. Use: makaron chat --project <id> --video <file|url> "your request"');
@@ -1537,7 +1757,7 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
1537
1757
  else if (args[i] === '--wait') wait = true;
1538
1758
  }
1539
1759
  if ((!images.length && !video) || !script) {
1540
- console.error('Usage: makaron video create --script "..." (--image <url> | --video <public-url>) [--duration 10] [--aspect 9:16] [--model kling|seedance] [--keep-original-sound]');
1760
+ console.error('Usage: makaron video create --script "..." (--image <url> | --video <public-url>) [--duration 10] [--aspect 9:16] [--model seedance-fast|seedance|kling|grok] [--video-resolution auto|480p|720p|1080p|4k] [--keep-original-sound]');
1541
1761
  process.exit(1);
1542
1762
  }
1543
1763
 
@@ -1548,15 +1768,15 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
1548
1768
 
1549
1769
  let videoUrl = isHttpUrl(video) ? video : null;
1550
1770
  let inputVideoMeta = null;
1551
- const selectedVideoModel = videoModel || 'kling';
1771
+ const selectedVideoModel = videoModel || 'seedance-fast';
1552
1772
  if (videoUrl) {
1553
- 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.\n`);
1773
+ 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. Grok does not support video references.\n`);
1554
1774
  }
1555
1775
  if (video && !videoUrl) {
1556
1776
  const valid = validateVideoFile(video, {
1557
1777
  maxDuration: MAX_VIDEO_PROVIDER_REFERENCE_DURATION,
1558
1778
  durationTolerance: MAX_VIDEO_PROVIDER_REFERENCE_DURATION_TOLERANCE,
1559
- ...(selectedVideoModel === 'seedance' ? {
1779
+ ...(selectedVideoModel === 'seedance' || selectedVideoModel === 'seedance-fast' ? {
1560
1780
  minFramePixels: SEEDANCE_MIN_VIDEO_FRAME_PIXELS,
1561
1781
  minSide: SEEDANCE_MIN_VIDEO_SIDE,
1562
1782
  maxSide: SEEDANCE_MAX_VIDEO_SIDE,
@@ -1574,12 +1794,11 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
1574
1794
  // Standalone MCP tool (no project timeline write)
1575
1795
  process.stderr.write('🎬 Submitting video...\n');
1576
1796
  const vArgs = videoUrl
1577
- ? { videoUrl, editPrompt: script, images, videoModel: selectedVideoModel, referType: selectedVideoModel === 'seedance' ? 'feature' : 'base' }
1578
- : { script, images };
1797
+ ? { videoUrl, editPrompt: script, images, videoModel: selectedVideoModel, videoResolution, referType: (selectedVideoModel === 'seedance' || selectedVideoModel === 'seedance-fast') ? 'feature' : 'base' }
1798
+ : { script, images, videoModel: selectedVideoModel, videoResolution };
1579
1799
  const effectiveDuration = duration || (inputVideoMeta?.duration ? Math.min(MAX_VIDEO_PROVIDER_REFERENCE_DURATION, Math.round(inputVideoMeta.duration)) : undefined);
1580
1800
  if (effectiveDuration) vArgs.duration = effectiveDuration;
1581
1801
  if (aspectRatio) vArgs.aspectRatio = aspectRatio;
1582
- if (videoModel && !videoUrl) vArgs.videoModel = videoModel;
1583
1802
  if (keepOriginalSound && videoUrl) vArgs.keepOriginalSound = true;
1584
1803
  const result = await callMcpTool(baseUrl, headers, videoUrl ? 'makaron_edit_video' : 'makaron_create_video', vArgs);
1585
1804
  const text = result?.content?.find(c => c.type === 'text')?.text;
@@ -1625,7 +1844,7 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
1625
1844
  console.log(`Video commands:
1626
1845
  video script --image <file> [--image <file>] "direction" Write video script
1627
1846
  video create --script "..." --image <url> [--duration 10] Submit video task
1628
- video create --script "..." --video <public-url> [--model kling|seedance] Edit a video (standalone)
1847
+ video create --script "..." --video <public-url> [--model seedance-fast|seedance|kling] Edit a video (standalone; Grok does not support video refs)
1629
1848
  video status <taskId> Check video status
1630
1849
  video status --snapshot <snapshotId> [--wait] Check v2 video snapshot
1631
1850
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "makaron-cli",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "description": "Talk to Makaron Agent from the terminal — create projects, edit images, generate videos",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -96,6 +96,32 @@ Returns immediately:
96
96
  | Add music | `npx makaron-cli chat --project <id> "add calm piano background music"` |
97
97
  | Create motion design | `npx makaron-cli chat --project <id> "make an animated Instagram story with this image"` |
98
98
 
99
+ ### Marketplace skills
100
+
101
+ Use marketplace skills when the user asks for a named Makaron effect, template, or skill such as "Football Captain", "足球队长", "World Cup MVP", or a marketplace UUID.
102
+
103
+ External users only need `MAKARON_API_KEY`; no admin permissions are required for listing, searching, showing, installing, or using marketplace skills.
104
+
105
+ ```bash
106
+ # Browse public marketplace skills
107
+ npx makaron-cli skills list
108
+ npx makaron-cli skills search "football"
109
+ npx makaron-cli skills search "足球"
110
+ npx makaron-cli skills show <marketplace-id-or-label>
111
+
112
+ # Install a marketplace skill into the API key owner's workspace
113
+ npx makaron-cli skills install <marketplace-id-or-label>
114
+
115
+ # Use a marketplace skill. If the skill is not installed yet, chat auto-installs it,
116
+ # then injects the installed skill name into the agent run.
117
+ npx makaron-cli chat --project auto \
118
+ --image selfie.jpg \
119
+ --skill <marketplace-id-or-label> \
120
+ -b "make this with the selected skill"
121
+ ```
122
+
123
+ `--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.
124
+
99
125
  ### With additional images (existing project)
100
126
 
101
127
  ```bash
@@ -212,8 +238,8 @@ npx makaron-cli analyze --video input.mp4 "describe the key actions and pacing"
212
238
  npx makaron-cli video create --script "Shot 1 (5s): <<<image_1>>> ..." --image https://...jpg --duration 5 --model kling
213
239
 
214
240
  # 3b. Edit a video from a local file or public URL
215
- npx makaron-cli video create --script "make it funny" --video input.mp4 --duration 5 --model seedance
216
- npx makaron-cli video create --script "make it warmer and cinematic" --video https://example.com/input.mp4 --duration 5 --model seedance
241
+ npx makaron-cli video create --script "make it funny" --video input.mp4 --duration 5 --model seedance-fast
242
+ npx makaron-cli video create --script "make it warmer and cinematic" --video https://example.com/input.mp4 --duration 5 --model seedance --video-resolution 1080p
217
243
 
218
244
  # 4. Check status
219
245
  npx makaron-cli video status <taskId>
@@ -225,9 +251,9 @@ npx makaron-cli video status <taskId>
225
251
  npx makaron-cli chat --project <id|auto> --video input.mp4 -b "make it funny"
226
252
  ```
227
253
 
228
- 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 kling|seedance`. SeeDance accepts integer output duration 4-15s (default 5s); Kling supports 5-15s.
254
+ 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|kling|grok`, `--video-resolution auto|480p|720p|1080p|4k`. Default model is `seedance-fast`. SeeDance accepts integer output duration 4-15s (default 5s); Kling supports 5-15s; Grok 1.5 supports 1-15s single-image-to-video only. 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.
229
255
 
230
- Video edit model behavior: `--model kling --video` uses Kling base/direct edit internally; `--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. Tiny metadata padding up to 15.5s is accepted and output duration is clamped to 15s.
256
+ Video edit model behavior: `--model kling --video` uses Kling base/direct edit internally; `--model seedance-fast --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. Tiny metadata padding up to 15.5s is accepted and output duration is clamped to 15s. Grok does not support video references.
231
257
 
232
258
  ### `music` — Music generation
233
259
 
@@ -339,7 +365,7 @@ send_message "All done!"
339
365
  - One project = one conversation thread. All history is preserved.
340
366
  - One run at a time per project. New message interrupts previous run.
341
367
  - Multi-image: `create --image a.jpg --image b.jpg` or `chat --image ref.jpg`.
342
- - Videos take 2-5 minutes to render. Use `responses get <runId> --wait --json` for the default customer-service path.
368
+ - Most videos take 3-5 minutes; Grok is usually around 30-40 seconds. Use `responses get <runId> --wait --json` for the default customer-service path.
343
369
  - Music takes ~60 seconds. Appears in output when done.
344
370
  - Images are typically ready in 15-30 seconds.
345
371
  - stdout is always machine-readable JSON/text. Human-friendly logs go to stderr.