makaron-cli 0.7.8 → 0.7.10

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
@@ -136,10 +136,13 @@ Outputs one JSON per line as artifacts appear:
136
136
 
137
137
  ### Dialogue events for external Agents
138
138
 
139
- Use this when another Agent needs to read what Makaron said, detect approval requirements, and relay artifacts without inventing customer-service wording.
139
+ Use this when another Agent needs to read what Makaron said, handle text checkpoints, and relay artifacts without inventing customer-service wording.
140
140
 
141
141
  ```bash
142
142
  npx makaron-cli responses events <runId> --jsonl
143
+ # alias: npx makaron-cli responses timeline <runId> --jsonl
144
+ # compact view: npx makaron-cli responses timeline <runId> --jsonl --compact
145
+ # pure Q&A view: npx makaron-cli responses timeline <runId> --jsonl --checkpoint-mode off
143
146
  ```
144
147
 
145
148
  Events use only three factual types:
@@ -150,7 +153,7 @@ Events use only three factual types:
150
153
  {"type":"artifact","kind":"image","status":"completed","url":"https://..."}
151
154
  ```
152
155
 
153
- If a message has `requires_approval: true`, the external Agent must record an approval before continuing to wait for artifacts or claiming completion:
156
+ Makaron uses a conservative checkpoint rule for creative/service Agents: if a run stops with substantive text, has no image/video/music/design artifact, and has no continuing execution action such as a tool call, the message is marked `requires_approval: true`. Pure status text such as queued, rendering, uploading, or completed is not treated as a checkpoint. Pure Q&A flows should pass `--checkpoint-mode off`. If a message has `requires_approval: true`, the external Agent must record an approval before continuing to wait for artifacts or claiming completion:
154
157
 
155
158
  ```bash
156
159
  npx makaron-cli responses approve msg_1 --run <runId> --note "Proceed."
@@ -159,12 +162,15 @@ npx makaron-cli responses ask-user msg_1 --run <runId>
159
162
  npx makaron-cli responses continue msg_1 --run <runId>
160
163
  ```
161
164
 
162
- Wrappers can enforce this with:
165
+ Important: approvals are a local Agent gate in v0. They are recorded by the CLI so wrappers and Skills can fail fast, but they do not pause or resume the remote Makaron runtime yet. Wrappers can enforce the local gate with:
163
166
 
164
167
  ```bash
165
- npx makaron-cli responses events <runId> --jsonl --fail-on-unapproved
168
+ npx makaron-cli responses events <runId> --jsonl --checkpoint-mode service --fail-on-unapproved
169
+ # alias: npx makaron-cli responses timeline <runId> --jsonl --compact --fail-on-unapproved
166
170
  ```
167
171
 
172
+ Use `--compact` when relaying to another Agent or chat system; it merges consecutive Makaron content chunks into a single readable message.
173
+
168
174
  ### Extract specific results
169
175
 
170
176
  ```bash
@@ -294,6 +300,33 @@ type MakaronOutput =
294
300
  | Motion design | "create an Instagram story with animated text" |
295
301
  | Multi-step | "edit the photo then make a video from it" |
296
302
 
303
+ ## Minimal Agent Wrapper
304
+
305
+ A new Agent can use this minimal flow:
306
+
307
+ ```bash
308
+ RUN_JSON=$(npx makaron-cli chat --project auto --json -b "$USER_PROMPT")
309
+ RUN_ID=$(echo "$RUN_JSON" | jq -r .runId)
310
+ PROJECT_URL=$(echo "$RUN_JSON" | jq -r .projectUrl)
311
+ send_message "Project created: $PROJECT_URL"
312
+
313
+ if ! npx makaron-cli responses timeline "$RUN_ID" --jsonl --compact --checkpoint-mode service --fail-on-unapproved > /tmp/makaron-events.jsonl; then
314
+ npx makaron-cli responses timeline "$RUN_ID" --jsonl --compact | while read -r event; do
315
+ TYPE=$(echo "$event" | jq -r .type)
316
+ REQUIRES=$(echo "$event" | jq -r ".requires_approval // false")
317
+ TEXT=$(echo "$event" | jq -r ".text // empty")
318
+ MSG_ID=$(echo "$event" | jq -r ".id // empty")
319
+ if [ "$TYPE" = "message" ] && [ "$REQUIRES" = "true" ]; then
320
+ send_message "$TEXT"
321
+ npx makaron-cli responses ask-user "$MSG_ID" --run "$RUN_ID"
322
+ exit 3
323
+ fi
324
+ done
325
+ fi
326
+
327
+ RESULT=$(npx makaron-cli responses get "$RUN_ID" --wait --json)
328
+ ```
329
+
297
330
  ## Recommended Pattern: Service Flow (Feishu/OpenClaw/Group Chat)
298
331
 
299
332
  When serving end-users in a chat environment (Feishu, Slack, Discord), use this proactive message pattern:
package/SKILL.md CHANGED
@@ -128,10 +128,13 @@ Outputs one JSON per line as artifacts appear:
128
128
 
129
129
  ### Dialogue events for external Agents
130
130
 
131
- Use this when another Agent needs to read what Makaron said, detect approval requirements, and relay artifacts without inventing customer-service wording.
131
+ Use this when another Agent needs to read what Makaron said, handle text checkpoints, and relay artifacts without inventing customer-service wording.
132
132
 
133
133
  ```bash
134
134
  npx makaron-cli responses events <runId> --jsonl
135
+ # alias: npx makaron-cli responses timeline <runId> --jsonl
136
+ # compact view: npx makaron-cli responses timeline <runId> --jsonl --compact
137
+ # pure Q&A view: npx makaron-cli responses timeline <runId> --jsonl --checkpoint-mode off
135
138
  ```
136
139
 
137
140
  Events use only three factual types:
@@ -142,7 +145,7 @@ Events use only three factual types:
142
145
  {"type":"artifact","kind":"image","status":"completed","url":"https://..."}
143
146
  ```
144
147
 
145
- If a message has `requires_approval: true`, the external Agent must record an approval before continuing to wait for artifacts or claiming completion:
148
+ Makaron uses a conservative checkpoint rule for creative/service Agents: if a run stops with substantive text, has no image/video/music/design artifact, and has no continuing execution action such as a tool call, the message is marked `requires_approval: true`. Pure status text such as queued, rendering, uploading, or completed is not treated as a checkpoint. Pure Q&A flows should pass `--checkpoint-mode off`. If a message has `requires_approval: true`, the external Agent must record an approval before continuing to wait for artifacts or claiming completion:
146
149
 
147
150
  ```bash
148
151
  npx makaron-cli responses approve msg_1 --run <runId> --note "Proceed."
@@ -151,12 +154,15 @@ npx makaron-cli responses ask-user msg_1 --run <runId>
151
154
  npx makaron-cli responses continue msg_1 --run <runId>
152
155
  ```
153
156
 
154
- Wrappers can enforce this with:
157
+ Important: approvals are a local Agent gate in v0. They are recorded by the CLI so wrappers and Skills can fail fast, but they do not pause or resume the remote Makaron runtime yet. Wrappers can enforce the local gate with:
155
158
 
156
159
  ```bash
157
- npx makaron-cli responses events <runId> --jsonl --fail-on-unapproved
160
+ npx makaron-cli responses events <runId> --jsonl --checkpoint-mode service --fail-on-unapproved
161
+ # alias: npx makaron-cli responses timeline <runId> --jsonl --compact --fail-on-unapproved
158
162
  ```
159
163
 
164
+ Use `--compact` when relaying to another Agent or chat system; it merges consecutive Makaron content chunks into a single readable message.
165
+
160
166
  ### Extract specific results
161
167
 
162
168
  ```bash
@@ -283,6 +289,33 @@ type MakaronOutput =
283
289
  | Motion design | "create an Instagram story with animated text" |
284
290
  | Multi-step | "edit the photo then make a video from it" |
285
291
 
292
+ ## Minimal Agent Wrapper
293
+
294
+ A new Agent can use this minimal flow:
295
+
296
+ ```bash
297
+ RUN_JSON=$(npx makaron-cli chat --project auto --json -b "$USER_PROMPT")
298
+ RUN_ID=$(echo "$RUN_JSON" | jq -r .runId)
299
+ PROJECT_URL=$(echo "$RUN_JSON" | jq -r .projectUrl)
300
+ send_message "Project created: $PROJECT_URL"
301
+
302
+ if ! npx makaron-cli responses timeline "$RUN_ID" --jsonl --compact --checkpoint-mode service --fail-on-unapproved > /tmp/makaron-events.jsonl; then
303
+ npx makaron-cli responses timeline "$RUN_ID" --jsonl --compact | while read -r event; do
304
+ TYPE=$(echo "$event" | jq -r .type)
305
+ REQUIRES=$(echo "$event" | jq -r ".requires_approval // false")
306
+ TEXT=$(echo "$event" | jq -r ".text // empty")
307
+ MSG_ID=$(echo "$event" | jq -r ".id // empty")
308
+ if [ "$TYPE" = "message" ] && [ "$REQUIRES" = "true" ]; then
309
+ send_message "$TEXT"
310
+ npx makaron-cli responses ask-user "$MSG_ID" --run "$RUN_ID"
311
+ exit 3
312
+ fi
313
+ done
314
+ fi
315
+
316
+ RESULT=$(npx makaron-cli responses get "$RUN_ID" --wait --json)
317
+ ```
318
+
286
319
  ## Recommended Pattern: Service Flow (Feishu/OpenClaw/Group Chat)
287
320
 
288
321
  When serving end-users in a chat environment (Feishu, Slack, Discord), use this proactive message pattern:
package/bin/makaron.mjs CHANGED
@@ -447,11 +447,61 @@ function stableEventId(prefix, runId, seq, fallback) {
447
447
  return fallback || `${prefix}_${runId}_${seq ?? Date.now()}`;
448
448
  }
449
449
 
450
- function normalizeDialogueMessage(runId, projectId, ev) {
450
+ function inferApprovalIntent(text) {
451
+ if (!text) return false;
452
+ const normalized = text.toLowerCase().replace(/\s+/g, ' ').trim();
453
+ const hasEnglishApprovalIntent = /\b(shall i|should i|do you want me to|confirm|approve|approval|permission)\b/.test(normalized);
454
+ const hasEnglishAction = /\b(go ahead|proceed|continue|generate|create|submit|start|run|render)\b/.test(normalized);
455
+ const hasChineseApprovalIntent = /(是否|要不要|是否要|需要我|请确认|确认|同意|批准|可以吗|是否可以)/.test(normalized);
456
+ const hasChineseAction = /(继续|开始|生成|创建|提交|执行|渲染|出图|出视频|制作)/.test(normalized);
457
+ return (hasEnglishApprovalIntent && hasEnglishAction) || (hasChineseApprovalIntent && hasChineseAction);
458
+ }
459
+
460
+ function isPureStatusMessage(text, sourceType, status) {
461
+ const normalized = String(text || '').toLowerCase().replace(/\s+/g, ' ').trim();
462
+ if (sourceType === 'status') return true;
463
+ if (status && !normalized) return true;
464
+ if (!normalized) return false;
465
+ return /^(queued|running|rendering|uploading|processing|generating|completed|complete|done|failed|aborted|started|submitted|waiting|polling)(\.|…|\.\.\.)?$/.test(normalized)
466
+ || /^(排队中|队列中|运行中|渲染中|上传中|处理中|生成中|已完成|完成|失败|已失败|已提交|等待中)$/.test(normalized);
467
+ }
468
+
469
+ function hasExplicitApprovalRequirement(data, text) {
470
+ return Boolean(
471
+ data.requires_approval
472
+ || data.requiresApproval
473
+ || data.action_required
474
+ || data.actionRequired
475
+ || data.proposal
476
+ || inferApprovalIntent(text)
477
+ );
478
+ }
479
+
480
+ function isPureQaRun(data) {
481
+ const raw = [
482
+ data.intent,
483
+ data.mode,
484
+ data.workflow,
485
+ data.workflow_type,
486
+ data.workflowType,
487
+ data.run_type,
488
+ data.runType,
489
+ data.kind,
490
+ ].filter(Boolean).join(' ').toLowerCase();
491
+ return /\b(qa|q&a|question_answer|question-answer|answer|pure_qa|pure-qa)\b/.test(raw);
492
+ }
493
+
494
+ function normalizeDialogueMessage(runId, projectId, ev, context = {}) {
451
495
  const data = ev.data || ev;
452
496
  const text = data.text || data.message || data.content || data.statusText || '';
453
497
  if (!text && ev.type !== 'tool_call') return null;
454
- const requiresApproval = Boolean(data.requires_approval || data.requiresApproval || data.action_required || data.actionRequired);
498
+ const explicitApproval = hasExplicitApprovalRequirement(data, text);
499
+ const textOnlyCheckpoint = Boolean(
500
+ context.stoppedWithoutArtifacts
501
+ && !explicitApproval
502
+ && !isPureStatusMessage(text, ev.type, data.status)
503
+ );
504
+ const requiresApproval = explicitApproval || textOnlyCheckpoint;
455
505
  const message = {
456
506
  type: 'message',
457
507
  id: stableEventId('msg', runId, ev.seq, data.id || ev.id),
@@ -465,6 +515,7 @@ function normalizeDialogueMessage(runId, projectId, ev) {
465
515
  if (requiresApproval) {
466
516
  message.requires_approval = true;
467
517
  message.approval_options = data.approval_options || data.approvalOptions || ['approve', 'revise', 'ask_user', 'continue'];
518
+ if (textOnlyCheckpoint) message.approval_reason = 'text_only_checkpoint';
468
519
  }
469
520
  if (data.proposal) message.proposal = data.proposal;
470
521
  return message;
@@ -474,6 +525,7 @@ function normalizeDialogueArtifact(runId, projectId, item, seq) {
474
525
  if (!item) return null;
475
526
  const kind = item.type || item.kind || (item.imageUrl ? 'image' : item.videoUrl ? 'video' : undefined);
476
527
  if (!kind) return null;
528
+ if (!['image', 'video', 'design', 'music', 'audio', 'file'].includes(kind)) return null;
477
529
  const artifact = {
478
530
  type: 'artifact',
479
531
  id: stableEventId('artifact', runId, seq, item.id || item.snapshotId || item.taskId),
@@ -491,7 +543,7 @@ function normalizeDialogueArtifact(runId, projectId, item, seq) {
491
543
  return artifact;
492
544
  }
493
545
 
494
- function normalizeDialogueEvent(runId, projectId, ev) {
546
+ function normalizeDialogueEvent(runId, projectId, ev, context = {}) {
495
547
  const data = ev.data || {};
496
548
  if (ev.type === 'message' || ev.type === 'approval' || ev.type === 'artifact') {
497
549
  return { ...data, ...ev, runId: ev.runId || runId, projectId: ev.projectId || projectId };
@@ -501,7 +553,7 @@ function normalizeDialogueEvent(runId, projectId, ev) {
501
553
  case 'status':
502
554
  case 'tool_call':
503
555
  case 'error':
504
- return normalizeDialogueMessage(runId, projectId, ev);
556
+ return normalizeDialogueMessage(runId, projectId, ev, context);
505
557
  case 'image':
506
558
  return normalizeDialogueArtifact(runId, projectId, { type: 'image', status: data.imageUrl ? 'completed' : 'running', imageUrl: data.imageUrl, snapshotId: data.snapshotId }, ev.seq);
507
559
  case 'render':
@@ -519,8 +571,22 @@ function normalizeDialogueEvent(runId, projectId, ev) {
519
571
  function buildDialogueEvents(runId, data) {
520
572
  const projectId = data.projectId || data.project_id;
521
573
  const events = [];
574
+ const hasArtifacts = Boolean(
575
+ (data.output || []).some(item => normalizeDialogueArtifact(runId, projectId, item, item.seq))
576
+ || (data.events || []).some(ev => normalizeDialogueEvent(runId, projectId, ev, { stoppedWithoutArtifacts: false })?.type === 'artifact')
577
+ );
578
+ const hasContinuationAction = (data.events || []).some(ev => ['tool_call', 'image', 'render', 'animation_task', 'video_snapshot', 'music_task'].includes(ev.type));
579
+ const checkpointMode = data.checkpointMode || 'service';
580
+ const context = {
581
+ stoppedWithoutArtifacts: checkpointMode !== 'off'
582
+ && !isPureQaRun(data)
583
+ && !hasArtifacts
584
+ && !hasContinuationAction
585
+ && !data.incomplete
586
+ && ['completed', 'failed', 'aborted', 'waiting', 'needs_input'].includes(data.status),
587
+ };
522
588
  for (const ev of data.events || []) {
523
- const normalized = normalizeDialogueEvent(runId, projectId, ev);
589
+ const normalized = normalizeDialogueEvent(runId, projectId, ev, context);
524
590
  if (normalized) events.push(normalized);
525
591
  }
526
592
  for (const item of data.output || []) {
@@ -533,6 +599,30 @@ function buildDialogueEvents(runId, data) {
533
599
  return events;
534
600
  }
535
601
 
602
+ function compactDialogueEvents(events) {
603
+ const compacted = [];
604
+ for (const ev of events) {
605
+ const prev = compacted[compacted.length - 1];
606
+ const canMerge = prev
607
+ && ev.type === 'message'
608
+ && prev.type === 'message'
609
+ && ev.source_type === 'content'
610
+ && prev.source_type === 'content'
611
+ && !ev.requires_approval
612
+ && !prev.requires_approval
613
+ && !ev.proposal
614
+ && !prev.proposal;
615
+ if (canMerge) {
616
+ prev.text = `${prev.text}${ev.text}`;
617
+ prev.id = `${prev.id}+${ev.id}`;
618
+ prev.seq_end = ev.seq;
619
+ } else {
620
+ compacted.push({ ...ev });
621
+ }
622
+ }
623
+ return compacted;
624
+ }
625
+
536
626
  function findUnhandledApprovalMessages(events) {
537
627
  const approved = new Set(events.filter(ev => ev.type === 'approval').map(ev => ev.messageId || ev.message_id));
538
628
  return events.filter(ev => ev.type === 'message' && ev.requires_approval && !approved.has(ev.id));
@@ -548,12 +638,13 @@ async function fetchRun(baseUrl, headers, runId, opts = {}) {
548
638
  }
549
639
 
550
640
  async function printDialogueEvents(baseUrl, headers, runId, opts = {}) {
551
- const { jsonl = false, failOnUnapproved = false, follow = false, interval = 5000 } = opts;
641
+ const { jsonl = false, failOnUnapproved = false, follow = false, interval = 5000, compact = false, checkpointMode = 'service' } = opts;
552
642
  const printed = new Set();
553
643
 
554
644
  while (true) {
555
645
  const data = await fetchRun(baseUrl, headers, runId, { events: true });
556
- const events = buildDialogueEvents(runId, data);
646
+ const dialogueEvents = buildDialogueEvents(runId, { ...data, checkpointMode });
647
+ const events = compact ? compactDialogueEvents(dialogueEvents) : dialogueEvents;
557
648
  const unhandled = findUnhandledApprovalMessages(events);
558
649
  if (failOnUnapproved && unhandled.length) {
559
650
  process.stderr.write(`Unhandled Makaron message requires approval: ${unhandled.map(ev => ev.id).join(', ')}\n`);
@@ -1226,17 +1317,23 @@ if (command === '--version' || command === '-v' || command === 'version') {
1226
1317
  }
1227
1318
  await watchRun(baseUrl, headers, runId, { interval, jsonl });
1228
1319
 
1229
- } else if (sub === 'events') {
1320
+ } else if (sub === 'events' || sub === 'timeline') {
1230
1321
  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;
1322
+ if (!runId) { console.error(`Usage: makaron responses ${sub} <runId> [--jsonl] [--compact] [--checkpoint-mode service|off] [--follow] [--interval <ms>] [--fail-on-unapproved]`); process.exit(1); }
1323
+ let interval = 5000, jsonl = false, follow = false, failOnUnapproved = false, compact = false, checkpointMode = 'service';
1233
1324
  for (let i = 3; i < args.length; i++) {
1234
1325
  if (args[i] === '--jsonl') jsonl = true;
1326
+ else if (args[i] === '--compact') compact = true;
1327
+ else if (args[i] === '--checkpoint-mode' && args[i + 1]) checkpointMode = args[++i];
1235
1328
  else if (args[i] === '--follow') follow = true;
1236
1329
  else if (args[i] === '--fail-on-unapproved') failOnUnapproved = true;
1237
1330
  else if (args[i] === '--interval' && args[i + 1]) interval = parseInt(args[++i]);
1238
1331
  }
1239
- await printDialogueEvents(baseUrl, headers, runId, { interval, jsonl, follow, failOnUnapproved });
1332
+ if (!['service', 'off'].includes(checkpointMode)) {
1333
+ console.error('--checkpoint-mode must be service or off');
1334
+ process.exit(1);
1335
+ }
1336
+ await printDialogueEvents(baseUrl, headers, runId, { interval, jsonl, follow, failOnUnapproved, compact, checkpointMode });
1240
1337
 
1241
1338
  } else if (['approve', 'revise', 'ask-user', 'continue'].includes(sub)) {
1242
1339
  const messageId = args[2];
@@ -1276,6 +1373,8 @@ if (command === '--version' || command === '-v' || command === 'version') {
1276
1373
  responses get <runId> --wait Poll until completed
1277
1374
  responses get <runId> --pick <field> Extract: first_image_url, first_video_url, project_url, output
1278
1375
  responses events <runId> --jsonl Emit message/approval/artifact events for external Agents
1376
+ responses timeline <runId> --jsonl Alias for responses events
1377
+ responses timeline <runId> --checkpoint-mode off Disable text-only checkpoints for pure Q&A
1279
1378
  responses approve <messageId> --run <runId> Record approval for a Makaron message
1280
1379
  responses revise <messageId> --run <runId> Record revision request for a Makaron message
1281
1380
  responses ask-user <messageId> --run <runId> Record that the user must decide
@@ -1718,6 +1817,7 @@ Commands:
1718
1817
  responses get <runId> Get run status and results
1719
1818
  responses get <runId> --wait Poll until completed
1720
1819
  responses events <runId> --jsonl Emit message/approval/artifact events
1820
+ responses timeline <runId> --checkpoint-mode off Disable text-only checkpoints for pure Q&A
1721
1821
  responses list --project <id> List runs for a project
1722
1822
  abort <runId> Abort a running Agent
1723
1823
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "makaron-cli",
3
- "version": "0.7.8",
3
+ "version": "0.7.10",
4
4
  "description": "Talk to Makaron Agent from the terminal — create projects, edit images, generate videos",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -128,10 +128,13 @@ Outputs one JSON per line as artifacts appear:
128
128
 
129
129
  ### Dialogue events for external Agents
130
130
 
131
- Use this when another Agent needs to read what Makaron said, detect approval requirements, and relay artifacts without inventing customer-service wording.
131
+ Use this when another Agent needs to read what Makaron said, handle text checkpoints, and relay artifacts without inventing customer-service wording.
132
132
 
133
133
  ```bash
134
134
  npx makaron-cli responses events <runId> --jsonl
135
+ # alias: npx makaron-cli responses timeline <runId> --jsonl
136
+ # compact view: npx makaron-cli responses timeline <runId> --jsonl --compact
137
+ # pure Q&A view: npx makaron-cli responses timeline <runId> --jsonl --checkpoint-mode off
135
138
  ```
136
139
 
137
140
  Events use only three factual types:
@@ -142,7 +145,7 @@ Events use only three factual types:
142
145
  {"type":"artifact","kind":"image","status":"completed","url":"https://..."}
143
146
  ```
144
147
 
145
- If a message has `requires_approval: true`, the external Agent must record an approval before continuing to wait for artifacts or claiming completion:
148
+ Makaron uses a conservative checkpoint rule for creative/service Agents: if a run stops with substantive text, has no image/video/music/design artifact, and has no continuing execution action such as a tool call, the message is marked `requires_approval: true`. Pure status text such as queued, rendering, uploading, or completed is not treated as a checkpoint. Pure Q&A flows should pass `--checkpoint-mode off`. If a message has `requires_approval: true`, the external Agent must record an approval before continuing to wait for artifacts or claiming completion:
146
149
 
147
150
  ```bash
148
151
  npx makaron-cli responses approve msg_1 --run <runId> --note "Proceed."
@@ -151,12 +154,15 @@ npx makaron-cli responses ask-user msg_1 --run <runId>
151
154
  npx makaron-cli responses continue msg_1 --run <runId>
152
155
  ```
153
156
 
154
- Wrappers can enforce this with:
157
+ Important: approvals are a local Agent gate in v0. They are recorded by the CLI so wrappers and Skills can fail fast, but they do not pause or resume the remote Makaron runtime yet. Wrappers can enforce the local gate with:
155
158
 
156
159
  ```bash
157
- npx makaron-cli responses events <runId> --jsonl --fail-on-unapproved
160
+ npx makaron-cli responses events <runId> --jsonl --checkpoint-mode service --fail-on-unapproved
161
+ # alias: npx makaron-cli responses timeline <runId> --jsonl --compact --fail-on-unapproved
158
162
  ```
159
163
 
164
+ Use `--compact` when relaying to another Agent or chat system; it merges consecutive Makaron content chunks into a single readable message.
165
+
160
166
  ### Extract specific results
161
167
 
162
168
  ```bash
@@ -283,6 +289,33 @@ type MakaronOutput =
283
289
  | Motion design | "create an Instagram story with animated text" |
284
290
  | Multi-step | "edit the photo then make a video from it" |
285
291
 
292
+ ## Minimal Agent Wrapper
293
+
294
+ A new Agent can use this minimal flow:
295
+
296
+ ```bash
297
+ RUN_JSON=$(npx makaron-cli chat --project auto --json -b "$USER_PROMPT")
298
+ RUN_ID=$(echo "$RUN_JSON" | jq -r .runId)
299
+ PROJECT_URL=$(echo "$RUN_JSON" | jq -r .projectUrl)
300
+ send_message "Project created: $PROJECT_URL"
301
+
302
+ if ! npx makaron-cli responses timeline "$RUN_ID" --jsonl --compact --checkpoint-mode service --fail-on-unapproved > /tmp/makaron-events.jsonl; then
303
+ npx makaron-cli responses timeline "$RUN_ID" --jsonl --compact | while read -r event; do
304
+ TYPE=$(echo "$event" | jq -r .type)
305
+ REQUIRES=$(echo "$event" | jq -r ".requires_approval // false")
306
+ TEXT=$(echo "$event" | jq -r ".text // empty")
307
+ MSG_ID=$(echo "$event" | jq -r ".id // empty")
308
+ if [ "$TYPE" = "message" ] && [ "$REQUIRES" = "true" ]; then
309
+ send_message "$TEXT"
310
+ npx makaron-cli responses ask-user "$MSG_ID" --run "$RUN_ID"
311
+ exit 3
312
+ fi
313
+ done
314
+ fi
315
+
316
+ RESULT=$(npx makaron-cli responses get "$RUN_ID" --wait --json)
317
+ ```
318
+
286
319
  ## Recommended Pattern: Service Flow (Feishu/OpenClaw/Group Chat)
287
320
 
288
321
  When serving end-users in a chat environment (Feishu, Slack, Discord), use this proactive message pattern: