| name | wonda-cli |
| description | Using the Wonda CLI to generate images, videos, music, and audio from the terminal — plus LinkedIn, Reddit, and X/Twitter research and automation |
Wonda CLI
Wonda CLI is a content creation toolkit for terminal-based agents. Use it to generate images, videos, music, and audio; edit and compose media; publish to social platforms; and research/automate across LinkedIn, Reddit, and X/Twitter.
Install
If wonda is not found on PATH, install it first:
npm i -g @degausai/wonda
brew tap degausai/tap && brew install wonda
Setup
- Auth:
wonda auth login (opens browser, recommended) or set WONDA_API_KEY env var
- Verify:
wonda auth check
OAuth connector auth
Claude web and Cowork connectors use Wonda's OAuth 2.1 flow instead of a CLI
API key field. The connector signs in through Wonda in the browser, grants the
requested account access, and receives OAuth tokens bound to the Wonda API
resource. The server swaps those tokens to the account's internal API key only
inside Wonda, so agents and connector hosts never see the sk_... key. For the
CLI and local stdio MCP path, keep using wonda auth login or
WONDA_API_KEY.
Claude Cowork local relay
Claude Cowork (the desktop app) runs local MCP servers on the host, so it can
load the .mcpb bundle or a local stdio wonda-mcp config directly, WAB
writes included (verified 2026-07-07). Claude web cannot. The Wonda local
relay is the alternative path: it lets the REMOTE connector (web or Cowork)
run actions on the user's own Mac and residential IP without any local MCP
config:
- Open
https://wonda.sh/download while signed in and install the notarized
Mac package.
- Pair the relay with
wonda relay pair or the first-run browser handoff. This
uses the existing cli-auth flow with a relay-scoped wrelay_...
credential stored in the macOS Keychain. Do not ask the user to paste an API
key or device code.
- Open
https://wonda.sh/setup, connect LinkedIn, X, and Reddit through the
headful local WAB, then approve the Wonda connector once in Claude.
The engine policy is auto | my_machine | cloud. auto uses the local relay
when it is online and cloud otherwise. my_machine must not silently fall back:
if the relay is offline, ask whether to switch to cloud.
Organizations & spend context
Wondercat orgs are shared wallets with their own seats and billing.
Members can spend from the org wallet (instead of their personal credits)
by switching context:
wonda organizations list (aliases: wonda orgs list, wonda org list) — see every org you belong to with your role and seat plan in each.
wonda use --org <slug> — sticky org context for this machine. Sets
X-Wonda-Org on every request; holds, charges, and wonda balance
route through the org wallet.
wonda use --personal — back to personal.
wonda usage — spend-only usage summary (total + per-model + per-project
breakdown) for a period (--month 2026-05, or --from/--to; defaults
to the current month, UTC). --project <name> restricts the report to one
project. In org context it reports org-wide usage including a per-member
breakdown — admin/owner role required. Admins can also download a full
Excel report from the org page on the web.
Projects (spend tagging)
Projects attribute spend to a named workstream for monitoring. Agents
should check the active project at task start (wonda use prints it) and
set one per task when the operator monitors spend by project:
wonda use --project <name> — sticky: every subsequent charge carries
the project (in wonda usage, the API, and the org Excel report).
wonda use --no-project stops tagging; switching org/personal context
clears the project automatically (projects are per-scope).
--project <name> on any command — one-off override for that invocation.
wonda project list|create|delete — manage the registry in the active
scope. Org projects are created by org admins/owners only; personal
projects are self-service. Tagging against a name that doesn't exist
fails with unknown_project (no silent new buckets, so typos can't
split the monitoring data).
wonda topup always tops up your personal wallet, regardless of
context. Topping up the org wallet (and configuring auto top-up) is
admin-only and happens on the web at /organizations/<slug>. If a
member runs out of org credits, the error tells them to ask an admin or
switch back to personal — they cannot top up the org wallet from CLI.
Roles inside an org are separate from the seat plan:
- Owner: the original creator. Cannot be demoted or kicked. Can transfer ownership to another member from the org page (rare).
- Admin: can invite (single or bulk via paste), kick, change roles, change seats, top up, configure auto top-up, change monthly limits.
- User: can only spend within the org wallet (subject to a per-member monthly limit if the admin set one).
A paid org seat (WONDA / WONDA_PREMIUM) grants the same paid feature access (skills, etc.) as a personal paid plan, but only while in org context. wonda use --personal falls back to the user's personal account plan.
Access tiers
Wonda is paid-only: every product surface (generation, media, publishing, scraping, analysis, skills, cloud twin) requires a paid plan. New accounts get no credits and no product access until they subscribe.
| Tier | Access |
|---|
| Anonymous (temporary account, no login) | No product access. The CLI mints a temporary account on first run, but it reaches only the local commands below plus the auth/billing endpoints. Run wonda auth login, then subscribe. |
| Free (logged in, no paid plan) | No product access. Subscribe at https://wonda.sh/account to use the product. |
WONDA ($19.99/mo, "Pro") | Everything except cloud twin: generation (image/generate, video/generate, ...), media upload/download, publishing, scraping, analytics, video analysis, skills (wonda skill install/list/get), transitions, clipping, email, reddit/linkedin account creation, styles, brand. |
WONDA_PREMIUM ($49.99/mo, "Premium") | Everything in WONDA, plus cloud twin (wonda twin: provisioning, scheduled runs, streamed login) with antidetect / shadowban protection, no caps, and US account creation. |
| Flagged (per-account PostHog kill-switches) | Included in the paid plan but still gateable by a per-account flag: wonda reddit signup (redditAccountCreationEnabled), wonda email (emailServerApiEnabled), public LinkedIn profile enrichment (linkedinProfileEnrichmentEnabled). |
| Local (no API call, no credits, no plan) | Run entirely on your machine, so they work without a plan: wonda brand extract <url> (no --save), wonda compose motion/wonda compose text, wonda wab record <url>, wonda edit ... ffmpeg primitive transforms on local files, and wonda doctor. The Chromium-backed ones need a one-time wonda wab install. |
If a command returns a 403 (paid_plan_required), subscribe at https://wonda.sh/account.
Voice cloning
Clone a voice from a 10s+ audio clip and use it in TTS. Hard limit: 20 cloned voices per account. Cost: $1.50 per clone.
wonda voice create "Andu" --file ./sample.mp3 --description "My voice"
wonda voice create "Brand" --media-id <uuid>
wonda voice create "Clean" --file ./raw.wav --noise-reduction --normalize-volume
wonda voice list
wonda voice get <voice-id>
wonda voice update <voice-id> --name "New Name" --description "..."
wonda voice delete <voice-id>
Use a cloned voice in TTS by passing the providerVoiceId from voice get as voiceId to /audio/speech:
wonda audio speech "Hello world" \
--model minimax-speech-2-8-hd \
--params '{"voiceId":"<providerVoiceId>"}'
7-day expiry: cloned voices that haven't been used in TTS within 7 days are automatically expired. Running TTS with a cloned voice automatically refreshes its expiry. Idle voices that lapse must be re-cloned ($1.50 again).
Credentials vault
Persist logins created on external platforms (Instagram, TikTok, Twitter, etc.) so they can be reused on the next run. Passwords are AES-256-GCM encrypted with a server-side key and only decrypted on get.
wonda credentials create --website instagram.com --username myhandle \
--email me@example.com --password-stdin <<< "hunter2" \
--metadata '{"signup_source":"wonda-email"}'
wonda credentials list --website instagram.com
wonda credentials get <id>
wonda credentials update <id> --username newhandle
wonda credentials delete <id>
wonda credentials use <id> --reason "instagram signup flow"
wonda credentials events <id>
Fields: website (required — typed input like insta is canonicalized to instagram.com), username, email, password (required), metadata (arbitrary JSON). At least one of username / email must be present. Multiple records per (website, username) are allowed — dedupe on your side if you need to.
Event log: every credentials get/use, create, password rotate, and other updates are recorded as events on the credential (actor: cli | web | system). Use credentials events <id> or the web UI's history icon to audit. The event log is append-only and cascades on credential delete.
Global output flags
All commands support these output control flags:
--json — Force JSON output (auto-enabled when stdout is piped)
--quiet — Only output the primary identifier (job ID, media ID, etc.) — ideal for scripting
-o <path> — Download output to file (implies --wait)
--fields status,outputs — Select specific JSON fields
--jq '.outputs[0].media.url' — Filter JSON output with a jq expression
CLI announcements & deprecation warnings
On every command the CLI polls GET /api/v1/updates (anonymous, 1h cache in ~/.wonda/state.json) for active announcements: deprecation notices, incident heads-ups, upgrade prompts. Messages are printed to stderr only, so stdout/JSON stays clean for piping.
Per-request deprecation hints arrive as the standard Warning: 299 - "<message>" HTTP header and are surfaced to stderr by the CLI's HTTP client as [deprecated METHOD /path] <message>.
Silence both channels with WONDA_QUIET=1 (env var) or --quiet (flag). Disable just the network checks with WONDA_NO_UPDATE_CHECK=1.
WAB / Wonda Automation Browser (wonda wab)
The Wonda Automation Browser (WAB) is a premium stealth antidetect browser, hardened so platforms cannot fingerprint it as automation. wonda wab is the one command for the antidetect Chromium stack (an undetected Playwright fork). It has two faces:
- Authenticated sessions. One persistent headful Chromium per persona that holds signed-in sessions for LinkedIn, X, Reddit, and friends. The CLI spawns it on demand, lets it idle out, and routes platform reads/writes through it whenever a command runs
--via wab. Cookies live in the persona's Chromium profile, not in ~/.wonda/config.json.
- Anonymous capture.
wonda wab record <url> (and wonda brand extract) drive an ephemeral Chromium with a fresh fingerprint, no persona, no cookies. See the record block below.
The mental model: you have accounts (one identity per platform). Each platform command routes to that account's cookies via either the flat JSON store (--via cookies, fast, no Chromium) or the account's persona (--via wab, live antidetect Chromium). A persona is the Chromium envelope that can hold multiple accounts under one fingerprint. In almost every case the persona is auto-created on first --via wab use, named after the account, so you never type a persona name.
The local wonda.mcpb Desktop Extension uses this same local WAB path from Claude Desktop or Claude Code: platform cookies stay on-device, reads use local cookies, and writes use the local WAB. Claude web and Cowork need the remote MCP connector instead.
Native login is the default for a new persona. wonda wab login <persona> <platform> opens a headful WAB window and you log in there. The session is minted INSIDE the WAB, so it is independent (logging out of the same account in an unrelated Chrome cannot revoke it) and the cookies are born under the WAB's own fingerprint, so session and browser identity stay coherent. A brand-new persona auto-created on first --via wab use chains straight into this flow on a TTY. After an X login, Wonda detects the signed-in screen_name and records it as the persona's X account binding. Existing bindings are never silently changed; a different detected handle produces a warning. Pasting cookies from another browser (wonda linkedin auth set, wonda x auth set, ...) still works and is the explicit fallback, but a hand-pasted li_at on a novel WAB fingerprint is the highest-risk shape.
wonda wab install
wonda wab start [account]
wonda wab stop [account]
wonda wab show [account]
wonda wab hide [account]
wonda wab screenshot [account]
wonda wab menubar
wonda wab status
wonda wab login <account> <linkedin|x|reddit|instagram>
wonda wab check <account> <linkedin|x|reddit|instagram>
wonda wab bind <persona> --x <acct> --reddit <acct> --linkedin <acct>
wonda wab record <url>
wonda wab sync-cookies [account]
wonda wab logs [account] --tail 100
wonda wab errors --tail 20 --since 24h
wonda wab top-failures --since 7d
wonda wab top-failures --platform x --json
wonda wab bundle-failures list
wonda wab bundle-failures show <id>
wonda wab bundle-failures ship <id>
wonda wab bundle-failures prune
wonda wab migrate-legacy
wonda wab restore <persona> [timestamp]
wonda wab backup disable
wonda wab backup enable
wonda wab backup status
wonda wab backup push [account]
wonda wab backup pull [account]
wonda wab backup pull [account] --dry-run
wonda wab backup list
wonda wab backup delete <plat> <persona> [acct]
wonda wab cookies list
wonda wab cookies status [account]
wonda wab cookies port <plat> <persona> [acct] --from-device <id|label>
wonda wab config set <persona> <key> <value>
wonda wab config get <persona>
Keep cron personas warm. A persona that continuously backs cookie-only read crons can use wonda wab config set <persona> idle-timeout off, followed by wonda wab start <persona>. The WAB then stays up and its existing 10-minute cookie sync keeps the flat files current. Use always-on only for cron-backing personas; wonda wab config unset <persona> idle-timeout restores the default 30-minute idle shutdown.
Local browser proxy (proxy_url). By default the local WAB dials direct (your own IP). Set wonda wab config set <persona> proxy_url managed to route the LOCAL browser through your account's minted twin proxy, so it shares the same egress as the cloud twin (useful for IP continuity or a VPN/office/CGNAT network). A literal socks5://…/https://… value is a manual override instead; unset clears it back to direct. The proxy is optional: if minting is disabled for the environment or unavailable, the browser falls back to a direct dial.
Lifecycle commands take an --account (e.g. wonda wab login <account> linkedin); the persona is auto-derived from the account name. wonda wab bind is the one place a persona is named explicitly: use it when one Chromium must host accounts that have different names per platform.
Anonymous capture (record). wonda wab record <url> records a URL to webm in an ephemeral Chromium (fresh fingerprint each call, no persona, no cookies). Use it for cookie-banner-gated pages (Notion public shares, pdf.js renders, any site where bare Playwright trips a bot check) and marketing demo capture.
wonda wab record https://example.notion.site/page \
--output recording.webm \
--duration 5 \
--viewport 960x1080 \
--inject-js scripts/page-script.mjs
ffmpeg -y -i recording.webm -t 5 -r 30 -an \
-c:v libx264 -pix_fmt yuv420p -crf 18 recording.mp4
The --inject-js file is wrapped in an async IIFE so top-level await works. It runs AFTER domcontentloaded + networkidle + 400 ms paint settle, BEFORE the duration timer starts. Any await inside counts against the recording window. Use it for dark-theme injection, cookie-banner removal, scroll animations, anything that needs to happen in page context.
Node.js requirement: wonda needs Node >= v20 on PATH. Brew users get it via the node dependency; npm users have it by definition; install.sh users may need brew install node (or any Node distribution). If Node is missing, wonda wab install fetches a private copy into ~/.wonda/node/.
Cookie cloud backup. On by default (opt out per machine with wonda wab backup disable). The WAB driver pushes the synced cookie JSON for each bound platform to the wondercat backend after every wab → disk sync and graceful shutdown; auto-push no-ops when no api_key is configured. Encrypted at rest server-side (AES-256-GCM) when SOCIAL_COOKIES_KEY is set, else plaintext jsonb; the wire payload is always plaintext because the server holds the key. Cookie values are never printed by list/status/port commands.
Recovery is guarded. wonda wab backup pull <account> and wonda wab cookies port <platform> <persona> [account] refuse to overwrite a non-empty or newer local cookie file unless --force is passed. Forced writes create a hidden .before-pull-* backup first. Use --dry-run to inspect planned writes. When the backend exposes multiple device rows for the same platform/persona/account, use wonda wab cookies port ... --from-device <id|label> so the source row is explicit.
Current backend compatibility: legacy servers still expose one last-write-wins row per (account, platform, persona, account_label). Newer servers may include device_id, device_label, source, status, generation, and provenance; the CLI displays those fields when present and shows legacy rows as device legacy.
Source lives at cli/wondercat/wab/. The driver is launch.mjs and per-platform action scripts under actions/<platform>/.
WAB reads fail early when the selected browser profile has no live platform session cookie. For LinkedIn, X, Reddit, and Instagram, the error includes wonda wab login <persona> <platform> instead of surfacing an unexplained platform 401/403. This preflight is read-only; writes retain their existing error handling and native login itself is unaffected.
Per-command transport (--via). linkedin, x, and reddit commands take:
--via cookies|wab: cookies reads the flat per-account JSON store (fast, no Chromium); wab routes through the account's persona Chromium (cookies + TLS fingerprint inherit from a real browser session). An unsupported value errors loudly rather than silently downgrading.
--via public: paid public-data API where a command explicitly supports it. For LinkedIn this avoids logged-in cookies and WAB profile reads, and uses the public scrape task route for wonda linkedin profile and wonda linkedin enrich.
--account <name>: which on-disk identity to use (cookie filename / persona). Persona resolution is implicit: the first --via wab use auto-creates a persona named after the account and (on a TTY) chains straight into login.
Defaults differ for reads vs writes. Read commands (profile, posts, search, timeline, etc.) default to cookies (direct API), because that path is fast and detection-safe. Write / engagement commands (post, comment, like, follow, connect, message, mute, repost, delete) default to wab, because the cookie-API path triggers anti-abuse heuristics on LinkedIn / X / Reddit at any meaningful volume. Pass --via cookies to a write command if you explicitly want the legacy API path (where the command supports it).
Commands that require --via wab. A few commands have no cookie path and only run through the Wonda Automation Browser: wonda linkedin comment, wonda linkedin reply-comment, wonda linkedin mute, wonda linkedin follow, wonda linkedin edit-post, wonda linkedin edit-comment, wonda linkedin delete-comment, wonda linkedin post --media, wonda x delete, wonda x reply --attach, wonda x dm send, wonda x dm accept, and wonda x dm start. On these, the default already resolves to wab (one stderr line noting it); passing --via cookies explicitly errors. Reddit's writes (vote, comment, subscribe, save, unsave, delete, and subreddit submit) are likewise wab-only.
Where it runs (--engine). --via picks the transport (browser vs. cookies); --engine picks the location, and the two are orthogonal. Values: local (this machine's WAB), cloud (the account's cloud twin, reached through the twin-action API with the control-session warm-up hidden behind a blocking wait), or auto (the default). auto resolves to local when the identity lives on this machine and cloud when it only exists as a cloud twin (a persona with no local footprint, or one cached as home=cloud from wonda twin provision). So wonda linkedin like <url> --account <twin> --engine cloud runs the like on the cloud twin with the same grammar as the local command. --via works the same on both engines: writes default to wab, and --via cookies is honored on the verbs that support it locally (connect, like/unlike, send-message); the wab-only cases (comment, comment reactions, and --dry-run sends) reject --via cookies on cloud exactly as they do locally. Wired for LinkedIn (connect, like/unlike, comment, reply-comment, edit-comment, send-message, follow, mute, delete-post, edit-post), X (like/unlike, bookmark, retweet/unretweet, follow/unfollow, delete), Reddit (vote, subscribe, save/unsave, delete), and Instagram (comment); other verbs run local only. --engine is accepted at the platform level (so both linkedin --engine cloud connect and linkedin connect --engine cloud work) but honored only on the wired verbs; passing it to a read or an unwired write is a clear error, not a silent no-op. wonda twin run-action is deprecated in favor of <platform> <verb> --engine cloud (it still works so running agents are not broken). On cloud, like supports plain likes and reactions (--reaction, routed to the react action); only comment reactions (--comment) stay local for now. auto resolves to local when a persona has no local footprint and no cloud twin (so first-use auto-create still works), and to cloud when a cloud twin exists for it. An explicit --engine cloud always runs on the cloud twin and never falls back to a live local relay. Note the cloud --via override takes effect once the twin-runner image is redeployed.
Per-account credentials. Cookies live in per-account JSON files on disk:
~/.wonda/x-cookies/<account>.json
~/.wonda/reddit-cookies/<account>.json
~/.wonda/linkedin-cookies/<account>.json (auto-migrated from the legacy single-file format)
Each file is a session-owned local cache, not a portable credential. A platform account may have separate sessions on multiple computers and a cloud Twin. The file records the owning device/persona WAB session, and every injection, overwrite, cookie-only read, refresh, and backup restore checks that identity first. The CLI does not upload these cookies to or download them from the legacy shared hosted-token store. --force never bypasses a session mismatch.
Pass --account <name> to auth set to keep multiple logins side-by-side on the current device. The binding is recorded against the resolved account persona in account-bindings.json, even when --persona is omitted, and if the matching persona's Chromium is running, the rotated cookies get pushed into that live context. Never use auth set to copy cookies from another device or a Twin. Native wab login is safer. The driver also syncs cookies back to disk every 10 minutes (and on graceful shutdown), so rotated cookies (ct0 cycles, token_v2 server-side refresh, etc.) flow back to the cookies path without manual re-paste. Direct X cookie requests also absorb response cookie rotation into the same store and send the full stored cookie jar, preserving device-trust cookies across reads.
The cloud Twin is its own stable, always-on session. Ordinary provisioning does not seed LinkedIn from local cookie files; use wonda twin login <persona> --platform linkedin to log in inside the Twin. Cloud backups are recovery artifacts for their source session only. Inspect local and cloud ownership with wonda wab cookies status [persona]; it never prints cookie values. Direct read verbs currently reject --engine cloud, so run cloud reads with wonda twin run-now <persona> --command "<platform read command>" and retrieve them with wonda twin output. This executes inside the Twin and never downloads its cookies locally.
Safely refresh LinkedIn's flat cookie file. Run wonda linkedin auth refresh --account <account> --persona <persona> before a cookie-only read batch. Fresh disk cookies are a fast local no-op. Stale or near-expiry cookies cause one WAB start (a no-op when already running), one WAB-to-disk sync, and a local/WAB-side LinkedIn session check. The refresh path never invokes the raw wonda linkedin auth check probe and never retries credentials. If the WAB session is dead, it exits nonzero with native re-login required; stop the batch and recover manually with wonda wab login <persona> linkedin.
wonda linkedin auth refresh --json returns {fresh, refreshed, ageSeconds, expiresAt, sessionAlive}. fresh describes the final disk state; refreshed is true only when WAB-to-disk sync ran; ageSeconds is the disk cookie age; expiresAt is the known expiry or null; and sessionAlive is the local/WAB-side login result. sessionAlive is null on a fresh local no-op because no WAB check was needed. --fresh-within changes both the maximum accepted disk age and the minimum remaining recorded li_at lifetime (15 minutes by default). Treat a nonzero exit as authoritative even with JSON output.
Cookie-backed LinkedIn reads accept opt-in --freshen alongside --via cookies. It runs the same local preflight only when the disk cookies are stale, then attempts the requested read once. The default remains unchanged: without --freshen, --via cookies stays fast and browser-free.
Safely refresh X's flat cookie file. wonda x auth refresh --account <account> --persona <persona> uses the same local-first lifecycle with X-specific freshness rules. Since auth_token has no locally readable expiry claim, freshness is the cookie store age only. A store newer than --fresh-within (15 minutes by default) is a browser-free no-op. A stale store starts the selected WAB if needed, syncs X cookies to disk, and checks the WAB-side session without invoking the raw x auth check probe. A dead session exits nonzero with the exact native login command.
wonda x auth refresh --json returns {fresh, refreshed, ageSeconds, sessionAlive}. Cookie-backed X reads accept opt-in --freshen; it runs the same refresh only when the selected disk store is stale, pins the read to the refreshed account, and never applies to WAB reads, writes, or auth commands.
Use wonda x auth status --account <name> for a pure-local view of cookie origin, latest capture provenance, generation, ownership, cookie names, and missing device-trust-cookie risk. It never contacts X and never prints cookie values. Use wonda x browser-bootstrap --account <name> to explicitly push the selected stored jar into running WAB personas bound to that account.
Action rate limits
Every platform command (linkedin, x, reddit, instagram), reads AND writes, runs through a per-profile rate-limit guard so a burst doesn't trip a platform's shadow-ban / anti-abuse heuristics. Accounting is per (platform, account) in a rolling 24h window, logged per profile under ~/.wonda/wab/personas/<persona>/ (so a cloud twin's caps persist across runs).
-
Reads are paced, never blocked: spacing is jittered to keep a profile under read_per_min (75/min default), holding across separate invocations.
-
Writes are checked against per-bucket daily caps. The LinkedIn defaults (other platforms track + count toward the total/day but have no per-type write cap by default):
| bucket | commands | safe | max |
|---|
| outreach | connect + send-message + inmail | 20 | 40 |
| post | post | 3 | 5 |
| comment | comment, reply-comment | 10 | 20 |
| react | like | 25 | 50 |
| visit | visit | 15 | 30 |
| search | search, search-posts | 25 | 50 |
| total (all non-read) | any of the above | warn at 90% | 100 |
salesnav search (Sales Navigator) is exempt: it carries no Commercial Use Limit, so it paces as an uncapped read instead of counting toward the search cap or the total/day.
Caps are SOFT by default: an over-safe / over-max action prints a shadow-ban-risk warning to stderr and proceeds. Pass --hard (or set mode: hard in config) to make over-cap writes abort (exit 1) instead.
wonda actions is a JSON data query (not a dashboard) for reading a profile's rolling-24h usage vs caps on demand; the caps/pacing/warnings run silently in the live hook regardless.
wonda actions
wonda actions --persona <persona>
wonda actions --platform linkedin
wonda actions sync
wonda actions sync --persona <persona>
wonda linkedin post "…" --hard
When an API key is configured, the local ledgers (actions log, WAB audit/error logs, cookie provenance) also sync to your Wonda account-health record automatically in the background on every command: best-effort, batched, and idempotent (a stable client event id per record means retries never double count), so offline use keeps working and sync catches up later. wonda actions sync forces a full flush and prints the server's insert/dedup counts; without an API key it is a silent no-op. Only event metadata travels, never cookie values or failure bundles. WONDA_TELEMETRY_DISABLED=1 turns the background sync off.
Override / disable / hard-mode via ~/.wonda/config.json under action_limits (caps are clamped to safety floors/ceilings so an override can loosen but not silently disable the guard):
{
"action_limits": {
"mode": "hard",
"read_per_min": 75,
"total_per_day": 100,
"buckets": { "linkedin": { "outreach": { "safe": 15, "max": 30 } } }
}
}
Config keys
wonda config get|set|list keys:
api-key: your wondercat API key.
base-url: API base (defaults to prod, set to https://staging.api.wondercat.ai for staging).
default-account: account used when a platform command doesn't pass --account.
wab-backup-enabled: true/false for cookie cloud backup (same as wonda wab backup enable/disable). On by default; only an explicit false disables it.
Transport is NOT a config key. Each command picks it per kind (reads default to cookies, writes / engagement default to wab), identically on every platform. Override it per command with --via cookies|wab (where the platform supports it).
How to think about content creation
You are a marketing director with access to a full production toolkit. Before touching any tool, think:
- What product category? (beauty, food, tech, fashion, fitness, etc.)
- What format performs for this category? (UGC memes for everyday products, cinematic for luxury, before/after for transformations, testimonial for services)
- What's the hook? (relatable scenario, surprising twist, aspirational lifestyle, social proof)
- What specific scene? (not "product on table" but "person discovering the product in a funny situation")
Decision flow
When asked to create content, follow this order:
Step 1: Gather context
wonda brand
wonda brand list
wonda brand show <brand-id>
wonda brand extract https://stripe.com
wonda brand extract https://stripe.com --save --make-active
wonda brand extract https://stripe.com --save --name "Stripe"
wonda brand extract https://stripe.com --no-output --save
wonda brand save
wonda brand save --from ./output/stripe.com --make-active
wonda brand pull <brand-id>
wonda brand activate <brand-id>
wonda brand upload-logo <brand-id> https://acme.com/logo.svg
wonda brand upload-font <brand-id> https://acme.com/Geist.woff2 --weight 700
wonda brand delete <brand-id>
wonda analytics instagram
wonda scrape social --handle @competitor --platform instagram --wait
wonda x search "topic OR keyword"
wonda x user-tweets @competitor
wonda reddit search "topic" --sort top --time week
wonda reddit feed marketing --sort hot
wonda linkedin search "topic" --type COMPANIES
wonda linkedin profile competitor-vanity-name
Step 2: Check content skills
Content skills are step-by-step guides for common content types. Each skill tells you exactly which models, prompts, and editing operations to use — and in what order. ALWAYS check skills before building from scratch.
Skills are server-hosted, per-account, and editable (the same model as wonda brand save) — you don't download a folder of .md files you own. Wonda ships a canonical set of default skills, served read-only as the fallback. Pull them live for a task; fork a default into your own copy when you want to tweak it; Wonda keeps full version history. skill list shows your effective skills (defaults overlaid with your own edits), and flags any fork whose default has since changed.
wonda skill list
wonda skill get <slug>
Default skill catalog (live source: wonda skill list, which also shows your own forks/edits and flags drift):
video
| Slug | What it does |
|---|
| product-demo-video | Premium ~15s multi-beat product demo, a dev hits a pain point, runs your tool, it does something real on screen, the payoff lands, then a branded CTA, built as one HTML composite captured per-frame and muxed with ffmpeg |
| product-video | Product/scene video from an image or from scratch |
| split-screen-demo | 5-second 16:9 LinkedIn loop, source doc to designed slides comparison ending on a CTA that crosses out the competitors |
| tiktok-ugc-pipeline | Reverse-engineer a viral reel, generate 5 variations, auto-post |
| ugc-dance-motion | Dance and motion transfer video from image + reference |
| ugc-hook-brainstorm | 25 graded scroll-stopping UGC hooks, hot casting, iPhone 16 aesthetic, psychological levers |
| ugc-reaction-batch | Batch produce TikTok-native UGC reaction videos |
| ugc-talking | Talking-head UGC ad, single clip, two-angle PIP, or long-form 20s+ |
image
| Slug | What it does |
|---|
| creative-static-ads | High-converting single-frame static ads, 6 conversion pillars, 8 format archetypes, 8 psychological hooks |
| linkedin-media-premium-generation | Render a single stop-the-scroll LinkedIn hero card (1920x1080) using a layered visual vocabulary: italic-serif headline, real product screenshot, paper grain, floating social-proof cards. Six layout patterns sharing the same DNA. Wonda CLI sourcing for real quotes from X / LinkedIn / Reddit. |
| premium-static-ads | Pixel-perfect HTML+Playwright static ads with brand extraction (wonda brand extract). Real fonts, exact tokens, reproducible templates |
| tiktok-slideshow-carousel | 3-5 slide TikTok carousel that looks organic but promotes your product, hook, bridge, reveal |
social-research
| Slug | What it does |
|---|
| analyze-reel | Analyze a viral reel or TikTok, viral breakdown + 5 adapted content ideas |
| linkedin-engager-intel | Pull every commenter + reactor on a LinkedIn post with profile URLs for warm outreach |
| linkedin-icp-qualify | Enrich LinkedIn engagers with their current employer (industry, headcount, HQ, description) so you can filter for ICP fit |
| linkedin-social-listening | Paste any keyword, pull every recent post that mentions it, then enrich each post's engagers into an ICP-qualified outreach shortlist |
| reddit-subreddit-intel | Scrape top posts, analyze virality patterns, generate post ideas |
| twitter-influencer-search | Find micro-influencers and amplifiers for product launches |
strategy
| Slug | What it does |
|---|
| linkedin-post-system | Turn a raw idea into a LinkedIn post that matches proven formats and the user's exact voice. Bootstraps the user's voice corpus via wonda linkedin profile/posts, maps the idea to one of 28 proven content formats, drafts 2 variants with anti-AI-slop guardrails. |
| marketing-brain | Strategy brain for hooks, visuals, ads, and competitive analysis |
utility
| Slug | What it does |
|---|
| extract-apply-style | Extract a visual style from any image, then generate new subjects in that style |
| ffmpeg | Local deterministic media transforms, trim, replace audio, burn captions, social formatting, scene splitting, silence cut, frame extraction, analysis artifacts |
| image-edit | Edit existing images, img2img, background removal, crop, text overlay, vectorize |
| slide-generation | Generate branded slide decks from any content source, codebase, Notion notes, or Google Docs |
| software-ui-mockups | Render real software UIs, terminal/CLI TUIs, the Chrome browser window, the macOS desktop, pixel-accurately in HTML for demo videos, slides, screenshots and docs, from the program's real source of truth |
| tiktok-caption-presets | TikTok-style textOverlay and animatedCaptions presets applied via wonda edit --preset |
Editing skills (optional). When a default doesn't quite fit, fork and edit it instead of working around it. Editing a default forks it into your account automatically:
wonda skill create my-ugc --from ugc-talking
wonda skill edit my-ugc --editor
wonda skill diff <slug>
wonda skill refactor <slug> --editor
If a skill matches → wonda skill get <slug>, read it, adapt to context, execute each step.
If no skill matches → build from scratch (Step 3).
Step 2.5: Decide whether finishing should be local
Not every media task should go back through Wonda editing. Use this routing rule:
- Use
wonda for AI generation, AI transcription/alignment, scraping, publishing, hosted transitions, and workflows that need media IDs or remote jobs.
- Use local
ffmpeg for deterministic transforms on files you already have or can download: trim, crop/scale/pad, concat (merging multiple clips), replace audio, extract audio/frame, reverse, normalize for delivery, burn captions, split scenes, cut silence, and build analysis artifacts. Always merge clips locally — server-side merge can hang for 30+ minutes once any input exceeds ~7MB.
When a task starts from a Wonda media ID but the actual edit is deterministic, move it to local files first:
wonda media download <mediaId> -o ./input.mp4
Before any local ffmpeg work:
which ffmpeg
which ffprobe
ffmpeg -version
ffprobe -v error -show_format -show_streams -of json ./input.mp4
Font rule for local caption/text work:
- Prefer an explicit font file path over a family name.
- Never assume a font exists. Check first with
fc-match, fc-list, /System/Library/Fonts, /Library/Fonts, ~/Library/Fonts, or /usr/share/fonts.
- If the task is mainly local finishing/captions/formatting/splitting/artifact extraction, check the
ffmpeg skill before inventing commands.
wonda edit video runs a local ffmpeg for every editor op: trim, crop, volume, speed, reverseVideo, extractFrame, extractAudio, editAudio, imageCrop, imageToVideo, merge, overlay, splitScreen, splitScenes, skipSilence. The render runs on your machine via ffmpeg: no server-side editor_job and no credit hold for the render itself (inputs are downloaded and the result uploaded around it). textOverlay and animatedCaptions also run locally, via the bundled hyperframes (Chromium) renderer. ffmpeg must be on PATH (wonda doctor verifies). The public API /video/edit, /image/edit, /audio/edit are no longer used for these and return 410 Gone.
- Always merge clips locally. Server-side merge can hang for 30+ minutes once any input exceeds ~7MB, and
wonda edit video --operation merge now runs in local ffmpeg by default for the same reason.
- Never mix per-clip audio then concat. Concat the video tracks first, then layer the full voiceover or music track once over the joined timeline. Per-clip audio bakes create cut-line collisions and silent gaps.
Default local export target unless the user asked otherwise:
-c:v libx264 -preset medium -crf 18 -pix_fmt yuv420p -movflags +faststart -c:a aac -b:a 192k
Always pass -y as the first flag so the command auto-overwrites the output. ffmpeg prompts interactively when the output path exists and agent shells hang on that prompt until timeout.
Step 2.6: Pick the right local tool
Editing maps to one of four tools. Pick the first row that matches.
| Need | Tool | Why |
|---|
| Primitive transform (trim, crop, speed, merge, overlay, ...) | wonda edit video --operation <op> | Wraps local ffmpeg. Free, deterministic, renders on your machine (no server render, no credits). |
| Motion graphics, animated text, lower thirds, intro/outro | wonda compose <kind> (hyperframes HTML compositions, local render) | One-shot, no Lambda, no Node bundled into wonda. Requires Node >= 22 + ffmpeg. |
| Kinetic captions, branded effects pipelines, scene FX | wonda transitions run --preset <name> (miruna's transitions service) | Hosted; richer effect library (SAM3 masking, scene transitions, caption presets). |
| One-off raw transform not covered by a primitive | Raw ffmpeg via Bash (see the ffmpeg skill) | Faster than picking a wrong primitive; matches "deterministic transform on local files". |
| Complex multi-step pipeline | Chain the above (wonda edit ... → raw ffmpeg → wonda compose ...) | Each step writes a local mp4; pass it as --input / --media to the next. |
Run wonda doctor once on a new machine to confirm ffmpeg, node, and hyperframes are all available. Pass --warm-chrome to pre-fetch hyperframes' bundled Chromium (~150 MB) so the first clipping render doesn't pause to download it.
Examples:
Primitive trim and merge (wonda edit, local ffmpeg):
wonda edit video --operation trim --media $VID \
--params '{"trimStartMs":3000,"trimEndMs":10000}' \
--wait -o ./trimmed.mp4
wonda edit video --operation merge --media $A,$B,$C \
--wait -o ./merged.mp4
Motion graphics intro (wonda compose, hyperframes):
wonda compose motion --template fade-in \
--text "Q4 Recap" --subtitle "Wondercat" \
--duration 4 --resolution portrait -o intro.mp4
wonda compose text --input ./clip.mp4 --text "NEW DROP" \
--position bottom-center -o overlay.mp4
Kinetic captions on a finished clip (transitions service):
wonda transitions run --media $VID --preset caption_word_pop --wait -o final.mp4
Raw ffmpeg for an op no primitive covers (e.g. concat with audio fade out):
ffmpeg -y -f concat -safe 0 -i list.txt \
-af "afade=out:st=29:d=1" \
-c:v libx264 -crf 18 -pix_fmt yuv420p \
-c:a aac -b:a 192k out.mp4
Multi-step pipeline (compose intro → wonda merge with main → transitions captions):
wonda compose motion --template scale-pop --text "Hello" --duration 3 -o intro.mp4
wonda edit video --operation merge --media $(wonda media upload intro.mp4 --quiet),$MAIN_VID \
--wait -o merged.mp4
MERGED_ID=$(wonda media upload merged.mp4 --quiet)
wonda transitions run --media $MERGED_ID --preset caption_word_pop --wait -o final.mp4
Step 3: Build from scratch (chain endpoints)
When no skill matches, chain individual CLI commands. Each step produces an output that feeds into the next.
Single asset:
wonda generate image --model gpt-image-2 --prompt "..." --aspect-ratio 9:16 --wait -o out.png
wonda generate video --model seedance-2 --prompt "..." --duration 5 --params '{"quality":"high"}' --wait -o out.mp4
wonda generate text --model <model> --prompt "..." --wait
wonda generate music --model suno-music --prompt "upbeat lo-fi" --wait -o music.mp3
Audio (speech, transcription, dialogue):
wonda audio voices
wonda audio speech --model elevenlabs-tts --prompt "Your script here" \
--params '{"voiceId":"hpp4J3VqNfWAUOO0d1Us"}' --wait -o speech.mp3
wonda audio transcribe --model elevenlabs-stt --attach $MEDIA --wait
wonda audio dialogue --model elevenlabs-dialogue \
--prompt 'ALICE: Hi! BOB: Hello!' \
--params '{"speakers":[{"label":"ALICE","voiceId":"hpp4J3VqNfWAUOO0d1Us"},{"label":"BOB","voiceId":"IKne3meq5aSn9XLyUdCD"}]}' \
--wait -o dialogue.mp3
Audio AI operations (direct-inference, NOT editor ops):
wonda audio enhance --model replicate-resemble-enhance --attach $MEDIA \
--params '{"denoise":true,"chunkSeconds":10}' --wait -o enhanced.wav
wonda audio extract-voice --model replicate-demucs --attach $MEDIA \
--wait -o vocals.wav
Add animated captions to a video:
The animatedCaptions operation handles everything in one step — it extracts audio, transcribes for word-level timing, and renders animated word-by-word captions onto the video.
VID_JOB=$(wonda generate video --model seedance-2 --prompt "..." --duration 5 --aspect-ratio 9:16 --params '{"quality":"high"}' --wait --quiet)
VID_MEDIA=$(wonda jobs get inference $VID_JOB --jq '.outputs[0].media.mediaId')
wonda edit video --operation animatedCaptions --media $VID_MEDIA \
--params '{"fontFamily":"TikTok Sans SemiCondensed","position":"bottom-center","sizePercent":80,"strokeWidth":2.5,"fontSizeScale":0.8,"highlightColor":"rgb(252, 61, 61)"}' \
--wait -o final.mp4
The video's original audio is preserved. Do NOT replace the audio with TTS — Sora already generated the speech.
Transitions (effects pipelines on a single video):
wonda transitions presets
wonda transitions operations
wonda transitions operations --json
wonda transitions llms
wonda transitions run --media $VID --preset flash_glow --wait -o out.mp4
wonda transitions run --media $VID \
--clips '[{"layer_type":"video","start_frame":0,"end_frame":60}]' --wait -o out.mp4
wonda transitions run --media $VID --clips ./timeline.json --wait -o out.mp4
wonda transitions run --media $VID --clips ./timeline_with_transitions.json --wait -o out.mp4
wonda transitions job <jobId>
Use exactly one of --preset or --clips. Requires a full (logged-in) account. Always read wonda transitions llms first when composing a clips timeline. It documents the detect/segment/effect dependencies, which ops need masks, and the full clip-spec shape (layer types, tracks, effects, transforms).
Preset variables (variables block). Each preset declares the template variables it accepts under variables in wonda transitions presets. Each entry has name, description, and required. Required variables MUST be supplied or the job is rejected with a 400 — no more silent skipping. Pass them with --var name=value (repeatable) or, for the common prompt case, the --prompt shortcut:
wonda transitions run --media $VID --preset flash_glow_prompted \
--prompt "woman in white dress" --wait -o out.mp4
wonda transitions run --media $VID --preset text_behind_person \
--var prompt="the person" --var text="HELLO WORLD" --wait -o out.mp4
wonda transitions run --media $VID --preset border_frame \
--var exit_start_frame=200 --var exit_end_frame=251 --wait -o out.mp4
The prompt variable is a detection text query describing which subject to mask, fed to SAM3 to produce per-frame segmentation masks. Not a content-generation prompt.
Building a custom --clips timeline that needs detection masks? Add a clip with layer_type: "video" and a mask: {layer_type: "mask", analysis_steps: [{name: segment, params: {prompt: "..."}}]}. SAM3 handles both detection and segmentation in one step from the prompt, so no separate detect step is needed.
Pre-warming masks before render (recommended)
For presets with mask:<label> variables, run wonda transitions ensure-masks first so the render starts with masks already prepared. The first call for a (media, label) pair takes 1-3 minutes; subsequent calls are near-instant.
wonda transitions ensure-masks --media $VID --labels person,phone --wait
wonda transitions run --media $VID --preset slide_reflect_background \
--var "masks=mask:person+phone" --wait -o out.mp4
ensure-masks flags:
--media MEDIA_ID — required, the video the masks are for
--label NAME — repeatable, one label per call (--label person --label phone)
--labels NAME,NAME — comma-separated alternative (--labels person,phone)
--wait — block until every label is prepared
--timeout DUR — cap wait time when --wait is set (default 10m)
Multi-prompt syntax: mask:woman+phone in --var is split into separate masks (woman, phone) and unioned per-frame. Pass each sub-label separately to ensure-masks so all of them are pre-warmed.
When to skip ensure-masks:
- Non-mask presets (no
mask:<label> variables) — nothing to prepare
- A previous render already used these (media, labels) — already prepared
When ensure-masks matters most:
- First render of a new media with mask-based presets
- Iterating params on a render — pre-warm once, then run as many times as you want without re-preparing
Multi-scene presets (requiresMultiScene: true). Some presets use scene-aware logic and expect a video with multiple cuts/scenes. Check requiresMultiScene in wonda transitions presets. If true, feeding a single continuous shot will produce only one scene and the effect may look underwhelming. Combine clips first or use a video with natural cuts.
Tweaking preset params. Every preset is clip-shape. Pull a single preset with wonda transitions preset <name> --json, read its clips: (single-track) or tracks: (multi-track) field, edit any clip param, and submit as --clips. For multi-track presets, flatten by giving each clip a track index drawn from the track it came from. If the preset declares sceneTransitions:, pass that array through unchanged on the request.
wonda transitions preset flash_glow_montage --json | jq '.preset.clips' > clips.json
wonda transitions run --media $VID --clips "$(cat clips.json)" --wait -o out.mp4
Auto-repair safety net (--auto-repair, --face-bbox). For --clips renders the worker runs a deterministic repair pass on the submitted JSON before rendering, default on. Repairs: width-fit font clamp, descender clamp against canvas bottom, stack-spacing snap (ROW1_py from cap-height formula), keyframe-bound clamp to [0, source_duration], same-y-row caption overlap trim, mask full-duration extension, stroke-width zeroing, letter-spacing target snap per font, mask-cutout duration extension, negative-start clamp, and (with --face-bbox) face-overlap caption shift. Pass --auto-repair=false for strict validation; out-of-spec values then surface as render errors.
wonda transitions run --media $VID --clips ./timeline.json \
--face-bbox 200,160,520,520 --wait -o out.mp4
wonda transitions run --media $VID --clips ./timeline.json \
--auto-repair=false --wait -o out.mp4
--face-bbox only shifts body captions. Decorative text you want behind the speaker still routes through an explicit mask_cutout {prompt: "person"} clip.
Output URL paths differ by job type:
- Inference jobs (generate, audio):
.outputs[0].media.url and .outputs[0].media.mediaId
- Editor jobs (edit):
.outputs[0].url and .outputs[0].mediaId
Model waterfall
Image
Default: gpt-image-2. OpenAI's flagship — strongest prompt adherence, best text-in-image, high-fidelity edits via reference images. Handles 1-4 reference images. Quality tiers: auto (default), low, medium, high — pass via --params '{"quality":"high"}'. Caps at 1536px output.
For img2img editing specifically (change, add/remove, restyle, bg-remove, crop, text overlay, vectorize), use wonda skill get image-edit — it has the full edit-specific decision tree.
Pick something else only when one of these applies:
- User explicitly requests another model
- More than 4 reference images →
nano-banana-2 (gpt-image-2 caps at 4 refs; nano-banana-2 accepts up to 14). For 1-4 refs, stay on gpt-image-2.
- Need vector output →
runware-vectorize
- Need background removal →
birefnet-bg-removal
- Cheapest possible / fastest drafts →
z-image
- Need >1536px / true 4K output →
nano-banana-pro (1K/2K/4K) or nano-banana-2 (1K/2K/4K). gpt-image-2 caps at 1536px.
- gpt-image-2 unavailable / OpenAI down →
nano-banana-2 or seedream-4-5 or grok-imagine-pro
Video
Default: seedance-2 (duration 5/10/15s, default 5s, quality: high). Escalation:
- Quality complaint or different style →
sora2 or sora2pro
- Max single-clip duration is 15s for Seedance 2, 20s for Sora → for longer content, stitch multiple clips via merge
- Veo (
veo3_1, veo3_1-fast) is available but NOT in the default waterfall. Only pick Veo when the user explicitly asks for Veo by name.
- Gemini Omni (
gemini-omni-video) is available but NOT in the default waterfall. Only pick it when the user asks for Gemini by name, or specifically needs multi-image reference T2V/I2V (up to 7 reference images) or 4K output.
Image-to-video routing (MANDATORY when attaching a reference image):
- Person/face visible in the reference image → MUST use
kling_3_pro (preserves identity better for faces)
- No person in reference image → use
seedance-2
- Text-to-video (no reference image): Seedance 2 generates people fine. This rule ONLY applies when you
--attach an image.
Kling model family:
kling_3_pro — Text-to-video and image-to-video, supports start/end images, custom elements (@Element1, @Element2), 3-15s duration, 16:9/9:16/1:1
kling_2_6_pro — General purpose, 5-10s, 16:9/9:16/1:1, text-to-video and image-to-video
kling_2_6_motion_control — Motion transfer: requires both a reference image AND a reference video, recreates the video's motion with the image's appearance
kling2_5-pro — Budget Kling option, 5-10s, supports first/last frame images
Kling prompt rules (important): Kling's prompt field caps at 2,500 characters and Kling responds poorly to Sora-style structured briefs (SCENE: / SUBJECT: / MOTION: / BANNED LOOK: section headers). In that format Kling latches onto atmosphere nouns and silently drops the central subject (verified empirically: the same 2,842-char Sora-style prompt that rendered correctly on Sora 2 Pro and Seedance 2 produced no phone at all on Kling — even when trimmed to 2,250 chars). When escalating Seedance → Kling, or targeting Kling directly, rewrite the prompt as short natural-language prose (~1,000–1,500 chars) and lead with the hero subject in the opening sentence rather than burying it inside a SUBJECT: block. Do NOT pass a Sora-formatted prompt through to Kling unchanged.
Other video models:
grok-imagine-video — xAI video generation, 5-15s, supports 7 aspect ratios including 4:3 and 3:2
gemini-omni-video: Google Gemini Omni. Text-to-video and image-to-video with up to 7 reference images (slots reference_image_1 through reference_image_7). Durations 4/6/8/10s, aspect ratios 9:16 and 16:9, resolutions 720p / 1080p / 4K. Pricing: $0.15 base + $0.075/s at 720p/1080p, $0.75 base + $0.075/s at 4K. No native audio (pair with a separate audio model if speech is needed).
topaz-video-upscale — Upscale video resolution (1-4x factor, supports fps conversion)
sync-lipsync-v2-pro — Legacy lipsync for user-supplied video + audio pairs. Inferior to native-audio generation and almost never the right choice for new content. See the "Lip sync" section for rules.
Seedance family (DEFAULT video model, watermarks automatically removed):
seedance-2 — Base Seedance 2.0 (T2V/I2V, 5-15s, high=standard/basic=fast)
seedance-2-omni — Multi-reference generation (images, audio refs)
seedance-2-video-edit — Edit existing video via text prompt
Video durations: Accepted --duration values vary by model. Check with wonda capabilities or wonda models info <slug>.
Audio
- Music:
suno-music (set --params '{"instrumental":true}' for no vocals)
- Text-to-speech:
elevenlabs-tts — only for explicit narrator/voice-over asks over silent footage. Do NOT use to "make a UGC character talk" — Sora / Sora 2 Pro / Veo 3.1 / Kling 3 / Seedance 2 generate native synced speech in any language, which looks and sounds far better. Always set voiceId in params. Default female voice: --params '{"voiceId":"21m00Tcm4TlvDq8ikWAM"}' (Rachel).
- Transcription:
elevenlabs-stt
- Multi-speaker dialogue:
elevenlabs-dialogue
- Enhance audio (clean up noisy speech):
replicate-resemble-enhance via wonda audio enhance — denoise + dereverberate. Use when a voice recording sounds muffled, echoey, or has background noise. NOT a general "sounds better" button; if the source is already clean this can soften it.
- Extract voice (isolate vocals / split stems):
replicate-demucs via wonda audio extract-voice — splits into voice and instrumental tracks. Use to pull a speaker or singer off a track, or to isolate the music behind a vocal.
Native synced speech (preferred over TTS + lipsync): Sora, Sora 2 Pro, Veo 3.1, Kling 3, and Seedance 2 all generate dialogue in any language directly inside the video, with mouth movements baked in. Put the line (and language) in the video model's --prompt. Never chain elevenlabs-tts → sync-lipsync-v2-pro to fake speech over a silent generation.
Characters
Characters are reusable saved combos (image + optional voice audio) you can mention in prompts with @name. The server auto-injects the image, optional face video, and audio into the right slots for the selected model. Works on Kling 3 Pro (start_image + element_1 + voice_audio) and Seedance 2 Omni (ref_image_1 + ref_video_1 + ref_audio_1). Name rules: must start with a letter, 1–31 chars, alphanumeric + _/-.
Provider gotchas (Seedance 2 Omni): when a character is mentioned, the API routes Seedance to MuAPI automatically. Replicate enforces a 15s ref_audio_1 cap and rejects famous-celebrity refs with E005 — input flagged as sensitive. MuAPI is the reliable path for character-driven jobs. Even on MuAPI, top-tier celebrity refs (think Sydney Sweeney, Leonardo DiCaprio) are blocked with "Face detected in uploaded image. Please use an image without real people." Non-celebrity faces and lesser-known public figures pass cleanly. If you see that error on a real-person ref, use Kling 3 Pro instead (its character pipeline runs voice cloning server-side, so the raw face audio never touches a moderation classifier).
From a Kling clip — extract a frame + voice from a generation you like:
VID=$(wonda generate video --model kling_3_pro --prompt "young man, grey tshirt, talking to camera" --wait --quiet)
VID_MEDIA=$(wonda jobs get inference $VID --jq '.outputs[0].media.mediaId')
wonda character from-media alex --source $VID_MEDIA --frame-ms 2500
wonda generate video --model kling_3_pro --prompt "@alex welcomes viewers to the channel" --wait -o alex-welcome.mp4
From scratch — generate a portrait and a TTS sample, then bind them:
IMG=$(wonda generate image --model nano-banana-2 --prompt "young woman, studio portrait" --wait --quiet)
IMG_MEDIA=$(wonda jobs get inference $IMG --jq '.outputs[0].media.mediaId')
AUD=$(wonda audio speech --model elevenlabs-tts --prompt "Hi, this is me" --params '{"voiceId":"21m00Tcm4TlvDq8ikWAM"}' --wait --quiet)
AUD_MEDIA=$(wonda jobs get inference $AUD --jq '.outputs[0].media.mediaId')
wonda character create maya --image $IMG_MEDIA --audio $AUD_MEDIA
List / inspect / update / delete: wonda character list, wonda character get <name>, wonda character update <name> --audio $NEW, wonda character delete <name>. Only one character with audio can be referenced per generation.
Prompt writing rules
Follow this waterfall top-to-bottom. Use the FIRST matching rule and stop.
-
PASSTHROUGH — If the user says "use my exact prompt" / "verbatim" / "no enhancements" → copy their words exactly. Zero modifications.
-
IMAGE-TO-VIDEO — When a source image feeds into a video model, describe MOTION ONLY. The model can see the image. Do NOT describe the image content.
- Good:
"gentle breathing motion, camera slowly pushes in, atmospheric lighting shifts"
- Bad:
"Two cats on a lavender background breathing softly" (describes the image)
-
EMPTY PROMPT (from scratch) — Use the user's exact request as the prompt. Do NOT add style descriptors, lighting, composition, or mood.
- User says "create an image of a cat with sunglasses" → prompt:
"create an image of a cat with sunglasses"
- Do NOT enhance to
"A playful orange tabby wearing oversized reflective sunglasses, studio lighting, shallow depth of field"
-
NON-EMPTY PROMPT (adapting a template) — Keep the structure and style, only swap content to match the user's request. Keep prompts literal and constraint-heavy.
Aspect ratio rules
Three cases, no exceptions:
- User specifies a ratio → use it:
--aspect-ratio 16:9
- User doesn't mention ratio → explicitly set
--aspect-ratio 9:16 for social content (UGC, TikTok, Reels, Stories). Portrait is the default for any social/marketing video.
- Editing existing media → use
--aspect-ratio auto to preserve source dimensions
UGC and social content is ALWAYS portrait (9:16). If someone asks for a TikTok, Reel, Story, or UGC video, always use --aspect-ratio 9:16. Landscape is only for YouTube, presentations, or when explicitly requested.
Square (1:1) is supported by all Kling models and some image models — use for Instagram feed posts when requested.
Common chaining patterns
These patterns show how to compose multi-step pipelines by chaining CLI commands. Each step's output feeds into the next.
No need to download and re-upload between steps. Every generation and edit
produces a media ID in its output. Pass that ID directly to the next command
via --media or --audio-media. Use --jq '.outputs[0].media.mediaId'
for inference jobs and --jq '.outputs[0].mediaId' for editor jobs.
Only use -o <file> on the FINAL step to download the finished output.
Animate an image to video
MEDIA=$(wonda media upload ./product.jpg --quiet)
wonda generate video --model seedance-2 --prompt "camera slowly pushes in, product rotates" \
--attach $MEDIA --duration 5 --params '{"quality":"high"}' --wait -o animated.mp4
wonda generate video --model kling_3_pro --prompt "the person turns and smiles" \
--attach $MEDIA --duration 5 --wait -o person.mp4
Replace audio on a video (TTS voiceover or music)
TTS_JOB=$(wonda audio speech --model elevenlabs-tts --prompt "The script" \
--params '{"voiceId":"21m00Tcm4TlvDq8ikWAM"}' --wait --quiet)
TTS_MEDIA=$(wonda jobs get inference $TTS_JOB --jq '.outputs[0].media.mediaId')