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.
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "makaron-cli",
3
- "version": "0.13.3",
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.",
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": "Makaron AI",
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.3",
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
- "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": "Makaron AI",
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": ["ai", "image-editing", "video", "music", "agent", "creative", "skills"],
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
- "websiteURL": "https://www.makaron.app",
20
- "brandColor": "#d946ef"
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('Usage: makaron project media <projectId> [--json]');
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({ runId, projectId, projectUrl: `${APP_URL}/projects/${projectId}`, status: 'running' }));
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
- await listProjectMedia(baseUrl, headers, projectId, { json: jsonOutput });
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "makaron-cli",
3
- "version": "0.13.4",
3
+ "version": "0.13.5",
4
4
  "description": "Talk to Makaron Agent from the terminal — create projects, edit images, generate videos",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -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