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.
- package/.claude-plugin/plugin.json +2 -2
- package/.codex-plugin/plugin.json +5 -5
- package/README.md +23 -5
- package/bin/makaron.mjs +235 -16
- package/package.json +1 -1
- package/skills/makaron/SKILL.md +31 -5
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "makaron-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "AI image editing, video generation,
|
|
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.
|
|
4
|
-
"description": "AI image editing, video generation,
|
|
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
|
|
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
|
|
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
|
-
-
|
|
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|
|
|
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
|
|
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 =
|
|
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,
|
|
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|
|
|
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 || '
|
|
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
|
|
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
package/skills/makaron/SKILL.md
CHANGED
|
@@ -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
|
|
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
|
-
-
|
|
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.
|