makaron-cli 0.7.7 → 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
@@ -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
package/SKILL.md CHANGED
@@ -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
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;
@@ -62,6 +63,20 @@ function saveAuth(data) {
62
63
  fs.writeFileSync(AUTH_FILE, JSON.stringify(data, null, 2));
63
64
  }
64
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
+
65
80
  function buildCookie(tokenJson) {
66
81
  const url = tokenJson._supabaseUrl || SUPABASE_URL;
67
82
  const ref = url.match(/\/\/([^.]+)\./)?.[1] || '';
@@ -426,6 +441,157 @@ function applyPick(data, field) {
426
441
  }
427
442
  }
428
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
+
429
595
  // ─── Watch (incremental event stream) ───────────────────────────────────────
430
596
 
431
597
  async function watchRun(baseUrl, headers, runId, opts = {}) {
@@ -1060,6 +1226,32 @@ if (command === '--version' || command === '-v' || command === 'version') {
1060
1226
  }
1061
1227
  await watchRun(baseUrl, headers, runId, { interval, jsonl });
1062
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
+
1063
1255
  } else if (sub === 'list') {
1064
1256
  let projectId = null;
1065
1257
  for (let i = 2; i < args.length; i++) {
@@ -1083,6 +1275,11 @@ if (command === '--version' || command === '-v' || command === 'version') {
1083
1275
  responses get <runId> Get status and output (JSON)
1084
1276
  responses get <runId> --wait Poll until completed
1085
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
1086
1283
  responses watch <runId> --jsonl Watch until done (incremental events)
1087
1284
  responses list --project <id> List runs for a project
1088
1285
  `);
@@ -1520,6 +1717,7 @@ Commands:
1520
1717
 
1521
1718
  responses get <runId> Get run status and results
1522
1719
  responses get <runId> --wait Poll until completed
1720
+ responses events <runId> --jsonl Emit message/approval/artifact events
1523
1721
  responses list --project <id> List runs for a project
1524
1722
  abort <runId> Abort a running Agent
1525
1723
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "makaron-cli",
3
- "version": "0.7.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
6
  "scripts": {
@@ -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