> ## Documentation Index
> Fetch the complete documentation index at: https://mulerun.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Studio

> Generate images, videos, speech, and music from the terminal

`mulerun studio` is the multimodal generation surface of the CLI. It bundles
40+ image, video, speech, and music model endpoints behind a single
subcommand — all reachable with your existing MuleRun login, no extra API
key.

```bash theme={null}
mulerun studio list                              # browse the model catalog
mulerun studio params <endpoint>                 # inspect a model's parameters
mulerun studio run <endpoint> [--param value …]  # generate something
mulerun studio status <api-path> <task-id>       # poll an async task
mulerun studio config                            # show resolved configuration
mulerun studio upgrade                           # update the studio engine
mulerun studio reset                             # remove the local install
```

Every command supports `--json` for machine-readable output. Long-running
endpoints (video, music) can be submitted with `--no-wait` and resumed
later via `status` — useful for CI, batches, or LLM-agent workflows.

## Authentication

Run `mulerun login` once and every `studio` invocation picks up the cached
credential automatically. The CLI exchanges your OAuth token for a
short-lived per-call key before each request, so no manual API-key
management is required. See [Configuration](/cli/configuration) for
non-interactive setups (`MULERUN_TOKEN`).

`mulerun studio --help` and `mulerun studio <subcommand> --help` are safe
to run while logged out — they don't touch the network.

## Endpoint identifier

The `<endpoint>` argument used by `params`, `run`, and the catalog uses
this shape:

| Form                          | Behavior                                                                             |
| ----------------------------- | ------------------------------------------------------------------------------------ |
| `<provider>/<model>`          | Auto-resolves the action when the model has exactly one                              |
| `<provider>/<model>/<action>` | Explicit action — required when a model exposes multiple (`generation`, `edit`, ...) |

Examples: `alibaba/wan2.6-t2v`, `openai/gpt-image-2/edit`,
`google/nano-banana-2/generation`.

## Subcommands

### `studio list`

Browse the catalog. With no flags, every registered endpoint is printed
grouped by provider; with flags, the list is narrowed.

| Option                 | Description                                                                                              |
| ---------------------- | -------------------------------------------------------------------------------------------------------- |
| `--provider <name>`    | Filter by provider (e.g. `alibaba`, `google`, `klingai`, `midjourney`, `minimax`, `openai`, `bytedance`) |
| `--output-type <type>` | Filter by output type (`image`, `video`, `audio`)                                                        |
| `--tag <tag>`          | Filter by tag, e.g. `SOTA`                                                                               |
| `--providers`          | Print provider names only — skip the model listing                                                       |
| `--json`               | Output as JSON                                                                                           |

```bash theme={null}
# Everything
mulerun studio list

# All Google models
mulerun studio list --provider google

# Every video endpoint
mulerun studio list --output-type video

# Only SOTA-tagged endpoints
mulerun studio list --tag SOTA

# Just the provider names
mulerun studio list --providers

# Machine-readable
mulerun studio list --json | jq -r '.models[] | "\(.model_id)/\(.action)"'
```

Text output groups endpoints by provider and shows the canonical
identifier, a one-line description, output type, and any tags:

```
[ALIBABA]
----------------------------------------

  alibaba/wan2.6-t2v/generation [SOTA]
    Wan2.6 Text-to-Video: ...
    Output: video

  alibaba/wan2.6-i2v/generation [SOTA]
    Wan2.6 Image-to-Video: ...
    Output: video
```

### `studio params <endpoint>`

Inspect every parameter an endpoint accepts — name, type, required
flag, default value, and enum choices when applicable.

| Option   | Description                                            |
| -------- | ------------------------------------------------------ |
| `--json` | Output as JSON (useful for codegen or LLM consumption) |

```bash theme={null}
mulerun studio params alibaba/wan2.6-t2v
mulerun studio params google/nano-banana-2/edit
mulerun studio params openai/gpt-image-2/generation --json
```

Text output:

```
alibaba/wan2.6-t2v/generation
Wan2.6 Text-to-Video: Generate videos from text prompts with multi-shot,
audio, and resolution control

Parameters:
------------------------------------------------------------

  --prompt (required)
    Type: string
    Text prompt describing the video to generate

  --size [default: 1280*720] [choices: 1280*720, 960*960, ...]
    Type: string
    Video resolution

  --duration [default: 5] [choices: 5, 10, 15]
    Type: integer
    Video duration in seconds

  ...

API Path: /vendors/alibaba/v1/wan2.6-t2v/generation
Output Type: video
Result Key: videos
```

The trailing metadata tells you the upstream API path (used by `status`)
and the JSON key under which result URLs appear (`videos`, `images`,
`audios`).

**Parameter naming.** Parameters are documented in `snake_case` (matching
the wire format) and exposed as `--kebab-case` flags. The CLI converts
between the two automatically.

| Documented name   | CLI flag            |
| ----------------- | ------------------- |
| `prompt`          | `--prompt`          |
| `negative_prompt` | `--negative-prompt` |
| `aspect_ratio`    | `--aspect-ratio`    |
| `first_frame`     | `--first-frame`     |

### `studio run <endpoint>`

The workhorse. Submits a generation task, polls until it completes, and
prints the result URLs.

| Option                | Default | Description                                                                                    |
| --------------------- | ------- | ---------------------------------------------------------------------------------------------- |
| `--no-wait`           | —       | Submit and return immediately. Use `status` later.                                             |
| `--poll-interval <s>` | `20`    | Polling interval in seconds.                                                                   |
| `--max-wait <s>`      | `900`   | Maximum wait before the poller gives up (15 min). The task itself keeps running on the server. |
| `--quiet`             | —       | Suppress per-poll progress lines on stderr.                                                    |
| `--json`              | —       | Machine-readable result on stdout.                                                             |
| `--extra <KEY=VALUE>` | —       | Inject an undeclared parameter. Repeatable.                                                    |

Model parameters are passed as flags using the kebab-case form. Both
`--key value` and `--key=value` syntax are accepted. Values are type-coerced
and enum-validated before any HTTP call is made:

| Type      | Accepted CLI value                                                   |
| --------- | -------------------------------------------------------------------- |
| `string`  | Any value                                                            |
| `integer` | Parsed with `parseInt`. Invalid values throw.                        |
| `number`  | Parsed with `parseFloat`. Invalid values throw.                      |
| `boolean` | `true` / `false`, or the bare flag (e.g. `--audio` ≡ `--audio true`) |
| `array`   | A JSON array string, e.g. `--images '["https://..."]'`               |

Missing required parameters and empty strings for required string
parameters produce a friendly error before the request is sent.

**Recipes:**

```bash theme={null}
# Text-to-image
mulerun studio run openai/gpt-image-2/generation \
  --prompt "A vector logo of a fox, flat style" \
  --size "1024x1024" \
  --quality high

# Text-to-image with batch + negative prompt
mulerun studio run alibaba/wan2.6-t2i \
  --prompt "Ink-wash painting of a Jiangnan water town" \
  --size "1280*1280" \
  --n 4 \
  --negative-prompt "blurry, low quality"

# Text-to-video with audio
mulerun studio run alibaba/wan2.6-t2v \
  --prompt "A golden retriever running on a beach at sunrise" \
  --duration 10 \
  --audio

# Image-to-video — local file path auto-converted to base64
mulerun studio run alibaba/wan2.6-i2v \
  --image ./hero.jpg \
  --prompt "Slow cinematic push-in, drifting clouds" \
  --duration 5

# Image editing with a mask
mulerun studio run openai/gpt-image-2/edit \
  --prompt "Put a bouquet of flowers in their hand" \
  --images '["https://example.com/person.jpg"]' \
  --mask ./mask.png

# Text-to-speech
mulerun studio run minimax/speech-2.8-hd \
  --prompt "Hello from the command line." \
  --voice-id "Charming_Lady" \
  --emotion happy

# Text-to-music
mulerun studio run minimax/music-2.5 \
  --prompt "upbeat synthwave" \
  --lyrics-prompt "[verse]\nNeon city\n[chorus]\nWe ride"
```

**Output (text, default):**

```
Creating task at /vendors/alibaba/v1/wan2.6-t2v/generation...
[12s] status: queued
[32s] status: running
[52s] status: succeeded

Task ID: 9d2b8c1a-...
Status:  succeeded

Results:
  https://cdn.mulerun.com/.../video-0.mp4
```

**Output (`--json`):**

```json theme={null}
{
  "task_id": "9d2b8c1a-...",
  "status": "succeeded",
  "videos": [
    "https://cdn.mulerun.com/.../video-0.mp4"
  ],
  "data": { ... }
}
```

The result-key (`videos`, `images`, `audios`) matches the `Result Key`
field shown by `studio params`.

#### Local image inputs

Any parameter that takes an image (`--image`, `--images`, `--first-frame`,
`--last-frame`, `--mask`, `--mask-image-url`, `--ref-images-url`,
`--reference-images`, ...) accepts three kinds of values:

| Value                                  | Behavior                                   |
| -------------------------------------- | ------------------------------------------ |
| `http://...` / `https://...` URL       | Forwarded unchanged                        |
| `data:image/...;base64,...` URI        | Forwarded unchanged                        |
| Local file path (relative or absolute) | Read, validated, base64-encoded, then sent |

Supported formats: PNG, JPG, JPEG, GIF, BMP, WebP, TIFF, SVG, ICO, HEIC,
HEIF, AVIF. Files are capped at **20 MB**; paths inside sensitive
directories (`/etc`, `~/.ssh`, `~/.aws`, ...) are blocked.

If you pass a path that doesn't exist, the CLI prints a yellow warning to
stderr and still attempts the request (treating the string as a URL). This
makes typos loud while still allowing intentional pass-through of
URL-shaped strings.

### `studio status <api-path> <task-id>`

Check on an async task submitted with `run --no-wait` (or via the HTTP
API directly).

| Option                | Default | Description                                                    |
| --------------------- | ------- | -------------------------------------------------------------- |
| `--wait`              | —       | Poll until the task completes instead of returning immediately |
| `--poll-interval <s>` | `20`    | Polling interval in seconds (with `--wait`)                    |
| `--max-wait <s>`      | `900`   | Maximum wait before the poller gives up                        |
| `--quiet`             | —       | Suppress per-poll progress lines                               |
| `--json`              | —       | Machine-readable output                                        |

```bash theme={null}
# Snapshot — one request, then exit
mulerun studio status /vendors/alibaba/v1/wan2.6-t2v/generation 9d2b8c1a-...

# Block until done
mulerun studio status /vendors/alibaba/v1/wan2.6-t2v/generation 9d2b8c1a-... --wait

# Faster polling, longer ceiling
mulerun studio status /vendors/alibaba/v1/wan2.6-t2v/generation 9d2b8c1a-... \
  --wait --poll-interval 10 --max-wait 1800
```

The `api-path` argument is printed by `run --no-wait`, and also shows up
as `api_path` in `studio list --json` and as **API Path** in
`studio params`.

**Status values:**

| Status                   | Terminal? | Meaning                          |
| ------------------------ | --------- | -------------------------------- |
| `pending`, `queued`      | No        | Accepted, waiting for a worker   |
| `running`, `processing`  | No        | A worker is generating the asset |
| `succeeded`, `completed` | **Yes**   | Result URLs are available        |
| `failed`                 | **Yes**   | An `error` field explains why    |

Snapshot calls (without `--wait`) only exit non-zero on the explicit
`failed` state; other statuses simply mean "not done yet."

### `studio config`

Print the resolved configuration (base URL, request timeout, retry
budget). No network call, no secrets revealed. Run this when you suspect
the wrong host is being contacted.

### `studio upgrade` / `studio reset`

Wrapper-only commands that operate on the local install of the studio
engine:

```bash theme={null}
mulerun studio upgrade   # reinstall the engine at @latest
mulerun studio reset     # remove the install — next run reinstalls
```

`upgrade` is safe to run on a schedule; `reset` is the escape hatch when
a stale install is misbehaving.

## Async workflow

For long jobs (video, music, high-effort image generation), submit
without waiting and resume polling later:

```bash theme={null}
# Step 1 — submit
SUBMIT=$(mulerun studio run alibaba/wan2.6-t2v \
  --prompt "A long cinematic shot over a glacier" \
  --duration 15 --no-wait --json)

TASK_ID=$(echo "$SUBMIT" | jq -r '.task_id')
API_PATH=$(echo "$SUBMIT" | jq -r '.api_path')

# Step 2 — wait (separate shell, later in CI, ...)
mulerun studio status "$API_PATH" "$TASK_ID" --wait --json > result.json

# Step 3 — download the assets
jq -r '.results[]' result.json | xargs -n1 curl -sSLO
```

If the poller in step 2 hits its `--max-wait` budget, the task itself
keeps running on the server — re-run the same `status --wait` to resume.

## Fan-out / fan-in

Submit many prompts in parallel, then wait on the whole batch:

```bash theme={null}
#!/usr/bin/env bash
set -euo pipefail

prompts=(
  "A neon-lit Tokyo street at midnight"
  "A misty pine forest at dawn"
  "A sandstorm rolling across desert dunes"
)

# Fan out
: > tasks.jsonl
for p in "${prompts[@]}"; do
  mulerun studio run alibaba/wan2.6-t2v \
    --prompt "$p" --duration 5 --no-wait --json --quiet \
    | jq -c --arg p "$p" '{task_id, api_path, prompt: $p}' >> tasks.jsonl
done

# Fan in
while read -r line; do
  tid=$(jq -r .task_id <<<"$line")
  path=$(jq -r .api_path <<<"$line")
  mulerun studio status "$path" "$tid" --wait --quiet --json > "result-$tid.json"
done < tasks.jsonl
```

## Scripting

Every studio command supports `--json`. Pair with `jq` for clean shell
pipelines:

```bash theme={null}
# Pick a random SOTA model
mulerun studio list --tag SOTA --json \
  | jq -r '.models[] | "\(.model_id)/\(.action)"' \
  | shuf -n 1

# Extract the first result URL
URL=$(mulerun studio run openai/gpt-image-2/generation \
  --prompt "A sunset" --json --quiet | jq -r '.images[0]')

# Download every generated asset
mulerun studio run alibaba/wan2.6-t2v --prompt "Ocean waves" --json --quiet \
  | jq -r '.videos[]' \
  | xargs -n1 curl -sSLO
```

**Stdout vs stderr.** Stdout carries the JSON / text result and is safe
to pipe. Stderr carries per-poll progress lines, warnings, and real
errors. Use `--quiet` to suppress progress while keeping real errors.

## Exit codes

| Code | Meaning                                                      |
| ---- | ------------------------------------------------------------ |
| `0`  | Task completed successfully                                  |
| `1`  | Validation, configuration, network, or terminal task failure |

When scripting against the CLI, always check `$?` before parsing the JSON
— a failed call may still emit JSON, but the shape will contain an
`error` field instead of results.

## Common errors

| Message                                                        | What it means                                                                              |
| -------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| `Model endpoint '...' not found.`                              | Spelling / version mismatch. Run `studio list` and copy the identifier.                    |
| `Multiple actions found for '...'.`                            | The model has multiple actions. Use the three-part identifier `provider/model/action`.     |
| `Missing required parameter(s): --xxx.`                        | Add the listed flags. `studio params <endpoint>` shows what's required.                    |
| `Invalid value for --xxx: '…'. Valid choices: …`               | The parameter has an enum. Pick one of the listed values.                                  |
| `Parameter '...' must be a valid JSON array.`                  | Pass array values as a JSON string: `--images '["https://..."]'`.                          |
| `File '...' is N.NMB, exceeding the 20MB limit`                | Resize, re-encode, or host the file yourself and pass the URL.                             |
| `Access denied: '...' is in a sensitive system/home directory` | System dirs (`/etc`, `/proc`) and dotfile dirs (`~/.ssh`, `~/.aws`) are blocked by design. |

## Tips

* The first `mulerun studio` invocation downloads the studio engine into
  `~/.mulerun/vendor/`; subsequent calls reuse it.
* For fast endpoints (text-to-image), shrinking `--poll-interval 5` makes
  the experience feel snappier.
* For very long videos, bump `--max-wait 1800` or `3600`. The task does
  not get cancelled when the poller stops — `status` can pick it back up.
* `--debug` prints the resolved engine path and the exact arguments
  forwarded to it; handy when an option doesn't seem to take effect.
