CLI

@nodaro/cli is a terminal client for Nodaro. It covers the core workflow/studio surface of the SDK — list and run workflows, run apps, run a single node directly, watch executions until they finish — wrapped in a small commander binary with multi-profile auth, JSON output for scripts, and --watch mode for interactive runs. (The CLI intentionally omits credits, developer-apps, OAuth, pipelines, reduce, and upload helpers — use the SDK directly for those.)

If you’re integrating from code, prefer the SDK directly. The CLI is the convenience wrapper for terminal work, cron jobs, CI pipelines, and ad-hoc inspection.

Install

Option A — npm (cross-platform, requires Node ≥ 20)

npm install -g @nodaro/cli
nodaro --version

Or run a one-off without installing:

npx @nodaro/cli projects list

Option B — standalone binary (no Node required)

Single-file executables compiled with bun build --compile — ~60 MB, ~10 ms cold start, no dependencies.

# macOS Apple Silicon
curl -L https://github.com/nodaroai/app.nodaro.ai/releases/latest/download/nodaro-darwin-arm64 \
  -o /usr/local/bin/nodaro && chmod +x /usr/local/bin/nodaro

# macOS Intel
curl -L https://github.com/nodaroai/app.nodaro.ai/releases/latest/download/nodaro-darwin-x64 \
  -o /usr/local/bin/nodaro && chmod +x /usr/local/bin/nodaro

# Linux x86_64
curl -L https://github.com/nodaroai/app.nodaro.ai/releases/latest/download/nodaro-linux-x64 \
  -o /usr/local/bin/nodaro && chmod +x /usr/local/bin/nodaro

# Linux ARM64
curl -L https://github.com/nodaroai/app.nodaro.ai/releases/latest/download/nodaro-linux-arm64 \
  -o /usr/local/bin/nodaro && chmod +x /usr/local/bin/nodaro

Windows: download nodaro-windows-x64.exe from the releases page and rename to nodaro.exe.

Authentication

Generate a token at https://app.nodaro.ai/settings/api, then save it locally:

nodaro auth login                                   # interactive (opens a browser by default; use --no-browser to paste a token)
nodaro auth login --token "$NODARO_TOKEN"           # non-interactive
nodaro auth login --no-browser                      # skip browser flow, paste token instead
nodaro auth login --profile staging --base-url https://next.nodaro.ai

The token is stored at ~/.config/nodaro/config.json with chmod 0600. Override the location with NODARO_CONFIG_DIR.

nodaro auth status                                  # show profile (token masked)
nodaro auth logout                                  # delete the saved profile

Profiles

Switch between staging / prod / local instances with --profile:

nodaro auth login --profile prod    --base-url https://app.nodaro.ai
nodaro auth login --profile staging --base-url https://next.nodaro.ai
nodaro auth login --profile local   --base-url http://localhost:8000

nodaro projects list --profile staging

When you omit --profile, the CLI uses the profile named production. The first profile you create becomes the default, so a single nodaro auth login (no --profile) sets you up without any extra flags.

Three ways to run something

The CLI exposes the three execution paths the platform supports — pick the one that matches what you’ve already built.

Goal Command
Run a saved DAG nodaro workflows run <workflowId>
Run a published app (curated inputs/outputs) nodaro apps run <slug> --input k=v
Run a single node directly (no DAG) nodaro nodes run <type> --param k=v

The nodes run path is the SDK / CLI equivalent of the MCP server’s verb tools (generate_image, generate_video, etc.) — the route convention is POST /v1/<type> for every generation node, so any node listed by nodaro nodes list can be invoked with nodaro nodes run.

Commands

# Auth
nodaro auth login [--profile <name>] [--token <token>] [--base-url <url>] [--no-browser]
nodaro auth status [--profile <name>] [--json]
nodaro auth logout [--profile <name>]

# Projects
nodaro projects list [--json]
nodaro projects get <id> [--json]
nodaro projects create --name <name> [--description <desc>] [--json]
nodaro projects update <id> [--name <name>] [--description <desc>] [--json]
nodaro projects delete <id> [--json]

# Workflows
nodaro workflows list --project <projectId> [--json]
nodaro workflows get <id> [--json]
nodaro workflows create --project <projectId> --name <name> [--file bundle.json] [--json]
nodaro workflows update <id> [--name <name>] [--file nodes-edges.json] [--json]
nodaro workflows delete <id> [--json]
nodaro workflows export <id> [--with-assets] [--output bundle.json]
nodaro workflows import <file> --project <projectId> [--json]
nodaro workflows run <id> [--watch] [--node n1 n2 ...] [--json]

# Apps — workflows wrapped in a curated UI
nodaro apps list [--search <query>] [--limit <n>] [--cursor <token>] [--category <slug>]
nodaro apps get <slug>                                  # show input schema + outputs
nodaro apps run <slug> --input prompt="…" [--watch]
nodaro apps run <slug> --params-file inputs.json [--watch]
nodaro apps runs <slug> [--limit <n>] [--cursor <token>] [--json]  # list past runs
nodaro apps run-get <slug> <runId>

# Nodes — list types + run a single node directly
nodaro nodes list [--category input|parameter|ai-image|ai-video|ai-audio|ai-text|processing|composition|output|control|entity|trigger|utility] [--json]
nodaro nodes get <type>                                 # full input schema
nodaro nodes run <type> --param prompt="…" --param provider=flux [--watch]
nodaro nodes run <type> --params-file body.json [--watch] [--poll-interval 1000]

# Pickers — valid values for parameter-picker nodes (setting, mood, person, …)
nodaro pickers list [--json]                            # all picker node types + option counts
nodaro pickers get <nodeType> [--full] [--category <c>] [--field <f>] [--json]
#   --full adds each option's description + the prompt fragment it injects
#   --category filters a single-dim picker; --field picks one dimension of a multi-dim picker (person/styling/framing)

# Prompt — AI wizard that turns a rough idea into an optimized prompt
nodaro prompt wizard [--node-type <type>] [--prompt "…"] [--provider <name>] [--style <name>] [--aspect-ratio <ratio>] [--duration <seconds>] [--llm-model <id>] [--reasoning-effort <level>]   # interactive Q&A; node picker if --node-type omitted
nodaro prompt analyze --node-type <type> [--prompt "…"] [--provider <name>] [--style <name>] [--aspect-ratio <ratio>] [--duration <seconds>] [--llm-model <id>] [--reasoning-effort <level>] [--json]   # return guided questions
nodaro prompt generate --node-type <type> --selection category=value [--selection ...] [--original-prompt "…"] [--provider <name>] [--style <name>] [--aspect-ratio <ratio>] [--duration <seconds>] [--llm-model <id>] [--reasoning-effort <level>] [--json]   # build a prompt from selections
nodaro prompt enhance --node-type <type> --prompt "…" [--provider <name>] [--style <name>] [--aspect-ratio <ratio>] [--duration <seconds>] [--llm-model <id>] [--reasoning-effort <level>] [--json]   # one-shot rewrite, no questions
#   --reasoning-effort <level>: none|low|medium|high|xhigh|max (model-dependent; unsupported/omitted -> vendor default). xhigh/max bill one tier up.

# Executions
nodaro executions get <id> [--watch] [--json]
nodaro executions cancel <id> [--mode cancelled|stopping]

# Jobs
nodaro jobs get <id> [--json]
nodaro jobs cancel <id> [--json]

# Characters — full lifecycle + studio operations
nodaro characters list [--project <id>] [--archived] [--limit <n>] [--json]
nodaro characters get <id>
nodaro characters create --name <name> [--description "..."] [--gender <gender>] [--style realistic|anime|3d-pixar|illustration] [--base-outfit "..."] [--seed-prompt "..."] [--node-id <id>] [--project <id>]
nodaro characters update <id> [--name <name>] [--description "..."] [--gender <gender>] [--style realistic|anime|3d-pixar|illustration] [--base-outfit "..."] [--seed-prompt "..."]
nodaro characters delete <id>
nodaro characters restore <id>
nodaro characters duplicate <id> [--node-id <id>] [--project <id>]
nodaro characters usage <id>
nodaro characters generate <id> [--seed-prompt "..."] [--description "..."] [--name <name>] [--count 1|2|4] [--provider <p>] [--watch]
nodaro characters generate-asset <id> --asset-type expressions|poses|lighting|angles|headAngles|bodyAngles|custom --variant <name> [--user-prompt "..."] [--description "..."] [--column <col>] [--attach-name <name>] [--provider <p>] [--watch]
nodaro characters generate-motion <id> --motion-prompt "..." [--attach-name <name>] [--description "..."] [--motion-description "..."] [--provider <p>] [--watch]
nodaro characters approve-portrait <id> --job <jobId>
nodaro characters recaption <id>

# Locations — full lifecycle + studio operations
nodaro locations list [--archived] [--json]
nodaro locations get <id> [--json]
nodaro locations create <name> --node-id <id> [--description "..."] [--category indoor|outdoor|urban|nature|fantasy|sci-fi|historical|futuristic|other] [--style realistic|anime|3d-pixar|illustration] [--project <id>] [--json]
nodaro locations update <id> [--name <name>] [--description "..."] [--category <category>] [--style <style>] [--style-lock true|false] [--canonical-description "..."] [--expected-updated-at <iso>] [--json]
nodaro locations delete <id> [--json]
nodaro locations restore <id> [--json]
nodaro locations generate --name <name> [--description "..."] [--user-prompt "..."] [--category <category>] [--style <style>] [--provider <p>] [--count 1|2|4] [--attach-to-location-id <id>] [--watch] [--json]
nodaro locations generate-asset <id> --asset-type timeOfDay|weather|seasons|angles|lighting|custom --variant <name> [--user-prompt "..."] [--description "..."] [--column <col>] [--attach-name <name>] [--provider <p>] [--watch] [--json]
nodaro locations generate-motion --name <name> --motion-prompt "..." --source-image-url <url> [--provider kling|kling-turbo|kling-3.0|wan-i2v|wan-2.7-i2v|seedance-2] [--style realistic|anime|3d-pixar|illustration] [--canonical-description "..."] [--attach-to-location-id <id>] [--attach-name <name>] [--aspect-ratio 1:1|3:4|16:9|9:16] [--watch] [--json]
nodaro locations approve-main-image <id> --candidate-job-id <jobId> [--json]
nodaro locations recaption <id> [--json]

# Objects — full lifecycle + studio operations
nodaro objects list [--project <id>] [--archived] [--json]
nodaro objects get <id> [--json]
nodaro objects create <name> --node-id <id> [--description "..."] [--category furniture|vehicle|weapon|food|clothing|electronics|nature|tool|animal|other] [--style realistic|anime|3d-pixar|illustration] [--project <id>] [--json]
nodaro objects update <id> [--name <name>] [--description "..."] [--category <category>] [--style <style>] [--style-lock true|false] [--canonical-description "..."] [--expected-updated-at <iso>] [--json]
nodaro objects delete <id> [--permanent] [--json]          # --permanent erases archived rows; default is soft-delete
nodaro objects restore <id> [--json]
nodaro objects generate --name <name> [--description "..."] [--user-prompt "..."] [--category <category>] [--style <style>] [--provider <p>] [--count 1|2|4] [--attach-to-object-id <id>] [--seed-prompt-hint "..."] [--watch] [--json]
nodaro objects generate-asset --asset-type angles|materials|variations|motion|custom --variant <name> --attach-to-object-id <id> [--attach-to-column <col>] [--name <name>] [--description "..."] [--seed-prompt-hint "..."] [--watch] [--json]
nodaro objects generate-motion --name <name> --motion-prompt "..." --source-image-url <url> [--provider kling-turbo|kling|kling-3.0|minimax|hailuo-2.3|wan-i2v|seedance|bytedance-lite] [--style realistic|anime|3d-pixar|illustration] [--canonical-description "..."] [--attach-to-object-id <id>] [--attach-name <name>] [--aspect-ratio 1:1|3:4|16:9|9:16|4:3] [--seed-prompt-hint "..."] [--watch] [--json]
nodaro objects approve-main-image <id> --candidate-job-id <jobId> [--expected-updated-at <iso>] [--json]
nodaro objects recaption <id> [--json]

# Voice — revoice an audio track or a talking video
nodaro voice changer --voice <id> --audio <url>|--video <url> [--stability <0..1>] [--similarity <0..1>] [--style <0..1>] [--remove-background-noise] [--watch] [--poll-interval <ms>] [--json]
nodaro voice change ...                                  # alias of `voice changer`
nodaro voice recast --audio <url>|--video <url> --voices <v1,v2,...>|--voices-json <json> [--model <id>] [--no-preserve-background] [--separation-quality fast|best] [--music-volume-mode match|normalize|manual] [--music-volume <0-200>] [--remove-background-noise] [--voice-fx <preset>] [--voice-fx-mix <0-100>] [--voice-fx-delay <20-2000>] [--voice-fx-decay <0-1>] [--watch] [--poll-interval <ms>] [--json]
                                                         # multi-speaker recast (Voice Changer Pro, Cloud only). --voices maps speakers in detection order;
                                                         # the literal `keep` keeps that speaker's original voice (e.g. --voices Rachel,keep,Aria).
                                                         # --voices-json takes the raw SDK array: voice ids, per-voice settings objects, or null keep-slots.
nodaro voice pro ...                                     # alias of `voice recast`

Param syntax

Both --input (apps) and --param (nodes) accept repeated key=value pairs with primitive coercion:

nodaro nodes run generate-image \
  --param prompt="a futuristic city skyline at dusk" \
  --param provider=flux \
  --param resolution=2K \
  --param generateAudio=false

For arrays, nested objects, or any value that doesn’t fit the flag form, use --params-file body.json. Flag values override file values for the same key.

Output formatting

Every read command supports --json for machine-readable output:

nodaro projects list --json | jq '.[].id'
nodaro workflows run wf_abc --json

Without --json the output is a small ASCII table for list commands and a pretty-printed JSON block for get commands.

Exit codes

Code Meaning
0 success
1 unauthorized / not found / argument error / network error
2 --watch finished and the execution ended in failed
130 --watch finished and the execution ended in cancelled

Examples

Run a workflow nightly with cron

0 3 * * * /usr/local/bin/nodaro workflows run wf_abc123 --json >> /var/log/nodaro-nightly.log 2>&1

Wait for completion and gate downstream work

nodaro workflows run wf_abc --watch && \
  echo "shipped" | mail -s "nodaro run done" me@example.com

Generate an image from a single command

nodaro nodes run generate-image \
  --param prompt="a snow leopard on a mountain ridge, cinematic" \
  --param provider=flux \
  --param resolution=2K \
  --watch --json | jq -r '.output_data.imageUrl'

Turn a rough idea into an optimized prompt

# one-shot enhance, then feed straight into a node run
PROMPT=$(nodaro prompt enhance \
  --node-type generate-image \
  --prompt "snow leopard" \
  --json | jq -r '.prompt')

nodaro nodes run generate-image --param prompt="$PROMPT" --watch

Need the wizard’s guided questions in a script? Use the two-step path — prompt analyze --json to fetch the questions, then prompt generate --selection category=value … to build the final prompt (the interactive prompt wizard requires a terminal).

Programmatic alternative

If you’re building integrations, prefer the typed SDK directly:

npm install @nodaro/sdk
import { createClient, StaticTokenAuth } from "@nodaro/sdk"

const nodaro = createClient({
  baseUrl: "https://app.nodaro.ai",
  auth: new StaticTokenAuth(process.env.NODARO_TOKEN!),
})

const exec = await nodaro.workflows.run("wf_abc")

The CLI is a thin convenience wrapper around @nodaro/sdk. Anything the CLI does, the SDK does too.

See also