makaron-cli 0.8.2 → 0.8.4

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/bin/makaron.mjs CHANGED
@@ -19,8 +19,6 @@ import { execFileSync } from 'child_process';
19
19
  // ─── Config ──────────────────────────────────────────────────────────────────
20
20
 
21
21
  const AUTH_FILE = path.join(process.env.HOME || '~', '.makaron', 'auth.json');
22
- const APPROVALS_FILE = path.join(path.dirname(AUTH_FILE), 'approvals.json');
23
- const DELIVERIES_FILE = path.join(path.dirname(AUTH_FILE), 'deliveries.json');
24
22
  const DEFAULT_URL = 'https://www.makaron.app';
25
23
  const BASE_URL = process.env.MAKARON_URL || DEFAULT_URL;
26
24
  const APP_URL = process.env.MAKARON_APP_URL || DEFAULT_URL;
@@ -29,10 +27,18 @@ const APP_URL = process.env.MAKARON_APP_URL || DEFAULT_URL;
29
27
  const SUPABASE_URL = 'https://sdyrtztrjgmmpnirswxt.supabase.co';
30
28
  const SUPABASE_ANON_KEY = 'sb_publishable_FJFN2YYaWaQjABUKLqxQcA_fhxPLFDY';
31
29
 
32
- const MAX_VIDEO_FILE_SIZE = 200 * 1024 * 1024;
33
- const MAX_VIDEO_DURATION = 15;
34
- const MAX_VIDEO_DURATION_TOLERANCE = 0.5;
30
+ const MAX_VIDEO_UPLOAD_FILE_SIZE_MB = 50;
31
+ const MAX_VIDEO_UPLOAD_FILE_SIZE = MAX_VIDEO_UPLOAD_FILE_SIZE_MB * 1024 * 1024;
32
+ const MAX_VIDEO_UPLOAD_DURATION = 120;
33
+ const MAX_VIDEO_UPLOAD_DURATION_TOLERANCE = 1;
34
+ const MAX_VIDEO_PROVIDER_REFERENCE_DURATION = 15;
35
+ const MAX_VIDEO_PROVIDER_REFERENCE_DURATION_TOLERANCE = 0.5;
35
36
  const MAX_VIDEO_FRAME_PIXELS = 2_086_876;
37
+ const SEEDANCE_MIN_VIDEO_FRAME_PIXELS = 409_600;
38
+ const SEEDANCE_MIN_VIDEO_SIDE = 300;
39
+ const SEEDANCE_MAX_VIDEO_SIDE = 6000;
40
+ const SEEDANCE_MIN_VIDEO_ASPECT = 0.4;
41
+ const SEEDANCE_MAX_VIDEO_ASPECT = 2.5;
36
42
 
37
43
  function getCliVersion() {
38
44
  try {
@@ -64,34 +70,6 @@ function saveAuth(data) {
64
70
  fs.writeFileSync(AUTH_FILE, JSON.stringify(data, null, 2));
65
71
  }
66
72
 
67
- function loadApprovals() {
68
- try {
69
- return JSON.parse(fs.readFileSync(APPROVALS_FILE, 'utf-8'));
70
- } catch {
71
- return [];
72
- }
73
- }
74
-
75
- function saveApprovals(approvals) {
76
- const dir = path.dirname(APPROVALS_FILE);
77
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
78
- fs.writeFileSync(APPROVALS_FILE, JSON.stringify(approvals, null, 2));
79
- }
80
-
81
- function loadDeliveries() {
82
- try {
83
- return JSON.parse(fs.readFileSync(DELIVERIES_FILE, 'utf-8'));
84
- } catch {
85
- return [];
86
- }
87
- }
88
-
89
- function saveDeliveries(deliveries) {
90
- const dir = path.dirname(DELIVERIES_FILE);
91
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
92
- fs.writeFileSync(DELIVERIES_FILE, JSON.stringify(deliveries, null, 2));
93
- }
94
-
95
73
  function buildCookie(tokenJson) {
96
74
  const url = tokenJson._supabaseUrl || SUPABASE_URL;
97
75
  const ref = url.match(/\/\/([^.]+)\./)?.[1] || '';
@@ -456,434 +434,6 @@ function applyPick(data, field) {
456
434
  }
457
435
  }
458
436
 
459
- // ─── Dialogue Events (message / approval / artifact) ─────────────────────────
460
-
461
- function stableEventId(prefix, runId, seq, fallback) {
462
- return fallback || `${prefix}_${runId}_${seq ?? Date.now()}`;
463
- }
464
-
465
- function inferApprovalIntent(text) {
466
- if (!text) return false;
467
- const normalized = text.toLowerCase().replace(/\s+/g, ' ').trim();
468
- const hasEnglishApprovalIntent = /\b(shall i|should i|do you want me to|confirm|approve|approval|permission)\b/.test(normalized);
469
- const hasEnglishAction = /\b(go ahead|proceed|continue|generate|create|submit|start|run|render)\b/.test(normalized);
470
- const hasChineseApprovalIntent = /(是否|要不要|是否要|需要我|请确认|确认|同意|批准|可以吗|是否可以)/.test(normalized);
471
- const hasChineseAction = /(继续|开始|生成|创建|提交|执行|渲染|出图|出视频|制作)/.test(normalized);
472
- return (hasEnglishApprovalIntent && hasEnglishAction) || (hasChineseApprovalIntent && hasChineseAction);
473
- }
474
-
475
- function isPureStatusMessage(text, sourceType, status) {
476
- const normalized = String(text || '').toLowerCase().replace(/\s+/g, ' ').trim();
477
- if (sourceType === 'status') return true;
478
- if (status && !normalized) return true;
479
- if (!normalized) return false;
480
- return /^(queued|running|rendering|uploading|processing|generating|completed|complete|done|failed|aborted|started|submitted|waiting|polling)(\.|…|\.\.\.)?$/.test(normalized)
481
- || /^(排队中|队列中|运行中|渲染中|上传中|处理中|生成中|已完成|完成|失败|已失败|已提交|等待中)$/.test(normalized);
482
- }
483
-
484
- function hasExplicitApprovalRequirement(data, text) {
485
- return Boolean(
486
- data.requires_approval
487
- || data.requiresApproval
488
- || data.action_required
489
- || data.actionRequired
490
- || data.proposal
491
- || inferApprovalIntent(text)
492
- );
493
- }
494
-
495
- function isPureQaRun(data) {
496
- const raw = [
497
- data.intent,
498
- data.mode,
499
- data.workflow,
500
- data.workflow_type,
501
- data.workflowType,
502
- data.run_type,
503
- data.runType,
504
- data.kind,
505
- ].filter(Boolean).join(' ').toLowerCase();
506
- return /\b(qa|q&a|question_answer|question-answer|answer|pure_qa|pure-qa)\b/.test(raw);
507
- }
508
-
509
- function normalizeDialogueMessage(runId, projectId, ev, context = {}) {
510
- const data = ev.data || ev;
511
- const text = data.text || data.message || data.content || data.statusText || '';
512
- if (!text && ev.type !== 'tool_call') return null;
513
- const explicitApproval = hasExplicitApprovalRequirement(data, text);
514
- const textOnlyCheckpoint = Boolean(
515
- context.stoppedWithoutArtifacts
516
- && ev.type !== 'error'
517
- && !explicitApproval
518
- && !isPureStatusMessage(text, ev.type, data.status)
519
- );
520
- const requiresApproval = explicitApproval || textOnlyCheckpoint;
521
- const message = {
522
- type: 'message',
523
- id: stableEventId('msg', runId, ev.seq, data.id || ev.id),
524
- runId,
525
- projectId,
526
- seq: ev.seq,
527
- status: data.status || undefined,
528
- text: text || `${data.tool || 'tool_call'}${data.input?.description ? `: ${data.input.description}` : ''}`,
529
- };
530
- if (ev.type) message.source_type = ev.type;
531
- if (requiresApproval) {
532
- message.requires_approval = true;
533
- message.approval_options = data.approval_options || data.approvalOptions || ['approve', 'revise', 'ask_user', 'continue'];
534
- if (textOnlyCheckpoint) message.approval_reason = 'text_only_checkpoint';
535
- }
536
- if (data.proposal) message.proposal = data.proposal;
537
- return message;
538
- }
539
-
540
- function normalizeDialogueArtifact(runId, projectId, item, seq) {
541
- if (!item) return null;
542
- const kind = item.type || item.kind || (item.imageUrl ? 'image' : item.videoUrl ? 'video' : undefined);
543
- if (!kind) return null;
544
- if (!['image', 'video', 'design', 'music', 'audio', 'file'].includes(kind)) return null;
545
- const artifact = {
546
- type: 'artifact',
547
- id: stableEventId('artifact', runId, seq, item.id || item.snapshotId || item.taskId),
548
- runId,
549
- projectId,
550
- seq,
551
- kind,
552
- status: item.status || (item.url || item.imageUrl || item.videoUrl || item.audioUrl ? 'completed' : 'running'),
553
- };
554
- const url = item.url || item.imageUrl || item.videoUrl || item.audioUrl;
555
- if (url) artifact.url = url;
556
- const fileName = item.fileName || item.filename || item.name || (url ? url.split('/').pop()?.split('?')[0] : null);
557
- const contentType = item.contentType || item.content_type || item.mimeType || item.mime_type;
558
- if (fileName) artifact.fileName = fileName;
559
- if (contentType) artifact.contentType = contentType;
560
- if (item.error) artifact.error = item.error;
561
- if (item.taskId) artifact.taskId = item.taskId;
562
- if (item.snapshotId) artifact.snapshotId = item.snapshotId;
563
- return artifact;
564
- }
565
-
566
- function normalizeDialogueEvent(runId, projectId, ev, context = {}) {
567
- const data = ev.data || {};
568
- if (ev.type === 'message' || ev.type === 'approval' || ev.type === 'artifact') {
569
- return { ...data, ...ev, runId: ev.runId || runId, projectId: ev.projectId || projectId };
570
- }
571
- switch (ev.type) {
572
- case 'content':
573
- case 'status':
574
- case 'tool_call':
575
- case 'error':
576
- return normalizeDialogueMessage(runId, projectId, ev, context);
577
- case 'image':
578
- return normalizeDialogueArtifact(runId, projectId, { type: 'image', status: data.imageUrl ? 'completed' : 'running', imageUrl: data.imageUrl, snapshotId: data.snapshotId }, ev.seq);
579
- case 'render':
580
- return normalizeDialogueArtifact(runId, projectId, { type: data.animation ? 'video' : 'design', status: data.published ? 'completed' : 'running', url: data.url, snapshotId: data.snapshotId }, ev.seq);
581
- case 'animation_task':
582
- case 'video_snapshot':
583
- return normalizeDialogueArtifact(runId, projectId, { type: 'video', status: 'running', taskId: data.taskId, snapshotId: data.snapshotId }, ev.seq);
584
- case 'music_task':
585
- return normalizeDialogueArtifact(runId, projectId, { type: 'music', status: 'running', taskId: data.taskId }, ev.seq);
586
- default:
587
- return null;
588
- }
589
- }
590
-
591
- function buildDialogueEvents(runId, data) {
592
- const projectId = data.projectId || data.project_id;
593
- const events = [];
594
- const hasArtifacts = Boolean(
595
- (data.output || []).some(item => normalizeDialogueArtifact(runId, projectId, item, item.seq))
596
- || (data.events || []).some(ev => normalizeDialogueEvent(runId, projectId, ev, { stoppedWithoutArtifacts: false })?.type === 'artifact')
597
- );
598
- const hasContinuationAction = (data.events || []).some(ev => ['tool_call', 'image', 'render', 'animation_task', 'video_snapshot', 'music_task'].includes(ev.type));
599
- const checkpointMode = data.checkpointMode || 'service';
600
- const context = {
601
- stoppedWithoutArtifacts: checkpointMode !== 'off'
602
- && !isPureQaRun(data)
603
- && !hasArtifacts
604
- && !hasContinuationAction
605
- && !data.incomplete
606
- && ['completed', 'failed', 'aborted', 'waiting', 'needs_input'].includes(data.status),
607
- };
608
- for (const ev of data.events || []) {
609
- const normalized = normalizeDialogueEvent(runId, projectId, ev, context);
610
- if (normalized) events.push(normalized);
611
- }
612
- for (const item of data.output || []) {
613
- const normalized = normalizeDialogueArtifact(runId, projectId, item, item.seq);
614
- if (normalized && !events.some(ev => ev.type === 'artifact' && ev.id === normalized.id)) events.push(normalized);
615
- }
616
- for (const approval of loadApprovals().filter(item => item.runId === runId)) {
617
- events.push(approval);
618
- }
619
- return events;
620
- }
621
-
622
- function compactDialogueEvents(events) {
623
- const compacted = [];
624
- for (const ev of events) {
625
- const prev = compacted[compacted.length - 1];
626
- const canMerge = prev
627
- && ev.type === 'message'
628
- && prev.type === 'message'
629
- && ev.source_type === 'content'
630
- && prev.source_type === 'content'
631
- && !ev.proposal
632
- && !prev.proposal
633
- && Boolean(ev.requires_approval) === Boolean(prev.requires_approval)
634
- && (ev.approval_reason || '') === (prev.approval_reason || '');
635
- if (canMerge) {
636
- prev.text = `${prev.text}${ev.text}`;
637
- prev.id = `${prev.id}+${ev.id}`;
638
- prev.seq_end = ev.seq;
639
- if (ev.requires_approval) {
640
- prev.requires_approval = true;
641
- prev.approval_options = prev.approval_options || ev.approval_options;
642
- }
643
- } else {
644
- compacted.push({ ...ev });
645
- }
646
- }
647
- return compacted;
648
- }
649
-
650
- function findUnhandledApprovalMessages(events) {
651
- const approved = new Set(events.filter(ev => ev.type === 'approval').map(ev => ev.messageId || ev.message_id));
652
- return events.filter(ev => ev.type === 'message' && ev.requires_approval && !approved.has(ev.id));
653
- }
654
-
655
- function isCompletedDeliverableArtifact(artifact) {
656
- return artifact?.type === 'artifact'
657
- && ['image', 'video', 'design', 'music', 'audio', 'file'].includes(artifact.kind)
658
- && artifact.status === 'completed'
659
- && Boolean(artifact.url);
660
- }
661
-
662
- function findUndeliveredArtifacts(runId, events) {
663
- const delivered = new Set(loadDeliveries().filter(item => item.runId === runId).map(item => item.artifactId || item.artifact_id));
664
- return events.filter(isCompletedDeliverableArtifact).filter(artifact => !delivered.has(artifact.id));
665
- }
666
-
667
- function buildCliCommand() {
668
- return 'npx makaron-cli@latest';
669
- }
670
-
671
- function buildHandleCommand(messageId, runId, choice) {
672
- return `${buildCliCommand()} responses handle ${messageId} --run ${runId} --choice ${choice}`;
673
- }
674
-
675
- function buildNextCommand(runId) {
676
- return `${buildCliCommand()} responses next ${runId} --json`;
677
- }
678
-
679
- function buildDeliverCommand(artifactId, runId) {
680
- return `${buildCliCommand()} responses deliver ${artifactId} --run ${runId}`;
681
- }
682
-
683
- function buildRunError(data, events) {
684
- const errorEvent = events.find(ev => ev.type === 'message' && ev.source_type === 'error') || null;
685
- const resultError = data.result?.error || data.error || data.message || null;
686
- if (!errorEvent && !resultError && !['failed', 'aborted'].includes(data.status)) return null;
687
- const errorType = (typeof resultError === 'object' && (resultError.type || resultError.code)) || data.error_type || data.errorType || data.status || 'failed';
688
- return {
689
- type: errorType,
690
- status: data.status || 'failed',
691
- message: errorEvent?.text || (typeof resultError === 'string' ? resultError : resultError?.message) || `Makaron run ${data.status || 'failed'}`,
692
- recoverable: Boolean((typeof resultError === 'object' && resultError.recoverable) || data.recoverable),
693
- detail: typeof resultError === 'object' ? resultError : undefined,
694
- };
695
- }
696
-
697
- function buildNextAction(runId, events, data = {}) {
698
- const runError = buildRunError(data, events);
699
- if (runError) {
700
- return {
701
- status: 'failed',
702
- blocking: true,
703
- runId,
704
- error: runError,
705
- next_command: buildNextCommand(runId),
706
- next_commands: {
707
- inspect: buildNextCommand(runId),
708
- },
709
- events,
710
- };
711
- }
712
- const checkpoint = findUnhandledApprovalMessages(events)[0] || null;
713
- const artifacts = events.filter(ev => ev.type === 'artifact');
714
- const approvals = events.filter(ev => ev.type === 'approval');
715
- if (data.incomplete || ['queued', 'running', 'rendering', 'processing', 'generating', 'submitted'].includes(data.status)) {
716
- return {
717
- status: 'running',
718
- blocking: false,
719
- runId,
720
- artifacts,
721
- approvals,
722
- next_command: buildNextCommand(runId),
723
- next_commands: {
724
- inspect: buildNextCommand(runId),
725
- },
726
- events,
727
- };
728
- }
729
- if (checkpoint) {
730
- return {
731
- status: 'needs_approval',
732
- blocking: true,
733
- runId,
734
- checkpoint,
735
- next_commands: {
736
- approve: buildHandleCommand(checkpoint.id, runId, 'approve'),
737
- revise: `${buildHandleCommand(checkpoint.id, runId, 'revise')} --note "what to change"`,
738
- ask_user: buildHandleCommand(checkpoint.id, runId, 'ask_user'),
739
- continue: buildHandleCommand(checkpoint.id, runId, 'continue'),
740
- inspect: buildNextCommand(runId),
741
- },
742
- events,
743
- };
744
- }
745
- const undeliveredArtifacts = findUndeliveredArtifacts(runId, events);
746
- if (undeliveredArtifacts.length) {
747
- const first = undeliveredArtifacts[0];
748
- return {
749
- status: 'has_artifacts',
750
- blocking: true,
751
- runId,
752
- artifacts,
753
- undelivered_artifacts: undeliveredArtifacts,
754
- approvals,
755
- next_commands: {
756
- deliver: buildDeliverCommand(first.id, runId),
757
- inspect: buildNextCommand(runId),
758
- },
759
- events,
760
- };
761
- }
762
- return {
763
- status: artifacts.length ? 'delivered' : 'ready',
764
- blocking: false,
765
- runId,
766
- artifacts,
767
- approvals,
768
- deliveries: loadDeliveries().filter(item => item.runId === runId),
769
- next_commands: {
770
- inspect: buildNextCommand(runId),
771
- },
772
- events,
773
- };
774
- }
775
-
776
- async function fetchRun(baseUrl, headers, runId, opts = {}) {
777
- const params = new URLSearchParams();
778
- if (opts.events) params.set('events', 'true');
779
- const suffix = params.toString() ? `?${params}` : '';
780
- const res = await fetch(`${baseUrl}/api/agent/run/${runId}${suffix}`, { headers });
781
- if (!res.ok) { process.stderr.write(`Error ${res.status}: ${await res.text()}\n`); process.exit(1); }
782
- return normalizeRunResponse(await res.json());
783
- }
784
-
785
- async function printDialogueEvents(baseUrl, headers, runId, opts = {}) {
786
- const { jsonl = false, failOnUnapproved = false, follow = false, interval = 5000, compact = false, checkpointMode = 'service' } = opts;
787
- const printed = new Set();
788
-
789
- while (true) {
790
- const data = await fetchRun(baseUrl, headers, runId, { events: true });
791
- const dialogueEvents = buildDialogueEvents(runId, { ...data, checkpointMode });
792
- const events = compact ? compactDialogueEvents(dialogueEvents) : dialogueEvents;
793
- const unhandled = findUnhandledApprovalMessages(events);
794
- if (failOnUnapproved && unhandled.length) {
795
- process.stderr.write(`Unhandled Makaron message requires approval: ${unhandled.map(ev => ev.id).join(', ')}\n`);
796
- process.exit(3);
797
- }
798
-
799
- const nextEvents = follow ? events.filter(ev => !printed.has(`${ev.type}:${ev.id || ev.seq}`)) : events;
800
- for (const ev of nextEvents) {
801
- printed.add(`${ev.type}:${ev.id || ev.seq}`);
802
- if (jsonl) console.log(JSON.stringify(ev));
803
- }
804
- if (!jsonl) console.log(JSON.stringify(nextEvents, null, 2));
805
-
806
- if (!follow || (!data.incomplete && ['completed', 'failed', 'aborted'].includes(data.status))) {
807
- if (data.status === 'failed' || data.status === 'aborted') process.exit(1);
808
- return;
809
- }
810
- await new Promise(r => setTimeout(r, data.next_poll_after_ms || interval));
811
- }
812
- }
813
-
814
- async function printAgentNext(baseUrl, headers, runId, opts = {}) {
815
- const { json = false, checkpointMode = 'service', failOnCheckpoint = true } = opts;
816
- const data = await fetchRun(baseUrl, headers, runId, { events: true });
817
- const events = compactDialogueEvents(buildDialogueEvents(runId, { ...data, checkpointMode }));
818
- const action = buildNextAction(runId, events, data);
819
- if (json) {
820
- console.log(JSON.stringify(action, null, 2));
821
- } else if (action.status === 'failed') {
822
- console.log(`failed: ${action.error?.message || 'Makaron run failed'}`);
823
- console.log(`inspect: ${action.next_commands.inspect}`);
824
- } else if (action.status === 'needs_approval') {
825
- console.log(`needs_approval: ${action.checkpoint.id}`);
826
- if (action.checkpoint.text) console.log(action.checkpoint.text);
827
- console.log(`approve: ${action.next_commands.approve}`);
828
- console.log(`revise: ${action.next_commands.revise}`);
829
- console.log(`ask_user: ${action.next_commands.ask_user}`);
830
- console.log(`continue: ${action.next_commands.continue}`);
831
- console.log(`inspect: ${action.next_commands.inspect}`);
832
- } else if (action.status === 'has_artifacts') {
833
- for (const artifact of action.undelivered_artifacts || action.artifacts) {
834
- console.log(`${artifact.kind} ${artifact.status}${artifact.url ? ` ${artifact.url}` : ''}`);
835
- }
836
- if (action.next_commands.deliver) console.log(`deliver: ${action.next_commands.deliver}`);
837
- console.log(`inspect: ${action.next_commands.inspect}`);
838
- } else if (action.status === 'running') {
839
- console.log('running');
840
- console.log(`inspect: ${action.next_commands.inspect}`);
841
- } else if (action.status === 'delivered') {
842
- console.log('delivered');
843
- console.log(`inspect: ${action.next_commands.inspect}`);
844
- } else {
845
- console.log('ready');
846
- console.log(`inspect: ${action.next_commands.inspect}`);
847
- }
848
- if (action.status === 'needs_approval' && failOnCheckpoint) process.exit(3);
849
- if (action.status === 'failed') process.exit(1);
850
- }
851
-
852
- function recordApproval(runId, messageId, choice, note) {
853
- const approvals = loadApprovals();
854
- const approval = {
855
- type: 'approval',
856
- id: `approval_${Date.now()}`,
857
- runId,
858
- messageId,
859
- choice,
860
- status: 'recorded',
861
- createdAt: new Date().toISOString(),
862
- };
863
- if (note) approval.note = note;
864
- approvals.push(approval);
865
- saveApprovals(approvals);
866
- console.log(JSON.stringify(approval));
867
- }
868
-
869
- function recordDelivery(runId, artifactId, opts = {}) {
870
- const deliveries = loadDeliveries();
871
- const delivery = {
872
- type: 'delivery',
873
- id: `delivery_${Date.now()}`,
874
- runId,
875
- artifactId,
876
- status: 'recorded',
877
- createdAt: new Date().toISOString(),
878
- };
879
- if (opts.channel) delivery.channel = opts.channel;
880
- if (opts.messageId) delivery.messageId = opts.messageId;
881
- if (opts.note) delivery.note = opts.note;
882
- deliveries.push(delivery);
883
- saveDeliveries(deliveries);
884
- console.log(JSON.stringify(delivery));
885
- }
886
-
887
437
  // ─── Watch (incremental event stream) ───────────────────────────────────────
888
438
 
889
439
  async function watchRun(baseUrl, headers, runId, opts = {}) {
@@ -1041,6 +591,34 @@ async function listProjects(baseUrl, headers) {
1041
591
  console.log('');
1042
592
  }
1043
593
 
594
+ async function listProjectMedia(baseUrl, headers, projectId, opts = {}) {
595
+ const res = await fetch(`${baseUrl}/api/projects/${projectId}/media`, { headers });
596
+ if (!res.ok) { console.error('Project media failed:', await res.text()); process.exit(1); }
597
+ const data = await res.json();
598
+ if (opts.json) {
599
+ console.log(JSON.stringify(data, null, 2));
600
+ return data;
601
+ }
602
+
603
+ console.log(`🎞️ ${data.title || 'Untitled'}`);
604
+ console.log(` Project: ${data.projectUrl || `${APP_URL}/projects/${projectId}`}`);
605
+ const media = data.media || [];
606
+ if (!media.length) {
607
+ console.log(' No timeline media yet.');
608
+ return data;
609
+ }
610
+ for (const item of media) {
611
+ const ref = item.ref || `<<<media_${item.index}>>>`;
612
+ const status = item.status && item.status !== 'completed' ? ` ${item.status}` : '';
613
+ const duration = typeof item.duration === 'number' ? ` ${item.duration}s` : '';
614
+ const dimensions = item.width && item.height ? ` ${item.width}x${item.height}` : '';
615
+ const description = item.description ? ` — ${item.description}` : '';
616
+ const url = item.url ? `\n ${item.url}` : '';
617
+ console.log(` ${String(item.index).padStart(2)}. ${ref} [${item.type}${status}${duration}${dimensions}]${description}${url}`);
618
+ }
619
+ return data;
620
+ }
621
+
1044
622
  function timeSince(date) {
1045
623
  const s = Math.floor((Date.now() - date.getTime()) / 1000);
1046
624
  if (s < 60) return 'just now';
@@ -1192,13 +770,20 @@ function probeLocalVideo(videoPath) {
1192
770
  return probeVideoWithFfprobe(videoPath) || probeVideoWithFfmpeg(videoPath);
1193
771
  }
1194
772
 
1195
- function validateVideoFile(videoPath) {
773
+ function validateVideoFile(videoPath, options = {}) {
774
+ const maxDuration = options.maxDuration ?? MAX_VIDEO_UPLOAD_DURATION;
775
+ const durationTolerance = options.durationTolerance ?? MAX_VIDEO_UPLOAD_DURATION_TOLERANCE;
776
+ const minFramePixels = options.minFramePixels ?? 0;
777
+ const minSide = options.minSide ?? 0;
778
+ const maxSide = options.maxSide ?? Infinity;
779
+ const minAspect = options.minAspect ?? 0;
780
+ const maxAspect = options.maxAspect ?? Infinity;
1196
781
  if (!fs.existsSync(videoPath)) {
1197
782
  return { ok: false, error: `Video file not found: ${videoPath}` };
1198
783
  }
1199
784
  const stat = fs.statSync(videoPath);
1200
- if (stat.size > MAX_VIDEO_FILE_SIZE) {
1201
- return { ok: false, error: `Video too large: ${(stat.size / 1024 / 1024).toFixed(1)}MB (max 200MB)` };
785
+ if (stat.size > MAX_VIDEO_UPLOAD_FILE_SIZE) {
786
+ return { ok: false, error: `Video too large: ${(stat.size / 1024 / 1024).toFixed(1)}MB (max ${MAX_VIDEO_UPLOAD_FILE_SIZE_MB}MB). The CLI uploads directly to Storage; use the frontend to transcode larger videos first.` };
1202
787
  }
1203
788
  const ext = path.extname(videoPath).slice(1).toLowerCase();
1204
789
  if (!['mp4', 'mov', 'webm'].includes(ext)) {
@@ -1208,12 +793,25 @@ function validateVideoFile(videoPath) {
1208
793
  if (!meta) {
1209
794
  return { ok: false, error: 'Cannot read video duration/resolution. Install ffmpeg/ffprobe or use the normal frontend upload flow.' };
1210
795
  }
1211
- if (meta.duration > MAX_VIDEO_DURATION + MAX_VIDEO_DURATION_TOLERANCE) {
1212
- return { ok: false, error: `Video too long: ${formatSeconds(meta.duration)}s (max ${MAX_VIDEO_DURATION}s, with ${MAX_VIDEO_DURATION_TOLERANCE}s metadata tolerance)` };
796
+ if (meta.duration > maxDuration + durationTolerance) {
797
+ return { ok: false, error: `Video too long: ${formatSeconds(meta.duration)}s (max ${maxDuration}s, with ${durationTolerance}s metadata tolerance)` };
1213
798
  }
1214
799
  if (meta.width * meta.height > MAX_VIDEO_FRAME_PIXELS) {
1215
800
  return { ok: false, error: `Video resolution too high: ${meta.width}x${meta.height} (${meta.width * meta.height} px). Max is <=1080p (${MAX_VIDEO_FRAME_PIXELS} px). Re-upload through the frontend to transcode, or export a smaller video.` };
1216
801
  }
802
+ const framePixels = meta.width * meta.height;
803
+ const aspect = meta.width / meta.height;
804
+ if (
805
+ framePixels < minFramePixels ||
806
+ meta.width < minSide ||
807
+ meta.height < minSide ||
808
+ meta.width > maxSide ||
809
+ meta.height > maxSide ||
810
+ aspect < minAspect ||
811
+ aspect > maxAspect
812
+ ) {
813
+ return { ok: false, error: `Video size does not meet provider limits: ${meta.width}x${meta.height} (${framePixels} px, aspect ${aspect.toFixed(2)}). Required: frame pixels >=${minFramePixels}, sides ${minSide}-${Number.isFinite(maxSide) ? maxSide : '∞'}px, aspect ${minAspect}-${Number.isFinite(maxAspect) ? maxAspect : '∞'}. Resize/pad with FFmpeg before submitting.` };
814
+ }
1217
815
  const mime = ext === 'mov' ? 'video/quicktime' : ext === 'webm' ? 'video/webm' : 'video/mp4';
1218
816
  return { ok: true, mime, meta };
1219
817
  }
@@ -1226,8 +824,8 @@ function validateVideoFileForAnalysis(videoPath) {
1226
824
  if (stat.size === 0) {
1227
825
  return { ok: false, error: `Video file is empty: ${videoPath}` };
1228
826
  }
1229
- if (stat.size > MAX_VIDEO_FILE_SIZE) {
1230
- return { ok: false, error: `Video too large: ${(stat.size / 1024 / 1024).toFixed(1)}MB (max 200MB)` };
827
+ if (stat.size > MAX_VIDEO_UPLOAD_FILE_SIZE) {
828
+ return { ok: false, error: `Video too large: ${(stat.size / 1024 / 1024).toFixed(1)}MB (max ${MAX_VIDEO_UPLOAD_FILE_SIZE_MB}MB). The CLI uploads directly to Storage; use the frontend to transcode larger videos first.` };
1231
829
  }
1232
830
  const ext = path.extname(videoPath).slice(1).toLowerCase();
1233
831
  if (!['mp4', 'mov', 'webm'].includes(ext)) {
@@ -1252,48 +850,6 @@ function saveMcpImage(result, outputPath) {
1252
850
  return null;
1253
851
  }
1254
852
 
1255
- function wantsHelp(values) {
1256
- return values.includes('--help') || values.includes('-h');
1257
- }
1258
-
1259
- function printResponsesUsage() {
1260
- console.log(`Responses commands:
1261
- responses get <runId> Get status and output (JSON)
1262
- responses get <runId> --wait Poll until completed
1263
- responses get <runId> --pick <field> Extract: first_image_url, first_video_url, project_url, output
1264
- responses next <runId> --json New Agent entry: compact timeline + checkpoint guidance
1265
- responses handle <messageId> --run <runId> --choice approve|revise|ask_user|continue
1266
- responses deliver <artifactId> --run <runId> Record artifact delivered to the user
1267
- responses events <runId> --jsonl Emit message/approval/artifact events for external Agents
1268
- responses timeline <runId> --jsonl Alias for responses events
1269
- responses timeline <runId> --checkpoint-mode off Disable text-only checkpoints for pure Q&A
1270
- responses approve <messageId> --run <runId> Record approval for a Makaron message
1271
- responses revise <messageId> --run <runId> Record revision request for a Makaron message
1272
- responses ask-user <messageId> --run <runId> Record that the user must decide
1273
- responses continue <messageId> --run <runId> Record continue decision
1274
- responses watch <runId> --jsonl Watch until done (incremental events)
1275
- responses list --project <id> List runs for a project
1276
- `);
1277
- }
1278
-
1279
- function printResponsesSubcommandUsage(sub) {
1280
- const usages = {
1281
- get: 'Usage: makaron responses get <runId> [--wait] [--json] [--pick <field>]',
1282
- watch: 'Usage: makaron responses watch <runId> [--jsonl] [--interval <ms>]',
1283
- next: 'Usage: makaron responses next <runId> [--json] [--checkpoint-mode service|off] [--no-fail]',
1284
- events: 'Usage: makaron responses events <runId> [--jsonl] [--compact] [--checkpoint-mode service|off] [--follow] [--interval <ms>] [--fail-on-unapproved]',
1285
- timeline: 'Usage: makaron responses timeline <runId> [--jsonl] [--compact] [--checkpoint-mode service|off] [--follow] [--interval <ms>] [--fail-on-unapproved]',
1286
- deliver: 'Usage: makaron responses deliver <artifactId> --run <runId> [--channel <name>] [--message-id <id>] [--note <text>]',
1287
- handle: 'Usage: makaron responses handle <messageId> --run <runId> --choice approve|revise|ask_user|continue [--note <text>]',
1288
- approve: 'Usage: makaron responses approve <messageId> --run <runId> [--note <text>]',
1289
- revise: 'Usage: makaron responses revise <messageId> --run <runId> [--note <text>]',
1290
- 'ask-user': 'Usage: makaron responses ask-user <messageId> --run <runId> [--note <text>]',
1291
- continue: 'Usage: makaron responses continue <messageId> --run <runId> [--note <text>]',
1292
- list: 'Usage: makaron responses list --project <id>',
1293
- };
1294
- console.log(usages[sub] || 'Usage: makaron responses --help');
1295
- }
1296
-
1297
853
  async function analyzeVideoCli(baseUrl, headers, rawVideo, questionParts) {
1298
854
  if (!rawVideo) {
1299
855
  console.error('Usage: makaron analyze --video <file|url> ["question"]');
@@ -1317,12 +873,133 @@ async function analyzeVideoCli(baseUrl, headers, rawVideo, questionParts) {
1317
873
  if (text) console.log(text);
1318
874
  }
1319
875
 
876
+ // ─── Help ───────────────────────────────────────────────────────────────────
877
+
878
+ function hasHelpFlag(values) {
879
+ return values.includes('--help') || values.includes('-h');
880
+ }
881
+
882
+ function printRootHelp() {
883
+ console.log(`Makaron CLI — Talk to Makaron Agent from the terminal
884
+
885
+ Commands:
886
+ register --json Get challenge for agent self-registration
887
+ register --verify --challenge-id <id> --answer <n> Verify and save API key
888
+ claim Get claim URL for human to link account
889
+ login Log in to Makaron (human interactive)
890
+ list (ls) List all projects
891
+ project media <projectId> --json List timeline media for a project
892
+ create --image <file> Create project from local image
893
+ create --image-url <url> Create project from URL
894
+ create --title "name" Create empty project (text-to-image)
895
+
896
+ chat --project <id> "message" Chat (non-blocking, polls for result)
897
+ chat --project <id> --video <file> Attach video to conversation
898
+ chat --project <id> -b "message" Background: submit and print runId
899
+ chat --project <id> --stream "msg" Legacy: stream SSE in real-time
900
+ chat --project <id> --json "msg" Output structured JSON result
901
+
902
+ responses get <runId> Get run status and results
903
+ responses get <runId> --wait Poll until completed
904
+ responses list --project <id> List runs for a project
905
+ abort <runId> Abort a running Agent
906
+
907
+ edit [--image <file>] "prompt" AI image edit / text-to-image
908
+ analyze --video <file|url> Analyze video content
909
+ video script|create|status Video generation
910
+ music create|status Music generation
911
+
912
+ admin Admin commands (skills, upload, set-admin)
913
+
914
+ Environment:
915
+ MAKARON_API_KEY API key (mk_live_xxx) — recommended for agents
916
+ MAKARON_URL API base (default: ${DEFAULT_URL})
917
+ `);
918
+ }
919
+
920
+ function printHelp(topic, subtopic) {
921
+ if (topic === 'login') {
922
+ console.log('Usage: makaron login');
923
+ } else if (topic === 'create') {
924
+ console.log('Usage: makaron create --image <file> [--image <file2>] | --image-url <url> | --title "name"');
925
+ } else if (topic === 'chat') {
926
+ console.log('Usage: makaron chat --project <id|auto> [--image <file>] [--video <file|url>] [--stream] [--background|-b] [--json] "your message"');
927
+ } else if (topic === 'responses' || topic === 'run') {
928
+ if (subtopic === 'get') console.log('Usage: makaron responses get <runId> [--wait] [--json] [--pick <field>]');
929
+ else if (subtopic === 'watch') console.log('Usage: makaron responses watch <runId> [--jsonl] [--interval <ms>]');
930
+ else if (subtopic === 'list') console.log('Usage: makaron responses list --project <id>');
931
+ else console.log(`Responses commands:
932
+ responses get <runId> Get status and output (JSON)
933
+ responses get <runId> --wait Poll until completed
934
+ responses get <runId> --pick <field> Extract: first_image_url, first_video_url, project_url, output
935
+ responses watch <runId> --jsonl Watch until done (incremental events)
936
+ responses list --project <id> List runs for a project
937
+ `);
938
+ } else if (topic === 'list' || topic === 'ls') {
939
+ console.log('Usage: makaron list');
940
+ } else if (topic === 'project' || topic === 'projects') {
941
+ if (subtopic === 'media') console.log('Usage: makaron project media <projectId> [--json]');
942
+ else console.log(`Project commands:
943
+ project media <projectId> --json List timeline media for a project
944
+ `);
945
+ } else if (topic === 'abort') {
946
+ console.log('Usage: makaron abort <runId>');
947
+ } else if (topic === 'edit') {
948
+ console.log('Usage: makaron edit [--image <file|url>] [--model gemini|qwen|openai] [--skill enhance|creative|wild|captions] [--ref <file>] [--out <file>] "prompt"');
949
+ } else if (topic === 'analyze') {
950
+ console.log('Usage: makaron analyze --video <file|url> ["question"]');
951
+ } else if (topic === 'video') {
952
+ if (subtopic === 'script') console.log('Usage: makaron video script --image <file> [--image <file>] [--lang en|zh] "direction"');
953
+ else if (subtopic === 'create') console.log('Usage: makaron video create --script "..." (--image <url> | --video <public-url>) [--duration 10] [--aspect 9:16] [--model kling|seedance] [--keep-original-sound]');
954
+ else if (subtopic === 'status') console.log('Usage: makaron video status <taskId> | --snapshot <snapshotId> [--wait]');
955
+ else console.log(`Video commands:
956
+ video script --image <file> [--image <file>] "direction" Write video script
957
+ video create --script "..." --image <url> [--duration 10] Submit video task
958
+ video create --script "..." --video <public-url> [--model kling|seedance] Edit a video (standalone)
959
+ video status <taskId> Check video status
960
+ video status --snapshot <snapshotId> [--wait] Check v2 video snapshot
961
+ `);
962
+ } else if (topic === 'music') {
963
+ if (subtopic === 'create') console.log('Usage: makaron music create [--vocals] [--style "genre"] "description"');
964
+ else if (subtopic === 'status') console.log('Usage: makaron music status <taskId>');
965
+ else console.log(`Music commands:
966
+ music create [--vocals] [--style "genre"] "description" Generate music
967
+ music status <taskId> Check music status
968
+ `);
969
+ } else if (topic === 'admin') {
970
+ if (subtopic === 'skills') console.log('Usage: makaron admin skills [add|update|delete] ...');
971
+ else if (subtopic === 'upload') console.log('Usage: makaron admin upload <local-file> <storage-path>');
972
+ else if (subtopic === 'fetch-skill') console.log('Usage: makaron admin fetch-skill <share-code|url>');
973
+ else if (subtopic === 'set-admin') console.log('Usage: makaron admin set-admin <email>');
974
+ else console.log(`Admin commands:
975
+ admin skills List all marketplace skills
976
+ admin skills add '<json>' Add a new skill
977
+ admin skills update <id> '<json>' Update a skill
978
+ admin skills delete <id> Delete a skill
979
+ admin upload <file> <storage-path> Upload file to Storage
980
+ admin fetch-skill <code|url> Download skill from share link
981
+ admin set-admin <email> Grant admin access to a user
982
+ `);
983
+ } else if (topic === 'register') {
984
+ if (subtopic === '--verify') console.log('Usage: makaron register --verify --challenge-id <id> --answer <number>');
985
+ else console.log('Usage: makaron register --json | makaron register --verify --challenge-id <id> --answer <number>');
986
+ } else if (topic === 'claim') {
987
+ console.log('Usage: makaron claim');
988
+ } else {
989
+ printRootHelp();
990
+ }
991
+ }
992
+
1320
993
  // ─── Main ────────────────────────────────────────────────────────────────────
1321
994
 
1322
995
  const args = process.argv.slice(2);
1323
996
  const command = args[0];
1324
997
 
1325
- if (command === '--version' || command === '-v' || command === 'version') {
998
+ if (!command || command === '--help' || command === '-h' || command === 'help') {
999
+ printRootHelp();
1000
+ } else if (hasHelpFlag(args)) {
1001
+ printHelp(command, args[1]);
1002
+ } else if (command === '--version' || command === '-v' || command === 'version') {
1326
1003
  console.log(getCliVersion());
1327
1004
  } else if (command === 'login') {
1328
1005
  await login();
@@ -1353,11 +1030,7 @@ if (command === '--version' || command === '-v' || command === 'version') {
1353
1030
  let videoModel = undefined;
1354
1031
  let preferredModel = undefined;
1355
1032
  for (let i = 1; i < args.length; i++) {
1356
- if (args[i] === '--help' || args[i] === '-h') {
1357
- console.error('Usage: makaron chat --project <id|auto> [--image <file>] [--video <file|url>] [--stream] [--background|-b] [--json] "your message"');
1358
- process.exit(0);
1359
- }
1360
- else if (args[i] === '--project' && args[i + 1]) projectId = args[++i];
1033
+ if (args[i] === '--project' && args[i + 1]) projectId = args[++i];
1361
1034
  else if (args[i] === '--image' && args[i + 1]) chatImages.push(args[++i]);
1362
1035
  else if (args[i] === '--video' && args[i + 1]) chatVideos.push(args[++i]);
1363
1036
  else if (args[i] === '--stream') useStream = true;
@@ -1455,7 +1128,7 @@ if (command === '--version' || command === '-v' || command === 'version') {
1455
1128
  const uploadedVideoUrls = [...prevalidatedVideoUrlList];
1456
1129
  const uploadedVideoMetas = prevalidatedVideoUrlList.map(() => null);
1457
1130
  if (prevalidatedVideoUrlList.length) {
1458
- process.stderr.write(`📹 Assuming public video URL(s) already match Makaron upload limits: ≤${MAX_VIDEO_DURATION}s, ≤200MB, ≤1080p.\n`);
1131
+ process.stderr.write(`📹 Assuming public video URL(s) already match Makaron upload limits: ≤${MAX_VIDEO_UPLOAD_DURATION}s, ≤${MAX_VIDEO_UPLOAD_FILE_SIZE_MB}MB, ≤1080p.\n`);
1459
1132
  }
1460
1133
  for (const videoPath of prevalidatedVideoFileList) {
1461
1134
  process.stderr.write(`📹 Uploading ${path.basename(videoPath)} (${(fs.statSync(videoPath).size/1024/1024).toFixed(1)}MB)...\n`);
@@ -1471,7 +1144,7 @@ if (command === '--version' || command === '-v' || command === 'version') {
1471
1144
 
1472
1145
  // Add videos to project via projects/create (same as images)
1473
1146
  if (uploadedVideoUrls.length === 0) {
1474
- process.stderr.write(`❌ No valid videos were uploaded. Local videos must be MP4/MOV/WebM, ≤${MAX_VIDEO_DURATION}s, ≤200MB, and ≤1080p.\n`);
1147
+ process.stderr.write(`❌ No valid videos were uploaded. Local videos must be MP4/MOV/WebM, ≤${MAX_VIDEO_UPLOAD_DURATION}s, ≤${MAX_VIDEO_UPLOAD_FILE_SIZE_MB}MB, and ≤1080p.\n`);
1475
1148
  process.exit(1);
1476
1149
  }
1477
1150
 
@@ -1521,16 +1194,8 @@ if (command === '--version' || command === '-v' || command === 'version') {
1521
1194
  }
1522
1195
  }
1523
1196
  } else if (command === 'responses' || command === 'run') {
1524
- const sub = args[1];
1525
- if (!sub || sub === '--help' || sub === '-h') {
1526
- printResponsesUsage();
1527
- process.exit(0);
1528
- }
1529
- if (wantsHelp(args.slice(2))) {
1530
- printResponsesSubcommandUsage(sub);
1531
- process.exit(0);
1532
- }
1533
1197
  const { headers, baseUrl } = getAuth();
1198
+ const sub = args[1];
1534
1199
 
1535
1200
  if (sub === 'get') {
1536
1201
  const runId = args[2];
@@ -1568,85 +1233,6 @@ if (command === '--version' || command === '-v' || command === 'version') {
1568
1233
  }
1569
1234
  await watchRun(baseUrl, headers, runId, { interval, jsonl });
1570
1235
 
1571
- } else if (sub === 'next') {
1572
- const runId = args[2];
1573
- if (!runId) { console.error('Usage: makaron responses next <runId> [--json] [--checkpoint-mode service|off] [--no-fail]'); process.exit(1); }
1574
- let jsonOutput = false, checkpointMode = 'service', failOnCheckpoint = true;
1575
- for (let i = 3; i < args.length; i++) {
1576
- if (args[i] === '--json') jsonOutput = true;
1577
- else if (args[i] === '--checkpoint-mode' && args[i + 1]) checkpointMode = args[++i];
1578
- else if (args[i] === '--no-fail') failOnCheckpoint = false;
1579
- }
1580
- if (!['service', 'off'].includes(checkpointMode)) {
1581
- console.error('--checkpoint-mode must be service or off');
1582
- process.exit(1);
1583
- }
1584
- await printAgentNext(baseUrl, headers, runId, { json: jsonOutput, checkpointMode, failOnCheckpoint });
1585
-
1586
- } else if (sub === 'events' || sub === 'timeline') {
1587
- const runId = args[2];
1588
- if (!runId) { console.error(`Usage: makaron responses ${sub} <runId> [--jsonl] [--compact] [--checkpoint-mode service|off] [--follow] [--interval <ms>] [--fail-on-unapproved]`); process.exit(1); }
1589
- let interval = 5000, jsonl = false, follow = false, failOnUnapproved = false, compact = false, checkpointMode = 'service';
1590
- for (let i = 3; i < args.length; i++) {
1591
- if (args[i] === '--jsonl') jsonl = true;
1592
- else if (args[i] === '--compact') compact = true;
1593
- else if (args[i] === '--checkpoint-mode' && args[i + 1]) checkpointMode = args[++i];
1594
- else if (args[i] === '--follow') follow = true;
1595
- else if (args[i] === '--fail-on-unapproved') failOnUnapproved = true;
1596
- else if (args[i] === '--interval' && args[i + 1]) interval = parseInt(args[++i]);
1597
- }
1598
- if (!['service', 'off'].includes(checkpointMode)) {
1599
- console.error('--checkpoint-mode must be service or off');
1600
- process.exit(1);
1601
- }
1602
- await printDialogueEvents(baseUrl, headers, runId, { interval, jsonl, follow, failOnUnapproved, compact, checkpointMode });
1603
-
1604
- } else if (sub === 'deliver') {
1605
- const artifactId = args[2];
1606
- if (!artifactId) { console.error('Usage: makaron responses deliver <artifactId> --run <runId> [--channel <name>] [--message-id <id>] [--note <text>]'); process.exit(1); }
1607
- let runId = null, note = null, channel = null, messageId = null;
1608
- const noteParts = [];
1609
- for (let i = 3; i < args.length; i++) {
1610
- if (args[i] === '--run' && args[i + 1]) runId = args[++i];
1611
- else if (args[i] === '--note' && args[i + 1]) note = args[++i];
1612
- else if (args[i] === '--channel' && args[i + 1]) channel = args[++i];
1613
- else if (args[i] === '--message-id' && args[i + 1]) messageId = args[++i];
1614
- else noteParts.push(args[i]);
1615
- }
1616
- if (!runId) { console.error('Usage: makaron responses deliver <artifactId> --run <runId> [--channel <name>] [--message-id <id>] [--note <text>]'); process.exit(1); }
1617
- if (!note && noteParts.length) note = noteParts.join(' ');
1618
- recordDelivery(runId, artifactId, { note, channel, messageId });
1619
-
1620
- } else if (sub === 'handle' || ['approve', 'revise', 'ask-user', 'continue'].includes(sub)) {
1621
- const messageId = args[2];
1622
- if (!messageId) {
1623
- console.error(sub === 'handle'
1624
- ? 'Usage: makaron responses handle <messageId> --run <runId> --choice approve|revise|ask_user|continue [--note <text>]'
1625
- : `Usage: makaron responses ${sub} <messageId> --run <runId> [--note <text>]`);
1626
- process.exit(1);
1627
- }
1628
- let runId = null, note = null, choice = sub === 'handle' ? null : sub;
1629
- const noteParts = [];
1630
- for (let i = 3; i < args.length; i++) {
1631
- if (args[i] === '--run' && args[i + 1]) runId = args[++i];
1632
- else if (args[i] === '--note' && args[i + 1]) note = args[++i];
1633
- else if (args[i] === '--choice' && args[i + 1]) choice = args[++i];
1634
- else noteParts.push(args[i]);
1635
- }
1636
- if (!runId || !choice) {
1637
- console.error(sub === 'handle'
1638
- ? 'Usage: makaron responses handle <messageId> --run <runId> --choice approve|revise|ask_user|continue [--note <text>]'
1639
- : `Usage: makaron responses ${sub} <messageId> --run <runId> [--note <text>]`);
1640
- process.exit(1);
1641
- }
1642
- choice = choice === 'ask-user' ? 'ask_user' : choice;
1643
- if (!['approve', 'revise', 'ask_user', 'continue'].includes(choice)) {
1644
- console.error('--choice must be approve, revise, ask_user, or continue');
1645
- process.exit(1);
1646
- }
1647
- if (!note && noteParts.length) note = noteParts.join(' ');
1648
- recordApproval(runId, messageId, choice, note);
1649
-
1650
1236
  } else if (sub === 'list') {
1651
1237
  let projectId = null;
1652
1238
  for (let i = 2; i < args.length; i++) {
@@ -1666,11 +1252,30 @@ if (command === '--version' || command === '-v' || command === 'version') {
1666
1252
  }
1667
1253
 
1668
1254
  } else {
1669
- printResponsesUsage();
1255
+ console.log(`Responses commands:
1256
+ responses get <runId> Get status and output (JSON)
1257
+ responses get <runId> --wait Poll until completed
1258
+ responses get <runId> --pick <field> Extract: first_image_url, first_video_url, project_url, output
1259
+ responses watch <runId> --jsonl Watch until done (incremental events)
1260
+ responses list --project <id> List runs for a project
1261
+ `);
1670
1262
  }
1671
1263
  } else if (command === 'list' || command === 'ls') {
1672
1264
  const { headers, baseUrl } = getAuth();
1673
1265
  await listProjects(baseUrl, headers);
1266
+ } else if (command === 'project' || command === 'projects') {
1267
+ const { headers, baseUrl } = getAuth();
1268
+ const sub = args[1];
1269
+ if (sub === 'media') {
1270
+ const projectId = args[2];
1271
+ if (!projectId) { console.error('Usage: makaron project media <projectId> [--json]'); process.exit(1); }
1272
+ const jsonOutput = args.includes('--json');
1273
+ await listProjectMedia(baseUrl, headers, projectId, { json: jsonOutput });
1274
+ } else {
1275
+ console.log(`Project commands:
1276
+ project media <projectId> --json List timeline media for a project
1277
+ `);
1278
+ }
1674
1279
  } else if (command === 'abort') {
1675
1280
  const { headers, baseUrl } = getAuth();
1676
1281
  const runId = args[1];
@@ -1771,11 +1376,22 @@ if (command === '--version' || command === '-v' || command === 'version') {
1771
1376
 
1772
1377
  let videoUrl = isHttpUrl(video) ? video : null;
1773
1378
  let inputVideoMeta = null;
1379
+ const selectedVideoModel = videoModel || 'kling';
1774
1380
  if (videoUrl) {
1775
- process.stderr.write(`📹 Assuming public video URL already matches Makaron upload limits: ≤${MAX_VIDEO_DURATION}s, ≤200MB, ≤1080p.\n`);
1381
+ process.stderr.write(`📹 Assuming public video URL already matches provider reference limits. Seedance requires ≤${MAX_VIDEO_PROVIDER_REFERENCE_DURATION}s, ≤50MB, sides 300-6000px, frame pixels 409,600-${MAX_VIDEO_FRAME_PIXELS}; Kling requires 200MB and ≤2K.\n`);
1776
1382
  }
1777
1383
  if (video && !videoUrl) {
1778
- const valid = validateVideoFile(video);
1384
+ const valid = validateVideoFile(video, {
1385
+ maxDuration: MAX_VIDEO_PROVIDER_REFERENCE_DURATION,
1386
+ durationTolerance: MAX_VIDEO_PROVIDER_REFERENCE_DURATION_TOLERANCE,
1387
+ ...(selectedVideoModel === 'seedance' ? {
1388
+ minFramePixels: SEEDANCE_MIN_VIDEO_FRAME_PIXELS,
1389
+ minSide: SEEDANCE_MIN_VIDEO_SIDE,
1390
+ maxSide: SEEDANCE_MAX_VIDEO_SIDE,
1391
+ minAspect: SEEDANCE_MIN_VIDEO_ASPECT,
1392
+ maxAspect: SEEDANCE_MAX_VIDEO_ASPECT,
1393
+ } : {}),
1394
+ });
1779
1395
  if (!valid.ok) { console.error(`❌ ${valid.error}`); process.exit(1); }
1780
1396
  inputVideoMeta = valid.meta;
1781
1397
  process.stderr.write(`📹 Uploading ${path.basename(video)} (${(fs.statSync(video).size/1024/1024).toFixed(1)}MB)...\n`);
@@ -1786,9 +1402,9 @@ if (command === '--version' || command === '-v' || command === 'version') {
1786
1402
  // Standalone MCP tool (no project timeline write)
1787
1403
  process.stderr.write('🎬 Submitting video...\n');
1788
1404
  const vArgs = videoUrl
1789
- ? { videoUrl, editPrompt: script, images, videoModel: videoModel || 'kling', referType: (videoModel || 'kling') === 'seedance' ? 'feature' : 'base' }
1405
+ ? { videoUrl, editPrompt: script, images, videoModel: selectedVideoModel, referType: selectedVideoModel === 'seedance' ? 'feature' : 'base' }
1790
1406
  : { script, images };
1791
- const effectiveDuration = duration || (inputVideoMeta?.duration ? Math.min(MAX_VIDEO_DURATION, Math.round(inputVideoMeta.duration)) : undefined);
1407
+ const effectiveDuration = duration || (inputVideoMeta?.duration ? Math.min(MAX_VIDEO_PROVIDER_REFERENCE_DURATION, Math.round(inputVideoMeta.duration)) : undefined);
1792
1408
  if (effectiveDuration) vArgs.duration = effectiveDuration;
1793
1409
  if (aspectRatio) vArgs.aspectRatio = aspectRatio;
1794
1410
  if (videoModel && !videoUrl) vArgs.videoModel = videoModel;
@@ -2081,43 +1697,5 @@ if (command === '--version' || command === '-v' || command === 'version') {
2081
1697
  console.log(JSON.stringify(data));
2082
1698
  console.error(`🔗 Share this link with a human: ${data.claim_url}`);
2083
1699
  } else {
2084
- console.log(`Makaron CLI — Talk to Makaron Agent from the terminal
2085
-
2086
- Commands:
2087
- register --json Get challenge for agent self-registration
2088
- register --verify --challenge-id <id> --answer <n> Verify and save API key
2089
- claim Get claim URL for human to link account
2090
- login Log in to Makaron (human interactive)
2091
- list (ls) List all projects
2092
- create --image <file> Create project from local image
2093
- create --image-url <url> Create project from URL
2094
- create --title "name" Create empty project (text-to-image)
2095
-
2096
- chat --project <id> "message" Chat (non-blocking, polls for result)
2097
- chat --project <id> --video <file> Attach video to conversation
2098
- chat --project <id> -b "message" Background: submit and print runId
2099
- chat --project <id> --stream "msg" Legacy: stream SSE in real-time
2100
- chat --project <id> --json "msg" Output structured JSON result
2101
-
2102
- responses get <runId> Get run status and results
2103
- responses get <runId> --wait Poll until completed
2104
- responses next <runId> --json New Agent entry: compact timeline + checkpoint guidance
2105
- responses handle <messageId> --run <runId> --choice approve|revise|ask_user|continue
2106
- responses deliver <artifactId> --run <runId> Record artifact delivered to the user
2107
- responses events <runId> --jsonl Emit message/approval/artifact events
2108
- responses timeline <runId> --checkpoint-mode off Disable text-only checkpoints for pure Q&A
2109
- responses list --project <id> List runs for a project
2110
- abort <runId> Abort a running Agent
2111
-
2112
- edit [--image <file>] "prompt" AI image edit / text-to-image
2113
- analyze --video <file|url> Analyze video content
2114
- video script|create|status Video generation
2115
- music create|status Music generation
2116
-
2117
- admin Admin commands (skills, upload, set-admin)
2118
-
2119
- Environment:
2120
- MAKARON_API_KEY API key (mk_live_xxx) — recommended for agents
2121
- MAKARON_URL API base (default: ${DEFAULT_URL})
2122
- `);
1700
+ printRootHelp();
2123
1701
  }