makaron-cli 0.10.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
package/bin/makaron.mjs CHANGED
@@ -792,6 +792,155 @@ function timeSince(date) {
792
792
  return `${Math.floor(s / 86400)}d ago`;
793
793
  }
794
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
+
795
944
  // ─── MCP Tool Caller ─────────────────────────────────────────────────────────
796
945
 
797
946
  async function callMcpTool(baseUrl, headers, toolName, args) {
@@ -1068,6 +1217,7 @@ Commands:
1068
1217
  responses get <runId> --wait Poll until completed
1069
1218
  responses list --project <id> List runs for a project
1070
1219
  abort <runId> Abort a running Agent
1220
+ skills list|search|show|install Browse and install marketplace skills
1071
1221
 
1072
1222
  edit [--image <file>] "prompt" AI image edit / text-to-image
1073
1223
  analyze --video <file|url> Analyze video content
@@ -1109,6 +1259,20 @@ function printHelp(topic, subtopic) {
1109
1259
  `);
1110
1260
  } else if (topic === 'abort') {
1111
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
+ `);
1112
1276
  } else if (topic === 'edit') {
1113
1277
  console.log('Usage: makaron edit [--image <file|url>] [--model gemini|qwen|openai] [--skill enhance|creative|wild|captions] [--ref <file>] [--out <file>] "prompt"');
1114
1278
  } else if (topic === 'analyze') {
@@ -1206,6 +1370,7 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
1206
1370
  else if (args[i] === '--image' && args[i + 1]) chatImages.push(args[++i]);
1207
1371
  else if (args[i] === '--video' && args[i + 1]) chatVideos.push(args[++i]);
1208
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);
1209
1374
  else if (args[i] === '--stream') useStream = true;
1210
1375
  else if (args[i] === '--background' || args[i] === '-b') background = true;
1211
1376
  else if (args[i] === '--json') jsonOutput = true;
@@ -1297,8 +1462,10 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
1297
1462
  }
1298
1463
  }
1299
1464
 
1465
+ const resolvedSkill = await resolveChatSkill(baseUrl, headers, activeSkill);
1466
+
1300
1467
  // Upload videos to project timeline (via /api/projects/create with videoUrls)
1301
- let finalPrompt = activeSkill ? `[Active skill: ${activeSkill}]\n${prompt}` : prompt;
1468
+ let finalPrompt = resolvedSkill ? `[Active skill: ${resolvedSkill}]\n${prompt}` : prompt;
1302
1469
  if (chatVideos.length > 0) {
1303
1470
  // Upload local files via signed URL (no size limit, works with API key auth)
1304
1471
  const uploadedVideoUrls = [...prevalidatedVideoUrlList];
@@ -1376,10 +1543,9 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
1376
1543
  if (sub === 'get') {
1377
1544
  const runId = args[2];
1378
1545
  if (!runId) { console.error('Usage: makaron responses get <runId> [--wait] [--json] [--pick <field>]'); process.exit(1); }
1379
- let wait = false, jsonOutput = false, pick = null;
1546
+ let wait = false, pick = null;
1380
1547
  for (let i = 3; i < args.length; i++) {
1381
1548
  if (args[i] === '--wait') wait = true;
1382
- if (args[i] === '--json') jsonOutput = true;
1383
1549
  if (args[i] === '--pick' && args[i + 1]) pick = args[++i];
1384
1550
  }
1385
1551
 
@@ -1439,6 +1605,54 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
1439
1605
  } else if (command === 'list' || command === 'ls') {
1440
1606
  const { headers, baseUrl } = getAuth();
1441
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
+ }
1442
1656
  } else if (command === 'project' || command === 'projects') {
1443
1657
  const { headers, baseUrl } = getAuth();
1444
1658
  const sub = args[1];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "makaron-cli",
3
- "version": "0.10.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