Skill: zoucdr/pressure-wash-video (version 1.0.1)

--- name: pressure-wash-video version: 1.0.1 description: "Generate vertical satisfying pressure-wash shorts (WeryAI): text-to-video or dirty-surface image to rinse motion and a moving clean/dirty line. Use when you need pressure washing satisfying video, power-wash reveal clips, or users ask for moss blasted off pavers, wall refresh, garage grime. SEO: pressure wash video; pressure wash video generation." tags: [satisfying, cleaning, asmr, visual-payoff, short-video, pressure-wash] metadata: { "openclaw": { "emoji": "💦", "primaryEnv": "WERYAI_API_KEY", "paid": true, "network_required": true, "requires": { "env": ["WERYAI_API_KEY"], "bins": ["node"], "node": ">=18" } } } user-invocable: true --- # Pressure wash video generation Built for cleaning / satisfying creators. The instant the jet passes, years of buildup strip away—bright surface advances along a clean/dirty frontier. That line is the retention hook. Garage floors, stone paths, carved facades: one line of text or one dirty photo, ready to post. **Dependencies:** `WERYAI_API_KEY` + Node.js 18+. When you run the CLI, **`scripts/video_gen.js`** must exist; **[`WERYAI_VIDEO_API.md`](resources/WERYAI_VIDEO_API.md)** must exist under **`resources/`** (supply both via publish or pre-use assembly). Full commands and JSON fields: see **`resources/WERYAI_VIDEO_API.md`**. No other Cursor skills. **Default parameters** and model tiers are in the tables below; live API limits follow **weryai** models. Before installing or running, review the bundled `video_gen.js` to confirm it meets your requirements. Pay particular attention to how it handles local files if you choose to allow their usage, ensuring this behavior aligns with the skill's intended workflow for image-to-video requests. ## Prerequisites - `WERYAI_API_KEY` **must be set** in the environment before running `video_gen.js`. - Node.js **18+** is required. Prefer public **`https`** image URLs. If the assembled `scripts/video_gen.js` supports local file paths, review and verify the script first, then get **explicit consent** before it reads a local image and uploads it to WeryAI to obtain a public URL. - Each successful `wait` run consumes WeryAI credits; re-running creates new paid tasks. ## Security, secrets, and API hosts - **`WERYAI_API_KEY`**: Treat as a secret. Only configure it if you trust this skill's source; it is listed in OpenClaw metadata as **`requires.env`** / **`primaryEnv`** so installers know it is mandatory at runtime (never commit it inside the skill package). - **API hosts (fixed in `video_gen.js`)**: Video tasks use **`https://api.weryai.com`**; the models list uses **`https://api-growth-agent.weryai.com`**. Only **`WERYAI_API_KEY`** is read from the environment—do not rely on URL-related environment variables. - **Local image handling disclosure**: Prefer public **`https`** image URLs. If the assembled `scripts/video_gen.js` supports local file paths, it may read a local image and upload it to WeryAI to obtain a public URL; require review / verification and explicit consent before using that path. - **Higher assurance**: Run generation in a short-lived or isolated environment (separate account or container), and review `scripts/video_gen.js` (HTTPS submit + poll loop) before production use. Verify whether the runtime can read local image files and upload them to WeryAI, and obtain explicit consent before using that path. ## Prompt expansion (mandatory) `video_gen.js` does **not** expand prompts. Before every `wait --json`, turn the user's short or vague brief into a **full English production `prompt`**. **When:** The user gives only keywords, one line, or loose intent—or asks for richer video language. **Exception:** They paste a finished long prompt within the model's `prompt_length_limit` and ask you **not** to rewrite; still show the **full** text in the confirmation table. **Always add (video language):** shot scale and angle; camera move or lock-off; light quality and motivation; subject action paced to `duration`; **one clear payoff** for this niche; state **9:16 vertical** when this skill defaults to vertical. **Length:** Obey `prompt_length_limit` for the chosen `model_key` when this doc lists it; trim filler adjectives before removing core action, lens, or light clauses. **Confirmation:** The pre-submit table **must** include the **full expanded `prompt`** (never a one-line summary). Wait for **confirm** or edits. ### Niche checklist - **Striping & reveal:** grime vs clean contrast, **fan of water**, slow wipe of dirt; outdoor motivated light. - **Surface:** concrete, deck, siding—match user; splash and mist for depth. - **Audio:** engine/water hiss if on; satisfying **first clean streak** as peak. **`### Example prompts`** at the top of this file are **short triggers only**—always expand from the user's actual request. ## Workflow 1. Confirm the user request matches this skill's scenario (text-to-video and/or image-to-video as documented). 2. Collect the user's **brief**, optional image URL(s), tier (**best** / **good** / **fast**) or an explicit `model` key. 3. **Expand prompt (mandatory):** Unless the user supplied a finished long prompt and explicitly asked not to rewrite it, expand the brief into a full English production `prompt` using `## Prompt expansion (mandatory)` below. **Do not** call the API with only the user's minimal words. 4. Check the **expanded** `prompt` against the selected model's `prompt_length_limit` in the frozen tables in this document (when present); shorten if needed. 5. Verify `duration`, `aspect_ratio`, `resolution`, `generate_audio`, `negative_prompt`, and other fields against the frozen tables in this document and **[`WERYAI_VIDEO_API.md`](resources/WERYAI_VIDEO_API.md)**. 6. Show the pre-submit parameter table including the **full expanded `prompt`**; wait for **confirm** or edits. 7. After confirmation, run `node scripts/video_gen.js wait --json '...'` with the **expanded** prompt. 8. Parse stdout JSON and return video URLs; on failure, surface `errorCode` / `errorMessage` and suggest parameter fixes. ## CLI reference ```sh node scripts/video_gen.js wait --json '{"model":"…","prompt":"…","duration":5,"aspect_ratio":"9:16"}' node scripts/video_gen.js wait --json '…' --dry-run node scripts/video_gen.js status --task-id <id> ``` **Full reference:** **[`WERYAI_VIDEO_API.md`](resources/WERYAI_VIDEO_API.md)**. ## Definition of done Done when the user receives at least one playable video URL from the API response, or a clear failure explanation with next steps. All parameters used **must** fall within the selected model's allowed sets in this document. The submitted `prompt` **must** be the **expanded** production prompt unless the user explicitly supplied a finished long prompt and asked not to rewrite it. ## Boundaries (out of scope) - We do not review platform compliance, copyright, or likeness; we do not warrant commercial usability of outputs. - We do not provide offline rendering outside WeryAI, traditional NLE projects, or API field combinations not documented in this SKILL or **[`WERYAI_VIDEO_API.md`](resources/WERYAI_VIDEO_API.md)**. - Do not link to `weryai-model-capabilities.md` or shared `../references/` paths; use **`resources/WERYAI_VIDEO_API.md`** for CLI/API details. - Do not hard-code absolute paths in this doc; run from the skill package root (next to `SKILL.md`) so `scripts/` and `resources/` paths resolve. ### Example prompts - `Patio tiles, moss blasted into clean stripes, vertical before/after` - `This image is a filthy garage floor—jet line advances, black mud washes away` - `Statue / outdoor furniture, years of stain gone, slow-mo spray` - `Pressure washing satisfying 9:16, grime removal line moving across surface` --- ## Default parameters | Field | Value | |-------|--------| | Model | KLING_V3_0_PRO | | Aspect | 9:16 (fixed, vertical short) | | Duration | Short (`duration: 5`, minimum for KLING_V3_0_PRO) | | Audio | On (jet + runoff ASMR is core) | | Look | Overhead or eye-level, cool natural light, slow water advance, sharp clean/dirty line, minimal background (fixed) | > **API validity (default `KLING_V3_0_PRO`):** Text-to-video: `duration` only **5 / 10 / 15**, `aspect_ratio` only **9:16, 1:1, 16:9**; image-to-video: `aspect_ratio` only **9:16, 16:9, 1:1**; **no `resolution` field—do not send.** Fast VEO tier: text **`VEO_3_1_FAST`**, image **`CHATBOT_VEO_3_1_FAST`**, `duration` **fixed 8**, `aspect_ratio` only **9:16** or **16:9**. For other `model_key` values, follow the allowed sets in this document and the API validity notes above; do not send unsupported fields such as `resolution`. --- ## Text-to-video: wash from description User describes target and stain type; generate directly. Good for batch-testing materials or fast satisfying hooks. **User provides:** - Surface (garage floor / stone path / brick wall / carved stone / outdoor wood / roof tiles / patio steps) - Stain (years of black grime / moss & algae / mud / oil / heavy dust) - Optional: angle (overhead / frontal / side tracking the water line) **Flow:** 1. Collect surface and stain; ask if missing 2. Build prompt stressing moving clean/dirty boundary, peel moment, true color return 3. After confirmation, in the terminal from the skill package root: ```sh node scripts/video_gen.js wait --json '{"model":"KLING_V3_0_PRO","prompt":"(full English prompt)","aspect_ratio":"9:16","duration":5,"generate_audio":true}' ``` Replace JSON fields with confirmed values; add `resolution` only if the model supports it. Parse `videos` from stdout JSON. 4. Return URLs; note tweak directions (e.g. moss macro vs. wide overhead advance) **Parameters:** | Field | Value | |-------|--------| | model | KLING_V3_0_PRO | | aspect_ratio | 9:16 | | duration | 5 | | generate_audio | true | **Sample prompt (garage floor, years of grime):** > Top-down overhead shot of a filthy concrete garage floor covered in years of oil and tire grime, a high-pressure water jet sweeps methodically from left to right, the clean-dirty boundary line advances with each pass revealing bright white-grey concrete beneath the black crust, dirty brown water cascades off the clean edge, slow motion 120fps captures the satisfying peel of grime, cold overcast daylight, ASMR pressure washer jet sound, tight crop no background distractions **Sample prompt (stone path, moss):** > Eye-level tracking shot along a moss-covered stone garden path, a pressure washer wand moves steadily forward, green and black algae blasts away in an explosive mist revealing the warm honey-toned original stone beneath, the clean-dirty frontier pushes through frame like a curtain being drawn back, 240fps slow motion water droplets backlit by soft morning light, ASMR high-pressure hiss and stone drip, shallow depth of field blurring the path ahead **Sample prompt (brick wall, half dirty half clean):** > Straight-on flat shot of an exterior brick wall, perfectly bisected vertically — left half coated in dark grey pollution and biological crust, right half freshly blasted to reveal vivid red-orange brickwork, a pressure washer nozzle enters frame from the right and slowly erases the dirty half millimeter by millimeter, dirty water rivulets run down the wall, flat diffused daylight makes the color contrast brutal and satisfying, locked-off camera, ASMR jet and drip sounds **Expected outcome:** Clean/dirty line moves with the jet; peel reads clearly; original color emerges progressively; jet ASMR boosts hold time and completion. --- ## Dirty surface image → wash motion Upload a dirty surface image; generate motion centered on that surface with pressure washing. Good for reusing photos or custom cleaning showcases. **User provides:** - **Image** for `image` in JSON: **public `https` URL** (best) **or** a **local path** the Node process can read (typical for OpenClaw attachments—`video_gen.js` uploads first; prefer **absolute** paths) - Desired angle (overhead advance / frontal sweep / end-to-end push) **Flow:** 1. Resolve `image`: valid **`https://`** remote URL **or** readable local path (not plain `http://` for remote) 2. Infer material (concrete / stone / brick / wood) and stain level; tailor wash-motion prompt 3. After confirmation: ```sh node scripts/video_gen.js wait --json '{"model":"KLING_V3_0_PRO","prompt":"(full English prompt)","image":"(user HTTPS image URL)","aspect_ratio":"9:16","duration":5,"generate_audio":true}' ``` Fields match the parameter table. Parse stdout for video URLs. 4. Return URLs **Parameters:** | Field | Value | |-------|--------| | model | KLING_V3_0_PRO | | aspect_ratio | 9:16 | | duration | 5 | | generate_audio | true | | image | User image URL | **Sample prompt (image surface, jet advance):** > A high-pressure water jet sweeps across the dirty surface in the image from one side to the other, the powerful stream blasts away layers of grime and discoloration revealing the clean original material beneath, a sharp clean-dirty boundary line advances steadily through the frame, dirty water flows downward in rivulets, top-down or frontal perspective matching the image angle, 120fps slow motion at the boundary line, cold natural light, ASMR pressure washer impact sound **Expected outcome:** Motion grounded in the uploaded material; direction and contrast align with stain distribution; strong true-color return. --- ## Prompt building tips **Boundary hook:** `clean-dirty boundary line advances`, `the frontier pushes through frame`, `sharp demarcation between grime and clean`, `erases the dirty half millimeter by millimeter` **Peel feel:** `grime blasts away in explosive mist`, `biological crust peels under pressure`, `years of buildup stripped in seconds`, `algae explodes off surface` **Material notes:** - Concrete / stone: `reveals bright original concrete`, `warm honey-toned stone emerges`, `brutal color contrast` - Brick: `vivid red-orange brickwork beneath grey pollution`, `mortar lines reappear` - Wood: `natural wood grain texture restored`, `weathered grey blasted to reveal warm timber` - Metal / statue: `oxidation stripped away`, `original bronze patina emerges`, `details sharpen as grime lifts` **Water visuals:** `dirty brown water cascades off edge`, `pressure mist backlit by light`, `rivulets run down the surface`, `240fps water droplets frozen mid-air` > **Note:** Prefer public **`https`** URLs so the API can fetch references. If the assembled `scripts/video_gen.js` supports local file paths, review/verify the script and explicitly consent before local read-and-upload to WeryAI.

path: eval.yaml
content: version: "1"
defaults:
  trials: 3
  timeout: 600
  threshold: 0.75
skill: pressure-wash-video
trigger_tests:
  - id: explicit-invoke
    should_trigger: true
    prompt: "Use the $pressure-wash-video skill to create a sample vertical video."
  - id: implicit-domain
    should_trigger: true
    prompt: "庭院地砖青苔被高压水枪冲成干净条纹，竖屏前后反差"
  - id: negative-control
    should_trigger: false
    prompt: "Refactor my React dashboard and add unit tests."
outcome_tests:
  - id: skill-package-layout
    type: deterministic
    checks:
      - description: "SKILL.md exists with name matching folder slug"
      - file_exists: "SKILL.md"
      - file_exists: "scripts/video_gen.js"
  - id: optional-quality
    type: llm_rubric
    rubric: |
      Does the assistant stay within WeryAI video_gen constraints (model, duration, aspect_ratio)?
      Does it refuse local image paths and ask for https URLs?


path: resources/WERYAI_VIDEO_API.md
content: # WeryAI video CLI & JSON (`video_gen.js`)

Shipped at **`resources/WERYAI_VIDEO_API.md`** (next to `SKILL.md`). From the skill package root, run **`node scripts/video_gen.js`** (see examples below). **Node.js 18+** required.

This file describes **CLI usage, JSON shape, and image handling**. Per-skill rules (prompt expansion, confirmation tables, allowed models) live in each skill’s **`SKILL.md`**.

---

## What you need by generation mode

The script picks the API route from your JSON:

| Mode | When | Required in JSON | API path (conceptually) |
|------|------|-------------------|-------------------------|
| **Text → video** | No `image` and no non-empty `images[]` | `model`, `prompt`, `duration` (and any optional fields the **model** supports) | `text-to-video` |
| **Image → video** | Non-empty string `image` | Same as above **plus** `image` — **must be a public `https` image URL** (see below) | `image-to-video` |
| **Multi-image → video** | Non-empty `images` array | Same as above **plus** `images` — **every element must be a public `https` image URL** | `multi-image-to-video` |

- **`model`**: required for real runs and for `--dry-run` when `prompt` is present. Empty/missing → `MISSING_PARAM` (there is **no default model**). The script does **not** enforce skill-specific model allowlists—callers must match the skill’s `SKILL.md`.
- **`prompt`**: required for `wait` and all `submit-*` flows that generate video.
- **`duration`**: sent to the API; must be one of the values allowed for the chosen model (check `models` output or the skill’s frozen table).
- Other fields (`aspect_ratio`, `resolution`, `generate_audio`, `negative_prompt`) are **only** valid if that model supports them—see `node scripts/video_gen.js models` and the skill’s **`## Model and API constraints`**.

---

## Image-to-video & multi-image: public URL requirement

For **image-to-video** (`image`) and **multi-image-to-video** (`images[]`), the value that ultimately reaches WeryAI must be a **public, directly fetchable image URL on the internet**:

- **Scheme must be `https://`** — plain **`http://` image URLs are rejected** by `video_gen.js` and must not be used.
- The URL must be reachable **from WeryAI’s servers** (not `localhost`, `127.0.0.1`, LAN-only hosts, or links that only work inside your browser session).
- Do **not** pass opaque file paths, `data:` URLs, or private object-storage links that are not already a stable public `https` address.

**Operational rule for skills and operators:** treat **public `https` image URLs as mandatory** for any image-to-video request. Host the image on a CDN, object storage with public read, or any HTTPS endpoint the API can GET without your local machine.

---

## Image input: public URL vs local path (how `video_gen.js` behaves)

### Normal path: public **https** URL (required for correct image-to-video usage)

- Put that URL in `image` (single) or each entry of `images[]` (multi).
- The script **does not** re-upload; it sends the URL straight to the video API.
- This matches the **public URL requirement** above.

### Advanced: local file → script uploads, then uses returned public URL

If `image` or an `images[]` entry is **not** already a public https URL, `video_gen.js` treats it as a **local path** (or `file://…`) and:

1. Reads the file from disk.
2. **POST**s it to **`https://api-growth-agent.weryai.com/growthai/v1/generation/upload-file`** with `Authorization: Bearer $WERYAI_API_KEY` and multipart fields `batch_no`, `fixed`, `file`.
3. Parses the response for a returned **public https** URL and substitutes that URL into the JSON sent to **video** generation — i.e. the video API still only ever sees a **public URL**; the script is just creating one from a local file.

**Implications (local path only)**

- Requires **`WERYAI_API_KEY`** with access to both **video** and **upload-file** endpoints.
- File must exist and be a **regular file**. Extensions with sensible MIME types in script: **`.jpg` / `.jpeg` / `.png` / `.webp` / `.gif`**.
- **`--dry-run`**: does **not** upload; stdout may still show local paths with a note that a real run would upload first.

**Skills / product docs:** prefer telling users to supply a **public `https` link**; use local paths only after **reviewing `video_gen.js`** and **explicit user consent** to read and upload files.

### Multi-image limits in this script

- `images` is passed through after upload resolution; the built request body keeps **at most the first 3** entries (`images.slice(0, 3)`). Models may allow fewer—always check `models` / skill tables (e.g. `upload_image_limit`).

---

## Environment

- **`WERYAI_API_KEY`** (required for `models`, generation, `status`, and local-image upload; **not** required for `--dry-run` only): bearer token. Never commit the value.
- **No other environment variables** are read. Hosts are fixed in `video_gen.js`. The **`wait`** command polls task status with **exponential backoff**: first delay **3s**, then **6s → 12s → 24s → …** (each step doubles), **capped at 60s** between polls, with a **total wall-clock budget of 600s (10 minutes)** before timeout.

## API hosts (fixed)

| Purpose | Base URL |
|---------|-----------|
| Video generation & task status | `https://api.weryai.com` |
| Model registry (`models` command) | `https://api-growth-agent.weryai.com` |
| Local file → public URL (upload) | `https://api-growth-agent.weryai.com/growthai/v1/generation/upload-file` |

---

## `models` — model metadata (JSON on stdout)

```sh
node scripts/video_gen.js models
node scripts/video_gen.js models --mode text_to_video
node scripts/video_gen.js models --mode image_to_video
node scripts/video_gen.js models --mode multi_image_to_video
```

Typical fields include `model_key`, `title`, `durations`, `aspect_ratios`, `resolutions`, `has_generate_audio`, `has_negative_prompt`, `prompt_length_limit`; image modes may add `support_multiple_images`, `support_first_last_frame`, `upload_image_limit`, etc. Use this to author **`## Model and API constraints`** in `SKILL.md`.

---

## `wait` — submit and block until done

```sh
node scripts/video_gen.js wait --json '{"model":"KLING_V3_0_PRO","prompt":"...","duration":5,"aspect_ratio":"9:16"}'
node scripts/video_gen.js wait --json '{"model":"...","prompt":"...","image":"https://cdn.example.com/pet.jpg","duration":5}'
node scripts/video_gen.js wait --json '{"model":"...","prompt":"...","images":["https://cdn.example.com/a.jpg","https://cdn.example.com/b.jpg"],"duration":5}'
# Advanced only: local paths trigger upload-file first; skills should default to public https URLs above.
node scripts/video_gen.js wait --json '{"model":"...","prompt":"...","image":"./girl.png","duration":5}'
node scripts/video_gen.js wait --json '...' --dry-run
```

Same JSON object is used for **`submit-*`**; only the blocking behavior differs.

---

## `submit-*` — submit only (async)

```sh
node scripts/video_gen.js submit-text  --json '{"model":"…","prompt":"…","duration":5}'
node scripts/video_gen.js submit-image --json '{"model":"…","prompt":"…","image":"https://…","duration":5}'
node scripts/video_gen.js submit-multi-image --json '{"model":"…","prompt":"…","images":["https://…","https://…"],"duration":5}'
# Advanced only: local file → upload then submit (same public-URL requirement for the API).
node scripts/video_gen.js submit-image --json '{"model":"…","prompt":"…","image":"./local.png","duration":5}'
```

**Note:** `submit-text`, `submit-image`, and `submit-multi-image` are **aliases** in this script: routing (text vs image vs multi-image) is decided **only** from JSON (`image` / `images`), exactly like `wait`. Use the verb that matches your intent; behavior is the same `submitTask` path aside from blocking.

Poll completion with **`status`** (below). Response JSON may include `batchId` / `taskIds` from the API; this CLI does **not** expose a separate `batch-status` subcommand.

---

## `status`

```sh
node scripts/video_gen.js status --task-id <id>
```

You may also pass a task id inside JSON as `task_id` or `taskId` when using `--json` (less common). If `--task-id` is omitted and JSON has no id, the script errors with `status command requires --task-id <id>`.

There is **no** `batch-status` command in `video_gen.js`; use per-task `status` with each `taskId` you care about. Invalid invocation prints a **Usage** line to stdout JSON (there is no `--help` flag).

---

## Agent UX: in-progress notice, task IDs, backoff polling, completion, skill name

Use this when an executor should **talk to the user** during async generation (not only return raw JSON).

1. **Right after a successful `submit-*`:** Tell the user that generation **has started** and paste **`taskId`** from stdout JSON. If the API returns **`batchId`** and/or **`taskIds`**, include those too so logs and support can correlate the job.

2. **Poll until terminal state:** Run `status --task-id <id>` repeatedly until the payload shows **completed** or **failed** (or your wrapper treats the script’s `phase` / `ok` as terminal). **Recommended delay between polls:** **3s, then 6s, then 12s, …** doubling each time, **cap at 60s**—the same pattern as **`wait`** uses inside `video_gen.js`. Stop early on success/failure; respect your own overall timeout if you need stricter limits than the built-in 10-minute `wait` budget.

3. **`wait` vs `submit-*`:** **`wait`** is one blocking call; it does **not** emit a separate “submitted” line for the user unless **your** layer prints one before/after. For “notify immediately, then poll in the open,” use **`submit-*`** and your own **`status`** loop with the backoff above.

4. **When generation finishes:** Summarize the **outcome** for the user: on success, **playable links** from **`videos`** (e.g. Markdown `[label](url)`); on failure, **`errorCode`** and **`errorMessage`**. **`video_gen.js` does not know which skill was used**—add **skill attribution** yourself from the active skill’s **`SKILL.md`** (e.g. frontmatter **`name`**) or the skill folder slug, in the same user-facing message.

---

## JSON field reference

| Field | Role |
|--------|------|
| `model` | **Required** (non-empty). No default. |
| `prompt` | **Required** for generation commands. |
| `duration` | Sent as number; default **5** if missing/invalid in script—still must be valid for the model. |
| `aspect_ratio` | Optional; model-dependent. |
| `resolution` | Optional; model-dependent. |
| `generate_audio` | Optional boolean; model-dependent. |
| `negative_prompt` | Optional string; model-dependent. |
| `image` | **Image-to-video:** use a **public `https` image URL** (required for normal use). Local path / `file://` is **script-only**: uploads first, then the API receives the returned public URL. |
| `images` | **Multi-image-to-video:** each entry must end up as a **public `https` URL** (directly, or via the script’s upload step). Up to **3** entries are forwarded after preparation. |

Do not send fields the chosen model does not support (reduces spurious `1002` / parameter errors).

---

## Status & stdout conventions

- HTTP OK with business `status === 0` (or `200` in some responses): request accepted for that layer.
- Generation success: look for completed phase and `videos` with `url` / `cover_url`.
- Errors: stdout JSON with `ok: false`, `errorCode`, `errorMessage` (script maps common API codes to short messages).
- Common keys: `ok`, `phase`, `taskId`, `batchId`, `taskIds`, `videos`, `tasks`, `inProgress`, `errorCode`, `errorMessage`.

---

**Reminder:** Skills may require user confirmation before any paid `wait` / `submit-*` call. For **any** image-to-video flow, assume **public `https` image URLs are mandatory** unless the skill explicitly documents a vetted local-upload path—always follow the active skill’s **`SKILL.md`**.


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)