@melius-ai/cli
v0.7.0
Published
The Melius command-line interface — create canvases and run AI image/video generations from your terminal
Maintainers
Readme
Mel CLI
The programmatic interface to the Melius platform. Built for AI agents running in sandboxed environments — every command returns structured JSON, uses deterministic exit codes, and requires zero interactive input.
Quick start
npm install -g @melius-ai/cli
# or with Homebrew (macOS/Linux): brew install melius-ai/tap/mel
# Authenticate (see "Get an API key" below to obtain a key)
mel auth login --api-key mel_<your-key>
# Build a canvas
mel project list
mel canvas create <projectId> --title "My canvas"
mel node create <canvasId> --type image --prompt "A sunset over the ocean"
mel run start <nodeId> --canvas-id <canvasId>
mel run wait <runId>Get an API key
- Sign up at app.melius.com/signup if you don't have a Melius account yet.
- In the Melius app, go to Team settings → Integrations and create an API key (it starts with
mel_and is shown once — copy it immediately). - Authenticate the CLI with it:
mel auth login --api-key mel_<your-key>.
Upgrading
Check your installed version with mel --version. Upgrade with the same tool you installed with:
# npm
npm install -g @melius-ai/cli@latest
# Homebrew (macOS/Linux)
brew update && brew upgrade melTo see the latest published version, run npm view @melius-ai/cli version (or visit the package page on npm). Releases follow semantic versioning — a major version bump (for example 1.x → 2.x) can include breaking changes, so review what changed before upgrading across one.
Sandbox setup
In the Claude Code sandbox, only two environment variables are needed:
export MEL_API_KEY=mel_...
export MEL_BASE_URL=https://api.melius.comThe API key's team is auto-injected
Commands
Authentication
| Command | Description |
| ----------------------------------- | ----------------------------------------- |
| mel auth login --api-key <key> | Store and validate an API key |
| mel auth logout | Clear stored credentials |
| mel auth whoami | Show current user (id, name, email) |
| mel auth create-key --name <n> | Create a new API key (raw key shown once) |
| mel auth list-keys | List keys for the current team |
| mel auth revoke-key --key-id <id> | Revoke an API key |
Projects
| Command | Description |
| -------------------------------- | --------------------------------- |
| mel project list | List projects in the current team |
| mel project get <id> | Get a project by ID |
| mel project create --title <t> | Create a new project |
Canvases
| Command | Description |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| mel canvas create <projectId> | Create a canvas (returns id for all subsequent commands) |
| mel canvas list <projectId> | List canvases in a project |
| mel canvas get <canvasId> | Get canvas metadata |
| mel canvas content <canvasId> | Primary read — returns live CRDT state; add --node-id and --include-all-versions to inspect one node plus its incident edges |
| mel canvas plan-layout <canvasId> | Compute positions for planned nodes (read-only, no writes) |
| mel canvas delete <canvasId> | Delete a canvas and all its content |
Nodes
All node mutations write directly to the CRDT document. Changes are synced to connected browser clients in real-time.
| Command | Description |
| ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| mel node create <canvasId> --type <t> | Add a node. Types: text, image, video, file, group |
| mel node bulk-create <canvasId> --json - | Create multiple nodes + edges in one call |
| mel node update <canvasId> <nodeId> | Update node fields (prompt, title, geometry, model) |
| mel node delete <canvasId> <nodeId> | Remove a node and its orphaned edges |
| mel node get <canvasId> <nodeId> | Legacy persisted-node read; prefer mel canvas content <canvasId> --node-id <nodeId> --include-all-versions for live inspection |
| mel node set-version <nodeId> --version-id <id> | Switch the active output version |
| mel node group <canvasId> --node-ids <ids> | Group existing nodes (computes bounding box, reparents) |
| mel node ungroup <canvasId> <groupId> | Ungroup — reparents children to the parent canvas/group and deletes the group |
Node create options:
--type <type> REQUIRED. text, image, video, file, group
--id <uuid> Auto-generated if omitted
--title <title> Defaults to type name ("Text", "Image", etc.)
--prompt <text> Text prompt (maps to textPrompt)
--model <m> Model name (requires --variant)
--variant <v> Model variant (requires --model)
--aspect-ratio <r> 21:9, 16:9, 4:3, 3:2, 1:1, 2:3, 3:4, 9:16, 9:21
--duration <n> Output duration in seconds (video only)
--x, --y, --w, --h Geometry (defaults per type: text 380x200, image 520x293)
--z-index <n> Stacking order
--group-id <uuid> Parent groupNode update: same options, all optional. Geometry must be updated as a complete set (--x --y --w --h together).
Edges
| Command | Description |
| ------------------------------------------------------------- | ---------------------------------------------------------- |
| mel edge create <canvasId> --src <id> --dst <id> --type <t> | Connect two nodes. Types: text, image, video, file |
| mel edge bulk-create <canvasId> --json - | Create multiple edges at once (preferred for workflows) |
| mel edge delete <canvasId> <edgeId> | Remove an edge |
Edge type determines data flow: text passes text output, image passes image output. ID is auto-generated if --id is omitted.
Runs (single node)
| Command | Description |
| ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| mel run start <nodeId> --canvas-id <canvasId> | Start a generation. Returns { id } |
| mel run get <runId> | Check run status: pending, running, finished, failed |
| mel run wait <runId> | Block until done. Exit 0 = finished, 1 = failed, 4 = timeout |
| mel run latest <canvasId> | Latest run status for every node on a canvas |
| mel run download <runId> | Presigned ZIP URL for a finished run's output |
| mel run magic-resize <sourceNodeId> --canvas-id <canvasId> --ratios <list> | Resize an image into multiple aspect ratios in one call. Returns { nodeId, nodeRunId, creditCost } |
Magic resize: mel run magic-resize creates a magic-resize node, wires the source image in, and runs it — all in one call. The source node must already have a finished image asset. --ratios is comma-separated (1–5): presets 21:9, 16:9, 4:3, 1:1, 3:4, 9:16, 9:21 render at 1K, or pass custom WxH pixel sizes (each side 64–4096, e.g. 1080x1350). Pass the returned nodeId to mel run wait to block until the variants are ready.
Auto-model resolution: when --model/--variant are omitted, mel run start automatically resolves the correct model from the platform defaults. --canvas-id is required so the CLI can verify the node is on the canvas and the backend can do edge-aware detection (picks image-to-image when the node has incoming image edges). Always source the node ID from the latest mel canvas content output or the current-turn idMap returned by mel node bulk-create.
# Auto-resolve (recommended)
mel run start <nodeId> --canvas-id <canvasId>
# Explicit model
mel run start <nodeId> --canvas-id <canvasId> --model flux-pro --variant text-to-image
# With overrides
mel run start <nodeId> --canvas-id <canvasId> --prompt "Override prompt" --variations 3Bulk runs (entire canvas)
| Command | Description |
| ----------------------------------- | -------------------------------------------------------------------- |
| mel bulk-run start <canvasId> | Run all nodes in dependency order. Returns BulkNodeRunResponse |
| mel bulk-run get <bulkRunId> | Check bulk run status and per-node progress |
| mel bulk-run wait <bulkRunId> | Block until done. Exit 0 = finished, 1 = failed, 4 = timeout |
| mel bulk-run download <bulkRunId> | Download ZIP of leaf node outputs. Returns { url, filename, size } |
Nodes are executed tier-by-tier: downstream nodes wait for their parents to finish. If a parent fails, its dependents are skipped but independent branches continue.
When you pass --node-ids, use UUIDs from the latest mel canvas content output or the idMap returned by mel node bulk-create.
# Run all nodes and wait for completion (recommended)
mel bulk-run start <canvasId> --wait
# Run specific nodes only
mel bulk-run start <canvasId> --node-ids <id1>,<id2>,<id3>
# Start without blocking, then check later
mel bulk-run start <canvasId>
mel bulk-run get <bulkRunId>
mel bulk-run wait <bulkRunId> --timeout 120
# Get per-node status
mel bulk-run get <bulkRunId> --fields status,completedNodes,failedNodes,skippedNodes
# Download leaf node outputs as ZIP after completion
mel bulk-run download <bulkRunId>Presets
Presets are pre-built canvas templates with nodes, edges, and groups already configured.
| Command | Description |
| ---------------------------------------- | ------------------------------------------------------------------------ |
| mel preset list | List available presets with node/edge configurations |
| mel preset apply <canvasId> <presetId> | Apply a preset — creates all nodes, edges, and groups with proper layout |
mel preset apply replicates the frontend layout algorithm: single-group presets get column-based layout, multi-group presets use their saved positions centered on --center-x/--center-y.
# Discover presets
mel preset list
mel preset list --fields id,title,icon
# Apply a preset and run all nodes
mel preset apply <canvasId> <presetId>
mel bulk-run start <canvasId> --waitModels
| Command | Description |
| ------------------------------------------------ | ------------------------------------------------------------- |
| mel model list --category <text\|image\|video> | Discover available models, modes, and supported aspect ratios |
| mel model defaults | Show auto-model defaults for each node type |
Fonts
| Command | Description |
| --------------- | -------------------------------------------------------------------- |
| mel font list | List the team's uploaded custom fonts for use with better-font-32b |
Use a familyName from mel font list with the better-font-32b image model by setting font: { source: "custom", familyName } in the node's model config. For non-custom typography, use a Google font family with source: "google" instead. Never guess custom font family names.
Comments
| Command | Description |
| ------------------------------------------------------------- | ----------------------------------- |
| mel comment list <canvasId> | List all comment threads |
| mel comment create <canvasId> --body <text> --x <n> --y <n> | Start a thread at a canvas position |
| mel comment reply <threadId> --body <text> | Reply to a thread |
| mel comment resolve <threadId> | Mark a thread as resolved |
Configuration
| Command | Description |
| ------------------------------ | ------------------------------------------------------- |
| mel config get [key] | Show config (or a specific key) |
| mel config set <key> <value> | Set a config value. Keys: apiKey, baseUrl, teamId |
| mel config path | Print config file path (~/.mel/config.json) |
Config priority: MEL_API_KEY / MEL_BASE_URL / MEL_TEAM_ID env vars > config file > defaults.
Upload
| Command | Description |
| ---------------------------------------- | ------------------------------------------ |
| mel upload <filePath> | Upload a local image to Melius |
| mel upload <filePath> --node-id <id> | Upload and associate with an existing node |
| mel upload <filePath> --canvas-id <id> | Upload and associate with a canvas |
Supported formats: PNG, JPEG, GIF, WebP, AVIF.
Returns { assetId, s3Bucket, s3Key, url }. Use the returned url as --src when creating a file node, or pass --node-id to link the upload directly to an existing node.
# Upload and create a file node from the result
mel upload ./photo.png
mel upload ./photo.png --node-id <nodeId> --canvas-id <canvasId>Assets (Files / DAM library)
Requires the Pro or Enterprise plan.
| Command | Description |
| ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
| mel asset search [query] | Search the team's Files/DAM library (uploads + past generations) by meaning; exact matches rank first. Returns up to 20 matches per page |
| mel asset folders | List the library's folders (id, name, path, fileCount) so you can resolve a folder name to an id for --folder-id |
| mel asset place <canvasId> <assetIds...> | Place existing library assets onto a canvas by id — references the original asset (no duplicate) |
mel asset search searches by meaning (semantic), with exact filename/title matches ranked first, so query with a filename or a description of the content. It returns { query, total, results: [{ assetId, filename, fileType, previewUrl, ... }] }. Filter with --file-type <image|video|audio|pdf|text> and --limit <1-20>; page deeper with --offset <n> (skip the first N matches — page until the running count reaches total); scope to one canvas with --canvas-id (omit to search the whole team library).
Scope to a folder with --folder-id (from mel asset folders): with a query it filters within that folder and its subfolders; omit the query to bring in the whole folder (every asset in it). mel asset folders returns each folder's root-relative path (e.g. Brand / Logos) to disambiguate same-named folders.
mel asset place creates a file node per asset (writes to CRDT, no re-upload) and returns { placed: [{ assetId, nodeId, mediaType }] }. Wire the returned nodeIds into generations with mel edge create. This is the correct way to reuse an existing library asset — do not re-upload it.
# Find an asset, then bring it onto a canvas and wire it
mel asset search "black jacket shot" --file-type image
mel asset place <canvasId> <assetId1> <assetId2>
# Bring in a whole folder: resolve the folder, then search it with no query
mel asset folders # → [{ id, name, path, fileCount }]
mel asset search --folder-id <folderId> # every asset in the folder
mel asset search "shark" --folder-id <folderId> # or filter within itShell completion
mel completion <shell> prints a tab-completion script for bash, zsh, or fish. The script is generated from the live command tree, so it never drifts from the actual commands.
# bash — add to ~/.bashrc
echo 'eval "$(mel completion bash)"' >> ~/.bashrc
# zsh — add to ~/.zshrc
echo 'eval "$(mel completion zsh)"' >> ~/.zshrc
# fish
mel completion fish > ~/.config/fish/completions/mel.fishAgent workflow
A typical agent session:
# 1. Discover the workspace
mel project list
mel canvas list <projectId>
# 2. Read current state
mel canvas content <canvasId>
# 3. Discover models
mel model list --category image
mel model defaults
# 4a. Build a generation pipeline from scratch
TEXT=$(mel node create <canvasId> --type text --prompt "Describe a product photo")
IMAGE=$(mel node create <canvasId> --type image --prompt "Professional product shot" --aspect-ratio 1:1)
mel edge create <canvasId> --src $(echo $TEXT | jq -r .id) --dst $(echo $IMAGE | jq -r .id) --type text
# 4b. Or do it all in one call (nodes + edges via combined JSON)
mel node bulk-create <canvasId> --json - <<'EOF'
{"nodes":[{"id":"brief","type":"text","prompt":"Describe a product photo"},{"id":"hero","type":"image","prompt":"Professional product shot","aspectRatio":"1:1"}],"edges":[{"src":"brief","dst":"hero","type":"text"}]}
EOF
# 4c. Or apply a preset template (creates all nodes, edges, and groups)
mel preset list
mel preset apply <canvasId> <presetId>
# 5a. Run a single node and wait
RUN=$(mel run start $(echo $IMAGE | jq -r .id) --canvas-id <canvasId>)
mel run wait $(echo $RUN | jq -r .id)
# 5b. Or run all nodes at once (dependency-aware)
mel bulk-run start <canvasId> --wait
# 6. Inspect results
mel canvas content <canvasId> --node-id $(echo $IMAGE | jq -r .id) --include-all-versions
# 7. Organize — group related nodes together
mel node group <canvasId> --node-ids <id1>,<id2>,<id3> --label "Hero section"
# 8. Leave feedback
mel comment create <canvasId> --body "Generation complete" --x 0 --y 0Output contract
All commands follow the same contract:
- Success: structured JSON on
stdout, exit code0 - Errors: structured JSON on
stderr, deterministic exit code
Exit codes
| Code | Meaning | Agent action |
| ---- | ---------------------- | ----------------------------- |
| 0 | Success | Parse stdout |
| 1 | API error (4xx/5xx) | Read error message |
| 2 | Usage error (bad args) | Fix command |
| 3 | Auth error (401/403) | Re-authenticate or check team |
| 4 | Timeout | Retry or increase --timeout |
| 5 | Network error | Check connectivity |
Error shape
{
"error": {
"code": "UNAUTHORIZED",
"message": "Invalid or expired API key",
"status": 401,
"suggestion": "Sign up at https://app.melius.com/signup, then create an API key in the Melius app under Team settings → Integrations. Then run: mel auth login --api-key mel_..."
}
}Every error includes a machine-readable code, a human-readable message, and often a suggestion with the exact command to fix the issue.
Rate limiting
The API is rate limited per plan tier. On a 429 the CLI prints a Rate limited — waiting Ns… notice to stderr and automatically backs off and retries — honoring the Retry-After header, otherwise exponential backoff — so polling commands like mel run wait ride through a limit instead of failing. stdout stays clean for parsing. After repeated 429s it gives up with a structured RATE_LIMITED error (exit 1).
Global flags
--json JSON output (default)
--text Tab-separated plain text
--fields <csv> Select specific fields: --fields id,title,status
--quiet Suppress output, exit code only
--verbose Log request/response metadata to stderrHow it works
The CLI is a thin, stateless wrapper around the Melius REST API. Node and edge mutations write directly to the CRDT document via the backend's direct mutation service — changes sync to connected browser clients in real-time via WebSocket.
