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.
- package/README.md +118 -87
- package/bin/makaron.mjs +232 -10
- 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
|
-
### `
|
|
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
|
|
46
|
+
npx makaron-cli chat --project <id> "make it look cinematic"
|
|
43
47
|
```
|
|
44
48
|
|
|
45
|
-
|
|
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
|
-
|
|
50
|
-
|
|
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
|
-
|
|
61
|
+
**Structured JSON output (for programmatic use):**
|
|
54
62
|
|
|
55
63
|
```bash
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
64
|
+
npx makaron-cli chat --project <id> --json --background "edit the photo"
|
|
65
|
+
# → {"runId":"...","projectId":"...","projectUrl":"...","status":"running"}
|
|
66
|
+
```
|
|
59
67
|
|
|
60
|
-
|
|
61
|
-
npx makaron-cli create --image-url https://example.com/photo.jpg
|
|
68
|
+
**Legacy streaming mode (real-time SSE):**
|
|
62
69
|
|
|
63
|
-
|
|
64
|
-
npx makaron-cli
|
|
70
|
+
```bash
|
|
71
|
+
npx makaron-cli chat --project <id> --stream "hello"
|
|
65
72
|
```
|
|
66
73
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
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
|
-
|
|
125
|
+
### `list` — Show all projects
|
|
79
126
|
|
|
80
127
|
```bash
|
|
81
|
-
npx makaron-cli
|
|
128
|
+
npx makaron-cli list
|
|
82
129
|
```
|
|
83
130
|
|
|
84
|
-
|
|
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
|
-
#
|
|
91
|
-
npx makaron-cli
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
177
|
-
# 1. Login (once)
|
|
178
|
-
npx makaron-cli login
|
|
208
|
+
For AI agents (Claude Code, OpenClaw, etc.) calling this CLI programmatically:
|
|
179
209
|
|
|
180
|
-
|
|
181
|
-
npx makaron-cli create --image my-photo.jpg
|
|
182
|
-
# → ID: proj_abc123
|
|
210
|
+
### Non-blocking workflow (recommended)
|
|
183
211
|
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
#
|
|
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
|
-
#
|
|
191
|
-
npx makaron-cli
|
|
218
|
+
# 3. Poll for result when ready
|
|
219
|
+
npx makaron-cli responses get $RUN_ID --wait
|
|
192
220
|
```
|
|
193
221
|
|
|
194
|
-
|
|
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
|
-
|
|
229
|
+
# Poll
|
|
230
|
+
npx makaron-cli responses get $RUN_ID --wait
|
|
231
|
+
```
|
|
197
232
|
|
|
198
|
-
###
|
|
233
|
+
### Sequential multi-turn
|
|
199
234
|
|
|
200
235
|
```bash
|
|
201
|
-
|
|
202
|
-
npx makaron-cli
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
260
|
+
### Output types in result
|
|
228
261
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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
|
-
|
|
700
|
-
chat --project <id>
|
|
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
|