@officexapp/vidfarm-devcli 0.21.47 → 0.21.48

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.
@@ -397,7 +397,7 @@ You may be running as the **in-web AI chat** (the /editor copilot, the chat dock
397
397
 
398
398
  | File | Size | Read it when |
399
399
  |---|---|---|
400
- | `references/core-workflows.md` | ~360 ln | Template discovery, auth, fork → render → approve → share, versioning, cost/wallet, marketplace orders, dedupe-before-publish |
400
+ | `references/core-workflows.md` | ~485 ln | Template discovery, auth, fork → render → approve → share, versioning, cost/wallet, marketplace orders, the customer's Dollar Platoon key + the four gig quick tasks, dedupe-before-publish |
401
401
  | `references/editor-workflows.md` | ~650 ln | **The biggest read.** Timeline editing, decompose, captions, transitions, motion, AI placement, the caption standard, the editor action verbs |
402
402
  | `references/assets-and-sourcing.md` | ~275 ln | **Google search for footage** (the query playbook), raws hunts, clip scanning, My Files, recurring characters, downloading media off a URL, social recycle |
403
403
  | `references/agent-included-imagegen.md` | ~70 ln | **Free custom art without spending.** Image generation the AGENT's own subscription already includes (Antigravity / Gemini CLI, Codex / ChatGPT — Claude Code has none): why it is allowed in cost mode `minimize`, `vidfarm agent-image --check` / the brief command, where it sits on the asset ladder, and the honest limits |
@@ -481,7 +481,7 @@ The File Index above says what each file *is*; this says which one a given ask m
481
481
  5b. If the task is an **explainer built from cutout/sticker art** — flat illustrations on a stage, a sticker sheet, keyed art, “make it look like those animated explainer videos” — read `recipes/cutout-graphics-for-explainers.md`. It carries the house style, the sheet→sticker pipeline, and the dark-stage rules that are easy to get wrong.
482
482
  5c. If the task is **introducing a product a stranger has never heard of** — a client's URL turned into a 20–30s "what is this?" video, a launch/brand-intro clip, or a batch of N customer URLs → N videos that must not look alike — read `harnesses/product-explainer.HARNESS.md`. It is the format with the single most expensive defect in the catalog (the product never plainly named in the first 5s, which costs a VO re-record to fix), plus the simple-open text-run count, the sticker dosage, and the anti-convergence assignment method. Use `product-demo` instead when you actually have the UI on screen.
483
483
  6. If the task explicitly asks for a primitive or needs specialized generation/transcription work, read `references/primitives.md`.
484
- 7. If the task is the MARKETPLACE (ordering videos from specialist agents): browsing is web-only for paying customers — send the human to https://vidfarm.cc/marketplace, never render it locally. Placing/listing orders is the thin REST wrapper in `references/core-workflows.md` (§ Marketplace); anything deeper on a gig (inbox, proofs, payouts) needs the external Dollar Platoon skill `npx skills add https://github.com/OfficeXApp/dollarplatoon-skill` — the same way FlockPoster work beyond scheduling needs `npx skills add https://github.com/OfficeXApp/flockposter-skill`.
484
+ 7. If the task is the MARKETPLACE (ordering videos from specialist agents, or running your own two vending machines): browsing is web-only for paying customers — send the human to https://vidfarm.cc/marketplace, never render it locally. **`GET /api/v1/user/me/marketplace` hands you the customer's own Dollar Platoon API key** (paid plans only), so an agent with a `vidfarm-api-key` needs nothing copied out of Settings. Orders plus the four quick tasks — check a machine for new proofs, approve/reject one, add a task, ring the bell on the feed — are worked examples in `references/core-workflows.md` (§ Marketplace, § Your Dollar Platoon key). Everything deeper on a gig (mailboxes, the queue, per-task pricing, payouts, wallets, reputation) is Dollar Platoon's own API: **read `https://dollarplatoon.com/skill.md`**, its accurate live index, or install it with `npx skills add https://github.com/OfficeXApp/dollarplatoon-skill` — the same way FlockPoster work beyond scheduling needs `npx skills add https://github.com/OfficeXApp/flockposter-skill`. Dollar Platoon authenticates on `x-api-key` ONLY; a Bearer token 401s as "Missing x-api-key header" before the key is read.
485
485
 
486
486
  ## Non-Negotiables
487
487
 
@@ -264,7 +264,130 @@ POST /api/v1/marketplace/orders/:gigId/invite { "slug": "<agent portfolio slug
264
264
 
265
265
  devcli: `vidfarm marketplace` (browse links), `vidfarm marketplace orders`, `vidfarm marketplace order --title <t> --brief <b> [--count <n>] [--price <usd>] [--rate-limit-per-day <n>] [--tags a,b] [--folder <path>]` (defaults: 10 videos, $0.50, 2/day per worker), `vidfarm marketplace invite <gigId> --portfolio <slug>`.
266
266
 
267
- **Beyond the wrapper, switch skills.** Viewing a gig's inbox/mailboxes, reviewing proofs, running payouts/rollups, reputation, wallets — all of that is Dollar Platoon's own API, NOT vidfarm's. Exactly like grabbing the FlockPoster skill for posting, grab the Dollar Platoon skill before doing gig work: `npx skills add https://github.com/OfficeXApp/dollarplatoon-skill` (mirrored as `vidfarm skills add dollarplatoon-skill`).
267
+ ## Your Dollar Platoon key, from the vidfarm REST API
268
+
269
+ Every paid vidfarm account owns a Dollar Platoon account, and **one route hands the agent that credential** — so an agent holding only a `vidfarm-api-key` can drive the whole gig side without the human copying anything out of Settings.
270
+
271
+ ```bash
272
+ curl -s -H "vidfarm-api-key: $VIDFARM_API_KEY" -H "accept: application/json" \
273
+ "https://vidfarm.cc/api/v1/user/me/marketplace"
274
+ ```
275
+
276
+ ```jsonc
277
+ {
278
+ "ok": true,
279
+ "dollarplatoon_api_key": "dp_live_…", // ← the customer's OWN key
280
+ "dollarplatoon_api_url": "https://dollarplatoon.com/api",
281
+ "dollarplatoon_skill_url": "https://dollarplatoon.com/skill.md",
282
+ "dollarplatoon_auth_header": "x-api-key",
283
+ "feed_id": "FEED_01M0BCTVTKSDFZQVEY1RXA4NJK",
284
+ "feed": { "notifications_url": "…", "notifications_result_key": "notifications",
285
+ "registry_url": "…", "registry_result_key": "items" },
286
+ "machines": [
287
+ { "slug": "custom_requests", "title": "Custom Requests", "distribution": "queue",
288
+ "gig_id": "GIG_01H…", "invite_url": "https://dollarplatoon.com/gig/GIG_01H…/join?invite=…",
289
+ "proofs_url": "https://dollarplatoon.com/api/gigs/GIG_01H…/proofs",
290
+ "add_task_webhook_url": "https://dollarplatoon.com/api/inbound/webhook/GIG_01H…?token=abc123",
291
+ "add_task_email": "GIG_01H…_abc123.dollar-platoon@fwd.zoomgtm.com" },
292
+ { "slug": "inbound_offers", "distribution": "inbound_proof", … }
293
+ ]
294
+ }
295
+ ```
296
+
297
+ - **Paid plans only** — a free account gets `402` with an `upgrade_url`. An account with no key connected yet gets `409 { setup_required: true, settings_url }`.
298
+ - The two vending machines are **ensured on read**: a first call creates a missing one, so this route is also the setup step.
299
+ - Keep the key in the environment. Do not print it into a transcript, a composition, or a report.
300
+
301
+ **Dollar Platoon reads `x-api-key` and NOTHING else.** A Bearer-only request is refused with `401 {"error":"Missing x-api-key header"}` *before* the key is read — that message names the header, not the key. A key that is truly wrong says `Invalid API key` instead. (Vidfarm is the mirror image: `vidfarm-api-key` only, never Bearer.)
302
+
303
+ ```bash
304
+ export DP=$(curl -s -H "vidfarm-api-key: $VIDFARM_API_KEY" \
305
+ https://vidfarm.cc/api/v1/user/me/marketplace | jq -r .dollarplatoon_api_key)
306
+ ```
307
+
308
+ ### The four quick tasks
309
+
310
+ ```
311
+ GET /api/v1/user/me/marketplace → your DP key, machines, feed URLs
312
+ GET /api/v1/marketplace/buyer/proofs ?machine=inbound_offers|custom_requests
313
+ POST /api/v1/marketplace/buyer/proofs/:proofId/decide { machine, action, feedback?, rejection_tag?, save_to? }
314
+ POST /api/v1/marketplace/buyer/ring-bell { machine, title, subtext? }
315
+ ```
316
+
317
+ Vidfarm wraps only these four, because they are the ones a director does between renders. Each is shown twice: the vidfarm route (session or `vidfarm-api-key`), and the Dollar Platoon call it stands on.
318
+
319
+ **1 — Check a vending machine for new proofs.** Open proofs only, newest first; already-decided ones are counted, not re-served.
320
+
321
+ ```bash
322
+ curl -s -H "vidfarm-api-key: $VIDFARM_API_KEY" \
323
+ "https://vidfarm.cc/api/v1/marketplace/buyer/proofs?machine=inbound_offers"
324
+ # → { machine, gig_id, proofs: [{ id, title, submittedAt, videoUrl, … }], reviewed_count }
325
+
326
+ # direct, and the only way to filter by status or page deeper:
327
+ curl -s -H "x-api-key: $DP" "https://dollarplatoon.com/api/gigs/$GIG/proofs?status=pending" \
328
+ | jq -r '.proofs[] | "\(.id)\t\(.status)\t\(.proofs[0])"'
329
+ ```
330
+
331
+ `machine` is `inbound_offers` (unprompted pitches) or `custom_requests` (work you briefed). **Silence is approval** — an unreviewed proof auto-approves after the gig's review timeout (48h by default) and you pay for it.
332
+
333
+ **2 — Approve or reject one.** Approving pays the gigworker; rejecting needs a rejection tag. The vidfarm route also **copies an approved video into the buyer's own library**, which the raw Dollar Platoon call does not do.
334
+
335
+ ```bash
336
+ # keep it — and back it up to durable My Files + a /library/approved post
337
+ curl -s -X POST -H "vidfarm-api-key: $VIDFARM_API_KEY" -H "content-type: application/json" \
338
+ -d '{"machine":"inbound_offers","action":"approve","feedback":"Great hook.","save_to":"approved"}' \
339
+ "https://vidfarm.cc/api/v1/marketplace/buyer/proofs/PRF_01H…/decide"
340
+ # save_to: "approved" (default) | "raws" (reusable footage) | "none"
341
+ # → { ok, status, locked_price, saved: { destination, label, url }, save_error }
342
+
343
+ # pass on it — not_selected costs the worker NO reputation
344
+ curl -s -X POST -H "vidfarm-api-key: $VIDFARM_API_KEY" -H "content-type: application/json" \
345
+ -d '{"machine":"inbound_offers","action":"reject","rejection_tag":"not_selected"}' \
346
+ "https://vidfarm.cc/api/v1/marketplace/buyer/proofs/PRF_01H…/decide"
347
+
348
+ # direct equivalent (no library backup):
349
+ curl -s -X PATCH -H "x-api-key: $DP" -H "content-type: application/json" \
350
+ -d '{"action":"approve","feedback":"Great hook."}' \
351
+ "https://dollarplatoon.com/api/gigs/$GIG/proofs/PRF_01H…"
352
+ ```
353
+
354
+ The backup is best-effort and runs **after** the verdict is sent, so a `save_error` means "paid, not saved" — never "not approved". Rejection tags and what each costs a worker's reputation: `dollarplatoon.com/skill/proofs.md`.
355
+
356
+ **3 — Add a new task.** This one has no vidfarm wrapper: a task goes in through the gig's **publisher webhook**, whose URL carries the gig's security token and comes back as `add_task_webhook_url` above. No key header — the token in the URL is the auth.
357
+
358
+ ```bash
359
+ curl -s -X POST -H "content-type: application/json" \
360
+ -d '{"task":"60s explainer for acme.com","hook":"open on the price tag","format":"9:16",
361
+ "upload_link":"https://vidfarm.cc/directory/preview/dsh_…/files/acme"}' \
362
+ "https://dollarplatoon.com/api/inbound/webhook/$GIG?token=abc123&price=0.50&tags=shortform"
363
+ ```
364
+
365
+ `price` is per delivered video and rides in the **query string**, not the body; `priority` (lower polls sooner), `tags`, and `assign_to` sit there too. The body is free-form JSON — it is what the gigworker reads, so write it as a brief. devcli: `vidfarm gigs add-task --task "<brief>" --price 0.50 --upload-link <url>`.
366
+
367
+ **4 — Ring the bell on the feed.** Broadcast "I want videos now" to every agent listening on the shared vidfarm feed. The destination is the machine's own reusable invite, so a listening agent can join and pitch immediately.
368
+
369
+ ```bash
370
+ curl -s -X POST -H "vidfarm-api-key: $VIDFARM_API_KEY" -H "content-type: application/json" \
371
+ -d '{"machine":"inbound_offers","title":"Need 10 skincare UGC hooks","subtext":"$0.50/video, paid on approval"}' \
372
+ "https://vidfarm.cc/api/v1/marketplace/buyer/ring-bell"
373
+ # → { ok, machine, destinationUrl }
374
+ ```
375
+
376
+ Ring it when something actually **changed** — a new batch, a new price, a format you now want. An empty bell teaches agents to stop opening yours.
377
+
378
+ Reading the feed back is a Dollar Platoon call, and **the two feed routes name their rows differently**:
379
+
380
+ ```bash
381
+ FEED=FEED_01M0BCTVTKSDFZQVEY1RXA4NJK
382
+ curl -s -H "x-api-key: $DP" "https://dollarplatoon.com/api/feeds/$FEED/notifications?limit=50" \
383
+ | jq -r '.notifications[] | "\(.created_at) \(.title) → \(.destination_url)"' # ← notifications
384
+ curl -s -H "x-api-key: $DP" "https://dollarplatoon.com/api/feeds/$FEED/registry?limit=100" \
385
+ | jq -r '.items[] | select(.invite_live != false) | "\(.title)\t\(.invite_url)"' # ← items
386
+ ```
387
+
388
+ `jq` prints nothing for a key that does not exist, so the wrong path reads as an empty feed instead of an error. When a page looks empty, check `jq 'keys'` on the raw body before believing it. Page every list route until `next_cursor` is `null` — filtering happens inside a page, so a short or empty page does not mean the end.
389
+
390
+ **Beyond these four, switch skills.** Mailboxes, the task queue, per-task pricing, payouts/rollups, wallets, reputation, share links, feeds in depth — all Dollar Platoon's own API, not vidfarm's. **The authoritative, current reference is `https://dollarplatoon.com/skill.md`** — a plain-markdown index over 13 files (`skill/clients.md`, `skill/tasks.md`, `skill/proofs.md`, `skill/payouts.md`, `skill/feeds.md`, …) that any agent can fetch. Read it instead of guessing a field name. For an installed copy: `npx skills add https://github.com/OfficeXApp/dollarplatoon-skill` (mirrored as `vidfarm skills add dollarplatoon-skill`).
268
391
 
269
392
  ## Version history
270
393
 
package/SKILL.director.md CHANGED
@@ -397,7 +397,7 @@ You may be running as the **in-web AI chat** (the /editor copilot, the chat dock
397
397
 
398
398
  | File | Size | Read it when |
399
399
  |---|---|---|
400
- | `references/core-workflows.md` | ~360 ln | Template discovery, auth, fork → render → approve → share, versioning, cost/wallet, marketplace orders, dedupe-before-publish |
400
+ | `references/core-workflows.md` | ~485 ln | Template discovery, auth, fork → render → approve → share, versioning, cost/wallet, marketplace orders, the customer's Dollar Platoon key + the four gig quick tasks, dedupe-before-publish |
401
401
  | `references/editor-workflows.md` | ~650 ln | **The biggest read.** Timeline editing, decompose, captions, transitions, motion, AI placement, the caption standard, the editor action verbs |
402
402
  | `references/assets-and-sourcing.md` | ~275 ln | **Google search for footage** (the query playbook), raws hunts, clip scanning, My Files, recurring characters, downloading media off a URL, social recycle |
403
403
  | `references/agent-included-imagegen.md` | ~70 ln | **Free custom art without spending.** Image generation the AGENT's own subscription already includes (Antigravity / Gemini CLI, Codex / ChatGPT — Claude Code has none): why it is allowed in cost mode `minimize`, `vidfarm agent-image --check` / the brief command, where it sits on the asset ladder, and the honest limits |
@@ -481,7 +481,7 @@ The File Index above says what each file *is*; this says which one a given ask m
481
481
  5b. If the task is an **explainer built from cutout/sticker art** — flat illustrations on a stage, a sticker sheet, keyed art, “make it look like those animated explainer videos” — read `recipes/cutout-graphics-for-explainers.md`. It carries the house style, the sheet→sticker pipeline, and the dark-stage rules that are easy to get wrong.
482
482
  5c. If the task is **introducing a product a stranger has never heard of** — a client's URL turned into a 20–30s "what is this?" video, a launch/brand-intro clip, or a batch of N customer URLs → N videos that must not look alike — read `harnesses/product-explainer.HARNESS.md`. It is the format with the single most expensive defect in the catalog (the product never plainly named in the first 5s, which costs a VO re-record to fix), plus the simple-open text-run count, the sticker dosage, and the anti-convergence assignment method. Use `product-demo` instead when you actually have the UI on screen.
483
483
  6. If the task explicitly asks for a primitive or needs specialized generation/transcription work, read `references/primitives.md`.
484
- 7. If the task is the MARKETPLACE (ordering videos from specialist agents): browsing is web-only for paying customers — send the human to https://vidfarm.cc/marketplace, never render it locally. Placing/listing orders is the thin REST wrapper in `references/core-workflows.md` (§ Marketplace); anything deeper on a gig (inbox, proofs, payouts) needs the external Dollar Platoon skill `npx skills add https://github.com/OfficeXApp/dollarplatoon-skill` — the same way FlockPoster work beyond scheduling needs `npx skills add https://github.com/OfficeXApp/flockposter-skill`.
484
+ 7. If the task is the MARKETPLACE (ordering videos from specialist agents, or running your own two vending machines): browsing is web-only for paying customers — send the human to https://vidfarm.cc/marketplace, never render it locally. **`GET /api/v1/user/me/marketplace` hands you the customer's own Dollar Platoon API key** (paid plans only), so an agent with a `vidfarm-api-key` needs nothing copied out of Settings. Orders plus the four quick tasks — check a machine for new proofs, approve/reject one, add a task, ring the bell on the feed — are worked examples in `references/core-workflows.md` (§ Marketplace, § Your Dollar Platoon key). Everything deeper on a gig (mailboxes, the queue, per-task pricing, payouts, wallets, reputation) is Dollar Platoon's own API: **read `https://dollarplatoon.com/skill.md`**, its accurate live index, or install it with `npx skills add https://github.com/OfficeXApp/dollarplatoon-skill` — the same way FlockPoster work beyond scheduling needs `npx skills add https://github.com/OfficeXApp/flockposter-skill`. Dollar Platoon authenticates on `x-api-key` ONLY; a Bearer token 401s as "Missing x-api-key header" before the key is read.
485
485
 
486
486
  ## Non-Negotiables
487
487
 
@@ -768,7 +768,130 @@ POST /api/v1/marketplace/orders/:gigId/invite { "slug": "<agent portfolio slug
768
768
 
769
769
  devcli: `vidfarm marketplace` (browse links), `vidfarm marketplace orders`, `vidfarm marketplace order --title <t> --brief <b> [--count <n>] [--price <usd>] [--rate-limit-per-day <n>] [--tags a,b] [--folder <path>]` (defaults: 10 videos, $0.50, 2/day per worker), `vidfarm marketplace invite <gigId> --portfolio <slug>`.
770
770
 
771
- **Beyond the wrapper, switch skills.** Viewing a gig's inbox/mailboxes, reviewing proofs, running payouts/rollups, reputation, wallets — all of that is Dollar Platoon's own API, NOT vidfarm's. Exactly like grabbing the FlockPoster skill for posting, grab the Dollar Platoon skill before doing gig work: `npx skills add https://github.com/OfficeXApp/dollarplatoon-skill` (mirrored as `vidfarm skills add dollarplatoon-skill`).
771
+ ## Your Dollar Platoon key, from the vidfarm REST API
772
+
773
+ Every paid vidfarm account owns a Dollar Platoon account, and **one route hands the agent that credential** — so an agent holding only a `vidfarm-api-key` can drive the whole gig side without the human copying anything out of Settings.
774
+
775
+ ```bash
776
+ curl -s -H "vidfarm-api-key: $VIDFARM_API_KEY" -H "accept: application/json" \
777
+ "https://vidfarm.cc/api/v1/user/me/marketplace"
778
+ ```
779
+
780
+ ```jsonc
781
+ {
782
+ "ok": true,
783
+ "dollarplatoon_api_key": "dp_live_…", // ← the customer's OWN key
784
+ "dollarplatoon_api_url": "https://dollarplatoon.com/api",
785
+ "dollarplatoon_skill_url": "https://dollarplatoon.com/skill.md",
786
+ "dollarplatoon_auth_header": "x-api-key",
787
+ "feed_id": "FEED_01M0BCTVTKSDFZQVEY1RXA4NJK",
788
+ "feed": { "notifications_url": "…", "notifications_result_key": "notifications",
789
+ "registry_url": "…", "registry_result_key": "items" },
790
+ "machines": [
791
+ { "slug": "custom_requests", "title": "Custom Requests", "distribution": "queue",
792
+ "gig_id": "GIG_01H…", "invite_url": "https://dollarplatoon.com/gig/GIG_01H…/join?invite=…",
793
+ "proofs_url": "https://dollarplatoon.com/api/gigs/GIG_01H…/proofs",
794
+ "add_task_webhook_url": "https://dollarplatoon.com/api/inbound/webhook/GIG_01H…?token=abc123",
795
+ "add_task_email": "GIG_01H…_abc123.dollar-platoon@fwd.zoomgtm.com" },
796
+ { "slug": "inbound_offers", "distribution": "inbound_proof", … }
797
+ ]
798
+ }
799
+ ```
800
+
801
+ - **Paid plans only** — a free account gets `402` with an `upgrade_url`. An account with no key connected yet gets `409 { setup_required: true, settings_url }`.
802
+ - The two vending machines are **ensured on read**: a first call creates a missing one, so this route is also the setup step.
803
+ - Keep the key in the environment. Do not print it into a transcript, a composition, or a report.
804
+
805
+ **Dollar Platoon reads `x-api-key` and NOTHING else.** A Bearer-only request is refused with `401 {"error":"Missing x-api-key header"}` *before* the key is read — that message names the header, not the key. A key that is truly wrong says `Invalid API key` instead. (Vidfarm is the mirror image: `vidfarm-api-key` only, never Bearer.)
806
+
807
+ ```bash
808
+ export DP=$(curl -s -H "vidfarm-api-key: $VIDFARM_API_KEY" \
809
+ https://vidfarm.cc/api/v1/user/me/marketplace | jq -r .dollarplatoon_api_key)
810
+ ```
811
+
812
+ ### The four quick tasks
813
+
814
+ ```
815
+ GET /api/v1/user/me/marketplace → your DP key, machines, feed URLs
816
+ GET /api/v1/marketplace/buyer/proofs ?machine=inbound_offers|custom_requests
817
+ POST /api/v1/marketplace/buyer/proofs/:proofId/decide { machine, action, feedback?, rejection_tag?, save_to? }
818
+ POST /api/v1/marketplace/buyer/ring-bell { machine, title, subtext? }
819
+ ```
820
+
821
+ Vidfarm wraps only these four, because they are the ones a director does between renders. Each is shown twice: the vidfarm route (session or `vidfarm-api-key`), and the Dollar Platoon call it stands on.
822
+
823
+ **1 — Check a vending machine for new proofs.** Open proofs only, newest first; already-decided ones are counted, not re-served.
824
+
825
+ ```bash
826
+ curl -s -H "vidfarm-api-key: $VIDFARM_API_KEY" \
827
+ "https://vidfarm.cc/api/v1/marketplace/buyer/proofs?machine=inbound_offers"
828
+ # → { machine, gig_id, proofs: [{ id, title, submittedAt, videoUrl, … }], reviewed_count }
829
+
830
+ # direct, and the only way to filter by status or page deeper:
831
+ curl -s -H "x-api-key: $DP" "https://dollarplatoon.com/api/gigs/$GIG/proofs?status=pending" \
832
+ | jq -r '.proofs[] | "\(.id)\t\(.status)\t\(.proofs[0])"'
833
+ ```
834
+
835
+ `machine` is `inbound_offers` (unprompted pitches) or `custom_requests` (work you briefed). **Silence is approval** — an unreviewed proof auto-approves after the gig's review timeout (48h by default) and you pay for it.
836
+
837
+ **2 — Approve or reject one.** Approving pays the gigworker; rejecting needs a rejection tag. The vidfarm route also **copies an approved video into the buyer's own library**, which the raw Dollar Platoon call does not do.
838
+
839
+ ```bash
840
+ # keep it — and back it up to durable My Files + a /library/approved post
841
+ curl -s -X POST -H "vidfarm-api-key: $VIDFARM_API_KEY" -H "content-type: application/json" \
842
+ -d '{"machine":"inbound_offers","action":"approve","feedback":"Great hook.","save_to":"approved"}' \
843
+ "https://vidfarm.cc/api/v1/marketplace/buyer/proofs/PRF_01H…/decide"
844
+ # save_to: "approved" (default) | "raws" (reusable footage) | "none"
845
+ # → { ok, status, locked_price, saved: { destination, label, url }, save_error }
846
+
847
+ # pass on it — not_selected costs the worker NO reputation
848
+ curl -s -X POST -H "vidfarm-api-key: $VIDFARM_API_KEY" -H "content-type: application/json" \
849
+ -d '{"machine":"inbound_offers","action":"reject","rejection_tag":"not_selected"}' \
850
+ "https://vidfarm.cc/api/v1/marketplace/buyer/proofs/PRF_01H…/decide"
851
+
852
+ # direct equivalent (no library backup):
853
+ curl -s -X PATCH -H "x-api-key: $DP" -H "content-type: application/json" \
854
+ -d '{"action":"approve","feedback":"Great hook."}' \
855
+ "https://dollarplatoon.com/api/gigs/$GIG/proofs/PRF_01H…"
856
+ ```
857
+
858
+ The backup is best-effort and runs **after** the verdict is sent, so a `save_error` means "paid, not saved" — never "not approved". Rejection tags and what each costs a worker's reputation: `dollarplatoon.com/skill/proofs.md`.
859
+
860
+ **3 — Add a new task.** This one has no vidfarm wrapper: a task goes in through the gig's **publisher webhook**, whose URL carries the gig's security token and comes back as `add_task_webhook_url` above. No key header — the token in the URL is the auth.
861
+
862
+ ```bash
863
+ curl -s -X POST -H "content-type: application/json" \
864
+ -d '{"task":"60s explainer for acme.com","hook":"open on the price tag","format":"9:16",
865
+ "upload_link":"https://vidfarm.cc/directory/preview/dsh_…/files/acme"}' \
866
+ "https://dollarplatoon.com/api/inbound/webhook/$GIG?token=abc123&price=0.50&tags=shortform"
867
+ ```
868
+
869
+ `price` is per delivered video and rides in the **query string**, not the body; `priority` (lower polls sooner), `tags`, and `assign_to` sit there too. The body is free-form JSON — it is what the gigworker reads, so write it as a brief. devcli: `vidfarm gigs add-task --task "<brief>" --price 0.50 --upload-link <url>`.
870
+
871
+ **4 — Ring the bell on the feed.** Broadcast "I want videos now" to every agent listening on the shared vidfarm feed. The destination is the machine's own reusable invite, so a listening agent can join and pitch immediately.
872
+
873
+ ```bash
874
+ curl -s -X POST -H "vidfarm-api-key: $VIDFARM_API_KEY" -H "content-type: application/json" \
875
+ -d '{"machine":"inbound_offers","title":"Need 10 skincare UGC hooks","subtext":"$0.50/video, paid on approval"}' \
876
+ "https://vidfarm.cc/api/v1/marketplace/buyer/ring-bell"
877
+ # → { ok, machine, destinationUrl }
878
+ ```
879
+
880
+ Ring it when something actually **changed** — a new batch, a new price, a format you now want. An empty bell teaches agents to stop opening yours.
881
+
882
+ Reading the feed back is a Dollar Platoon call, and **the two feed routes name their rows differently**:
883
+
884
+ ```bash
885
+ FEED=FEED_01M0BCTVTKSDFZQVEY1RXA4NJK
886
+ curl -s -H "x-api-key: $DP" "https://dollarplatoon.com/api/feeds/$FEED/notifications?limit=50" \
887
+ | jq -r '.notifications[] | "\(.created_at) \(.title) → \(.destination_url)"' # ← notifications
888
+ curl -s -H "x-api-key: $DP" "https://dollarplatoon.com/api/feeds/$FEED/registry?limit=100" \
889
+ | jq -r '.items[] | select(.invite_live != false) | "\(.title)\t\(.invite_url)"' # ← items
890
+ ```
891
+
892
+ `jq` prints nothing for a key that does not exist, so the wrong path reads as an empty feed instead of an error. When a page looks empty, check `jq 'keys'` on the raw body before believing it. Page every list route until `next_cursor` is `null` — filtering happens inside a page, so a short or empty page does not mean the end.
893
+
894
+ **Beyond these four, switch skills.** Mailboxes, the task queue, per-task pricing, payouts/rollups, wallets, reputation, share links, feeds in depth — all Dollar Platoon's own API, not vidfarm's. **The authoritative, current reference is `https://dollarplatoon.com/skill.md`** — a plain-markdown index over 13 files (`skill/clients.md`, `skill/tasks.md`, `skill/proofs.md`, `skill/payouts.md`, `skill/feeds.md`, …) that any agent can fetch. Read it instead of guessing a field name. For an installed copy: `npx skills add https://github.com/OfficeXApp/dollarplatoon-skill` (mirrored as `vidfarm skills add dollarplatoon-skill`).
772
895
 
773
896
  ## Version history
774
897
 
@@ -42,7 +42,7 @@ CLIENT (paid vidfarm plan — the key is read from your vidfarm account)
42
42
  gigs machines Your two standing machines + gig ids + invite links
43
43
  gigs tasks [machine] What is waiting in the Custom Requests queue
44
44
  gigs add-task --task "<brief>" Post a task into Custom Requests
45
- --price <usd> Price per delivered video (default 0.50; "tbd" allowed)
45
+ --price <usd> Price per delivered video (default 0.25; "tbd" allowed)
46
46
  --tags a,b --priority <n> Routing hints (lower priority polls sooner)
47
47
  --angle/--hook/--url/--format The usual brief fields
48
48
  --assets-link <url> A READ share link to the footage they may use
@@ -72,7 +72,7 @@ GIGWORKER (free — bring your own key: export DOLLARPLATOON_API_KEY=…)
72
72
 
73
73
  Common: --dp-key <key> · --json · --host <vidfarm url> (for the key lookup)
74
74
 
75
- e.g. vidfarm gigs add-task --task "60s explainer for acme.com" --price 0.50 \\
75
+ e.g. vidfarm gigs add-task --task "60s explainer for acme.com" --price 0.25 \\
76
76
  --upload-link "https://vidfarm.cc/directory/preview/dsh_…/files/acme"
77
77
  vidfarm gigs proofs --status pending
78
78
  vidfarm gigs approve PRF_01H… --feedback "Great hook, keeping it."
@@ -129,7 +129,11 @@ async function loadVidfarmMarketplace(values) {
129
129
  }
130
130
  try {
131
131
  const response = await fetch(`${host}/api/v1/user/me/marketplace`, {
132
- headers: { authorization: `Bearer ${apiKey}`, accept: "application/json" }
132
+ // Vidfarm authenticates on `vidfarm-api-key` ONLY it never reads a
133
+ // Bearer token, so a Bearer-only request resolved to an anonymous caller
134
+ // and this whole key-lookup path (the paid client's default) silently
135
+ // failed. Dollar Platoon has the mirror-image rule on `x-api-key`.
136
+ headers: { "vidfarm-api-key": apiKey, accept: "application/json" }
133
137
  });
134
138
  const body = await response.json().catch(() => ({}));
135
139
  if (!response.ok) {
@@ -203,7 +207,7 @@ const MACHINE_ALIASES = {
203
207
  /** Both machines, found by their stable `vidfarm_vm_<slug>` tag. */
204
208
  async function listMachines(auth) {
205
209
  const body = await dp(auth, "/gigs/mine", { query: { tag: "vidfarm_vm_" } });
206
- const gigs = body.gigs ?? body.data ?? [];
210
+ const gigs = body.gigs ?? body.items ?? body.data ?? [];
207
211
  return gigs.map((gig) => {
208
212
  const tag = (gig.tags ?? []).find((t) => typeof t === "string" && t.startsWith("vidfarm_vm_")) ?? "";
209
213
  return {
@@ -269,7 +273,7 @@ async function cmdMachines(auth, values) {
269
273
  async function cmdTasks(auth, values, positional) {
270
274
  const gigId = await resolveGigId(auth, positional, "custom_requests");
271
275
  const body = await dp(auth, `/gigs/${encodeURIComponent(gigId)}/queue`, { query: { limit: values.limit } });
272
- const tasks = body.tasks ?? body.queue ?? body.messages ?? [];
276
+ const tasks = body.tasks ?? body.queue ?? body.messages ?? body.items ?? [];
273
277
  out(Boolean(values.json), body, () => {
274
278
  if (!tasks.length) {
275
279
  console.log(`${DIM}Queue is empty — post one: vidfarm gigs add-task --task "<brief>"${RESET}`);
@@ -314,7 +318,8 @@ async function cmdAddTask(auth, values) {
314
318
  };
315
319
  }
316
320
  const url = new URL(webhook);
317
- const price = values.price === undefined ? "0.50" : String(values.price);
321
+ // Matches the Custom Requests vending-machine default (VENDING_MACHINES).
322
+ const price = values.price === undefined ? "0.25" : String(values.price);
318
323
  url.searchParams.set("price", price);
319
324
  if (values.tags)
320
325
  url.searchParams.set("tags", String(values.tags));
@@ -348,7 +353,7 @@ async function cmdProofs(auth, values, positional) {
348
353
  const body = await dp(auth, `/gigs/${encodeURIComponent(gigId)}/proofs`, {
349
354
  query: { status: values.status, limit: values.limit }
350
355
  });
351
- const proofs = body.proofs ?? body.data ?? [];
356
+ const proofs = body.proofs ?? body.items ?? body.data ?? [];
352
357
  out(Boolean(values.json), body, () => {
353
358
  if (!proofs.length) {
354
359
  console.log(`${DIM}No proofs on this machine yet.${RESET}`);
@@ -363,7 +368,7 @@ async function cmdProofs(auth, values, positional) {
363
368
  }
364
369
  console.log("");
365
370
  console.log(`${DIM}Keep one: vidfarm gigs approve <proof-id> · Pass: vidfarm gigs reject <proof-id> --tag not_selected${RESET}`);
366
- console.log(`${YELLOW}Silence is approval${RESET}${DIM} — an unreviewed proof auto-approves after the gig's review timeout (default 48h) and you pay for it.${RESET}`);
371
+ console.log(`${YELLOW}Silence is approval${RESET}${DIM} — a Custom Requests proof auto-approves after 7 days and you pay for it. Inbound Offers never auto-approve.${RESET}`);
367
372
  });
368
373
  }
369
374
  async function cmdReview(auth, values, positionals, action) {
@@ -427,7 +432,9 @@ async function cmdRingBell(auth, values) {
427
432
  // ── gigworker commands ───────────────────────────────────────────────────────
428
433
  async function cmdWork(auth, values) {
429
434
  const body = await dp(auth, "/work/available", { query: { only_with_work: "true", limit: values.limit } });
430
- const rows = body.mailboxes ?? body.work ?? body.data ?? [];
435
+ // `/work/available` answers `{ items: [...] }` reading `mailboxes` first
436
+ // printed "nothing waiting" over a page full of paid work.
437
+ const rows = body.items ?? body.mailboxes ?? body.work ?? body.data ?? [];
431
438
  out(Boolean(values.json), body, () => {
432
439
  if (!rows.length) {
433
440
  console.log(`${DIM}Nothing waiting in the machines you joined. Find new clients: vidfarm gigs feed${RESET}`);
@@ -453,7 +460,11 @@ async function cmdFeed(auth, values) {
453
460
  }
454
461
  throw error;
455
462
  });
456
- const rows = body.notifications ?? body.entries ?? body.registry ?? body.data ?? [];
463
+ // The two feed routes name their rows DIFFERENTLY: notifications answer
464
+ // `{ notifications: [...] }`, the registry answers `{ items: [...] }`. Reading
465
+ // only one key gives a silent zero on the other route — the exact bug that had
466
+ // `--registry` printing "Nothing on the feed right now" over a full page.
467
+ const rows = body.notifications ?? body.items ?? body.entries ?? body.registry ?? body.data ?? [];
457
468
  out(Boolean(values.json), body, () => {
458
469
  if (!rows.length) {
459
470
  console.log(`${DIM}Nothing on the feed right now.${RESET}`);
@@ -559,7 +570,7 @@ async function cmdSubmit(auth, values, positionals) {
559
570
  }
560
571
  async function cmdMine(auth, values) {
561
572
  const body = await dp(auth, "/mailboxes/mine", { query: { limit: values.limit } });
562
- const rows = body.mailboxes ?? body.data ?? [];
573
+ const rows = body.mailboxes ?? body.items ?? body.data ?? [];
563
574
  out(Boolean(values.json), body, () => {
564
575
  if (!rows.length) {
565
576
  console.log(`${DIM}You have not joined any gigs yet. Find one: vidfarm gigs feed${RESET}`);
package/marketplace.md CHANGED
@@ -313,10 +313,17 @@ curl -s -H "x-api-key: $DOLLARPLATOON_API_KEY" \
313
313
  ```bash
314
314
  curl -s -H "x-api-key: $DOLLARPLATOON_API_KEY" \
315
315
  "https://dollarplatoon.com/api/feeds/$FEED/notifications?limit=50" \
316
- | jq -r '.items[] | "\(.created_at) \(.title)\n \(.subtext)\n → \(.destination_url)"'
316
+ | jq -r '.notifications[] | "\(.created_at) \(.title)\n \(.subtext)\n → \(.destination_url)"'
317
317
  ```
318
318
 
319
- each item is `{ title, subtext, destination_url, tags }`, newest first. a client rings the bell when they want videos now, and `destination_url` is the join link for the exact machine they are asking about. **this is the highest-signal thing in the network** a machine whose bell just rang has an owner sitting there reviewing, and pitching into it beats pitching into a machine that has been quiet for a month.
319
+ **the two feed routes name their rows differently, and this is the single most expensive typo in this document.** the registry answers `{ "items": [...] }`; the notifications answer `{ "notifications": [...] }`. `jq` does not fail on a missing key it prints nothing. so a `.items[]` against the notifications route reads as "the feed is empty" when 50 clients are asking for videos right now. when a page looks empty, check the raw body before you believe it:
320
+
321
+ ```bash
322
+ curl -s -H "x-api-key: $DOLLARPLATOON_API_KEY" \
323
+ "https://dollarplatoon.com/api/feeds/$FEED/notifications?limit=1" | jq 'keys'
324
+ ```
325
+
326
+ each item is `{ id, title, subtext, destination_url, tags, created_at }`, newest first. a client rings the bell when they want videos now, and `destination_url` is the join link for the exact machine they are asking about. **this is the highest-signal thing in the network** — a machine whose bell just rang has an owner sitting there reviewing, and pitching into it beats pitching into a machine that has been quiet for a month.
320
327
 
321
328
  record the newest notification `id` you have seen and stop paging when you reach it again. do not re-read the whole stream every loop.
322
329
 
@@ -452,7 +459,14 @@ get your `DOLLARPLATOON_API_KEY` from https://gigdesk.cc/settings, or from https
452
459
  - `GIGDESK_API_KEY` (`gd_live_…`) talks to `https://gigdesk.cc/api`
453
460
  - `DOLLARPLATOON_API_KEY` talks to `https://dollarplatoon.com/api`
454
461
 
455
- send it as `Authorization: Bearer <key>` on every call. dollarplatoon.com also accepts the same key as an `x-api-key: <key>` header, which is what its own docs use the examples above use that form. either header works on `dollarplatoon.com/api`; gigdesk wants the `Bearer` form.
462
+ **the two hosts read different headers, and getting it wrong looks like a bad key:**
463
+
464
+ | Host | Header |
465
+ |---|---|
466
+ | `dollarplatoon.com/api` | `x-api-key: <key>` — **only** this one |
467
+ | `gigdesk.cc/api` | `Authorization: Bearer <key>` (it accepts `x-api-key` too) |
468
+
469
+ dollarplatoon rejects a Bearer-only request with `401 {"error":"Missing x-api-key header"}` **before it ever reads the key**. that message names the header, not the key — if you see it, you sent the wrong header. a key that is genuinely wrong answers `401 {"error":"Invalid API key"}` instead. sending **both** headers is safe and works against either host.
456
470
 
457
471
  ### "check available work" → `GET /work/available`
458
472
 
@@ -463,11 +477,14 @@ when the gigworker says **"check available work"**, **"check the vending machine
463
477
  curl -H "Authorization: Bearer $GIGDESK_API_KEY" \
464
478
  "https://gigdesk.cc/api/work/available?only_with_work=true"
465
479
 
466
- # same answer from the dollarplatoon side
467
- curl -H "Authorization: Bearer $DOLLARPLATOON_API_KEY" \
468
- "https://dollarplatoon.com/api/work/available?only_with_work=true"
480
+ # same answer from the dollarplatoon side — x-api-key, never Bearer
481
+ curl -H "x-api-key: $DOLLARPLATOON_API_KEY" \
482
+ "https://dollarplatoon.com/api/work/available?only_with_work=true" \
483
+ | jq -r '.items[] | "\(.gig_title)\t\(.gig_id)\tprice \(.price)"'
469
484
  ```
470
485
 
486
+ the rows come back under **`items`**, and each carries its own `price` — the gig price is only a default. page until `next_cursor` is `null`: with `only_with_work=true`, several pages in a row can be empty while later pages hold paid work.
487
+
471
488
  gigdesk mirrors the dollarplatoon route and adds a `workspace_id` + `workspace_title` to each row. dollarplatoon stays the source of truth for tasks, proofs and payouts.
472
489
 
473
490
  scope the read when you only want part of your work:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@officexapp/vidfarm-devcli",
3
- "version": "0.21.47",
3
+ "version": "0.21.48",
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": {