makaron-cli 0.2.0 → 0.3.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/README.md +70 -0
- package/bin/makaron.mjs +155 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -101,6 +101,76 @@ npx makaron-cli abort <runId>
|
|
|
101
101
|
|
|
102
102
|
Press `Ctrl+C` during `chat` to abort automatically.
|
|
103
103
|
|
|
104
|
+
### `edit` — AI image editing (direct MCP tool call)
|
|
105
|
+
|
|
106
|
+
Unlike `chat` (which uses the Agent with project context), `edit` directly calls the image generation model for one-shot results.
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# Text-to-image (no input image)
|
|
110
|
+
npx makaron-cli edit "a cyberpunk cityscape at night, neon reflections"
|
|
111
|
+
|
|
112
|
+
# Edit an existing image
|
|
113
|
+
npx makaron-cli edit --image photo.jpg "add cinematic warm lighting"
|
|
114
|
+
|
|
115
|
+
# With model and skill
|
|
116
|
+
npx makaron-cli edit --image photo.jpg --model openai --skill captions "add elegant title"
|
|
117
|
+
|
|
118
|
+
# With reference images (up to 3)
|
|
119
|
+
npx makaron-cli edit --image photo.jpg --ref style1.jpg --ref style2.jpg "match this style"
|
|
120
|
+
|
|
121
|
+
# Specify output path and aspect ratio
|
|
122
|
+
npx makaron-cli edit --out result.jpg --aspect 9:16 "vertical poster design"
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Options:
|
|
126
|
+
- `--image <file|url>` — input image (omit for text-to-image)
|
|
127
|
+
- `--model gemini|qwen|openai|pony|wai` — model selection (default: auto)
|
|
128
|
+
- `--skill enhance|creative|wild|captions` — activate skill template
|
|
129
|
+
- `--ref <file|url>` — reference image (repeatable, up to 3)
|
|
130
|
+
- `--aspect <ratio>` — target aspect ratio (e.g. `4:5`, `1:1`, `16:9`)
|
|
131
|
+
- `--out <path>` — output file path (default: `makaron-output-{timestamp}.jpg`)
|
|
132
|
+
|
|
133
|
+
Output: saves image to local file, prints the file path to stdout.
|
|
134
|
+
|
|
135
|
+
### `video` — Video generation
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
# Write a video script from images
|
|
139
|
+
npx makaron-cli video script --image img1.jpg --image img2.jpg "cinematic story"
|
|
140
|
+
npx makaron-cli video script --image img1.jpg --image img2.jpg --lang zh "电影感故事"
|
|
141
|
+
|
|
142
|
+
# Submit video rendering (images must be public URLs)
|
|
143
|
+
npx makaron-cli video create --script "Shot 1..." --image https://...img1.jpg --duration 10
|
|
144
|
+
npx makaron-cli video create --script-file script.txt --image https://...jpg --model seedance
|
|
145
|
+
|
|
146
|
+
# Check status
|
|
147
|
+
npx makaron-cli video status <taskId>
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Options for `video create`:
|
|
151
|
+
- `--script "..."` or `--script-file <path>` — video script
|
|
152
|
+
- `--image <url>` — public image URL (repeatable, up to 7)
|
|
153
|
+
- `--duration 3|5|7|10|15` — seconds (omit for smart mode)
|
|
154
|
+
- `--aspect 9:16|16:9|1:1` — aspect ratio
|
|
155
|
+
- `--model kling|seedance` — video model (default: kling)
|
|
156
|
+
|
|
157
|
+
### `music` — Music generation
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
# Generate instrumental music
|
|
161
|
+
npx makaron-cli music create "gentle piano, warm strings, cinematic"
|
|
162
|
+
|
|
163
|
+
# With vocals and style
|
|
164
|
+
npx makaron-cli music create --vocals --style "lo-fi, ambient" "rainy day vibes"
|
|
165
|
+
|
|
166
|
+
# Check status
|
|
167
|
+
npx makaron-cli music status <taskId>
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Options for `music create`:
|
|
171
|
+
- `--vocals` — include vocals (default: instrumental only)
|
|
172
|
+
- `--style "genre"` — genre/mood tags
|
|
173
|
+
|
|
104
174
|
## Example: Full workflow
|
|
105
175
|
|
|
106
176
|
```bash
|
package/bin/makaron.mjs
CHANGED
|
@@ -337,6 +337,43 @@ function timeSince(date) {
|
|
|
337
337
|
return `${Math.floor(s / 86400)}d ago`;
|
|
338
338
|
}
|
|
339
339
|
|
|
340
|
+
// ─── MCP Tool Caller ─────────────────────────────────────────────────────────
|
|
341
|
+
|
|
342
|
+
async function callMcpTool(baseUrl, headers, toolName, args) {
|
|
343
|
+
const res = await fetch(`${baseUrl}/api/mcp`, {
|
|
344
|
+
method: 'POST',
|
|
345
|
+
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json, text/event-stream', ...headers },
|
|
346
|
+
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/call', params: { name: toolName, arguments: args } }),
|
|
347
|
+
});
|
|
348
|
+
if (!res.ok) { console.error(`MCP error ${res.status}:`, await res.text()); process.exit(1); }
|
|
349
|
+
const data = await res.json();
|
|
350
|
+
if (data.error) { console.error(`MCP error:`, data.error.message); process.exit(1); }
|
|
351
|
+
return data.result;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function imageToArg(imgPath) {
|
|
355
|
+
if (imgPath.startsWith('http://') || imgPath.startsWith('https://')) return imgPath;
|
|
356
|
+
const buf = fs.readFileSync(imgPath);
|
|
357
|
+
const ext = imgPath.split('.').pop()?.toLowerCase();
|
|
358
|
+
const mime = { jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png', webp: 'image/webp' }[ext] || 'image/jpeg';
|
|
359
|
+
return `data:${mime};base64,${buf.toString('base64')}`;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function saveMcpImage(result, outputPath) {
|
|
363
|
+
const content = result?.content || [];
|
|
364
|
+
const textBlock = content.find(c => c.type === 'text');
|
|
365
|
+
const imageBlock = content.find(c => c.type === 'image');
|
|
366
|
+
if (textBlock) process.stderr.write(`${textBlock.text}\n`);
|
|
367
|
+
if (imageBlock) {
|
|
368
|
+
const out = outputPath || `makaron-output-${Date.now()}.jpg`;
|
|
369
|
+
fs.writeFileSync(out, Buffer.from(imageBlock.data, 'base64'));
|
|
370
|
+
console.log(out);
|
|
371
|
+
return out;
|
|
372
|
+
}
|
|
373
|
+
if (textBlock) console.log(textBlock.text);
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
|
|
340
377
|
// ─── Main ────────────────────────────────────────────────────────────────────
|
|
341
378
|
|
|
342
379
|
const args = process.argv.slice(2);
|
|
@@ -419,6 +456,119 @@ if (command === 'login') {
|
|
|
419
456
|
} else {
|
|
420
457
|
console.error(`❌ Abort failed:`, await res.text());
|
|
421
458
|
}
|
|
459
|
+
} else if (command === 'edit') {
|
|
460
|
+
const { headers, baseUrl } = getAuth();
|
|
461
|
+
const editArgs = {};
|
|
462
|
+
const promptParts = [];
|
|
463
|
+
let outputPath = null;
|
|
464
|
+
for (let i = 1; i < args.length; i++) {
|
|
465
|
+
if (args[i] === '--image' && args[i + 1]) editArgs.image = imageToArg(args[++i]);
|
|
466
|
+
else if (args[i] === '--model' && args[i + 1]) editArgs.model = args[++i];
|
|
467
|
+
else if (args[i] === '--skill' && args[i + 1]) editArgs.skill = args[++i];
|
|
468
|
+
else if (args[i] === '--ref' && args[i + 1]) {
|
|
469
|
+
editArgs.referenceImages = editArgs.referenceImages || [];
|
|
470
|
+
editArgs.referenceImages.push(imageToArg(args[++i]));
|
|
471
|
+
}
|
|
472
|
+
else if (args[i] === '--aspect' && args[i + 1]) editArgs.aspectRatio = args[++i];
|
|
473
|
+
else if (args[i] === '--out' && args[i + 1]) outputPath = args[++i];
|
|
474
|
+
else promptParts.push(args[i]);
|
|
475
|
+
}
|
|
476
|
+
editArgs.editPrompt = promptParts.join(' ');
|
|
477
|
+
if (!editArgs.editPrompt) { console.error('Usage: makaron edit [--image <file|url>] [--model gemini|qwen|openai] [--skill enhance|creative|wild|captions] [--ref <file>] [--out <file>] "prompt"'); process.exit(1); }
|
|
478
|
+
process.stderr.write('🎨 Generating...\n');
|
|
479
|
+
const result = await callMcpTool(baseUrl, headers, 'makaron_edit_image', editArgs);
|
|
480
|
+
saveMcpImage(result, outputPath);
|
|
481
|
+
|
|
482
|
+
} else if (command === 'video') {
|
|
483
|
+
const { headers, baseUrl } = getAuth();
|
|
484
|
+
const sub = args[1];
|
|
485
|
+
|
|
486
|
+
if (sub === 'script') {
|
|
487
|
+
const images = [];
|
|
488
|
+
const promptParts = [];
|
|
489
|
+
let language = 'en';
|
|
490
|
+
for (let i = 2; i < args.length; i++) {
|
|
491
|
+
if (args[i] === '--image' && args[i + 1]) images.push(imageToArg(args[++i]));
|
|
492
|
+
else if (args[i] === '--lang' && args[i + 1]) language = args[++i];
|
|
493
|
+
else promptParts.push(args[i]);
|
|
494
|
+
}
|
|
495
|
+
if (!images.length) { console.error('Usage: makaron video script --image <file> [--image <file>] [--lang en|zh] "direction"'); process.exit(1); }
|
|
496
|
+
process.stderr.write('🎬 Writing script...\n');
|
|
497
|
+
const result = await callMcpTool(baseUrl, headers, 'makaron_write_video_script', { images, userRequest: promptParts.join(' ') || undefined, language });
|
|
498
|
+
const text = result?.content?.find(c => c.type === 'text')?.text;
|
|
499
|
+
if (text) console.log(text);
|
|
500
|
+
|
|
501
|
+
} else if (sub === 'create') {
|
|
502
|
+
const images = [];
|
|
503
|
+
let script = '', duration = undefined, aspectRatio = undefined, videoModel = undefined;
|
|
504
|
+
for (let i = 2; i < args.length; i++) {
|
|
505
|
+
if (args[i] === '--image' && args[i + 1]) images.push(args[++i]);
|
|
506
|
+
else if (args[i] === '--script' && args[i + 1]) script = args[++i];
|
|
507
|
+
else if (args[i] === '--script-file' && args[i + 1]) script = fs.readFileSync(args[++i], 'utf-8');
|
|
508
|
+
else if (args[i] === '--duration' && args[i + 1]) duration = Number(args[++i]);
|
|
509
|
+
else if (args[i] === '--aspect' && args[i + 1]) aspectRatio = args[++i];
|
|
510
|
+
else if (args[i] === '--model' && args[i + 1]) videoModel = args[++i];
|
|
511
|
+
}
|
|
512
|
+
if (!images.length || !script) { console.error('Usage: makaron video create --script "..." --image <url> [--duration 10] [--aspect 9:16] [--model kling|seedance]'); process.exit(1); }
|
|
513
|
+
process.stderr.write('🎬 Submitting video...\n');
|
|
514
|
+
const vArgs = { script, images };
|
|
515
|
+
if (duration) vArgs.duration = duration;
|
|
516
|
+
if (aspectRatio) vArgs.aspectRatio = aspectRatio;
|
|
517
|
+
if (videoModel) vArgs.videoModel = videoModel;
|
|
518
|
+
const result = await callMcpTool(baseUrl, headers, 'makaron_create_video', vArgs);
|
|
519
|
+
const text = result?.content?.find(c => c.type === 'text')?.text;
|
|
520
|
+
if (text) console.log(text);
|
|
521
|
+
|
|
522
|
+
} else if (sub === 'status') {
|
|
523
|
+
const taskId = args[2];
|
|
524
|
+
if (!taskId) { console.error('Usage: makaron video status <taskId>'); process.exit(1); }
|
|
525
|
+
const result = await callMcpTool(baseUrl, headers, 'makaron_get_video_status', { taskId });
|
|
526
|
+
const text = result?.content?.find(c => c.type === 'text')?.text;
|
|
527
|
+
if (text) console.log(text);
|
|
528
|
+
|
|
529
|
+
} else {
|
|
530
|
+
console.log(`Video commands:
|
|
531
|
+
video script --image <file> [--image <file>] "direction" Write video script
|
|
532
|
+
video create --script "..." --image <url> [--duration 10] Submit video task
|
|
533
|
+
video status <taskId> Check video status
|
|
534
|
+
`);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
} else if (command === 'music') {
|
|
538
|
+
const { headers, baseUrl } = getAuth();
|
|
539
|
+
const sub = args[1];
|
|
540
|
+
|
|
541
|
+
if (sub === 'create') {
|
|
542
|
+
const promptParts = [];
|
|
543
|
+
let instrumental = true, style = undefined;
|
|
544
|
+
for (let i = 2; i < args.length; i++) {
|
|
545
|
+
if (args[i] === '--vocals') instrumental = false;
|
|
546
|
+
else if (args[i] === '--style' && args[i + 1]) style = args[++i];
|
|
547
|
+
else promptParts.push(args[i]);
|
|
548
|
+
}
|
|
549
|
+
const prompt = promptParts.join(' ');
|
|
550
|
+
if (!prompt) { console.error('Usage: makaron music create [--vocals] [--style "lo-fi"] "gentle piano"'); process.exit(1); }
|
|
551
|
+
process.stderr.write('🎵 Generating music...\n');
|
|
552
|
+
const mArgs = { prompt, instrumental };
|
|
553
|
+
if (style) mArgs.style = style;
|
|
554
|
+
const result = await callMcpTool(baseUrl, headers, 'makaron_create_music', mArgs);
|
|
555
|
+
const text = result?.content?.find(c => c.type === 'text')?.text;
|
|
556
|
+
if (text) console.log(text);
|
|
557
|
+
|
|
558
|
+
} else if (sub === 'status') {
|
|
559
|
+
const taskId = args[2];
|
|
560
|
+
if (!taskId) { console.error('Usage: makaron music status <taskId>'); process.exit(1); }
|
|
561
|
+
const result = await callMcpTool(baseUrl, headers, 'makaron_get_music_status', { taskId });
|
|
562
|
+
const text = result?.content?.find(c => c.type === 'text')?.text;
|
|
563
|
+
if (text) console.log(text);
|
|
564
|
+
|
|
565
|
+
} else {
|
|
566
|
+
console.log(`Music commands:
|
|
567
|
+
music create [--vocals] [--style "genre"] "description" Generate music
|
|
568
|
+
music status <taskId> Check music status
|
|
569
|
+
`);
|
|
570
|
+
}
|
|
571
|
+
|
|
422
572
|
} else if (command === 'admin') {
|
|
423
573
|
const { headers, baseUrl } = getAuth();
|
|
424
574
|
const sub = args[1];
|
|
@@ -549,6 +699,11 @@ Commands:
|
|
|
549
699
|
chat --project <id> "message" Chat with Makaron Agent
|
|
550
700
|
chat --project <id> --image <file> "message" Add image + chat
|
|
551
701
|
abort <runId> Abort a running Agent
|
|
702
|
+
|
|
703
|
+
edit [--image <file>] "prompt" AI image edit / text-to-image
|
|
704
|
+
video script|create|status Video generation
|
|
705
|
+
music create|status Music generation
|
|
706
|
+
|
|
552
707
|
admin Admin commands (skills, upload, set-admin)
|
|
553
708
|
|
|
554
709
|
Environment:
|