makaron-cli 0.13.8 → 0.13.9

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,6 +1,6 @@
1
1
  {
2
2
  "name": "makaron-cli",
3
- "version": "0.13.8",
3
+ "version": "0.13.9",
4
4
  "description": "Give Claude Code a creative agent. Pass complete creative requests and source media to Makaron Chat.",
5
5
  "author": {
6
6
  "name": "Versa AI",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "makaron-cli",
3
- "version": "0.13.8",
3
+ "version": "0.13.9",
4
4
  "description": "Give Codex a creative agent. Pass complete creative requests and source media to Makaron Chat.",
5
5
  "author": {
6
6
  "name": "Versa AI",
package/README.md CHANGED
@@ -377,7 +377,7 @@ For project/timeline video editing, use:
377
377
  npx makaron-cli chat --project <id|auto> --video input.mp4 -b "make it funny"
378
378
  ```
379
379
 
380
- Options for `video create`: `--script "..."`, `--script-file <path>`, `--image <url>` (repeatable, up to the selected model limit), `--video <file|url>` and `--audio <file|url>` (repeatable where supported), `--duration <seconds>`, `--aspect 9:16|16:9|1:1`, `--video-model seedance-fast|seedance-mini|seedance|seedance-2.5|kling|grok|google-omni|minimax-h3`, `--video-resolution auto|480p|720p|768p|1080p|2k|4k`. Default model is `seedance-fast`. SeeDance accepts native text-to-video with no image and integer output duration 4-15s (default 5s); `seedance-mini` supports 480p/720p and is best for cheaper drafts/multi-size tests; MiniMax H3 accepts native text-to-video, 4-15s output, public 768p/2k resolution, and up to 9 image, up to 3 video, and up to 3 audio references through Makaron Agent/chat. H3 defaults to 768p; request 2k explicitly for maximum/final quality. Kling supports 5-15s; Grok 1.5 supports 1-15s single-image-to-video only; Gemini Omni supports 3-10s fast 720p image/video generation and editing with native generated audio, including up to 6 image references when no video reference is provided. For `--video-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.
380
+ Options for `video create`: `--script "..."`, `--script-file <path>`, `--image <url>` (repeatable, up to the selected model limit), `--video <file|url>` and `--audio <file|url>` (repeatable where supported), `--duration <seconds>`, `--aspect 9:16|16:9|1:1`, `--video-model seedance-fast|seedance-mini|seedance|seedance-2.5|kling|grok|google-omni|minimax-h3|sync-lipsync-v3`, `--video-resolution auto|480p|720p|768p|1080p|2k|4k`. Default model is `seedance-fast`. SeeDance accepts native text-to-video with no image and integer output duration 4-15s (default 5s); `seedance-mini` supports 480p/720p and is best for cheaper drafts/multi-size tests; MiniMax H3 accepts native text-to-video, 4-15s output, public 768p/2k resolution, and up to 9 image, up to 3 video, and up to 3 audio references through Makaron Agent/chat. `sync-lipsync-v3` requires exactly one video plus one MP3/WAV and preserves that replacement audio while aligning the mouth. H3 defaults to 768p; request 2k explicitly for maximum/final quality. Kling supports 5-15s; Grok 1.5 supports 1-15s single-image-to-video only; Gemini Omni supports 3-10s fast 720p image/video generation and editing with native generated audio, including up to 6 image references when no video reference is provided. For `--video-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.
381
381
 
382
382
  Seedance 2.5: use `--video-model seedance-2.5` for 4-30 second output at 480p/720p. `--image` accepts local files or URLs (up to 30), while repeatable `--video` and `--audio` accept up to 10 each. Use `--video-operation generate|edit|extend`, `--extend-direction forward|backward`, `--output-format mp4|mov`, `--web-search`, `--generated-audio` / `--no-generated-audio`, and `--relaxed-content-filter`. Edit/extend require a video reference. The Evolink route does not currently expose 4K output.
383
383
 
@@ -428,6 +428,7 @@ type CompletionAction = {
428
428
  3. Stop when `status` is `"completed"`, `"failed"`, or `"aborted"`
429
429
  4. Top-level `status: "completed"` means ALL artifacts are ready (including rendered videos)
430
430
  5. If an async video fails, top-level `status` is `"failed"` and the failed video may include `completion_actions` for a safe retry or diagnosis. Agents can surface these as the next user-confirmed step.
431
+ 6. `responses get --wait` reconciles pending video output against completed Project Media with the same `snapshot_id` or `task_id`. A lagging Run row therefore does not block delivery after the durable project video is ready.
431
432
 
432
433
  ## Exit Codes
433
434
 
@@ -508,6 +509,15 @@ send_message "All done!"
508
509
 
509
510
  Admin commands require an API key with admin privileges. Ask your admin to run `makaron admin set-admin <your-email>` to grant access.
510
511
 
512
+ ### Add credits to a user
513
+
514
+ Use either the account email address or the Supabase user UUID. Credits must be a positive integer.
515
+
516
+ ```bash
517
+ npx makaron-cli admin add-credits user@example.com 1000
518
+ npx makaron-cli admin add-credits <user-id> 1000 --json
519
+ ```
520
+
511
521
  ### List all marketplace skills
512
522
 
513
523
  ```bash
package/bin/makaron.mjs CHANGED
@@ -328,6 +328,86 @@ function normalizeRunResponse(data) {
328
328
  return data;
329
329
  }
330
330
 
331
+ function projectMediaIdentity(item) {
332
+ const snapshotId = item?.snapshot_id || item?.snapshotId;
333
+ const taskId = item?.task_id || item?.taskId;
334
+ return {
335
+ snapshotId: typeof snapshotId === 'string' && snapshotId ? snapshotId : null,
336
+ taskId: typeof taskId === 'string' && taskId ? taskId : null,
337
+ };
338
+ }
339
+
340
+ function findMatchingCompletedProjectVideo(item, media) {
341
+ const identity = projectMediaIdentity(item);
342
+ if (!identity.snapshotId && !identity.taskId) return null;
343
+ return (media || []).find(candidate => {
344
+ if (candidate?.type !== 'video' || candidate?.status !== 'completed' || !candidate?.url) return false;
345
+ const candidateIdentity = projectMediaIdentity(candidate);
346
+ return (identity.snapshotId && candidateIdentity.snapshotId === identity.snapshotId)
347
+ || (identity.taskId && candidateIdentity.taskId === identity.taskId);
348
+ }) || null;
349
+ }
350
+
351
+ async function reconcileRunWithProjectMedia(baseUrl, headers, data) {
352
+ normalizeRunResponse(data);
353
+ const output = Array.isArray(data.output) ? data.output : [];
354
+ const pendingVideos = output.filter(item =>
355
+ item?.type === 'video' && (item.status === 'queued' || item.status === 'rendering')
356
+ );
357
+ const agentDone = data.agent_status === 'completed'
358
+ || (data.status === 'in_progress' && data.incomplete === true);
359
+ if (!agentDone || pendingVideos.length === 0 || !data.projectId) return data;
360
+
361
+ let projectMedia;
362
+ try {
363
+ const res = await fetch(`${baseUrl}/api/projects/${data.projectId}/media`, { headers });
364
+ if (!res.ok) return data;
365
+ projectMedia = await res.json();
366
+ } catch {
367
+ return data;
368
+ }
369
+
370
+ let reconciled = false;
371
+ for (const item of pendingVideos) {
372
+ const completed = findMatchingCompletedProjectVideo(item, projectMedia.media);
373
+ if (!completed) continue;
374
+ const identity = projectMediaIdentity(completed);
375
+ item.status = 'completed';
376
+ item.url = completed.url;
377
+ if (identity.snapshotId) item.snapshot_id = identity.snapshotId;
378
+ if (identity.taskId) item.task_id = identity.taskId;
379
+ if (completed.posterUrl) item.poster_url = completed.posterUrl;
380
+ for (const field of ['duration', 'width', 'height']) {
381
+ if (typeof completed[field] === 'number') item[field] = completed[field];
382
+ }
383
+
384
+ const legacyVideos = Array.isArray(data.result?.videos) ? data.result.videos : [];
385
+ const legacy = legacyVideos.find(video => {
386
+ const legacyIdentity = projectMediaIdentity(video);
387
+ return (identity.snapshotId && legacyIdentity.snapshotId === identity.snapshotId)
388
+ || (identity.taskId && legacyIdentity.taskId === identity.taskId);
389
+ });
390
+ if (legacy) {
391
+ legacy.status = 'completed';
392
+ legacy.videoUrl = completed.url;
393
+ if (identity.snapshotId && !legacy.snapshotId) legacy.snapshotId = identity.snapshotId;
394
+ }
395
+ reconciled = true;
396
+ }
397
+
398
+ if (!reconciled) return data;
399
+ const pendingArtifacts = output.some(item =>
400
+ (item?.type === 'video' || item?.type === 'music')
401
+ && (item.status === 'queued' || item.status === 'rendering')
402
+ );
403
+ if (!pendingArtifacts) {
404
+ data.status = 'completed';
405
+ data.incomplete = false;
406
+ delete data.next_poll_after_ms;
407
+ }
408
+ return data;
409
+ }
410
+
331
411
  function collectCompletionActions(data) {
332
412
  const items = [];
333
413
  const add = (action, source) => {
@@ -612,6 +692,7 @@ async function pollRun(baseUrl, headers, runId, opts = {}) {
612
692
  continue;
613
693
  }
614
694
  data = await res.json();
695
+ data = await reconcileRunWithProjectMedia(baseUrl, headers, data);
615
696
  } catch {
616
697
  continue;
617
698
  }
@@ -764,6 +845,7 @@ async function watchRun(baseUrl, headers, runId, opts = {}) {
764
845
  continue;
765
846
  }
766
847
  data = await res.json();
848
+ data = await reconcileRunWithProjectMedia(baseUrl, headers, data);
767
849
  } catch {
768
850
  await new Promise(r => setTimeout(r, interval));
769
851
  continue;
@@ -1791,7 +1873,7 @@ Commands:
1791
1873
  video script|create|status Video generation
1792
1874
  music create|status Music generation
1793
1875
 
1794
- admin Admin commands (skills, upload, set-admin)
1876
+ admin Admin commands (skills, credits, upload, set-admin)
1795
1877
 
1796
1878
  Examples:
1797
1879
  makaron chat --project auto "plan a launch poster"
@@ -1928,7 +2010,7 @@ Not sure which built-in skill to use? Start with:
1928
2010
  console.log('Usage: makaron analyze --video <file|url> ["question"]');
1929
2011
  } else if (topic === 'video') {
1930
2012
  if (subtopic === 'script') console.log('Usage: makaron video script --image <file> [--image <file>] [--lang en|zh] "direction"');
1931
- else if (subtopic === 'create') console.log('Usage: makaron video create --script "..." [--image <url> ...] [--video <url> ...] [--audio <url> ...] [--duration 10] [--aspect 9:16] [--video-model seedance-fast|seedance-mini|seedance|seedance-2.5|kling|grok|google-omni|minimax-h3] [--operation generate|edit|extend] [--video-resolution auto|480p|720p|768p|1080p|2k|4k] [--keep-original-sound]');
2013
+ else if (subtopic === 'create') console.log('Usage: makaron video create --script "..." [--image <url> ...] [--video <url> ...] [--audio <url> ...] [--duration 10] [--aspect 9:16] [--video-model seedance-fast|seedance-mini|seedance|seedance-2.5|kling|grok|google-omni|minimax-h3|sync-lipsync-v3] [--operation generate|edit|extend] [--video-resolution auto|480p|720p|768p|1080p|2k|4k] [--keep-original-sound]');
1932
2014
  else if (subtopic === 'status') console.log('Usage: makaron video status <taskId> | --snapshot <snapshotId> [--wait]');
1933
2015
  else console.log(`Video commands:
1934
2016
  video script --image <file> [--image <file>] "direction" Write video script
@@ -1936,6 +2018,7 @@ Not sure which built-in skill to use? Start with:
1936
2018
  video create --script "..." --video-model minimax-h3 MiniMax H3 text-to-video (default 768P)
1937
2019
  video create --script "..." --image <url> [--duration 10] Submit video task
1938
2020
  video create --script "..." --video <public-url> [--video-model seedance-fast|seedance-mini|seedance|seedance-2.5|kling|google-omni|minimax-h3] Edit/reference a video (Grok does not support video refs)
2021
+ video create --script "Use the supplied audio" --video <url> --audio <url> --video-model sync-lipsync-v3 Lip-sync exact replacement audio
1939
2022
  video status <taskId> Check video status
1940
2023
  video status --snapshot <snapshotId> [--wait] Check v2 video snapshot
1941
2024
  `);
@@ -1951,6 +2034,7 @@ Not sure which built-in skill to use? Start with:
1951
2034
  else if (subtopic === 'skill-categories') console.log('Usage: makaron admin skill-categories [--json|add|update|delete] ...');
1952
2035
  else if (subtopic === 'upload') console.log('Usage: makaron admin upload <local-file> <storage-path>');
1953
2036
  else if (subtopic === 'fetch-skill') console.log('Usage: makaron admin fetch-skill <share-code|url>');
2037
+ else if (subtopic === 'add-credits') console.log('Usage: makaron admin add-credits <email-or-user-id> <credits> [--json]');
1954
2038
  else if (subtopic === 'set-admin') console.log('Usage: makaron admin set-admin <email>');
1955
2039
  else console.log(`Admin commands:
1956
2040
  admin skills List all marketplace skills
@@ -1963,6 +2047,7 @@ Not sure which built-in skill to use? Start with:
1963
2047
  admin skill-categories delete <id> Delete a category
1964
2048
  admin upload <file> <storage-path> Upload file to Storage
1965
2049
  admin fetch-skill <code|url> Download skill from share link
2050
+ admin add-credits <user> <credits> Add credits by email or user ID
1966
2051
  admin set-admin <email> Grant admin access to a user
1967
2052
  `);
1968
2053
  } else if (topic === 'register') {
@@ -2398,7 +2483,7 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2398
2483
  const res = await fetch(`${baseUrl}/api/agent/run/${runId}`, { headers });
2399
2484
  if (!res.ok) { process.stderr.write(`Error ${res.status}: ${await res.text()}\n`); process.exit(1); }
2400
2485
  let data = await res.json();
2401
- normalizeRunResponse(data);
2486
+ data = await reconcileRunWithProjectMedia(baseUrl, headers, data);
2402
2487
  if (exportCompositions && data.status === 'completed') {
2403
2488
  data = await exportAnimatedCompositionsFromRun(baseUrl, headers, data, { publish: publishExports, quiet: jsonOutput || !!pick });
2404
2489
  }
@@ -2768,6 +2853,7 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2768
2853
  const isSeedance25 = selectedVideoModel === 'seedance-2.5';
2769
2854
  const isSeedanceModel = selectedVideoModel === 'seedance-fast' || selectedVideoModel === 'seedance-mini' || selectedVideoModel === 'seedance' || isSeedance25;
2770
2855
  const isMinimaxH3 = selectedVideoModel === 'minimax-h3';
2856
+ const isSyncLipsync = selectedVideoModel === 'sync-lipsync-v3';
2771
2857
  const supportsNativeTextToVideo = isSeedanceModel || isMinimaxH3;
2772
2858
  if (!script || (!images.length && !videos.length && !audios.length && !supportsNativeTextToVideo)) {
2773
2859
  console.error('Usage: makaron video create --script "..." [--image <url>] [--video <file|url>] [--audio <file|url>] [--duration 30] [--video-model seedance-2.5|minimax-h3]');
@@ -2779,6 +2865,10 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2779
2865
  if (isMinimaxH3 && images.length > 9) { console.error('MiniMax H3 supports at most 9 image references.'); process.exit(1); }
2780
2866
  if (isMinimaxH3 && videos.length > 3) { console.error('MiniMax H3 supports at most 3 video references.'); process.exit(1); }
2781
2867
  if (isMinimaxH3 && audios.length > 3) { console.error('MiniMax H3 supports at most 3 audio references.'); process.exit(1); }
2868
+ if (isSyncLipsync && (images.length !== 0 || videos.length !== 1 || audios.length !== 1)) { console.error('Sync Lipsync v3 requires exactly one --video and one --audio, with no --image.'); process.exit(1); }
2869
+ if (isSyncLipsync && !/<<<audio_1>>>/i.test(script)) {
2870
+ script += '\nUse <<<audio_1>>> as the exact replacement soundtrack.';
2871
+ }
2782
2872
  if (videoOperation && !['generate', 'edit', 'extend'].includes(videoOperation)) { console.error('--video-operation must be generate, edit, or extend.'); process.exit(1); }
2783
2873
  if (extendDirection && !['forward', 'backward'].includes(extendDirection)) { console.error('--extend-direction must be forward or backward.'); process.exit(1); }
2784
2874
  if (outputFormat && !['mp4', 'mov'].includes(outputFormat)) { console.error('--output-format must be mp4 or mov.'); process.exit(1); }
@@ -2788,8 +2878,9 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2788
2878
  process.exit(1);
2789
2879
  }
2790
2880
 
2791
- const providerMaxDuration = isSeedance25 ? SEEDANCE25_MAX_VIDEO_REFERENCE_DURATION : MAX_VIDEO_PROVIDER_REFERENCE_DURATION;
2792
- const providerMaxPixels = isMinimaxH3 ? Infinity : isSeedance25 ? SEEDANCE25_MAX_VIDEO_FRAME_PIXELS : MAX_VIDEO_FRAME_PIXELS;
2881
+ let providerMaxDuration = isSeedance25 ? SEEDANCE25_MAX_VIDEO_REFERENCE_DURATION : MAX_VIDEO_PROVIDER_REFERENCE_DURATION;
2882
+ if (isSyncLipsync) providerMaxDuration = 60;
2883
+ const providerMaxPixels = isSyncLipsync || isMinimaxH3 ? Infinity : isSeedance25 ? SEEDANCE25_MAX_VIDEO_FRAME_PIXELS : MAX_VIDEO_FRAME_PIXELS;
2793
2884
  const localImages = images.filter(image => !isHttpUrl(image));
2794
2885
  if (localImages.length) {
2795
2886
  const uploadedImages = await uploadImageFilesViaSignedUrl(baseUrl, headers, undefined, localImages);
@@ -2804,7 +2895,7 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2804
2895
  maxDuration: providerMaxDuration,
2805
2896
  durationTolerance: MAX_VIDEO_PROVIDER_REFERENCE_DURATION_TOLERANCE,
2806
2897
  maxFramePixels: providerMaxPixels,
2807
- maxFileSize: isSeedance25 ? 200 * 1024 * 1024 : MAX_VIDEO_UPLOAD_FILE_SIZE,
2898
+ maxFileSize: isSeedance25 || isSyncLipsync ? 200 * 1024 * 1024 : MAX_VIDEO_UPLOAD_FILE_SIZE,
2808
2899
  ...(selectedVideoModel === 'minimax-h3' ? {
2809
2900
  allowedExtensions: ['mp4', 'mov'],
2810
2901
  minSide: MINIMAX_H3_MIN_VIDEO_SIDE,
@@ -2830,7 +2921,7 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2830
2921
  const audioUrls = [];
2831
2922
  for (const audio of audios) {
2832
2923
  if (isHttpUrl(audio)) { audioUrls.push(audio); continue; }
2833
- const valid = validateAudioReferenceFile(audio, { maxDuration: isSeedance25 ? 30 : MAX_AUDIO_REFERENCE_DURATION });
2924
+ const valid = validateAudioReferenceFile(audio, { maxDuration: isSyncLipsync ? 60 : isSeedance25 ? 30 : MAX_AUDIO_REFERENCE_DURATION });
2834
2925
  if (!valid.ok) { console.error(`❌ ${valid.error}`); process.exit(1); }
2835
2926
  process.stderr.write(`🎵 Uploading ${path.basename(audio)}...\n`);
2836
2927
  const uploaded = await uploadFileViaSignedUrl(baseUrl, headers, undefined, audio, valid.mime, { uploadKind: 'audio' });
@@ -2840,7 +2931,9 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2840
2931
  // Standalone MCP tool (no project timeline write)
2841
2932
  process.stderr.write('🎬 Submitting video...\n');
2842
2933
  const resolvedOperation = videoOperation || (isSeedance25 && videoUrls.length ? 'edit' : 'generate');
2843
- const vArgs = isSeedance25
2934
+ const vArgs = isSyncLipsync
2935
+ ? { script, images, videoUrls, audioUrls, videoModel: selectedVideoModel, videoResolution }
2936
+ : isSeedance25
2844
2937
  ? { script, images, videoUrls, audioUrls, videoModel: selectedVideoModel, videoResolution, operation: resolvedOperation, extendDirection, outputFormat, generateAudio, contentFilter, webSearch }
2845
2938
  : isMinimaxH3
2846
2939
  ? { script, images, videoUrls, audioUrls, videoModel: selectedVideoModel, videoResolution }
@@ -2851,7 +2944,7 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2851
2944
  if (effectiveDuration) vArgs.duration = effectiveDuration;
2852
2945
  if (aspectRatio) vArgs.aspectRatio = aspectRatio;
2853
2946
  if (keepOriginalSound && videoUrls.length && !isSeedance25) vArgs.keepOriginalSound = true;
2854
- const result = await callMcpTool(baseUrl, headers, videoUrls.length && !isSeedance25 && !isMinimaxH3 ? 'makaron_edit_video' : 'makaron_create_video', vArgs);
2947
+ const result = await callMcpTool(baseUrl, headers, videoUrls.length && !isSeedance25 && !isMinimaxH3 && !isSyncLipsync ? 'makaron_edit_video' : 'makaron_create_video', vArgs);
2855
2948
  const text = result?.content?.find(c => c.type === 'text')?.text;
2856
2949
  if (text) {
2857
2950
  console.log(text);
@@ -2898,6 +2991,7 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
2898
2991
  video create --script "..." --video-model minimax-h3 MiniMax H3 text-to-video (default 768P)
2899
2992
  video create --script "..." --image <url> [--duration 10] Submit video task
2900
2993
  video create --script "..." --video <public-url> [--video-model seedance-fast|seedance-mini|seedance|seedance-2.5|kling|google-omni|minimax-h3] Edit/reference a video (Grok does not support video refs)
2994
+ video create --script "Use the supplied audio" --video <url> --audio <url> --video-model sync-lipsync-v3 Lip-sync exact replacement audio
2901
2995
  video status <taskId> Check video status
2902
2996
  video status --snapshot <snapshotId> [--wait] Check v2 video snapshot
2903
2997
  `);
@@ -3103,6 +3197,31 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
3103
3197
  if (!res.ok) { console.error(`Error ${res.status}:`, await res.text()); process.exit(1); }
3104
3198
  console.log(`✅ ${email} is now admin`);
3105
3199
 
3200
+ } else if (sub === 'add-credits') {
3201
+ const user = args[2]?.trim();
3202
+ const credits = Number(args[3]);
3203
+ if (!user || !Number.isSafeInteger(credits) || credits <= 0) {
3204
+ console.error('Usage: makaron admin add-credits <email-or-user-id> <credits> [--json]');
3205
+ console.error('Credits must be a positive integer.');
3206
+ process.exit(1);
3207
+ }
3208
+
3209
+ const res = await fetch(`${baseUrl}/api/admin/add-credits`, {
3210
+ method: 'POST',
3211
+ headers: { 'Content-Type': 'application/json', ...headers },
3212
+ body: JSON.stringify({ email: user, credits }),
3213
+ });
3214
+ const data = await res.json().catch(() => null);
3215
+ if (!res.ok) {
3216
+ console.error(`Error ${res.status}:`, data?.error || 'Failed to add credits');
3217
+ process.exit(1);
3218
+ }
3219
+ if (args.includes('--json')) {
3220
+ console.log(JSON.stringify(data));
3221
+ } else {
3222
+ console.log(`✅ Added ${credits} credits to ${user}. New balance: ${data.newBalance}`);
3223
+ }
3224
+
3106
3225
  } else {
3107
3226
  console.log(`Admin commands:
3108
3227
  admin skills List all marketplace skills
@@ -3115,6 +3234,7 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
3115
3234
  admin skill-categories delete <id> Delete a category
3116
3235
  admin upload <file> <storage-path> Upload file to Storage
3117
3236
  admin fetch-skill <code|url> Download skill from share link
3237
+ admin add-credits <user> <credits> Add credits by email or user ID
3118
3238
  admin set-admin <email> Grant admin access to a user
3119
3239
  `);
3120
3240
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "makaron-cli",
3
- "version": "0.13.8",
3
+ "version": "0.13.9",
4
4
  "description": "Talk to Makaron Agent from the terminal — create projects, edit images, generate videos",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -162,7 +162,7 @@ npx makaron-cli chat --project auto \
162
162
 
163
163
  `--skill` accepts an installed skill name, a marketplace UUID, or a unique marketplace label. Marketplace search matches every localized title, every localized default prompt, and category ids. 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.
164
164
 
165
- For an explicitly authorized marketplace administrator, `admin skills` shows category assignment and 4-locale title/prompt completeness, while `admin skill-categories` supports list/add/update/delete. New skills must include `en`, `zh`, `zh-Hant`, and `ja` in both `labels` and `prompts`, plus at least one existing `categories` id. Partial localized updates are safely merged server-side.
165
+ For an explicitly authorized administrator, `admin add-credits <email-or-user-id> <positive-integer> [--json]` adds credits to a user. Marketplace administrators can use `admin skills` to see category assignment and 4-locale title/prompt completeness, while `admin skill-categories` supports list/add/update/delete. New skills must include `en`, `zh`, `zh-Hant`, and `ja` in both `labels` and `prompts`, plus at least one existing `categories` id. Partial localized updates are safely merged server-side.
166
166
 
167
167
  ### With additional images (existing project)
168
168
 
@@ -327,7 +327,7 @@ npx makaron-cli video status <taskId>
327
327
  npx makaron-cli chat --project <id|auto> --video input.mp4 -b "make it funny"
328
328
  ```
329
329
 
330
- Options for `video create`: `--script "..."`, `--script-file <path>`, `--image <url>` (repeatable, up to the selected model limit), `--video <file|url>` and `--audio <file|url>` (repeatable where supported), `--duration <seconds>`, `--aspect 9:16|16:9|1:1`, `--video-model seedance-fast|seedance-mini|seedance|seedance-2.5|kling|grok|google-omni|minimax-h3`, `--video-resolution auto|480p|720p|768p|1080p|2k|4k`. Default model is `seedance-fast`. SeeDance accepts native text-to-video with no image and integer output duration 4-15s (default 5s); `seedance-mini` supports 480p/720p and is best for cheaper drafts/multi-size tests; MiniMax H3 accepts native text-to-video, 4-15s output, public 768p/2k resolution, and up to 9 image, up to 3 video, and up to 3 audio references through Makaron Agent/chat. H3 defaults to 768p; request 2k explicitly for maximum/final quality. Kling supports 5-15s; Grok 1.5 supports 1-15s single-image-to-video only; Gemini Omni supports 3-10s fast 720p image/video generation and editing with native generated audio, including up to 6 image references when no video reference is provided. For `--video-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.
330
+ Options for `video create`: `--script "..."`, `--script-file <path>`, `--image <url>` (repeatable, up to the selected model limit), `--video <file|url>` and `--audio <file|url>` (repeatable where supported), `--duration <seconds>`, `--aspect 9:16|16:9|1:1`, `--video-model seedance-fast|seedance-mini|seedance|seedance-2.5|kling|grok|google-omni|minimax-h3|sync-lipsync-v3`, `--video-resolution auto|480p|720p|768p|1080p|2k|4k`. Default model is `seedance-fast`. SeeDance accepts native text-to-video with no image and integer output duration 4-15s (default 5s); `seedance-mini` supports 480p/720p and is best for cheaper drafts/multi-size tests; MiniMax H3 accepts native text-to-video, 4-15s output, public 768p/2k resolution, and up to 9 image, up to 3 video, and up to 3 audio references through Makaron Agent/chat. `sync-lipsync-v3` requires exactly one video plus one MP3/WAV and preserves that replacement audio while aligning the mouth. H3 defaults to 768p; request 2k explicitly for maximum/final quality. Kling supports 5-15s; Grok 1.5 supports 1-15s single-image-to-video only; Gemini Omni supports 3-10s fast 720p image/video generation and editing with native generated audio, including up to 6 image references when no video reference is provided. For `--video-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.
331
331
 
332
332
  Seedance 2.5 uses `--video-model seedance-2.5` and supports 4-30s at 480p/720p, up to 30 images + 10 videos + 10 audios, repeatable local/URL references, `--video-operation generate|edit|extend`, `--extend-direction`, `--output-format mp4|mov`, and `--web-search`. The Evolink route does not currently expose 4K output.
333
333
 
@@ -378,6 +378,7 @@ type CompletionAction = {
378
378
  3. Stop when `status` is `"completed"`, `"failed"`, or `"aborted"`
379
379
  4. Top-level `status: "completed"` means ALL artifacts are ready (including rendered videos)
380
380
  5. If an async video fails, top-level `status` is `"failed"` and the failed video may include `completion_actions` for a safe retry or diagnosis. Agents can surface these as the next user-confirmed step.
381
+ 6. `responses get --wait` reconciles pending video output against completed Project Media with the same `snapshot_id` or `task_id`. A lagging Run row therefore does not block delivery after the durable project video is ready.
381
382
 
382
383
  ## Exit Codes
383
384