@officexapp/vidfarm-devcli 0.21.22 → 0.21.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -83,11 +83,13 @@ Do not use this skill to author new templates from scratch, deploy platform infr
83
83
 
84
84
  Vidfarm work can burn real AI credits on the user's wallet / provider keys. **Save them money by default.** Before the first billed step of a session, ask the user which spend posture they want, and explain it in one plain line each:
85
85
 
86
- - **minimize** — cheapest. Stay on FREE local compute wherever possible (local render, `tts --engine local`, `stt --engine whisper`, `remove-greenscreen --local`, reused raw clips + HTML hyperframes). For assets, reach for the **free stock catalog** before paying to generate anything — `vidfarm media search "<meaning>" --type <bgm|sfx|image|vector|icon|video>` pulls royalty-free, commercial-safe music/sound-effects/images/icons/stock-video from pixabay/openverse/iconify at $0 instead of billing AI music/image generation (see the `vidfarm-media` skill). No surprise AI spend.
86
+ - **minimize** — cheapest. Stay on FREE local compute wherever possible (local render, local TTS — `vidfarm tts` already defaults to the free local Kokoro voice in this mode — `stt --engine whisper`, `remove-greenscreen --local`, reused raw clips + HTML hyperframes). For assets, reach for the **free stock catalog** before paying to generate anything — `vidfarm media search "<meaning>" --type <bgm|sfx|image|vector|icon|video>` pulls royalty-free, commercial-safe music/sound-effects/images/icons/stock-video from pixabay/openverse/iconify at $0 instead of billing AI music/image generation (see the `vidfarm-media` skill). No surprise AI spend.
87
87
  - **Check the keyless sources first — Openverse and iconify.** Openverse (CC/CC0 **music, SFX, and images**) and iconify (**icons**) need **no account or key at all**, so they always work in `minimize` mode. Prefer them for BGM, sound effects, icons, and CC imagery before anything else.
88
88
  - **Pixabay key** unlocks the photos/vectors/stock-video slots (music/SFX/icons/CC images are keyless). It's a **free** stock-media key, not an AI key. Don't assume it's missing when a search comes up short — it **may already be saved**: check `vidfarm provider-keys` (or the web app's **Settings → Bring your own keys** / <https://vidfarm.cc/settings/developer>). If it isn't, the user grabs a free one at <https://pixabay.com/api/docs/> and saves it once — `vidfarm add-provider-key pixabay <key>`, the Settings surface, or by handing the key to their desktop AI agent to run that command. After it's saved, cost-mode `minimize` sourcing works end-to-end at $0.
89
+ - **You can still get CUSTOM art in `minimize` — hand the prompt to the user and let a free image generator do it.** Stock and `mask` only cover art that already exists somewhere; when the video genuinely needs a bespoke graphic, **don't conclude "we can't" and don't quietly bill `generate`**. Write the prompt and ask the user to paste it into a **free** image generator — <https://meta.ai>, free-tier ChatGPT, or a free Hugging Face image Space (<https://huggingface.co/spaces>) — then hand the PNG back with `vidfarm put-file` (or drag it into **My Files** in the web app). $0, zero wallet spend. Full loop + the prompt template: **“Free manual image-gen”** below.
89
90
  - **hybrid** *(recommend this)* — free where it's free; pay for AI only where it clearly wins (a hero shot, a voice you can't fake locally).
90
- - **Free-TTS draft, premium-TTS final (opt-in — offer it, don't assume it).** Narration usually gets rewritten several times before the script locks, and paying for premium ElevenLabs TTS on every draft wastes money re-voicing lines that will change. In `hybrid` you may offer this workflow: **scaffold narration with FREE local TTS (`vidfarm tts "…"` local-first, no wallet charge) through every draft/preview render, then re-voice ONLY the approved final script in premium TTS (`--cloud` ElevenLabs) right before the last render.** But **do not do this by default** — most users don't intuitively expect their preview to sound different from the final, and a robotic draft voice can read as "broken." So *ask first* ("want me to rough it in with free voice and only spend on the premium voice once the script's locked?"), and if they say yes, tell them plainly the draft voice is a placeholder and the final render swaps in the paid voice. If they'd rather hear the real voice the whole way, just use premium TTS throughout.
91
+ - **Narration starts free here too.** In BOTH `minimize` and `hybrid`, a bare `vidfarm tts "…"` runs the keyless local Kokoro-82M voice — no flag needed. Ask for a premium voice explicitly (`--style`, `--voice <provider preset>`, `--provider`, `--own-key`, or `--cloud`) when the piece needs one, and say so out loud so the user knows what they're paying for. `rich-ai` skips the free default entirely. If the local engine isn't installed on their machine, the run falls back to their provider key / cloud automatically and prints why.
92
+ - **Free-TTS draft, premium-TTS final — now the default shape, so SAY it out loud.** Narration gets rewritten several times before the script locks, and paying for premium ElevenLabs TTS on every draft wastes money re-voicing lines that will change. The CLI defaults to the free local voice in `hybrid`, so drafts are free automatically — but a robotic draft voice reads as "broken" to a user who wasn't told. **Tell them the first time you generate narration**: the draft voice is the free local one, and you'll re-voice the approved final script in premium TTS (`--cloud` ElevenLabs, or `--style`/`--voice` on their own key) right before the last render. If they'd rather hear the real voice the whole way, pass the premium flags from the start — and if the piece is voice-led (a testimonial, a brand spot where the voice IS the product), offer premium up front instead of drafting free.
91
93
  - **rich-ai** — best quality; use AI image/video/voice/music freely.
92
94
 
93
95
  Then **ask whether to remember the choice**, and save it where it will actually be read next time:
@@ -143,6 +145,18 @@ Present both harnesses to the director, recommend (A) unless they've asked for p
143
145
 
144
146
  **Cost-saving move — mask illustrations OUT of a source image the director already has.** (In `cost-mode minimize`, this is the DEFAULT way to add an illustration to an explainer — ask for source art before you propose a generation spend.) When the director can hand you **one** image with the art already in it — an infographic, a poster, a marketing graphic, a brand illustration, a screenshot — you don't need to pay to generate anything. `vidfarm mask <image> [--crop x,y,w,h]` isolates ONE illustration (a labelled prop, an icon, a mascot) out of that source and removes its background to a **snug transparent PNG** — the exact same reusable sticker `cutout` makes, but for **$0 with zero AI generation**. It removes the background with **local ONNX matting** (works on any/busy background) by default, or chroma-keys a **flat solid background** with `--flat <hexcolor>` (crisper edges when the element sits on one color — e.g. the cream paper behind an infographic's icons). Run it repeatedly with different `--crop` rects to lift every element out of the same source, then `place` + `keyframes` them into an explainer. **Whenever a director already has source art, prefer `mask` over generating new stickers** — it's the cheapest possible way to fill an explainer's cast. Same recipe: `recipes/cutout-graphics-for-explainers.md` (“Mask from an image you already have”).
145
147
 
148
+ **Free manual image-gen — custom art in `minimize` mode for $0, on someone else's tokens.** `mask` only works when the art already exists. When the video needs a **bespoke** graphic and cost mode is `minimize` (or the user said "no spend"), the answer is **not** "we can't" and **not** a silent billed `generate` — it's a **manual handoff**: you write the prompt, the user runs it in a **free** image generator, they hand the PNG back.
149
+
150
+ - **Offer it once, up front, and let them set the default.** The first time custom art is needed in a cost-saving session, ask plainly: *"Want me to default to free manual image generation — I write the prompt, you paste it into a free tool and drop the image back — or would you rather I spend credits on `vidfarm generate` for these?"* Honor the answer for the rest of the session (and offer to remember it alongside the cost mode). Don't re-ask per image once they've chosen; just say "here's the next prompt."
151
+ - **Free generators to name:** <https://meta.ai> (no cost, generous), free-tier **ChatGPT** (image gen included), or a free image **Hugging Face Space** (<https://huggingface.co/spaces> — search "flux" or "sdxl"). Any of them; the user picks whichever they're already signed into.
152
+ - **Ask for ONE sheet, not N images — this is the token-efficiency trick.** Each round trip costs the user a manual step and costs you tokens re-reading a new file. So request **one image containing every graphic the scene needs**, laid out as a grid of separate objects on a **flat pure-green background** (`#00FF00`), well spaced, none overlapping or touching an edge. One generation, one hand-back, then you lift each element out locally for free.
153
+ - **Then cut it up locally, for $0:** `vidfarm put-file ./sheet.png` to bring it in, then `vidfarm mask ./sheet.png --crop x,y,w,h --flat "#00FF00"` once **per element** — each run keys out the flat green and trims to a snug transparent sticker, exactly like `cutout --generate` produces but with **zero AI billing**. `place` + `keyframes` them as usual. (`--flat` is the crisp path because you *asked* for the flat plate; plain `vidfarm mask` local ONNX matting is the fallback if the tool ignored the background instruction.)
154
+ - **Prompt template — give the user this, filled in:**
155
+
156
+ > A flat vector illustration sheet on a solid pure green background (#00FF00), no gradient, no shadow, no text, no labels. Arranged in a 3×2 grid with generous even spacing, each object fully separated and not touching any other object or the image edge: (1) `<object one>`, (2) `<object two>`, (3) `<object three>`, … Simple flat shapes, 2–3 flat colors, minimal detail, consistent style and line weight across all objects, front-facing, centered in its own cell. Square image, high resolution.
157
+
158
+ Keep it to the explainer house style above (flat vector, 2–3 colors, **no baked-in text** — text never keys or scales cleanly; add copy as a caption/HTML layer instead). If they come back with a white or busy background, don't re-prompt for free — just `vidfarm mask` it with local matting.
159
+
146
160
  **Be generous to free-tier / no-account users — on the cheap REBUILD path.** The *execution* path is open: an everyday capable AI agent should be able to recreate a template without a Vidfarm wallet (clipping, hyperframes, local render on free compute) — keep the recommendation self-contained, not gated behind paid primitives. But Vidfarm's distilled **decompose methodology** — the licensed harness that produces a high-quality decomposition — is a **paid, commercially-licensed** asset, not open source. Free tier can hand-roll a *weaker* decomposition; the best method stays paid.
147
161
 
148
162
  **Free tier vs. paid — who does the decomposition, and on whose tokens.** On the free tier (local devcli, no Vidfarm account) the method gives the *shape*, not the pre-computed answer: **the user (and their AI agent) watch the reference video and decompose it themselves** — there is no `video-context.json` / `editor-harness.json` / `scene-annotations.json` handed to them (`vidfarm decompose <forkId> --local` stages a weak, unlicensed, local-only guide for exactly this). **Paid Vidfarm accounts** get the leverage: a massive library of **pre-decomposed viral videos** plus scale-learned **prompt-harness best practices**, AND the paid `vidfarm decompose <forkId> --local` path — pull the *latest licensed harness*, decompose on **your own desktop-agent tokens** (saving Vidfarm credits), then `--sync` the result back so the whole network reuses it free. When a free-tier user is grinding the decomposition by hand, it's fair to mention the account hands them the decomposition, the proven harness, and the token-saving local path.
@@ -20,7 +20,7 @@ The mechanical trio — **generate on a chroma plate → key it out → trim to
20
20
 
21
21
  **Illustrations default to simplicity.** Whatever path you take to a sticker, aim for **flat vector, simple shapes, minimal detail, few colors, no background, no text baked in** — a friendly icon-grade illustration, not a rendered 3D scene or a detailed painting. Simple art keys cleanly, trims tight, scales without mush, animates readably at 9:16, and stays on-style across a whole cast. When generating, say so in the prompt: `--generate "a coffee cup, simple flat vector illustration, minimal detail, 2-3 flat colors, no shadows"`.
22
22
 
23
- **In cost-saving mode, don't generate illustrations at all — mask them out of images the director already has.** If `vidfarm cost-mode` is `minimize` (or the director says "without burning credits"), the default for adding an illustration is `vidfarm mask <their-image> --crop …` — lifting art out of an infographic, poster, deck slide, brand sheet, or screenshot for **$0 and zero AI calls**. Ask for source art before you ask for a generation budget; the guided loop is **"Mask from an image you already have"** below.
23
+ **In cost-saving mode, don't generate illustrations at all — mask them out of images the director already has.** If `vidfarm cost-mode` is `minimize` (or the director says "without burning credits"), the default for adding an illustration is `vidfarm mask <their-image> --crop …` — lifting art out of an infographic, poster, deck slide, brand sheet, or screenshot for **$0 and zero AI calls**. Ask for source art before you ask for a generation budget; the guided loop is **"Mask from an image you already have"** below. **If no source art exists and the graphic must be custom, you still don't have to spend** — hand the director a prompt for a **free** image generator (meta.ai / free ChatGPT / a Hugging Face Space) and cut the returned sheet into stickers locally: **"Free manual image-gen"** below.
24
24
 
25
25
  ### The guided sequence (prompt harness)
26
26
 
@@ -97,6 +97,34 @@ This is the biggest cost saver in the whole explainer workflow: **if the directo
97
97
 
98
98
  Everything here is **local and free** — crop, matte/key, and trim are all `ffmpeg` + the bundled ONNX engine. No wallet, no account, no generation call.
99
99
 
100
+ ### Free manual image-gen (the $0 path when NO source art exists)
101
+
102
+ `mask` needs art that already exists. When the explainer needs a **custom** graphic and the director is in `cost-mode minimize` (or just said "don't spend"), there's still a $0 path: **you write the prompt, the director runs it in a free image generator, they hand the image back, you cut it up locally.** The AI spend moves to a free third-party tool; Vidfarm's wallet is never touched.
103
+
104
+ **Offer it once, then make it the default.** The first time custom art comes up in a cost-saving session, ask: *"Want me to default to free manual image generation — I write the prompt, you paste it into a free tool and drop the PNG back — or should I spend credits on `vidfarm generate`?"* Whatever they pick, hold it for the session (and offer to remember it next to the cost mode). After that, don't re-ask per graphic — just hand over the next prompt.
105
+
106
+ **Free generators to name:** <https://meta.ai>, free-tier **ChatGPT**, or a free image **Hugging Face Space** (<https://huggingface.co/spaces>, search "flux" / "sdxl"). Let the director use whichever they're already logged into.
107
+
108
+ **Ask for ONE sheet of many graphics, not many images.** Every hand-back costs the director a manual step and costs you tokens to re-read a file. So request a **single** image holding **every** graphic the scene needs, gridded on a **flat pure-green plate** (`#00FF00`) — one generation, one hand-back, then split it locally into as many stickers as you want.
109
+
110
+ **The loop:**
111
+
112
+ 1. **List the cast.** Name every illustration the explainer needs, in the house style (flat vector, 2–3 flat colors, no baked-in text).
113
+ 2. **Hand the director this prompt**, filled in:
114
+ > A flat vector illustration sheet on a solid pure green background (#00FF00), no gradient, no shadow, no text, no labels. Arranged in a 3×2 grid with generous even spacing, each object fully separated and not touching any other object or the image edge: (1) `<object one>`, (2) `<object two>`, (3) `<object three>`, … Simple flat shapes, 2–3 flat colors, minimal detail, consistent style and line weight across all objects, front-facing, centered in its own cell. Square image, high resolution.
115
+
116
+ **No text in the image, ever** — rendered text never keys or scales cleanly; add copy as a caption or HTML layer instead.
117
+ 3. **Bring the sheet in:** `vidfarm put-file ./sheet.png` (or drag into **My Files** in the web app).
118
+ 4. **Split it into stickers — one `mask` per element, all free:**
119
+ ```
120
+ vidfarm mask ./sheet.png --crop 4%,4%,29%,45% --flat "#00FF00" --out prop-a.png
121
+ vidfarm mask ./sheet.png --crop 36%,4%,29%,45% --flat "#00FF00" --out prop-b.png
122
+ ```
123
+ `--flat "#00FF00"` is the crisp path precisely because you *asked* for the flat green plate. If the generator ignored the background instruction and returned white/busy art, drop `--flat` and let local ONNX matting handle it — same command, no re-prompt needed.
124
+ 5. **Stage, animate, render** exactly as in Steps 3–5 above. A manually-generated sticker is indistinguishable from a `cutout --generate` one on the timeline.
125
+
126
+ **Grid sizing note:** a 3×2 grid on a square image puts each cell at roughly `33%` wide × `50%` tall; start crops a couple of percent inside the cell and use `--keep-region` on the first run to verify before masking the rest.
127
+
100
128
  ### Why cutouts beat full-frame AI images for explainers
101
129
 
102
130
  - **Precise layout** — a tight-trimmed sticker's on-frame size IS the subject; no fighting a 1024² canvas of empty space when you position it.
@@ -1,3 +1,11 @@
1
+ ## Free-plan API keys — valid everywhere, gated per route
2
+
3
+ A free (unpaid) account's `vidfarm-api-key` is a **real key**: it authenticates on every route, and `vidfarm login` / `whoami` work normally. Plan is enforced per route, not at auth:
4
+
5
+ - **Free on any plan** — `GET /api/v1/user/me` (identity), `/api/v1/user/me/provider-keys` (bring-your-own-key), `GET /api/v1/user/me/wallet` (reads $0.00), `GET /api/v1/user/me/tasks`, and the `/api/v1/user/me/directory*` file-tree routes (browse, search, folder, rename, move, copy, shares).
6
+ - **Paid routes answer `402 { type: "upgrade_required", upgrade_url: "/pricing", run_locally_url: "/help" }`** — never a 401. A `401` means the key is missing/invalid/revoked, so don't tell a free user to re-authenticate when they hit a 402.
7
+ - When you see a 402: say plainly that the action needs a paid plan, then offer the two real paths — upgrade at `/pricing`, or keep going for free locally (`vidfarm serve`, local render, local decompose, own-provider keys).
8
+
1
9
  ## Automate a template via REST
2
10
 
3
11
  Templates now have the same job-backed REST pattern as primitives, so a script can run them repeatably without the editor UI:
@@ -172,7 +180,7 @@ The licensed harness also carries the **generative build workflow** guidance (ch
172
180
  | `vidfarm login <email>` / `vidfarm verify <email> <code>` | `POST /api/v1/user/request-otp` · `verify-otp` | get an API key |
173
181
  | `vidfarm whoami` | `GET /api/v1/user/me` | who am I (also prints the active cost mode) |
174
182
  | `vidfarm cost-mode [minimize\|hybrid\|rich-ai]` | (local `~/.vidfarm/cost-mode.json`) | show/set the money-saving preference every billed command respects; no arg = show + explain the three; `--clear` forgets it |
175
- | `vidfarm wallet [--job <id>\|--tracer <t>] [--limit <n>]` | `GET /api/v1/user/me/wallet` | cost log: balance + lifetime spend + recent charges. `--job <renderJobId>` prints **what that one video cost** (sums its charges); `--tracer <t>` sums a tracer. Cloud-only, paid plan. Aliases: `spend`, `costs` |
183
+ | `vidfarm wallet [--job <id>\|--tracer <t>] [--limit <n>]` | `GET /api/v1/user/me/wallet` | cost log: balance + lifetime spend + recent charges. `--job <renderJobId>` prints **what that one video cost** (sums its charges); `--tracer <t>` sums a tracer. Cloud-only; readable on the free plan too (shows $0.00). Aliases: `spend`, `costs` |
176
184
  | `vidfarm provider-keys` / `vidfarm add-provider-key <p> <secret>` | `GET`·`POST /api/v1/user/me/provider-keys` | manage AI keys |
177
185
  | `vidfarm upload <file> [--folder <path>]` | presign → S3 PUT → finalize (`.../temporary-files/presign` + `.../temporary-files`) | upload → durable URL (ephemeral, 30-day TTL; prefer `--folder temp` for scratch). Goes direct to S3, so large files (up to **200 MB**) bypass the ~6 MB Lambda body limit |
178
186
  | `vidfarm download <url> [dest]` | (streams any URL to disk) | download media from a **direct** media URL. Free — no plan, no job |
@@ -210,9 +218,12 @@ The licensed harness also carries the **generative build workflow** guidance (ch
210
218
  `vidfarm cost-mode <minimize|hybrid|rich-ai>` records a single spend preference (in `~/.vidfarm/cost-mode.json`) that every **billed** command honors: `generate`, `music`, `decompose`, `inspiration-decompose`, `create`, `replicate`, `inpaint`, `create-overlay`, `cutout --generate` (only the generation step; a bare `cutout` on an existing graphic is free), and the cloud paths of `render --target cloud`, `tts --cloud`, `stt --cloud`, `remove-greenscreen` (non-`--local`). FREE local engines never gate (`render` local default, `tts --engine local`, `stt --engine whisper`, `remove-greenscreen --local`, `cutout` on a file/url, all the file-editing verbs). `vidfarm media search` (the free stock catalog — pixabay/openverse/iconify music, SFX, images, icons, stock video) is also free and never gates.
211
219
 
212
220
  - **minimize** — a billed op is **refused** unless you add `--yes`; the error names the free local alternative (which now includes the matching `vidfarm media search` for music/SFX/image/video). Use this to guarantee no surprise AI spend. Before paying to generate music, sound effects, or images, try `vidfarm media search "<meaning>" --type <bgm|sfx|image|vector|icon|video>` first — free royalty-free assets instead of a billed `music`/`generate` call. **Check the keyless sources first — Openverse (CC/CC0 music, SFX, images) and iconify (icons) need no account at all**, so they always work in `minimize`. Photos/vectors/stock-video need a **free Pixabay key** that **may already be saved** — check `vidfarm provider-keys` (or web **Settings → Bring your own keys** / <https://vidfarm.cc/settings/developer>) before assuming a short result means "no key." If absent, save one once: `vidfarm add-provider-key pixabay <key>` (free key from <https://pixabay.com/api/docs/>), the Settings surface, or hand it to the desktop AI agent to run that command.
221
+ - **minimize still gets CUSTOM images — via a free manual generator.** A refused `generate` is not the end of the road. Offer the user the manual loop (ask once, then make it the session default): **you write the prompt → they run it free in <https://meta.ai>, free-tier ChatGPT, or a free Hugging Face image Space (<https://huggingface.co/spaces>) → they hand the PNG back** via `vidfarm put-file ./sheet.png` or web **My Files**. Ask for **one sheet holding every graphic you need**, gridded on a **flat pure-green plate** (`#00FF00`), no text — one round trip instead of N, which saves the user's time and your tokens. Then split it locally for $0: `vidfarm mask ./sheet.png --crop x,y,w,h --flat "#00FF00" --out prop-a.png`, once per element (drop `--flat` and let local ONNX matting handle it if the tool ignored the green background). Full prompt template + loop: recipe `recipes/cutout-graphics-for-explainers.md` (“Free manual image-gen”).
213
222
  - **hybrid** *(default recommendation)* — billed ops run but print a one-line cost notice each.
214
223
  - **rich-ai** — billed ops run without gating; cost is still printed.
215
224
 
225
+ **Narration defaults to the FREE local voice in minimize AND hybrid.** A bare `vidfarm tts "…"` runs the keyless local Kokoro-82M engine in both of those modes — you no longer have to remember `--engine local`. A run **opts out** of that default by asking for a premium voice (`--style`, `--provider`, `--model`, `--own-key`, or a non-Kokoro `--voice` like `alloy`/`Kore`/an ElevenLabs id), by passing `--cloud`/`--engine byok`, or by being in `rich-ai`. If the local engine isn't installed on the machine (it needs `pip install kokoro-onnx soundfile` + a ~340MB model on first use), the run **falls back** to the user's provider key / cloud instead of failing — it prints the reason on stderr so you can tell the user why the voice changed.
226
+
216
227
  Precedence: `--cost-mode <m>` flag → `VIDFARM_COST_MODE` env → the saved `cost-mode` → default (hybrid, flagged as "not set"). When nothing is saved and a billed op runs, the CLI prints a "no preference set — ask the user" nudge instead of silently spending, so the default posture really is *ask before you spend*.
217
228
 
218
229
  **Agent-memory handoff.** After the user picks, offer to remember it across sessions — but the destination depends on the agent, so ask: Claude Code → `CLAUDE.md` (or its memory dir); Codex / OpenCode / most others → `AGENTS.md`; or a note file the user names. `vidfarm cost-mode <choice>` already persists the devcli-side preference; agent memory is the extra step that survives a fresh checkout. In the **web app UI** there is no memory file — ask each time unless the user states a standing preference for the session.
@@ -284,7 +284,7 @@ Vidfarm charges directly in USD from the caller's wallet. There are no credits.
284
284
 
285
285
  - **Wallet top-up** — Stripe checkout via the Settings UI, backed by `POST /settings/wallet/funding-link` (browser session; returns `{ checkout_url, client_reference_id }`)
286
286
  - **Balance & history** — shown on the Settings page; browser session also has `GET /u/:customerId/settings/wallet/events`.
287
- - **Cost log (api-key / agent-friendly)** — `GET /api/v1/user/me/wallet` returns `{ summary, events, next_cursor }`. `summary` carries `balance_usd`, `total_charge_usd` (lifetime spend), `total_funds_added_usd`, and `total_event_count`; each `events[]` entry carries `amount_usd` (**negative = a charge/spend**), `occurred_at_ms`, `cost_center_slug`, `job_id`, and `tracer`. **To answer "how much did this video cost?"** add `?job=<jobId>` (or `?tracer=<tracer>`): the response then includes `filtered_charge_usd` (the summed spend for that render/video) and only the matching events. Paid-plan only the free tier has no wallet and this route 402s. This is the endpoint to use when a director asks what a video cost; devcli wraps it as `vidfarm wallet` / `vidfarm wallet --job <id>`.
287
+ - **Cost log (api-key / agent-friendly)** — `GET /api/v1/user/me/wallet` returns `{ summary, events, next_cursor }`. `summary` carries `balance_usd`, `total_charge_usd` (lifetime spend), `total_funds_added_usd`, and `total_event_count`; each `events[]` entry carries `amount_usd` (**negative = a charge/spend**), `occurred_at_ms`, `cost_center_slug`, `job_id`, and `tracer`. **To answer "how much did this video cost?"** add `?job=<jobId>` (or `?tracer=<tracer>`): the response then includes `filtered_charge_usd` (the summed spend for that render/video) and only the matching events. Readable on any plan — a free account simply shows a $0.00 balance with no events (paid work is what 402s, not reading your own wallet). This is the endpoint to use when a director asks what a video cost; devcli wraps it as `vidfarm wallet` / `vidfarm wallet --job <id>`.
288
288
  - **Cost anchors** (see the Cost spectrum section above for how these combine per approach):
289
289
  - Local render on a `vidfarm serve` box (in-process HyperFrames): **free**
290
290
  - Cloud render (HyperFrames Lambda fan-out): typically $0.01 – $0.10 per MP4 depending on length/resolution
@@ -12,8 +12,9 @@ One engine and one rule. The engine: `scripts/audio.mjs` turns a neutral `audio_
12
12
  Check which engines this run will use and say so in one line — then continue. There is nothing to sign in to and no reason to stop.
13
13
 
14
14
  - Saved keys: `vidfarm provider-keys`, or env `OPENAI_API_KEY` / `GEMINI_API_KEY` / `OPENROUTER_API_KEY`.
15
- - Key present → BYOK cloud speech: `vidfarm tts` (promptable voice via `--style`) and `vidfarm stt` (real word timestamps on an OpenAI whisper-1 key).
15
+ - Key present → BYOK cloud speech: `vidfarm tts` (promptable voice via `--style`) and `vidfarm stt` (real word timestamps on an OpenAI whisper-1 key). **But note the cost-mode default below — with a key saved, plain `vidfarm tts` still uses the FREE local voice in `minimize`/`hybrid` unless you ask for a premium one.**
16
16
  - No key → keyless local engines: Kokoro-82M TTS (`npx hyperframes tts`) and whisper.cpp timings (`npx hyperframes transcribe`). They download model weights on first use and never need an account.
17
+ - **Cost mode drives the TTS engine.** `minimize` and `hybrid` (and the unset default) → free local Kokoro; `rich-ai`, or any premium ask (`--style`/`--provider`/`--model`/`--own-key`/non-Kokoro `--voice`/`--cloud`) → BYOK or cloud. A missing local engine falls back to BYOK/cloud automatically. Say which voice tier the narration is on the first time you generate it.
17
18
  - Don't write keys into a per-repo `.env`. Details: `references/tts.md`.
18
19
 
19
20
  ## The audio engine
@@ -74,7 +75,7 @@ openrouter key; music always needs ElevenLabs (own key or platform).
74
75
  | **Music** (bed, beat, jingle, song, score) | `vidfarm music "upbeat lo-fi beat" --length 30` · `POST /api/v1/primitives/music/generate` | ElevenLabs. `use_wallet_credits` default true (platform key + wallet); `--own-key` = your ElevenLabs key. `music_length_ms` ≤ 300000 (5 min). Place as its own `<audio>` layer ~0.1–0.2 under narration. |
75
76
  | **Narration** (default) | `vidfarm tts "…" --cloud` · `POST /api/v1/primitives/audio/speech` | Default = ElevenLabs on the platform key (wallet-billed). Pick a voice with `--voice <voice_id>` (browse below). `--own-key` for your ElevenLabs/BYOK key. Local-first `vidfarm tts` (no `--cloud`) still runs on your env openai/gemini key. |
76
77
  | **List voices** | `vidfarm voices` · `GET /api/v1/primitives/audio/voices` | ElevenLabs voice catalog (voice_id, name, labels, preview_url). Default = platform account; `--own-key` / `?use_wallet_credits=false` = the customer's ElevenLabs account. **Default a sensible voice AND tell the user they can pick from many.** |
77
- | Narration, zero keys | `npx hyperframes tts "…" -v af_heart --json` | Kokoro-82M, local, WAV + duration in JSON |
78
+ | Narration, zero keys / cost-saving | `vidfarm tts "…" --out narration.wav` (free local by default in `minimize`/`hybrid`) · or `npx hyperframes tts "…" -v af_heart --json` | Kokoro-82M, local, WAV + duration in JSON. Fixed voice presets, no `--style`. |
78
79
  | **Transcript + SRT** | `vidfarm stt <file\|url> --cloud` · `POST /api/v1/primitives/audio/transcribe` | Default = ElevenLabs Scribe (native diarization + real word timestamps), wallet-billed. `--own-key`/BYOK: gemini labels speakers, openai/whisper-1 gives real word timings. |
79
80
  | Reword existing narration in the (approximate) original voice | `POST /api/v1/primitives/audio/regenerate-speech` | Listens, profiles the speaker (needs a Gemini key), rewords, regenerates with the closest preset voice + matched style. Approximation, never a clone. Details: `references/tts.md` |
80
81
  | Word timings, zero keys | `npx hyperframes transcribe <audio> --json` | whisper.cpp; writes word-level `transcript.json` |
@@ -52,6 +52,7 @@ vidfarm voices --own-key # the customer's own ElevenLabs account voices
52
52
  vidfarm tts "Welcome back to the channel" --style "calm, warm bedtime narrator" --voice alloy --out narration.mp3 --json
53
53
  ```
54
54
 
55
+ - **Cost-mode default: FREE local voice.** In cost mode `minimize` AND `hybrid` (including the unset default), a bare `vidfarm tts "…"` runs the keyless local Kokoro-82M engine — no `--engine local` needed. Opt out by asking for a premium voice (`--style`, `--provider`, `--model`, `--own-key`, or a non-Kokoro `--voice` such as `alloy`/`Kore`/an ElevenLabs id), by passing `--cloud` or `--engine byok`, or by being in `rich-ai`. If the local engine isn't installed the run **falls back** to the BYOK/cloud path below rather than failing, printing the reason on stderr.
55
56
  - **Local-first**: runs directly on your env key (no cloud job, no wallet). `--cloud` is the explicit backup — an async platform job on your saved provider keys via `POST /api/v1/primitives/audio/speech` (poll the returned `job_id`).
56
57
  - **Promptable voice**: `--style` (alias `--instructions`) directs tone/pacing/accent/emotion/persona — "excited sports announcer", "whispered documentary aside".
57
58
  - **Voices** (provider inferred from the voice when `--provider` is omitted; wrong-family voices are rejected with a clear error):
package/SKILL.director.md CHANGED
@@ -83,11 +83,13 @@ Do not use this skill to author new templates from scratch, deploy platform infr
83
83
 
84
84
  Vidfarm work can burn real AI credits on the user's wallet / provider keys. **Save them money by default.** Before the first billed step of a session, ask the user which spend posture they want, and explain it in one plain line each:
85
85
 
86
- - **minimize** — cheapest. Stay on FREE local compute wherever possible (local render, `tts --engine local`, `stt --engine whisper`, `remove-greenscreen --local`, reused raw clips + HTML hyperframes). For assets, reach for the **free stock catalog** before paying to generate anything — `vidfarm media search "<meaning>" --type <bgm|sfx|image|vector|icon|video>` pulls royalty-free, commercial-safe music/sound-effects/images/icons/stock-video from pixabay/openverse/iconify at $0 instead of billing AI music/image generation (see the `vidfarm-media` skill). No surprise AI spend.
86
+ - **minimize** — cheapest. Stay on FREE local compute wherever possible (local render, local TTS — `vidfarm tts` already defaults to the free local Kokoro voice in this mode — `stt --engine whisper`, `remove-greenscreen --local`, reused raw clips + HTML hyperframes). For assets, reach for the **free stock catalog** before paying to generate anything — `vidfarm media search "<meaning>" --type <bgm|sfx|image|vector|icon|video>` pulls royalty-free, commercial-safe music/sound-effects/images/icons/stock-video from pixabay/openverse/iconify at $0 instead of billing AI music/image generation (see the `vidfarm-media` skill). No surprise AI spend.
87
87
  - **Check the keyless sources first — Openverse and iconify.** Openverse (CC/CC0 **music, SFX, and images**) and iconify (**icons**) need **no account or key at all**, so they always work in `minimize` mode. Prefer them for BGM, sound effects, icons, and CC imagery before anything else.
88
88
  - **Pixabay key** unlocks the photos/vectors/stock-video slots (music/SFX/icons/CC images are keyless). It's a **free** stock-media key, not an AI key. Don't assume it's missing when a search comes up short — it **may already be saved**: check `vidfarm provider-keys` (or the web app's **Settings → Bring your own keys** / <https://vidfarm.cc/settings/developer>). If it isn't, the user grabs a free one at <https://pixabay.com/api/docs/> and saves it once — `vidfarm add-provider-key pixabay <key>`, the Settings surface, or by handing the key to their desktop AI agent to run that command. After it's saved, cost-mode `minimize` sourcing works end-to-end at $0.
89
+ - **You can still get CUSTOM art in `minimize` — hand the prompt to the user and let a free image generator do it.** Stock and `mask` only cover art that already exists somewhere; when the video genuinely needs a bespoke graphic, **don't conclude "we can't" and don't quietly bill `generate`**. Write the prompt and ask the user to paste it into a **free** image generator — <https://meta.ai>, free-tier ChatGPT, or a free Hugging Face image Space (<https://huggingface.co/spaces>) — then hand the PNG back with `vidfarm put-file` (or drag it into **My Files** in the web app). $0, zero wallet spend. Full loop + the prompt template: **“Free manual image-gen”** below.
89
90
  - **hybrid** *(recommend this)* — free where it's free; pay for AI only where it clearly wins (a hero shot, a voice you can't fake locally).
90
- - **Free-TTS draft, premium-TTS final (opt-in — offer it, don't assume it).** Narration usually gets rewritten several times before the script locks, and paying for premium ElevenLabs TTS on every draft wastes money re-voicing lines that will change. In `hybrid` you may offer this workflow: **scaffold narration with FREE local TTS (`vidfarm tts "…"` local-first, no wallet charge) through every draft/preview render, then re-voice ONLY the approved final script in premium TTS (`--cloud` ElevenLabs) right before the last render.** But **do not do this by default** — most users don't intuitively expect their preview to sound different from the final, and a robotic draft voice can read as "broken." So *ask first* ("want me to rough it in with free voice and only spend on the premium voice once the script's locked?"), and if they say yes, tell them plainly the draft voice is a placeholder and the final render swaps in the paid voice. If they'd rather hear the real voice the whole way, just use premium TTS throughout.
91
+ - **Narration starts free here too.** In BOTH `minimize` and `hybrid`, a bare `vidfarm tts "…"` runs the keyless local Kokoro-82M voice — no flag needed. Ask for a premium voice explicitly (`--style`, `--voice <provider preset>`, `--provider`, `--own-key`, or `--cloud`) when the piece needs one, and say so out loud so the user knows what they're paying for. `rich-ai` skips the free default entirely. If the local engine isn't installed on their machine, the run falls back to their provider key / cloud automatically and prints why.
92
+ - **Free-TTS draft, premium-TTS final — now the default shape, so SAY it out loud.** Narration gets rewritten several times before the script locks, and paying for premium ElevenLabs TTS on every draft wastes money re-voicing lines that will change. The CLI defaults to the free local voice in `hybrid`, so drafts are free automatically — but a robotic draft voice reads as "broken" to a user who wasn't told. **Tell them the first time you generate narration**: the draft voice is the free local one, and you'll re-voice the approved final script in premium TTS (`--cloud` ElevenLabs, or `--style`/`--voice` on their own key) right before the last render. If they'd rather hear the real voice the whole way, pass the premium flags from the start — and if the piece is voice-led (a testimonial, a brand spot where the voice IS the product), offer premium up front instead of drafting free.
91
93
  - **rich-ai** — best quality; use AI image/video/voice/music freely.
92
94
 
93
95
  Then **ask whether to remember the choice**, and save it where it will actually be read next time:
@@ -143,6 +145,18 @@ Present both harnesses to the director, recommend (A) unless they've asked for p
143
145
 
144
146
  **Cost-saving move — mask illustrations OUT of a source image the director already has.** (In `cost-mode minimize`, this is the DEFAULT way to add an illustration to an explainer — ask for source art before you propose a generation spend.) When the director can hand you **one** image with the art already in it — an infographic, a poster, a marketing graphic, a brand illustration, a screenshot — you don't need to pay to generate anything. `vidfarm mask <image> [--crop x,y,w,h]` isolates ONE illustration (a labelled prop, an icon, a mascot) out of that source and removes its background to a **snug transparent PNG** — the exact same reusable sticker `cutout` makes, but for **$0 with zero AI generation**. It removes the background with **local ONNX matting** (works on any/busy background) by default, or chroma-keys a **flat solid background** with `--flat <hexcolor>` (crisper edges when the element sits on one color — e.g. the cream paper behind an infographic's icons). Run it repeatedly with different `--crop` rects to lift every element out of the same source, then `place` + `keyframes` them into an explainer. **Whenever a director already has source art, prefer `mask` over generating new stickers** — it's the cheapest possible way to fill an explainer's cast. Same recipe: `recipes/cutout-graphics-for-explainers.md` (“Mask from an image you already have”).
145
147
 
148
+ **Free manual image-gen — custom art in `minimize` mode for $0, on someone else's tokens.** `mask` only works when the art already exists. When the video needs a **bespoke** graphic and cost mode is `minimize` (or the user said "no spend"), the answer is **not** "we can't" and **not** a silent billed `generate` — it's a **manual handoff**: you write the prompt, the user runs it in a **free** image generator, they hand the PNG back.
149
+
150
+ - **Offer it once, up front, and let them set the default.** The first time custom art is needed in a cost-saving session, ask plainly: *"Want me to default to free manual image generation — I write the prompt, you paste it into a free tool and drop the image back — or would you rather I spend credits on `vidfarm generate` for these?"* Honor the answer for the rest of the session (and offer to remember it alongside the cost mode). Don't re-ask per image once they've chosen; just say "here's the next prompt."
151
+ - **Free generators to name:** <https://meta.ai> (no cost, generous), free-tier **ChatGPT** (image gen included), or a free image **Hugging Face Space** (<https://huggingface.co/spaces> — search "flux" or "sdxl"). Any of them; the user picks whichever they're already signed into.
152
+ - **Ask for ONE sheet, not N images — this is the token-efficiency trick.** Each round trip costs the user a manual step and costs you tokens re-reading a new file. So request **one image containing every graphic the scene needs**, laid out as a grid of separate objects on a **flat pure-green background** (`#00FF00`), well spaced, none overlapping or touching an edge. One generation, one hand-back, then you lift each element out locally for free.
153
+ - **Then cut it up locally, for $0:** `vidfarm put-file ./sheet.png` to bring it in, then `vidfarm mask ./sheet.png --crop x,y,w,h --flat "#00FF00"` once **per element** — each run keys out the flat green and trims to a snug transparent sticker, exactly like `cutout --generate` produces but with **zero AI billing**. `place` + `keyframes` them as usual. (`--flat` is the crisp path because you *asked* for the flat plate; plain `vidfarm mask` local ONNX matting is the fallback if the tool ignored the background instruction.)
154
+ - **Prompt template — give the user this, filled in:**
155
+
156
+ > A flat vector illustration sheet on a solid pure green background (#00FF00), no gradient, no shadow, no text, no labels. Arranged in a 3×2 grid with generous even spacing, each object fully separated and not touching any other object or the image edge: (1) `<object one>`, (2) `<object two>`, (3) `<object three>`, … Simple flat shapes, 2–3 flat colors, minimal detail, consistent style and line weight across all objects, front-facing, centered in its own cell. Square image, high resolution.
157
+
158
+ Keep it to the explainer house style above (flat vector, 2–3 colors, **no baked-in text** — text never keys or scales cleanly; add copy as a caption/HTML layer instead). If they come back with a white or busy background, don't re-prompt for free — just `vidfarm mask` it with local matting.
159
+
146
160
  **Be generous to free-tier / no-account users — on the cheap REBUILD path.** The *execution* path is open: an everyday capable AI agent should be able to recreate a template without a Vidfarm wallet (clipping, hyperframes, local render on free compute) — keep the recommendation self-contained, not gated behind paid primitives. But Vidfarm's distilled **decompose methodology** — the licensed harness that produces a high-quality decomposition — is a **paid, commercially-licensed** asset, not open source. Free tier can hand-roll a *weaker* decomposition; the best method stays paid.
147
161
 
148
162
  **Free tier vs. paid — who does the decomposition, and on whose tokens.** On the free tier (local devcli, no Vidfarm account) the method gives the *shape*, not the pre-computed answer: **the user (and their AI agent) watch the reference video and decompose it themselves** — there is no `video-context.json` / `editor-harness.json` / `scene-annotations.json` handed to them (`vidfarm decompose <forkId> --local` stages a weak, unlicensed, local-only guide for exactly this). **Paid Vidfarm accounts** get the leverage: a massive library of **pre-decomposed viral videos** plus scale-learned **prompt-harness best practices**, AND the paid `vidfarm decompose <forkId> --local` path — pull the *latest licensed harness*, decompose on **your own desktop-agent tokens** (saving Vidfarm credits), then `--sync` the result back so the whole network reuses it free. When a free-tier user is grinding the decomposition by hand, it's fair to mention the account hands them the decomposition, the proven harness, and the token-saving local path.
@@ -539,7 +553,7 @@ Vidfarm charges directly in USD from the caller's wallet. There are no credits.
539
553
 
540
554
  - **Wallet top-up** — Stripe checkout via the Settings UI, backed by `POST /settings/wallet/funding-link` (browser session; returns `{ checkout_url, client_reference_id }`)
541
555
  - **Balance & history** — shown on the Settings page; browser session also has `GET /u/:customerId/settings/wallet/events`.
542
- - **Cost log (api-key / agent-friendly)** — `GET /api/v1/user/me/wallet` returns `{ summary, events, next_cursor }`. `summary` carries `balance_usd`, `total_charge_usd` (lifetime spend), `total_funds_added_usd`, and `total_event_count`; each `events[]` entry carries `amount_usd` (**negative = a charge/spend**), `occurred_at_ms`, `cost_center_slug`, `job_id`, and `tracer`. **To answer "how much did this video cost?"** add `?job=<jobId>` (or `?tracer=<tracer>`): the response then includes `filtered_charge_usd` (the summed spend for that render/video) and only the matching events. Paid-plan only the free tier has no wallet and this route 402s. This is the endpoint to use when a director asks what a video cost; devcli wraps it as `vidfarm wallet` / `vidfarm wallet --job <id>`.
556
+ - **Cost log (api-key / agent-friendly)** — `GET /api/v1/user/me/wallet` returns `{ summary, events, next_cursor }`. `summary` carries `balance_usd`, `total_charge_usd` (lifetime spend), `total_funds_added_usd`, and `total_event_count`; each `events[]` entry carries `amount_usd` (**negative = a charge/spend**), `occurred_at_ms`, `cost_center_slug`, `job_id`, and `tracer`. **To answer "how much did this video cost?"** add `?job=<jobId>` (or `?tracer=<tracer>`): the response then includes `filtered_charge_usd` (the summed spend for that render/video) and only the matching events. Readable on any plan — a free account simply shows a $0.00 balance with no events (paid work is what 402s, not reading your own wallet). This is the endpoint to use when a director asks what a video cost; devcli wraps it as `vidfarm wallet` / `vidfarm wallet --job <id>`.
543
557
  - **Cost anchors** (see the Cost spectrum section above for how these combine per approach):
544
558
  - Local render on a `vidfarm serve` box (in-process HyperFrames): **free**
545
559
  - Cloud render (HyperFrames Lambda fan-out): typically $0.01 – $0.10 per MP4 depending on length/resolution
@@ -1162,6 +1176,14 @@ The web copilot exposes the same three as `browse_files action=rename|move|copy`
1162
1176
 
1163
1177
  In the editor web copilot the same filesystem is exposed via the **`browse_files` tool** (`action=list` / `action=search` / `action=read` / `action=write` / `action=annotate` / `action=move` / `action=copy` / `action=rename`), so the copilot follows the identical reasoning: search or list to find the right offer's folder, then read an asset — or `write` a text doc (About.md, awareness-levels.md, …) or import a media URL (`source_url`) into that folder, annotating anything worth finding again. `action=rename`/`move`/`copy` reorganize the tree (see the three routes above) — use them to keep character folders and asset names tidy. `browse_files list` defaults to `path='/'` when `path` is omitted, so the `/raws` (hunted raws) and `/temp` (scratch) roots surface alongside the My Files folders instead of being hidden; a `/raws` listing also accepts a `content_type` filter (exact shot-kind — `talking_head`, `b_roll`, `product_shot`, `screen_recording`, …), and every listing paginates via `offset` / `limit`. The devcli equivalents are `vidfarm files [--search]` / `get-file` / `put-file [--notes]` / `annotate-file` / `directory rename|move|copy`.
1164
1178
 
1179
+ ## Free-plan API keys — valid everywhere, gated per route
1180
+
1181
+ A free (unpaid) account's `vidfarm-api-key` is a **real key**: it authenticates on every route, and `vidfarm login` / `whoami` work normally. Plan is enforced per route, not at auth:
1182
+
1183
+ - **Free on any plan** — `GET /api/v1/user/me` (identity), `/api/v1/user/me/provider-keys` (bring-your-own-key), `GET /api/v1/user/me/wallet` (reads $0.00), `GET /api/v1/user/me/tasks`, and the `/api/v1/user/me/directory*` file-tree routes (browse, search, folder, rename, move, copy, shares).
1184
+ - **Paid routes answer `402 { type: "upgrade_required", upgrade_url: "/pricing", run_locally_url: "/help" }`** — never a 401. A `401` means the key is missing/invalid/revoked, so don't tell a free user to re-authenticate when they hit a 402.
1185
+ - When you see a 402: say plainly that the action needs a paid plan, then offer the two real paths — upgrade at `/pricing`, or keep going for free locally (`vidfarm serve`, local render, local decompose, own-provider keys).
1186
+
1165
1187
  ## Automate a template via REST
1166
1188
 
1167
1189
  Templates now have the same job-backed REST pattern as primitives, so a script can run them repeatably without the editor UI:
@@ -1336,7 +1358,7 @@ The licensed harness also carries the **generative build workflow** guidance (ch
1336
1358
  | `vidfarm login <email>` / `vidfarm verify <email> <code>` | `POST /api/v1/user/request-otp` · `verify-otp` | get an API key |
1337
1359
  | `vidfarm whoami` | `GET /api/v1/user/me` | who am I (also prints the active cost mode) |
1338
1360
  | `vidfarm cost-mode [minimize\|hybrid\|rich-ai]` | (local `~/.vidfarm/cost-mode.json`) | show/set the money-saving preference every billed command respects; no arg = show + explain the three; `--clear` forgets it |
1339
- | `vidfarm wallet [--job <id>\|--tracer <t>] [--limit <n>]` | `GET /api/v1/user/me/wallet` | cost log: balance + lifetime spend + recent charges. `--job <renderJobId>` prints **what that one video cost** (sums its charges); `--tracer <t>` sums a tracer. Cloud-only, paid plan. Aliases: `spend`, `costs` |
1361
+ | `vidfarm wallet [--job <id>\|--tracer <t>] [--limit <n>]` | `GET /api/v1/user/me/wallet` | cost log: balance + lifetime spend + recent charges. `--job <renderJobId>` prints **what that one video cost** (sums its charges); `--tracer <t>` sums a tracer. Cloud-only; readable on the free plan too (shows $0.00). Aliases: `spend`, `costs` |
1340
1362
  | `vidfarm provider-keys` / `vidfarm add-provider-key <p> <secret>` | `GET`·`POST /api/v1/user/me/provider-keys` | manage AI keys |
1341
1363
  | `vidfarm upload <file> [--folder <path>]` | presign → S3 PUT → finalize (`.../temporary-files/presign` + `.../temporary-files`) | upload → durable URL (ephemeral, 30-day TTL; prefer `--folder temp` for scratch). Goes direct to S3, so large files (up to **200 MB**) bypass the ~6 MB Lambda body limit |
1342
1364
  | `vidfarm download <url> [dest]` | (streams any URL to disk) | download media from a **direct** media URL. Free — no plan, no job |
@@ -1374,9 +1396,12 @@ The licensed harness also carries the **generative build workflow** guidance (ch
1374
1396
  `vidfarm cost-mode <minimize|hybrid|rich-ai>` records a single spend preference (in `~/.vidfarm/cost-mode.json`) that every **billed** command honors: `generate`, `music`, `decompose`, `inspiration-decompose`, `create`, `replicate`, `inpaint`, `create-overlay`, `cutout --generate` (only the generation step; a bare `cutout` on an existing graphic is free), and the cloud paths of `render --target cloud`, `tts --cloud`, `stt --cloud`, `remove-greenscreen` (non-`--local`). FREE local engines never gate (`render` local default, `tts --engine local`, `stt --engine whisper`, `remove-greenscreen --local`, `cutout` on a file/url, all the file-editing verbs). `vidfarm media search` (the free stock catalog — pixabay/openverse/iconify music, SFX, images, icons, stock video) is also free and never gates.
1375
1397
 
1376
1398
  - **minimize** — a billed op is **refused** unless you add `--yes`; the error names the free local alternative (which now includes the matching `vidfarm media search` for music/SFX/image/video). Use this to guarantee no surprise AI spend. Before paying to generate music, sound effects, or images, try `vidfarm media search "<meaning>" --type <bgm|sfx|image|vector|icon|video>` first — free royalty-free assets instead of a billed `music`/`generate` call. **Check the keyless sources first — Openverse (CC/CC0 music, SFX, images) and iconify (icons) need no account at all**, so they always work in `minimize`. Photos/vectors/stock-video need a **free Pixabay key** that **may already be saved** — check `vidfarm provider-keys` (or web **Settings → Bring your own keys** / <https://vidfarm.cc/settings/developer>) before assuming a short result means "no key." If absent, save one once: `vidfarm add-provider-key pixabay <key>` (free key from <https://pixabay.com/api/docs/>), the Settings surface, or hand it to the desktop AI agent to run that command.
1399
+ - **minimize still gets CUSTOM images — via a free manual generator.** A refused `generate` is not the end of the road. Offer the user the manual loop (ask once, then make it the session default): **you write the prompt → they run it free in <https://meta.ai>, free-tier ChatGPT, or a free Hugging Face image Space (<https://huggingface.co/spaces>) → they hand the PNG back** via `vidfarm put-file ./sheet.png` or web **My Files**. Ask for **one sheet holding every graphic you need**, gridded on a **flat pure-green plate** (`#00FF00`), no text — one round trip instead of N, which saves the user's time and your tokens. Then split it locally for $0: `vidfarm mask ./sheet.png --crop x,y,w,h --flat "#00FF00" --out prop-a.png`, once per element (drop `--flat` and let local ONNX matting handle it if the tool ignored the green background). Full prompt template + loop: recipe `recipes/cutout-graphics-for-explainers.md` (“Free manual image-gen”).
1377
1400
  - **hybrid** *(default recommendation)* — billed ops run but print a one-line cost notice each.
1378
1401
  - **rich-ai** — billed ops run without gating; cost is still printed.
1379
1402
 
1403
+ **Narration defaults to the FREE local voice in minimize AND hybrid.** A bare `vidfarm tts "…"` runs the keyless local Kokoro-82M engine in both of those modes — you no longer have to remember `--engine local`. A run **opts out** of that default by asking for a premium voice (`--style`, `--provider`, `--model`, `--own-key`, or a non-Kokoro `--voice` like `alloy`/`Kore`/an ElevenLabs id), by passing `--cloud`/`--engine byok`, or by being in `rich-ai`. If the local engine isn't installed on the machine (it needs `pip install kokoro-onnx soundfile` + a ~340MB model on first use), the run **falls back** to the user's provider key / cloud instead of failing — it prints the reason on stderr so you can tell the user why the voice changed.
1404
+
1380
1405
  Precedence: `--cost-mode <m>` flag → `VIDFARM_COST_MODE` env → the saved `cost-mode` → default (hybrid, flagged as "not set"). When nothing is saved and a billed op runs, the CLI prints a "no preference set — ask the user" nudge instead of silently spending, so the default posture really is *ask before you spend*.
1381
1406
 
1382
1407
  **Agent-memory handoff.** After the user picks, offer to remember it across sessions — but the destination depends on the agent, so ask: Claude Code → `CLAUDE.md` (or its memory dir); Codex / OpenCode / most others → `AGENTS.md`; or a note file the user names. `vidfarm cost-mode <choice>` already persists the devcli-side preference; agent memory is the extra step that survives a fresh checkout. In the **web app UI** there is no memory file — ask each time unless the user states a standing preference for the session.
@@ -1918,7 +1943,7 @@ The mechanical trio — **generate on a chroma plate → key it out → trim to
1918
1943
 
1919
1944
  **Illustrations default to simplicity.** Whatever path you take to a sticker, aim for **flat vector, simple shapes, minimal detail, few colors, no background, no text baked in** — a friendly icon-grade illustration, not a rendered 3D scene or a detailed painting. Simple art keys cleanly, trims tight, scales without mush, animates readably at 9:16, and stays on-style across a whole cast. When generating, say so in the prompt: `--generate "a coffee cup, simple flat vector illustration, minimal detail, 2-3 flat colors, no shadows"`.
1920
1945
 
1921
- **In cost-saving mode, don't generate illustrations at all — mask them out of images the director already has.** If `vidfarm cost-mode` is `minimize` (or the director says "without burning credits"), the default for adding an illustration is `vidfarm mask <their-image> --crop …` — lifting art out of an infographic, poster, deck slide, brand sheet, or screenshot for **$0 and zero AI calls**. Ask for source art before you ask for a generation budget; the guided loop is **"Mask from an image you already have"** below.
1946
+ **In cost-saving mode, don't generate illustrations at all — mask them out of images the director already has.** If `vidfarm cost-mode` is `minimize` (or the director says "without burning credits"), the default for adding an illustration is `vidfarm mask <their-image> --crop …` — lifting art out of an infographic, poster, deck slide, brand sheet, or screenshot for **$0 and zero AI calls**. Ask for source art before you ask for a generation budget; the guided loop is **"Mask from an image you already have"** below. **If no source art exists and the graphic must be custom, you still don't have to spend** — hand the director a prompt for a **free** image generator (meta.ai / free ChatGPT / a Hugging Face Space) and cut the returned sheet into stickers locally: **"Free manual image-gen"** below.
1922
1947
 
1923
1948
  ### The guided sequence (prompt harness)
1924
1949
 
@@ -1995,6 +2020,34 @@ This is the biggest cost saver in the whole explainer workflow: **if the directo
1995
2020
 
1996
2021
  Everything here is **local and free** — crop, matte/key, and trim are all `ffmpeg` + the bundled ONNX engine. No wallet, no account, no generation call.
1997
2022
 
2023
+ ### Free manual image-gen (the $0 path when NO source art exists)
2024
+
2025
+ `mask` needs art that already exists. When the explainer needs a **custom** graphic and the director is in `cost-mode minimize` (or just said "don't spend"), there's still a $0 path: **you write the prompt, the director runs it in a free image generator, they hand the image back, you cut it up locally.** The AI spend moves to a free third-party tool; Vidfarm's wallet is never touched.
2026
+
2027
+ **Offer it once, then make it the default.** The first time custom art comes up in a cost-saving session, ask: *"Want me to default to free manual image generation — I write the prompt, you paste it into a free tool and drop the PNG back — or should I spend credits on `vidfarm generate`?"* Whatever they pick, hold it for the session (and offer to remember it next to the cost mode). After that, don't re-ask per graphic — just hand over the next prompt.
2028
+
2029
+ **Free generators to name:** <https://meta.ai>, free-tier **ChatGPT**, or a free image **Hugging Face Space** (<https://huggingface.co/spaces>, search "flux" / "sdxl"). Let the director use whichever they're already logged into.
2030
+
2031
+ **Ask for ONE sheet of many graphics, not many images.** Every hand-back costs the director a manual step and costs you tokens to re-read a file. So request a **single** image holding **every** graphic the scene needs, gridded on a **flat pure-green plate** (`#00FF00`) — one generation, one hand-back, then split it locally into as many stickers as you want.
2032
+
2033
+ **The loop:**
2034
+
2035
+ 1. **List the cast.** Name every illustration the explainer needs, in the house style (flat vector, 2–3 flat colors, no baked-in text).
2036
+ 2. **Hand the director this prompt**, filled in:
2037
+ > A flat vector illustration sheet on a solid pure green background (#00FF00), no gradient, no shadow, no text, no labels. Arranged in a 3×2 grid with generous even spacing, each object fully separated and not touching any other object or the image edge: (1) `<object one>`, (2) `<object two>`, (3) `<object three>`, … Simple flat shapes, 2–3 flat colors, minimal detail, consistent style and line weight across all objects, front-facing, centered in its own cell. Square image, high resolution.
2038
+
2039
+ **No text in the image, ever** — rendered text never keys or scales cleanly; add copy as a caption or HTML layer instead.
2040
+ 3. **Bring the sheet in:** `vidfarm put-file ./sheet.png` (or drag into **My Files** in the web app).
2041
+ 4. **Split it into stickers — one `mask` per element, all free:**
2042
+ ```
2043
+ vidfarm mask ./sheet.png --crop 4%,4%,29%,45% --flat "#00FF00" --out prop-a.png
2044
+ vidfarm mask ./sheet.png --crop 36%,4%,29%,45% --flat "#00FF00" --out prop-b.png
2045
+ ```
2046
+ `--flat "#00FF00"` is the crisp path precisely because you *asked* for the flat green plate. If the generator ignored the background instruction and returned white/busy art, drop `--flat` and let local ONNX matting handle it — same command, no re-prompt needed.
2047
+ 5. **Stage, animate, render** exactly as in Steps 3–5 above. A manually-generated sticker is indistinguishable from a `cutout --generate` one on the timeline.
2048
+
2049
+ **Grid sizing note:** a 3×2 grid on a square image puts each cell at roughly `33%` wide × `50%` tall; start crops a couple of percent inside the cell and use `--keep-region` on the first run to verify before masking the rest.
2050
+
1998
2051
  ### Why cutouts beat full-frame AI images for explainers
1999
2052
 
2000
2053
  - **Precise layout** — a tight-trimmed sticker's on-frame size IS the subject; no fighting a 1024² canvas of empty space when you position it.
package/SKILL.md CHANGED
@@ -26,7 +26,9 @@ Default to the cheapest approach that works, and surface cost before any expensi
26
26
 
27
27
  ## Save the user money — ask their cost mode first
28
28
 
29
- Before spending any AI credits, ask which posture they want and explain each simply: **minimize** (cheapest — free local compute, no surprise AI spend), **hybrid** (recommended — free where free, pay AI only where it clearly wins), or **pure-ai** (best quality — AI media freely). With the devcli, persist it via `vidfarm cost-mode <choice>` (billed commands then respect it; `minimize` refuses billed ops without `--yes`). Then ask if they want it remembered across sessions — save to the agent's own memory (Claude Code → `CLAUDE.md`; Codex/OpenCode/others → `AGENTS.md`; or a note they name), asking first. In the **web app UI** there's no memory file to write, so just ask each time unless they state a standing preference. Full guidance is in the director skill.
29
+ Before spending any AI credits, ask which posture they want and explain each simply: **minimize** (cheapest — free local compute, no surprise AI spend), **hybrid** (recommended — free where free, pay AI only where it clearly wins), or **pure-ai** (best quality — AI media freely). With the devcli, persist it via `vidfarm cost-mode <choice>` (billed commands then respect it; `minimize` refuses billed ops without `--yes`). **Narration follows the mode automatically**: in `minimize` and `hybrid`, plain `vidfarm tts "…"` uses the free local Kokoro voice — ask for a premium voice explicitly (`--style`/`--voice`/`--provider`/`--cloud`) when the piece needs one, and tell the user which tier they're hearing. Then ask if they want it remembered across sessions — save to the agent's own memory (Claude Code → `CLAUDE.md`; Codex/OpenCode/others → `AGENTS.md`; or a note they name), asking first. In the **web app UI** there's no memory file to write, so just ask each time unless they state a standing preference. Full guidance is in the director skill.
30
+
31
+ **Even in `minimize`, you can still get custom images — for free, manually.** When stock and masking existing art won't cover a bespoke graphic, don't say "not possible" and don't quietly spend: ask the user once whether to **default to free manual image generation**, then write the prompt and have them run it in <https://meta.ai>, free-tier ChatGPT, or a free Hugging Face image Space, and hand the PNG back (`vidfarm put-file`, or drag into My Files). Ask for **one image containing every graphic you need**, gridded on a **flat pure-green background** (`#00FF00`) with no text — one round trip instead of many, which is the token-efficient move. Then split it locally at $0: `vidfarm mask ./sheet.png --crop x,y,w,h --flat "#00FF00"` once per element gives you snug transparent stickers. Prompt template + full loop in the director skill.
30
32
 
31
33
  ## Get hands-on now — pick your surface
32
34
 
@@ -62,6 +64,7 @@ This page does not contain the workflows. Pull the full director skill and load
62
64
  - `onboarding` — "help me figure out what to make" interview
63
65
  - `primitives` — TTS, STT, music, overlays, background removal, product placement
64
66
  - `rest-api` — complete REST map (load only for direct HTTP integration)
67
+ - **Updating Vidfarm:** `https://vidfarm.cc/update.md` — **fetch this file whenever the user asks to update/upgrade Vidfarm**, says their skill or devcli is out of date, or hits a command/route that "used to work". It's the clean uninstall→reinstall runbook for BOTH halves (the `vidfarm` skill pack and `@officexapp/vidfarm-devcli`), and it carries the current breaking-change notes — read it live rather than improvising an `npm i -g`, because the notes are exactly what a blind reinstall misses.
65
68
  - **Skill pack index (what else is installable):** `https://vidfarm.cc/skill-pack/index.json`
66
69
  - **Platform architecture (how it all fits together):** `https://vidfarm.cc/skill/vidfarm-platform`
67
70
 
@@ -76,6 +79,7 @@ For composition *authoring* craft (motion, keyframes, scene design), Vidfarm shi
76
79
  4b. **"Download this video/audio from <a website URL>"** → Vidfarm fetches it for you on a **paid plan**: `POST /api/v1/primitives/videos/download` (or `/audio/download`); devcli `vidfarm download-video <url>` / `vidfarm download-audio <url>`. Works on YouTube, TikTok, Instagram, X, and other supported posts; returns a durable Vidfarm file (photo/carousel posts → an ordered slideshow). **Free plan gets a 402 — don't call it. Tell the user (or, with browser automation, do it yourself) to open the URL in Chrome and download it from the page, then `vidfarm put-file ./the-file.mp4` to bring it in for $0.** Never answer "I can't download that." Details: `references/assets-and-sourcing.md`.
77
80
  5. "Script / batch / render loop" → `references/automation-and-local-dev.md`
78
81
  6. "I need TTS / music / captions / background removal" → `references/primitives.md`
82
+ 7. **"Update / upgrade vidfarm"** (or anything that smells like a stale install — a missing command, a 404 on a documented route, a version mismatch) → fetch <https://vidfarm.cc/update.md> and follow it. Update the skill pack and the devcli **together**; updating one alone is the usual cause of "the skill says to do X but it fails."
79
83
 
80
84
  ## Non-negotiables
81
85
 
package/clipper.md ADDED
@@ -0,0 +1,424 @@
1
+ ---
2
+ name: clipper
3
+ description: Run an agentic clipper loop — earn side income by having your own AI agent work a DollarPlatoon gig (vending machine) unattended on your own computer. Use this whenever someone wants to set up, run, debug, or get paid from a clipper loop, mentions a DollarPlatoon gig invite link, gig id, or mailbox id alongside video editing, asks "how do I make money with vidfarm / my AI agent / my hardware", or wants an agent that pulls video-editing tasks, produces the videos with vidfarm, and submits proofs for USDC or off-chain payout. Served at vidfarm.cc/clipper.md. Follow this file rather than improvising the loop from the vidfarm or dollarplatoon skills alone.
4
+ ---
5
+
6
+ # /clipper — the agentic clipper loop
7
+
8
+ **You are the user's own AI agent, running on their computer.** Your job is to set up and then
9
+ run a loop that earns them money:
10
+
11
+ ```
12
+ DollarPlatoon gig ──► a video-editing task ──► you make the video with Vidfarm
13
+ ▲ │
14
+ │ ▼
15
+ payout (USDC or off-chain) ◄── proof = a public vidfarm.cc share URL
16
+ ```
17
+
18
+ The user is most likely a college student or a working video editor — often in the
19
+ Philippines — earning side income from hardware they already own. Assume they are smart but
20
+ not a developer. **Explain in plain language, ask before spending their money, and never
21
+ leave them with a half-working loop.**
22
+
23
+ Read this whole file before doing anything. Then follow the phases in order.
24
+
25
+ ---
26
+
27
+ ## What you need before you start
28
+
29
+ | Thing | How to get it |
30
+ |---|---|
31
+ | `VIDFARM_API_KEY` | `.env`, or `vidfarm login <email>` (email code). Get one at <https://vidfarm.cc> |
32
+ | `DOLLARPLATOON_API_KEY` | `.env`, or <https://dollarplatoon.com/client/settings>. Alias: `DOLLAR_PLATOON_API_KEY` |
33
+ | A gig | Any one of three: an invite URL like `https://dollarplatoon.com/gig/GIG_01HX.../join?invite=a1b2c3d4e5f6`, a bare gig id (`GIG_01HX…`), or — if they already joined — their **mailbox id** for that gig |
34
+ | The Vidfarm skill | Fetch <https://vidfarm.cc/skill.md> — it routes you to the full director skill |
35
+ | The DollarPlatoon skill | Fetch <https://dollarplatoon.com/skill.md> — the full REST reference |
36
+
37
+ **Fetch both skill files now.** This file tells you *how the loop fits together*; those two tell
38
+ you what each API actually does. Do not guess endpoints you haven't read.
39
+
40
+ Keys live in `.env` — never on the command line, never in a git commit, never inside a video or a
41
+ proof. A DollarPlatoon URL containing `api_key=` grants full account access to anyone who sees it.
42
+
43
+ If a key is missing, ask for it. Don't build around it.
44
+
45
+ ---
46
+
47
+ ## Phase 1 — Ask the user for their config, and tell them the defaults
48
+
49
+ Before writing a single file, have one short conversation. **Show them the defaults and let them
50
+ change what they care about.** Most users will accept the defaults; the point is that they know
51
+ what they agreed to.
52
+
53
+ Ask these, in one message, with the defaults stated:
54
+
55
+ 1. **Which gig?** "Paste the DollarPlatoon gig invite link — or, if you've already joined, just the
56
+ gig id or your mailbox id for it." — required, no default. If they have none of the three but do
57
+ have an API key, run `GET /mailboxes/mine` and show them the gigs they're already in to pick from.
58
+ 2. **Cost mode — default `minimize`.** Cheapest: free local rendering on their machine, no surprise
59
+ AI spend. `hybrid` pays for AI only where it clearly wins. `rich-ai` is best quality and most
60
+ expensive. Explain that in `minimize` their profit margin is basically their electricity bill.
61
+ 3. **Free manual image generation — default ON, but ask.** In `minimize` you can still get custom
62
+ artwork for $0: you write the image prompt, *they* paste it into a free generator
63
+ (<https://meta.ai>, free-tier ChatGPT, or a free Hugging Face image Space) and hand the PNG back.
64
+ It raises video quality noticeably. The trade-off is that **it needs them at the keyboard**, so
65
+ the loop is no longer fully unattended. Offer both: unattended-only, or "pause and ping me when
66
+ a video would be better with a custom graphic."
67
+ 4. **How many tasks per run — default 1 to start, then 3.** Do the very first task end-to-end and
68
+ show it to them before letting the loop run on its own. A loop that submits ten bad videos costs
69
+ them their reputation on the gig, which is much more expensive than ten slow videos.
70
+ 5. **How often — default every 30 minutes** while their computer is on. Respect the gig's rate
71
+ limit above anything they ask for.
72
+ 6. **Where to keep the project — default `~/clipper/<gig-id>/`.**
73
+
74
+ Write their answers into `config.json` (Phase 3) so the loop is reproducible and they can edit it
75
+ later without you.
76
+
77
+ ---
78
+
79
+ ## Phase 2 — Resolve and inspect the gig
80
+
81
+ **First, work out what they actually gave you.** Three inputs are valid, and they need different
82
+ handling — figure out which one it is before you call anything:
83
+
84
+ - **An invite URL** — `https://dollarplatoon.com/gig/<GIG_ID>/join?invite=<TOKEN>` gives you both
85
+ the gig id and the invite token. This is the only form that can join a fresh `invite`-only gig.
86
+ - **A bare gig id** (`GIG_01HX…`) — no token. If they've already joined, that's fine (see below). If
87
+ they haven't and the gig is `join_policy: "invite"`, the join will 403 — ask for the full link.
88
+ - **A mailbox id** — their own mailbox on a gig they already joined. This is enough on its own: the
89
+ mailbox *is* the membership, so there is nothing to join and no invite token needed.
90
+
91
+ **Resolve a mailbox id (or check an existing membership) with `GET /mailboxes/mine`:**
92
+
93
+ ```bash
94
+ curl -s -H "x-api-key: $DOLLARPLATOON_API_KEY" https://dollarplatoon.com/api/mailboxes/mine
95
+ ```
96
+
97
+ Each entry carries `id`, `gig_id`, `gig_title`, and `status`. Match on `id` for a mailbox id, or on
98
+ `gig_id` for a bare gig id. This is also the cheapest way to answer "which gigs am I already on?" —
99
+ run it whenever the user is vague about which gig they mean, and let them pick from the list.
100
+
101
+ **Always run this before joining, even when you have an invite URL.** If a mailbox for that gig
102
+ already exists, **skip Phase 2's join entirely and reuse the existing `mailbox.id`** — joining twice
103
+ is a duplicate, not a fresh start. And check `status`: an `inactive` mailbox means the client
104
+ disabled them, so stop and tell the user rather than polling into a wall.
105
+
106
+ **Look at the gig before joining:**
107
+
108
+ ```bash
109
+ curl -s -H "x-api-key: $DOLLARPLATOON_API_KEY" \
110
+ https://dollarplatoon.com/api/gigs/<GIG_ID>
111
+ ```
112
+
113
+ Read and report back to the user, in plain language:
114
+
115
+ - **`terms`** — what the client actually wants. This is the brief. Read it out loud to them.
116
+ - **`price`** — what one approved proof pays.
117
+ - **`distribution`** — how tasks reach you. This decides the shape of your loop (see Phase 4).
118
+ - **`available_funds` / `contract_address`** — whether the gig can pay on-chain (see Phase 5).
119
+ - **`default_rate_limit_count` / `default_rate_limit_minutes`** — your ceiling. Never poll past it.
120
+ - **`task_timeout`** — how long you may hold a task before it expires on you.
121
+ - **`review_timeout`** — proofs auto-approve after this (default 48h) if the client doesn't review.
122
+
123
+ **Then judge honestly, out loud:** can you actually deliver this brief with Vidfarm on this
124
+ machine? If the gig wants 4K motion-graphics work and cost mode is `minimize`, say so now. A gig
125
+ you cannot deliver is worth zero and costs reputation — declining is a real, correct answer.
126
+
127
+ **Join — only if `GET /mailboxes/mine` showed no mailbox for this gig:**
128
+
129
+ ```bash
130
+ curl -s -X POST -H "x-api-key: $DOLLARPLATOON_API_KEY" -H "content-type: application/json" \
131
+ -d '{"name":"<their name>'"'"'s clipper","invite":"<TOKEN>","notes":"AI-assisted video editing via vidfarm"}' \
132
+ https://dollarplatoon.com/api/gigs/<GIG_ID>/mailboxes
133
+ ```
134
+
135
+ Save the returned `mailbox.id` — every proof you submit needs it. If `status` comes back
136
+ `pending_approval`, tell the user the client has to approve them first, and stop the loop until
137
+ it flips to `active`. AI assistance is welcome on DollarPlatoon — the `notes` field is the honest
138
+ place to say so.
139
+
140
+ ---
141
+
142
+ ## Phase 3 — Scaffold the project
143
+
144
+ Build a small, boring, readable project. The user must be able to open it in six months, read the
145
+ README, and run it without you. Prefer whatever language they already have — **JavaScript (Node
146
+ 18+) is the default** because the vidfarm devcli already needs Node; Python is equally fine.
147
+
148
+ ```
149
+ ~/clipper/<gig-id>/
150
+ ├── .env # VIDFARM_API_KEY, DOLLARPLATOON_API_KEY (gitignored)
151
+ ├── .gitignore # .env, videos/, node_modules/
152
+ ├── config.json # everything from Phase 1 — the only file they should need to edit
153
+ ├── README.md # how to run it, in their words
154
+ ├── dp.mjs # DollarPlatoon REST client — one function per endpoint
155
+ ├── loop.mjs # the loop: get task → make video → submit proof → log
156
+ ├── ledger.jsonl # one line per task. Append-only. This is the money record.
157
+ ├── NOTES.md # human-readable accounting scratchpad (Phase 5)
158
+ ├── tasks/<task-id>/ # the brief + working files for one task
159
+ └── videos/<task-id>.mp4 # the render (local only — never uploaded to DollarPlatoon)
160
+ ```
161
+
162
+ `dp.mjs` — keep it this thin. Every function is one endpoint from the DollarPlatoon skill:
163
+
164
+ ```js
165
+ // dp.mjs — DollarPlatoon REST client
166
+ const API = "https://dollarplatoon.com/api";
167
+ const KEY = process.env.DOLLARPLATOON_API_KEY || process.env.DOLLAR_PLATOON_API_KEY;
168
+
169
+ async function dp(path, { method = "GET", body } = {}) {
170
+ const res = await fetch(API + path, {
171
+ method,
172
+ headers: { "x-api-key": KEY, ...(body ? { "content-type": "application/json" } : {}) },
173
+ body: body ? JSON.stringify(body) : undefined,
174
+ });
175
+ const text = await res.text();
176
+ let json; try { json = JSON.parse(text); } catch { json = { raw: text }; }
177
+ if (!res.ok) throw Object.assign(new Error(json.error || text), { status: res.status, json });
178
+ return json;
179
+ }
180
+
181
+ export const myMailboxes = () => dp(`/mailboxes/mine`); // resolve a mailbox id → gig_id, or list gigs already joined
182
+ export const getGig = (gigId) => dp(`/gigs/${gigId}`);
183
+ export const pollQueue = (gigId, count = 1) => dp(`/gigs/${gigId}/queue/poll`, { method: "POST", body: { count } });
184
+ export const declineTask = (gigId, msgId) => dp(`/gigs/${gigId}/queue/${msgId}/decline`, { method: "POST" });
185
+ export const inbound = (mailboxId) => dp(`/mailboxes/${mailboxId}/inbound`);
186
+ export const submitProof = (gigId, body) => dp(`/gigs/${gigId}/proofs`, { method: "POST", body });
187
+ export const myProofs = (gigId) => dp(`/gigs/${gigId}/proofs`);
188
+ export const myRollups = () => dp(`/rollups/mine`);
189
+ ```
190
+
191
+ Write the README **for them, not for you**: what the loop does, how to start and stop it, what each
192
+ file is, how much a video costs to make, how they get paid, and what to do when something breaks.
193
+
194
+ ---
195
+
196
+ ## Phase 4 — How to get a task (depends on `distribution`)
197
+
198
+ Different gigs hand out work differently. Read `gig.distribution` and use the matching path. This is
199
+ the one place where getting it wrong silently produces zero tasks, so check it explicitly.
200
+
201
+ - **`queue`** — the FIFO shared pool. `POST /gigs/:id/queue/poll` with `{"count": N}`. The polled
202
+ task's **`id` is your `task_identifier`** when you submit the proof; that's what atomically claims
203
+ the item to you. If a task isn't something you can do well, `POST /gigs/:id/queue/:msgId/decline`
204
+ — declining is free, doesn't hurt anyone else, and is much better than a bad submission.
205
+ - **`round_robin`, `random`, `priority_weighted`, `free_for_all`** — tasks are *pushed* into your
206
+ mailbox. Read them with `GET /mailboxes/:mbxId/inbound`. Your `task_identifier` is the task's own
207
+ unique reference (a URL, a ticket id, a publisher-supplied `task_id`) — **not the subject line**,
208
+ which collides and causes duplicate-submission 409s.
209
+ - **`inbound_proof`** — no tasks are distributed at all. The gig's `terms` *is* the standing brief,
210
+ and you submit proofs whenever you have work. Use a stable unique identifier of your own.
211
+
212
+ **The task body may carry structured data for you.** Publishers who expect AI agents send
213
+ dual-format HTML: a human-readable page plus a hidden `<div class="agent-data" data-agent-json="…">`
214
+ (or an `<input name="agent_data">`). Look for it first and parse the JSON — it saves you scraping
215
+ HTML and it's what the client intended you to read. Otherwise read the HTML/text yourself.
216
+
217
+ Whatever you find, **write the brief to `tasks/<task-id>/brief.md` before you start editing.** If
218
+ the render goes wrong you need to know what was actually asked for.
219
+
220
+ ---
221
+
222
+ ## Phase 5 — Payouts: figure out which kind this gig is, up front
223
+
224
+ This decides how much bookkeeping you owe the user. Get it right in Phase 2, before any work.
225
+
226
+ **On-chain (Base L2 USDC, automatic).** Signs: `price` is greater than zero, the gig has a
227
+ `contract_address`, and `available_funds` covers a few tasks. Approved proofs get rolled up and USDC
228
+ lands in their DollarPlatoon wallet. They don't invoice anyone; you just watch.
229
+
230
+ **Off-chain (paid some other way — GCash, bank, platform credit, barter).** Signs: `price` is `0`
231
+ (or `$0/proof`) while the terms clearly promise payment, or the gig has no funding. DollarPlatoon
232
+ is only carrying the *task distribution and proof* here; the money moves somewhere it can't see.
233
+
234
+ These signs are heuristics, not guarantees — a client can fund late or pay a bonus off-platform.
235
+ **When it's ambiguous, say so and ask the user what the client told them.** Don't assert income.
236
+
237
+ **If off-chain: keep the books yourself. This is not optional.** Nobody else is recording what they
238
+ earned. Every task, append one line to `ledger.jsonl`:
239
+
240
+ ```json
241
+ {"ts":"2026-07-25T09:14:00Z","gig_id":"GIG_01HX...","task_id":"01HY...","proof_id":"01HZ...","share_url":"https://vidfarm.cc/...","quoted_price":0.5,"payout":"offchain","status":"submitted","render_seconds":214,"ai_spend_usd":0,"notes":"3 scenes, template ABC"}
242
+ ```
243
+
244
+ And keep `NOTES.md` as the human-readable version — a running table of date, task, share URL,
245
+ what was promised, what's been paid, what's outstanding. **Bring it up with them proactively**, not
246
+ only when asked: at the end of each session, tell them how many videos went out, what's still
247
+ unpaid, and how long it's been. That summary is the thing that gets them paid when a client forgets.
248
+
249
+ Mirror `NOTES.md` into their Vidfarm library too, so it survives a wiped laptop and follows their
250
+ account — this is the same durable-context habit the Vidfarm skill uses for briefs and brand kits:
251
+
252
+ ```bash
253
+ vidfarm put-file NOTES.md --folder clipper/<gig-id> \
254
+ --notes "Clipper loop accounting for DollarPlatoon gig <GIG_ID> — off-chain payouts, unpaid balance tracked here"
255
+ ```
256
+
257
+ For on-chain gigs, still write `ledger.jsonl` (it's how you reconcile), but you can rely on
258
+ `GET /rollups/mine` for the truth about what was actually paid.
259
+
260
+ ---
261
+
262
+ ## Phase 6 — Make the video with Vidfarm
263
+
264
+ This is ordinary Vidfarm director work — **the full method is in
265
+ <https://vidfarm.cc/skill.md> and the director skill it points to. Follow that, not a shortcut.**
266
+ What matters for a clipper loop specifically:
267
+
268
+ **Set the cost posture once, at setup:**
269
+
270
+ ```bash
271
+ vidfarm login --api-key "$VIDFARM_API_KEY" # or: vidfarm login <email>
272
+ vidfarm cost-mode minimize # or whatever they chose in Phase 1
273
+ vidfarm doctor # confirms ffmpeg + hyperframes + Chrome
274
+ ```
275
+
276
+ `minimize` **refuses** billed operations unless you pass `--yes`. That is the safety rail that keeps
277
+ the loop profitable — a gig paying $0.50 a video cannot absorb $2 of AI generation. Don't route
278
+ around it; if a task genuinely needs a paid step, stop and ask the user.
279
+
280
+ **Reach for the cheap paintbrushes first** — this is the whole economics of the loop:
281
+
282
+ 1. **Raw clips** — cut and remix footage they already have or hunt from a source URL
283
+ (`vidfarm raws scan <src> --prompt "…"`). Free local compute. The workhorse.
284
+ 2. **HTML/JS hyperframes** — text, titles, graphics, data-viz as CSS/GSAP layers. Free,
285
+ deterministic, re-skinnable. **Never use AI video to put text on screen.**
286
+ 3. **Free stock** — `vidfarm media search "<meaning>" --type bgm|sfx|image|video`. Openverse and
287
+ Iconify need no account at all, so they always work in `minimize`.
288
+ 4. **Pure AI generation** — last resort, and it's what costs money.
289
+
290
+ A rough shape of one task (adapt to the brief; the director skill has the real workflows):
291
+
292
+ ```bash
293
+ vidfarm discover "<what the brief asks for>" # find a template
294
+ vidfarm fork <template_id> # → forkId
295
+ vidfarm pull <forkId> --dir tasks/<task-id> # composition + grounding onto disk
296
+ # …edit: set-text / set-media / place / captions / raws — per the director skill…
297
+ vidfarm lint tasks/<task-id> # catch timing + media errors before rendering
298
+ vidfarm render <forkId> --dir tasks/<task-id> --out videos/<task-id>.mp4 --wait # LOCAL = $0.00
299
+ ```
300
+
301
+ **Free manual image generation** (if they said yes in Phase 1): when the video genuinely needs a
302
+ custom graphic that stock and masking can't cover, don't quietly spend and don't say "not possible."
303
+ Write the prompt, ask them to run it in a free generator, and ask for **one sheet containing every
304
+ graphic you need**, gridded on a flat pure-green background (`#00FF00`), no text — one round trip
305
+ instead of five. Then split it locally for $0:
306
+
307
+ ```bash
308
+ vidfarm put-file ./sheet.png
309
+ vidfarm mask ./sheet.png --crop x,y,w,h --flat "#00FF00" --out prop-a.png # once per element
310
+ ```
311
+
312
+ **Watch the finished video before you submit it.** Check `vidfarm stills` frames or open the MP4.
313
+ Ask yourself whether it actually satisfies `terms`. If it doesn't, fix it or decline the task —
314
+ a rejected proof carries a reputation penalty (`low_quality` 1x, `incomplete` 2x, `fake_proof` 5x)
315
+ and those follow their wallet across every gig on the network.
316
+
317
+ ---
318
+
319
+ ## Phase 7 — Submit the proof as a URL, never as a video file
320
+
321
+ **Do not upload video files to DollarPlatoon.** Videos are large, the upload is slow and fragile,
322
+ and the client just wants to watch it. Instead, put the video on Vidfarm and submit the public link
323
+ as a text proof. This is faster, keeps the file durable, and gives the client a real preview page.
324
+
325
+ ```bash
326
+ vidfarm approve --video ./videos/<task-id>.mp4 --caption "<gig title> — task <task-id>"
327
+ # uploads to durable My Files (up to 200 MB, direct to S3) and prints: share_url
328
+ ```
329
+
330
+ `vidfarm approve` is the right command because it stores the MP4 in **durable My Files**, not the
331
+ 30-day temp store — a temp-hosted proof would 404 on the client's share page a month later, after
332
+ they've already paid. Then:
333
+
334
+ ```js
335
+ await submitProof(gigId, {
336
+ mailbox_id: MAILBOX_ID,
337
+ task_identifier: task.id, // queue gigs: the polled task's id. others: the task's own ref.
338
+ proofs: [shareUrl], // the public vidfarm.cc page. Add the direct MP4 viewUrl too if the client asks for a file.
339
+ });
340
+ ```
341
+
342
+ Check the response:
343
+
344
+ - **`warning: "available funds are less than the task price"`** — the proof was accepted but
345
+ **cannot be paid until the client deposits more.** Log it and tell the user; don't keep grinding
346
+ out videos for a gig that can't pay. This is the single most important warning in the loop.
347
+ - **`429`** — you hit the rate limit. The `rate_limit` object has `retry_at`. Sleep, don't retry.
348
+ - **`409`** — duplicate `task_identifier`. You already submitted this one; don't resubmit.
349
+ - **`410`** — the task expired while you were rendering. Ask the client for an extension, or move on.
350
+
351
+ Proofs auto-approve after `review_timeout` (default 48h) if the client doesn't review. Price is
352
+ locked at submission time, so a client lowering the price later can't reduce what they already owe.
353
+
354
+ ---
355
+
356
+ ## Phase 8 — Run the loop
357
+
358
+ Once one task has gone end-to-end and the user has seen the video, let it run. Keep it dull and
359
+ observable:
360
+
361
+ - **Respect the rate limit** — treat `default_rate_limit_count` / `_minutes` as a hard ceiling, and
362
+ back off on any `429` until `retry_at`.
363
+ - **One task at a time** unless the user asked otherwise. Renders are CPU-heavy; a laptop running
364
+ three at once produces three late videos instead of one good one.
365
+ - **Never claim more than you can finish before `task_timeout`.**
366
+ - **Log every attempt to `ledger.jsonl`, including failures.** Silent failures are how a loop
367
+ quietly earns nothing for a week.
368
+ - **Stop the loop and tell the user** if: proofs start getting rejected, funds warnings appear, the
369
+ gig goes `paused`, or three tasks in a row fail. Don't burn their machine and their reputation
370
+ overnight on a broken pipeline.
371
+ - Their computer must be awake. Schedule it with `cron` / Task Scheduler / a `launchd` job, or just
372
+ a `while` loop with a sleep — whatever they'll actually be able to restart themselves.
373
+
374
+ **Report to the user each session** (and write the same into `NOTES.md`): tasks done, videos
375
+ submitted with their share URLs, proofs approved vs pending, anything the client rejected and why,
376
+ what they earned (or are owed, for off-chain), and what it cost — AI spend should be `$0.00` in
377
+ `minimize`, and if it isn't, that's the first thing to say.
378
+
379
+ ---
380
+
381
+ ## Honesty rules
382
+
383
+ These exist because the loop touches real customers, real reputation, and real money.
384
+
385
+ - **Never fake a proof.** Every submitted URL must point at a video you actually rendered for that
386
+ task. `fake_proof` is a 5x reputation penalty and it is permanent and wallet-anchored.
387
+ - **Never promise earnings.** Pay is per approved proof and demand-driven — quiet days pay nothing.
388
+ If the user asks "how much will I make", give them the gig's `price` and the honest answer that it
389
+ depends entirely on how many tasks the client posts.
390
+ - **Never spend their money silently.** In `minimize`, a billed step stops and asks. Report actual
391
+ spend, not estimates — `vidfarm wallet` has the truth.
392
+ - **Report what really happened.** If a render failed, a proof was rejected, or you skipped tasks,
393
+ say so plainly with the error. A loop the user can't trust is worse than no loop.
394
+ - **Don't work prohibited gigs.** If a gig's `terms` ask for something illegal, deceptive, or in a
395
+ prohibited vertical, refuse and tell the user why. DollarPlatoon has no dispute resolution — you
396
+ are their only check.
397
+
398
+ ---
399
+
400
+ ## When something breaks
401
+
402
+ | Symptom | Likely cause | Fix |
403
+ |---|---|---|
404
+ | Poll returns no tasks | Gig isn't `distribution: "queue"` | Check `gig.distribution`, use Phase 4's matching path |
405
+ | `403` on join | Missing or revoked invite token | Check `GET /mailboxes/mine` first — they may already be in; otherwise ask for the full invite URL again |
406
+ | User only has a mailbox id | Already joined; no invite needed | `GET /mailboxes/mine`, match on `id`, use its `gig_id` and skip the join |
407
+ | Mailbox `status: "inactive"` | Client disabled them | Stop the loop; they have to talk to the client |
408
+ | Join returns `pending_approval` | Gig has `requires_approval` | Wait for the client; don't poll |
409
+ | `429` on poll or proof | Worker rate limit | Sleep until `rate_limit.retry_at` |
410
+ | `409` on proof | Duplicate `task_identifier` | You already submitted; check `GET /gigs/:id/proofs` |
411
+ | `410` on proof | Task expired (`task_timeout`) | Ask the client to `extend`, or take a fresh task |
412
+ | Proof accepted with a funds `warning` | Gig treasury is short | Tell the user; pause the loop |
413
+ | Render fails or hangs | Local toolchain | `vidfarm doctor`, then `vidfarm doctor --kill-orphans` |
414
+ | `402` on a vidfarm command | Free plan or `minimize` blocking a billed route | Use the free path the error names |
415
+ | Share URL 404s later | Video went to the temp store | Re-approve with `vidfarm approve --video` (durable My Files) |
416
+
417
+ ---
418
+
419
+ ## More
420
+
421
+ - Explainer: <https://vidfarm.cc/blog/agentic-clipper-loops>
422
+ - Vidfarm skill (start here for anything video): <https://vidfarm.cc/skill.md>
423
+ - DollarPlatoon skill (full REST reference): <https://dollarplatoon.com/skill.md>
424
+ - Vidfarm marketplace: <https://vidfarm.cc/marketplace>
package/dist/src/cli.js CHANGED
@@ -407,8 +407,9 @@ Raws (the third library — mine long-form video into a reusable raws store):
407
407
  save only that exact clipped range.
408
408
 
409
409
  Speech (TTS/STT) — LOCAL-FIRST on your own AI key; --cloud is the explicit backup:
410
- tts "<text>" Text → narration audio file. LOCAL: your OPENAI/GEMINI/
411
- The VOICE STYLE is promptable via --style. OPENROUTER_API_KEY, no cloud job
410
+ tts "<text>" Text → narration audio file. FREE local Kokoro by default in
411
+ The VOICE STYLE is promptable via --style. cost mode minimize/hybrid; else
412
+ your OPENAI/GEMINI/OPENROUTER key
412
413
  --style "<direction>" Voice-style prompt: tone/pacing/accent/emotion/persona
413
414
  (e.g. "calm, warm bedtime narrator", "excited sports announcer")
414
415
  --voice <name> Provider voice preset (openai: alloy/ash/coral…; gemini: Kore/Puck…).
@@ -417,8 +418,13 @@ Speech (TTS/STT) — LOCAL-FIRST on your own AI key; --cloud is the explicit bac
417
418
  --out <file> Output audio path (default tts-<id>.mp3|wav)
418
419
  --provider <p> --model <m> --format mp3|wav
419
420
  --engine local KEYLESS local Kokoro-82M engine (free, no account) — fixed voice
420
- presets (af_heart, am_adam, bf_emma, …), no --style. When NO
421
- provider key is found the default byok engine auto-falls back here.
421
+ presets (af_heart, am_adam, bf_emma, …), no --style. This is the
422
+ DEFAULT in cost mode minimize/hybrid; it also auto-runs when NO
423
+ provider key is found. Asking for a premium voice (--style,
424
+ --provider, --model, --own-key, or a non-Kokoro --voice) opts out,
425
+ as does cost mode rich-ai. If the local engine is unavailable the
426
+ run falls back to your provider key / cloud instead of failing.
427
+ --engine byok Force your own provider key even in minimize/hybrid.
422
428
  --cloud BACKUP: platform job → POST /api/v1/primitives/audio/speech (+ poll).
423
429
  Defaults to ElevenLabs on the platform key (wallet-billed); --voice
424
430
  takes an ElevenLabs voice_id (see: vidfarm voices).
@@ -685,6 +691,8 @@ Cost spectrum (default to the cheapest approach that works; see SKILL.director.m
685
691
  respects it: minimize refuses billed spend without --yes and points you at the free
686
692
  local path; hybrid/rich-ai run but print each op's cost. FREE local engines never gate
687
693
  (local render, tts --engine local, stt --engine whisper, remove-greenscreen --local).
694
+ In minimize AND hybrid, 'vidfarm tts' DEFAULTS to the free local Kokoro voice — ask for
695
+ a premium voice (--style/--voice/--provider/--own-key/--cloud) or rich-ai to opt out.
688
696
 
689
697
  Escape hatch — call ANY route directly:
690
698
  api <METHOD> <path> Raw REST call with auth + pretty errors
@@ -1196,7 +1204,7 @@ function assertApiOk(result, action) {
1196
1204
  const hint = result.status === 401 || result.status === 403
1197
1205
  ? " Check --api-key / VIDFARM_API_KEY and that you own this resource."
1198
1206
  : result.status === 402
1199
- ? " The account is not on a paid plan (or the wallet is empty)."
1207
+ ? " This route is paid-only — your key is valid but the account is on the free plan (or the wallet is empty). Upgrade at /pricing, or keep working locally: `vidfarm serve` and the local render/decompose paths are free."
1200
1208
  : result.status === 429
1201
1209
  ? " Rate-limited; wait and retry."
1202
1210
  : "";
@@ -5133,6 +5141,35 @@ async function runTtsCommand(argv) {
5133
5141
  await runLocalKokoroTts({ text, voice, style, out: parsed.values.out, format: explicitFormat, json });
5134
5142
  return;
5135
5143
  }
5144
+ // COST-MODE DEFAULT: in minimize/hybrid, narration starts on the FREE local
5145
+ // Kokoro engine unless the run asked for a premium/cloud voice (see
5146
+ // wantsPremiumVoice). The BYOK key path is a real fallback, not a downgrade —
5147
+ // if Kokoro is missing its python deps / model weights we fall THROUGH to it
5148
+ // below rather than failing the run.
5149
+ const costMode = resolveCostMode(parsed.values);
5150
+ const premium = wantsPremiumVoice(parsed.values, voice, style);
5151
+ /** Set when the cost-mode default ran Kokoro and it failed — so the keyless
5152
+ * path below doesn't pointlessly retry the same engine. */
5153
+ let kokoroError = null;
5154
+ if (!engine && !wantCloud && costMode.mode !== "pure-ai" && !premium) {
5155
+ try {
5156
+ await runLocalKokoroTts({
5157
+ text,
5158
+ voice,
5159
+ style,
5160
+ out: parsed.values.out,
5161
+ format: explicitFormat,
5162
+ json,
5163
+ costModeDefault: costMode.mode
5164
+ });
5165
+ return;
5166
+ }
5167
+ catch (error) {
5168
+ kokoroError = String(error?.message ?? error).split("\n")[0].slice(0, 200);
5169
+ // stderr, not stdout — keeps --json output parseable when we fall back.
5170
+ console.error(`${DIM}Local Kokoro engine unavailable (${kokoroError}) — falling back to your provider key / cloud TTS.${RESET}`);
5171
+ }
5172
+ }
5136
5173
  if (!wantCloud) {
5137
5174
  // A Gemini voice preset ("Kore", "Puck", …) should find a Gemini-capable
5138
5175
  // key first — openrouter's default TTS model is Gemini-family too.
@@ -5148,6 +5185,14 @@ async function runTtsCommand(argv) {
5148
5185
  const wanted = requestedProvider ?? `${voiceProvider} (implied by --voice "${voice}")`;
5149
5186
  throw new Error(`tts: provider ${wanted} was requested but no matching key is configured. ${speechKeyHint("tts")}`);
5150
5187
  }
5188
+ // Kokoro already ran and failed on the cost-mode default above — don't
5189
+ // retry the same broken engine, and don't dump its raw error. Name both
5190
+ // dead ends (no local deps, no key) with the two ways forward.
5191
+ if (kokoroError) {
5192
+ throw new Error(`tts: no provider key configured AND the free local Kokoro engine is unavailable (${kokoroError}). ` +
5193
+ "Fix one of the two: install the local engine (`pip install kokoro-onnx soundfile`, or point " +
5194
+ `HYPERFRAMES_PYTHON at a venv that has them), or ${speechKeyHint("tts")}`);
5195
+ }
5151
5196
  // Keyless fallback (no explicit provider/voice): rather than erroring
5152
5197
  // (speechKeyHint), speak through the local Kokoro engine so agents
5153
5198
  // always get audio out of the box.
@@ -5237,17 +5282,41 @@ async function runTtsCommand(argv) {
5237
5282
  console.log(`${GREEN}Wrote ${outPath}${RESET} ${DIM}(${mediaUrl})${RESET}`);
5238
5283
  }
5239
5284
  }
5285
+ /** Kokoro voice ids look like af_heart / am_adam / bf_emma. Anything else is a
5286
+ * cloud provider preset (openai "alloy", gemini "Kore", an ElevenLabs id).
5287
+ * Declared as a hoisted function, not a const: `wantsPremiumVoice` runs during
5288
+ * the top-level `main()` await, which is BEFORE this point in module order. */
5289
+ function isKokoroVoice(voice) {
5290
+ return /^[a-z]{2}_[a-z0-9]+$/i.test(voice);
5291
+ }
5292
+ /**
5293
+ * Did this run explicitly ask for a PREMIUM (cloud/BYOK) voice? If so, cost mode
5294
+ * must not silently route it to the free local engine — Kokoro has fixed voice
5295
+ * presets and no style direction, so honoring these flags locally would be a
5296
+ * silent downgrade. Any of: --provider, --model, --style, --own-key, or a
5297
+ * --voice that isn't a Kokoro preset.
5298
+ */
5299
+ function wantsPremiumVoice(values, voice, style) {
5300
+ if (values.provider || values.model || values["own-key"])
5301
+ return true;
5302
+ if (style)
5303
+ return true;
5304
+ if (voice?.trim() && !isKokoroVoice(voice.trim()))
5305
+ return true;
5306
+ return false;
5307
+ }
5240
5308
  // Keyless local TTS via the bundled hyperframes engine (Kokoro-82M ONNX —
5241
- // free, no account, no HeyGen). Used by `--engine local` and as the automatic
5242
- // fallback when no provider key is configured. Kokoro emits WAV natively; an
5243
- // mp3 request transcodes through the bundled ffmpeg.
5309
+ // free, no account, no HeyGen). Used by `--engine local`, as the cost-mode
5310
+ // default in minimize/hybrid, and as the automatic fallback when no provider
5311
+ // key is configured. Kokoro emits WAV natively; an mp3 request transcodes
5312
+ // through the bundled ffmpeg.
5244
5313
  async function runLocalKokoroTts(input) {
5245
5314
  const wantMp3 = input.format === "mp3" || /\.mp3(\?|#|$)/i.test(input.out ?? "");
5246
5315
  // Kokoro voice ids look like af_heart / am_adam / bf_emma. A provider voice
5247
5316
  // (openai "alloy", gemini "Kore") passed through the fallback path would
5248
5317
  // just error — drop it to the default with a note instead.
5249
5318
  let voice = input.voice?.trim() || undefined;
5250
- if (voice && !/^[a-z]{2}_[a-z0-9]+$/i.test(voice)) {
5319
+ if (voice && !isKokoroVoice(voice)) {
5251
5320
  if (!input.json)
5252
5321
  console.log(`${DIM}Voice "${voice}" is a provider voice — the local Kokoro engine has its own presets (af_heart, af_nova, am_adam, bf_emma, …). Using the default voice.${RESET}`);
5253
5322
  voice = undefined;
@@ -5258,8 +5327,12 @@ async function runLocalKokoroTts(input) {
5258
5327
  const outPath = path.resolve(process.cwd(), input.out ?? `tts-${Date.now().toString(36)}.${wantMp3 ? "mp3" : "wav"}`);
5259
5328
  mkdirSync(path.dirname(outPath), { recursive: true });
5260
5329
  const wavPath = wantMp3 ? path.join(mkdtempSync(path.join(tmpdir(), "vidfarm-tts-")), "speech.wav") : outPath;
5261
- if (!input.json)
5330
+ if (!input.json) {
5262
5331
  console.log(`${DIM}Generating speech with the local Kokoro-82M engine (free, no account)…${RESET}`);
5332
+ if (input.costModeDefault) {
5333
+ console.log(`${DIM}(cost mode ${costModeDisplayName(input.costModeDefault)} → free local voice by default. Want a premium voice? add --style/--voice/--provider, or --cloud.)${RESET}`);
5334
+ }
5335
+ }
5263
5336
  const run = await runHyperframesCommand("tts", [input.text, "-o", wavPath, ...(voice ? ["-v", voice] : [])], { stdio: input.json ? "capture" : "inherit" });
5264
5337
  if (run.code !== 0 || !existsSync(wavPath)) {
5265
5338
  const detail = run.stderr.trim().split("\n").pop() ?? "";
@@ -5276,7 +5349,7 @@ async function runLocalKokoroTts(input) {
5276
5349
  }
5277
5350
  const bytes = safeSize(outPath);
5278
5351
  if (input.json) {
5279
- printJson({ ok: true, mode: "local-engine", engine: "kokoro-82m", provider: null, voice: voice ?? "af_heart", fallback: Boolean(input.fallback), content_type: wantMp3 ? "audio/mpeg" : "audio/wav", bytes, out: outPath });
5352
+ printJson({ ok: true, mode: "local-engine", engine: "kokoro-82m", provider: null, voice: voice ?? "af_heart", fallback: Boolean(input.fallback), cost_mode_default: input.costModeDefault ?? null, content_type: wantMp3 ? "audio/mpeg" : "audio/wav", bytes, out: outPath });
5280
5353
  }
5281
5354
  else {
5282
5355
  console.log(`${GREEN}Wrote ${outPath}${RESET} ${DIM}(local Kokoro-82M — free, no account, ${formatBytes(bytes)})${RESET}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@officexapp/vidfarm-devcli",
3
- "version": "0.21.22",
3
+ "version": "0.21.24",
4
4
  "description": "Local bridge for the Vidfarm Trackpad Editor. `vidfarm serve <template_id>` boots the FULL editor on localhost (disk-backed records/storage, free in-process render); edit composition.html on disk (Claude Code, Codex, etc.) and the browser live-morphs it.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -50,6 +50,8 @@
50
50
  "README.md",
51
51
  "SKILL.md",
52
52
  "SKILL.director.md",
53
+ "clipper.md",
54
+ "update.md",
53
55
  "!readme.secret.md",
54
56
  "!**/*.secret.*"
55
57
  ],
package/update.md ADDED
@@ -0,0 +1,143 @@
1
+ ---
2
+ name: vidfarm-update
3
+ description: How to update Vidfarm — clean-uninstall the `vidfarm` agent skill and the `vidfarm-devcli`, then reinstall the latest of both from vidfarm.cc/skill.md and npm. Read this whenever the user asks to update/upgrade Vidfarm, says their vidfarm skill or devcli is out of date, hits a command or route that "used to work", or gets a version-mismatch or unknown-command error. Also carries breaking-change notes per version — check it before assuming an old workflow still applies.
4
+ ---
5
+
6
+ # Updating Vidfarm
7
+
8
+ **Read this file live at <https://vidfarm.cc/update.md> every time you update — don't work from a
9
+ cached copy.** It carries the current breaking-change notes, and an update that skips them is how a
10
+ working setup quietly breaks.
11
+
12
+ Vidfarm ships as two moving parts, and they are versioned together:
13
+
14
+ 1. **The `vidfarm` agent skill** — the director workflows on disk, installed under
15
+ `.agents/skills/vidfarm/` with a `.claude/skills/vidfarm` symlink.
16
+ 2. **The `vidfarm-devcli`** — the `vidfarm` command, published as `@officexapp/vidfarm-devcli`.
17
+
18
+ Updating one but not the other is the most common cause of "this command doesn't exist" and "the
19
+ skill says to do X but it fails" — the skill documents routes and flags the installed CLI may not
20
+ have yet. **Always update both together.**
21
+
22
+ ---
23
+
24
+ ## The update, in four steps
25
+
26
+ ### 1. Record what they're on now
27
+
28
+ Worth ten seconds, because it tells you which breaking-change notes below actually apply:
29
+
30
+ ```bash
31
+ vidfarm --version # devcli version
32
+ npm view @officexapp/vidfarm-devcli version # latest published
33
+ cat skills-lock.json # which skill packs are installed, and from where
34
+ ```
35
+
36
+ ### 2. Remove the old skill
37
+
38
+ There is no `skills remove` command — a skill is just a directory plus a lock entry, so delete both.
39
+ Run this from the project root (the directory holding `.agents/skills`):
40
+
41
+ ```bash
42
+ rm -rf .agents/skills/vidfarm .claude/skills/vidfarm
43
+ ```
44
+
45
+ Then drop `"vidfarm"` from the `skills` object in `skills-lock.json` (leave any other packs alone —
46
+ `hyperframes`, `vidfarm-media`, etc. are separate entries and other tools write to this file too).
47
+
48
+ A clean removal matters more than it looks: `skills add` overwrites files it knows about, so a file
49
+ that was *deleted upstream* survives an in-place refresh and keeps feeding the agent stale
50
+ instructions. Deleting the directory is what guarantees you end up with exactly the current pack.
51
+
52
+ ### 3. Reinstall the latest of both
53
+
54
+ ```bash
55
+ npm i -g @officexapp/vidfarm-devcli@latest # the CLI
56
+ vidfarm skills add vidfarm # the skill pack, fetched from vidfarm.cc/skill-pack/*
57
+ ```
58
+
59
+ `vidfarm skills update` refreshes every installed pack in place — that's the quick path when nothing
60
+ has been removed upstream. The uninstall-then-add above is the thorough one; prefer it when the user
61
+ is troubleshooting, is several versions behind, or when a note below says to.
62
+
63
+ If the user also has the HyperFrames engine installed, bring it along — Vidfarm renders *are*
64
+ HyperFrames renders, and a version skew between them shows up as render failures:
65
+
66
+ ```bash
67
+ npm i -g hyperframes@latest
68
+ ```
69
+
70
+ ### 4. Verify before declaring success
71
+
72
+ ```bash
73
+ vidfarm --version # should match the npm latest from step 1
74
+ vidfarm doctor # ffmpeg + hyperframes + Chrome + auth, all in one check
75
+ vidfarm whoami # confirms the API key survived the reinstall, prints the active cost mode
76
+ vidfarm skills list # confirms the vidfarm pack is present, and from which source
77
+ ```
78
+
79
+ `vidfarm doctor` is the real test — a version number proves the package installed, not that the
80
+ toolchain works. If `whoami` fails, the login just needs redoing: `vidfarm login <email>`, or
81
+ `vidfarm login --api-key "$VIDFARM_API_KEY"`. Credentials live in `~/.vidfarm/` and normally survive
82
+ an update untouched.
83
+
84
+ Then **re-read <https://vidfarm.cc/skill.md>** and follow it to the director skill. The freshly
85
+ installed pack may route differently than the one you loaded at the start of the session.
86
+
87
+ ---
88
+
89
+ ## Breaking changes and version notes
90
+
91
+ Check this section against the version from step 1 and apply anything between their version and the
92
+ latest. If nothing here mentions their version, a plain update is safe.
93
+
94
+ **Current published devcli: `0.21.23`.**
95
+
96
+ - **0.21.x — the package moved scopes.** The devcli is `@officexapp/vidfarm-devcli`. The old
97
+ `@mevdragon/vidfarm-devcli` is not maintained. If `npm ls -g` shows the `@mevdragon` package,
98
+ uninstall it explicitly, or two `vidfarm` binaries will fight over the same name on `PATH`:
99
+
100
+ ```bash
101
+ npm rm -g @mevdragon/vidfarm-devcli
102
+ npm i -g @officexapp/vidfarm-devcli@latest
103
+ which vidfarm && vidfarm --version # confirm the survivor is the new one
104
+ ```
105
+
106
+ - **`update-skill` is retired.** It now warns and delegates. Use `vidfarm skills add vidfarm`.
107
+ `--global` and `--platform` are gone; scope an install with `--dir <project-root>` instead.
108
+
109
+ - **0.6.0 and earlier `vidfarm serve` is broken when installed from npm** (the tarball shipped
110
+ without its runtime). Anyone still on 0.5.x must update — there is no workaround.
111
+
112
+ - **Cost mode is remembered across updates.** It lives in `~/.vidfarm/cost-mode.json`, outside the
113
+ package, so an update never silently changes their spending posture. Confirm it with `vidfarm
114
+ whoami` anyway and say what it is out loud — it decides whether billed commands are refused.
115
+
116
+ ---
117
+
118
+ ## If the update leaves things broken
119
+
120
+ | Symptom | Cause | Fix |
121
+ |---|---|---|
122
+ | `vidfarm: command not found` | Global bin not on `PATH`, or the install failed | `npm ls -g --depth=0`, then check `npm bin -g` is on `PATH` |
123
+ | Wrong version after installing | An older `@mevdragon` copy is shadowing it | `npm rm -g @mevdragon/vidfarm-devcli`, then `which vidfarm` |
124
+ | `EACCES` on global install | npm's global dir needs root | Use a Node version manager (nvm/fnm) rather than `sudo npm` |
125
+ | Skill still shows old instructions | The directory wasn't actually removed | Redo step 2, confirm `.agents/skills/vidfarm` is gone, then `skills add` |
126
+ | `skills add` fails to fetch | Offline or the host is unreachable | It falls back to copies bundled in the installed package — check `vidfarm skills list` for the source it used |
127
+ | `401` / `whoami` fails | Credential not carried over | `vidfarm login <email>` (email code), or `--api-key` |
128
+ | Renders fail after updating | devcli / hyperframes version skew | `npm i -g hyperframes@latest`, then `vidfarm doctor` |
129
+ | A documented route 404s | Skill is newer than the deployed backend | Report it — don't work around it by calling the renderer or an internal route directly |
130
+
131
+ **Tell the user plainly what changed.** After updating, say which versions they went from and to,
132
+ and name anything from the breaking-changes section that affects them. If the update failed partway,
133
+ say so with the error rather than reporting success — a half-updated toolchain fails later, in the
134
+ middle of a render, where it's much harder to diagnose.
135
+
136
+ ---
137
+
138
+ ## More
139
+
140
+ - Entry skill (start here after updating): <https://vidfarm.cc/skill.md>
141
+ - Full director skill: <https://vidfarm.cc/skill/vidfarm>
142
+ - Skill pack index (what else is installable): <https://vidfarm.cc/skill-pack/index.json>
143
+ - Agentic clipper loop: <https://vidfarm.cc/clipper.md>