makaron-cli 0.13.4 → 0.13.5
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 +4 -4
- package/.codex-plugin/plugin.json +21 -12
- package/README.md +35 -0
- package/bin/makaron.mjs +196 -8
- package/package.json +1 -1
- package/skills/makaron/SKILL.md +15 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "makaron-cli",
|
|
3
|
-
"version": "0.13.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.13.5",
|
|
4
|
+
"description": "Give Claude Code a creative agent. Pass complete creative requests and source media to Makaron Chat.",
|
|
5
5
|
"author": {
|
|
6
|
-
"name": "
|
|
6
|
+
"name": "Versa AI",
|
|
7
|
+
"email": "tianyi@versa-ai.com",
|
|
7
8
|
"url": "https://www.makaron.app"
|
|
8
9
|
},
|
|
9
10
|
"homepage": "https://www.makaron.app/agent",
|
|
10
|
-
"repository": "https://github.com/vegekyd/ai-image-editor",
|
|
11
11
|
"skills": "./skills/"
|
|
12
12
|
}
|
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "makaron-cli",
|
|
3
|
-
"version": "0.13.
|
|
4
|
-
"description": "
|
|
5
|
-
"displayName": "Makaron",
|
|
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 — 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.",
|
|
3
|
+
"version": "0.13.5",
|
|
4
|
+
"description": "Give Codex a creative agent. Pass complete creative requests and source media to Makaron Chat.",
|
|
8
5
|
"author": {
|
|
9
|
-
"name": "
|
|
6
|
+
"name": "Versa AI",
|
|
7
|
+
"email": "tianyi@versa-ai.com",
|
|
10
8
|
"url": "https://www.makaron.app"
|
|
11
9
|
},
|
|
12
10
|
"homepage": "https://www.makaron.app/agent",
|
|
13
|
-
"repository": "https://github.com/vegekyd/ai-image-editor",
|
|
14
11
|
"license": "MIT",
|
|
15
|
-
"keywords": ["
|
|
16
|
-
"category": "Creative Tools",
|
|
17
|
-
"capabilities": ["image-editing", "video-generation", "music-creation", "text-to-image", "marketplace-skills"],
|
|
12
|
+
"keywords": ["creative-production", "image-editing", "video-generation", "music", "agent", "cli"],
|
|
18
13
|
"skills": "./skills/",
|
|
19
|
-
"
|
|
20
|
-
|
|
14
|
+
"interface": {
|
|
15
|
+
"displayName": "Makaron Creative Studio",
|
|
16
|
+
"shortDescription": "Give Codex a creative agent",
|
|
17
|
+
"longDescription": "Pass any complete creative request and source media to Makaron Chat. Makaron plans and produces images, video, music, and motion design in a persistent project that a human can continue editing in makaron.app.",
|
|
18
|
+
"developerName": "Versa AI",
|
|
19
|
+
"category": "Creative Tools",
|
|
20
|
+
"capabilities": ["Read", "Write"],
|
|
21
|
+
"websiteURL": "https://www.makaron.app/agent",
|
|
22
|
+
"privacyPolicyURL": "https://www.makaron.app/privacy",
|
|
23
|
+
"defaultPrompt": [
|
|
24
|
+
"Pass this complete creative brief and its source files to Makaron Chat.",
|
|
25
|
+
"Ask Makaron Chat to turn this image into the finished media described in my request.",
|
|
26
|
+
"Continue this Makaron project with my complete follow-up request."
|
|
27
|
+
],
|
|
28
|
+
"brandColor": "#d946ef"
|
|
29
|
+
}
|
|
21
30
|
}
|
package/README.md
CHANGED
|
@@ -151,6 +151,41 @@ npx makaron-cli project media <projectId> --json
|
|
|
151
151
|
|
|
152
152
|
This is project-scoped. `responses get <runId> --pick output` only returns artifacts from one run; `project media` returns the whole project timeline: original uploads, references, generated images, video snapshots, and editable compositions.
|
|
153
153
|
|
|
154
|
+
External source ranges can be added without uploading either the original video or a derivative clip:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
npx makaron-cli project media add <projectId> \
|
|
158
|
+
--source-url "https://cdn.example.com/source.mp4" \
|
|
159
|
+
--start-sec 12.5 --end-sec 19 \
|
|
160
|
+
--source-uri "dam://project/asset" \
|
|
161
|
+
--description "Racket frame molding"
|
|
162
|
+
|
|
163
|
+
# Batch form: a JSON array or {"source_ranges": [...]}
|
|
164
|
+
npx makaron-cli project media add <projectId> --input ranges.json --json
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
The returned `<<<media_N>>>` is immediately usable by a later Agent run. Its range remains non-destructive metadata; Remotion must trim the original URL to these exact source bounds.
|
|
168
|
+
Use `description` as the provider-neutral media-understanding field. Put any
|
|
169
|
+
already-known summary, editorial purpose, concrete scene evidence, confidence,
|
|
170
|
+
and limitations there. Makaron exposes the full description in Media List
|
|
171
|
+
context so the Agent can edit from it without repeating image/video analysis
|
|
172
|
+
unless a required detail is missing or uncertain.
|
|
173
|
+
|
|
174
|
+
For an agent-to-agent handoff, create the project, import the external ranges,
|
|
175
|
+
and start the Agent in one command:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
npx makaron-cli chat --project auto \
|
|
179
|
+
--media-manifest set-01.json \
|
|
180
|
+
--json -b \
|
|
181
|
+
"Make a 30-second 9:16 TikTok with English VO and burned-in captions"
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
The manifest is a JSON array or `{ "title": "...", "source_ranges": [...] }`.
|
|
185
|
+
It is validated before project creation and supports up to 20 ranges for one
|
|
186
|
+
Makaron task. Batch planning remains the upstream orchestrator's responsibility:
|
|
187
|
+
convert each plan into one manifest and start one independent Makaron task.
|
|
188
|
+
|
|
154
189
|
### Export editable Remotion compositions
|
|
155
190
|
|
|
156
191
|
Animated Remotion compositions are saved as editable timeline/code artifacts first. To materialize one into an MP4 that CLI, V, or another service can read, call the backend export worker:
|
package/bin/makaron.mjs
CHANGED
|
@@ -156,6 +156,71 @@ function readJsonInput(filePath) {
|
|
|
156
156
|
return JSON.parse(raw);
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
const MAX_MEDIA_MANIFEST_RANGES = 20;
|
|
160
|
+
|
|
161
|
+
function normalizeMediaManifest(input) {
|
|
162
|
+
const manifest = Array.isArray(input) ? { source_ranges: input } : input;
|
|
163
|
+
if (!manifest || typeof manifest !== 'object') {
|
|
164
|
+
throw new Error('Media manifest must be a JSON object or an array of source ranges.');
|
|
165
|
+
}
|
|
166
|
+
const rawRanges = Array.isArray(manifest.source_ranges)
|
|
167
|
+
? manifest.source_ranges
|
|
168
|
+
: Array.isArray(manifest.sourceRanges)
|
|
169
|
+
? manifest.sourceRanges
|
|
170
|
+
: null;
|
|
171
|
+
if (!rawRanges?.length) {
|
|
172
|
+
throw new Error('Media manifest must contain a non-empty source_ranges array.');
|
|
173
|
+
}
|
|
174
|
+
if (rawRanges.length > MAX_MEDIA_MANIFEST_RANGES) {
|
|
175
|
+
throw new Error(`Media manifest supports at most ${MAX_MEDIA_MANIFEST_RANGES} source ranges per Makaron task.`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const sourceRanges = rawRanges.map((raw, index) => {
|
|
179
|
+
if (!raw || typeof raw !== 'object') throw new Error(`source_ranges[${index}] must be an object.`);
|
|
180
|
+
const sourceUrl = typeof raw.source_url === 'string' ? raw.source_url.trim() : '';
|
|
181
|
+
let parsed;
|
|
182
|
+
try {
|
|
183
|
+
parsed = new URL(sourceUrl);
|
|
184
|
+
} catch {
|
|
185
|
+
throw new Error(`source_ranges[${index}].source_url must be a valid HTTP(S) URL.`);
|
|
186
|
+
}
|
|
187
|
+
if (!['http:', 'https:'].includes(parsed.protocol)) {
|
|
188
|
+
throw new Error(`source_ranges[${index}].source_url must use HTTP or HTTPS.`);
|
|
189
|
+
}
|
|
190
|
+
const startSec = Number(raw.start_sec);
|
|
191
|
+
const endSec = Number(raw.end_sec);
|
|
192
|
+
if (!Number.isFinite(startSec) || startSec < 0) {
|
|
193
|
+
throw new Error(`source_ranges[${index}].start_sec must be a finite number >= 0.`);
|
|
194
|
+
}
|
|
195
|
+
if (!Number.isFinite(endSec) || endSec <= startSec) {
|
|
196
|
+
throw new Error(`source_ranges[${index}].end_sec must be greater than start_sec.`);
|
|
197
|
+
}
|
|
198
|
+
const range = { source_url: sourceUrl, start_sec: startSec, end_sec: endSec };
|
|
199
|
+
for (const key of ['source_uri', 'project_id', 'asset_id', 'file_name', 'description']) {
|
|
200
|
+
if (typeof raw[key] === 'string' && raw[key].trim()) range[key] = raw[key].trim();
|
|
201
|
+
}
|
|
202
|
+
for (const key of ['width', 'height']) {
|
|
203
|
+
const value = Number(raw[key]);
|
|
204
|
+
if (Number.isFinite(value) && value > 0) range[key] = value;
|
|
205
|
+
}
|
|
206
|
+
return range;
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
sourceRanges,
|
|
211
|
+
title: typeof manifest.title === 'string' && manifest.title.trim() ? manifest.title.trim() : undefined,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function readMediaManifest(filePath) {
|
|
216
|
+
try {
|
|
217
|
+
return normalizeMediaManifest(readJsonInput(filePath));
|
|
218
|
+
} catch (error) {
|
|
219
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
220
|
+
throw new Error(`Invalid media manifest: ${message}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
159
224
|
// ─── Auth ────────────────────────────────────────────────────────────────────
|
|
160
225
|
|
|
161
226
|
function loadAuth() {
|
|
@@ -285,6 +350,7 @@ Options:
|
|
|
285
350
|
--image <file|url> Attach a reference image or screenshot. Repeatable.
|
|
286
351
|
--video <file|url> Attach a video to the project timeline. Repeatable.
|
|
287
352
|
--audio <file|url> Attach a song, beat, or voice reference. MP3/WAV, repeatable.
|
|
353
|
+
--media-manifest <file|-> Import source_url + start_sec + end_sec ranges before this run.
|
|
288
354
|
--skill <id|label|name> Use an installed skill or auto-install a matched marketplace skill.
|
|
289
355
|
--background, -b Submit and print a runId.
|
|
290
356
|
--json Output structured JSON.
|
|
@@ -313,6 +379,9 @@ What you can ask:
|
|
|
313
379
|
Video cuts and assembly
|
|
314
380
|
makaron chat --project <id> --video clip.mp4 "cut out the dead air and keep the best 20 seconds"
|
|
315
381
|
|
|
382
|
+
Agent-to-agent source-range handoff
|
|
383
|
+
makaron chat --project auto --media-manifest set-01.json -b --json "make a 30s vertical video"
|
|
384
|
+
|
|
316
385
|
Music
|
|
317
386
|
makaron chat --project <id> "add calm piano background music"
|
|
318
387
|
|
|
@@ -826,9 +895,33 @@ async function listProjectMedia(baseUrl, headers, projectId, opts = {}) {
|
|
|
826
895
|
const status = item.status && item.status !== 'completed' ? ` ${item.status}` : '';
|
|
827
896
|
const duration = typeof item.duration === 'number' ? ` ${item.duration}s` : '';
|
|
828
897
|
const dimensions = item.width && item.height ? ` ${item.width}x${item.height}` : '';
|
|
829
|
-
const description = item.description ? ` — ${item.description}` : '';
|
|
898
|
+
const description = item.description ? ` — ${String(item.description).replace(/\s*\n\s*/g, ' | ')}` : '';
|
|
899
|
+
const sourceRange = item.source_range || item.sourceRange;
|
|
900
|
+
const range = sourceRange
|
|
901
|
+
? ` source ${formatSeconds(sourceRange.start_sec)}-${formatSeconds(sourceRange.end_sec)}s`
|
|
902
|
+
: '';
|
|
830
903
|
const url = item.url ? `\n ${item.url}` : '';
|
|
831
|
-
console.log(` ${String(item.index).padStart(2)}. ${ref} [${item.type}${status}${duration}${dimensions}]${description}${url}`);
|
|
904
|
+
console.log(` ${String(item.index).padStart(2)}. ${ref} [${item.type}${status}${duration}${dimensions}${range}]${description}${url}`);
|
|
905
|
+
}
|
|
906
|
+
return data;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
async function addProjectMediaSourceRanges(baseUrl, headers, projectId, ranges, opts = {}) {
|
|
910
|
+
const res = await fetch(`${baseUrl}/api/projects/${projectId}/media`, {
|
|
911
|
+
method: 'POST',
|
|
912
|
+
headers: { 'Content-Type': 'application/json', ...headers },
|
|
913
|
+
body: JSON.stringify({ source_ranges: ranges }),
|
|
914
|
+
});
|
|
915
|
+
if (!res.ok) { console.error('Project media add failed:', await res.text()); process.exit(1); }
|
|
916
|
+
const data = await res.json();
|
|
917
|
+
if (opts.json && !opts.silent) {
|
|
918
|
+
console.log(JSON.stringify(data, null, 2));
|
|
919
|
+
return data;
|
|
920
|
+
}
|
|
921
|
+
if (!opts.silent) {
|
|
922
|
+
for (const item of data.media || []) {
|
|
923
|
+
console.log(`${item.ref} ${item.source_url} ${formatSeconds(item.start_sec)}-${formatSeconds(item.end_sec)}s${item.created ? '' : ' (existing)'}`);
|
|
924
|
+
}
|
|
832
925
|
}
|
|
833
926
|
return data;
|
|
834
927
|
}
|
|
@@ -1593,6 +1686,8 @@ Commands:
|
|
|
1593
1686
|
credits Show current credit balance
|
|
1594
1687
|
list (ls) List all projects
|
|
1595
1688
|
project media <projectId> --json List timeline media for a project
|
|
1689
|
+
project media add <projectId> --source-url <url> --start-sec <n> --end-sec <n>
|
|
1690
|
+
Add an external source range without uploading video
|
|
1596
1691
|
create --image <file> Create project from local image
|
|
1597
1692
|
create --image-url <url> Create project from URL
|
|
1598
1693
|
create --title "name" Create empty project (text-to-image)
|
|
@@ -1601,6 +1696,8 @@ Commands:
|
|
|
1601
1696
|
chat --project <id> --skill <id> Use a built-in or marketplace skill
|
|
1602
1697
|
chat --project <id> --video <file> Attach video to conversation
|
|
1603
1698
|
chat --project <id> --audio <file> Attach song/beat/voice reference
|
|
1699
|
+
chat --project auto --media-manifest <file> "message"
|
|
1700
|
+
Create, import external ranges, and run Agent
|
|
1604
1701
|
chat --project <id> -b "message" Background: submit and print runId
|
|
1605
1702
|
chat --project <id> --stream "msg" Legacy: stream SSE in real-time
|
|
1606
1703
|
chat --project <id> --json "msg" Output structured JSON result
|
|
@@ -1708,9 +1805,12 @@ function printHelp(topic, subtopic) {
|
|
|
1708
1805
|
} else if (topic === 'credits' || topic === 'credit' || topic === 'balance') {
|
|
1709
1806
|
console.log('Usage: makaron credits [--json]');
|
|
1710
1807
|
} else if (topic === 'project' || topic === 'projects') {
|
|
1711
|
-
if (subtopic === 'media') console.log(
|
|
1808
|
+
if (subtopic === 'media') console.log(`Usage: makaron project media <projectId> [--json]
|
|
1809
|
+
makaron project media add <projectId> --source-url <url> --start-sec <n> --end-sec <n> [--source-uri <uri>] [--description <text>] [--json]
|
|
1810
|
+
makaron project media add <projectId> --input <ranges.json> [--json]`);
|
|
1712
1811
|
else console.log(`Project commands:
|
|
1713
1812
|
project media <projectId> --json List timeline media for a project
|
|
1813
|
+
project media add <projectId> ... Add external source_url + start_sec + end_sec media
|
|
1714
1814
|
`);
|
|
1715
1815
|
} else if (topic === 'abort') {
|
|
1716
1816
|
console.log('Usage: makaron abort <runId>');
|
|
@@ -1868,11 +1968,20 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
|
|
|
1868
1968
|
let background = false;
|
|
1869
1969
|
let jsonOutput = false;
|
|
1870
1970
|
let activeSkill = undefined;
|
|
1971
|
+
let mediaManifestPath = undefined;
|
|
1871
1972
|
for (let i = 1; i < args.length; i++) {
|
|
1872
1973
|
if (args[i] === '--project' && args[i + 1]) projectId = args[++i];
|
|
1873
1974
|
else if (args[i] === '--image' && args[i + 1]) chatImages.push(args[++i]);
|
|
1874
1975
|
else if (args[i] === '--video' && args[i + 1]) chatVideos.push(args[++i]);
|
|
1875
1976
|
else if (args[i] === '--audio' && args[i + 1]) chatAudios.push(args[++i]);
|
|
1977
|
+
else if (args[i] === '--media-manifest') {
|
|
1978
|
+
if (!args[i + 1] || args[i + 1].startsWith('--')) {
|
|
1979
|
+
process.stderr.write('❌ --media-manifest requires a JSON file path or -.\n');
|
|
1980
|
+
process.exit(1);
|
|
1981
|
+
}
|
|
1982
|
+
mediaManifestPath = args[++i];
|
|
1983
|
+
}
|
|
1984
|
+
else if (args[i].startsWith('--media-manifest=')) mediaManifestPath = args[i].slice('--media-manifest='.length);
|
|
1876
1985
|
else if (args[i] === '--skill' && args[i + 1]) activeSkill = args[++i];
|
|
1877
1986
|
else if (args[i].startsWith('--skill=')) activeSkill = args[i].slice('--skill='.length);
|
|
1878
1987
|
else if (args[i] === '--stream') useStream = true;
|
|
@@ -1898,6 +2007,15 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
|
|
|
1898
2007
|
console.error('Run: makaron chat --help');
|
|
1899
2008
|
process.exit(1);
|
|
1900
2009
|
}
|
|
2010
|
+
let mediaManifest;
|
|
2011
|
+
if (mediaManifestPath) {
|
|
2012
|
+
try {
|
|
2013
|
+
mediaManifest = readMediaManifest(mediaManifestPath);
|
|
2014
|
+
} catch (error) {
|
|
2015
|
+
process.stderr.write(`❌ ${error instanceof Error ? error.message : String(error)}\n`);
|
|
2016
|
+
process.exit(1);
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
1901
2019
|
const { headers, baseUrl } = getAuth();
|
|
1902
2020
|
// Split images into URLs vs local files
|
|
1903
2021
|
const imageUrlList = chatImages.filter(p => p.startsWith('http://') || p.startsWith('https://'));
|
|
@@ -1954,13 +2072,31 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
|
|
|
1954
2072
|
const res = await fetch(`${baseUrl}/api/projects/create`, {
|
|
1955
2073
|
method: 'POST',
|
|
1956
2074
|
headers: { 'Content-Type': 'application/json', ...headers },
|
|
1957
|
-
body: JSON.stringify({ title: prompt.slice(0, 50) }),
|
|
2075
|
+
body: JSON.stringify({ title: mediaManifest?.title || prompt.slice(0, 50) }),
|
|
1958
2076
|
});
|
|
1959
2077
|
if (!res.ok) { process.stderr.write(`❌ Failed to create project: ${await res.text()}\n`); process.exit(1); }
|
|
1960
2078
|
const data = await res.json();
|
|
1961
2079
|
projectId = data.projectId;
|
|
1962
2080
|
process.stderr.write(`📦 Project created: ${projectId}\n`);
|
|
1963
2081
|
}
|
|
2082
|
+
let importedManifestMedia = [];
|
|
2083
|
+
if (mediaManifest) {
|
|
2084
|
+
const imported = await addProjectMediaSourceRanges(
|
|
2085
|
+
baseUrl,
|
|
2086
|
+
headers,
|
|
2087
|
+
projectId,
|
|
2088
|
+
mediaManifest.sourceRanges,
|
|
2089
|
+
{ silent: true },
|
|
2090
|
+
);
|
|
2091
|
+
importedManifestMedia = imported.media || [];
|
|
2092
|
+
if (importedManifestMedia.length !== mediaManifest.sourceRanges.length) {
|
|
2093
|
+
process.stderr.write(`❌ Imported ${importedManifestMedia.length}/${mediaManifest.sourceRanges.length} source range(s); aborting run.\n`);
|
|
2094
|
+
process.exit(1);
|
|
2095
|
+
}
|
|
2096
|
+
uploadedTurnMediaCount += importedManifestMedia.length;
|
|
2097
|
+
uploadedTurnVideoCount += importedManifestMedia.length;
|
|
2098
|
+
process.stderr.write(`📎 Imported ${importedManifestMedia.length} external source range(s) from media manifest\n`);
|
|
2099
|
+
}
|
|
1964
2100
|
// Upload additional images to existing project
|
|
1965
2101
|
if (imageFileList.length > 0 || imageUrlList.length > 0) {
|
|
1966
2102
|
const uploadedImageUrls = imageFileList.length
|
|
@@ -2106,7 +2242,20 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
|
|
|
2106
2242
|
if (background) {
|
|
2107
2243
|
// Just print runId and exit
|
|
2108
2244
|
if (jsonOutput) {
|
|
2109
|
-
console.log(JSON.stringify({
|
|
2245
|
+
console.log(JSON.stringify({
|
|
2246
|
+
runId,
|
|
2247
|
+
projectId,
|
|
2248
|
+
projectUrl: `${APP_URL}/projects/${projectId}`,
|
|
2249
|
+
status: 'running',
|
|
2250
|
+
...(importedManifestMedia.length ? {
|
|
2251
|
+
importedMedia: importedManifestMedia.map(item => ({
|
|
2252
|
+
ref: item.ref,
|
|
2253
|
+
source_url: item.source_url,
|
|
2254
|
+
start_sec: item.start_sec,
|
|
2255
|
+
end_sec: item.end_sec,
|
|
2256
|
+
})),
|
|
2257
|
+
} : {}),
|
|
2258
|
+
}));
|
|
2110
2259
|
} else {
|
|
2111
2260
|
console.log(runId);
|
|
2112
2261
|
}
|
|
@@ -2344,13 +2493,52 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
|
|
|
2344
2493
|
const { headers, baseUrl } = getAuth();
|
|
2345
2494
|
const sub = args[1];
|
|
2346
2495
|
if (sub === 'media') {
|
|
2347
|
-
const projectId = args[2];
|
|
2348
|
-
if (!projectId) { console.error('Usage: makaron project media <projectId> [--json]'); process.exit(1); }
|
|
2349
2496
|
const jsonOutput = args.includes('--json');
|
|
2350
|
-
|
|
2497
|
+
if (args[2] === 'add') {
|
|
2498
|
+
const projectId = args[3];
|
|
2499
|
+
if (!projectId) { console.error('Usage: makaron project media add <projectId> --source-url <url> --start-sec <n> --end-sec <n>'); process.exit(1); }
|
|
2500
|
+
const readOption = (name) => {
|
|
2501
|
+
const index = args.indexOf(name);
|
|
2502
|
+
return index >= 0 ? args[index + 1] : undefined;
|
|
2503
|
+
};
|
|
2504
|
+
const inputPath = readOption('--input');
|
|
2505
|
+
let ranges;
|
|
2506
|
+
if (inputPath) {
|
|
2507
|
+
const input = readJsonInput(inputPath);
|
|
2508
|
+
ranges = Array.isArray(input) ? input : input.source_ranges || input.sourceRanges;
|
|
2509
|
+
} else {
|
|
2510
|
+
const sourceUrl = readOption('--source-url');
|
|
2511
|
+
const startSec = Number(readOption('--start-sec'));
|
|
2512
|
+
const endSec = Number(readOption('--end-sec'));
|
|
2513
|
+
if (!sourceUrl || !Number.isFinite(startSec) || !Number.isFinite(endSec)) {
|
|
2514
|
+
console.error('Provide --source-url, --start-sec, and --end-sec, or --input <ranges.json>.');
|
|
2515
|
+
process.exit(1);
|
|
2516
|
+
}
|
|
2517
|
+
ranges = [{
|
|
2518
|
+
source_url: sourceUrl,
|
|
2519
|
+
start_sec: startSec,
|
|
2520
|
+
end_sec: endSec,
|
|
2521
|
+
...(readOption('--source-uri') ? { source_uri: readOption('--source-uri') } : {}),
|
|
2522
|
+
...(readOption('--project-id') ? { project_id: readOption('--project-id') } : {}),
|
|
2523
|
+
...(readOption('--asset-id') ? { asset_id: readOption('--asset-id') } : {}),
|
|
2524
|
+
...(readOption('--file-name') ? { file_name: readOption('--file-name') } : {}),
|
|
2525
|
+
...(readOption('--description') ? { description: readOption('--description') } : {}),
|
|
2526
|
+
}];
|
|
2527
|
+
}
|
|
2528
|
+
if (!Array.isArray(ranges) || !ranges.length) {
|
|
2529
|
+
console.error('Input must contain a non-empty source_ranges array.');
|
|
2530
|
+
process.exit(1);
|
|
2531
|
+
}
|
|
2532
|
+
await addProjectMediaSourceRanges(baseUrl, headers, projectId, ranges, { json: jsonOutput });
|
|
2533
|
+
} else {
|
|
2534
|
+
const projectId = args[2];
|
|
2535
|
+
if (!projectId) { console.error('Usage: makaron project media <projectId> [--json]'); process.exit(1); }
|
|
2536
|
+
await listProjectMedia(baseUrl, headers, projectId, { json: jsonOutput });
|
|
2537
|
+
}
|
|
2351
2538
|
} else {
|
|
2352
2539
|
console.log(`Project commands:
|
|
2353
2540
|
project media <projectId> --json List timeline media for a project
|
|
2541
|
+
project media add <projectId> ... Add external source_url + start_sec + end_sec media
|
|
2354
2542
|
`);
|
|
2355
2543
|
}
|
|
2356
2544
|
} else if (command === 'abort') {
|
package/package.json
CHANGED
package/skills/makaron/SKILL.md
CHANGED
|
@@ -149,6 +149,21 @@ npx makaron-cli project media <projectId> --json
|
|
|
149
149
|
|
|
150
150
|
This is project-scoped. `responses get <runId> --pick output` only returns artifacts from one run; `project media` returns the whole project timeline: original uploads, references, generated images, video snapshots, and editable compositions.
|
|
151
151
|
|
|
152
|
+
Publish an external video interval directly into that Media List without uploading the original or a derivative MP4:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
npx makaron-cli project media add <projectId> --source-url "https://cdn.example.com/source.mp4" --start-sec 12.5 --end-sec 19 --source-uri "dam://project/asset" --description "Racket frame molding"
|
|
156
|
+
npx makaron-cli project media add <projectId> --input ranges.json --json
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The JSON input may be an array or `{ "source_ranges": [...] }`. Each item uses `source_url + start_sec + end_sec`; optional `source_uri`, `project_id`, and `asset_id` preserve durable source identity. Put existing media understanding (summary, editorial purpose, scene evidence, confidence, and limitations) in `description`. Makaron reads that provider-neutral Media List field before deciding whether any additional image/video analysis is needed.
|
|
160
|
+
|
|
161
|
+
For one-call orchestration, use `chat --project auto --media-manifest plan.json`. Makaron validates the manifest, creates the project, imports its ranges, and starts the Agent. If an upstream service returns multiple plans, the caller should start one independent Makaron task per plan instead of passing the provider-specific batch response into Makaron.
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
npx makaron-cli chat --project auto --media-manifest set-01.json --json -b "Make a 30-second 9:16 TikTok with English VO and captions"
|
|
165
|
+
```
|
|
166
|
+
|
|
152
167
|
### With video input (edit, compose, extend)
|
|
153
168
|
|
|
154
169
|
```bash
|