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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +14 -97
- package/SKILL.md +13 -96
- package/bin/makaron.mjs +228 -650
- package/package.json +3 -3
- package/skills/makaron/SKILL.md +13 -96
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
|
|
33
|
-
const
|
|
34
|
-
const
|
|
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 >
|
|
1201
|
-
return { ok: false, error: `Video too large: ${(stat.size / 1024 / 1024).toFixed(1)}MB (max
|
|
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 >
|
|
1212
|
-
return { ok: false, error: `Video too long: ${formatSeconds(meta.duration)}s (max ${
|
|
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 >
|
|
1230
|
-
return { ok: false, error: `Video too large: ${(stat.size / 1024 / 1024).toFixed(1)}MB (max
|
|
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 === '--
|
|
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] === '--
|
|
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: ≤${
|
|
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, ≤${
|
|
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
|
-
|
|
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
|
|
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:
|
|
1405
|
+
? { videoUrl, editPrompt: script, images, videoModel: selectedVideoModel, referType: selectedVideoModel === 'seedance' ? 'feature' : 'base' }
|
|
1790
1406
|
: { script, images };
|
|
1791
|
-
const effectiveDuration = duration || (inputVideoMeta?.duration ? Math.min(
|
|
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
|
-
|
|
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
|
}
|