# Wonda CLI

> Using the Wonda CLI to generate images, videos, music, and audio from the terminal — plus LinkedIn, Reddit, and X/Twitter research and automation

- Skill: `degausai/wonda-cli` (Agent Skill)
- Install (CLI): `npx skillmds@latest add degausai/wonda-cli`
- Raw SKILL.md: https://api.skillmd.com/api/skills/degausai/wonda-cli/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Research & Search
- Author: degausai (https://skillmd.com/u/degausai)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/degausai/wonda-cli

---


# 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. The recommended installs are the signed desktop installers (CLI + tray icon + always-on relay, zero extra steps): macOS `brew install --cask degausai/tap/wonda-app` (or the wonda-macos.pkg from releases), Windows winget/wonda-windows-setup.exe. The CLI-only channels below work everywhere and can add the desktop app later with `wonda app install`:

```bash
# npm
npm i -g @degausai/wonda

# Homebrew
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:

1. Open `https://wonda.sh/download` while signed in and install the notarized
   Mac package.
2. 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.
3. 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 requires a paid plan, including API calls, local platform reads, browser automation, media composition, file editing, diagnostics, generation, publishing, scraping, analysis, skills, relay operation, and cloud twin. Before any product command does work, the CLI performs a live entitlement check against Wonda. The check fails closed when access cannot be verified. New accounts get no product access until they subscribe, and a positive credit balance does not substitute for a plan.

| Tier                                            | Access                                                                                                                                                                                                                                                                              |
| ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Anonymous** (no login)                        | No product access. Authentication, config, pricing, organization discovery/selection, shell completion, and local shutdown commands remain available so the user can subscribe, recover, or stop background services. 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).                                             |

If a command returns a `403` (`paid_plan_required`), subscribe at https://wonda.sh/account.

This applies to the local stdio MCP path too because MCP executes the same CLI. Platform cookies and data remain on-device, but the command must first receive a successful paid-access decision from `GET /api/v1/auth/access`. A relay-only installation may use its scoped `wrelay_...` credential for that entitlement probe. The scoped credential still cannot call ordinary account APIs.

### 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.

```bash
# Clone from a local file (auto-uploads to media library first)
wonda voice create "Andu" --file ./sample.mp3 --description "My voice"

# Clone from existing wonda media
wonda voice create "Brand" --media-id <uuid>

# Optional source-audio preprocessing
wonda voice create "Clean" --file ./raw.wav --noise-reduction --normalize-volume

# List cloned voices (each row reports isExpired and expiresInDays)
wonda voice list

# One voice
wonda voice get <voice-id>

# Rename / re-describe (local only, no provider call)
wonda voice update <voice-id> --name "New Name" --description "..."

# Delete
wonda voice delete <voice-id>
```

**Use a cloned voice in TTS** by passing the `providerVoiceId` from `voice get` as `voiceId` to `/audio/speech`:

```bash
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`.

```bash
# Create
wonda credentials create --website instagram.com --username myhandle \
  --email me@example.com --password-stdin <<< "hunter2" \
  --metadata '{"signup_source":"wonda-email"}'

# List (passwords omitted)
wonda credentials list --website instagram.com

# Get full record including decrypted password
wonda credentials get <id>

# Update any field (use --password-stdin to rotate; --username "" to clear)
wonda credentials update <id> --username newhandle

# Delete
wonda credentials delete <id>

# Fetch + record why you're using it in one call — POST, not GET, because
# it writes a 'used' event with the reason. Prefer this over `get` whenever
# you can articulate the reason.
wonda credentials use <id> --reason "instagram signup flow"

# See recent events (created / used / rotated / updated) for audit
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`)

**1Password is included in local WAB profiles.** WAB downloads the official stable 1Password browser extension, verifies its publisher signature, and pins it to the toolbar on first setup. Open the WAB with `wonda wab show <persona>`, then click 1Password to sign in. Each persona keeps its own 1Password session. The desktop app may require you to approve WAB as an additional browser; signing in directly in the extension also works. Cached extension files work offline. WAB checks cached extensions for updates at most once a day when a persona starts, including failed checks; changing the Chromium version triggers another compatibility check. If the extension has never installed successfully, each start retries installation. An already-running browser picks up an extension update on its next start. Startup and `wab install` prune unused cached versions older than seven days; versions used by a live WAB process are kept. Set `WAB_ONEPASSWORD=0` before starting WAB to skip the bundled extension. Anonymous captures, scratch sessions, and cloud twins do not load it. A Wonda data path containing a comma also skips 1Password with a driver-log warning; use a `WONDA_HOME` path without commas to enable the extension. Enhanced Safe Browsing or administrator policies can also block automatic extension loading; WAB reports Chromium’s rejection in its driver log and preserves your browser settings.

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 screenshot <url>`, `wonda wab record <url>`, and `wonda brand extract` drive an ephemeral Chromium with a fresh fingerprint, no persona, and no cookies. Screenshot supports responsive single, batch, and manifest capture. See the screenshot and record blocks 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.

```bash
wonda wab install                             # one-time: npm install + stealth-browser Chromium (shared by sessions, screenshot, record, brand extract)
wonda wab update                              # refresh the runtime to the version this CLI pins (runtime versions ship inside CLI releases): staged download, verified, then atomically swapped in, so a failed download leaves the current driver tree untouched; refuses the runtime swap while personas run (wab stop them first; the relay picks the new runtime up on its next spawn, no restart), while an already-current tree's cheap manifest refresh proceeds without the gate: it reconciles drifted current files back to this build's embedded bytes (additive rewrites, safe under a running persona) and only prunes stale files when NO personas are running, deferring the prune (and keeping the mismatch visible) until the tree is quiet; --check reports installed vs pinned without changing anything, --force reinstalls even when current and re-downloads the shared Chromium build (corrupt-cache recovery; caveat: --force clears the cached Chromium before re-downloading, so a failed forced download leaves the shared cache without it until a retry succeeds). The driver tree also carries a manifest recording what produced it: the embedded driver asset identity compatibility is actually keyed on, plus the CLI build and platform as provenance. A tree whose recorded assets differ from this binary's embedded set (or that predates manifests) is refreshed automatically at the next browser spawn or wab update; two builds shipping byte-identical driver assets share one manifest without churn, so the recorded CLI version can lag behind the newest compatible binary. --check reports the manifest state (ok|absent|mismatch|unreadable) without touching anything; --json only changes the output format and still performs the update
wonda wab start [account]                     # spawn (offscreen by default; --visible to show)
wonda wab stop [account]                      # graceful shutdown
wonda wab show [account]                       # peek a background WAB on-screen to watch it (suspends the macOS focus guard); starts it offscreen first if needed
wonda wab hide [account]                       # send a surfaced WAB back offscreen, resume silent background operation
wonda wab screenshot [persona]                 # persona mode: capture an already-open tab without surfacing it; --json still returns inline base64, --output writes a file, --tab/--full-page optional
wonda wab screenshot <url> --output page.png   # anonymous PNG in a fresh ephemeral browser; responsive waits, injection, animation, element, clip, batch, manifest, and diagnostic JSON controls, see below
wonda wab browse [url] --persona <persona>     # load a page and scroll it like a person: pause, scroll, pause, scroll; stops early once the scrolled element stops advancing, see below (plain text output, not JSON)
wonda wab menubar                              # Tray control, macOS menu bar or Windows notification area: the Wonda cat icon with a corner status badge. Green filled = running here and serving; orange half = standing by, which proves only that this machine is not the active device (whether another device or no device is serving is a separate three-way the badge cannot answer, and the menu's detail line only echoes the last relay-health check, a snapshot that can lag while this machine stays in standby); red triangle = running but cannot serve; grey hollow = not running here. The badge carries shape as well as color (filled/half/triangle/hollow) on both platforms; macOS falls back to a 🐱 text item with the same states as glyphs. Click for Restart Relay, a state-following Stop/Start toggle (`wonda relay disable`/`enable`, never `relay stop`), Open Log (macOS only; the Windows task runs the relay with no log file), and Show/Hide per running WAB; the tooltip carries the full status sentence. The bottom item "Quit Wonda" runs `wonda app quit` (stop relay + disable autostart + remove every open-at-login mechanism (macOS login item, Windows Startup entry) + remove the icon; quit stays quit until the app or `wonda app open` runs again). The Windows tray is a PowerShell/WinForms NotifyIcon consuming `relay health --json` (state) plus a periodic `wab status --json` (the persona Show/Hide list) and shelling wonda verbs, zero platform logic in the script. --stop removes only the icon and leaves the relay running
# macOS Dock menu: right-click a running WAB's Dock tile (the 🐱) for "Show on screen" / "Send to background" (same as wab show/hide). Each running persona has its own Dock tile and its menu controls only that persona. Opt out with WAB_DOCK_MENU=0.
# macOS: a background WAB no longer steals focus or flashes the menu bar / Dock when it opens a new tab; the Dock tile stays, it just never comes to the foreground until you `wab show` it.
wonda wab status                              # list personas + last activity + browser-context health (a running daemon whose Chromium died shows browser-dead with a restart hint instead of a misleading plain "running")
wonda wab login <account> <linkedin|x|reddit|instagram> # RECOMMENDED for a new persona: open headful window, user logs in, session minted in-WAB (independent + fingerprint-coherent)
wonda wab check <account> <linkedin|x|reddit|instagram> # non-interactive session-alive probe
wonda wab bind <persona> --x <acct> --reddit <acct> --linkedin <acct>  # multi-account power-user path: bind N accounts to ONE persona
wonda wab record <url>                        # anonymous one-shot webm recording (no account, no cookies), see below
wonda wab sync-cookies [account]              # force wab → disk cookie sync now (don't wait for the 10-min timer)
wonda wab logs [account] --tail 100           # tail driver.log (--audit for structured per-command log)
wonda wab errors --tail 20 --since 24h        # tail the cross-persona action-failure log
wonda wab top-failures --since 7d             # rank local WAB failures by platform/action/reason, joined with DOM recovery stats
wonda wab top-failures --platform x --json    # machine-readable local failure ranking
wonda wab bundle-failures list                # recent action failure bundles (one per failed run: screenshot, dom, visible-elements, cookies-summary REDACTED)
wonda wab bundle-failures show <id>           # print manifest + file tree for a bundle (id = unix-ms-ts prefix)
wonda wab bundle-failures ship <id>           # zip to ~/Downloads/wonda-failure-<id>.zip for sharing
wonda wab bundle-failures prune               # remove bundles older than 30d (or --max-per-persona, --all)
# Telemetry: on every wab action failure we report (action, platform, reason, error-string, has_bundle, cli_version) as a wab_action_failed PostHog event so maintainers can spot platform rotations across users. NO bundle contents, NO cookies, NO DOM, NO screenshots leave the user's machine. Opt out: WONDA_TELEMETRY_DISABLED=1. For server-side breakdowns, group `wab_action_failed` by `platform`, `action`, `reason`, and `has_bundle` in PostHog. Locally, `wonda wab top-failures` reads only `~/.wonda/wab/errors.jsonl` and persona-local `dom-recoveries.jsonl`, then shows count, last seen, recovered rate, bundle count, and a sample bundle id.
wonda wab migrate-legacy                      # copy a legacy WAB-driver profile into a persona slot
wonda wab restore <persona> [timestamp]       # restore from an hourly snapshot (--list to enumerate)
wonda wab backup disable                      # opt out of auto-push (on by default; existing cloud backups untouched)
wonda wab backup enable                       # opt back in (auto-push synced cookie JSON to wondercat after every disk sync)
wonda wab backup status                       # show config + remote inventory
wonda wab backup push [account]               # one-shot manual push for all platform bindings
wonda wab backup pull [account]               # guarded restore to ~/.wonda/<platform>-cookies/<account>.json; refuses non-empty local unless --force
wonda wab backup pull [account] --dry-run     # preview restores without writing
wonda wab backup list                         # inventory of cloud backups, including device/provenance metadata when available
wonda wab backup delete <plat> <persona> [acct] # remove one backup
wonda wab cookies list                        # explicit cookie backup inventory, metadata only
wonda wab cookies status [account]            # local cookie files plus cloud backup rows
wonda wab cookies port <plat> <persona> [acct] --from-device <id|label> # safely port one selected cloud row to this machine
wonda wab config set <persona> <key> <value>  # persist per-persona spawn defaults (idle-timeout, locale, visible, interactive, proxy_url, timezone, geo_lat/lon)
wonda wab config get <persona>                # print a persona's persisted config
```

**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.

**Scroll a page like a person (`browse`).** `wonda wab browse [url] --persona <persona>` loads a page in the persona's WAB and scrolls it: a pause to look at the page, then scroll, pause, scroll, for `--scrolls` times (default `5`). `--scrolls` accepts `1`-`200`; a value outside that range is a hard error, not clamped, and it is rejected before a browser is launched. `--first-wait` (default `10s`) is the pause before the first scroll; `--wait` (default `5s`) is the pause between scrolls; both are jittered +/-30% because a precisely repeated interval is itself a fingerprint, and both are floored at 250ms so a very small value is not jittered down to zero — `--first-wait 0 --wait 0` still pauses ~250ms per wait, not 0, so with `--scrolls 200` that floor alone adds up to roughly 50s. It only scrolls — no clicking, no engagement — so it works on any site.

Progress is measured on the element that was actually scrolled: the viewport-filling overflow container when the site has one (LinkedIn's feed lives in `<main id="workspace">`, where `window.scrollY` never moves at all), otherwise the document scroller. Scrolling stops early once that element stops advancing across 2 consecutive scrolls, reported as `(reached bottom)` in the printed summary, so a short page does not grind against the bottom. An infinite feed keeps going for the full `--scrolls`.

`--persona` falls back to your configured default account exactly like the other `wonda wab` commands, and an invalid persona name is rejected up front instead of silently creating an empty logged-out profile. With a url, `browse` navigates in its OWN isolated tab (like every other WAB write) and reports the page actually browsed (after any redirect). Omitting the url dispatches to the persona's shared **default** tab instead — the driver's initial page, the same tab id `wonda wab show`/`hide` reference — scrolling whatever that tab already shows. `wonda wab login` does NOT leave anything there: it opens and navigates its own separate `<platform>-login` tab, so a persona that was just logged in still has an untouched default tab (often still `about:blank`). `wonda wab show`/`hide` don't navigate the default tab either — they only toggle the window's on/off-screen visibility — so neither one "opens a page" there. What actually leaves a page on default is something that navigates it directly, e.g. `wonda wab start --open <platform|url>` (also what the MCP `wab_open` tool calls). **This is NOT a way to continue a page from a previous `wonda wab browse <url>` run**: that run navigated in its own isolated tab, which the no-url form never sees. If the default tab has no page open (`about:blank`) — the common case right after logging a new persona in, since login's tab is separate — the command fails and asks for a url rather than reporting a successful scroll of nothing.

`browse` prints a plain-text summary, not JSON, so the global `--json` / `--fields` / `--jq` flags do not apply to it.

```bash
wonda wab browse https://example.com --persona <persona> --scrolls 6
wonda wab browse --persona <persona>                      # scroll the shared default tab's current page, no navigation
```

**Anonymous PNG capture (`screenshot`).** `wonda wab screenshot` has a compatibility-preserving persona mode and a new anonymous URL mode.

`wonda wab screenshot [persona]` still captures the persona's already-open tab without surfacing the window. Its existing flags retain their meaning: `--tab` selects the tab, `--full-page` captures the scrollable page, and `--output` writes a file. With `--json` and no output file it still returns `{path, base64, mimeType}` so MCP and existing automation receive the inline PNG unchanged.

An absolute `http://` or `https://` argument selects anonymous mode. It launches a fresh ephemeral Chromium with no persona, cookies, or persistent state:

```bash
wonda wab screenshot \
  'http://127.0.0.1:8765/hero-lab.html#close-colorflight' \
  --output tmp/close-proof.png \
  --viewport 2048x982 \
  --scale 1 \
  --wait-until networkidle \
  --wait-for '#hero-preview' \
  --delay 700ms \
  --animations disabled
```

Anonymous defaults are `--viewport 1280x720`, `--scale 1`, `--wait-until networkidle`, `--delay 0`, and `--animations allow`. `--wait-until` accepts `load`, `domcontentloaded`, or `networkidle`. Wonda automatically waits for `document.fonts.ready`; `--wait-for <selector>` waits for a visible element, and `--delay <duration>` adds a final settling delay. `--inject-js <file>` runs after navigation in an async IIFE, so top-level `await` works.

For deterministic motion, use `--animations disabled` to finish finite animations and cancel infinite ones at capture, or `--freeze-at 800ms` to seek Web Animations to an exact timeline offset and pause them. The two options conflict. `--selector '.hero-stage'` captures the first matching element. `--clip x,y,width,height` captures an exact document-coordinate rectangle in CSS pixels. The rectangle may extend beyond the viewport but must fit within the rendered page. `--selector`, `--clip`, and `--full-page` are mutually exclusive capture modes.

Pass several absolute URLs to capture all of them. Or pass relative path, query, or quoted hash arguments after one absolute base URL; when any relative target is present, the first URL is only the resolution base and is not captured separately. Add `--viewports` for a viewport matrix. Chromium launches once for the whole batch:

```bash
wonda wab screenshot http://127.0.0.1:8765/hero-lab.html \
  '#fast-close' '#close-colorflight' \
  --viewports 2048x982,1440x1000,390x844 \
  --output tmp/qa \
  --output-template '{route}-{viewport}.png'
```

For a single capture without `--output-template`, `--output` is the PNG path. For a batch, manifest, or any template-driven capture, it is an output directory. `--output-template` supports `{index}`, `{route}`, and `{viewport}`, and expanded paths must be unique. `{route}` is sanitized; overlong expanded path components are shortened with a stable hash suffix. `--viewport` and `--viewports` conflict. Without output flags, one URL writes `screenshot-<timestamp>.png` in the current directory and a batch writes under `screenshots-<timestamp>/`. A template without `--output` is relative to the current directory, or to the manifest directory when it comes from the manifest.

A strict JSON manifest describes the same matrix:

```json
{
  "url": "http://127.0.0.1:8765/hero-lab.html",
  "routes": ["#fast-close", "#close-colorflight"],
  "viewports": ["2048x982", "1440x1000", "390x844"],
  "scale": 1,
  "waitUntil": "networkidle",
  "waitFor": "#hero-preview",
  "delay": "700ms",
  "injectJs": "scripts/visual-state.mjs",
  "animations": "disabled",
  "outputTemplate": "{route}-{viewport}.png"
}
```

Run it with `wonda wab screenshot --manifest visual-qa.json --output tmp/qa`. Use `urls` instead of `url` for independent absolute targets; `routes` resolves relative to `url`. Manifest keys mirror the anonymous scalar flags in camel case, including `freezeAt`, `selector`, `clip`, and `fullPage`. Explicit CLI flags override manifest values.

Anonymous `--json` returns `{ok, captures: [...]}` without embedding PNG bytes. Every capture reports `url`, `finalUrl`, `viewport`, `dpr`, `pageDimensions`, `consoleErrors`, `failedRequests`, and `fontsLoaded`. A successful capture also reports `path`, which means the PNG was written there. A failed capture omits `path`, carries `error`, and keeps any diagnostics collected before the failure. The browser continues the remaining matrix after an item fails and exits nonzero when any capture failed. Use this output for visual QA diagnostics. Non-JSON output prints only successfully generated PNG paths.

**Anonymous video 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.

```bash
wonda wab record https://example.notion.site/page \
  --output recording.webm \
  --duration 5 \
  --viewport 960x1080 \
  --inject-js scripts/page-script.mjs   # optional: runs after load, before timer starts

# Transcode webm to mp4 at 30 fps (the stealth browser records webm/VP8)
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 t

…(truncated)
