makaron-cli 0.7.10 → 0.8.1

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,24 @@ Outputs one JSON per line as artifacts appear:
134
134
  {"event":"done","status":"completed"}
135
135
  ```
136
136
 
137
+ ### New Agent quickstart
138
+
139
+ For a new Agent, keep the default service flow to two response commands:
140
+
141
+ ```bash
142
+ npx makaron-cli responses next <runId> --json
143
+ npx makaron-cli responses handle <messageId> --run <runId> --choice approve
144
+ npx makaron-cli responses deliver <artifactId> --run <runId> --channel feishu --message-id <messageId>
145
+ ```
146
+
147
+ `responses next` emits a compact timeline, detects text-only checkpoints for creative/service work, and returns `next_commands` when the Agent must handle a Makaron message before waiting for artifacts. It exits with code `3` on an unhandled checkpoint so wrappers stop instead of silently waiting. After handling a checkpoint, run the returned `next_commands.inspect` command to continue. While the run is still generating, it returns `status: "running"` and `blocking: false`. If completed image/video artifacts exist and have not been delivered, it returns `status: "has_artifacts"` and `blocking: true` with compact `undelivered_artifacts` entries containing `kind`, `status`, `url`, `fileName`, and `contentType` when available, plus `next_commands.deliver`; the Agent must send the artifact URL to the user, then record delivery with `responses deliver`. A non-checkpoint response returns `status: "ready"` or `status: "delivered"`. If a run failed, `responses next --json` prints `status: "failed"` and `blocking: true` plus `error.type`, `error.message`, `error.recoverable`, `error.detail`, and `next_command` to stdout before exiting `1`, so wrappers can parse the failure without scraping stderr. Use `--no-fail` to inspect checkpoint JSON without failing. For pure Q&A runs, use:
148
+
149
+ ```bash
150
+ npx makaron-cli responses next <runId> --json --checkpoint-mode off
151
+ ```
152
+
153
+ `responses handle` is the single checkpoint action command. Valid choices are `approve`, `revise`, `ask_user`, and `continue`. `responses deliver` records that a completed artifact was actually delivered to the user, preventing repeated delivery prompts.
154
+
137
155
  ### Dialogue events for external Agents
138
156
 
139
157
  Use this when another Agent needs to read what Makaron said, handle text checkpoints, and relay artifacts without inventing customer-service wording.
@@ -145,6 +163,8 @@ npx makaron-cli responses events <runId> --jsonl
145
163
  # pure Q&A view: npx makaron-cli responses timeline <runId> --jsonl --checkpoint-mode off
146
164
  ```
147
165
 
166
+ `events` and `timeline` are lower-level commands. New Agents should start with `responses next` and use these only when they need raw event streams.
167
+
148
168
  Events use only three factual types:
149
169
 
150
170
  ```json
@@ -160,6 +180,7 @@ npx makaron-cli responses approve msg_1 --run <runId> --note "Proceed."
160
180
  npx makaron-cli responses revise msg_1 --run <runId> "make it softer"
161
181
  npx makaron-cli responses ask-user msg_1 --run <runId>
162
182
  npx makaron-cli responses continue msg_1 --run <runId>
183
+ npx makaron-cli responses handle msg_1 --run <runId> --choice approve
163
184
  ```
164
185
 
165
186
  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:
@@ -310,18 +331,26 @@ RUN_ID=$(echo "$RUN_JSON" | jq -r .runId)
310
331
  PROJECT_URL=$(echo "$RUN_JSON" | jq -r .projectUrl)
311
332
  send_message "Project created: $PROJECT_URL"
312
333
 
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
334
+ if ! NEXT=$(npx makaron-cli responses next "$RUN_ID" --json); then
335
+ STATUS=$(echo "$NEXT" | jq -r .status)
336
+ if [ "$STATUS" = "needs_approval" ]; then
337
+ MSG_ID=$(echo "$NEXT" | jq -r .checkpoint.id)
338
+ TEXT=$(echo "$NEXT" | jq -r .checkpoint.text)
339
+ send_message "$TEXT"
340
+ npx makaron-cli responses handle "$MSG_ID" --run "$RUN_ID" --choice ask_user
341
+ exit 3
342
+ elif [ "$STATUS" = "failed" ]; then
343
+ send_message "$(echo "$NEXT" | jq -r .error.message)"
344
+ exit 1
345
+ fi
346
+ fi
347
+
348
+ NEXT=$(npx makaron-cli responses next "$RUN_ID" --json --no-fail)
349
+ if [ "$(echo "$NEXT" | jq -r .status)" = "has_artifacts" ]; then
350
+ ARTIFACT_ID=$(echo "$NEXT" | jq -r ".undelivered_artifacts[0].id")
351
+ ARTIFACT_URL=$(echo "$NEXT" | jq -r ".undelivered_artifacts[0].url")
352
+ DELIVERY_MESSAGE_ID=$(send_message "$ARTIFACT_URL")
353
+ npx makaron-cli responses deliver "$ARTIFACT_ID" --run "$RUN_ID" --channel feishu --message-id "$DELIVERY_MESSAGE_ID"
325
354
  fi
326
355
 
327
356
  RESULT=$(npx makaron-cli responses get "$RUN_ID" --wait --json)
package/SKILL.md CHANGED
@@ -126,6 +126,24 @@ Outputs one JSON per line as artifacts appear:
126
126
  {"event":"done","status":"completed"}
127
127
  ```
128
128
 
129
+ ### New Agent quickstart
130
+
131
+ For a new Agent, keep the default service flow to two response commands:
132
+
133
+ ```bash
134
+ npx makaron-cli responses next <runId> --json
135
+ npx makaron-cli responses handle <messageId> --run <runId> --choice approve
136
+ npx makaron-cli responses deliver <artifactId> --run <runId> --channel feishu --message-id <messageId>
137
+ ```
138
+
139
+ `responses next` emits a compact timeline, detects text-only checkpoints for creative/service work, and returns `next_commands` when the Agent must handle a Makaron message before waiting for artifacts. It exits with code `3` on an unhandled checkpoint so wrappers stop instead of silently waiting. After handling a checkpoint, run the returned `next_commands.inspect` command to continue. While the run is still generating, it returns `status: "running"` and `blocking: false`. If completed image/video artifacts exist and have not been delivered, it returns `status: "has_artifacts"` and `blocking: true` with compact `undelivered_artifacts` entries containing `kind`, `status`, `url`, `fileName`, and `contentType` when available, plus `next_commands.deliver`; the Agent must send the artifact URL to the user, then record delivery with `responses deliver`. A non-checkpoint response returns `status: "ready"` or `status: "delivered"`. If a run failed, `responses next --json` prints `status: "failed"` and `blocking: true` plus `error.type`, `error.message`, `error.recoverable`, `error.detail`, and `next_command` to stdout before exiting `1`, so wrappers can parse the failure without scraping stderr. Use `--no-fail` to inspect checkpoint JSON without failing. For pure Q&A runs, use:
140
+
141
+ ```bash
142
+ npx makaron-cli responses next <runId> --json --checkpoint-mode off
143
+ ```
144
+
145
+ `responses handle` is the single checkpoint action command. Valid choices are `approve`, `revise`, `ask_user`, and `continue`. `responses deliver` records that a completed artifact was actually delivered to the user, preventing repeated delivery prompts.
146
+
129
147
  ### Dialogue events for external Agents
130
148
 
131
149
  Use this when another Agent needs to read what Makaron said, handle text checkpoints, and relay artifacts without inventing customer-service wording.
@@ -137,6 +155,8 @@ npx makaron-cli responses events <runId> --jsonl
137
155
  # pure Q&A view: npx makaron-cli responses timeline <runId> --jsonl --checkpoint-mode off
138
156
  ```
139
157
 
158
+ `events` and `timeline` are lower-level commands. New Agents should start with `responses next` and use these only when they need raw event streams.
159
+
140
160
  Events use only three factual types:
141
161
 
142
162
  ```json
@@ -152,6 +172,7 @@ npx makaron-cli responses approve msg_1 --run <runId> --note "Proceed."
152
172
  npx makaron-cli responses revise msg_1 --run <runId> "make it softer"
153
173
  npx makaron-cli responses ask-user msg_1 --run <runId>
154
174
  npx makaron-cli responses continue msg_1 --run <runId>
175
+ npx makaron-cli responses handle msg_1 --run <runId> --choice approve
155
176
  ```
156
177
 
157
178
  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:
@@ -299,18 +320,26 @@ RUN_ID=$(echo "$RUN_JSON" | jq -r .runId)
299
320
  PROJECT_URL=$(echo "$RUN_JSON" | jq -r .projectUrl)
300
321
  send_message "Project created: $PROJECT_URL"
301
322
 
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
323
+ if ! NEXT=$(npx makaron-cli responses next "$RUN_ID" --json); then
324
+ STATUS=$(echo "$NEXT" | jq -r .status)
325
+ if [ "$STATUS" = "needs_approval" ]; then
326
+ MSG_ID=$(echo "$NEXT" | jq -r .checkpoint.id)
327
+ TEXT=$(echo "$NEXT" | jq -r .checkpoint.text)
328
+ send_message "$TEXT"
329
+ npx makaron-cli responses handle "$MSG_ID" --run "$RUN_ID" --choice ask_user
330
+ exit 3
331
+ elif [ "$STATUS" = "failed" ]; then
332
+ send_message "$(echo "$NEXT" | jq -r .error.message)"
333
+ exit 1
334
+ fi
335
+ fi
336
+
337
+ NEXT=$(npx makaron-cli responses next "$RUN_ID" --json --no-fail)
338
+ if [ "$(echo "$NEXT" | jq -r .status)" = "has_artifacts" ]; then
339
+ ARTIFACT_ID=$(echo "$NEXT" | jq -r ".undelivered_artifacts[0].id")
340
+ ARTIFACT_URL=$(echo "$NEXT" | jq -r ".undelivered_artifacts[0].url")
341
+ DELIVERY_MESSAGE_ID=$(send_message "$ARTIFACT_URL")
342
+ npx makaron-cli responses deliver "$ARTIFACT_ID" --run "$RUN_ID" --channel feishu --message-id "$DELIVERY_MESSAGE_ID"
314
343
  fi
315
344
 
316
345
  RESULT=$(npx makaron-cli responses get "$RUN_ID" --wait --json)
package/bin/makaron.mjs CHANGED
@@ -20,6 +20,7 @@ import { execFileSync } from 'child_process';
20
20
 
21
21
  const AUTH_FILE = path.join(process.env.HOME || '~', '.makaron', 'auth.json');
22
22
  const APPROVALS_FILE = path.join(path.dirname(AUTH_FILE), 'approvals.json');
23
+ const DELIVERIES_FILE = path.join(path.dirname(AUTH_FILE), 'deliveries.json');
23
24
  const DEFAULT_URL = 'https://www.makaron.app';
24
25
  const BASE_URL = process.env.MAKARON_URL || DEFAULT_URL;
25
26
  const APP_URL = process.env.MAKARON_APP_URL || DEFAULT_URL;
@@ -77,6 +78,20 @@ function saveApprovals(approvals) {
77
78
  fs.writeFileSync(APPROVALS_FILE, JSON.stringify(approvals, null, 2));
78
79
  }
79
80
 
81
+ function loadDeliveries() {
82
+ try {
83
+ return JSON.parse(fs.readFileSync(DELIVERIES_FILE, 'utf-8'));
84
+ } catch {
85
+ return [];
86
+ }
87
+ }
88
+
89
+ function saveDeliveries(deliveries) {
90
+ const dir = path.dirname(DELIVERIES_FILE);
91
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
92
+ fs.writeFileSync(DELIVERIES_FILE, JSON.stringify(deliveries, null, 2));
93
+ }
94
+
80
95
  function buildCookie(tokenJson) {
81
96
  const url = tokenJson._supabaseUrl || SUPABASE_URL;
82
97
  const ref = url.match(/\/\/([^.]+)\./)?.[1] || '';
@@ -498,6 +513,7 @@ function normalizeDialogueMessage(runId, projectId, ev, context = {}) {
498
513
  const explicitApproval = hasExplicitApprovalRequirement(data, text);
499
514
  const textOnlyCheckpoint = Boolean(
500
515
  context.stoppedWithoutArtifacts
516
+ && ev.type !== 'error'
501
517
  && !explicitApproval
502
518
  && !isPureStatusMessage(text, ev.type, data.status)
503
519
  );
@@ -537,6 +553,10 @@ function normalizeDialogueArtifact(runId, projectId, item, seq) {
537
553
  };
538
554
  const url = item.url || item.imageUrl || item.videoUrl || item.audioUrl;
539
555
  if (url) artifact.url = url;
556
+ const fileName = item.fileName || item.filename || item.name || (url ? url.split('/').pop()?.split('?')[0] : null);
557
+ const contentType = item.contentType || item.content_type || item.mimeType || item.mime_type;
558
+ if (fileName) artifact.fileName = fileName;
559
+ if (contentType) artifact.contentType = contentType;
540
560
  if (item.error) artifact.error = item.error;
541
561
  if (item.taskId) artifact.taskId = item.taskId;
542
562
  if (item.snapshotId) artifact.snapshotId = item.snapshotId;
@@ -608,14 +628,18 @@ function compactDialogueEvents(events) {
608
628
  && prev.type === 'message'
609
629
  && ev.source_type === 'content'
610
630
  && prev.source_type === 'content'
611
- && !ev.requires_approval
612
- && !prev.requires_approval
613
631
  && !ev.proposal
614
- && !prev.proposal;
632
+ && !prev.proposal
633
+ && Boolean(ev.requires_approval) === Boolean(prev.requires_approval)
634
+ && (ev.approval_reason || '') === (prev.approval_reason || '');
615
635
  if (canMerge) {
616
636
  prev.text = `${prev.text}${ev.text}`;
617
637
  prev.id = `${prev.id}+${ev.id}`;
618
638
  prev.seq_end = ev.seq;
639
+ if (ev.requires_approval) {
640
+ prev.requires_approval = true;
641
+ prev.approval_options = prev.approval_options || ev.approval_options;
642
+ }
619
643
  } else {
620
644
  compacted.push({ ...ev });
621
645
  }
@@ -628,6 +652,127 @@ function findUnhandledApprovalMessages(events) {
628
652
  return events.filter(ev => ev.type === 'message' && ev.requires_approval && !approved.has(ev.id));
629
653
  }
630
654
 
655
+ function isCompletedDeliverableArtifact(artifact) {
656
+ return artifact?.type === 'artifact'
657
+ && ['image', 'video', 'design', 'music', 'audio', 'file'].includes(artifact.kind)
658
+ && artifact.status === 'completed'
659
+ && Boolean(artifact.url);
660
+ }
661
+
662
+ function findUndeliveredArtifacts(runId, events) {
663
+ const delivered = new Set(loadDeliveries().filter(item => item.runId === runId).map(item => item.artifactId || item.artifact_id));
664
+ return events.filter(isCompletedDeliverableArtifact).filter(artifact => !delivered.has(artifact.id));
665
+ }
666
+
667
+ function buildCliCommand() {
668
+ return 'npx makaron-cli@latest';
669
+ }
670
+
671
+ function buildHandleCommand(messageId, runId, choice) {
672
+ return `${buildCliCommand()} responses handle ${messageId} --run ${runId} --choice ${choice}`;
673
+ }
674
+
675
+ function buildNextCommand(runId) {
676
+ return `${buildCliCommand()} responses next ${runId} --json`;
677
+ }
678
+
679
+ function buildDeliverCommand(artifactId, runId) {
680
+ return `${buildCliCommand()} responses deliver ${artifactId} --run ${runId}`;
681
+ }
682
+
683
+ function buildRunError(data, events) {
684
+ const errorEvent = events.find(ev => ev.type === 'message' && ev.source_type === 'error') || null;
685
+ const resultError = data.result?.error || data.error || data.message || null;
686
+ if (!errorEvent && !resultError && !['failed', 'aborted'].includes(data.status)) return null;
687
+ const errorType = (typeof resultError === 'object' && (resultError.type || resultError.code)) || data.error_type || data.errorType || data.status || 'failed';
688
+ return {
689
+ type: errorType,
690
+ status: data.status || 'failed',
691
+ message: errorEvent?.text || (typeof resultError === 'string' ? resultError : resultError?.message) || `Makaron run ${data.status || 'failed'}`,
692
+ recoverable: Boolean((typeof resultError === 'object' && resultError.recoverable) || data.recoverable),
693
+ detail: typeof resultError === 'object' ? resultError : undefined,
694
+ };
695
+ }
696
+
697
+ function buildNextAction(runId, events, data = {}) {
698
+ const runError = buildRunError(data, events);
699
+ if (runError) {
700
+ return {
701
+ status: 'failed',
702
+ blocking: true,
703
+ runId,
704
+ error: runError,
705
+ next_command: buildNextCommand(runId),
706
+ next_commands: {
707
+ inspect: buildNextCommand(runId),
708
+ },
709
+ events,
710
+ };
711
+ }
712
+ const checkpoint = findUnhandledApprovalMessages(events)[0] || null;
713
+ const artifacts = events.filter(ev => ev.type === 'artifact');
714
+ const approvals = events.filter(ev => ev.type === 'approval');
715
+ if (data.incomplete || ['queued', 'running', 'rendering', 'processing', 'generating', 'submitted'].includes(data.status)) {
716
+ return {
717
+ status: 'running',
718
+ blocking: false,
719
+ runId,
720
+ artifacts,
721
+ approvals,
722
+ next_command: buildNextCommand(runId),
723
+ next_commands: {
724
+ inspect: buildNextCommand(runId),
725
+ },
726
+ events,
727
+ };
728
+ }
729
+ if (checkpoint) {
730
+ return {
731
+ status: 'needs_approval',
732
+ blocking: true,
733
+ runId,
734
+ checkpoint,
735
+ next_commands: {
736
+ approve: buildHandleCommand(checkpoint.id, runId, 'approve'),
737
+ revise: `${buildHandleCommand(checkpoint.id, runId, 'revise')} --note "what to change"`,
738
+ ask_user: buildHandleCommand(checkpoint.id, runId, 'ask_user'),
739
+ continue: buildHandleCommand(checkpoint.id, runId, 'continue'),
740
+ inspect: buildNextCommand(runId),
741
+ },
742
+ events,
743
+ };
744
+ }
745
+ const undeliveredArtifacts = findUndeliveredArtifacts(runId, events);
746
+ if (undeliveredArtifacts.length) {
747
+ const first = undeliveredArtifacts[0];
748
+ return {
749
+ status: 'has_artifacts',
750
+ blocking: true,
751
+ runId,
752
+ artifacts,
753
+ undelivered_artifacts: undeliveredArtifacts,
754
+ approvals,
755
+ next_commands: {
756
+ deliver: buildDeliverCommand(first.id, runId),
757
+ inspect: buildNextCommand(runId),
758
+ },
759
+ events,
760
+ };
761
+ }
762
+ return {
763
+ status: artifacts.length ? 'delivered' : 'ready',
764
+ blocking: false,
765
+ runId,
766
+ artifacts,
767
+ approvals,
768
+ deliveries: loadDeliveries().filter(item => item.runId === runId),
769
+ next_commands: {
770
+ inspect: buildNextCommand(runId),
771
+ },
772
+ events,
773
+ };
774
+ }
775
+
631
776
  async function fetchRun(baseUrl, headers, runId, opts = {}) {
632
777
  const params = new URLSearchParams();
633
778
  if (opts.events) params.set('events', 'true');
@@ -666,6 +811,44 @@ async function printDialogueEvents(baseUrl, headers, runId, opts = {}) {
666
811
  }
667
812
  }
668
813
 
814
+ async function printAgentNext(baseUrl, headers, runId, opts = {}) {
815
+ const { json = false, checkpointMode = 'service', failOnCheckpoint = true } = opts;
816
+ const data = await fetchRun(baseUrl, headers, runId, { events: true });
817
+ const events = compactDialogueEvents(buildDialogueEvents(runId, { ...data, checkpointMode }));
818
+ const action = buildNextAction(runId, events, data);
819
+ if (json) {
820
+ console.log(JSON.stringify(action, null, 2));
821
+ } else if (action.status === 'failed') {
822
+ console.log(`failed: ${action.error?.message || 'Makaron run failed'}`);
823
+ console.log(`inspect: ${action.next_commands.inspect}`);
824
+ } else if (action.status === 'needs_approval') {
825
+ console.log(`needs_approval: ${action.checkpoint.id}`);
826
+ if (action.checkpoint.text) console.log(action.checkpoint.text);
827
+ console.log(`approve: ${action.next_commands.approve}`);
828
+ console.log(`revise: ${action.next_commands.revise}`);
829
+ console.log(`ask_user: ${action.next_commands.ask_user}`);
830
+ console.log(`continue: ${action.next_commands.continue}`);
831
+ console.log(`inspect: ${action.next_commands.inspect}`);
832
+ } else if (action.status === 'has_artifacts') {
833
+ for (const artifact of action.undelivered_artifacts || action.artifacts) {
834
+ console.log(`${artifact.kind} ${artifact.status}${artifact.url ? ` ${artifact.url}` : ''}`);
835
+ }
836
+ if (action.next_commands.deliver) console.log(`deliver: ${action.next_commands.deliver}`);
837
+ console.log(`inspect: ${action.next_commands.inspect}`);
838
+ } else if (action.status === 'running') {
839
+ console.log('running');
840
+ console.log(`inspect: ${action.next_commands.inspect}`);
841
+ } else if (action.status === 'delivered') {
842
+ console.log('delivered');
843
+ console.log(`inspect: ${action.next_commands.inspect}`);
844
+ } else {
845
+ console.log('ready');
846
+ console.log(`inspect: ${action.next_commands.inspect}`);
847
+ }
848
+ if (action.status === 'needs_approval' && failOnCheckpoint) process.exit(3);
849
+ if (action.status === 'failed') process.exit(1);
850
+ }
851
+
669
852
  function recordApproval(runId, messageId, choice, note) {
670
853
  const approvals = loadApprovals();
671
854
  const approval = {
@@ -683,6 +866,24 @@ function recordApproval(runId, messageId, choice, note) {
683
866
  console.log(JSON.stringify(approval));
684
867
  }
685
868
 
869
+ function recordDelivery(runId, artifactId, opts = {}) {
870
+ const deliveries = loadDeliveries();
871
+ const delivery = {
872
+ type: 'delivery',
873
+ id: `delivery_${Date.now()}`,
874
+ runId,
875
+ artifactId,
876
+ status: 'recorded',
877
+ createdAt: new Date().toISOString(),
878
+ };
879
+ if (opts.channel) delivery.channel = opts.channel;
880
+ if (opts.messageId) delivery.messageId = opts.messageId;
881
+ if (opts.note) delivery.note = opts.note;
882
+ deliveries.push(delivery);
883
+ saveDeliveries(deliveries);
884
+ console.log(JSON.stringify(delivery));
885
+ }
886
+
686
887
  // ─── Watch (incremental event stream) ───────────────────────────────────────
687
888
 
688
889
  async function watchRun(baseUrl, headers, runId, opts = {}) {
@@ -1317,6 +1518,21 @@ if (command === '--version' || command === '-v' || command === 'version') {
1317
1518
  }
1318
1519
  await watchRun(baseUrl, headers, runId, { interval, jsonl });
1319
1520
 
1521
+ } else if (sub === 'next') {
1522
+ const runId = args[2];
1523
+ if (!runId) { console.error('Usage: makaron responses next <runId> [--json] [--checkpoint-mode service|off] [--no-fail]'); process.exit(1); }
1524
+ let jsonOutput = false, checkpointMode = 'service', failOnCheckpoint = true;
1525
+ for (let i = 3; i < args.length; i++) {
1526
+ if (args[i] === '--json') jsonOutput = true;
1527
+ else if (args[i] === '--checkpoint-mode' && args[i + 1]) checkpointMode = args[++i];
1528
+ else if (args[i] === '--no-fail') failOnCheckpoint = false;
1529
+ }
1530
+ if (!['service', 'off'].includes(checkpointMode)) {
1531
+ console.error('--checkpoint-mode must be service or off');
1532
+ process.exit(1);
1533
+ }
1534
+ await printAgentNext(baseUrl, headers, runId, { json: jsonOutput, checkpointMode, failOnCheckpoint });
1535
+
1320
1536
  } else if (sub === 'events' || sub === 'timeline') {
1321
1537
  const runId = args[2];
1322
1538
  if (!runId) { console.error(`Usage: makaron responses ${sub} <runId> [--jsonl] [--compact] [--checkpoint-mode service|off] [--follow] [--interval <ms>] [--fail-on-unapproved]`); process.exit(1); }
@@ -1335,19 +1551,51 @@ if (command === '--version' || command === '-v' || command === 'version') {
1335
1551
  }
1336
1552
  await printDialogueEvents(baseUrl, headers, runId, { interval, jsonl, follow, failOnUnapproved, compact, checkpointMode });
1337
1553
 
1338
- } else if (['approve', 'revise', 'ask-user', 'continue'].includes(sub)) {
1554
+ } else if (sub === 'deliver') {
1555
+ const artifactId = args[2];
1556
+ if (!artifactId) { console.error('Usage: makaron responses deliver <artifactId> --run <runId> [--channel <name>] [--message-id <id>] [--note <text>]'); process.exit(1); }
1557
+ let runId = null, note = null, channel = null, messageId = null;
1558
+ const noteParts = [];
1559
+ for (let i = 3; i < args.length; i++) {
1560
+ if (args[i] === '--run' && args[i + 1]) runId = args[++i];
1561
+ else if (args[i] === '--note' && args[i + 1]) note = args[++i];
1562
+ else if (args[i] === '--channel' && args[i + 1]) channel = args[++i];
1563
+ else if (args[i] === '--message-id' && args[i + 1]) messageId = args[++i];
1564
+ else noteParts.push(args[i]);
1565
+ }
1566
+ if (!runId) { console.error('Usage: makaron responses deliver <artifactId> --run <runId> [--channel <name>] [--message-id <id>] [--note <text>]'); process.exit(1); }
1567
+ if (!note && noteParts.length) note = noteParts.join(' ');
1568
+ recordDelivery(runId, artifactId, { note, channel, messageId });
1569
+
1570
+ } else if (sub === 'handle' || ['approve', 'revise', 'ask-user', 'continue'].includes(sub)) {
1339
1571
  const messageId = args[2];
1340
- if (!messageId) { console.error(`Usage: makaron responses ${sub} <messageId> --run <runId> [--note <text>]`); process.exit(1); }
1341
- let runId = null, note = null;
1572
+ if (!messageId) {
1573
+ console.error(sub === 'handle'
1574
+ ? 'Usage: makaron responses handle <messageId> --run <runId> --choice approve|revise|ask_user|continue [--note <text>]'
1575
+ : `Usage: makaron responses ${sub} <messageId> --run <runId> [--note <text>]`);
1576
+ process.exit(1);
1577
+ }
1578
+ let runId = null, note = null, choice = sub === 'handle' ? null : sub;
1342
1579
  const noteParts = [];
1343
1580
  for (let i = 3; i < args.length; i++) {
1344
1581
  if (args[i] === '--run' && args[i + 1]) runId = args[++i];
1345
1582
  else if (args[i] === '--note' && args[i + 1]) note = args[++i];
1583
+ else if (args[i] === '--choice' && args[i + 1]) choice = args[++i];
1346
1584
  else noteParts.push(args[i]);
1347
1585
  }
1348
- if (!runId) { console.error(`Usage: makaron responses ${sub} <messageId> --run <runId> [--note <text>]`); process.exit(1); }
1586
+ if (!runId || !choice) {
1587
+ console.error(sub === 'handle'
1588
+ ? 'Usage: makaron responses handle <messageId> --run <runId> --choice approve|revise|ask_user|continue [--note <text>]'
1589
+ : `Usage: makaron responses ${sub} <messageId> --run <runId> [--note <text>]`);
1590
+ process.exit(1);
1591
+ }
1592
+ choice = choice === 'ask-user' ? 'ask_user' : choice;
1593
+ if (!['approve', 'revise', 'ask_user', 'continue'].includes(choice)) {
1594
+ console.error('--choice must be approve, revise, ask_user, or continue');
1595
+ process.exit(1);
1596
+ }
1349
1597
  if (!note && noteParts.length) note = noteParts.join(' ');
1350
- recordApproval(runId, messageId, sub === 'ask-user' ? 'ask_user' : sub, note);
1598
+ recordApproval(runId, messageId, choice, note);
1351
1599
 
1352
1600
  } else if (sub === 'list') {
1353
1601
  let projectId = null;
@@ -1372,6 +1620,9 @@ if (command === '--version' || command === '-v' || command === 'version') {
1372
1620
  responses get <runId> Get status and output (JSON)
1373
1621
  responses get <runId> --wait Poll until completed
1374
1622
  responses get <runId> --pick <field> Extract: first_image_url, first_video_url, project_url, output
1623
+ responses next <runId> --json New Agent entry: compact timeline + checkpoint guidance
1624
+ responses handle <messageId> --run <runId> --choice approve|revise|ask_user|continue
1625
+ responses deliver <artifactId> --run <runId> Record artifact delivered to the user
1375
1626
  responses events <runId> --jsonl Emit message/approval/artifact events for external Agents
1376
1627
  responses timeline <runId> --jsonl Alias for responses events
1377
1628
  responses timeline <runId> --checkpoint-mode off Disable text-only checkpoints for pure Q&A
@@ -1816,6 +2067,9 @@ Commands:
1816
2067
 
1817
2068
  responses get <runId> Get run status and results
1818
2069
  responses get <runId> --wait Poll until completed
2070
+ responses next <runId> --json New Agent entry: compact timeline + checkpoint guidance
2071
+ responses handle <messageId> --run <runId> --choice approve|revise|ask_user|continue
2072
+ responses deliver <artifactId> --run <runId> Record artifact delivered to the user
1819
2073
  responses events <runId> --jsonl Emit message/approval/artifact events
1820
2074
  responses timeline <runId> --checkpoint-mode off Disable text-only checkpoints for pure Q&A
1821
2075
  responses list --project <id> List runs for a project
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "makaron-cli",
3
- "version": "0.7.10",
3
+ "version": "0.8.1",
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,24 @@ Outputs one JSON per line as artifacts appear:
126
126
  {"event":"done","status":"completed"}
127
127
  ```
128
128
 
129
+ ### New Agent quickstart
130
+
131
+ For a new Agent, keep the default service flow to two response commands:
132
+
133
+ ```bash
134
+ npx makaron-cli responses next <runId> --json
135
+ npx makaron-cli responses handle <messageId> --run <runId> --choice approve
136
+ npx makaron-cli responses deliver <artifactId> --run <runId> --channel feishu --message-id <messageId>
137
+ ```
138
+
139
+ `responses next` emits a compact timeline, detects text-only checkpoints for creative/service work, and returns `next_commands` when the Agent must handle a Makaron message before waiting for artifacts. It exits with code `3` on an unhandled checkpoint so wrappers stop instead of silently waiting. After handling a checkpoint, run the returned `next_commands.inspect` command to continue. While the run is still generating, it returns `status: "running"` and `blocking: false`. If completed image/video artifacts exist and have not been delivered, it returns `status: "has_artifacts"` and `blocking: true` with compact `undelivered_artifacts` entries containing `kind`, `status`, `url`, `fileName`, and `contentType` when available, plus `next_commands.deliver`; the Agent must send the artifact URL to the user, then record delivery with `responses deliver`. A non-checkpoint response returns `status: "ready"` or `status: "delivered"`. If a run failed, `responses next --json` prints `status: "failed"` and `blocking: true` plus `error.type`, `error.message`, `error.recoverable`, `error.detail`, and `next_command` to stdout before exiting `1`, so wrappers can parse the failure without scraping stderr. Use `--no-fail` to inspect checkpoint JSON without failing. For pure Q&A runs, use:
140
+
141
+ ```bash
142
+ npx makaron-cli responses next <runId> --json --checkpoint-mode off
143
+ ```
144
+
145
+ `responses handle` is the single checkpoint action command. Valid choices are `approve`, `revise`, `ask_user`, and `continue`. `responses deliver` records that a completed artifact was actually delivered to the user, preventing repeated delivery prompts.
146
+
129
147
  ### Dialogue events for external Agents
130
148
 
131
149
  Use this when another Agent needs to read what Makaron said, handle text checkpoints, and relay artifacts without inventing customer-service wording.
@@ -137,6 +155,8 @@ npx makaron-cli responses events <runId> --jsonl
137
155
  # pure Q&A view: npx makaron-cli responses timeline <runId> --jsonl --checkpoint-mode off
138
156
  ```
139
157
 
158
+ `events` and `timeline` are lower-level commands. New Agents should start with `responses next` and use these only when they need raw event streams.
159
+
140
160
  Events use only three factual types:
141
161
 
142
162
  ```json
@@ -152,6 +172,7 @@ npx makaron-cli responses approve msg_1 --run <runId> --note "Proceed."
152
172
  npx makaron-cli responses revise msg_1 --run <runId> "make it softer"
153
173
  npx makaron-cli responses ask-user msg_1 --run <runId>
154
174
  npx makaron-cli responses continue msg_1 --run <runId>
175
+ npx makaron-cli responses handle msg_1 --run <runId> --choice approve
155
176
  ```
156
177
 
157
178
  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:
@@ -299,18 +320,26 @@ RUN_ID=$(echo "$RUN_JSON" | jq -r .runId)
299
320
  PROJECT_URL=$(echo "$RUN_JSON" | jq -r .projectUrl)
300
321
  send_message "Project created: $PROJECT_URL"
301
322
 
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
323
+ if ! NEXT=$(npx makaron-cli responses next "$RUN_ID" --json); then
324
+ STATUS=$(echo "$NEXT" | jq -r .status)
325
+ if [ "$STATUS" = "needs_approval" ]; then
326
+ MSG_ID=$(echo "$NEXT" | jq -r .checkpoint.id)
327
+ TEXT=$(echo "$NEXT" | jq -r .checkpoint.text)
328
+ send_message "$TEXT"
329
+ npx makaron-cli responses handle "$MSG_ID" --run "$RUN_ID" --choice ask_user
330
+ exit 3
331
+ elif [ "$STATUS" = "failed" ]; then
332
+ send_message "$(echo "$NEXT" | jq -r .error.message)"
333
+ exit 1
334
+ fi
335
+ fi
336
+
337
+ NEXT=$(npx makaron-cli responses next "$RUN_ID" --json --no-fail)
338
+ if [ "$(echo "$NEXT" | jq -r .status)" = "has_artifacts" ]; then
339
+ ARTIFACT_ID=$(echo "$NEXT" | jq -r ".undelivered_artifacts[0].id")
340
+ ARTIFACT_URL=$(echo "$NEXT" | jq -r ".undelivered_artifacts[0].url")
341
+ DELIVERY_MESSAGE_ID=$(send_message "$ARTIFACT_URL")
342
+ npx makaron-cli responses deliver "$ARTIFACT_ID" --run "$RUN_ID" --channel feishu --message-id "$DELIVERY_MESSAGE_ID"
314
343
  fi
315
344
 
316
345
  RESULT=$(npx makaron-cli responses get "$RUN_ID" --wait --json)