makaron-cli 0.3.0 → 0.4.0

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.
Files changed (3) hide show
  1. package/README.md +118 -87
  2. package/bin/makaron.mjs +232 -10
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -36,62 +36,111 @@ The CLI checks `MAKARON_API_KEY` first, then falls back to the saved session in
36
36
 
37
37
  ## Commands
38
38
 
39
- ### `list` — Show all projects
39
+ ### `chat` — Send a message to Makaron Agent
40
+
41
+ This is the main command. The Agent can edit images, generate videos, compose music, and create designs.
42
+
43
+ **Default mode (non-blocking poll):**
40
44
 
41
45
  ```bash
42
- npx makaron-cli list
46
+ npx makaron-cli chat --project <id> "make it look cinematic"
43
47
  ```
44
48
 
45
- Output:
46
- ```
47
- 📁 12 projects
49
+ The CLI submits the request and polls every 3s for results. No long-lived connection — you can Ctrl+C and come back later with `responses get`.
48
50
 
49
- abc123def My Photo 3 snaps 2h ago
50
- xyz789ghi Sunset Edit 5 snaps 1d ago
51
+ **Background mode submit and exit immediately:**
52
+
53
+ ```bash
54
+ npx makaron-cli chat --project <id> --background "make a 5s video"
55
+ # → prints runId and exits in <1s
56
+
57
+ # Later, check the result:
58
+ npx makaron-cli responses get <runId> --wait
51
59
  ```
52
60
 
53
- ### `create` Create a new project
61
+ **Structured JSON output (for programmatic use):**
54
62
 
55
63
  ```bash
56
- # From local image file(s)
57
- npx makaron-cli create --image photo.jpg
58
- npx makaron-cli create --image img1.jpg --image img2.jpg
64
+ npx makaron-cli chat --project <id> --json --background "edit the photo"
65
+ # {"runId":"...","projectId":"...","projectUrl":"...","status":"running"}
66
+ ```
59
67
 
60
- # From URL(s)
61
- npx makaron-cli create --image-url https://example.com/photo.jpg
68
+ **Legacy streaming mode (real-time SSE):**
62
69
 
63
- # Empty project (for text-to-image)
64
- npx makaron-cli create --title "My New Project"
70
+ ```bash
71
+ npx makaron-cli chat --project <id> --stream "hello"
65
72
  ```
66
73
 
67
- Output:
68
- ```
69
- Project created
70
- ID: abc123def456
71
- Images: 1
72
- [1] https://sdyrtztrjgmmpnirswxt.supabase.co/storage/v1/object/public/images/...
73
- URL: https://www.makaron.app/projects/abc123def456
74
+ **Options:**
75
+ - `--project <id>` — target project (required)
76
+ - `--image <file>` — upload image to project before chatting
77
+ - `--background` / `-b` — submit and exit, print runId
78
+ - `--json` — structured JSON output
79
+ - `--stream` — legacy real-time SSE mode
80
+ - `--video-model kling|seedance` — preferred video model
81
+ - `--model <name>` — preferred image model
82
+
83
+ ### `responses` — Query run status and results
84
+
85
+ ```bash
86
+ # Get current status (single query)
87
+ npx makaron-cli responses get <runId>
88
+
89
+ # Poll until completed
90
+ npx makaron-cli responses get <runId> --wait
91
+
92
+ # Wait for video/music rendering too (not just Agent completion)
93
+ npx makaron-cli responses get <runId> --wait --wait-artifacts
94
+
95
+ # List runs for a project
96
+ npx makaron-cli responses list --project <id>
74
97
  ```
75
98
 
76
- ### `chat` — Send a message to Makaron Agent
99
+ **Result JSON structure:**
100
+
101
+ ```json
102
+ {
103
+ "runId": "...",
104
+ "projectId": "...",
105
+ "status": "completed",
106
+ "eventCount": 31,
107
+ "result": {
108
+ "text": "Agent's text response...",
109
+ "images": [{ "snapshotId": "...", "imageUrl": "https://..." }],
110
+ "designs": [{
111
+ "snapshotId": "...",
112
+ "imageUrl": "https://...",
113
+ "width": 1080,
114
+ "height": 1920,
115
+ "animation": { "fps": 30, "durationInSeconds": 3 },
116
+ "props": { "title": "..." },
117
+ "code": "function Design(props) { ... }"
118
+ }],
119
+ "videos": [{ "taskId": "...", "status": "completed", "videoUrl": "https://..." }],
120
+ "music": [{ "taskId": "...", "status": "completed", "audioUrl": "https://..." }]
121
+ }
122
+ }
123
+ ```
77
124
 
78
- This is the main command. The Agent can edit images, generate videos, compose music, and create designs.
125
+ ### `list` Show all projects
79
126
 
80
127
  ```bash
81
- npx makaron-cli chat --project <id> "make it look cinematic"
128
+ npx makaron-cli list
82
129
  ```
83
130
 
84
- The Agent streams its response in real-time:
85
- - **Text** → stdout (pipe-friendly)
86
- - **Status/progress** → stderr
87
- - **Images, videos, music** → URLs printed to stderr
131
+ ### `create` Create a new project
88
132
 
89
133
  ```bash
90
- # Add extra reference images to the project before chatting
91
- npx makaron-cli chat --project <id> --image ref.jpg "use this style"
92
- ```
134
+ # From local image file(s)
135
+ npx makaron-cli create --image photo.jpg
136
+ npx makaron-cli create --image img1.jpg --image img2.jpg
137
+
138
+ # From URL(s)
139
+ npx makaron-cli create --image-url https://example.com/photo.jpg
93
140
 
94
- After the Agent finishes, the CLI automatically polls for any pending video/music tasks until they complete.
141
+ # Empty project (for text-to-image)
142
+ npx makaron-cli create --title "My New Project"
143
+ ```
95
144
 
96
145
  ### `abort` — Stop a running Agent
97
146
 
@@ -99,8 +148,6 @@ After the Agent finishes, the CLI automatically polls for any pending video/musi
99
148
  npx makaron-cli abort <runId>
100
149
  ```
101
150
 
102
- Press `Ctrl+C` during `chat` to abort automatically.
103
-
104
151
  ### `edit` — AI image editing (direct MCP tool call)
105
152
 
106
153
  Unlike `chat` (which uses the Agent with project context), `edit` directly calls the image generation model for one-shot results.
@@ -130,30 +177,19 @@ Options:
130
177
  - `--aspect <ratio>` — target aspect ratio (e.g. `4:5`, `1:1`, `16:9`)
131
178
  - `--out <path>` — output file path (default: `makaron-output-{timestamp}.jpg`)
132
179
 
133
- Output: saves image to local file, prints the file path to stdout.
134
-
135
180
  ### `video` — Video generation
136
181
 
137
182
  ```bash
138
183
  # Write a video script from images
139
184
  npx makaron-cli video script --image img1.jpg --image img2.jpg "cinematic story"
140
- npx makaron-cli video script --image img1.jpg --image img2.jpg --lang zh "电影感故事"
141
185
 
142
186
  # Submit video rendering (images must be public URLs)
143
187
  npx makaron-cli video create --script "Shot 1..." --image https://...img1.jpg --duration 10
144
- npx makaron-cli video create --script-file script.txt --image https://...jpg --model seedance
145
188
 
146
189
  # Check status
147
190
  npx makaron-cli video status <taskId>
148
191
  ```
149
192
 
150
- Options for `video create`:
151
- - `--script "..."` or `--script-file <path>` — video script
152
- - `--image <url>` — public image URL (repeatable, up to 7)
153
- - `--duration 3|5|7|10|15` — seconds (omit for smart mode)
154
- - `--aspect 9:16|16:9|1:1` — aspect ratio
155
- - `--model kling|seedance` — video model (default: kling)
156
-
157
193
  ### `music` — Music generation
158
194
 
159
195
  ```bash
@@ -167,48 +203,45 @@ npx makaron-cli music create --vocals --style "lo-fi, ambient" "rainy day vibes"
167
203
  npx makaron-cli music status <taskId>
168
204
  ```
169
205
 
170
- Options for `music create`:
171
- - `--vocals` — include vocals (default: instrumental only)
172
- - `--style "genre"` — genre/mood tags
173
-
174
- ## Example: Full workflow
206
+ ## Agent Integration Guide
175
207
 
176
- ```bash
177
- # 1. Login (once)
178
- npx makaron-cli login
208
+ For AI agents (Claude Code, OpenClaw, etc.) calling this CLI programmatically:
179
209
 
180
- # 2. Create a project from a photo
181
- npx makaron-cli create --image my-photo.jpg
182
- # → ID: proj_abc123
210
+ ### Non-blocking workflow (recommended)
183
211
 
184
- # 3. Edit the image
185
- npx makaron-cli chat --project proj_abc123 "add dramatic lighting and film grain"
212
+ ```bash
213
+ # 1. Submit task returns immediately
214
+ RUN_ID=$(npx makaron-cli chat --project $PROJECT_ID --background "make a 5s video")
186
215
 
187
- # 4. Generate a video from the result
188
- npx makaron-cli chat --project proj_abc123 "make a 5 second cinematic video"
216
+ # 2. Do other work while Makaron processes...
189
217
 
190
- # 5. Add background music
191
- npx makaron-cli chat --project proj_abc123 "add epic orchestral background music"
218
+ # 3. Poll for result when ready
219
+ npx makaron-cli responses get $RUN_ID --wait
192
220
  ```
193
221
 
194
- ## Agent Integration Guide
222
+ ### JSON mode for structured parsing
223
+
224
+ ```bash
225
+ # Submit
226
+ RESULT=$(npx makaron-cli chat --project $ID --json --background "edit the photo")
227
+ RUN_ID=$(echo $RESULT | jq -r .runId)
195
228
 
196
- For AI agents (Claude Code, Cursor, etc.) calling this CLI programmatically:
229
+ # Poll
230
+ npx makaron-cli responses get $RUN_ID --wait
231
+ ```
197
232
 
198
- ### Setup (one-time)
233
+ ### Sequential multi-turn
199
234
 
200
235
  ```bash
201
- export MAKARON_API_KEY=mk_live_your_key_here
202
- npx makaron-cli list # verify it works
203
- ```
236
+ # Turn 1: edit image
237
+ npx makaron-cli chat --project $ID "make it cinematic"
238
+ # Wait for completion (default mode polls automatically)
204
239
 
205
- ### Best practices
240
+ # Turn 2: make video from the edited image
241
+ npx makaron-cli chat --project $ID "now make a 5s video from this"
242
+ ```
206
243
 
207
- 1. **Always capture the project ID** from `create` output you need it for all `chat` commands
208
- 2. **One instruction per `chat` call** — the Agent handles complex requests, but single clear instructions work best
209
- 3. **Check results via the URL** — every project has a web URL at `https://www.makaron.app/projects/<id>`
210
- 4. **Video generation takes 2-5 minutes** — the CLI auto-polls and prints the URL when done
211
- 5. **Music generation takes ~60 seconds** — also auto-polled
244
+ Note: One project runs one Agent at a time. A new message while the previous is still running will interrupt the first.
212
245
 
213
246
  ### What the Agent can do
214
247
 
@@ -224,16 +257,16 @@ npx makaron-cli list # verify it works
224
257
  | Background music | "add calm piano music" |
225
258
  | Design/motion graphics | "create an Instagram story with animated text" |
226
259
 
227
- ### Output parsing
260
+ ### Output types in result
228
261
 
229
- - **stdout** = Agent's text response (markdown)
230
- - **stderr** = progress, tool calls, result URLs
231
- - Key patterns in stderr:
232
- - `🖼️ Image: <url>` generated/edited image URL
233
- - `🎬 Video done (<seconds>s): <url>` completed video URL
234
- - `🎵 Music done (<seconds>s): <url>` completed music URL
235
- - `🎨 Design published: <description>` motion graphic/design created
236
- - `🔗 <url>` project web URL (always last)
262
+ | Type | Field | Contains |
263
+ |------|-------|----------|
264
+ | Text | `result.text` | Agent's conversational response |
265
+ | Image | `result.images[].imageUrl` | Generated/edited image URL |
266
+ | Design (still) | `result.designs[].imageUrl` | Poster screenshot + code |
267
+ | Design (animated) | `result.designs[].animation` | fps + duration + code |
268
+ | Video | `result.videos[].videoUrl` | MP4 URL (after rendering) |
269
+ | Music | `result.music[].audioUrl` | MP3 URL (after generating) |
237
270
 
238
271
  ### Environment variables
239
272
 
@@ -250,8 +283,6 @@ All generated content (images, videos, designs) is saved to the project and visi
250
283
  https://www.makaron.app/projects/<project-id>
251
284
  ```
252
285
 
253
- Open this URL in a browser to see the timeline of all edits, play videos, and download assets.
254
-
255
286
  ## Admin: Skill Marketplace Operations
256
287
 
257
288
  Admin commands require an API key with admin privileges. Ask your admin to run `makaron admin set-admin <your-email>` to grant access.
@@ -272,7 +303,7 @@ npx makaron-cli admin upload cover.jpg marketplace/covers/skill-name.jpg
272
303
  npx makaron-cli admin upload before.jpg marketplace/before/before-name.jpg
273
304
 
274
305
  # Upload skill zip
275
- npx makaron-cli admin upload skill.zip marketplace/skills/skill-name.zip
306
+ npx makaron-cli admin upload skill-name.zip marketplace/skills/skill-name.zip
276
307
  ```
277
308
 
278
309
  Storage paths follow this convention:
package/bin/makaron.mjs CHANGED
@@ -6,6 +6,8 @@
6
6
  * npx makaron-cli login
7
7
  * npx makaron-cli create --image photo.jpg
8
8
  * npx makaron-cli chat --project <id> "make it look cinematic"
9
+ * npx makaron-cli chat --project <id> -b "message" # background, returns runId
10
+ * npx makaron-cli responses get <runId> --wait # poll until done
9
11
  * npx makaron-cli list
10
12
  */
11
13
 
@@ -226,6 +228,138 @@ async function streamAgent(baseUrl, headers, projectId, prompt) {
226
228
  return { runId, results };
227
229
  }
228
230
 
231
+ // ─── Run + Poll (non-blocking) ──────────────────────────────────────────────
232
+
233
+ async function submitRun(baseUrl, headers, projectId, prompt, opts = {}) {
234
+ const body = { projectId, prompt };
235
+ if (opts.preferredModel) body.preferredModel = opts.preferredModel;
236
+ if (opts.videoModel) body.videoModel = opts.videoModel;
237
+ if (opts.currentSnapshotIndex != null) body.currentSnapshotIndex = opts.currentSnapshotIndex;
238
+ if (opts.isNsfw) body.isNsfw = opts.isNsfw;
239
+
240
+ const res = await fetch(`${baseUrl}/api/agent/run`, {
241
+ method: 'POST',
242
+ headers: { 'Content-Type': 'application/json', ...headers },
243
+ body: JSON.stringify(body),
244
+ });
245
+
246
+ if (!res.ok) {
247
+ const text = await res.text();
248
+ console.error(`Error ${res.status}: ${text}`);
249
+ process.exit(1);
250
+ }
251
+
252
+ return await res.json();
253
+ }
254
+
255
+ async function pollRun(baseUrl, headers, runId, opts = {}) {
256
+ const { json = false, waitForArtifacts = false, background = false } = opts;
257
+ if (background) return;
258
+
259
+ let lastSeq = -1;
260
+ let printedText = '';
261
+ const start = Date.now();
262
+
263
+ while (true) {
264
+ await new Promise(r => setTimeout(r, 3000));
265
+ const elapsed = Math.round((Date.now() - start) / 1000);
266
+
267
+ const params = new URLSearchParams({ events: 'true' });
268
+ if (lastSeq >= 0) params.set('after', String(lastSeq));
269
+ if (waitForArtifacts) params.set('wait_for_artifacts', 'true');
270
+
271
+ let data;
272
+ try {
273
+ const res = await fetch(`${baseUrl}/api/agent/run/${runId}?${params}`, { headers });
274
+ if (!res.ok) {
275
+ if (elapsed > 800) { process.stderr.write(`\n❌ Timeout after ${elapsed}s\n`); process.exit(1); }
276
+ continue;
277
+ }
278
+ data = await res.json();
279
+ } catch {
280
+ continue;
281
+ }
282
+
283
+ // Process incremental events
284
+ if (data.events?.length) {
285
+ for (const ev of data.events) {
286
+ if (ev.seq > lastSeq) lastSeq = ev.seq;
287
+ if (json) continue; // skip printing in json mode
288
+ switch (ev.type) {
289
+ case 'content': {
290
+ const newText = ev.data?.text || '';
291
+ process.stdout.write(newText);
292
+ printedText += newText;
293
+ break;
294
+ }
295
+ case 'status':
296
+ process.stderr.write(`\r⏳ ${ev.data?.text || ''}`);
297
+ break;
298
+ case 'tool_call':
299
+ process.stderr.write(`\n🔧 ${ev.data?.tool || ''}`);
300
+ if (ev.data?.input?.editPrompt) process.stderr.write(`\n editPrompt: ${ev.data.input.editPrompt}`);
301
+ if (ev.data?.input?.model) process.stderr.write(`\n model: ${ev.data.input.model}`);
302
+ if (ev.data?.input?.description) process.stderr.write(`: ${ev.data.input.description.substring(0, 80)}`);
303
+ process.stderr.write('\n');
304
+ break;
305
+ case 'image':
306
+ process.stderr.write(`\n🖼️ Image: ${ev.data?.imageUrl || '(uploading...)'}\n`);
307
+ break;
308
+ case 'render':
309
+ if (ev.data?.published) {
310
+ const desc = ev.data.animation
311
+ ? `${ev.data.animation.durationInSeconds}s video (${ev.data.width}x${ev.data.height})`
312
+ : `still design (${ev.data.width}x${ev.data.height})`;
313
+ process.stderr.write(`\n🎨 Design published: ${desc}\n`);
314
+ }
315
+ break;
316
+ case 'animation_task':
317
+ process.stderr.write(`\n🎬 Video submitted: ${ev.data?.taskId}\n`);
318
+ break;
319
+ case 'music_task':
320
+ process.stderr.write(`\n🎵 Music submitted: ${ev.data?.taskId}\n`);
321
+ break;
322
+ case 'error':
323
+ process.stderr.write(`\n❌ Error: ${ev.data?.message}\n`);
324
+ break;
325
+ }
326
+ }
327
+ } else if (!json) {
328
+ process.stderr.write(`\r⏳ Working... ${elapsed}s (${data.eventCount || 0} events)`);
329
+ }
330
+
331
+ // Check terminal status
332
+ if (data.status === 'completed' || data.status === 'failed' || data.status === 'aborted') {
333
+ if (printedText && !json) process.stdout.write('\n');
334
+
335
+ if (json) {
336
+ // Structured JSON output — add projectUrl
337
+ data.projectUrl = `${APP_URL}/projects/${data.projectId}`;
338
+ console.log(JSON.stringify(data, null, 2));
339
+ } else {
340
+ process.stderr.write('\n━━━ Results ━━━\n');
341
+ if (data.result) {
342
+ for (const img of data.result.images || []) process.stderr.write(`🖼️ Image: ${img.imageUrl}\n`);
343
+ for (const d of data.result.designs || []) process.stderr.write(`🎨 Design (${d.width}x${d.height})\n`);
344
+ for (const v of data.result.videos || []) {
345
+ if (v.videoUrl) process.stderr.write(`🎬 Video: ${v.videoUrl}\n`);
346
+ else process.stderr.write(`🎬 Video ${v.taskId}: ${v.status || 'submitted'}\n`);
347
+ }
348
+ for (const m of data.result.music || []) {
349
+ if (m.audioUrl) process.stderr.write(`🎵 Music: ${m.audioUrl}\n`);
350
+ else process.stderr.write(`🎵 Music ${m.taskId}: ${m.status || 'submitted'}\n`);
351
+ }
352
+ if (data.result.error) process.stderr.write(`❌ ${data.result.error}\n`);
353
+ }
354
+ process.stderr.write(`🔗 ${APP_URL}/projects/${data.projectId}\n`);
355
+ }
356
+
357
+ if (data.status === 'failed') process.exit(1);
358
+ return data;
359
+ }
360
+ }
361
+ }
362
+
229
363
  // ─── Async Task Polling ──────────────────────────────────────────────────────
230
364
 
231
365
  async function pollVideo(baseUrl, headers, taskId) {
@@ -401,16 +535,27 @@ if (command === 'login') {
401
535
  let projectId = null;
402
536
  const chatImages = [];
403
537
  const promptParts = [];
538
+ let useStream = false;
539
+ let background = false;
540
+ let jsonOutput = false;
541
+ let videoModel = undefined;
542
+ let preferredModel = undefined;
404
543
  for (let i = 1; i < args.length; i++) {
405
544
  if (args[i] === '--project' && args[i + 1]) projectId = args[++i];
406
545
  else if (args[i] === '--image' && args[i + 1]) chatImages.push(args[++i]);
546
+ else if (args[i] === '--stream') useStream = true;
547
+ else if (args[i] === '--background' || args[i] === '-b') background = true;
548
+ else if (args[i] === '--json') jsonOutput = true;
549
+ else if (args[i] === '--video-model' && args[i + 1]) videoModel = args[++i];
550
+ else if (args[i] === '--model' && args[i + 1]) preferredModel = args[++i];
407
551
  else promptParts.push(args[i]);
408
552
  }
409
553
  const prompt = promptParts.join(' ');
410
554
  if (!projectId || !prompt) {
411
- console.error('Usage: makaron chat --project <id> [--image <file>] "your message"');
555
+ console.error('Usage: makaron chat --project <id> [--image <file>] [--stream] [--background|-b] [--json] "your message"');
412
556
  process.exit(1);
413
557
  }
558
+ // Upload images if provided
414
559
  if (chatImages.length > 0) {
415
560
  const base64s = chatImages.map(imgPath => {
416
561
  process.stderr.write(`📤 Uploading ${path.basename(imgPath)}...\n`);
@@ -429,13 +574,83 @@ if (command === 'login') {
429
574
  process.stderr.write(`⚠️ Failed to upload images: ${await res.text()}\n`);
430
575
  }
431
576
  }
432
- const { results } = await streamAgent(baseUrl, headers, projectId, prompt);
433
- process.stderr.write('\n━━━ Results ━━━\n');
434
- for (const img of results.images) process.stderr.write(`🖼️ Image: ${img.imageUrl}\n`);
435
- for (const d of results.designs) process.stderr.write(`🎨 ${d.desc}\n`);
436
- process.stderr.write(`🔗 ${APP_URL}/projects/${projectId}\n`);
437
- for (const task of results.animationTasks) await pollVideo(baseUrl, headers, task.taskId);
438
- for (const task of results.musicTasks) await pollMusic(baseUrl, headers, task.taskId);
577
+
578
+ if (useStream) {
579
+ // Legacy SSE mode
580
+ const { results } = await streamAgent(baseUrl, headers, projectId, prompt);
581
+ process.stderr.write('\n━━━ Results ━━━\n');
582
+ for (const img of results.images) process.stderr.write(`🖼️ Image: ${img.imageUrl}\n`);
583
+ for (const d of results.designs) process.stderr.write(`🎨 ${d.desc}\n`);
584
+ process.stderr.write(`🔗 ${APP_URL}/projects/${projectId}\n`);
585
+ for (const task of results.animationTasks) await pollVideo(baseUrl, headers, task.taskId);
586
+ for (const task of results.musicTasks) await pollMusic(baseUrl, headers, task.taskId);
587
+ } else {
588
+ // Default: fire-and-forget + poll
589
+ const { runId } = await submitRun(baseUrl, headers, projectId, prompt, { videoModel, preferredModel });
590
+ if (background) {
591
+ // Just print runId and exit
592
+ if (jsonOutput) {
593
+ console.log(JSON.stringify({ runId, projectId, projectUrl: `${APP_URL}/projects/${projectId}`, status: 'running' }));
594
+ } else {
595
+ console.log(runId);
596
+ }
597
+ } else {
598
+ process.stderr.write(`🚀 Run started: ${runId}\n`);
599
+ await pollRun(baseUrl, headers, runId, { json: jsonOutput });
600
+ }
601
+ }
602
+ } else if (command === 'responses' || command === 'run') {
603
+ const { headers, baseUrl } = getAuth();
604
+ const sub = args[1];
605
+
606
+ if (sub === 'get') {
607
+ const runId = args[2];
608
+ if (!runId) { console.error('Usage: makaron responses get <runId> [--wait] [--json]'); process.exit(1); }
609
+ let wait = false, jsonOutput = false, waitForArtifacts = false;
610
+ for (let i = 3; i < args.length; i++) {
611
+ if (args[i] === '--wait') wait = true;
612
+ if (args[i] === '--json') jsonOutput = true;
613
+ if (args[i] === '--wait-artifacts') waitForArtifacts = true;
614
+ }
615
+
616
+ if (wait) {
617
+ await pollRun(baseUrl, headers, runId, { json: jsonOutput, waitForArtifacts });
618
+ } else {
619
+ const params = new URLSearchParams();
620
+ if (waitForArtifacts) params.set('wait_for_artifacts', 'true');
621
+ const res = await fetch(`${baseUrl}/api/agent/run/${runId}?${params}`, { headers });
622
+ if (!res.ok) { console.error(`Error ${res.status}:`, await res.text()); process.exit(1); }
623
+ const data = await res.json();
624
+ console.log(JSON.stringify(data, null, 2));
625
+ }
626
+
627
+ } else if (sub === 'list') {
628
+ let projectId = null;
629
+ for (let i = 2; i < args.length; i++) {
630
+ if (args[i] === '--project' && args[i + 1]) projectId = args[++i];
631
+ }
632
+ if (!projectId) { console.error('Usage: makaron responses list --project <id>'); process.exit(1); }
633
+ // Query runs for project
634
+ const res = await fetch(`${baseUrl}/api/agent/run?projectId=${projectId}`, { headers });
635
+ if (!res.ok) { console.error(`Error ${res.status}:`, await res.text()); process.exit(1); }
636
+ const data = await res.json();
637
+ if (data.runs?.length) {
638
+ for (const r of data.runs) {
639
+ const age = timeSince(new Date(r.started_at));
640
+ console.log(` ${r.id} ${r.status.padEnd(10)} ${age} ${(r.prompt || '').slice(0, 50)}`);
641
+ }
642
+ } else {
643
+ console.log('No runs found for this project.');
644
+ }
645
+
646
+ } else {
647
+ console.log(`Responses commands:
648
+ responses get <runId> Get run status and results
649
+ responses get <runId> --wait Poll until completed
650
+ responses get <runId> --wait-artifacts Wait for video/music too
651
+ responses list --project <id> List runs for a project
652
+ `);
653
+ }
439
654
  } else if (command === 'list' || command === 'ls') {
440
655
  const { headers, baseUrl } = getAuth();
441
656
  await listProjects(baseUrl, headers);
@@ -696,8 +911,15 @@ Commands:
696
911
  create --image <file> Create project from local image
697
912
  create --image-url <url> Create project from URL
698
913
  create --title "name" Create empty project (text-to-image)
699
- chat --project <id> "message" Chat with Makaron Agent
700
- chat --project <id> --image <file> "message" Add image + chat
914
+
915
+ chat --project <id> "message" Chat (non-blocking, polls for result)
916
+ chat --project <id> -b "message" Background: submit and print runId
917
+ chat --project <id> --stream "msg" Legacy: stream SSE in real-time
918
+ chat --project <id> --json "msg" Output structured JSON result
919
+
920
+ responses get <runId> Get run status and results
921
+ responses get <runId> --wait Poll until completed
922
+ responses list --project <id> List runs for a project
701
923
  abort <runId> Abort a running Agent
702
924
 
703
925
  edit [--image <file>] "prompt" AI image edit / text-to-image
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "makaron-cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Talk to Makaron Agent from the terminal — create projects, edit images, generate videos",
5
5
  "type": "module",
6
6
  "bin": {