# Vhscli

> Use the `vhscli` CLI to analyze images/video/pdfs with a prompt, or generate images/videos/audio. Use when the user asks about local media, wants AI images/videos/audio, or mentions vhscli, vhs, seedream, seedance, seed-audio, nano-banana, or gpt-image.

- Skill: `getvhs/vhscli` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add getvhs/vhscli`
- Raw SKILL.md: https://api.skillmd.com/api/skills/getvhs/vhscli/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: getvhs (https://skillmd.com/u/getvhs)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/getvhs/vhscli

---


# vhscli

`vhscli` is a command-line tool for multimodal AI: chat about
text/images/video/pdfs, or generate images, videos, and audio from prompts.
It's a thin client — auth, uploads, and model execution all happen
server-side, so users don't store any provider API keys locally.

Run `vhscli --help` or `vhscli <command> --help` to see current help — the CLI
is the source of truth.

## Invocation

Always run via `npx @getvhs/vhscli@latest` so you pick up the newest models,
flags, and fixes. Don't pin a version, and don't call a bare `vhscli` binary
even if one is on PATH — it may be stale.

```
npx @getvhs/vhscli@latest <command> ...
```

Throughout this doc, commands are written as `vhscli ...` for readability —
substitute `npx @getvhs/vhscli@latest ...` when running.

Requires Node.js ≥ 24.

## Top-level

```
vhscli [-v|--version] [-h|--help]
vhscli <command> [options] ...
```

- `-v`, `--version` — print version (only when no command is given)
- `-h`, `--help` — show help (works on root and every subcommand)

Commands:

- `login` — log in with google (opens browser; saves session to `~/.vhs/session.json`)
- `logout` — log out and delete local access tokens
- `whoami [--json]` — print the logged-in user's name and email (local read;
  no network). plain: `Name <email>`; `--json`: one event line
- `account [--json]` — email and balance (hits the backend). plain: email,
  then `balance: $X.XX`
- `models` — list available models
- `generate <model> <prompt> -o <path>` — generate an image, video, or
  audio, wait, and save it (`-o` is required)
- `submit <model> <prompt> -o <path>` — submit the same task as `generate` but
  exit immediately, printing the task id; finish later with `resume`
- `chat <prompt>` — chat with seed-2.0 / `a2:seed-2-pro` (text, image,
  video, or pdf input)
- `resume <task_id> -o <path>` — wait for one task and write its output
- `poll -` — watch task ids from stdin; print the first to finish, then exit
  (writes no files)
- `save <task_id> -o <path>` — write an already-finished task's output
  (never waits)

The CLI stores nothing locally: a task lives in the VHS backend and the task
id is the only handle to it. There is no project, no database, no config —
see "How a task is tracked" below.

`--json` on `generate` / `submit` / `resume` / `save` / `poll` / `whoami` /
`account` puts NDJSON events on stdout and reroutes ordinary logs to stderr
(see "--json" below).

## Auth

Assume auth is already configured. If a command fails with an auth error, run
`vhscli login` to open a browser for Google OAuth. Do NOT run `vhscli login`
preemptively — it requires interactive browser login.

## Models

- **Chat / understand** (text / image / video / pdf): `seed-2.0`
  (`a2:seed-2-pro` under `vhscli chat`)
- **Generate images**: `seedream-5` (default), `seedream-5-pro`,
  `nano-banana-2`, `nano-banana-pro`, `gpt-image-2` — under `vhscli generate`
- **Generate video**: `seedance-2` (up to 4k), `seedance-2.5` (up to 720p) —
  under `vhscli generate`
- **Generate audio**: `seed-audio-1` — under `vhscli generate`

## Prompt guides

Before you invoke `vhscli generate` (or do non-trivial understanding with
`vhscli chat`), **Read the matching prompt guide first** and shape the prompt
around it. The guides are concise, model-specific references distilled from
each provider's docs — formulas, what to lead with, what works, what fails.
Wording that's great for one model often underperforms on another, so don't
skip this.

| Model(s)                           | Guide file (Read before prompting) |
| ---------------------------------- | ---------------------------------- |
| `seed-2.0` (used by `vhscli chat`) | `prompt_guide/seed-2.txt`          |
| `seedream-5`, `seedream-5-pro`     | `prompt_guide/seedream.txt`        |
| `nano-banana-2`, `nano-banana-pro` | `prompt_guide/nano-banana.txt`     |
| `seedance-2`, `seedance-2.5`       | `prompt_guide/seedance-2.txt`      |
| `gpt-image-2`                      | `prompt_guide/gpt-image-2.txt`     |

Trigger: any time the user asks for output from one of these models, Read its
guide before building the prompt. For trivial chat (plain text Q&A with no
media) you can skip `seed-2.txt`.

## Stdin prompts

Every command that takes a prompt also accepts `-` as the prompt, meaning
"read from stdin":

```
cat my_prompt.txt | vhscli generate nano-banana-pro - -o out.png
echo "what is this?" | vhscli chat - -i photo.jpg
```

---

## vhscli chat — chat about text, images, video, or pdfs

```
vhscli chat <prompt> [-i <image>...] [-f <pdf>...] [-v <video>] [--fps <n>]
```

Mode is picked from your flags:

- prompt only → text chat
- `-i` → ask about images (repeatable)
- `-f` → ask about pdf documents (repeatable)
- `-v` → ask about a single video

Options:

- `-i <path>` — image to ask about (repeat `-i` for more)
- `-f <path>` — pdf document to ask about (repeat `-f` for more)
- `-v <path>` — single video to ask about
- `--fps <n>` — frames/sec sampled from the video, 0.2–5 (default: 1)

One-shot — each call is independent, no memory of previous calls. Output goes
to stdout, nothing is saved to disk. Audio inside a video is not understood.

Examples:

```
vhscli chat "explain how to make sourdough in 5 steps"
vhscli chat "describe the scene. return json with objects, setting, mood." -i photo.jpg
vhscli chat "transcribe all visible text verbatim, preserving line breaks." -i receipt.jpg
vhscli chat "compare image 1 and image 2 in 3 bullets." -i a.jpg -i b.jpg
vhscli chat "summarize this paper in 5 bullets; include a page number per bullet." -f paper.pdf
vhscli chat "list key events with start_time and end_time in HH:mm:ss as json." -v clip.mp4 --fps 2
```

---

## Image models — shared `--size`

Every image model takes one flag for the frame:

```
--size <width>x<height>   # default: 1920x1080
```

One value carries aspect ratio and resolution together, and it means the same
frame on every model. Examples: `1920x1080`, `1024x1024`, `1080x1920`,
`3840x2160`.

CLI checks form only: each edge 64–4096. The backend fits your size to the
provider — a frame is never refused for being unrenderable, but what comes
back may differ:

| model | what it does with a size |
| --- | --- |
| `gpt-image-2` | snaps both edges to a multiple of 16, inside 655360–8294400 pixels, max edge 3840, ratio at most 3:1 |
| `seedream-5`, `seedream-5-pro` | scales into 3686400–10404496 pixels — its floor is above 2K, so a small frame is scaled **up** (e.g. `960x540` → ~`2560x1440`) |
| `nano-banana-2`, `nano-banana-pro` | rounds to its nearest aspect ratio and nearest `1K`/`2K`/`4K` tier |

Output format follows the `-o` extension (`.png`, `.jpg`/`.jpeg`, `.webp`);
the CLI converts if needed.

---

## vhscli generate seedream-5 — generate an image (default choice)

```
vhscli generate seedream-5 <prompt> -o <path> [-i <image>...] [--size <WxH>]
```

Options:

- `-o`, `--output <path>` — output file path, required (e.g. `out.jpg`)
- `-i <path>` — reference image, max 14 (repeat `-i` for more)
- `--size <WxH>` — pixel dimensions (default: `1920x1080`); see shared `--size`

Examples:

```
vhscli generate seedream-5 "a red fox in a snowy forest" -o fox.jpg
vhscli generate seedream-5 "swap the outfit" -o out.png -i person.jpg -i outfit.jpg --size 2048x2048
```

---

## vhscli generate seedream-5-pro — generate an image (Seedream 5.0, pro tier)

```
vhscli generate seedream-5-pro <prompt> -o <path> [-i <image>...] [--size <WxH>]
```

Same flags and prompt guide as `seedream-5`, but the pro tier: stronger prompt
adherence and finer detail, at roughly **2 minutes per image** instead of
seconds. Prefer plain `seedream-5` by default; reach for `seedream-5-pro` when
the user asks for the best quality, or when `seedream-5` keeps missing details
in a complex prompt. Prefer a `.png` `-o` (other extensions still work — the
CLI converts).

Examples:

```
vhscli generate seedream-5-pro "a lone lighthouse on a cliff at dusk, long exposure, crashing surf" -o lighthouse.png
vhscli generate seedream-5-pro "add a flock of birds across the sky, keep the style" -i scene.png -o birds.png
```

Because it is slow, it pairs well with `vhscli submit` (below) when generating
several images: submit them all, then `poll` + `save` (or `resume` one at a
time).

---

## vhscli generate nano-banana-2 — generate an image (Google)

```
vhscli generate nano-banana-2 <prompt> -o <path> [-i <image>...] [--size <WxH>]
```

Options:

- `-o`, `--output <path>` — output file path, required (e.g. `out.png`)
- `-i <path>` — reference image, max 14 (repeat `-i` for more)
- `--size <WxH>` — pixel dimensions (default: `1920x1080`); served as the
  nearest of `21:9`/`16:9`/`3:2`/`4:3`/`5:4`/`1:1`/`4:5`/`3:4`/`2:3`/`9:16`
  at the nearest of `1K`/`2K`/`4K`

Examples:

```
vhscli generate nano-banana-2 "remove the man from the photo, keep everything else" -i photo.jpg -o clean.png
vhscli generate nano-banana-2 "90s skateboarder poster, vertical composition" -o poster.png --size 1080x1920
vhscli generate nano-banana-2 "a glossy candle in a bell jar on a marble counter, soft light" -o candle.png
```

---

## vhscli generate nano-banana-pro — generate an image (Google, premium)

```
vhscli generate nano-banana-pro <prompt> -o <path> [-i <image>...] [--size <WxH>]
```

Same sizing and flags as nano-banana-2. Higher-quality sibling — better text
rendering and richer textures.

Examples:

```
vhscli generate nano-banana-pro "studio portrait, cinematic lighting, three-quarter framing" -o portrait.jpg --size 1536x2048
vhscli generate nano-banana-pro "a sun-drenched minimalist living room with a 3d armchair from this sketch" -i sketch.jpg -o room.png
```

---

## vhscli generate gpt-image-2 — generate or edit an image (OpenAI)

```
vhscli generate gpt-image-2 <prompt> -o <path> [-i <image>...] [--size <WxH>]
```

Options:

- `-o`, `--output <path>` — output file path, required (e.g. `out.png`)
- `-i <path>` — reference image for edits (repeat `-i` for more)
- `--size <WxH>` — pixel dimensions (default: `1920x1080`); see shared `--size`

Use png or webp when you need transparency.

Examples:

```
vhscli generate gpt-image-2 "a children's book drawing of a veterinarian examining a cat" -o vet.png
vhscli generate gpt-image-2 "replace the background with a starry night, keep the subject unchanged" -i photo.jpg -o night.png
vhscli generate gpt-image-2 "ultra-wide landscape of the swiss alps at golden hour" --size 3840x2160 -o alps.jpg
```

---

## vhscli generate seedance-2 — generate a video

```
vhscli generate seedance-2 <prompt> -o <path>
                           [--first-frame <image>] [--last-frame <image>]
                           [-i <image>...] [-v <video>...] [-a <audio>...]
                           [--ratio <r>] [--resolution <res>] [--duration <n>]
                           [--no-audio]
```

Mode is picked from your flags:

- prompt only → text-to-video
- `--first-frame` → animate from that frame (optionally `--last-frame` too)
- `-i` / `-v` / `-a` → use as references

Options:

- `-o`, `--output <path>` — output file path, required (`.mp4`, `.webm`, or
  `.mov`; prefer `.mp4`)
- `--first-frame <image>` — use as the first frame
- `--last-frame <image>` — use as the last frame (requires `--first-frame`)
- `-i <path>` — reference image, max 9 (repeat `-i`). conflicts with
  `--first-frame`
- `-v <path>` — reference video, max 3 (repeat `-v`). conflicts with
  `--first-frame`
- `-a <path>` — reference audio, max 3 (repeat `-a`). requires `-i` or `-v`,
  conflicts with `--first-frame`
- `--ratio <r>` — aspect ratio (default: 16:9). one of: `21:9`, `16:9`,
  `4:3`, `1:1`, `3:4`, `9:16`
- `--resolution <res>` — `480p`, `720p`, `1080p`, or `4k` (default: 720p).
  lowercase only
- `--duration <n>` — length in seconds, 4–15 (default: 5)
- `--audio` / `--no-audio` — toggle the audio track (default: `--audio`).
  pass `--no-audio` for a silent video

Defaults to 5s @ 720p, 16:9, with audio. Jobs run in the cloud and can take
minutes — the CLI polls automatically. If you don't want to block, use
`vhscli submit seedance-2 ...` (same flags) to detach immediately, then
`vhscli resume <task_id> -o cat.mp4` later (submit prints the id). A
`vhscli generate` interrupted mid-poll is finished the same way.

Examples:

```
# text-to-video
vhscli generate seedance-2 "a cat jumping off a couch" -o cat.mp4 --duration 6 --ratio 16:9

# animate a still image
vhscli generate seedance-2 "camera pans right" -o pan.mp4 --first-frame start.jpg

# with a first and last frame
vhscli generate seedance-2 "morph between these" -o morph.mp4 --first-frame a.jpg --last-frame b.jpg

# reference-based with audio
vhscli generate seedance-2 "lip sync the words" -o out.mp4 -i face.jpg -a voice.mp3
```

---

## vhscli generate seedance-2.5 — generate a video (capped at 720p)

```
vhscli generate seedance-2.5 <prompt> -o <path>
                             [...same flags as seedance-2 except --resolution]
```

Same model surface as `seedance-2` (ratios, duration, first/last frame, refs,
audio) except `--resolution` accepts only `480p` or `720p` (default: 720p).
For `1080p` / `4k`, use `seedance-2`. Same prompt guide as `seedance-2`.

Examples:

```
vhscli generate seedance-2.5 "a woman in a red dress walks through a rainy neon-lit alley, slow tracking shot" -o alley.mp4
vhscli generate seedance-2.5 "animate this photo: gentle pan to the right" --first-frame photo.jpg -o pan.mp4
```

---

## vhscli generate seed-audio-1 — generate speech audio

```
vhscli generate seed-audio-1 <prompt> -o <path> [-i <audio>...]
```

Options:

- `-o`, `--output <path>` — output file path, required (must be `.mp3`)
- `-i <path>` — reference voice clip for cloning or blending, max 3
  (repeat `-i` for more). each clip should be ≤30s and ≤10MB

Output is always mp3.

Examples:

```
vhscli generate seed-audio-1 "Welcome to VHS." -o welcome.mp3
vhscli generate seed-audio-1 "Read this in the reference voice." -i voice.mp3 -o out.mp3
vhscli generate seed-audio-1 "Blend these voices." -i v1.mp3 -i v2.mp3 -o blend.mp3
```

---

## How a task is tracked — what `generate`, `submit`, `resume`, `poll`, and `save` share

A generation is a row in the VHS backend. The CLI writes no database and no
sidecar files, so **the task id is the only handle to a task** — lose it and
you have stranded something you paid for.

- `generate` submits, waits, and saves to `-o`. It prints the id first, so an
  interrupted run is resumable.
- `submit` prints the id (`task_id: <uuid>`) and exits without waiting. `-o`
  is validated but nothing is written yet.
- `resume <task_id> -o <path>` waits for **one** task and writes its output
  (`poll` + `save` in one step).
- `poll -` watches many ids from stdin and prints the **first** to finish.
- `save <task_id> -o <path>` writes a finished task; errors if still running.

`-o` is required on every command that writes a file. The task knows which
model ran and what was asked, but only you know where the file belongs. The
CLI writes exactly to `-o` — it does not re-home or de-conflict, so check the
path first if you must not overwrite.

Everything is keyed by the task id and idempotent, so re-running a command
that died mid-flight joins the existing task rather than paying twice. Use
`--task-id <uuid>` to choose the id yourself when you need it recorded before
the submit returns.

`vhscli chat` has no task to track — it is fast and prints to stdout.

---

## vhscli submit — submit a task and exit (don't wait)

```
vhscli submit <model> <prompt> -o <path> [...same flags as `vhscli generate <model>`]
```

`submit` takes the **same models and the same options** as `generate`
(`seedance-2`, `seedance-2.5`, `seedream-5`, `seedream-5-pro`,
`nano-banana-2`, `nano-banana-pro`, `gpt-image-2`, `seed-audio-1`). The only
difference is that once the backend has the task it prints `task_id: <uuid>`
and exits without polling. `-o` is checked but nothing is written until you
resume or save.

Use it when:

- The job is long (e.g. seedance video) and you don't want to keep the
  terminal blocked.
- You want to fan out several tasks in parallel and pull results later
  (`poll` + `save`).

**Capture the printed id.** For one task, finish with
`vhscli resume <task_id> -o <path>`. For several, use `poll` + `save` below.
Nothing on this machine remembers the task for you.

`--task-id <uuid>` submits under an id you choose instead of a fresh one, so
you can write the id down *before* the submit returns. Use it when losing the
id would strand a paid-for task: mint a uuid, persist it, then submit. If the
command dies before it prints anything, re-run the identical command — the
backend row and the submit are both keyed by that id and idempotent, so the
re-run joins the same task rather than starting a second one.

Examples:

```
# kick off a video, get the terminal back, finish later
vhscli submit seedance-2 "a robot dancing in tokyo at night" -o robot.mp4
# prints: task_id: 7d3c1b2a-...
# ... do other work ...
vhscli resume 7d3c1b2a-... -o robot.mp4

# fan out several image jobs, then collect as they finish
vhscli submit seedream-5 "a red fox in a snowy forest" -o fox.jpg   # task_id: <id1>
vhscli submit seedream-5 "a blue jay on a branch"      -o jay.jpg   # task_id: <id2>
vhscli submit seedream-5 "an orca breaching"           -o orca.jpg  # task_id: <id3>
# then poll + save (see below) — not a multi-id resume
```

---

## vhscli resume — finish one submitted generation by task id

```
vhscli resume <task_id> -o <path>
```

Takes the id `submit` printed and the path to write. `resume`:

- Looks the task up on the server (fatal if the id is unknown there).
- Recovers which model ran from the task itself, and checks `-o` matches that
  model's output kind before waiting.
- Waits for the task to finish if it is still running.
- Saves the media to `-o`. The extension sets the saved format; the CLI
  converts if needed. It writes exactly there — no re-homing, no ` (N)`.

Safe to re-run: a finished task is re-read from the server, never resubmitted.
One id per call — to fan out, use `poll` + `save`, or run one `resume` per id
in parallel shells.

When to use `resume`:

- You ran `vhscli submit ...` and now want the result for a single task.
- Your `vhscli generate ...` was interrupted (ctrl-c, crash, closed terminal,
  lost network) — it printed the id before it started waiting.

Examples:

```
vhscli resume 7d3c1b2a-... -o fox.png
```

---

## vhscli poll — watch many tasks; print the first to finish

```
vhscli poll -
```

Reads task ids from stdin, one per line, and prints **exactly one** — the
first to finish — then exits. Writes no files and takes no `-o`. Pair with
`vhscli save <task_id> -o <path>`.

stdout is that one id and nothing else (pipes cleanly); human-readable state
goes to stderr. With `--json` it is a single
`{"event":"task","task_id":"...","state":"done"|"error"|"missing"}` line
instead.

Ids may keep arriving while poll runs; each starts being watched as it lands.
One result ends the process, so a driver keeps its **own** list of outstanding
ids, drops the one it was just given, and polls again with the rest —
re-sending anything it never saw reported. Run zero or one poll at a time,
never one parked on nothing.

Polling again immediately is cheap: a task that finished while the last poll
was exiting is answered off a level check, with no waiting. An id naming no
task reports `missing` rather than hanging forever.

Examples:

```
id=$(printf '%s\n' "${ids[@]}" | vhscli poll -) && vhscli save "$id" -o "$id.png"
```

Fan-out loop sketch:

```
left=("${ids[@]}")
while [ ${#left[@]} -gt 0 ]; do
  id="$(printf '%s\n' "${left[@]}" | vhscli poll -)"
  vhscli save "$id" -o "$id.png"
  left=($(printf '%s\n' "${left[@]}" | grep -vFx "$id"))
done
```

---

## vhscli save — write a finished task's output

```
vhscli save <task_id> -o <path>
```

Writes an already-finished task's output to `-o` and exits. It never waits: a
task still running is an error here (`poll` is what waits). Safe to re-run —
the outcome is read back off the task, not regenerated.

Prefer `save` over fetching a result URL yourself: the result envelope differs
per model, the download lands atomically, and the file is converted when the
provider's format differs from your `-o` extension. An errored task fails with
the task's error.

Examples:

```
vhscli save 7d3c1b2a-... -o cat.png
```

---

## Understanding local images, video, and pdfs

**Do NOT use the Read tool, or any built-in file-reading capability, to "look
at" images, video, or pdfs.** That path either fails or gives you a garbled
snippet. The only correct way to understand local visual or document content
is `vhscli chat` with `-i` / `-v` / `-f`.

```
vhscli chat "what's happening?" -i photo.jpg
vhscli chat "transcribe the speech" -v clip.mp4 --fps 2
vhscli chat "summarize this paper" -f paper.pdf
```

### Prompt patterns for visual / document understanding

`vhscli chat` understands images, pdfs, and video frames, but **not** audio
inside videos. Ask for **structured JSON output** when you'll parse the
answer, and **name every field** you want. Be explicit about formats
(timestamp style, units, language).

Image — describe / classify:

```
vhscli chat "describe the scene. return json {objects:[{label,bbox?}], setting, mood, dominant_colors:[]}." -i photo.jpg
vhscli chat "classify the image into one of: cat, dog, bird, other. return json {label, confidence_0_1, reasoning}." -i pic.jpg
```

Image — OCR / text extraction:

```
vhscli chat "transcribe all visible text verbatim, preserving line breaks and reading order. do not paraphrase." -i receipt.jpg
vhscli chat "extract the receipt as json {merchant, date_iso, items:[{name, qty, unit_price, line_total}], subtotal, tax, total, currency}." -i receipt.jpg
```

Image — comparison (number them in the prompt):

```
vhscli chat "compare image 1 and image 2. return json {same_subject:bool, differences:[], which_is_better, why}." -i a.jpg -i b.jpg
vhscli chat "image 1 is the original, image 2 is an edit. list every visible change as json {changes:[{region, before, after}]}." -i orig.png -i edit.png
```

PDF — summarize / outline (always ask for page anchors):

```
vhscli chat "summarize this paper in 5 bullets. each bullet must include the source page as {page:int, point:string}. return json {bullets:[...]}." -f paper.pdf
vhscli chat "extract the outline as json [{page, heading_level, heading, bullets:[]}]." -f doc.pdf
```

PDF — QA / extraction:

```
vhscli chat "answer using only this document. question: what is the experimental setup? return json {answer, citations:[{page, quote}]}." -f paper.pdf
vhscli chat "extract every table as json [{page, title?, headers:[], rows:[[...]]}]." -f report.pdf
```

Video — events / timeline (state the timestamp format):

```
vhscli chat "list key events. return json [{start_time, end_time, event}]. use HH:mm:ss." -v clip.mp4 --fps 2
vhscli chat "describe the movement sequence and any safety risks. return json [{start_time, end_time, event, danger:'none'|'low'|'med'|'high'}]. HH:mm:ss." -v clip.mp4 --fps 3
```

Video — temporal QA / counting:

```
vhscli chat "at what timestamp does the referee first appear? return json {timestamp_hms, evidence}." -v match.mp4 --fps 2
vhscli chat "count how many distinct people appear. return json {count, per_person:[{first_seen_hms, description}]}." -v scene.mp4 --fps 3
```

Choosing `--fps` for video (default 1, range 0.2–5):

- **3–5** — counting actions, sports, fast cuts, dense motion.
- **1** — general description, dialogue scenes.
- **0.2–0.5** — long static footage, headcount, slow surveillance.

Higher fps = more detail but more tokens and slower. Lower fps = cheaper but
may miss brief events.

## Tips

- Always quote prompts.
- `-o` is required for `vhscli generate` / `vhscli submit` / `vhscli resume` /
  `vhscli save`. It's relative to your cwd; output format follows the
  extension and the CLI converts if needed. The CLI writes exactly there and
  will overwrite, so pick a free name yourself if that matters.
- Short options accept no-space form: `-ofoo.jpg`. Long options accept `=`:
  `--size=1920x1080`.
- Use `--` to pass a prompt starting with a dash:
  `vhscli generate seedream-5 -o x.jpg -- "-weird prompt"`.
- Reference images (`-i`, `--first-frame`, `--last-frame`) can be any common
  format; non-JPEG/PNG inputs (e.g. HEIC, WebP, TIFF, BMP) are converted to
  JPEG before upload.
- Reference files are uploaded to temporary cloud storage for the provider to
  fetch, and expire there on their own. Each run re-uploads them, so passing a
  large reference repeatedly costs an upload each time.
- Unknown command? `vhscli` will suggest the closest match.

## --json (scripting)

Add `--json` to `generate`, `submit`, `resume`, `save`, `poll`, `whoami`, or
`account` to get one JSON object per line on stdout while logs go to stderr:

```
{"event":"submitted","task_id":"..."}
{"event":"progress","task_id":"...","elapsed_s":42}
{"event":"task","task_id":"...","state":"done"|"error"|"missing","message":"..."}
{"event":"done","task_id":"...","path":"/abs/out.mp4","hash":"sha256:...","size":123}
{"event":"whoami","user_id":"...","email":"...","name":"..."}
{"event":"account","user_id":"...","email":"...","name":"...","balance_usd":14.04}
{"event":"error","message":"...","code":"auth"|"credit"|"network"}
```

- `submitted` fires as soon as the backend accepts the job — persist the
  task_id and you can `vhscli resume <task_id> -o <path> --json` later, even
  after a crash, from any machine you are logged in on. The id is the whole
  handle; nothing local is needed.
- `task` is what `poll` emits when a watched id reaches an outcome (`done` /
  `error` / `missing`). It does not write a file — follow with `save`.
- `done.path` is the file written (always the `-o` you asked for); `hash` is
  sha256 of its content. Emitted by commands that write (`generate`,
  `resume`, `save`).
- `code` on `error` appears only for actionable failures: `auth` (log in),
  `credit` (top up), `network` (retry may work). No code means the message
  is all there is. A failing command emits `error` and exits 1.

