CLI Reference
CLI version 0.2.0. API version v1. Requires Node.js 20 or newer.
Commands are gavana GROUP ACTION [ARGUMENTS] [OPTIONS]. gavana-canvas, craftboard, and craftboard-canvas are the same program under different names.
Every group accepts --help, which prints usage for that group. gavana canvas --help and gavana node create --help both work.
How output works
One JSON object goes to stdout. Progress and errors go to stderr, so stdout stays parseable.
{ "ok": true, "result": {} }Output formats
--output | Produces |
|---|---|
json (default) | The ok/result envelope on one line |
jsonl | One JSON object per line; arrays are expanded into rows |
markdown | A chat-ready rendering, including image links for completed generation |
human | A flattened, indented label: value view for terminal reading |
raw | Bare strings and numbers, one per line, without JSON quotes |
--pretty indents the JSON envelope by two spaces. -r / --raw is equivalent to --output raw and may only be combined with --output json or --output raw — pairing it with human, jsonl, or markdown is a usage error.
Selecting with --jq
--jq takes a jq-style path and is evaluated by a small built-in selector, so no jq installation is required.
gavana canvas list --limit 25 --jq '.canvases[].handle' -r
gavana canvas get canvas:OWNER_UID:CANVAS_ID --jq '.canvas.revision' -r
gavana asset list --jq '.assets[]' --output jsonlSupported syntax: property paths (.canvas.revision), non-negative array indexes (.canvases[0].title), and [] projections (.connections[].models[]). Paths are limited to 500 characters, must start with ., and cannot select __proto__, prototype, or constructor. Anything else is a usage error.
--jq changes the shape of the output. Without it you get { "ok": true, "result": … }; with it you get only the selected value, unwrapped. Scripts that expect the envelope must not use --jq, and scripts that use --jq must not look for .result.
Errors
Errors are written to stderr:
{
"ok": false,
"error": {
"code": "input_validation_error",
"message": "baseRevision is required.",
"fields": [
{
"field": "baseRevision",
"message": "Read the canvas and pass its current revision."
}
],
"requestId": "7f8e0d9d-4bf8-4e6e-96e0-8bcd6a8f315a"
}
}requestId is safe to share with support. Nothing else from a failing request is. Never share a token.
Server error codes map from HTTP status: 400/422 to validation, 401 to unauthorized, 403 to forbidden, 404 to not_found, 409 to conflict, 429 to rate_limited, 5xx to upstream. Locally raised errors use usage, configuration, network, timeout, canceled, invalid_response, or internal.
Retry behaviour
Transient network failures and 429, 502, 503, 504 responses are retried automatically — up to three attempts, respecting Retry-After — only for GET requests. No write is ever retried automatically, and no paid generation request is ever retried automatically.
Exit codes
| Exit | Meaning |
|---|---|
0 | Success |
1 | Any other failure, including a failed doctor |
2 | Usage, configuration, or validation error; HTTP 400, 413, 415, 422 |
3 | Authentication or authorization error; HTTP 401, 403 |
4 | Revision or idempotency conflict; HTTP 409 |
5 | Not found; HTTP 404 |
7 | Network error — Gavana could not be reached |
8 | Wait timeout — the Run or Job can still be resumed |
9 | A waited Run or Job ended failed, canceled, or expired |
Exit 9 applies to image generate|edit|variations, video generate, action run, recipe run, job wait, and run wait — the commands that wait for a terminal state. The complete terminal resource is still written to stdout, so a script can read the reason. Exit 8 means you stopped waiting, not that the work stopped: resume it with run wait or job wait.
Global options
| Option | Effect |
|---|---|
--output FORMAT | human, json, jsonl, markdown, or raw; JSON is the default |
--pretty | Pretty-print JSON |
--jq PATH | Select a jq-style path, for example .canvases[].handle |
-r, --raw | Print selected strings and numbers without JSON quotes |
--limit N | Maximum list items to return |
--cursor VALUE | Continue a list from page.nextCursor |
--base-url URL | Overrides saved configuration |
--profile NAME | Use one named account/environment profile |
--token-stdin | Read a login token from stdin |
--base-revision VALUE | Enforce a previously read canvas revision |
--idempotency-key VALUE | Make retries deterministic |
--destination VALUE | agent-canvas, new-canvas, or an existing canvas handle |
--model VALUE | Provider model id, or a model: handle from model list |
--element VALUE | Repeatable exact element:<id>@v<n> revision for image generation |
--connection VALUE | Disambiguate a provider model id by connection |
--input VALUE | Recipe KEY=VALUE or Action input; repeat as needed |
--param NAME=VALUE | Action parameter; repeat for multiple parameters |
--webhook-url URL | Send one signed callback when a Run finishes |
--webhook-secret-env VAR | Read the signing secret from this variable (default GAVANA_WEBHOOK_SECRET) |
--canvas-title VALUE | Title used when --destination is new-canvas |
--no-wait | Return immediately after queueing Recipe, image, video, or Action work |
--progress | Write Run or video Job state transitions to stderr |
Also available everywhere: --help / -h, --version / -v / -V, --yes for destructive confirmations, --timeout SECONDS and --interval SECONDS for waits, and --json / --file to supply a full JSON payload instead of individual flags.
Argument parsing
--flag valueand--flag=valueare both accepted.- Boolean flags take no value:
help,version,pretty,raw,yes,wait,no-wait,progress,token-stdin,no-verify,no-browser,audio,no-audio,read-only. - Repeatable options:
--reference,--element,--input,--param,--target,--ratio,--approved,--field. Any other repeated option keeps its last value. --ends option parsing; everything after it is positional.- A non-boolean flag whose next argument starts with
--is treated astrue. If a value could start with--, use--flag=value.
Command groups
Twenty-three groups: action, ai-connection, api, asset, auth, campaign, canvas, capabilities, completion, config, connection, doctor, element, image, job, mcp, model, node, provider, recipe, run, version, video.
auth
Authenticate a profile.
gavana auth login [--profile NAME] [--read-only] [--no-browser] [--no-verify]
gavana auth login --token-stdin [--profile NAME] [--base-url URL]
gavana auth status [--profile NAME]
gavana auth logout [--profile NAME]| Option | Effect |
|---|---|
--read-only | Request only canvas:read, asset:read, and element:read at consent |
--no-browser | Print the authorization URL instead of opening a browser |
--token-stdin | Read a Personal Access Token from stdin instead of using OAuth |
--no-verify | Skip the post-login verification call |
--base-url URL | Sign in to a specific environment |
Browser login. The CLI registers an OAuth client at /oauth/register, opens /oauth/authorize with PKCE (S256), listens on a loopback callback, and exchanges the code at /oauth/token for the resource /api/canvas-agent/v1. It times out after three minutes. If verification or config writing fails afterwards, the CLI revokes the token it just obtained rather than leaving a live credential behind.
Output. login returns authenticated, profile, baseUrl, configPath, a masked token, verified, loginMethod (browser or token), and — when verification ran — authType and scopes. status adds configured and, where readable, canvasCount. logout revokes an OAuth token at /oauth/revoke, removes the profile, and returns the remaining activeProfile.
Tokens are masked as the first seven characters, an ellipsis, and the last four.
gavana auth login --read-only
gavana auth status --jq '.scopes[]' -rExit codes. 0; 2 for a usage error such as an unknown action; 3 if the credential is rejected; 7/8 for network or timeout.
config
Manage named profiles. Never contacts the network.
gavana config list
gavana config get [PROFILE]
gavana config use PROFILEconfig with no action behaves as list; profiles is accepted as an alias for list.
Output. list returns activeProfile, configPath, and a sorted profiles array of name, baseUrl, active, and configured. get returns one profile’s profile, baseUrl, configured, masked token, and configPath. use returns active: true, the profile name, and configPath.
gavana config list --output human
gavana config use workExit codes. 0; 2 for an unknown profile or an invalid profile name.
doctor
Diagnose the local setup.
gavana doctor [--profile NAME]Runs four checks — node, credentials, base_url, authentication — and returns ok, profile, and the checks array. A failed authentication check includes the error code and requestId.
gavana doctor --output human
gavana doctor --jq '.checks[]' --output jsonlExit codes. 0 when every check passes, 1 when any check fails.
api
Raw passthrough to the Canvas API. Paths are restricted to /api/canvas-agent/v1.
gavana api GET /canvases --field limit=10
gavana api POST /canvases --json '{"title":"Concepts"}'
gavana api /canvasesThe method is optional and defaults to GET. Valid methods are GET, POST, PUT, PATCH, DELETE, case-insensitive.
| Option | Effect |
|---|---|
--field NAME=VALUE | Repeatable. Query parameters on GET, request body fields otherwise |
--json TEXT | A full JSON body |
--file PATH | A JSON body read from a file, or - for stdin |
Paths must match a conservative pattern and cannot contain ... --json and --file are mutually exclusive.
Output. The API response, unmodified, inside the standard envelope. When the server sent an X-Request-ID and the payload has no requestId of its own, the CLI adds it.
gavana api GET /canvases --field limit=5 --jq '.canvases[].handle' -r
gavana api GET /auth/status --prettyExit codes. Mapped from the HTTP status: 2, 3, 4, 5, 7, 8, or 1.
mcp
Register or print an MCP client configuration.
gavana mcp install codex
gavana mcp install claude
gavana mcp config cursor|chatgpt|localinstall executes the client’s own registration command. config prints the definition without changing anything. install supports only codex and claude; config accepts codex, claude, cursor, chatgpt, and local (stdio is an alias for local).
| Client | Definition |
|---|---|
codex | streamable-http; runs codex mcp add gavana --url ENDPOINT |
claude | streamable-http; runs claude mcp add --transport http gavana ENDPOINT |
cursor | streamable-http; prints an mcpServers object with a url |
chatgpt | streamable-http; prints the endpoint and connector instructions |
local | stdio; npx -y @gavana.ai/mcp@0.2.0 |
The hosted endpoint is the profile’s base URL plus /mcp. For a local server, set GAVANA_MCP_READ_ONLY=true in the process environment if you want only non-mutating tools registered.
gavana mcp install codex
gavana mcp config local --prettyExit codes. 0; 2 for an unsupported client or action; 1 if the client command fails.
capabilities
Print the machine-readable capability summary. Requires no credential.
gavana capabilitiesOutput. apiVersion, cliVersion, the ten toolsets (canvas, recipes, assets, elements, models, actions, images, videos, runs, campaigns), the 29 hosted remote capabilities each with toolset, readOnly, and paid flags, and the endpoints object (/mcp, /api/canvas-agent/v1).
gavana capabilities --jq '.remote[]' --output jsonlExit codes. 0.
version
gavana version
gavana --version
gavana -vOutput. { "name": "@gavana.ai/cli", "version": "0.2.0", "node": "v20.x.x" }.
Exit codes. 0.
completion
gavana completion zsh
gavana completion bash
gavana completion fishWrites a completion script to stdout. With no shell argument it uses --shell, then the basename of $SHELL, then zsh.
gavana completion zsh >> ~/.zshrcExit codes. 0; 2 for an unsupported shell.
canvas
gavana canvas list [--limit N] [--cursor CURSOR]
gavana canvas agent
gavana canvas create --title "New canvas" [--id ID]
gavana canvas get canvas:OWNER_UID:CANVAS_ID
gavana canvas render canvas:CANVAS_ID [--file canvas.svg]
gavana canvas apply canvas:CANVAS_ID --file batch.json [--base-revision REV] [--idempotency-key KEY] [--yes]| Subcommand | Notes |
|---|---|
list | --limit is 1–25. Continue with --cursor from page.nextCursor |
agent | Resolves or creates the persistent Agent Canvas |
create | --title defaults to the joined positional arguments, then to Untitled canvas. Accepts --json/--file instead |
get | Returns the full graph and the current revision |
render | Returns SVG. With --file it writes the file and returns mediaType, file, and bytes; without, it returns mediaType and svg. --file - returns the SVG inline |
apply | Applies an atomic operation batch |
canvas apply in detail. Operations come from --json, --file, or --operations as a JSON array. If the batch contains any node.delete or connection.delete, --yes is required. --base-revision is read from the canvas automatically when omitted; --idempotency-key is generated as a UUID when omitted. Supply both explicitly for anything you might need to retry.
gavana canvas list --limit 25 --jq '.canvases[].handle' -r
gavana canvas get canvas:OWNER_UID:CANVAS_ID --jq '.canvas.revision' -r
gavana canvas render canvas:OWNER_UID:CANVAS_ID --file ./board.svg
gavana canvas apply canvas:OWNER_UID:CANVAS_ID --file ./batch.json --idempotency-key brief-2026-08-04-aExit codes. 0; 2 for a missing handle or a malformed batch; 4 on a revision conflict; 5 if the canvas does not exist.
node
Every node write is applied as a single-operation atomic batch, with the canvas’s current revision read automatically unless you pass --base-revision, and a generated UUID idempotency key unless you pass --idempotency-key.
gavana node get canvas:CANVAS_ID node:NODE_ID
gavana node create canvas:CANVAS_ID --type text --title "Direction" --prompt "..."
gavana node update canvas:CANVAS_ID node:NODE_ID --title "Approved"
gavana node move canvas:CANVAS_ID node:NODE_ID --x 800 --y 120
gavana node resize canvas:CANVAS_ID node:NODE_ID --width 420 --height 300
gavana node delete canvas:CANVAS_ID node:NODE_ID --yes| Subcommand | Options |
|---|---|
create | --type (default text), --title (default Agent Node), --x --y (default 0), --width, --height, --content, --prompt, --metadata JSON, --client-id |
update | --title, --x and --y (both or neither), --width, --height, --content, --prompt, --metadata JSON |
move | --x and --y, both required |
resize | --width and --height, both required |
delete | --yes required |
--content and --prompt are written into the node’s metadata. An update with no recognised option is a usage error rather than a no-op write. All of these accept --json/--file to supply the node or patch directly.
gavana node create canvas:OWNER_UID:CANVAS_ID \
--type text --title "Customer objections" \
--x 1200 --y 200 --width 1040 --height 720 \
--metadata '{"isSection":true}'
gavana node create canvas:OWNER_UID:CANVAS_ID \
--type sticky --title "Observation" \
--content "Buyers hesitate at the price reveal" \
--x 1248 --y 300
gavana node delete canvas:OWNER_UID:CANVAS_ID node:NODE_ID --yesExit codes. 0; 2 for a missing handle, an empty patch, or a delete without --yes; 4 on a revision conflict; 5 if the canvas or node does not exist.
connection
gavana connection list canvas:CANVAS_ID
gavana connection create canvas:CANVAS_ID --from node:NODE_ID --to node:NODE_ID [--mode MODE]
gavana connection delete canvas:CANVAS_ID connection:CONNECTION_ID --yeslist reads the canvas and returns canvas, revision, and connections.
--mode values: omit it for a normal dependency or transformation flow, reference for an image reference guiding another node, list for a Prompt List or Image List flow, first-frame and last-frame for the frames of a video node. A first-frame or last-frame edge must connect an image node to a video node, and a video node may have only one of each. --from and --to can also be given as the second and third positional arguments, and --client-id names the new connection within a batch.
gavana connection list canvas:OWNER_UID:CANVAS_ID --jq '.connections[]' --output jsonl
gavana connection create canvas:OWNER_UID:CANVAS_ID --from node:BRIEF --to node:GENERATOR
gavana connection create canvas:OWNER_UID:CANVAS_ID --from node:STILL --to node:CLIP --mode first-frame
gavana connection delete canvas:OWNER_UID:CANVAS_ID connection:CONNECTION_ID --yesExit codes. 0; 2 for a missing handle or a delete without --yes; 4 on a revision conflict; 5 if an endpoint does not exist.
asset
gavana asset list [--canvas canvas:CANVAS_ID] [--limit N] [--cursor CURSOR]
gavana asset get asset:ASSET_ID
gavana asset get asset:OWNER_UID:ASSET_ID
gavana asset upload path/to/image.png--limit on list is 1–200. The canvas filter may also be the first positional argument.
upload accepts a file path, --file PATH, - for stdin, or clipboard on macOS. The file must be a PNG, JPEG, WebP, or GIF — the CLI sniffs the magic bytes rather than trusting the extension — and must be at most 50 MB. Anything else is a usage error before a byte is sent.
Use the owner-qualified asset:OWNER_UID:ASSET_ID handle exactly as returned for a shared asset.
Scopes. list and get need asset:read. upload needs asset:read plus either image:generate or video:generate; when the upload is scoped to a canvas it also needs canvas:read and canvas:write.
gavana asset list --canvas canvas:OWNER_UID:CANVAS_ID --limit 50 --jq '.assets[].handle' -r
gavana asset upload ./product.png --jq '.asset.handle' -r
pbpaste | true; gavana asset upload clipboardExit codes. 0; 2 for an unsupported file, an oversized file, or a missing handle; 5 if the asset does not exist.
element
Elements are reusable visual references built from existing assets, written guidelines, or both. Current Elements use element:<id> handles; every immutable revision uses element:<id>@v<n>.
gavana element list [query] [--state active|archived] [--limit N] [--cursor CURSOR]
gavana element get element:<id>@v<n>
gavana element history element:<id> [--limit N] [--cursor CURSOR]
gavana element create --name "Soft window light" --type lighting [--source-asset asset:<id>] [--guidelines "..."]
gavana element update element:<id> --name "..." --type lighting [--source-asset asset:<id>] [--guidelines "..."]
gavana element collections element:<id> [--collection element-collection:<id>]
gavana element archive element:<id> [--yes]
gavana element restore element:<id>
gavana element collection-list [--limit N] [--cursor CURSOR]
gavana element collection-create --name "Campaign assets"
gavana element collection-rename element-collection:<id> --name "..."
gavana element collection-delete element-collection:<id> [--yes]create and update accept up to eight source assets, guidelines up to 2,000 characters, or both. Updating visual content creates a new immutable revision. Collection membership is deliberately separate: use element collections, which does not create a visual revision.
archive is recoverable and asks for confirmation interactively; scripts must pass --yes. Collection deletion has the same rule and never deletes the Elements inside it. The CLI exposes no permanent Element deletion.
Scopes. Read commands need element:read. Create, update, organize, archive, restore, and collection mutations need element:read plus element:write.
gavana element list "light" --jq '.elements[].versionHandle' -r
gavana element get element:soft-window-light@v2 --pretty
gavana element archive element:soft-window-light --yesExit codes. 0; 2 for an invalid or unpinned handle or a destructive non-interactive command without --yes; 3 for a missing scope; 5 when the Element or collection does not exist.
provider and ai-connection
Two names for the same command. Lists the AI provider connections saved on the account.
gavana provider list [--limit N] [--cursor CURSOR]
gavana ai-connection list [--limit N] [--cursor CURSOR]--limit is 1–100. Only list exists.
Scopes. Requires image:generate or video:generate — a purely read-only token cannot browse connections.
gavana provider list --output humanExit codes. 0; 2 for an invalid --limit; 3 if the token has neither generation scope.
model
gavana model list [query] [--provider PROVIDER] [--capability CAPABILITY] [--limit N] [--cursor CURSOR]
gavana model get model:OPAQUE_MODEL_KEY--limit is 1–100. The query may be given as --query or as positional words. Useful capability filters include image.generate, image.edit, video.generate, video.generate.fromImage, video.generate.fromFrames, and video.generate.fromReferences.
model get returns the model’s exact parameters, valid option values, minimums and maximums, capabilities, connection, and estimated duration. Read it before starting paid work — it is how you find out which durations and aspect ratios a model actually accepts.
Scopes. Both subcommands require image:generate or video:generate. Browsing the model catalog is not available to a read-only token.
gavana model list --capability video.generate --jq '.models[].handle' -r
gavana model get model:OPAQUE_MODEL_KEY --prettyExit codes. 0; 2 for an invalid --limit; 5 if the model handle does not exist.
action
Deterministic Image Actions make precise raster changes without asking an AI model and without spending AI credits. The first stable set is resize, crop, change aspect ratio, side-by-side composite, add text, overlay image, colour grade, and rotate.
gavana action list [query] [--limit N] [--cursor CURSOR]
gavana action get action:SLUG
gavana action run action:SLUG --input VALUE --destination DESTINATION [--param NAME=VALUE]--limit on list is 1–100.
action run in detail. You must supply exactly as many --input values as the Action declares, in the order shown by action get — the CLI checks the count before sending anything. Inputs accept a node: or asset: handle, a local PNG/JPEG/WebP/GIF path, - for stdin, or clipboard on macOS. Local, stdin, and clipboard inputs are uploaded privately first, which is why they additionally require image:generate or video:generate on the token; existing handles do not.
Scopes. list and get need canvas:read. run needs canvas:read, canvas:write, and asset:read — no generation scope, because Actions are deterministic — plus a generation scope only when an input is a local file, stdin, or the clipboard, and job:manage to wait for the result.
| Option | Effect |
|---|---|
--destination | agent-canvas, new-canvas, or a canvas handle. Required |
--canvas-title | Required when --destination is new-canvas |
--param NAME=VALUE | Repeatable schema parameter |
--target node:NODE_ID | Write into an existing node instead of creating one |
--title | Title for the created target node; defaults to the Action title plus “result” |
--no-wait | Return the queued run: handle immediately |
--progress | Stream state transitions to stderr |
--timeout SECONDS | Default 900 (15 minutes) |
--interval SECONDS | Poll interval, default 1.5 |
--webhook-url, --webhook-secret-env | One signed terminal callback |
Declared parameters also get generated flags: a parameter id in camelCase becomes a kebab-case flag, so aspectRatio is --aspect-ratio. Numeric parameters are validated as numbers.
gavana action list
gavana action get action:resize --pretty
gavana action run action:resize \
--input ./product.png \
--destination agent-canvas \
--width 1080 --height 1350
gavana action run action:side-by-side-composite \
--input node:FIRST_IMAGE \
--input asset:SECOND_IMAGE \
--destination canvas:OWNER_UID:CANVAS_ID \
--direction horizontal --gap 24Output. The terminal Run with typed outputs and durable node: and asset: handles, plus a destination object recording what you requested, the resolved canvas, and the target node ids.
Exit codes. 0; 2 for the wrong number of inputs, a missing destination, or an unsupported file; 3 if the token lacks a required scope; 8 if you stopped waiting; 9 if the Run ended failed, canceled, or expired.
recipe
Recipes are reusable canvas workflows. Forking and running are deliberately separate.
gavana recipe search [query] [--limit N] [--cursor CURSOR]
gavana recipe get recipe:RECIPE_ID [--version VERSION]
gavana recipe fork recipe:RECIPE_ID --canvas canvas:CANVAS_ID --idempotency-key KEY [--x X --y Y]
gavana recipe run recipe:RECIPE_ID --input KEY=VALUE --destination DESTINATION--limit on search is 1–100.
recipe fork adds a private, editable Recipe card to a canvas and never executes it. Options: --canvas (or the second positional argument), --version, --base-revision, --x, --y, --idempotency-key.
recipe run is the explicit execution step. It binds typed inputs, creates the declared output nodes on the destination canvas, and starts text or image work.
| Option | Effect |
|---|---|
--input KEY=VALUE | Repeatable. One per declared input |
--destination | agent-canvas, new-canvas, or a canvas handle. Required |
--canvas-title | Required when --destination is new-canvas |
--version | Pin a Recipe version |
--instance-node node:NODE_ID | Run a connected private Recipe instance instead of supplying inputs |
--model, --size, --quality | Override generation settings |
--no-wait, --progress, --timeout, --interval | Waiting behaviour; default timeout 900 seconds |
--webhook-url, --webhook-secret-env | One signed terminal callback |
--base-revision, --idempotency-key | Concurrency and retry control |
Input keys. Use the exact keys recipe get returns. The CLI also accepts a port’s node id, its normalised label, and — where unambiguous — the first word of its label. Supplying the same input twice is an error.
Input values.
| Form | Meaning |
|---|---|
plain text | Literal text |
node:NODE_ID or asset:ASSET_ID | An existing handle |
@path/to/file | On a written port, read the file as text |
@- | Read text from stdin |
@@literal | An escaped value that really starts with @ |
A path, -, or clipboard on an image port | Uploaded privately, then passed as an asset: handle |
The @path distinction is the one that bites: on a written port @brief.md inserts the file’s text; on an image port the value is treated as an image reference. To use a local visual on a written port that accepts “an image or written note”, upload it first with asset upload and pass the returned asset: handle.
recipe run is paid work. Every Recipe start currently requires image:generate and its dependent scopes — including text-only Recipes. Ask for explicit approval in the current turn, and never retry a failed run automatically.
gavana recipe search "product visual" --jq '.recipes[].handle' -r
gavana recipe get recipe:product-visual-direction --pretty
gavana recipe fork recipe:product-visual-direction \
--canvas canvas:OWNER_UID:CANVAS_ID \
--idempotency-key fork-product-visual-a
gavana recipe run recipe:product-visual-direction \
--input product-context="A matte black travel bottle for a quiet premium campaign" \
--destination canvas:OWNER_UID:CANVAS_ID \
--idempotency-key brief-2026-08-04-a \
--progress
gavana recipe run recipe:social-creative-angles \
--input offer-brief=@brief.md \
--destination agent-canvasOutput. The terminal Run with typed outputs, durable handles, estimatedSeconds, observed queue and execution timing, and — on failure — a stable failure.code and failure.retryable. With --no-wait, the queued Run plus a destination object.
Exit codes. 0; 2 for a missing input, an unknown input key, or a missing destination; 3 for a missing scope; 4 on a conflict; 8 if you stopped waiting; 9 on a terminal failed, canceled, or expired.
image
gavana image generate --destination DESTINATION --prompt "..."
gavana image edit --destination DESTINATION --reference REFERENCE --prompt "..."
gavana image variations --destination DESTINATION --reference REFERENCE --prompt "..."| Option | Effect |
|---|---|
--destination | agent-canvas, new-canvas, or a canvas handle. Required; may also be the first positional argument |
--canvas-title | Required when --destination is new-canvas |
--prompt | The instruction |
--prompt-node node:NODE_ID | Use an existing node’s text as the prompt |
--reference | Repeatable, up to 16. A node:/asset: handle, a local raster path, -, or clipboard |
--element | Repeatable, up to 8. An exact immutable element:<id>@v<n> handle |
--count N | 1–4 outputs |
--target node:NODE_ID | Repeatable. Write into existing nodes instead of creating them |
--target-node node:NODE_ID | A single explicit target |
--model, --connection, --size, --quality, --source | Generation settings |
--title, --x, --y, --width, --height | Geometry for automatically created targets |
--no-wait, --progress, --timeout, --interval | Waiting behaviour; default timeout 900 seconds |
--webhook-url, --webhook-secret-env | One signed terminal callback |
--base-revision, --idempotency-key | Concurrency and retry control |
Local references must be PNG, JPEG, WebP, or GIF and at most 50 MB each. Identical files are uploaded once and reused. When --target is omitted, the CLI creates the target nodes for you.
Elements may be prompt-only, image-only, or both. Their source images share the same 16-reference limit as direct references. JSON input may add a role and influence, for example { "handle": "element:soft-window-light@v2", "role": "style", "influence": 0.8 }.
Image generation charges the AI provider connected to the account. Ask before running it, and do not retry a failure automatically.
gavana image generate \
--destination agent-canvas \
--model model:OPAQUE_MODEL_KEY \
--prompt "A studio product photograph on a seamless grey backdrop" \
--progress
gavana image edit \
--destination canvas:OWNER_UID:CANVAS_ID \
--reference node:SOURCE_IMAGE \
--prompt "Warm the lighting and remove the reflection" \
--idempotency-key edit-hero-a
gavana image variations \
--destination new-canvas --canvas-title "Variations" \
--reference ./source.png \
--prompt "Four alternate angles" --count 4Output. The terminal Run with images, durable node: and asset: handles, and a destination object. --output markdown renders the results as image links.
Exit codes. 0; 2 for a missing destination or prompt, an invalid --count, or an unsupported reference; 3 for a missing scope; 4 if a reused idempotency key produced incomplete automatic targets; 8 if you stopped waiting; 9 on a terminal failure.
video
gavana video generate --model model:OPAQUE_MODEL_KEY --prompt "..." [--duration 15]
gavana video download job:JOB_ID --file output.mp4 [--yes]Video options
| Option | Effect |
|---|---|
--prompt VALUE | Video instruction. Required, maximum 8,000 characters |
--duration SECONDS | Exact model-supported duration, a whole number from 1 through 120 |
--aspect-ratio VALUE | Exact model-supported aspect ratio |
--resolution VALUE | Exact model-supported resolution |
--first-frame VALUE | node:, asset:, a public HTTPS URL, a local image, -, or clipboard |
--last-frame VALUE | Ending frame; requires --first-frame |
--reference VALUE | Model reference image; repeat up to nine times |
--canvas VALUE | Canvas containing any node: frame or reference |
--audio / --no-audio | Request or disable generated audio when the model supports it |
--download VALUE | Stream a successful generated video to this file |
Also accepted: --model (required), --connection, --idempotency-key (8–200 characters), --no-wait, --progress, --timeout (default 1800 seconds), --interval, --yes.
Validation before spending. video generate resolves the model and checks your options against its declared capabilities and parameters before starting paid work. It rejects --first-frame on a model without video.generate.fromImage, --last-frame without video.generate.fromFrames, --reference without video.generate.fromReferences, and any duration, aspect ratio, resolution, or audio setting the model does not list. It also refuses a bare provider model id that matches more than one connection — use the model: handle.
Other rules: --last-frame requires --first-frame; --canvas is required when any frame or reference is a node: handle; reference values must be unique; HTTPS URLs must not contain credentials; and --download cannot be combined with --no-wait.
video download. Streams a completed Job’s output to a file. It refuses to overwrite an existing file unless you pass --yes, writing through a temporary file created with mode 0600 and cleaning it up on failure. Returns job, file, bytes, and contentType.
gavana model list --capability video.generate --jq '.models[].handle' -r
gavana model get model:OPAQUE_MODEL_KEY --pretty
gavana video generate \
--model model:OPAQUE_MODEL_KEY \
--prompt "A slow product turntable on a matte grey surface" \
--duration 15 --aspect-ratio 9:16 \
--download ./turntable.mp4 \
--progress
gavana video generate \
--model model:OPAQUE_MODEL_KEY \
--prompt "Animate the fabric naturally" \
--first-frame node:PRODUCT_STILL \
--canvas canvas:OWNER_UID:CANVAS_ID \
--no-wait
gavana video download job:JOB_ID --file ./result.mp4Scopes. Generation requires canvas:read, asset:read, and video:generate. Waiting, downloading, and cancelling additionally require job:manage.
Exit codes. 0; 2 for an unsupported option for the chosen model, a missing prompt or model, or an existing output file without --yes; 3 for a missing scope; 8 if you stopped waiting; 9 on a terminal failure.
job
Image and Action work returns a run: handle; job: remains a compatibility alias for the same temporary record. Video generation returns a job: handle natively.
gavana job get job:JOB_ID
gavana job wait job:JOB_ID [--progress] [--timeout SECONDS] [--interval SECONDS]
gavana job cancel job:JOB_ID --yeswait defaults to a 900-second timeout and a 1.5-second poll interval. --progress writes each state transition, including a percentage when the provider reports one, to stderr as JSON.
gavana job wait job:JOB_ID --progress --output markdown
gavana job cancel job:JOB_ID --yesScopes. All three require job:manage.
Exit codes. 0; 2 for a cancel without --yes; 5 if the job does not exist or has expired; 8 on wait timeout; 9 if the job ended failed, canceled, or expired.
run
The shared observation interface for Recipe, image, and Action work.
gavana run get run:RUN_ID
gavana run wait run:RUN_ID [--progress] [--timeout SECONDS] [--interval SECONDS]
gavana run cancel run:RUN_ID --yesSame defaults as job. A failed provider request is returned as a terminal Run result rather than a generic request error, so run wait finishes with a readable reason: a stable failure.code and a failure.retryable flag.
gavana run wait run:RUN_ID --output markdown
gavana run get run:RUN_ID --jq '.status' -r
gavana run cancel run:RUN_ID --yesScopes. All three require job:manage.
Exit codes. 0; 2 for a cancel without --yes; 5 if the Run does not exist or has expired; 8 on wait timeout; 9 on a terminal failed, canceled, or expired.
campaign (retired)
The campaign command surface is retired and disabled. Running it returns:
The retired campaign command surface is disabled. Use Recipe Library forks and explicit canvas/image commands instead.It can be re-enabled for migration only by setting GAVANA_ENABLE_LEGACY_CAMPAIGN_COMMANDS=true (or the CRAFTBOARD_ENABLE_LEGACY_CAMPAIGN_COMMANDS alias). When enabled it exposes campaign plan, campaign start, campaign get, campaign review, and campaign cancel.
Do not build on it. Use Recipes and explicit canvas and image commands. See legacy campaigns for the API-side status.
Scopes by command
| Command | Scopes |
|---|---|
auth status, version, capabilities, completion, config, mcp | None |
doctor | None beyond whatever the credential already has |
canvas list/get/render, node get, connection list | canvas:read |
canvas create, canvas apply, node and connection writes | canvas:read, canvas:write |
asset list/get | asset:read |
asset upload | asset:read, plus image:generate or video:generate |
element list/get/history, element collection-list | element:read |
| Element and Element-collection mutations | element:read, element:write |
model list/get, provider list, ai-connection list | image:generate or video:generate |
recipe search/get | canvas:read |
recipe fork | canvas:read, canvas:write |
action list/get | canvas:read |
action run | canvas:read, canvas:write, asset:read |
image generate/edit/variations, recipe run | canvas:read, canvas:write, asset:read, image:generate; add element:read when applying Elements |
video generate | canvas:read, asset:read, video:generate |
run get/wait/cancel, job get/wait/cancel, video download | job:manage |
api | Whatever the target endpoint requires |
A batch sent with validateOnly needs only canvas:read — validation does not write, and does not consume an idempotency key. The CLI’s canvas apply always mutates; use MCP canvas_validate or the API for validation.
Stable handles
Copy these from responses. Never construct one.
canvas:CANVAS_ID
canvas:OWNER_UID:CANVAS_ID
node:NODE_ID
connection:CONNECTION_ID
asset:ASSET_ID
asset:OWNER_UID:ASSET_ID shared asset
element:ELEMENT_ID
element:ELEMENT_ID@v2 immutable revision
element-collection:COLLECTION_ID
recipe:RECIPE_ID
action:SLUG
model:OPAQUE_MODEL_KEY
run:RUN_ID
job:JOB_IDModel, Run, and Job ids are opaque. Copy the complete returned handle; do not decode, guess, or assemble one.
Destinations
Every command that produces canvas output takes --destination:
| Value | Meaning |
|---|---|
agent-canvas | The account’s persistent Agent Canvas |
new-canvas | A fresh canvas. Requires --canvas-title and an 8–200 character idempotency key |
canvas:OWNER_UID:CANVAS_ID | An existing canvas |
With new-canvas the idempotency key determines the canvas identity — the CLI derives the new canvas id from a hash of the key, so re-running the same command with the same key reuses that canvas instead of creating another.
Pagination
| Command | --limit maximum |
|---|---|
canvas list | 25 |
recipe search, action list, model list, provider list | 100 |
asset list | 200 |
Continue with --cursor set to page.nextCursor from the previous response, keeping the same filters. Cursors are opaque and at most 8,192 characters.
Webhooks
A signed callback replaces polling — useful for a start-only token that lacks job:manage.
read -rs 'GAVANA_WEBHOOK_SECRET?Webhook signing secret: '; printf '\n'
export GAVANA_WEBHOOK_SECRET
gavana image generate \
--destination agent-canvas \
--prompt "A studio product photograph" \
--webhook-url 'https://automation.example.com/hooks/gavana' \
--no-wait
unset GAVANA_WEBHOOK_SECRETThe secret is read from GAVANA_WEBHOOK_SECRET by default, or CRAFTBOARD_WEBHOOK_SECRET, or whichever variable you name with --webhook-secret-env. It must be 32–512 characters, and the variable name must be a valid identifier. The CLI never prints the secret, and --webhook-secret-env without --webhook-url is a usage error.
Recipe, image, and Action starts all accept webhooks. Successful image and Action callbacks are sent only after Gavana has stored the durable canvas, node, and asset results. Failed, canceled, and expired callbacks describe the terminal state without requiring images. Recipe callbacks carry their typed terminal outputs. If a start-only token is revoked or expires mid-Run, the Recipe stops with delegation_revoked and sends its signed failed callback.
Webhooks are deliberately unavailable through MCP so a signing secret cannot enter model-visible tool arguments. See Webhooks.
Result retention
Image and Action run: handles and their legacy job: aliases address the same temporary record and expire together. The defaults: 24 hours while unacknowledged, 15 minutes after the first successful server finalization (from a Run GET or a callback), and seven days for the expired tombstone. Operators may override these windows. Recipe Run records currently do not use this temporary TTL.
Persist the returned asset: and node: handles — those stay durable after the observation handles expire.
Telemetry
For commands that reach the API, the CLI reports a start and finish event — command name, status, duration, and correlation ids — to /api/agent-analytics/events on your Gavana instance. Requests carry X-Gavana-Agent-Surface and correlation headers so a Run can be traced end to end. No canvas content, prompt text, or credential is included, and a failed report never fails the command.