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 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
- # Watch until all artifacts are ready
65
- npx makaron-cli responses watch $RUN_ID --jsonl
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 watch $RUN_ID --jsonl
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
- ### Watch until done (streaming events)
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. Watch and send each artifact as it appears
282
- npx makaron-cli responses watch $RUN_ID --jsonl | while read -r line; do
283
- EVENT=$(echo "$line" | jq -r '.event')
284
- TYPE=$(echo "$line" | jq -r '.item.type // empty')
285
- URL=$(echo "$line" | jq -r '.item.url // empty')
286
- STATUS=$(echo "$line" | jq -r '.item.status // empty')
287
-
288
- if [ "$EVENT" = "output.added" ] && [ "$TYPE" = "image" ]; then
289
- # Send image immediately as media (not just link)
290
- send_image "$URL"
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 reactive**: Don't wait for the full run to finish. Send progress messages and artifacts as they appear.
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
- - **Stream artifacts**: Use `watch --jsonl` to push each artifact the moment it's ready. An image at 15s should reach the user at 15s, not after the video finishes at 5 minutes.
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 `watch` to get URL when ready.
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
- # Watch until all artifacts are ready
54
- npx makaron-cli responses watch $RUN_ID --jsonl
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 watch $RUN_ID --jsonl
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
- ### Watch until done (streaming events)
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. Watch and send each artifact as it appears
271
- npx makaron-cli responses watch $RUN_ID --jsonl | while read -r line; do
272
- EVENT=$(echo "$line" | jq -r '.event')
273
- TYPE=$(echo "$line" | jq -r '.item.type // empty')
274
- URL=$(echo "$line" | jq -r '.item.url // empty')
275
- STATUS=$(echo "$line" | jq -r '.item.status // empty')
276
-
277
- if [ "$EVENT" = "output.added" ] && [ "$TYPE" = "image" ]; then
278
- # Send image immediately as media (not just link)
279
- send_image "$URL"
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 reactive**: Don't wait for the full run to finish. Send progress messages and artifacts as they appear.
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
- - **Stream artifacts**: Use `watch --jsonl` to push each artifact the moment it's ready. An image at 15s should reach the user at 15s, not after the video finishes at 5 minutes.
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 `watch` to get URL when ready.
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 === 'login') {
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.6",
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
  },
@@ -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
- # Watch until all artifacts are ready
54
- npx makaron-cli responses watch $RUN_ID --jsonl
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 watch $RUN_ID --jsonl
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
- ### Watch until done (streaming events)
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. Watch and send each artifact as it appears
271
- npx makaron-cli responses watch $RUN_ID --jsonl | while read -r line; do
272
- EVENT=$(echo "$line" | jq -r '.event')
273
- TYPE=$(echo "$line" | jq -r '.item.type // empty')
274
- URL=$(echo "$line" | jq -r '.item.url // empty')
275
- STATUS=$(echo "$line" | jq -r '.item.status // empty')
276
-
277
- if [ "$EVENT" = "output.added" ] && [ "$TYPE" = "image" ]; then
278
- # Send image immediately as media (not just link)
279
- send_image "$URL"
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 reactive**: Don't wait for the full run to finish. Send progress messages and artifacts as they appear.
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
- - **Stream artifacts**: Use `watch --jsonl` to push each artifact the moment it's ready. An image at 15s should reach the user at 15s, not after the video finishes at 5 minutes.
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 `watch` to get URL when ready.
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.