@officexapp/vidfarm-devcli 0.21.46 → 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
 
@@ -313,8 +313,11 @@ vidfarm gigs approve PRF_01H… --feedback "Great hook, keeping it."
313
313
  vidfarm gigs reject PRF_01H… --tag not_selected # a pass — costs the worker NO reputation
314
314
 
315
315
  # GIGWORKER (free, no vidfarm account: export DOLLARPLATOON_API_KEY=…)
316
- vidfarm gigs join-feed && vidfarm gigs feed # who is asking for videos right now
316
+ vidfarm gigs join-feed && vidfarm gigs feed # join read-only, then see who wants videos right now
317
317
  vidfarm gigs join <invite-url> # or --registry to list every joinable machine
318
+ # BEFORE working it, check the machine is funded — see the warning below
319
+ curl -s -H "x-api-key: $DOLLARPLATOON_API_KEY" \
320
+ "https://dollarplatoon.com/api/gigs/<gig-id>" | jq '{available_funds, reserved_funds}'
318
321
  vidfarm gigs work # work waiting across every machine you joined
319
322
  vidfarm gigs claim <gig-id> # claim off the FIFO queue — note the task id
320
323
  vidfarm gigs submit <gig-id> --task <task-id> --proof https://…/final.mp4
@@ -322,6 +325,18 @@ vidfarm gigs submit <gig-id> --task <task-id> --proof https://…/final.mp4
322
325
 
323
326
  Key resolution is `--dp-key` → `DOLLARPLATOON_API_KEY` → `GIGDESK_API_KEY` → the paid account's own key via `GET /api/v1/user/me/marketplace`. **Review promptly: silence is approval** — an unreviewed proof auto-approves after the gig's review timeout (default 48h) and you pay for it.
324
327
 
328
+ **Warn the gigworker off an empty machine.** A gig is a vending machine with its own on-chain balance, and `available_funds: 0` means it cannot pay, no matter how good the delivery is. Read `available_funds` on `GET /gigs/:id` before doing any work, and tell the worker plainly what it says:
329
+
330
+ - **`0`** — do not work it yet. The proof can be approved and still sit unpaid indefinitely; nothing obliges the client to top up.
331
+ - **less than the task price** — partial coverage only. The submit response carries `"warning": "Warning: gig available funds are less than the task price"` and still accepts the proof, so acceptance is not a promise of payment.
332
+ - **`reserved_funds`** is already committed to proofs ahead of this one. Judge payability on `available_funds` alone, never on the sum.
333
+ - Budget the **10% platform fee**: a $1.00 payout draws $1.10, so a $5.00 machine covers four $1.00 videos, not five.
334
+ - Compare the funds against the **task** price, not the gig's. The gig `price` is a default only; a task may carry its own, and `price: null` (`price_tbd`) means the client names the amount at approval.
335
+
336
+ Funds never move between gigs, so one funded machine says nothing about the client's other machines — check each one. Pair the funding check with the client's reputation score before committing a batch.
337
+
338
+ The upstream contract lives at **https://dollarplatoon.com/skill.md** (an index; the gigworker playbook is `skill/gigworkers.md`, funding and fees are `skill/payouts.md`). Read it rather than guessing a field name — `GET /gigs/:id` carries `price`, `terms`, `available_funds`, `reserved_funds`, `review_timeout` and `distribution`, and there is no `price_per_proof`.
339
+
325
340
  A link is a **bearer credential**: whoever holds it has that mode. Prefer `upload` over `edit` for crowdsourcing (nobody can delete a colleague's work), keep one link per campaign rather than one per worker, and disable it the day the batch closes. **Free tier fallback:** minting is paid, so a free user puts a Google Drive / Dropbox / WeTransfer folder URL into the same `assets_link` / `upload_link` fields — worker agents treat them as plain URLs. What is lost: vector search, the scoped subtree, and the one-command `vidfarm shared put`.
326
341
 
327
342
  Two things to know. **Search is included in `read`** — that is what makes a shared folder useful to a gigworker's AI agent: it finds footage by meaning instead of paging through folders. The query embedding is billed to the **folder's owner**, not the visitor. And **only `/files` and `/temp` accept direct uploads** — `/raws`, `/approved` and `/projects` have no plain-upload backend even for the owner (raws are scanned/imported, approved posts are published renders, projects are fork storage), so an `upload` link there only creates folders. To crowdsource footage, share a `/files` folder for `upload` and import the contributions into `/raws` yourself.
@@ -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
 
@@ -2349,8 +2472,11 @@ vidfarm gigs approve PRF_01H… --feedback "Great hook, keeping it."
2349
2472
  vidfarm gigs reject PRF_01H… --tag not_selected # a pass — costs the worker NO reputation
2350
2473
 
2351
2474
  # GIGWORKER (free, no vidfarm account: export DOLLARPLATOON_API_KEY=…)
2352
- vidfarm gigs join-feed && vidfarm gigs feed # who is asking for videos right now
2475
+ vidfarm gigs join-feed && vidfarm gigs feed # join read-only, then see who wants videos right now
2353
2476
  vidfarm gigs join <invite-url> # or --registry to list every joinable machine
2477
+ # BEFORE working it, check the machine is funded — see the warning below
2478
+ curl -s -H "x-api-key: $DOLLARPLATOON_API_KEY" \
2479
+ "https://dollarplatoon.com/api/gigs/<gig-id>" | jq '{available_funds, reserved_funds}'
2354
2480
  vidfarm gigs work # work waiting across every machine you joined
2355
2481
  vidfarm gigs claim <gig-id> # claim off the FIFO queue — note the task id
2356
2482
  vidfarm gigs submit <gig-id> --task <task-id> --proof https://…/final.mp4
@@ -2358,6 +2484,18 @@ vidfarm gigs submit <gig-id> --task <task-id> --proof https://…/final.mp4
2358
2484
 
2359
2485
  Key resolution is `--dp-key` → `DOLLARPLATOON_API_KEY` → `GIGDESK_API_KEY` → the paid account's own key via `GET /api/v1/user/me/marketplace`. **Review promptly: silence is approval** — an unreviewed proof auto-approves after the gig's review timeout (default 48h) and you pay for it.
2360
2486
 
2487
+ **Warn the gigworker off an empty machine.** A gig is a vending machine with its own on-chain balance, and `available_funds: 0` means it cannot pay, no matter how good the delivery is. Read `available_funds` on `GET /gigs/:id` before doing any work, and tell the worker plainly what it says:
2488
+
2489
+ - **`0`** — do not work it yet. The proof can be approved and still sit unpaid indefinitely; nothing obliges the client to top up.
2490
+ - **less than the task price** — partial coverage only. The submit response carries `"warning": "Warning: gig available funds are less than the task price"` and still accepts the proof, so acceptance is not a promise of payment.
2491
+ - **`reserved_funds`** is already committed to proofs ahead of this one. Judge payability on `available_funds` alone, never on the sum.
2492
+ - Budget the **10% platform fee**: a $1.00 payout draws $1.10, so a $5.00 machine covers four $1.00 videos, not five.
2493
+ - Compare the funds against the **task** price, not the gig's. The gig `price` is a default only; a task may carry its own, and `price: null` (`price_tbd`) means the client names the amount at approval.
2494
+
2495
+ Funds never move between gigs, so one funded machine says nothing about the client's other machines — check each one. Pair the funding check with the client's reputation score before committing a batch.
2496
+
2497
+ The upstream contract lives at **https://dollarplatoon.com/skill.md** (an index; the gigworker playbook is `skill/gigworkers.md`, funding and fees are `skill/payouts.md`). Read it rather than guessing a field name — `GET /gigs/:id` carries `price`, `terms`, `available_funds`, `reserved_funds`, `review_timeout` and `distribution`, and there is no `price_per_proof`.
2498
+
2361
2499
  A link is a **bearer credential**: whoever holds it has that mode. Prefer `upload` over `edit` for crowdsourcing (nobody can delete a colleague's work), keep one link per campaign rather than one per worker, and disable it the day the batch closes. **Free tier fallback:** minting is paid, so a free user puts a Google Drive / Dropbox / WeTransfer folder URL into the same `assets_link` / `upload_link` fields — worker agents treat them as plain URLs. What is lost: vector search, the scoped subtree, and the one-command `vidfarm shared put`.
2362
2500
 
2363
2501
  Two things to know. **Search is included in `read`** — that is what makes a shared folder useful to a gigworker's AI agent: it finds footage by meaning instead of paging through folders. The query embedding is billed to the **folder's owner**, not the visitor. And **only `/files` and `/temp` accept direct uploads** — `/raws`, `/approved` and `/projects` have no plain-upload backend even for the owner (raws are scanned/imported, approved posts are published renders, projects are fork storage), so an `upload` link there only creates folders. To crowdsource footage, share a `/files` folder for `upload` and import the contributions into `/raws` yourself.
package/SKILL.md CHANGED
@@ -191,4 +191,4 @@ Otherwise fetch `https://vidfarm.cc/skill-pack/vidfarm/files/<path>`. Load one f
191
191
  | `recipes/*.md` | `find-and-fork-template` · `retheme-template` · `local-edit-render-approve` · `onboard-a-new-director` · `bulk-scripting-with-a-harness` · `cutout-graphics-for-explainers` |
192
192
  | `harnesses/README.md` | anything harness-shaped — start here. Bases: `short-form` · `hooks` · `ugc-testimonial` · `explainer` · `product-demo` · `product-explainer` |
193
193
 
194
- Also served at `vidfarm.cc`: `/experiments.md` (ad testing), `/crowdsourcing.md` (producing a batch with gigworkers), `/update.md` (upgrade runbook), `/experimental` (methods under live testing), `/skill/vidfarm-platform` (architecture), `/skill/hyperframes` (composition-authoring craft — route broad "make me a video" asks here first).
194
+ Also served at `vidfarm.cc`: `/experiments.md` (ad testing), `/marketplace.md` (producing a batch with gigworkers), `/update.md` (upgrade runbook), `/experimental` (methods under live testing), `/skill/vidfarm-platform` (architecture), `/skill/hyperframes` (composition-authoring craft — route broad "make me a video" asks here first).
package/dist/src/cli.js CHANGED
@@ -13046,7 +13046,10 @@ void main().catch(async (error) => {
13046
13046
  || error instanceof CostModeBlockedError
13047
13047
  // A missing OPTIONAL native module (better-sqlite3 with no C++ toolchain,
13048
13048
  // the common Windows case) is an environment fact, not a vidfarm bug.
13049
- || error instanceof NativeModuleUnavailableError) {
13049
+ || error instanceof NativeModuleUnavailableError
13050
+ // marketplace-gigs is imported lazily, so match its "no key yet" error by
13051
+ // name rather than pulling the module into the startup path just for this.
13052
+ || (error instanceof Error && error.name === "GigsAuthMissingError")) {
13050
13053
  console.error(error.message);
13051
13054
  process.exit(1);
13052
13055
  }
@@ -27,8 +27,13 @@ const YELLOW = "\x1b[33m";
27
27
  const RESET = "\x1b[0m";
28
28
  /** The shared vidfarm feed: the registry + notification stream every client's machines are listed in. */
29
29
  const VIDFARM_FEED_ID = "FEED_01M0BCTVTKSDFZQVEY1RXA4NJK";
30
- /** Standing reusable invite for that feed it is public by design. */
31
- const VIDFARM_FEED_INVITE = "MDFNMEQyRUVYMVdDNzQ2Qzha";
30
+ // Two standing invites exist for that feed. The read+register+publish one is
31
+ // PRIVATE signup applies it server-side (services/dollarplatoon.ts) for paid
32
+ // clients, who are the only ones allowed to list a machine. This module ships in
33
+ // the public npm package, so it may only ever carry the read-only invite: every
34
+ // `gigs join-feed` caller is a gigworker who just needs to see the work.
35
+ /** Standing reusable read-only invite for that feed — public by design. */
36
+ const VIDFARM_FEED_READ_INVITE = "MDFNMEJDVFZUWjVaUEFHNDlO";
32
37
  const DEFAULT_DP_API = "https://dollarplatoon.com/api";
33
38
  const GIGDESK_API = "https://gigdesk.cc/api";
34
39
  export const GIGS_HELP = `vidfarm gigs — your vending machines, both sides of them
@@ -37,7 +42,7 @@ CLIENT (paid vidfarm plan — the key is read from your vidfarm account)
37
42
  gigs machines Your two standing machines + gig ids + invite links
38
43
  gigs tasks [machine] What is waiting in the Custom Requests queue
39
44
  gigs add-task --task "<brief>" Post a task into Custom Requests
40
- --price <usd> Price per delivered video (default 0.50; "tbd" allowed)
45
+ --price <usd> Price per delivered video (default 0.25; "tbd" allowed)
41
46
  --tags a,b --priority <n> Routing hints (lower priority polls sooner)
42
47
  --angle/--hook/--url/--format The usual brief fields
43
48
  --assets-link <url> A READ share link to the footage they may use
@@ -58,7 +63,7 @@ GIGWORKER (free — bring your own key: export DOLLARPLATOON_API_KEY=…)
58
63
  gigs feed Who is asking for videos right now (notifications)
59
64
  --registry Every machine listed in the feed, joinable
60
65
  --limit <n>
61
- gigs join-feed [--name "<you>"] Join the shared vidfarm feed (needed to read it)
66
+ gigs join-feed [--name "<you>"] Join the shared vidfarm feed, read-only (needed to read it)
62
67
  gigs join <gig-id> [--invite <token>] [--name "<you>"] [--wallet 0x…]
63
68
  gigs claim <gig-id> [--count 2] Claim task(s) off a FIFO queue machine
64
69
  gigs submit <gig-id> --task <task-id> --proof <url> [--proof <url>…]
@@ -67,11 +72,22 @@ GIGWORKER (free — bring your own key: export DOLLARPLATOON_API_KEY=…)
67
72
 
68
73
  Common: --dp-key <key> · --json · --host <vidfarm url> (for the key lookup)
69
74
 
70
- 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 \\
71
76
  --upload-link "https://vidfarm.cc/directory/preview/dsh_…/files/acme"
72
77
  vidfarm gigs proofs --status pending
73
78
  vidfarm gigs approve PRF_01H… --feedback "Great hook, keeping it."
74
79
  DOLLARPLATOON_API_KEY=… vidfarm gigs work`;
80
+ /**
81
+ * "You have no key yet, here is how to get one" — the single most common first
82
+ * run for a gigworker, and a deliberate instruction rather than a bug. cli.ts
83
+ * prints these without a stack trace and files no crash report.
84
+ */
85
+ export class GigsAuthMissingError extends Error {
86
+ constructor(message) {
87
+ super(message);
88
+ this.name = "GigsAuthMissingError";
89
+ }
90
+ }
75
91
  async function resolveAuth(values) {
76
92
  const explicit = values["dp-key"]?.trim();
77
93
  if (explicit)
@@ -90,7 +106,7 @@ async function resolveAuth(values) {
90
106
  source: "your vidfarm account"
91
107
  };
92
108
  }
93
- throw new Error([
109
+ throw new GigsAuthMissingError([
94
110
  "No Dollar Platoon key found.",
95
111
  " Client: run `vidfarm login` (paid plan) — the key comes from your account.",
96
112
  " Gigworker: export DOLLARPLATOON_API_KEY=… from https://dollarplatoon.com/gigworker/settings",
@@ -113,13 +129,21 @@ async function loadVidfarmMarketplace(values) {
113
129
  }
114
130
  try {
115
131
  const response = await fetch(`${host}/api/v1/user/me/marketplace`, {
116
- 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" }
117
137
  });
118
138
  const body = await response.json().catch(() => ({}));
119
139
  if (!response.ok) {
120
140
  // 402/409 are meaningful: not paid, or no key connected yet. Surface them
121
- // rather than falling through to a bare "no key found".
122
- if (body?.error)
141
+ // rather than falling through to a bare "no key found". Everything else —
142
+ // above all a 401 from a stale `vidfarm login` — must fall through: this
143
+ // lookup is the LAST resort in resolveAuth, and a gigworker who never had
144
+ // a vidfarm account needs its "here is how to get a key" message, not a
145
+ // bare "Unauthorized" from a client-side route they were never using.
146
+ if (body?.error && (response.status === 402 || response.status === 409))
123
147
  throw new Error(body.error);
124
148
  marketplaceCache = null;
125
149
  return null;
@@ -144,6 +168,11 @@ async function dp(auth, path, init = {}) {
144
168
  const response = await fetch(url, {
145
169
  method: init.method ?? "GET",
146
170
  headers: {
171
+ // DollarPlatoon authenticates ONLY on x-api-key — a Bearer token alone is
172
+ // rejected with "Missing x-api-key header" before the key is even read.
173
+ // GigDesk documents Bearer and accepts x-api-key too, so sending both is
174
+ // correct for either host and keeps this transport host-agnostic.
175
+ "x-api-key": auth.key,
147
176
  authorization: `Bearer ${auth.key}`,
148
177
  accept: "application/json",
149
178
  ...(init.body === undefined ? {} : { "content-type": "application/json" })
@@ -178,7 +207,7 @@ const MACHINE_ALIASES = {
178
207
  /** Both machines, found by their stable `vidfarm_vm_<slug>` tag. */
179
208
  async function listMachines(auth) {
180
209
  const body = await dp(auth, "/gigs/mine", { query: { tag: "vidfarm_vm_" } });
181
- const gigs = body.gigs ?? body.data ?? [];
210
+ const gigs = body.gigs ?? body.items ?? body.data ?? [];
182
211
  return gigs.map((gig) => {
183
212
  const tag = (gig.tags ?? []).find((t) => typeof t === "string" && t.startsWith("vidfarm_vm_")) ?? "";
184
213
  return {
@@ -244,7 +273,7 @@ async function cmdMachines(auth, values) {
244
273
  async function cmdTasks(auth, values, positional) {
245
274
  const gigId = await resolveGigId(auth, positional, "custom_requests");
246
275
  const body = await dp(auth, `/gigs/${encodeURIComponent(gigId)}/queue`, { query: { limit: values.limit } });
247
- const tasks = body.tasks ?? body.queue ?? body.messages ?? [];
276
+ const tasks = body.tasks ?? body.queue ?? body.messages ?? body.items ?? [];
248
277
  out(Boolean(values.json), body, () => {
249
278
  if (!tasks.length) {
250
279
  console.log(`${DIM}Queue is empty — post one: vidfarm gigs add-task --task "<brief>"${RESET}`);
@@ -289,7 +318,8 @@ async function cmdAddTask(auth, values) {
289
318
  };
290
319
  }
291
320
  const url = new URL(webhook);
292
- 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);
293
323
  url.searchParams.set("price", price);
294
324
  if (values.tags)
295
325
  url.searchParams.set("tags", String(values.tags));
@@ -323,7 +353,7 @@ async function cmdProofs(auth, values, positional) {
323
353
  const body = await dp(auth, `/gigs/${encodeURIComponent(gigId)}/proofs`, {
324
354
  query: { status: values.status, limit: values.limit }
325
355
  });
326
- const proofs = body.proofs ?? body.data ?? [];
356
+ const proofs = body.proofs ?? body.items ?? body.data ?? [];
327
357
  out(Boolean(values.json), body, () => {
328
358
  if (!proofs.length) {
329
359
  console.log(`${DIM}No proofs on this machine yet.${RESET}`);
@@ -338,7 +368,7 @@ async function cmdProofs(auth, values, positional) {
338
368
  }
339
369
  console.log("");
340
370
  console.log(`${DIM}Keep one: vidfarm gigs approve <proof-id> · Pass: vidfarm gigs reject <proof-id> --tag not_selected${RESET}`);
341
- 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}`);
342
372
  });
343
373
  }
344
374
  async function cmdReview(auth, values, positionals, action) {
@@ -402,7 +432,9 @@ async function cmdRingBell(auth, values) {
402
432
  // ── gigworker commands ───────────────────────────────────────────────────────
403
433
  async function cmdWork(auth, values) {
404
434
  const body = await dp(auth, "/work/available", { query: { only_with_work: "true", limit: values.limit } });
405
- 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 ?? [];
406
438
  out(Boolean(values.json), body, () => {
407
439
  if (!rows.length) {
408
440
  console.log(`${DIM}Nothing waiting in the machines you joined. Find new clients: vidfarm gigs feed${RESET}`);
@@ -420,12 +452,19 @@ async function cmdFeed(auth, values) {
420
452
  const registry = Boolean(values.registry);
421
453
  const path = registry ? `/feeds/${VIDFARM_FEED_ID}/registry` : `/feeds/${VIDFARM_FEED_ID}/notifications`;
422
454
  const body = await dp(auth, path, { query: { limit: values.limit ?? "50" } }).catch((error) => {
423
- if (/40[13]/.test(error.message)) {
455
+ // A non-member gets 404 on EVERY feed route — never 403 — so a stranger cannot
456
+ // confirm a feed exists. 404 is therefore the code this actually fires on; the
457
+ // others are kept for a revoked key or a lost membership.
458
+ if (/40[134]/.test(error.message)) {
424
459
  throw new Error("You are not on the vidfarm feed yet — join it first: vidfarm gigs join-feed");
425
460
  }
426
461
  throw error;
427
462
  });
428
- 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 ?? [];
429
468
  out(Boolean(values.json), body, () => {
430
469
  if (!rows.length) {
431
470
  console.log(`${DIM}Nothing on the feed right now.${RESET}`);
@@ -446,7 +485,7 @@ async function cmdFeed(auth, values) {
446
485
  async function cmdJoinFeed(auth, values) {
447
486
  const result = await dp(auth, `/feeds/${VIDFARM_FEED_ID}/join`, {
448
487
  method: "POST",
449
- body: { invite: VIDFARM_FEED_INVITE, ...(values.name ? { display_name: String(values.name) } : {}) }
488
+ body: { invite: VIDFARM_FEED_READ_INVITE, ...(values.name ? { display_name: String(values.name) } : {}) }
450
489
  });
451
490
  out(Boolean(values.json), result, () => {
452
491
  console.log(`${GREEN}✓${RESET} On the vidfarm feed. Read it: ${BOLD}vidfarm gigs feed${RESET}`);
@@ -531,7 +570,7 @@ async function cmdSubmit(auth, values, positionals) {
531
570
  }
532
571
  async function cmdMine(auth, values) {
533
572
  const body = await dp(auth, "/mailboxes/mine", { query: { limit: values.limit } });
534
- const rows = body.mailboxes ?? body.data ?? [];
573
+ const rows = body.mailboxes ?? body.items ?? body.data ?? [];
535
574
  out(Boolean(values.json), body, () => {
536
575
  if (!rows.length) {
537
576
  console.log(`${DIM}You have not joined any gigs yet. Find one: vidfarm gigs feed${RESET}`);
package/experiments.md CHANGED
@@ -129,7 +129,7 @@ That is the strong starting checkpoint. It is not arbitrary — it is the cheape
129
129
  Test **N videos regardless of their composition params.** Every video may differ in format, hook, angle, pacing, everything. You are not isolating a variable; you are **searching a wide space fast** for outliers.
130
130
 
131
131
  - **Best for:** new users, cold starts, any product with no performance history at all.
132
- - **Editing:** thrives with **distributed task forces** — other people's AI agents (gigworkers) doing the editing. Variance between editors is a *feature* here: it widens the search. Set up the loop from `https://vidfarm.cc/crowdsourcing.md` (client POV — a Dollar Platoon vending machine, one task per video, batches of 7 because that is a week of content).
132
+ - **Editing:** thrives with **distributed task forces** — other people's AI agents (gigworkers) doing the editing. Variance between editors is a *feature* here: it widens the search. Set up the loop from `https://vidfarm.cc/marketplace.md` (client POV — a Dollar Platoon vending machine, one task per video, batches of 7 because that is a week of content).
133
133
  - **Typical shape:** the user brings the selling angles; the gigworkers take creative liberty on everything else. You get diverse videos, and diversity gets you to an outlier fastest.
134
134
  - **What you learn:** *that* something works. Not yet why.
135
135
 
@@ -420,6 +420,6 @@ When you accumulate winners, you are building the real deliverable: **a library
420
420
  ## Where to go next
421
421
 
422
422
  - **Making the videos:** `https://vidfarm.cc/skill.md` (and the director skill it points to — hooks, harnesses, review, cost mode, interactive vs autonomous)
423
- - **Crowdsourcing the editing (Creative Mode, client POV):** `https://vidfarm.cc/crowdsourcing.md`
423
+ - **Crowdsourcing the editing (Creative Mode, client POV):** `https://vidfarm.cc/marketplace.md`
424
424
  - **The agentic clipper loop programme:** `https://vidfarm.cc/clipper.md`
425
425
  - **Scheduling and analytics:** the `flockposter` skill, or the Vidfarm email channel scheduling flow
@@ -1,8 +1,11 @@
1
- # Crowdsourcing.md
1
+ # Marketplace.md
2
2
 
3
- How to crowdsource videos from vidfarm.cc
3
+ How to crowdsource videos from the vidfarm.cc marketplace.
4
4
  Please redirect user based on whether they are a client or a gigworker, instructions below.
5
5
 
6
+ > Served at `https://vidfarm.cc/marketplace.md`. The older `https://vidfarm.cc/crowdsourcing.md`
7
+ > redirects here — same document, one canonical name.
8
+
6
9
  ## Client Instructions
7
10
 
8
11
  Please help setup agentic clipper loops for a client so they can get crowdsourced viral videos for their app.
@@ -310,10 +313,17 @@ curl -s -H "x-api-key: $DOLLARPLATOON_API_KEY" \
310
313
  ```bash
311
314
  curl -s -H "x-api-key: $DOLLARPLATOON_API_KEY" \
312
315
  "https://dollarplatoon.com/api/feeds/$FEED/notifications?limit=50" \
313
- | jq -r '.items[] | "\(.created_at) \(.title)\n \(.subtext)\n → \(.destination_url)"'
316
+ | jq -r '.notifications[] | "\(.created_at) \(.title)\n \(.subtext)\n → \(.destination_url)"'
314
317
  ```
315
318
 
316
- 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.
317
327
 
318
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.
319
329
 
@@ -321,6 +331,24 @@ record the newest notification `id` you have seen and stop paging when you reach
321
331
 
322
332
  ### Step 3 — join a machine and set your payout wallet
323
333
 
334
+ **first: check the machine has money in it.** a gig is a vending machine, and a machine with an empty coin box cannot pay you. read `available_funds` on the gig before you do any work:
335
+
336
+ ```bash
337
+ curl -s -H "x-api-key: $DOLLARPLATOON_API_KEY" \
338
+ "https://dollarplatoon.com/api/gigs/$GIG" \
339
+ | jq '{available_funds, reserved_funds, price, review_timeout, distribution}'
340
+ ```
341
+
342
+ what the numbers mean:
343
+
344
+ - **`available_funds` is `0`** — the machine is empty. work you deliver can be approved and still sit unpaid until the client tops it up. nothing forces them to. treat `0` as *do not work this gig yet*.
345
+ - **`available_funds` is less than the task price** — it can pay some deliveries, not all. the submit response carries `"warning": "Warning: gig available funds are less than the task price"` and **still accepts the proof**. do not read that acceptance as a promise of payment.
346
+ - **`reserved_funds`** is already promised to proofs ahead of you in the queue. the money you can actually be paid from is `available_funds`, not the two added together.
347
+ - budget the **10% platform fee**: a $1.00 payout draws $1.10 out of the machine. a gig holding $5.00 pays four $1.00 videos, not five.
348
+ - **compare the funds against the TASK price, not the gig `price`.** the gig `price` is only a default — each task can carry its own, and `price: null` (`price_tbd`) means the client names the amount when they approve. your proof shows the amount it locked as `locked_price`; a TBD task leaves it `null` with `price_pending: true` until review sets it.
349
+
350
+ funds are per gig and **cannot** move between gigs, so a client with one well-funded machine tells you nothing about their empty one. check each machine you join. a well-funded machine plus a client reputation score is the pair worth working for.
351
+
324
352
  take an `invite_url` from the registry (or a `destination_url` from a notification), pull the gig id and invite token out of it, and join. **set your Base L2 payout address in the same call** — this is the field that decides where your money lands:
325
353
 
326
354
  ```bash
@@ -431,7 +459,14 @@ get your `DOLLARPLATOON_API_KEY` from https://gigdesk.cc/settings, or from https
431
459
  - `GIGDESK_API_KEY` (`gd_live_…`) talks to `https://gigdesk.cc/api`
432
460
  - `DOLLARPLATOON_API_KEY` talks to `https://dollarplatoon.com/api`
433
461
 
434
- 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.
435
470
 
436
471
  ### "check available work" → `GET /work/available`
437
472
 
@@ -442,11 +477,14 @@ when the gigworker says **"check available work"**, **"check the vending machine
442
477
  curl -H "Authorization: Bearer $GIGDESK_API_KEY" \
443
478
  "https://gigdesk.cc/api/work/available?only_with_work=true"
444
479
 
445
- # same answer from the dollarplatoon side
446
- curl -H "Authorization: Bearer $DOLLARPLATOON_API_KEY" \
447
- "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)"'
448
484
  ```
449
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
+
450
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.
451
489
 
452
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.46",
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": {
@@ -8,6 +8,77 @@
8
8
  "vidfarm-devcli": "dist/src/cli.js"
9
9
  },
10
10
  "//files": "PUBLISHED = CLOUD-ONLY CLI. Ships ONLY the CLI's static import closure (backend-free) + shared client utils + skills/docs + the curated local-frontend assets (public/serve-shells snapshots, public/assets + demo/dist prebuilt bundles) that `vidfarm serve` serves offline while proxying data to the cloud. The full backend (dist/src/app.js, editor-chat, serverless-*, reskin/, landing/homepage, services/hyperframes|providers|storage|ghostcut|upstream, infra) is INTENTIONALLY EXCLUDED so the server is never open-sourced — only the RENDERED HTML shells ship, never the source that produced them. `prepack` runs scripts/check-devcli-pack-safe.mjs which HARD-FAILS if any backend file leaks in or a needed CLI file is missing. Do not broaden these globs without re-running that guard.",
11
+ "files": [
12
+ "dist/src/cli.js",
13
+ "dist/src/devcli/auth-store.js",
14
+ "dist/src/devcli/agent-imagegen.js",
15
+ "dist/src/devcli/browser-harness.js",
16
+ "dist/src/devcli/captions.js",
17
+ "dist/src/devcli/clip-store.js",
18
+ "dist/src/devcli/clips.js",
19
+ "dist/src/devcli/composition-edit.js",
20
+ "dist/src/devcli/consult.js",
21
+ "dist/src/devcli/cost-mode.js",
22
+ "dist/src/devcli/dedupe-local.js",
23
+ "dist/src/devcli/doctor.js",
24
+ "dist/src/devcli/experiments.js",
25
+ "dist/src/devcli/handoff.js",
26
+ "dist/src/devcli/greenscreen-local.js",
27
+ "dist/src/devcli/hyperframes-cli.js",
28
+ "dist/src/devcli/interaction-mode.js",
29
+ "dist/src/devcli/local-backend.js",
30
+ "dist/src/devcli/marketplace-gigs.js",
31
+ "dist/src/devcli/local-frontend-server.js",
32
+ "dist/src/devcli/local-render.js",
33
+ "dist/src/devcli/plate-key.js",
34
+ "dist/src/devcli/port-utils.js",
35
+ "dist/src/devcli/process-scan.js",
36
+ "dist/src/devcli/qa-check.js",
37
+ "dist/src/devcli/harness.js",
38
+ "dist/src/devcli/sequence.js",
39
+ "dist/src/devcli/skill-docs.js",
40
+ "dist/src/devcli/skills.js",
41
+ "dist/src/devcli/shared-folder.js",
42
+ "dist/src/devcli/speech.js",
43
+ "dist/src/devcli/sticker-pack.js",
44
+ "dist/src/devcli/studio-brand.js",
45
+ "dist/src/devcli/stills.js",
46
+ "dist/src/devcli/storyboard.js",
47
+ "dist/src/devcli/telemetry.js",
48
+ "dist/src/devcli/timeline-edit.js",
49
+ "dist/src/devcli/transitions.js",
50
+ "dist/src/hyperframes/composition.js",
51
+ "dist/src/lib/**/*.js",
52
+ "dist/src/services/brainstorm-prompts.js",
53
+ "dist/src/services/captions.js",
54
+ "dist/src/services/composition-lint.js",
55
+ "dist/src/services/provider-errors.js",
56
+ "dist/src/services/sequence-prompts.js",
57
+ "dist/src/services/speech.js",
58
+ "dist/src/services/clip-curation/**/*.js",
59
+ "dist/src/services/clip-curation/**/*.json",
60
+ "public/serve-shells/**/*.html",
61
+ "public/assets/**/*.js",
62
+ "public/assets/**/*.css",
63
+ "public/assets/**/*.map",
64
+ "public/assets/**/*.ico",
65
+ "public/assets/**/*.png",
66
+ "public/assets/**/*.svg",
67
+ "demo/dist/**",
68
+ ".agents/skills/**",
69
+ "README.md",
70
+ "SKILL.md",
71
+ "SKILL.director.md",
72
+ "src/assets/SELLING_WITH_HOOKS.md",
73
+ "src/assets/SELLING_AWARENESS_STAGES.md",
74
+ "clipper.md",
75
+ "experiments.md",
76
+ "marketplace.md",
77
+ "update.md",
78
+ "experimental/**/*.md",
79
+ "!readme.secret.md",
80
+ "!**/*.secret.*"
81
+ ],
11
82
  "publishConfig": {
12
83
  "access": "public"
13
84
  },
@@ -18,19 +89,104 @@
18
89
  "engines": {
19
90
  "node": ">=22.0.0"
20
91
  },
92
+ "scripts": {
93
+ "prepare": "patch-package || true",
94
+ "dev": "tsx --import ./src/instrument.ts watch src/index.ts",
95
+ "dev:frontend": "node scripts/build-homepage-client.mjs --watch",
96
+ "dev:cli": "tsx src/cli.ts",
97
+ "build:frontend": "node scripts/build-homepage-client.mjs",
98
+ "build:hyperframes-editor": "cd demo && node scripts/build.mjs",
99
+ "build:serve-shells": "tsx scripts/build-serve-shells.ts",
100
+ "seo:refresh": "tsx scripts/generate-route-seo.ts",
101
+ "portfolio:share": "tsx scripts/render-portfolio-share.ts",
102
+ "operator:template-artifact": "node scripts/platform-operator-template-artifact.mjs",
103
+ "bootstrap:staging-templates": "tsx scripts/bootstrap-staging-template-releases.ts",
104
+ "migrate:sqlite-to-dynamodb": "tsx scripts/migrate-sqlite-to-dynamodb.ts",
105
+ "deploy:prod": "bash scripts/deploy-prod.sh",
106
+ "deploy:staging": "bash scripts/deploy-staging.sh",
107
+ "build": "npm run build:frontend && npm run build:hyperframes-editor && npm run build:serve-shells && node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.json",
108
+ "start": "node --import ./dist/src/instrument.js --enable-source-maps dist/src/index.js",
109
+ "check": "tsc -p tsconfig.json --noEmit && npm run check:skills",
110
+ "test:clips": "node --import tsx --test test/clip-curation.test.ts",
111
+ "test:social-download": "node --import tsx --test test/video-download-media.test.ts test/social-download-lookup.test.ts",
112
+ "test:qa": "node --import tsx --test test/qa-check.test.ts",
113
+ "test:studio-brand": "node --import tsx --test test/studio-brand.test.ts",
114
+ "test:stickers": "node --import tsx --test test/sticker-pack.test.ts test/plate-key.test.ts",
115
+ "test:social-recycle": "node --import tsx --test test/social-recycle.test.ts",
116
+ "test:dedupe": "node --import tsx --test test/dedupe-recipe.test.ts",
117
+ "test:iconscout": "node --import tsx --test test/iconscout.test.ts",
118
+ "test:web-search": "node --import tsx --test test/web-search.test.ts",
119
+ "test:billing": "node --import tsx --test test/billing-wallet-cas.test.ts",
120
+ "test:skill-docs": "node --import tsx --test test/skill-docs.test.ts",
121
+ "test:consult": "node --import tsx --test test/consult.test.ts",
122
+ "test:agent-imagegen": "node --import tsx --test test/agent-imagegen.test.ts",
123
+ "test:social-meta": "node --import tsx --test test/social-meta.test.ts",
124
+ "check:skills": "node scripts/build-director-skill-rollup.mjs --check && node scripts/check-skill-routes.mjs && node scripts/check-skill-nav.mjs",
125
+ "benchmark:editor-chat": "node --import tsx scripts/benchmark-editor-chat-harness.mjs",
126
+ "cdk:deploy:prod-serverless": "npm run build && dotenv -e .env.production -- npx aws-cdk deploy --app 'node dist/infra/cdk/bin/vidfarm-prod.js'",
127
+ "cdk:synth:staging-serverless": "npm run build && dotenv -e .env.staging -- npx aws-cdk synth --app 'node dist/infra/cdk/bin/vidfarm-serverless-staging.js'",
128
+ "cdk:deploy:staging-serverless": "npm run build && dotenv -e .env.staging -- npx aws-cdk deploy --app 'node dist/infra/cdk/bin/vidfarm-serverless-staging.js'",
129
+ "cdk:deploy:staging-serverless:hotswap": "npm run build && dotenv -e .env.staging -- npx aws-cdk deploy --app 'node dist/infra/cdk/bin/vidfarm-serverless-staging.js' --hotswap-fallback --require-approval never",
130
+ "cdk:deploy:prod-serverless:hotswap": "npm run build && dotenv -e .env.production -- npx aws-cdk deploy --app 'node dist/infra/cdk/bin/vidfarm-prod.js' --hotswap-fallback --require-approval never",
131
+ "prepack": "npm run build && node scripts/check-devcli-pack-safe.mjs",
132
+ "check:pack-safe": "node scripts/check-devcli-pack-safe.mjs",
133
+ "pack:devcli": "npm run build && node scripts/check-devcli-pack-safe.mjs && node scripts/build-devcli-package.mjs",
134
+ "pack:devcli:fast": "node scripts/check-devcli-pack-safe.mjs && node scripts/build-devcli-package.mjs",
135
+ "test:render-routing": "node --import tsx --test test/render-routing.test.ts",
136
+ "test:shared-folder": "node --import tsx --test test/shared-folder.test.ts"
137
+ },
21
138
  "dependencies": {
22
139
  "@ai-sdk/google": "^3.0.79",
23
140
  "@ai-sdk/openai": "^3.0.65",
24
141
  "@ai-sdk/openai-compatible": "^2.0.48",
142
+ "@assistant-ui/react": "^0.14.8",
143
+ "@assistant-ui/react-data-stream": "^0.12.14",
144
+ "@aws-sdk/client-cloudformation": "^3.1076.0",
145
+ "@aws-sdk/client-dynamodb": "^3.1056.0",
146
+ "@aws-sdk/client-lambda": "^3.1087.0",
147
+ "@aws-sdk/client-s3": "^3.1064.0",
148
+ "@aws-sdk/client-sfn": "^3.1073.0",
149
+ "@aws-sdk/lib-dynamodb": "^3.1073.0",
150
+ "@aws-sdk/s3-request-presigner": "^3.787.0",
25
151
  "@ffprobe-installer/ffprobe": "^2.1.2",
152
+ "@fontsource/libre-baskerville": "^5.2.10",
153
+ "@fontsource/montserrat": "^5.2.8",
154
+ "@fontsource/source-code-pro": "^5.2.7",
155
+ "@fontsource/tiktok-sans": "^5.2.4",
156
+ "@hono/node-server": "^1.14.4",
157
+ "@hyperframes/aws-lambda": "0.8.2",
158
+ "@hyperframes/studio-server": "0.8.2",
159
+ "@sentry/aws-serverless": "^10.56.0",
160
+ "@sentry/hono": "^10.56.0",
26
161
  "@sentry/node": "^10.56.0",
162
+ "@sentry/react": "^10.56.0",
27
163
  "ai": "^6.0.191",
164
+ "dotenv": "^16.5.0",
165
+ "esbuild": "^0.28.1",
28
166
  "ffmpeg-static": "^5.3.0",
167
+ "fontkit": "^2.0.4",
168
+ "hono": "^4.8.3",
29
169
  "hyperframes": "0.8.2",
30
170
  "linkedom": "^0.18.12",
171
+ "react": "^18.3.1",
172
+ "react-dom": "^18.3.1",
31
173
  "sharp": "^0.34.5",
32
174
  "zod": "^3.25.28"
33
175
  },
176
+ "devDependencies": {
177
+ "@types/better-sqlite3": "^7.6.13",
178
+ "@types/node": "^24.0.1",
179
+ "@types/react": "^18.3.23",
180
+ "@types/react-dom": "^18.3.7",
181
+ "aws-cdk-lib": "^2.196.0",
182
+ "constructs": "^10.4.2",
183
+ "dotenv-cli": "^11.0.0",
184
+ "patch-package": "^8.0.1",
185
+ "react-grab": "^0.1.48",
186
+ "tsx": "^4.19.4",
187
+ "typescript": "^5.8.3",
188
+ "zustand": "^5.0.13"
189
+ },
34
190
  "main": "index.js",
35
191
  "keywords": [],
36
192
  "author": "",