makaron-cli 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +119 -204
  2. package/SKILL.md +201 -0
  3. package/bin/makaron.mjs +136 -16
  4. package/package.json +4 -2
package/README.md CHANGED
@@ -1,287 +1,202 @@
1
- # makaron-cli
1
+ # Makaron CLI — Agent Integration Skill
2
2
 
3
- Talk to **Makaron Agent** from the terminal create projects, edit images, generate videos, and compose music.
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
- Zero dependencies. Single file. Works with `npx`.
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
- ```
14
-
15
- ## Authentication
16
-
17
- ### API Key (recommended for agents)
9
+ # or use directly: npx makaron-cli
18
10
 
19
- Set the `MAKARON_API_KEY` environment variable. That's it — no login step needed.
20
-
21
- ```bash
22
11
  export MAKARON_API_KEY=mk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
23
- npx makaron-cli list
24
12
  ```
25
13
 
26
- Get your API key from the Makaron dashboard (Settings → API Keys), or ask your admin to generate one.
14
+ Verify: `npx makaron-cli list` should show projects.
27
15
 
28
- ### Interactive login (humans)
16
+ ## Core Workflow
29
17
 
30
18
  ```bash
31
- npx makaron-cli login
32
- # Enter email + password token saved to ~/.makaron/auth.json
33
- ```
19
+ # One-shot: create project + upload image + submit prompt — all in one command
20
+ RUN_ID=$(npx makaron-cli chat --project auto --image photo.jpg -b "make it cinematic and create a 5s video")
34
21
 
35
- The CLI checks `MAKARON_API_KEY` first, then falls back to the saved session in `~/.makaron/auth.json`.
36
-
37
- ## Commands
38
-
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):**
22
+ # Watch until all artifacts are ready
23
+ npx makaron-cli responses watch $RUN_ID --jsonl
24
+ ```
44
25
 
26
+ Or with an existing project:
45
27
  ```bash
46
- npx makaron-cli chat --project <id> "make it look cinematic"
28
+ RUN_ID=$(npx makaron-cli chat --project $PROJECT_ID -b "make a 5s video")
29
+ npx makaron-cli responses watch $RUN_ID --jsonl
47
30
  ```
48
31
 
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`.
32
+ ## Primary: `chat` (Agent-driven creative work)
33
+
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.
50
35
 
51
- **Background mode submit and exit immediately:**
36
+ ### Submit a request
52
37
 
53
38
  ```bash
54
- npx makaron-cli chat --project <id> --background "make a 5s video"
55
- # prints runId and exits in <1s
39
+ # With existing project
40
+ npx makaron-cli chat --project <id> --json -b "<prompt>"
56
41
 
57
- # Later, check the result:
58
- npx makaron-cli responses get <runId> --wait
42
+ # Auto-create project (with or without images)
43
+ npx makaron-cli chat --project auto --image photo.jpg --json -b "make it cinematic"
44
+ npx makaron-cli chat --project auto --image img1.jpg --image img2.jpg --json -b "combine these"
59
45
  ```
60
46
 
61
- **Structured JSON output (for programmatic use):**
62
-
63
- ```bash
64
- npx makaron-cli chat --project <id> --json --background "edit the photo"
65
- # → {"runId":"...","projectId":"...","projectUrl":"...","status":"running"}
47
+ Returns immediately:
48
+ ```json
49
+ {"runId": "xxx", "projectId": "...", "projectUrl": "https://www.makaron.app/projects/...", "status": "running"}
66
50
  ```
67
51
 
68
- **Legacy streaming mode (real-time SSE):**
52
+ ### With additional images (existing project)
69
53
 
70
54
  ```bash
71
- npx makaron-cli chat --project <id> --stream "hello"
55
+ npx makaron-cli chat --project <id> --image ref1.jpg --image ref2.jpg -b "use these as style reference"
72
56
  ```
73
57
 
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
58
+ ### Check status (single query)
84
59
 
85
60
  ```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>
97
- ```
98
-
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
- }
61
+ npx makaron-cli responses get <runId> --json
123
62
  ```
124
63
 
125
- ### `list` Show all projects
64
+ ### Watch until done (streaming events)
126
65
 
127
66
  ```bash
128
- npx makaron-cli list
67
+ npx makaron-cli responses watch <runId> --jsonl
129
68
  ```
130
69
 
131
- ### `create` Create a new project
132
-
133
- ```bash
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
140
-
141
- # Empty project (for text-to-image)
142
- npx makaron-cli create --title "My New Project"
70
+ Outputs one JSON per line as artifacts appear:
71
+ ```
72
+ {"event":"output.added","item":{"id":"out_1","type":"image","status":"completed","url":"https://..."}}
73
+ {"event":"output.added","item":{"id":"out_2","type":"video","status":"rendering","task_id":"xxx"}}
74
+ {"event":"output.updated","item":{"id":"out_2","type":"video","status":"completed","url":"https://..."}}
75
+ {"event":"done","status":"completed"}
143
76
  ```
144
77
 
145
- ### `abort` Stop a running Agent
78
+ ### Extract specific results
146
79
 
147
80
  ```bash
148
- npx makaron-cli abort <runId>
81
+ npx makaron-cli responses get <runId> --pick first_image_url
82
+ npx makaron-cli responses get <runId> --pick image_urls # all images (JSON array)
83
+ npx makaron-cli responses get <runId> --pick first_video_url
84
+ npx makaron-cli responses get <runId> --pick video_urls # all videos
85
+ npx makaron-cli responses get <runId> --pick project_url
86
+ npx makaron-cli responses get <runId> --pick text # agent's text reply
87
+ npx makaron-cli responses get <runId> --pick output # full output array
88
+ npx makaron-cli responses get <runId> --pick status
149
89
  ```
150
90
 
151
- ### `edit` AI image editing (direct MCP tool call)
91
+ ## Fallback: Direct tool calls (no project context)
152
92
 
153
- Unlike `chat` (which uses the Agent with project context), `edit` directly calls the image generation model for one-shot results.
93
+ Use these only when `chat` is unavailable or you need raw model access without project/conversation context.
154
94
 
155
- ```bash
156
- # Text-to-image (no input image)
157
- npx makaron-cli edit "a cyberpunk cityscape at night, neon reflections"
95
+ ### `edit` — One-shot image editing
158
96
 
97
+ ```bash
159
98
  # Edit an existing image
160
99
  npx makaron-cli edit --image photo.jpg "add cinematic warm lighting"
161
100
 
162
- # With model and skill
163
- npx makaron-cli edit --image photo.jpg --model openai --skill captions "add elegant title"
101
+ # Text-to-image (no input)
102
+ npx makaron-cli edit "a cyberpunk cityscape at night"
164
103
 
165
- # With reference images (up to 3)
166
- npx makaron-cli edit --image photo.jpg --ref style1.jpg --ref style2.jpg "match this style"
104
+ # With model/skill/reference
105
+ npx makaron-cli edit --image photo.jpg --model openai --skill captions "add title"
106
+ npx makaron-cli edit --image photo.jpg --ref style.jpg "match this style"
167
107
 
168
- # Specify output path and aspect ratio
169
- npx makaron-cli edit --out result.jpg --aspect 9:16 "vertical poster design"
108
+ # Output to file
109
+ npx makaron-cli edit --image photo.jpg --out result.jpg "make it dramatic"
170
110
  ```
171
111
 
172
- Options:
173
- - `--image <file|url>` — input image (omit for text-to-image)
174
- - `--model gemini|qwen|openai|pony|wai` — model selection (default: auto)
175
- - `--skill enhance|creative|wild|captions` — activate skill template
176
- - `--ref <file|url>` — reference image (repeatable, up to 3)
177
- - `--aspect <ratio>` — target aspect ratio (e.g. `4:5`, `1:1`, `16:9`)
178
- - `--out <path>` — output file path (default: `makaron-output-{timestamp}.jpg`)
112
+ Options: `--image`, `--model gemini|qwen|openai|pony|wai`, `--skill enhance|creative|wild|captions`, `--ref <file>` (up to 3), `--aspect <ratio>`, `--out <path>`
179
113
 
180
- ### `video` — Video generation
114
+ ### `video` — Video generation (3 steps)
181
115
 
182
116
  ```bash
183
- # Write a video script from images
184
- npx makaron-cli video script --image img1.jpg --image img2.jpg "cinematic story"
117
+ # 1. Write script from images
118
+ npx makaron-cli video script --image img1.jpg "cinematic story"
185
119
 
186
- # Submit video rendering (images must be public URLs)
187
- npx makaron-cli video create --script "Shot 1..." --image https://...img1.jpg --duration 10
120
+ # 2. Submit rendering (images must be public URLs from step 1 or uploaded)
121
+ npx makaron-cli video create --script "Shot 1 (5s): <<<image_1>>> ..." --image https://...jpg --duration 5 --model kling
188
122
 
189
- # Check status
123
+ # 3. Check status
190
124
  npx makaron-cli video status <taskId>
191
125
  ```
192
126
 
127
+ 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`
128
+
193
129
  ### `music` — Music generation
194
130
 
195
131
  ```bash
196
- # Generate instrumental music
197
132
  npx makaron-cli music create "gentle piano, warm strings, cinematic"
198
-
199
- # With vocals and style
200
- npx makaron-cli music create --vocals --style "lo-fi, ambient" "rainy day vibes"
201
-
202
- # Check status
133
+ npx makaron-cli music create --vocals --style "lo-fi" "rainy day vibes"
203
134
  npx makaron-cli music status <taskId>
204
135
  ```
205
136
 
206
- ## Agent Integration Guide
207
-
208
- For AI agents (Claude Code, OpenClaw, etc.) calling this CLI programmatically:
209
-
210
- ### Non-blocking workflow (recommended)
211
-
212
- ```bash
213
- # 1. Submit task — returns immediately
214
- RUN_ID=$(npx makaron-cli chat --project $PROJECT_ID --background "make a 5s video")
215
-
216
- # 2. Do other work while Makaron processes...
217
-
218
- # 3. Poll for result when ready
219
- npx makaron-cli responses get $RUN_ID --wait
220
- ```
137
+ Options: `--vocals` (include vocals), `--style "genre"`
221
138
 
222
- ### JSON mode for structured parsing
139
+ ## Response Schema
223
140
 
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)
141
+ ```typescript
142
+ type MakaronRunResponse = {
143
+ id: string
144
+ status: "in_progress" | "completed" | "failed" | "aborted"
145
+ incomplete: boolean // true = keep polling
146
+ project_id: string
147
+ project_url: string
148
+ next_poll_after_ms?: number // suggested poll interval
149
+ output: MakaronOutput[]
150
+ }
228
151
 
229
- # Poll
230
- npx makaron-cli responses get $RUN_ID --wait
152
+ type MakaronOutput =
153
+ | { id: string; type: "text"; status: "completed"; content: string }
154
+ | { id: string; type: "image"; status: "completed"; url: string; snapshot_id: string }
155
+ | { id: string; type: "design"; status: "completed"; url: string; width: number; height: number; animated: boolean; duration?: number }
156
+ | { id: string; type: "video"; status: "queued"|"rendering"|"completed"|"failed"; task_id: string; url?: string; elapsed_seconds?: number }
157
+ | { id: string; type: "music"; status: "queued"|"rendering"|"completed"|"failed"; task_id: string; url?: string; elapsed_seconds?: number }
231
158
  ```
232
159
 
233
- ### Sequential multi-turn
160
+ ## Polling Rules
234
161
 
235
- ```bash
236
- # Turn 1: edit image
237
- npx makaron-cli chat --project $ID "make it cinematic"
238
- # Wait for completion (default mode polls automatically)
162
+ 1. Poll while `incomplete: true` or `status` is `"in_progress"`
163
+ 2. Use `next_poll_after_ms` as interval (default 5000ms)
164
+ 3. Stop when `status` is `"completed"`, `"failed"`, or `"aborted"`
165
+ 4. Top-level `status: "completed"` means ALL artifacts are ready (including rendered videos)
239
166
 
240
- # Turn 2: make video from the edited image
241
- npx makaron-cli chat --project $ID "now make a 5s video from this"
242
- ```
167
+ ## Exit Codes
243
168
 
244
- Note: One project runs one Agent at a time. A new message while the previous is still running will interrupt the first.
169
+ | Code | Meaning |
170
+ |------|---------|
171
+ | 0 | Success (completed) or valid in-progress response |
172
+ | 1 | Failed, aborted, or HTTP error |
173
+ | 2 | Timeout (partial response still printed to stdout) |
245
174
 
246
- ### What the Agent can do
175
+ ## What Makaron Agent Can Do
247
176
 
248
- | Capability | Example prompt |
249
- |-----------|---------------|
250
- | Image editing | "make the sky more dramatic" |
177
+ | Task | Example prompt |
178
+ |------|---------------|
179
+ | Edit photo | "make it cinematic with warm tones" |
251
180
  | Style transfer | "convert to oil painting style" |
252
- | Add elements | "add a cat sitting on the table" |
253
- | Remove elements | "remove the person in the background" |
254
- | Text-to-image | "generate a cyberpunk cityscape" (on empty project) |
255
- | Video from image | "create a 5 second video" |
256
- | Video with script | "make a video: camera slowly zooms in while leaves fall" |
181
+ | Add/remove elements | "add a cat on the table" / "remove background person" |
182
+ | Text-to-image | "generate a cyberpunk cityscape" |
183
+ | Video from image | "create a 5 second video of her walking" |
184
+ | Video with model | "用seedance模型做5秒视频" |
257
185
  | Background music | "add calm piano music" |
258
- | Design/motion graphics | "create an Instagram story with animated text" |
259
-
260
- ### Output types in result
261
-
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) |
270
-
271
- ### Environment variables
272
-
273
- | Variable | Purpose | Default |
274
- |----------|---------|---------|
275
- | `MAKARON_API_KEY` | API key (`mk_live_xxx`) | — |
276
- | `MAKARON_URL` | API endpoint override | `https://www.makaron.app` |
277
-
278
- ## Viewing results
279
-
280
- All generated content (images, videos, designs) is saved to the project and visible at:
281
-
282
- ```
283
- https://www.makaron.app/projects/<project-id>
284
- ```
186
+ | Motion design | "create an Instagram story with animated text" |
187
+ | Multi-step | "edit the photo then make a video from it" |
188
+
189
+ ## Important Notes
190
+
191
+ - One project = one conversation thread. All history is preserved.
192
+ - One run at a time per project. New message interrupts previous run.
193
+ - Multi-image: `create --image a.jpg --image b.jpg` or `chat --image ref.jpg`.
194
+ - Videos take 2-5 minutes to render. Use `watch` to get URL when ready.
195
+ - Music takes ~60 seconds. Appears in output when done.
196
+ - Images are typically ready in 15-30 seconds.
197
+ - stdout is always machine-readable JSON/text. Human-friendly logs go to stderr.
198
+ - Always use `chat` as the primary interface — even for single image edits.
199
+ - `edit`/`video`/`music` are fallback tools for when `chat` is unavailable or you need raw model access without project context.
285
200
 
286
201
  ## Admin: Skill Marketplace Operations
287
202
 
package/SKILL.md ADDED
@@ -0,0 +1,201 @@
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
+ # One-shot: create project + upload image + submit prompt — all in one command
22
+ RUN_ID=$(npx makaron-cli chat --project auto --image photo.jpg -b "make it cinematic and create a 5s video")
23
+
24
+ # Watch until all artifacts are ready
25
+ npx makaron-cli responses watch $RUN_ID --jsonl
26
+ ```
27
+
28
+ Or with an existing project:
29
+ ```bash
30
+ RUN_ID=$(npx makaron-cli chat --project $PROJECT_ID -b "make a 5s video")
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
+ # With existing project
42
+ npx makaron-cli chat --project <id> --json -b "<prompt>"
43
+
44
+ # Auto-create project (with or without images)
45
+ npx makaron-cli chat --project auto --image photo.jpg --json -b "make it cinematic"
46
+ npx makaron-cli chat --project auto --image img1.jpg --image img2.jpg --json -b "combine these"
47
+ ```
48
+
49
+ Returns immediately:
50
+ ```json
51
+ {"runId": "xxx", "projectId": "...", "projectUrl": "https://www.makaron.app/projects/...", "status": "running"}
52
+ ```
53
+
54
+ ### With additional images (existing project)
55
+
56
+ ```bash
57
+ npx makaron-cli chat --project <id> --image ref1.jpg --image ref2.jpg -b "use these as style reference"
58
+ ```
59
+
60
+ ### Check status (single query)
61
+
62
+ ```bash
63
+ npx makaron-cli responses get <runId> --json
64
+ ```
65
+
66
+ ### Watch until done (streaming events)
67
+
68
+ ```bash
69
+ npx makaron-cli responses watch <runId> --jsonl
70
+ ```
71
+
72
+ Outputs one JSON per line as artifacts appear:
73
+ ```
74
+ {"event":"output.added","item":{"id":"out_1","type":"image","status":"completed","url":"https://..."}}
75
+ {"event":"output.added","item":{"id":"out_2","type":"video","status":"rendering","task_id":"xxx"}}
76
+ {"event":"output.updated","item":{"id":"out_2","type":"video","status":"completed","url":"https://..."}}
77
+ {"event":"done","status":"completed"}
78
+ ```
79
+
80
+ ### Extract specific results
81
+
82
+ ```bash
83
+ npx makaron-cli responses get <runId> --pick first_image_url
84
+ npx makaron-cli responses get <runId> --pick image_urls # all images (JSON array)
85
+ npx makaron-cli responses get <runId> --pick first_video_url
86
+ npx makaron-cli responses get <runId> --pick video_urls # all videos
87
+ npx makaron-cli responses get <runId> --pick project_url
88
+ npx makaron-cli responses get <runId> --pick text # agent's text reply
89
+ npx makaron-cli responses get <runId> --pick output # full output array
90
+ npx makaron-cli responses get <runId> --pick status
91
+ ```
92
+
93
+ ## Fallback: Direct tool calls (no project context)
94
+
95
+ Use these only when `chat` is unavailable or you need raw model access without project/conversation context.
96
+
97
+ ### `edit` — One-shot image editing
98
+
99
+ ```bash
100
+ # Edit an existing image
101
+ npx makaron-cli edit --image photo.jpg "add cinematic warm lighting"
102
+
103
+ # Text-to-image (no input)
104
+ npx makaron-cli edit "a cyberpunk cityscape at night"
105
+
106
+ # With model/skill/reference
107
+ npx makaron-cli edit --image photo.jpg --model openai --skill captions "add title"
108
+ npx makaron-cli edit --image photo.jpg --ref style.jpg "match this style"
109
+
110
+ # Output to file
111
+ npx makaron-cli edit --image photo.jpg --out result.jpg "make it dramatic"
112
+ ```
113
+
114
+ Options: `--image`, `--model gemini|qwen|openai|pony|wai`, `--skill enhance|creative|wild|captions`, `--ref <file>` (up to 3), `--aspect <ratio>`, `--out <path>`
115
+
116
+ ### `video` — Video generation (3 steps)
117
+
118
+ ```bash
119
+ # 1. Write script from images
120
+ npx makaron-cli video script --image img1.jpg "cinematic story"
121
+
122
+ # 2. Submit rendering (images must be public URLs from step 1 or uploaded)
123
+ npx makaron-cli video create --script "Shot 1 (5s): <<<image_1>>> ..." --image https://...jpg --duration 5 --model kling
124
+
125
+ # 3. Check status
126
+ npx makaron-cli video status <taskId>
127
+ ```
128
+
129
+ 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`
130
+
131
+ ### `music` — Music generation
132
+
133
+ ```bash
134
+ npx makaron-cli music create "gentle piano, warm strings, cinematic"
135
+ npx makaron-cli music create --vocals --style "lo-fi" "rainy day vibes"
136
+ npx makaron-cli music status <taskId>
137
+ ```
138
+
139
+ Options: `--vocals` (include vocals), `--style "genre"`
140
+
141
+ ## Response Schema
142
+
143
+ ```typescript
144
+ type MakaronRunResponse = {
145
+ id: string
146
+ status: "in_progress" | "completed" | "failed" | "aborted"
147
+ incomplete: boolean // true = keep polling
148
+ project_id: string
149
+ project_url: string
150
+ next_poll_after_ms?: number // suggested poll interval
151
+ output: MakaronOutput[]
152
+ }
153
+
154
+ type MakaronOutput =
155
+ | { id: string; type: "text"; status: "completed"; content: string }
156
+ | { id: string; type: "image"; status: "completed"; url: string; snapshot_id: string }
157
+ | { id: string; type: "design"; status: "completed"; url: string; width: number; height: number; animated: boolean; duration?: number }
158
+ | { id: string; type: "video"; status: "queued"|"rendering"|"completed"|"failed"; task_id: string; url?: string; elapsed_seconds?: number }
159
+ | { id: string; type: "music"; status: "queued"|"rendering"|"completed"|"failed"; task_id: string; url?: string; elapsed_seconds?: number }
160
+ ```
161
+
162
+ ## Polling Rules
163
+
164
+ 1. Poll while `incomplete: true` or `status` is `"in_progress"`
165
+ 2. Use `next_poll_after_ms` as interval (default 5000ms)
166
+ 3. Stop when `status` is `"completed"`, `"failed"`, or `"aborted"`
167
+ 4. Top-level `status: "completed"` means ALL artifacts are ready (including rendered videos)
168
+
169
+ ## Exit Codes
170
+
171
+ | Code | Meaning |
172
+ |------|---------|
173
+ | 0 | Success (completed) or valid in-progress response |
174
+ | 1 | Failed, aborted, or HTTP error |
175
+ | 2 | Timeout (partial response still printed to stdout) |
176
+
177
+ ## What Makaron Agent Can Do
178
+
179
+ | Task | Example prompt |
180
+ |------|---------------|
181
+ | Edit photo | "make it cinematic with warm tones" |
182
+ | Style transfer | "convert to oil painting style" |
183
+ | Add/remove elements | "add a cat on the table" / "remove background person" |
184
+ | Text-to-image | "generate a cyberpunk cityscape" |
185
+ | Video from image | "create a 5 second video of her walking" |
186
+ | Video with model | "用seedance模型做5秒视频" |
187
+ | Background music | "add calm piano music" |
188
+ | Motion design | "create an Instagram story with animated text" |
189
+ | Multi-step | "edit the photo then make a video from it" |
190
+
191
+ ## Important Notes
192
+
193
+ - One project = one conversation thread. All history is preserved.
194
+ - One run at a time per project. New message interrupts previous run.
195
+ - Multi-image: `create --image a.jpg --image b.jpg` or `chat --image ref.jpg`.
196
+ - Videos take 2-5 minutes to render. Use `watch` to get URL when ready.
197
+ - Music takes ~60 seconds. Appears in output when done.
198
+ - Images are typically ready in 15-30 seconds.
199
+ - stdout is always machine-readable JSON/text. Human-friendly logs go to stderr.
200
+ - Always use `chat` as the primary interface — even for single image edits.
201
+ - `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
@@ -360,6 +360,79 @@ async function pollRun(baseUrl, headers, runId, opts = {}) {
360
360
  }
361
361
  }
362
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
+
363
436
  // ─── Async Task Polling ──────────────────────────────────────────────────────
364
437
 
365
438
  async function pollVideo(baseUrl, headers, taskId) {
@@ -551,11 +624,44 @@ if (command === 'login') {
551
624
  else promptParts.push(args[i]);
552
625
  }
553
626
  const prompt = promptParts.join(' ');
554
- if (!projectId || !prompt) {
555
- console.error('Usage: makaron chat --project <id> [--image <file>] [--stream] [--background|-b] [--json] "your message"');
627
+ if (!prompt) {
628
+ console.error('Usage: makaron chat --project <id|auto> [--image <file>] [--stream] [--background|-b] [--json] "your message"');
556
629
  process.exit(1);
557
630
  }
558
- // Upload images if provided
631
+ // --project auto: create a new project (with images if provided)
632
+ if (!projectId || projectId === 'auto') {
633
+ if (chatImages.length === 0) {
634
+ // Create empty project
635
+ process.stderr.write(`📦 Creating new project...\n`);
636
+ const res = await fetch(`${baseUrl}/api/projects/create`, {
637
+ method: 'POST',
638
+ headers: { 'Content-Type': 'application/json', ...headers },
639
+ body: JSON.stringify({ title: prompt.slice(0, 50) }),
640
+ });
641
+ if (!res.ok) { process.stderr.write(`❌ Failed to create project: ${await res.text()}\n`); process.exit(1); }
642
+ const data = await res.json();
643
+ projectId = data.projectId;
644
+ process.stderr.write(`📦 Project created: ${projectId}\n`);
645
+ } else {
646
+ // Create project with images
647
+ const base64s = chatImages.map(imgPath => {
648
+ process.stderr.write(`📤 Uploading ${path.basename(imgPath)}...\n`);
649
+ const buf = fs.readFileSync(imgPath);
650
+ return `data:image/jpeg;base64,${buf.toString('base64')}`;
651
+ });
652
+ const res = await fetch(`${baseUrl}/api/projects/create`, {
653
+ method: 'POST',
654
+ headers: { 'Content-Type': 'application/json', ...headers },
655
+ body: JSON.stringify({ imageBase64s: base64s }),
656
+ });
657
+ if (!res.ok) { process.stderr.write(`❌ Failed to create project: ${await res.text()}\n`); process.exit(1); }
658
+ const data = await res.json();
659
+ projectId = data.projectId;
660
+ process.stderr.write(`📦 Project created: ${projectId} (${data.snapshots?.length || 0} images)\n`);
661
+ }
662
+ chatImages.length = 0; // already uploaded
663
+ }
664
+ // Upload additional images to existing project
559
665
  if (chatImages.length > 0) {
560
666
  const base64s = chatImages.map(imgPath => {
561
667
  process.stderr.write(`📤 Uploading ${path.basename(imgPath)}...\n`);
@@ -605,34 +711,47 @@ if (command === 'login') {
605
711
 
606
712
  if (sub === 'get') {
607
713
  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;
714
+ if (!runId) { console.error('Usage: makaron responses get <runId> [--wait] [--json] [--pick <field>]'); process.exit(1); }
715
+ let wait = false, jsonOutput = false, pick = null;
610
716
  for (let i = 3; i < args.length; i++) {
611
717
  if (args[i] === '--wait') wait = true;
612
718
  if (args[i] === '--json') jsonOutput = true;
613
- if (args[i] === '--wait-artifacts') waitForArtifacts = true;
719
+ if (args[i] === '--pick' && args[i + 1]) pick = args[++i];
614
720
  }
615
721
 
616
722
  if (wait) {
617
- await pollRun(baseUrl, headers, runId, { json: jsonOutput, waitForArtifacts });
723
+ await pollRun(baseUrl, headers, runId, { json: true });
618
724
  } 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); }
725
+ const res = await fetch(`${baseUrl}/api/agent/run/${runId}`, { headers });
726
+ if (!res.ok) { process.stderr.write(`Error ${res.status}: ${await res.text()}\n`); process.exit(1); }
623
727
  const data = await res.json();
624
- console.log(JSON.stringify(data, null, 2));
728
+ if (pick) {
729
+ const picked = applyPick(data, pick);
730
+ if (picked !== undefined) console.log(typeof picked === 'string' ? picked : JSON.stringify(picked));
731
+ } else {
732
+ console.log(JSON.stringify(data, null, 2));
733
+ }
734
+ if (data.status === 'failed' || data.status === 'aborted') process.exit(1);
625
735
  }
626
736
 
737
+ } else if (sub === 'watch') {
738
+ const runId = args[2];
739
+ if (!runId) { console.error('Usage: makaron responses watch <runId> [--jsonl] [--interval <ms>]'); process.exit(1); }
740
+ let interval = 5000, jsonl = false;
741
+ for (let i = 3; i < args.length; i++) {
742
+ if (args[i] === '--jsonl') jsonl = true;
743
+ if (args[i] === '--interval' && args[i + 1]) interval = parseInt(args[++i]);
744
+ }
745
+ await watchRun(baseUrl, headers, runId, { interval, jsonl });
746
+
627
747
  } else if (sub === 'list') {
628
748
  let projectId = null;
629
749
  for (let i = 2; i < args.length; i++) {
630
750
  if (args[i] === '--project' && args[i + 1]) projectId = args[++i];
631
751
  }
632
752
  if (!projectId) { console.error('Usage: makaron responses list --project <id>'); process.exit(1); }
633
- // Query runs for project
634
753
  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); }
754
+ if (!res.ok) { process.stderr.write(`Error ${res.status}: ${await res.text()}\n`); process.exit(1); }
636
755
  const data = await res.json();
637
756
  if (data.runs?.length) {
638
757
  for (const r of data.runs) {
@@ -645,9 +764,10 @@ if (command === 'login') {
645
764
 
646
765
  } else {
647
766
  console.log(`Responses commands:
648
- responses get <runId> Get run status and results
767
+ responses get <runId> Get status and output (JSON)
649
768
  responses get <runId> --wait Poll until completed
650
- responses get <runId> --wait-artifacts Wait for video/music too
769
+ responses get <runId> --pick <field> Extract: first_image_url, first_video_url, project_url, output
770
+ responses watch <runId> --jsonl Watch until done (incremental events)
651
771
  responses list --project <id> List runs for a project
652
772
  `);
653
773
  }
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "makaron-cli",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
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",