makaron-cli 0.7.6 → 0.7.8
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/README.md +49 -23
- package/SKILL.md +49 -23
- package/bin/makaron.mjs +211 -2
- package/package.json +6 -1
- package/skills/makaron/SKILL.md +49 -23
package/README.md
CHANGED
|
@@ -61,14 +61,14 @@ Share the `claim_url` with a human. They log in and the API key gets linked to t
|
|
|
61
61
|
# One-shot: create project + upload image + submit prompt — all in one command
|
|
62
62
|
RUN_ID=$(npx makaron-cli chat --project auto --image photo.jpg -b "make it cinematic and create a 5s video")
|
|
63
63
|
|
|
64
|
-
#
|
|
65
|
-
npx makaron-cli responses
|
|
64
|
+
# Wait for the final customer-ready result
|
|
65
|
+
npx makaron-cli responses get $RUN_ID --wait --json
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
Or with an existing project:
|
|
69
69
|
```bash
|
|
70
70
|
RUN_ID=$(npx makaron-cli chat --project $PROJECT_ID -b "make a 5s video")
|
|
71
|
-
npx makaron-cli responses
|
|
71
|
+
npx makaron-cli responses get $RUN_ID --wait --json
|
|
72
72
|
```
|
|
73
73
|
|
|
74
74
|
## Primary: `chat` (Agent-driven creative work)
|
|
@@ -120,7 +120,7 @@ Use `chat --project <id|auto> --video ...` for any project/timeline video work.
|
|
|
120
120
|
npx makaron-cli responses get <runId> --json
|
|
121
121
|
```
|
|
122
122
|
|
|
123
|
-
###
|
|
123
|
+
### Advanced: stream incremental events
|
|
124
124
|
|
|
125
125
|
```bash
|
|
126
126
|
npx makaron-cli responses watch <runId> --jsonl
|
|
@@ -134,6 +134,37 @@ Outputs one JSON per line as artifacts appear:
|
|
|
134
134
|
{"event":"done","status":"completed"}
|
|
135
135
|
```
|
|
136
136
|
|
|
137
|
+
### Dialogue events for external Agents
|
|
138
|
+
|
|
139
|
+
Use this when another Agent needs to read what Makaron said, detect approval requirements, and relay artifacts without inventing customer-service wording.
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
npx makaron-cli responses events <runId> --jsonl
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Events use only three factual types:
|
|
146
|
+
|
|
147
|
+
```json
|
|
148
|
+
{"type":"message","id":"msg_1","runId":"run_xxx","seq":1,"text":"Makaron original message","requires_approval":true,"approval_options":["approve","revise","ask_user","continue"]}
|
|
149
|
+
{"type":"approval","messageId":"msg_1","choice":"approve","status":"recorded"}
|
|
150
|
+
{"type":"artifact","kind":"image","status":"completed","url":"https://..."}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
If a message has `requires_approval: true`, the external Agent must record an approval before continuing to wait for artifacts or claiming completion:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
npx makaron-cli responses approve msg_1 --run <runId> --note "Proceed."
|
|
157
|
+
npx makaron-cli responses revise msg_1 --run <runId> "make it softer"
|
|
158
|
+
npx makaron-cli responses ask-user msg_1 --run <runId>
|
|
159
|
+
npx makaron-cli responses continue msg_1 --run <runId>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Wrappers can enforce this with:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
npx makaron-cli responses events <runId> --jsonl --fail-on-unapproved
|
|
166
|
+
```
|
|
167
|
+
|
|
137
168
|
### Extract specific results
|
|
138
169
|
|
|
139
170
|
```bash
|
|
@@ -278,38 +309,33 @@ RUN_ID=$(npx makaron-cli chat --project auto --image photo.jpg -b "make it cinem
|
|
|
278
309
|
PROJECT_URL=$(npx makaron-cli responses get $RUN_ID --pick project_url)
|
|
279
310
|
send_message "Project created: $PROJECT_URL"
|
|
280
311
|
|
|
281
|
-
# 4.
|
|
282
|
-
npx makaron-cli responses
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
elif [ "$EVENT" = "output.updated" ] && [ "$TYPE" = "video" ] && [ "$STATUS" = "completed" ]; then
|
|
292
|
-
# Video ready — send as media
|
|
293
|
-
send_video "$URL"
|
|
294
|
-
elif [ "$EVENT" = "done" ]; then
|
|
295
|
-
send_message "All done!"
|
|
296
|
-
fi
|
|
312
|
+
# 4. Wait for the final customer-ready result
|
|
313
|
+
RESULT=$(npx makaron-cli responses get $RUN_ID --wait --json)
|
|
314
|
+
IMAGE_URLS=$(echo "$RESULT" | jq -r '[.result.images[]?.imageUrl, .output[]? | select(.type == "image") | .url] | map(select(. != null)) | unique | .[]')
|
|
315
|
+
VIDEO_URLS=$(echo "$RESULT" | jq -r '[.result.videos[]?.videoUrl, .output[]? | select(.type == "video") | .url] | map(select(. != null)) | unique | .[]')
|
|
316
|
+
|
|
317
|
+
for URL in $IMAGE_URLS; do
|
|
318
|
+
send_image "$URL"
|
|
319
|
+
done
|
|
320
|
+
for URL in $VIDEO_URLS; do
|
|
321
|
+
send_video "$URL"
|
|
297
322
|
done
|
|
323
|
+
send_message "All done!"
|
|
298
324
|
```
|
|
299
325
|
|
|
300
326
|
**Key principles for service agents:**
|
|
301
|
-
- **Proactive, not
|
|
327
|
+
- **Proactive, not silent**: Acknowledge immediately, send the project link early, then send the final customer-ready media when the run completes.
|
|
302
328
|
- **Media over links**: When possible, send images/videos as native media in the chat (download URL and upload as attachment), not just paste the URL.
|
|
303
329
|
- **Immediate acknowledgment**: Reply within 1 second of receiving user request. Don't make users wait for project creation.
|
|
304
330
|
- **Project link early**: Send the project URL right after creation so users can check anytime.
|
|
305
|
-
- **
|
|
331
|
+
- **Use `get --wait --json` as the default service path**: reserve `watch --jsonl` for advanced streaming or debugging integrations that explicitly need incremental events.
|
|
306
332
|
|
|
307
333
|
## Important Notes
|
|
308
334
|
|
|
309
335
|
- One project = one conversation thread. All history is preserved.
|
|
310
336
|
- One run at a time per project. New message interrupts previous run.
|
|
311
337
|
- Multi-image: `create --image a.jpg --image b.jpg` or `chat --image ref.jpg`.
|
|
312
|
-
- Videos take 2-5 minutes to render. Use `
|
|
338
|
+
- Videos take 2-5 minutes to render. Use `responses get <runId> --wait --json` for the default customer-service path.
|
|
313
339
|
- Music takes ~60 seconds. Appears in output when done.
|
|
314
340
|
- Images are typically ready in 15-30 seconds.
|
|
315
341
|
- stdout is always machine-readable JSON/text. Human-friendly logs go to stderr.
|
package/SKILL.md
CHANGED
|
@@ -50,14 +50,14 @@ Verify: `npx makaron-cli list` should show projects.
|
|
|
50
50
|
# One-shot: create project + upload image + submit prompt — all in one command
|
|
51
51
|
RUN_ID=$(npx makaron-cli chat --project auto --image photo.jpg -b "make it cinematic and create a 5s video")
|
|
52
52
|
|
|
53
|
-
#
|
|
54
|
-
npx makaron-cli responses
|
|
53
|
+
# Wait for the final customer-ready result
|
|
54
|
+
npx makaron-cli responses get $RUN_ID --wait --json
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
Or with an existing project:
|
|
58
58
|
```bash
|
|
59
59
|
RUN_ID=$(npx makaron-cli chat --project $PROJECT_ID -b "make a 5s video")
|
|
60
|
-
npx makaron-cli responses
|
|
60
|
+
npx makaron-cli responses get $RUN_ID --wait --json
|
|
61
61
|
```
|
|
62
62
|
|
|
63
63
|
## Primary: `chat` (Agent-driven creative work)
|
|
@@ -112,7 +112,7 @@ Use `chat --project <id|auto> --video ...` for any project/timeline video work.
|
|
|
112
112
|
npx makaron-cli responses get <runId> --json
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
-
###
|
|
115
|
+
### Advanced: stream incremental events
|
|
116
116
|
|
|
117
117
|
```bash
|
|
118
118
|
npx makaron-cli responses watch <runId> --jsonl
|
|
@@ -126,6 +126,37 @@ Outputs one JSON per line as artifacts appear:
|
|
|
126
126
|
{"event":"done","status":"completed"}
|
|
127
127
|
```
|
|
128
128
|
|
|
129
|
+
### Dialogue events for external Agents
|
|
130
|
+
|
|
131
|
+
Use this when another Agent needs to read what Makaron said, detect approval requirements, and relay artifacts without inventing customer-service wording.
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
npx makaron-cli responses events <runId> --jsonl
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Events use only three factual types:
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{"type":"message","id":"msg_1","runId":"run_xxx","seq":1,"text":"Makaron original message","requires_approval":true,"approval_options":["approve","revise","ask_user","continue"]}
|
|
141
|
+
{"type":"approval","messageId":"msg_1","choice":"approve","status":"recorded"}
|
|
142
|
+
{"type":"artifact","kind":"image","status":"completed","url":"https://..."}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
If a message has `requires_approval: true`, the external Agent must record an approval before continuing to wait for artifacts or claiming completion:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
npx makaron-cli responses approve msg_1 --run <runId> --note "Proceed."
|
|
149
|
+
npx makaron-cli responses revise msg_1 --run <runId> "make it softer"
|
|
150
|
+
npx makaron-cli responses ask-user msg_1 --run <runId>
|
|
151
|
+
npx makaron-cli responses continue msg_1 --run <runId>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Wrappers can enforce this with:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
npx makaron-cli responses events <runId> --jsonl --fail-on-unapproved
|
|
158
|
+
```
|
|
159
|
+
|
|
129
160
|
### Extract specific results
|
|
130
161
|
|
|
131
162
|
```bash
|
|
@@ -267,38 +298,33 @@ RUN_ID=$(npx makaron-cli chat --project auto --image photo.jpg -b "make it cinem
|
|
|
267
298
|
PROJECT_URL=$(npx makaron-cli responses get $RUN_ID --pick project_url)
|
|
268
299
|
send_message "Project created: $PROJECT_URL"
|
|
269
300
|
|
|
270
|
-
# 4.
|
|
271
|
-
npx makaron-cli responses
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
elif [ "$EVENT" = "output.updated" ] && [ "$TYPE" = "video" ] && [ "$STATUS" = "completed" ]; then
|
|
281
|
-
# Video ready — send as media
|
|
282
|
-
send_video "$URL"
|
|
283
|
-
elif [ "$EVENT" = "done" ]; then
|
|
284
|
-
send_message "All done!"
|
|
285
|
-
fi
|
|
301
|
+
# 4. Wait for the final customer-ready result
|
|
302
|
+
RESULT=$(npx makaron-cli responses get $RUN_ID --wait --json)
|
|
303
|
+
IMAGE_URLS=$(echo "$RESULT" | jq -r '[.result.images[]?.imageUrl, .output[]? | select(.type == "image") | .url] | map(select(. != null)) | unique | .[]')
|
|
304
|
+
VIDEO_URLS=$(echo "$RESULT" | jq -r '[.result.videos[]?.videoUrl, .output[]? | select(.type == "video") | .url] | map(select(. != null)) | unique | .[]')
|
|
305
|
+
|
|
306
|
+
for URL in $IMAGE_URLS; do
|
|
307
|
+
send_image "$URL"
|
|
308
|
+
done
|
|
309
|
+
for URL in $VIDEO_URLS; do
|
|
310
|
+
send_video "$URL"
|
|
286
311
|
done
|
|
312
|
+
send_message "All done!"
|
|
287
313
|
```
|
|
288
314
|
|
|
289
315
|
**Key principles for service agents:**
|
|
290
|
-
- **Proactive, not
|
|
316
|
+
- **Proactive, not silent**: Acknowledge immediately, send the project link early, then send the final customer-ready media when the run completes.
|
|
291
317
|
- **Media over links**: When possible, send images/videos as native media in the chat (download URL and upload as attachment), not just paste the URL.
|
|
292
318
|
- **Immediate acknowledgment**: Reply within 1 second of receiving user request. Don't make users wait for project creation.
|
|
293
319
|
- **Project link early**: Send the project URL right after creation so users can check anytime.
|
|
294
|
-
- **
|
|
320
|
+
- **Use `get --wait --json` as the default service path**: reserve `watch --jsonl` for advanced streaming or debugging integrations that explicitly need incremental events.
|
|
295
321
|
|
|
296
322
|
## Important Notes
|
|
297
323
|
|
|
298
324
|
- One project = one conversation thread. All history is preserved.
|
|
299
325
|
- One run at a time per project. New message interrupts previous run.
|
|
300
326
|
- Multi-image: `create --image a.jpg --image b.jpg` or `chat --image ref.jpg`.
|
|
301
|
-
- Videos take 2-5 minutes to render. Use `
|
|
327
|
+
- Videos take 2-5 minutes to render. Use `responses get <runId> --wait --json` for the default customer-service path.
|
|
302
328
|
- Music takes ~60 seconds. Appears in output when done.
|
|
303
329
|
- Images are typically ready in 15-30 seconds.
|
|
304
330
|
- stdout is always machine-readable JSON/text. Human-friendly logs go to stderr.
|
package/bin/makaron.mjs
CHANGED
|
@@ -19,6 +19,7 @@ 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');
|
|
22
23
|
const DEFAULT_URL = 'https://www.makaron.app';
|
|
23
24
|
const BASE_URL = process.env.MAKARON_URL || DEFAULT_URL;
|
|
24
25
|
const APP_URL = process.env.MAKARON_APP_URL || DEFAULT_URL;
|
|
@@ -32,6 +33,15 @@ const MAX_VIDEO_DURATION = 15;
|
|
|
32
33
|
const MAX_VIDEO_DURATION_TOLERANCE = 0.5;
|
|
33
34
|
const MAX_VIDEO_FRAME_PIXELS = 2_086_876;
|
|
34
35
|
|
|
36
|
+
function getCliVersion() {
|
|
37
|
+
try {
|
|
38
|
+
const pkg = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
|
|
39
|
+
return pkg.version || '0.0.0';
|
|
40
|
+
} catch {
|
|
41
|
+
return '0.0.0';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
35
45
|
function formatSeconds(seconds) {
|
|
36
46
|
if (!Number.isFinite(seconds)) return String(seconds);
|
|
37
47
|
return Number.isInteger(seconds) ? String(seconds) : seconds.toFixed(1).replace(/\.0$/, '');
|
|
@@ -53,6 +63,20 @@ function saveAuth(data) {
|
|
|
53
63
|
fs.writeFileSync(AUTH_FILE, JSON.stringify(data, null, 2));
|
|
54
64
|
}
|
|
55
65
|
|
|
66
|
+
function loadApprovals() {
|
|
67
|
+
try {
|
|
68
|
+
return JSON.parse(fs.readFileSync(APPROVALS_FILE, 'utf-8'));
|
|
69
|
+
} catch {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function saveApprovals(approvals) {
|
|
75
|
+
const dir = path.dirname(APPROVALS_FILE);
|
|
76
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
77
|
+
fs.writeFileSync(APPROVALS_FILE, JSON.stringify(approvals, null, 2));
|
|
78
|
+
}
|
|
79
|
+
|
|
56
80
|
function buildCookie(tokenJson) {
|
|
57
81
|
const url = tokenJson._supabaseUrl || SUPABASE_URL;
|
|
58
82
|
const ref = url.match(/\/\/([^.]+)\./)?.[1] || '';
|
|
@@ -409,7 +433,7 @@ function applyPick(data, field) {
|
|
|
409
433
|
case 'design_urls': return (data.output || []).filter(o => o.type === 'design' && o.url).map(o => o.url);
|
|
410
434
|
case 'first_music_url': return data.output?.find(o => o.type === 'music' && o.url)?.url || null;
|
|
411
435
|
case 'music_urls': return (data.output || []).filter(o => o.type === 'music' && o.url).map(o => o.url);
|
|
412
|
-
case 'project_url': return data.project_url || null;
|
|
436
|
+
case 'project_url': return data.project_url || data.projectUrl || null;
|
|
413
437
|
case 'output': return data.output || [];
|
|
414
438
|
case 'text': return data.output?.find(o => o.type === 'text')?.content || null;
|
|
415
439
|
case 'status': return data.status;
|
|
@@ -417,6 +441,157 @@ function applyPick(data, field) {
|
|
|
417
441
|
}
|
|
418
442
|
}
|
|
419
443
|
|
|
444
|
+
// ─── Dialogue Events (message / approval / artifact) ─────────────────────────
|
|
445
|
+
|
|
446
|
+
function stableEventId(prefix, runId, seq, fallback) {
|
|
447
|
+
return fallback || `${prefix}_${runId}_${seq ?? Date.now()}`;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function normalizeDialogueMessage(runId, projectId, ev) {
|
|
451
|
+
const data = ev.data || ev;
|
|
452
|
+
const text = data.text || data.message || data.content || data.statusText || '';
|
|
453
|
+
if (!text && ev.type !== 'tool_call') return null;
|
|
454
|
+
const requiresApproval = Boolean(data.requires_approval || data.requiresApproval || data.action_required || data.actionRequired);
|
|
455
|
+
const message = {
|
|
456
|
+
type: 'message',
|
|
457
|
+
id: stableEventId('msg', runId, ev.seq, data.id || ev.id),
|
|
458
|
+
runId,
|
|
459
|
+
projectId,
|
|
460
|
+
seq: ev.seq,
|
|
461
|
+
status: data.status || undefined,
|
|
462
|
+
text: text || `${data.tool || 'tool_call'}${data.input?.description ? `: ${data.input.description}` : ''}`,
|
|
463
|
+
};
|
|
464
|
+
if (ev.type) message.source_type = ev.type;
|
|
465
|
+
if (requiresApproval) {
|
|
466
|
+
message.requires_approval = true;
|
|
467
|
+
message.approval_options = data.approval_options || data.approvalOptions || ['approve', 'revise', 'ask_user', 'continue'];
|
|
468
|
+
}
|
|
469
|
+
if (data.proposal) message.proposal = data.proposal;
|
|
470
|
+
return message;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function normalizeDialogueArtifact(runId, projectId, item, seq) {
|
|
474
|
+
if (!item) return null;
|
|
475
|
+
const kind = item.type || item.kind || (item.imageUrl ? 'image' : item.videoUrl ? 'video' : undefined);
|
|
476
|
+
if (!kind) return null;
|
|
477
|
+
const artifact = {
|
|
478
|
+
type: 'artifact',
|
|
479
|
+
id: stableEventId('artifact', runId, seq, item.id || item.snapshotId || item.taskId),
|
|
480
|
+
runId,
|
|
481
|
+
projectId,
|
|
482
|
+
seq,
|
|
483
|
+
kind,
|
|
484
|
+
status: item.status || (item.url || item.imageUrl || item.videoUrl || item.audioUrl ? 'completed' : 'running'),
|
|
485
|
+
};
|
|
486
|
+
const url = item.url || item.imageUrl || item.videoUrl || item.audioUrl;
|
|
487
|
+
if (url) artifact.url = url;
|
|
488
|
+
if (item.error) artifact.error = item.error;
|
|
489
|
+
if (item.taskId) artifact.taskId = item.taskId;
|
|
490
|
+
if (item.snapshotId) artifact.snapshotId = item.snapshotId;
|
|
491
|
+
return artifact;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function normalizeDialogueEvent(runId, projectId, ev) {
|
|
495
|
+
const data = ev.data || {};
|
|
496
|
+
if (ev.type === 'message' || ev.type === 'approval' || ev.type === 'artifact') {
|
|
497
|
+
return { ...data, ...ev, runId: ev.runId || runId, projectId: ev.projectId || projectId };
|
|
498
|
+
}
|
|
499
|
+
switch (ev.type) {
|
|
500
|
+
case 'content':
|
|
501
|
+
case 'status':
|
|
502
|
+
case 'tool_call':
|
|
503
|
+
case 'error':
|
|
504
|
+
return normalizeDialogueMessage(runId, projectId, ev);
|
|
505
|
+
case 'image':
|
|
506
|
+
return normalizeDialogueArtifact(runId, projectId, { type: 'image', status: data.imageUrl ? 'completed' : 'running', imageUrl: data.imageUrl, snapshotId: data.snapshotId }, ev.seq);
|
|
507
|
+
case 'render':
|
|
508
|
+
return normalizeDialogueArtifact(runId, projectId, { type: data.animation ? 'video' : 'design', status: data.published ? 'completed' : 'running', url: data.url, snapshotId: data.snapshotId }, ev.seq);
|
|
509
|
+
case 'animation_task':
|
|
510
|
+
case 'video_snapshot':
|
|
511
|
+
return normalizeDialogueArtifact(runId, projectId, { type: 'video', status: 'running', taskId: data.taskId, snapshotId: data.snapshotId }, ev.seq);
|
|
512
|
+
case 'music_task':
|
|
513
|
+
return normalizeDialogueArtifact(runId, projectId, { type: 'music', status: 'running', taskId: data.taskId }, ev.seq);
|
|
514
|
+
default:
|
|
515
|
+
return null;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function buildDialogueEvents(runId, data) {
|
|
520
|
+
const projectId = data.projectId || data.project_id;
|
|
521
|
+
const events = [];
|
|
522
|
+
for (const ev of data.events || []) {
|
|
523
|
+
const normalized = normalizeDialogueEvent(runId, projectId, ev);
|
|
524
|
+
if (normalized) events.push(normalized);
|
|
525
|
+
}
|
|
526
|
+
for (const item of data.output || []) {
|
|
527
|
+
const normalized = normalizeDialogueArtifact(runId, projectId, item, item.seq);
|
|
528
|
+
if (normalized && !events.some(ev => ev.type === 'artifact' && ev.id === normalized.id)) events.push(normalized);
|
|
529
|
+
}
|
|
530
|
+
for (const approval of loadApprovals().filter(item => item.runId === runId)) {
|
|
531
|
+
events.push(approval);
|
|
532
|
+
}
|
|
533
|
+
return events;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function findUnhandledApprovalMessages(events) {
|
|
537
|
+
const approved = new Set(events.filter(ev => ev.type === 'approval').map(ev => ev.messageId || ev.message_id));
|
|
538
|
+
return events.filter(ev => ev.type === 'message' && ev.requires_approval && !approved.has(ev.id));
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
async function fetchRun(baseUrl, headers, runId, opts = {}) {
|
|
542
|
+
const params = new URLSearchParams();
|
|
543
|
+
if (opts.events) params.set('events', 'true');
|
|
544
|
+
const suffix = params.toString() ? `?${params}` : '';
|
|
545
|
+
const res = await fetch(`${baseUrl}/api/agent/run/${runId}${suffix}`, { headers });
|
|
546
|
+
if (!res.ok) { process.stderr.write(`Error ${res.status}: ${await res.text()}\n`); process.exit(1); }
|
|
547
|
+
return normalizeRunResponse(await res.json());
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
async function printDialogueEvents(baseUrl, headers, runId, opts = {}) {
|
|
551
|
+
const { jsonl = false, failOnUnapproved = false, follow = false, interval = 5000 } = opts;
|
|
552
|
+
const printed = new Set();
|
|
553
|
+
|
|
554
|
+
while (true) {
|
|
555
|
+
const data = await fetchRun(baseUrl, headers, runId, { events: true });
|
|
556
|
+
const events = buildDialogueEvents(runId, data);
|
|
557
|
+
const unhandled = findUnhandledApprovalMessages(events);
|
|
558
|
+
if (failOnUnapproved && unhandled.length) {
|
|
559
|
+
process.stderr.write(`Unhandled Makaron message requires approval: ${unhandled.map(ev => ev.id).join(', ')}\n`);
|
|
560
|
+
process.exit(3);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
const nextEvents = follow ? events.filter(ev => !printed.has(`${ev.type}:${ev.id || ev.seq}`)) : events;
|
|
564
|
+
for (const ev of nextEvents) {
|
|
565
|
+
printed.add(`${ev.type}:${ev.id || ev.seq}`);
|
|
566
|
+
if (jsonl) console.log(JSON.stringify(ev));
|
|
567
|
+
}
|
|
568
|
+
if (!jsonl) console.log(JSON.stringify(nextEvents, null, 2));
|
|
569
|
+
|
|
570
|
+
if (!follow || (!data.incomplete && ['completed', 'failed', 'aborted'].includes(data.status))) {
|
|
571
|
+
if (data.status === 'failed' || data.status === 'aborted') process.exit(1);
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
await new Promise(r => setTimeout(r, data.next_poll_after_ms || interval));
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function recordApproval(runId, messageId, choice, note) {
|
|
579
|
+
const approvals = loadApprovals();
|
|
580
|
+
const approval = {
|
|
581
|
+
type: 'approval',
|
|
582
|
+
id: `approval_${Date.now()}`,
|
|
583
|
+
runId,
|
|
584
|
+
messageId,
|
|
585
|
+
choice,
|
|
586
|
+
status: 'recorded',
|
|
587
|
+
createdAt: new Date().toISOString(),
|
|
588
|
+
};
|
|
589
|
+
if (note) approval.note = note;
|
|
590
|
+
approvals.push(approval);
|
|
591
|
+
saveApprovals(approvals);
|
|
592
|
+
console.log(JSON.stringify(approval));
|
|
593
|
+
}
|
|
594
|
+
|
|
420
595
|
// ─── Watch (incremental event stream) ───────────────────────────────────────
|
|
421
596
|
|
|
422
597
|
async function watchRun(baseUrl, headers, runId, opts = {}) {
|
|
@@ -813,7 +988,9 @@ async function analyzeVideoCli(baseUrl, headers, rawVideo, questionParts) {
|
|
|
813
988
|
const args = process.argv.slice(2);
|
|
814
989
|
const command = args[0];
|
|
815
990
|
|
|
816
|
-
if (command === '
|
|
991
|
+
if (command === '--version' || command === '-v' || command === 'version') {
|
|
992
|
+
console.log(getCliVersion());
|
|
993
|
+
} else if (command === 'login') {
|
|
817
994
|
await login();
|
|
818
995
|
} else if (command === 'create') {
|
|
819
996
|
const { headers, baseUrl } = getAuth();
|
|
@@ -1049,6 +1226,32 @@ if (command === 'login') {
|
|
|
1049
1226
|
}
|
|
1050
1227
|
await watchRun(baseUrl, headers, runId, { interval, jsonl });
|
|
1051
1228
|
|
|
1229
|
+
} else if (sub === 'events') {
|
|
1230
|
+
const runId = args[2];
|
|
1231
|
+
if (!runId) { console.error('Usage: makaron responses events <runId> [--jsonl] [--follow] [--interval <ms>] [--fail-on-unapproved]'); process.exit(1); }
|
|
1232
|
+
let interval = 5000, jsonl = false, follow = false, failOnUnapproved = false;
|
|
1233
|
+
for (let i = 3; i < args.length; i++) {
|
|
1234
|
+
if (args[i] === '--jsonl') jsonl = true;
|
|
1235
|
+
else if (args[i] === '--follow') follow = true;
|
|
1236
|
+
else if (args[i] === '--fail-on-unapproved') failOnUnapproved = true;
|
|
1237
|
+
else if (args[i] === '--interval' && args[i + 1]) interval = parseInt(args[++i]);
|
|
1238
|
+
}
|
|
1239
|
+
await printDialogueEvents(baseUrl, headers, runId, { interval, jsonl, follow, failOnUnapproved });
|
|
1240
|
+
|
|
1241
|
+
} else if (['approve', 'revise', 'ask-user', 'continue'].includes(sub)) {
|
|
1242
|
+
const messageId = args[2];
|
|
1243
|
+
if (!messageId) { console.error(`Usage: makaron responses ${sub} <messageId> --run <runId> [--note <text>]`); process.exit(1); }
|
|
1244
|
+
let runId = null, note = null;
|
|
1245
|
+
const noteParts = [];
|
|
1246
|
+
for (let i = 3; i < args.length; i++) {
|
|
1247
|
+
if (args[i] === '--run' && args[i + 1]) runId = args[++i];
|
|
1248
|
+
else if (args[i] === '--note' && args[i + 1]) note = args[++i];
|
|
1249
|
+
else noteParts.push(args[i]);
|
|
1250
|
+
}
|
|
1251
|
+
if (!runId) { console.error(`Usage: makaron responses ${sub} <messageId> --run <runId> [--note <text>]`); process.exit(1); }
|
|
1252
|
+
if (!note && noteParts.length) note = noteParts.join(' ');
|
|
1253
|
+
recordApproval(runId, messageId, sub === 'ask-user' ? 'ask_user' : sub, note);
|
|
1254
|
+
|
|
1052
1255
|
} else if (sub === 'list') {
|
|
1053
1256
|
let projectId = null;
|
|
1054
1257
|
for (let i = 2; i < args.length; i++) {
|
|
@@ -1072,6 +1275,11 @@ if (command === 'login') {
|
|
|
1072
1275
|
responses get <runId> Get status and output (JSON)
|
|
1073
1276
|
responses get <runId> --wait Poll until completed
|
|
1074
1277
|
responses get <runId> --pick <field> Extract: first_image_url, first_video_url, project_url, output
|
|
1278
|
+
responses events <runId> --jsonl Emit message/approval/artifact events for external Agents
|
|
1279
|
+
responses approve <messageId> --run <runId> Record approval for a Makaron message
|
|
1280
|
+
responses revise <messageId> --run <runId> Record revision request for a Makaron message
|
|
1281
|
+
responses ask-user <messageId> --run <runId> Record that the user must decide
|
|
1282
|
+
responses continue <messageId> --run <runId> Record continue decision
|
|
1075
1283
|
responses watch <runId> --jsonl Watch until done (incremental events)
|
|
1076
1284
|
responses list --project <id> List runs for a project
|
|
1077
1285
|
`);
|
|
@@ -1509,6 +1717,7 @@ Commands:
|
|
|
1509
1717
|
|
|
1510
1718
|
responses get <runId> Get run status and results
|
|
1511
1719
|
responses get <runId> --wait Poll until completed
|
|
1720
|
+
responses events <runId> --jsonl Emit message/approval/artifact events
|
|
1512
1721
|
responses list --project <id> List runs for a project
|
|
1513
1722
|
abort <runId> Abort a running Agent
|
|
1514
1723
|
|
package/package.json
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "makaron-cli",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.8",
|
|
4
4
|
"description": "Talk to Makaron Agent from the terminal — create projects, edit images, generate videos",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "node test/smoke.mjs",
|
|
8
|
+
"test:smoke": "node test/smoke.mjs",
|
|
9
|
+
"test:live": "MAKARON_LIVE=1 node test/live-smoke.mjs"
|
|
10
|
+
},
|
|
6
11
|
"bin": {
|
|
7
12
|
"makaron": "./bin/makaron.mjs"
|
|
8
13
|
},
|
package/skills/makaron/SKILL.md
CHANGED
|
@@ -50,14 +50,14 @@ Verify: `npx makaron-cli list` should show projects.
|
|
|
50
50
|
# One-shot: create project + upload image + submit prompt — all in one command
|
|
51
51
|
RUN_ID=$(npx makaron-cli chat --project auto --image photo.jpg -b "make it cinematic and create a 5s video")
|
|
52
52
|
|
|
53
|
-
#
|
|
54
|
-
npx makaron-cli responses
|
|
53
|
+
# Wait for the final customer-ready result
|
|
54
|
+
npx makaron-cli responses get $RUN_ID --wait --json
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
Or with an existing project:
|
|
58
58
|
```bash
|
|
59
59
|
RUN_ID=$(npx makaron-cli chat --project $PROJECT_ID -b "make a 5s video")
|
|
60
|
-
npx makaron-cli responses
|
|
60
|
+
npx makaron-cli responses get $RUN_ID --wait --json
|
|
61
61
|
```
|
|
62
62
|
|
|
63
63
|
## Primary: `chat` (Agent-driven creative work)
|
|
@@ -112,7 +112,7 @@ Use `chat --project <id|auto> --video ...` for any project/timeline video work.
|
|
|
112
112
|
npx makaron-cli responses get <runId> --json
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
-
###
|
|
115
|
+
### Advanced: stream incremental events
|
|
116
116
|
|
|
117
117
|
```bash
|
|
118
118
|
npx makaron-cli responses watch <runId> --jsonl
|
|
@@ -126,6 +126,37 @@ Outputs one JSON per line as artifacts appear:
|
|
|
126
126
|
{"event":"done","status":"completed"}
|
|
127
127
|
```
|
|
128
128
|
|
|
129
|
+
### Dialogue events for external Agents
|
|
130
|
+
|
|
131
|
+
Use this when another Agent needs to read what Makaron said, detect approval requirements, and relay artifacts without inventing customer-service wording.
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
npx makaron-cli responses events <runId> --jsonl
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Events use only three factual types:
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{"type":"message","id":"msg_1","runId":"run_xxx","seq":1,"text":"Makaron original message","requires_approval":true,"approval_options":["approve","revise","ask_user","continue"]}
|
|
141
|
+
{"type":"approval","messageId":"msg_1","choice":"approve","status":"recorded"}
|
|
142
|
+
{"type":"artifact","kind":"image","status":"completed","url":"https://..."}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
If a message has `requires_approval: true`, the external Agent must record an approval before continuing to wait for artifacts or claiming completion:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
npx makaron-cli responses approve msg_1 --run <runId> --note "Proceed."
|
|
149
|
+
npx makaron-cli responses revise msg_1 --run <runId> "make it softer"
|
|
150
|
+
npx makaron-cli responses ask-user msg_1 --run <runId>
|
|
151
|
+
npx makaron-cli responses continue msg_1 --run <runId>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Wrappers can enforce this with:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
npx makaron-cli responses events <runId> --jsonl --fail-on-unapproved
|
|
158
|
+
```
|
|
159
|
+
|
|
129
160
|
### Extract specific results
|
|
130
161
|
|
|
131
162
|
```bash
|
|
@@ -267,38 +298,33 @@ RUN_ID=$(npx makaron-cli chat --project auto --image photo.jpg -b "make it cinem
|
|
|
267
298
|
PROJECT_URL=$(npx makaron-cli responses get $RUN_ID --pick project_url)
|
|
268
299
|
send_message "Project created: $PROJECT_URL"
|
|
269
300
|
|
|
270
|
-
# 4.
|
|
271
|
-
npx makaron-cli responses
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
elif [ "$EVENT" = "output.updated" ] && [ "$TYPE" = "video" ] && [ "$STATUS" = "completed" ]; then
|
|
281
|
-
# Video ready — send as media
|
|
282
|
-
send_video "$URL"
|
|
283
|
-
elif [ "$EVENT" = "done" ]; then
|
|
284
|
-
send_message "All done!"
|
|
285
|
-
fi
|
|
301
|
+
# 4. Wait for the final customer-ready result
|
|
302
|
+
RESULT=$(npx makaron-cli responses get $RUN_ID --wait --json)
|
|
303
|
+
IMAGE_URLS=$(echo "$RESULT" | jq -r '[.result.images[]?.imageUrl, .output[]? | select(.type == "image") | .url] | map(select(. != null)) | unique | .[]')
|
|
304
|
+
VIDEO_URLS=$(echo "$RESULT" | jq -r '[.result.videos[]?.videoUrl, .output[]? | select(.type == "video") | .url] | map(select(. != null)) | unique | .[]')
|
|
305
|
+
|
|
306
|
+
for URL in $IMAGE_URLS; do
|
|
307
|
+
send_image "$URL"
|
|
308
|
+
done
|
|
309
|
+
for URL in $VIDEO_URLS; do
|
|
310
|
+
send_video "$URL"
|
|
286
311
|
done
|
|
312
|
+
send_message "All done!"
|
|
287
313
|
```
|
|
288
314
|
|
|
289
315
|
**Key principles for service agents:**
|
|
290
|
-
- **Proactive, not
|
|
316
|
+
- **Proactive, not silent**: Acknowledge immediately, send the project link early, then send the final customer-ready media when the run completes.
|
|
291
317
|
- **Media over links**: When possible, send images/videos as native media in the chat (download URL and upload as attachment), not just paste the URL.
|
|
292
318
|
- **Immediate acknowledgment**: Reply within 1 second of receiving user request. Don't make users wait for project creation.
|
|
293
319
|
- **Project link early**: Send the project URL right after creation so users can check anytime.
|
|
294
|
-
- **
|
|
320
|
+
- **Use `get --wait --json` as the default service path**: reserve `watch --jsonl` for advanced streaming or debugging integrations that explicitly need incremental events.
|
|
295
321
|
|
|
296
322
|
## Important Notes
|
|
297
323
|
|
|
298
324
|
- One project = one conversation thread. All history is preserved.
|
|
299
325
|
- One run at a time per project. New message interrupts previous run.
|
|
300
326
|
- Multi-image: `create --image a.jpg --image b.jpg` or `chat --image ref.jpg`.
|
|
301
|
-
- Videos take 2-5 minutes to render. Use `
|
|
327
|
+
- Videos take 2-5 minutes to render. Use `responses get <runId> --wait --json` for the default customer-service path.
|
|
302
328
|
- Music takes ~60 seconds. Appears in output when done.
|
|
303
329
|
- Images are typically ready in 15-30 seconds.
|
|
304
330
|
- stdout is always machine-readable JSON/text. Human-friendly logs go to stderr.
|