makaron-cli 0.3.0 → 0.5.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 -176
- package/SKILL.md +197 -0
- package/bin/makaron.mjs +319 -10
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -1,256 +1,198 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Makaron CLI — Agent Integration Skill
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Makaron is a multimodal AI creative agent. You talk to it via `makaron chat`, and it produces images, videos, music, and animated designs — all saved to a persistent project.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## Install
|
|
5
|
+
## Setup
|
|
8
6
|
|
|
9
7
|
```bash
|
|
10
|
-
npx makaron-cli # run directly (always latest)
|
|
11
|
-
# or
|
|
12
8
|
npm install -g makaron-cli
|
|
13
|
-
|
|
9
|
+
# or use directly: npx makaron-cli
|
|
14
10
|
|
|
15
|
-
|
|
11
|
+
export MAKARON_API_KEY=mk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
12
|
+
```
|
|
16
13
|
|
|
17
|
-
|
|
14
|
+
Verify: `npx makaron-cli list` should show projects.
|
|
18
15
|
|
|
19
|
-
|
|
16
|
+
## Core Workflow
|
|
20
17
|
|
|
21
18
|
```bash
|
|
22
|
-
|
|
23
|
-
npx makaron-cli
|
|
24
|
-
```
|
|
19
|
+
# 1. Create project with image(s)
|
|
20
|
+
PROJECT_ID=$(npx makaron-cli create --image photo.jpg 2>/dev/null | grep "ID:" | awk '{print $2}')
|
|
25
21
|
|
|
26
|
-
|
|
22
|
+
# Multi-image project:
|
|
23
|
+
PROJECT_ID=$(npx makaron-cli create --image img1.jpg --image img2.jpg 2>/dev/null | grep "ID:" | awk '{print $2}')
|
|
27
24
|
|
|
28
|
-
|
|
25
|
+
# 2. Submit creative request (non-blocking)
|
|
26
|
+
RUN_ID=$(npx makaron-cli chat --project $PROJECT_ID -b "make it cinematic and create a 5s video")
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
npx makaron-cli
|
|
32
|
-
# Enter email + password → token saved to ~/.makaron/auth.json
|
|
28
|
+
# 3. Watch until all artifacts are ready
|
|
29
|
+
npx makaron-cli responses watch $RUN_ID --jsonl
|
|
33
30
|
```
|
|
34
31
|
|
|
35
|
-
|
|
32
|
+
## Primary: `chat` (Agent-driven creative work)
|
|
36
33
|
|
|
37
|
-
|
|
34
|
+
Use `chat` for all creative tasks. Makaron Agent decides how to execute — it can edit images, generate videos, compose music, and create designs in a single conversation.
|
|
38
35
|
|
|
39
|
-
###
|
|
36
|
+
### Submit a request
|
|
40
37
|
|
|
41
38
|
```bash
|
|
42
|
-
npx makaron-cli
|
|
39
|
+
npx makaron-cli chat --project <id> --json -b "<prompt>"
|
|
43
40
|
```
|
|
44
41
|
|
|
45
|
-
|
|
42
|
+
Returns immediately:
|
|
43
|
+
```json
|
|
44
|
+
{"runId": "xxx", "projectId": "...", "projectUrl": "https://www.makaron.app/projects/...", "status": "running"}
|
|
46
45
|
```
|
|
47
|
-
📁 12 projects
|
|
48
46
|
|
|
49
|
-
|
|
50
|
-
xyz789ghi Sunset Edit 5 snaps 1d ago
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
### `create` — Create a new project
|
|
47
|
+
### With additional images
|
|
54
48
|
|
|
55
49
|
```bash
|
|
56
|
-
#
|
|
57
|
-
npx makaron-cli
|
|
58
|
-
npx makaron-cli create --image img1.jpg --image img2.jpg
|
|
59
|
-
|
|
60
|
-
# From URL(s)
|
|
61
|
-
npx makaron-cli create --image-url https://example.com/photo.jpg
|
|
62
|
-
|
|
63
|
-
# Empty project (for text-to-image)
|
|
64
|
-
npx makaron-cli create --title "My New Project"
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
Output:
|
|
50
|
+
# Add reference images to project before chatting
|
|
51
|
+
npx makaron-cli chat --project <id> --image ref1.jpg --image ref2.jpg -b "use these as style reference"
|
|
68
52
|
```
|
|
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
|
-
```
|
|
75
|
-
|
|
76
|
-
### `chat` — Send a message to Makaron Agent
|
|
77
53
|
|
|
78
|
-
|
|
54
|
+
### Check status (single query)
|
|
79
55
|
|
|
80
56
|
```bash
|
|
81
|
-
npx makaron-cli
|
|
57
|
+
npx makaron-cli responses get <runId> --json
|
|
82
58
|
```
|
|
83
59
|
|
|
84
|
-
|
|
85
|
-
- **Text** → stdout (pipe-friendly)
|
|
86
|
-
- **Status/progress** → stderr
|
|
87
|
-
- **Images, videos, music** → URLs printed to stderr
|
|
60
|
+
### Watch until done (streaming events)
|
|
88
61
|
|
|
89
62
|
```bash
|
|
90
|
-
|
|
91
|
-
npx makaron-cli chat --project <id> --image ref.jpg "use this style"
|
|
63
|
+
npx makaron-cli responses watch <runId> --jsonl
|
|
92
64
|
```
|
|
93
65
|
|
|
94
|
-
|
|
66
|
+
Outputs one JSON per line as artifacts appear:
|
|
67
|
+
```
|
|
68
|
+
{"event":"output.added","item":{"id":"out_1","type":"image","status":"completed","url":"https://..."}}
|
|
69
|
+
{"event":"output.added","item":{"id":"out_2","type":"video","status":"rendering","task_id":"xxx"}}
|
|
70
|
+
{"event":"output.updated","item":{"id":"out_2","type":"video","status":"completed","url":"https://..."}}
|
|
71
|
+
{"event":"done","status":"completed"}
|
|
72
|
+
```
|
|
95
73
|
|
|
96
|
-
###
|
|
74
|
+
### Extract specific results
|
|
97
75
|
|
|
98
76
|
```bash
|
|
99
|
-
npx makaron-cli
|
|
77
|
+
npx makaron-cli responses get <runId> --pick first_image_url
|
|
78
|
+
npx makaron-cli responses get <runId> --pick image_urls # all images (JSON array)
|
|
79
|
+
npx makaron-cli responses get <runId> --pick first_video_url
|
|
80
|
+
npx makaron-cli responses get <runId> --pick video_urls # all videos
|
|
81
|
+
npx makaron-cli responses get <runId> --pick project_url
|
|
82
|
+
npx makaron-cli responses get <runId> --pick text # agent's text reply
|
|
83
|
+
npx makaron-cli responses get <runId> --pick output # full output array
|
|
84
|
+
npx makaron-cli responses get <runId> --pick status
|
|
100
85
|
```
|
|
101
86
|
|
|
102
|
-
|
|
87
|
+
## Fallback: Direct tool calls (no project context)
|
|
103
88
|
|
|
104
|
-
|
|
89
|
+
Use these only when `chat` is unavailable or you need raw model access without project/conversation context.
|
|
105
90
|
|
|
106
|
-
|
|
91
|
+
### `edit` — One-shot image editing
|
|
107
92
|
|
|
108
93
|
```bash
|
|
109
|
-
# Text-to-image (no input image)
|
|
110
|
-
npx makaron-cli edit "a cyberpunk cityscape at night, neon reflections"
|
|
111
|
-
|
|
112
94
|
# Edit an existing image
|
|
113
95
|
npx makaron-cli edit --image photo.jpg "add cinematic warm lighting"
|
|
114
96
|
|
|
115
|
-
#
|
|
116
|
-
npx makaron-cli edit
|
|
97
|
+
# Text-to-image (no input)
|
|
98
|
+
npx makaron-cli edit "a cyberpunk cityscape at night"
|
|
117
99
|
|
|
118
|
-
# With reference
|
|
119
|
-
npx makaron-cli edit --image photo.jpg --
|
|
100
|
+
# With model/skill/reference
|
|
101
|
+
npx makaron-cli edit --image photo.jpg --model openai --skill captions "add title"
|
|
102
|
+
npx makaron-cli edit --image photo.jpg --ref style.jpg "match this style"
|
|
120
103
|
|
|
121
|
-
#
|
|
122
|
-
npx makaron-cli edit --
|
|
104
|
+
# Output to file
|
|
105
|
+
npx makaron-cli edit --image photo.jpg --out result.jpg "make it dramatic"
|
|
123
106
|
```
|
|
124
107
|
|
|
125
|
-
Options:
|
|
126
|
-
- `--image <file|url>` — input image (omit for text-to-image)
|
|
127
|
-
- `--model gemini|qwen|openai|pony|wai` — model selection (default: auto)
|
|
128
|
-
- `--skill enhance|creative|wild|captions` — activate skill template
|
|
129
|
-
- `--ref <file|url>` — reference image (repeatable, up to 3)
|
|
130
|
-
- `--aspect <ratio>` — target aspect ratio (e.g. `4:5`, `1:1`, `16:9`)
|
|
131
|
-
- `--out <path>` — output file path (default: `makaron-output-{timestamp}.jpg`)
|
|
108
|
+
Options: `--image`, `--model gemini|qwen|openai|pony|wai`, `--skill enhance|creative|wild|captions`, `--ref <file>` (up to 3), `--aspect <ratio>`, `--out <path>`
|
|
132
109
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
### `video` — Video generation
|
|
110
|
+
### `video` — Video generation (3 steps)
|
|
136
111
|
|
|
137
112
|
```bash
|
|
138
|
-
# Write
|
|
139
|
-
npx makaron-cli video script --image img1.jpg
|
|
140
|
-
npx makaron-cli video script --image img1.jpg --image img2.jpg --lang zh "电影感故事"
|
|
113
|
+
# 1. Write script from images
|
|
114
|
+
npx makaron-cli video script --image img1.jpg "cinematic story"
|
|
141
115
|
|
|
142
|
-
# Submit
|
|
143
|
-
npx makaron-cli video create --script "Shot 1..." --image https://...
|
|
144
|
-
npx makaron-cli video create --script-file script.txt --image https://...jpg --model seedance
|
|
116
|
+
# 2. Submit rendering (images must be public URLs from step 1 or uploaded)
|
|
117
|
+
npx makaron-cli video create --script "Shot 1 (5s): <<<image_1>>> ..." --image https://...jpg --duration 5 --model kling
|
|
145
118
|
|
|
146
|
-
# Check status
|
|
119
|
+
# 3. Check status
|
|
147
120
|
npx makaron-cli video status <taskId>
|
|
148
121
|
```
|
|
149
122
|
|
|
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)
|
|
123
|
+
Options for `video create`: `--script "..."`, `--script-file <path>`, `--image <url>` (repeatable, up to 7), `--duration 3|5|7|10|15`, `--aspect 9:16|16:9|1:1`, `--model kling|seedance`
|
|
156
124
|
|
|
157
125
|
### `music` — Music generation
|
|
158
126
|
|
|
159
127
|
```bash
|
|
160
|
-
# Generate instrumental music
|
|
161
128
|
npx makaron-cli music create "gentle piano, warm strings, cinematic"
|
|
162
|
-
|
|
163
|
-
# With vocals and style
|
|
164
|
-
npx makaron-cli music create --vocals --style "lo-fi, ambient" "rainy day vibes"
|
|
165
|
-
|
|
166
|
-
# Check status
|
|
129
|
+
npx makaron-cli music create --vocals --style "lo-fi" "rainy day vibes"
|
|
167
130
|
npx makaron-cli music status <taskId>
|
|
168
131
|
```
|
|
169
132
|
|
|
170
|
-
Options
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
npx makaron-cli chat --project proj_abc123 "add epic orchestral background music"
|
|
133
|
+
Options: `--vocals` (include vocals), `--style "genre"`
|
|
134
|
+
|
|
135
|
+
## Response Schema
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
type MakaronRunResponse = {
|
|
139
|
+
id: string
|
|
140
|
+
status: "in_progress" | "completed" | "failed" | "aborted"
|
|
141
|
+
incomplete: boolean // true = keep polling
|
|
142
|
+
project_id: string
|
|
143
|
+
project_url: string
|
|
144
|
+
next_poll_after_ms?: number // suggested poll interval
|
|
145
|
+
output: MakaronOutput[]
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
type MakaronOutput =
|
|
149
|
+
| { id: string; type: "text"; status: "completed"; content: string }
|
|
150
|
+
| { id: string; type: "image"; status: "completed"; url: string; snapshot_id: string }
|
|
151
|
+
| { id: string; type: "design"; status: "completed"; url: string; width: number; height: number; animated: boolean; duration?: number }
|
|
152
|
+
| { id: string; type: "video"; status: "queued"|"rendering"|"completed"|"failed"; task_id: string; url?: string; elapsed_seconds?: number }
|
|
153
|
+
| { id: string; type: "music"; status: "queued"|"rendering"|"completed"|"failed"; task_id: string; url?: string; elapsed_seconds?: number }
|
|
192
154
|
```
|
|
193
155
|
|
|
194
|
-
##
|
|
195
|
-
|
|
196
|
-
For AI agents (Claude Code, Cursor, etc.) calling this CLI programmatically:
|
|
197
|
-
|
|
198
|
-
### Setup (one-time)
|
|
156
|
+
## Polling Rules
|
|
199
157
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
158
|
+
1. Poll while `incomplete: true` or `status` is `"in_progress"`
|
|
159
|
+
2. Use `next_poll_after_ms` as interval (default 5000ms)
|
|
160
|
+
3. Stop when `status` is `"completed"`, `"failed"`, or `"aborted"`
|
|
161
|
+
4. Top-level `status: "completed"` means ALL artifacts are ready (including rendered videos)
|
|
204
162
|
|
|
205
|
-
|
|
163
|
+
## Exit Codes
|
|
206
164
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
165
|
+
| Code | Meaning |
|
|
166
|
+
|------|---------|
|
|
167
|
+
| 0 | Success (completed) or valid in-progress response |
|
|
168
|
+
| 1 | Failed, aborted, or HTTP error |
|
|
169
|
+
| 2 | Timeout (partial response still printed to stdout) |
|
|
212
170
|
|
|
213
|
-
|
|
171
|
+
## What Makaron Agent Can Do
|
|
214
172
|
|
|
215
|
-
|
|
|
216
|
-
|
|
217
|
-
|
|
|
173
|
+
| Task | Example prompt |
|
|
174
|
+
|------|---------------|
|
|
175
|
+
| Edit photo | "make it cinematic with warm tones" |
|
|
218
176
|
| Style transfer | "convert to oil painting style" |
|
|
219
|
-
| Add elements | "add a cat
|
|
220
|
-
|
|
|
221
|
-
|
|
|
222
|
-
| Video
|
|
223
|
-
| Video with script | "make a video: camera slowly zooms in while leaves fall" |
|
|
177
|
+
| Add/remove elements | "add a cat on the table" / "remove background person" |
|
|
178
|
+
| Text-to-image | "generate a cyberpunk cityscape" |
|
|
179
|
+
| Video from image | "create a 5 second video of her walking" |
|
|
180
|
+
| Video with model | "用seedance模型做5秒视频" |
|
|
224
181
|
| Background music | "add calm piano music" |
|
|
225
|
-
|
|
|
226
|
-
|
|
227
|
-
### Output parsing
|
|
228
|
-
|
|
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)
|
|
237
|
-
|
|
238
|
-
### Environment variables
|
|
182
|
+
| Motion design | "create an Instagram story with animated text" |
|
|
183
|
+
| Multi-step | "edit the photo then make a video from it" |
|
|
239
184
|
|
|
240
|
-
|
|
241
|
-
|----------|---------|---------|
|
|
242
|
-
| `MAKARON_API_KEY` | API key (`mk_live_xxx`) | — |
|
|
243
|
-
| `MAKARON_URL` | API endpoint override | `https://www.makaron.app` |
|
|
244
|
-
|
|
245
|
-
## Viewing results
|
|
246
|
-
|
|
247
|
-
All generated content (images, videos, designs) is saved to the project and visible at:
|
|
248
|
-
|
|
249
|
-
```
|
|
250
|
-
https://www.makaron.app/projects/<project-id>
|
|
251
|
-
```
|
|
185
|
+
## Important Notes
|
|
252
186
|
|
|
253
|
-
|
|
187
|
+
- One project = one conversation thread. All history is preserved.
|
|
188
|
+
- One run at a time per project. New message interrupts previous run.
|
|
189
|
+
- Multi-image: `create --image a.jpg --image b.jpg` or `chat --image ref.jpg`.
|
|
190
|
+
- Videos take 2-5 minutes to render. Use `watch` to get URL when ready.
|
|
191
|
+
- Music takes ~60 seconds. Appears in output when done.
|
|
192
|
+
- Images are typically ready in 15-30 seconds.
|
|
193
|
+
- stdout is always machine-readable JSON/text. Human-friendly logs go to stderr.
|
|
194
|
+
- Always use `chat` as the primary interface — even for single image edits.
|
|
195
|
+
- `edit`/`video`/`music` are fallback tools for when `chat` is unavailable or you need raw model access without project context.
|
|
254
196
|
|
|
255
197
|
## Admin: Skill Marketplace Operations
|
|
256
198
|
|
|
@@ -272,7 +214,7 @@ npx makaron-cli admin upload cover.jpg marketplace/covers/skill-name.jpg
|
|
|
272
214
|
npx makaron-cli admin upload before.jpg marketplace/before/before-name.jpg
|
|
273
215
|
|
|
274
216
|
# Upload skill zip
|
|
275
|
-
npx makaron-cli admin upload skill.zip marketplace/skills/skill-name.zip
|
|
217
|
+
npx makaron-cli admin upload skill-name.zip marketplace/skills/skill-name.zip
|
|
276
218
|
```
|
|
277
219
|
|
|
278
220
|
Storage paths follow this convention:
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: makaron
|
|
3
|
+
description: Use Makaron CLI to generate AI images, videos, music, and motion designs. Trigger when user needs creative media production — photo editing, video generation, music composition, or design creation. Requires `npx makaron-cli` and MAKARON_API_KEY env var.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Makaron CLI — Agent Integration Skill
|
|
7
|
+
|
|
8
|
+
Makaron is a multimodal AI creative agent. You talk to it via `makaron chat`, and it produces images, videos, music, and animated designs — all saved to a persistent project.
|
|
9
|
+
|
|
10
|
+
## Setup
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
export MAKARON_API_KEY=mk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Verify: `npx makaron-cli list` should show projects.
|
|
17
|
+
|
|
18
|
+
## Core Workflow
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# 1. Create project with image(s)
|
|
22
|
+
PROJECT_ID=$(npx makaron-cli create --image photo.jpg 2>/dev/null | grep "ID:" | awk '{print $2}')
|
|
23
|
+
|
|
24
|
+
# Multi-image project:
|
|
25
|
+
PROJECT_ID=$(npx makaron-cli create --image img1.jpg --image img2.jpg 2>/dev/null | grep "ID:" | awk '{print $2}')
|
|
26
|
+
|
|
27
|
+
# 2. Submit creative request (non-blocking)
|
|
28
|
+
RUN_ID=$(npx makaron-cli chat --project $PROJECT_ID -b "make it cinematic and create a 5s video")
|
|
29
|
+
|
|
30
|
+
# 3. Watch until all artifacts are ready
|
|
31
|
+
npx makaron-cli responses watch $RUN_ID --jsonl
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Primary: `chat` (Agent-driven creative work)
|
|
35
|
+
|
|
36
|
+
Use `chat` for all creative tasks. Makaron Agent decides how to execute — it can edit images, generate videos, compose music, and create designs in a single conversation.
|
|
37
|
+
|
|
38
|
+
### Submit a request
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx makaron-cli chat --project <id> --json -b "<prompt>"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Returns immediately:
|
|
45
|
+
```json
|
|
46
|
+
{"runId": "xxx", "projectId": "...", "projectUrl": "https://www.makaron.app/projects/...", "status": "running"}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### With additional images
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Add reference images to project before chatting
|
|
53
|
+
npx makaron-cli chat --project <id> --image ref1.jpg --image ref2.jpg -b "use these as style reference"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Check status (single query)
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npx makaron-cli responses get <runId> --json
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Watch until done (streaming events)
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npx makaron-cli responses watch <runId> --jsonl
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Outputs one JSON per line as artifacts appear:
|
|
69
|
+
```
|
|
70
|
+
{"event":"output.added","item":{"id":"out_1","type":"image","status":"completed","url":"https://..."}}
|
|
71
|
+
{"event":"output.added","item":{"id":"out_2","type":"video","status":"rendering","task_id":"xxx"}}
|
|
72
|
+
{"event":"output.updated","item":{"id":"out_2","type":"video","status":"completed","url":"https://..."}}
|
|
73
|
+
{"event":"done","status":"completed"}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Extract specific results
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npx makaron-cli responses get <runId> --pick first_image_url
|
|
80
|
+
npx makaron-cli responses get <runId> --pick image_urls # all images (JSON array)
|
|
81
|
+
npx makaron-cli responses get <runId> --pick first_video_url
|
|
82
|
+
npx makaron-cli responses get <runId> --pick video_urls # all videos
|
|
83
|
+
npx makaron-cli responses get <runId> --pick project_url
|
|
84
|
+
npx makaron-cli responses get <runId> --pick text # agent's text reply
|
|
85
|
+
npx makaron-cli responses get <runId> --pick output # full output array
|
|
86
|
+
npx makaron-cli responses get <runId> --pick status
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Fallback: Direct tool calls (no project context)
|
|
90
|
+
|
|
91
|
+
Use these only when `chat` is unavailable or you need raw model access without project/conversation context.
|
|
92
|
+
|
|
93
|
+
### `edit` — One-shot image editing
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Edit an existing image
|
|
97
|
+
npx makaron-cli edit --image photo.jpg "add cinematic warm lighting"
|
|
98
|
+
|
|
99
|
+
# Text-to-image (no input)
|
|
100
|
+
npx makaron-cli edit "a cyberpunk cityscape at night"
|
|
101
|
+
|
|
102
|
+
# With model/skill/reference
|
|
103
|
+
npx makaron-cli edit --image photo.jpg --model openai --skill captions "add title"
|
|
104
|
+
npx makaron-cli edit --image photo.jpg --ref style.jpg "match this style"
|
|
105
|
+
|
|
106
|
+
# Output to file
|
|
107
|
+
npx makaron-cli edit --image photo.jpg --out result.jpg "make it dramatic"
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Options: `--image`, `--model gemini|qwen|openai|pony|wai`, `--skill enhance|creative|wild|captions`, `--ref <file>` (up to 3), `--aspect <ratio>`, `--out <path>`
|
|
111
|
+
|
|
112
|
+
### `video` — Video generation (3 steps)
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
# 1. Write script from images
|
|
116
|
+
npx makaron-cli video script --image img1.jpg "cinematic story"
|
|
117
|
+
|
|
118
|
+
# 2. Submit rendering (images must be public URLs from step 1 or uploaded)
|
|
119
|
+
npx makaron-cli video create --script "Shot 1 (5s): <<<image_1>>> ..." --image https://...jpg --duration 5 --model kling
|
|
120
|
+
|
|
121
|
+
# 3. Check status
|
|
122
|
+
npx makaron-cli video status <taskId>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Options for `video create`: `--script "..."`, `--script-file <path>`, `--image <url>` (repeatable, up to 7), `--duration 3|5|7|10|15`, `--aspect 9:16|16:9|1:1`, `--model kling|seedance`
|
|
126
|
+
|
|
127
|
+
### `music` — Music generation
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
npx makaron-cli music create "gentle piano, warm strings, cinematic"
|
|
131
|
+
npx makaron-cli music create --vocals --style "lo-fi" "rainy day vibes"
|
|
132
|
+
npx makaron-cli music status <taskId>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Options: `--vocals` (include vocals), `--style "genre"`
|
|
136
|
+
|
|
137
|
+
## Response Schema
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
type MakaronRunResponse = {
|
|
141
|
+
id: string
|
|
142
|
+
status: "in_progress" | "completed" | "failed" | "aborted"
|
|
143
|
+
incomplete: boolean // true = keep polling
|
|
144
|
+
project_id: string
|
|
145
|
+
project_url: string
|
|
146
|
+
next_poll_after_ms?: number // suggested poll interval
|
|
147
|
+
output: MakaronOutput[]
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
type MakaronOutput =
|
|
151
|
+
| { id: string; type: "text"; status: "completed"; content: string }
|
|
152
|
+
| { id: string; type: "image"; status: "completed"; url: string; snapshot_id: string }
|
|
153
|
+
| { id: string; type: "design"; status: "completed"; url: string; width: number; height: number; animated: boolean; duration?: number }
|
|
154
|
+
| { id: string; type: "video"; status: "queued"|"rendering"|"completed"|"failed"; task_id: string; url?: string; elapsed_seconds?: number }
|
|
155
|
+
| { id: string; type: "music"; status: "queued"|"rendering"|"completed"|"failed"; task_id: string; url?: string; elapsed_seconds?: number }
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Polling Rules
|
|
159
|
+
|
|
160
|
+
1. Poll while `incomplete: true` or `status` is `"in_progress"`
|
|
161
|
+
2. Use `next_poll_after_ms` as interval (default 5000ms)
|
|
162
|
+
3. Stop when `status` is `"completed"`, `"failed"`, or `"aborted"`
|
|
163
|
+
4. Top-level `status: "completed"` means ALL artifacts are ready (including rendered videos)
|
|
164
|
+
|
|
165
|
+
## Exit Codes
|
|
166
|
+
|
|
167
|
+
| Code | Meaning |
|
|
168
|
+
|------|---------|
|
|
169
|
+
| 0 | Success (completed) or valid in-progress response |
|
|
170
|
+
| 1 | Failed, aborted, or HTTP error |
|
|
171
|
+
| 2 | Timeout (partial response still printed to stdout) |
|
|
172
|
+
|
|
173
|
+
## What Makaron Agent Can Do
|
|
174
|
+
|
|
175
|
+
| Task | Example prompt |
|
|
176
|
+
|------|---------------|
|
|
177
|
+
| Edit photo | "make it cinematic with warm tones" |
|
|
178
|
+
| Style transfer | "convert to oil painting style" |
|
|
179
|
+
| Add/remove elements | "add a cat on the table" / "remove background person" |
|
|
180
|
+
| Text-to-image | "generate a cyberpunk cityscape" |
|
|
181
|
+
| Video from image | "create a 5 second video of her walking" |
|
|
182
|
+
| Video with model | "用seedance模型做5秒视频" |
|
|
183
|
+
| Background music | "add calm piano music" |
|
|
184
|
+
| Motion design | "create an Instagram story with animated text" |
|
|
185
|
+
| Multi-step | "edit the photo then make a video from it" |
|
|
186
|
+
|
|
187
|
+
## Important Notes
|
|
188
|
+
|
|
189
|
+
- One project = one conversation thread. All history is preserved.
|
|
190
|
+
- One run at a time per project. New message interrupts previous run.
|
|
191
|
+
- Multi-image: `create --image a.jpg --image b.jpg` or `chat --image ref.jpg`.
|
|
192
|
+
- Videos take 2-5 minutes to render. Use `watch` to get URL when ready.
|
|
193
|
+
- Music takes ~60 seconds. Appears in output when done.
|
|
194
|
+
- Images are typically ready in 15-30 seconds.
|
|
195
|
+
- stdout is always machine-readable JSON/text. Human-friendly logs go to stderr.
|
|
196
|
+
- Always use `chat` as the primary interface — even for single image edits.
|
|
197
|
+
- `edit`/`video`/`music` are fallback tools for when `chat` is unavailable or you need raw model access without project context.
|
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,211 @@ 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
|
+
|
|
363
|
+
// ─── Pick Helper ────────────────────────────────────────────────────────────
|
|
364
|
+
|
|
365
|
+
function applyPick(data, field) {
|
|
366
|
+
switch (field) {
|
|
367
|
+
case 'first_image_url': return data.output?.find(o => o.type === 'image')?.url || null;
|
|
368
|
+
case 'image_urls': return (data.output || []).filter(o => o.type === 'image' && o.url).map(o => o.url);
|
|
369
|
+
case 'first_video_url': return data.output?.find(o => o.type === 'video' && o.url)?.url || null;
|
|
370
|
+
case 'video_urls': return (data.output || []).filter(o => o.type === 'video' && o.url).map(o => o.url);
|
|
371
|
+
case 'first_design_url': return data.output?.find(o => o.type === 'design')?.url || null;
|
|
372
|
+
case 'design_urls': return (data.output || []).filter(o => o.type === 'design' && o.url).map(o => o.url);
|
|
373
|
+
case 'first_music_url': return data.output?.find(o => o.type === 'music' && o.url)?.url || null;
|
|
374
|
+
case 'music_urls': return (data.output || []).filter(o => o.type === 'music' && o.url).map(o => o.url);
|
|
375
|
+
case 'project_url': return data.project_url || null;
|
|
376
|
+
case 'output': return data.output || [];
|
|
377
|
+
case 'text': return data.output?.find(o => o.type === 'text')?.content || null;
|
|
378
|
+
case 'status': return data.status;
|
|
379
|
+
default: return data[field] !== undefined ? data[field] : null;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// ─── Watch (incremental event stream) ───────────────────────────────────────
|
|
384
|
+
|
|
385
|
+
async function watchRun(baseUrl, headers, runId, opts = {}) {
|
|
386
|
+
const { interval = 5000, jsonl = true } = opts;
|
|
387
|
+
let lastOutput = [];
|
|
388
|
+
const start = Date.now();
|
|
389
|
+
|
|
390
|
+
while (true) {
|
|
391
|
+
const elapsed = Math.round((Date.now() - start) / 1000);
|
|
392
|
+
let data;
|
|
393
|
+
try {
|
|
394
|
+
const res = await fetch(`${baseUrl}/api/agent/run/${runId}`, { headers });
|
|
395
|
+
if (!res.ok) {
|
|
396
|
+
if (elapsed > 800) { process.stderr.write(`Timeout after ${elapsed}s\n`); process.exit(2); }
|
|
397
|
+
await new Promise(r => setTimeout(r, interval));
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
data = await res.json();
|
|
401
|
+
} catch {
|
|
402
|
+
await new Promise(r => setTimeout(r, interval));
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const currentOutput = data.output || [];
|
|
407
|
+
|
|
408
|
+
// Diff: find new or updated items
|
|
409
|
+
for (const item of currentOutput) {
|
|
410
|
+
const prev = lastOutput.find(o => o.id === item.id);
|
|
411
|
+
if (!prev) {
|
|
412
|
+
// New item
|
|
413
|
+
if (jsonl) console.log(JSON.stringify({ event: 'output.added', item }));
|
|
414
|
+
else process.stderr.write(`+ [${item.type}] ${item.url || item.content?.slice(0, 60) || item.status}\n`);
|
|
415
|
+
} else if (JSON.stringify(prev) !== JSON.stringify(item)) {
|
|
416
|
+
// Updated item (e.g. video status changed)
|
|
417
|
+
if (jsonl) console.log(JSON.stringify({ event: 'output.updated', item }));
|
|
418
|
+
else process.stderr.write(`~ [${item.type}] ${item.status} ${item.url || ''}\n`);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
lastOutput = currentOutput;
|
|
423
|
+
|
|
424
|
+
// Check terminal status
|
|
425
|
+
if (!data.incomplete && (data.status === 'completed' || data.status === 'failed' || data.status === 'aborted')) {
|
|
426
|
+
if (jsonl) console.log(JSON.stringify({ event: 'done', status: data.status }));
|
|
427
|
+
if (data.status === 'failed' || data.status === 'aborted') process.exit(1);
|
|
428
|
+
process.exit(0);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const waitMs = data.next_poll_after_ms || interval;
|
|
432
|
+
await new Promise(r => setTimeout(r, waitMs));
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
229
436
|
// ─── Async Task Polling ──────────────────────────────────────────────────────
|
|
230
437
|
|
|
231
438
|
async function pollVideo(baseUrl, headers, taskId) {
|
|
@@ -401,16 +608,27 @@ if (command === 'login') {
|
|
|
401
608
|
let projectId = null;
|
|
402
609
|
const chatImages = [];
|
|
403
610
|
const promptParts = [];
|
|
611
|
+
let useStream = false;
|
|
612
|
+
let background = false;
|
|
613
|
+
let jsonOutput = false;
|
|
614
|
+
let videoModel = undefined;
|
|
615
|
+
let preferredModel = undefined;
|
|
404
616
|
for (let i = 1; i < args.length; i++) {
|
|
405
617
|
if (args[i] === '--project' && args[i + 1]) projectId = args[++i];
|
|
406
618
|
else if (args[i] === '--image' && args[i + 1]) chatImages.push(args[++i]);
|
|
619
|
+
else if (args[i] === '--stream') useStream = true;
|
|
620
|
+
else if (args[i] === '--background' || args[i] === '-b') background = true;
|
|
621
|
+
else if (args[i] === '--json') jsonOutput = true;
|
|
622
|
+
else if (args[i] === '--video-model' && args[i + 1]) videoModel = args[++i];
|
|
623
|
+
else if (args[i] === '--model' && args[i + 1]) preferredModel = args[++i];
|
|
407
624
|
else promptParts.push(args[i]);
|
|
408
625
|
}
|
|
409
626
|
const prompt = promptParts.join(' ');
|
|
410
627
|
if (!projectId || !prompt) {
|
|
411
|
-
console.error('Usage: makaron chat --project <id> [--image <file>] "your message"');
|
|
628
|
+
console.error('Usage: makaron chat --project <id> [--image <file>] [--stream] [--background|-b] [--json] "your message"');
|
|
412
629
|
process.exit(1);
|
|
413
630
|
}
|
|
631
|
+
// Upload images if provided
|
|
414
632
|
if (chatImages.length > 0) {
|
|
415
633
|
const base64s = chatImages.map(imgPath => {
|
|
416
634
|
process.stderr.write(`📤 Uploading ${path.basename(imgPath)}...\n`);
|
|
@@ -429,13 +647,97 @@ if (command === 'login') {
|
|
|
429
647
|
process.stderr.write(`⚠️ Failed to upload images: ${await res.text()}\n`);
|
|
430
648
|
}
|
|
431
649
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
650
|
+
|
|
651
|
+
if (useStream) {
|
|
652
|
+
// Legacy SSE mode
|
|
653
|
+
const { results } = await streamAgent(baseUrl, headers, projectId, prompt);
|
|
654
|
+
process.stderr.write('\n━━━ Results ━━━\n');
|
|
655
|
+
for (const img of results.images) process.stderr.write(`🖼️ Image: ${img.imageUrl}\n`);
|
|
656
|
+
for (const d of results.designs) process.stderr.write(`🎨 ${d.desc}\n`);
|
|
657
|
+
process.stderr.write(`🔗 ${APP_URL}/projects/${projectId}\n`);
|
|
658
|
+
for (const task of results.animationTasks) await pollVideo(baseUrl, headers, task.taskId);
|
|
659
|
+
for (const task of results.musicTasks) await pollMusic(baseUrl, headers, task.taskId);
|
|
660
|
+
} else {
|
|
661
|
+
// Default: fire-and-forget + poll
|
|
662
|
+
const { runId } = await submitRun(baseUrl, headers, projectId, prompt, { videoModel, preferredModel });
|
|
663
|
+
if (background) {
|
|
664
|
+
// Just print runId and exit
|
|
665
|
+
if (jsonOutput) {
|
|
666
|
+
console.log(JSON.stringify({ runId, projectId, projectUrl: `${APP_URL}/projects/${projectId}`, status: 'running' }));
|
|
667
|
+
} else {
|
|
668
|
+
console.log(runId);
|
|
669
|
+
}
|
|
670
|
+
} else {
|
|
671
|
+
process.stderr.write(`🚀 Run started: ${runId}\n`);
|
|
672
|
+
await pollRun(baseUrl, headers, runId, { json: jsonOutput });
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
} else if (command === 'responses' || command === 'run') {
|
|
676
|
+
const { headers, baseUrl } = getAuth();
|
|
677
|
+
const sub = args[1];
|
|
678
|
+
|
|
679
|
+
if (sub === 'get') {
|
|
680
|
+
const runId = args[2];
|
|
681
|
+
if (!runId) { console.error('Usage: makaron responses get <runId> [--wait] [--json] [--pick <field>]'); process.exit(1); }
|
|
682
|
+
let wait = false, jsonOutput = false, pick = null;
|
|
683
|
+
for (let i = 3; i < args.length; i++) {
|
|
684
|
+
if (args[i] === '--wait') wait = true;
|
|
685
|
+
if (args[i] === '--json') jsonOutput = true;
|
|
686
|
+
if (args[i] === '--pick' && args[i + 1]) pick = args[++i];
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
if (wait) {
|
|
690
|
+
await pollRun(baseUrl, headers, runId, { json: true });
|
|
691
|
+
} else {
|
|
692
|
+
const res = await fetch(`${baseUrl}/api/agent/run/${runId}`, { headers });
|
|
693
|
+
if (!res.ok) { process.stderr.write(`Error ${res.status}: ${await res.text()}\n`); process.exit(1); }
|
|
694
|
+
const data = await res.json();
|
|
695
|
+
if (pick) {
|
|
696
|
+
const picked = applyPick(data, pick);
|
|
697
|
+
if (picked !== undefined) console.log(typeof picked === 'string' ? picked : JSON.stringify(picked));
|
|
698
|
+
} else {
|
|
699
|
+
console.log(JSON.stringify(data, null, 2));
|
|
700
|
+
}
|
|
701
|
+
if (data.status === 'failed' || data.status === 'aborted') process.exit(1);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
} else if (sub === 'watch') {
|
|
705
|
+
const runId = args[2];
|
|
706
|
+
if (!runId) { console.error('Usage: makaron responses watch <runId> [--jsonl] [--interval <ms>]'); process.exit(1); }
|
|
707
|
+
let interval = 5000, jsonl = false;
|
|
708
|
+
for (let i = 3; i < args.length; i++) {
|
|
709
|
+
if (args[i] === '--jsonl') jsonl = true;
|
|
710
|
+
if (args[i] === '--interval' && args[i + 1]) interval = parseInt(args[++i]);
|
|
711
|
+
}
|
|
712
|
+
await watchRun(baseUrl, headers, runId, { interval, jsonl });
|
|
713
|
+
|
|
714
|
+
} else if (sub === 'list') {
|
|
715
|
+
let projectId = null;
|
|
716
|
+
for (let i = 2; i < args.length; i++) {
|
|
717
|
+
if (args[i] === '--project' && args[i + 1]) projectId = args[++i];
|
|
718
|
+
}
|
|
719
|
+
if (!projectId) { console.error('Usage: makaron responses list --project <id>'); process.exit(1); }
|
|
720
|
+
const res = await fetch(`${baseUrl}/api/agent/run?projectId=${projectId}`, { headers });
|
|
721
|
+
if (!res.ok) { process.stderr.write(`Error ${res.status}: ${await res.text()}\n`); process.exit(1); }
|
|
722
|
+
const data = await res.json();
|
|
723
|
+
if (data.runs?.length) {
|
|
724
|
+
for (const r of data.runs) {
|
|
725
|
+
const age = timeSince(new Date(r.started_at));
|
|
726
|
+
console.log(` ${r.id} ${r.status.padEnd(10)} ${age} ${(r.prompt || '').slice(0, 50)}`);
|
|
727
|
+
}
|
|
728
|
+
} else {
|
|
729
|
+
console.log('No runs found for this project.');
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
} else {
|
|
733
|
+
console.log(`Responses commands:
|
|
734
|
+
responses get <runId> Get status and output (JSON)
|
|
735
|
+
responses get <runId> --wait Poll until completed
|
|
736
|
+
responses get <runId> --pick <field> Extract: first_image_url, first_video_url, project_url, output
|
|
737
|
+
responses watch <runId> --jsonl Watch until done (incremental events)
|
|
738
|
+
responses list --project <id> List runs for a project
|
|
739
|
+
`);
|
|
740
|
+
}
|
|
439
741
|
} else if (command === 'list' || command === 'ls') {
|
|
440
742
|
const { headers, baseUrl } = getAuth();
|
|
441
743
|
await listProjects(baseUrl, headers);
|
|
@@ -696,8 +998,15 @@ Commands:
|
|
|
696
998
|
create --image <file> Create project from local image
|
|
697
999
|
create --image-url <url> Create project from URL
|
|
698
1000
|
create --title "name" Create empty project (text-to-image)
|
|
699
|
-
|
|
700
|
-
chat --project <id>
|
|
1001
|
+
|
|
1002
|
+
chat --project <id> "message" Chat (non-blocking, polls for result)
|
|
1003
|
+
chat --project <id> -b "message" Background: submit and print runId
|
|
1004
|
+
chat --project <id> --stream "msg" Legacy: stream SSE in real-time
|
|
1005
|
+
chat --project <id> --json "msg" Output structured JSON result
|
|
1006
|
+
|
|
1007
|
+
responses get <runId> Get run status and results
|
|
1008
|
+
responses get <runId> --wait Poll until completed
|
|
1009
|
+
responses list --project <id> List runs for a project
|
|
701
1010
|
abort <runId> Abort a running Agent
|
|
702
1011
|
|
|
703
1012
|
edit [--image <file>] "prompt" AI image edit / text-to-image
|
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "makaron-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Talk to Makaron Agent from the terminal — create projects, edit images, generate videos",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"makaron": "./bin/makaron.mjs"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
|
-
"bin/"
|
|
10
|
+
"bin/",
|
|
11
|
+
"SKILL.md",
|
|
12
|
+
"README.md"
|
|
11
13
|
],
|
|
12
14
|
"keywords": [
|
|
13
15
|
"makaron",
|