@officexapp/vidfarm-devcli 0.21.61 → 0.21.63

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.
Files changed (29) hide show
  1. package/.agents/skills/dollarplatoon-skill/SKILL.md +163 -1174
  2. package/.agents/skills/dollarplatoon-skill/SOURCE.md +62 -0
  3. package/.agents/skills/dollarplatoon-skill/skill/clients.md +234 -0
  4. package/.agents/skills/dollarplatoon-skill/skill/feeds.md +326 -0
  5. package/.agents/skills/dollarplatoon-skill/skill/gigs.md +395 -0
  6. package/.agents/skills/dollarplatoon-skill/skill/gigworkers.md +324 -0
  7. package/.agents/skills/dollarplatoon-skill/skill/orders.md +573 -0
  8. package/.agents/skills/dollarplatoon-skill/skill/payouts.md +234 -0
  9. package/.agents/skills/dollarplatoon-skill/skill/platform.md +174 -0
  10. package/.agents/skills/dollarplatoon-skill/skill/prices.md +75 -0
  11. package/.agents/skills/dollarplatoon-skill/skill/pricing-and-tags.md +255 -0
  12. package/.agents/skills/dollarplatoon-skill/skill/proofs.md +555 -0
  13. package/.agents/skills/dollarplatoon-skill/skill/queue.md +404 -0
  14. package/.agents/skills/dollarplatoon-skill/skill/quickstart.md +191 -0
  15. package/.agents/skills/dollarplatoon-skill/skill/staging.md +178 -0
  16. package/.agents/skills/dollarplatoon-skill/skill/tasks.md +588 -0
  17. package/.agents/skills/dollarplatoon-skill/skill/web-pages.md +586 -0
  18. package/.agents/skills/vidfarm/SKILL.md +3 -3
  19. package/.agents/skills/vidfarm/references/core-workflows.md +39 -0
  20. package/SKILL.director.md +42 -3
  21. package/SKILL.md +3 -1
  22. package/clipper.md +20 -0
  23. package/dist/src/cli.js +50 -6
  24. package/dist/src/devcli/delivery-seal.js +119 -0
  25. package/dist/src/devcli/marketplace-console.js +1253 -0
  26. package/dist/src/devcli/marketplace-gigs.js +162 -16
  27. package/marketplace.md +275 -1
  28. package/package.json +28 -3
  29. package/public/assets/homepage-client-app.js +13 -13
@@ -8,6 +8,10 @@
8
8
  // api key, read the feed to find clients, join a machine, claim a task, and
9
9
  // submit your proof.
10
10
  //
11
+ // Those two sides are a VIDFARM distinction — paid plan versus none. Dollar
12
+ // Platoon itself has no personas: one account, and per machine you are its
13
+ // Owner or a Participant. Its web pages live under `/gigs*` only.
14
+ //
11
15
  // Everything here talks to Dollar Platoon's own API. The key is resolved in
12
16
  // this order:
13
17
  // 1. --dp-key <key>
@@ -21,6 +25,7 @@ import { parseArgs } from "node:util";
21
25
  import { readFileSync } from "node:fs";
22
26
  import { readStoredAuth } from "./auth-store.js";
23
27
  import { verifyProof } from "./proof-verify.js";
28
+ import { buildSealedDeliverable, sealDeliverable, describeSealedDeliverable } from "./delivery-seal.js";
24
29
  const BOLD = "\x1b[1m";
25
30
  const DIM = "\x1b[2m";
26
31
  const GREEN = "\x1b[32m";
@@ -116,6 +121,17 @@ GIGWORKER — "help me earn on vidfarm" (free — bring your own key: export DOL
116
121
  --private-note "<text>" Anything else that follows the master (ProRes, project
117
122
  files, music licence id). 8000 chars, and on a SUBMITTED
118
123
  proof it cannot be changed — withdraw it first, or draft it.
124
+ Delivering to a VIDFARM order (a vending-machine shop on vidfarm.cc/marketplace):
125
+ --project <url> The composition folder — composition.html + composition.json
126
+ — so the buyer can FORK your build, not just download it.
127
+ --delivery-url <url> The pack this settles: https://vidfarm.cc/delivery/del_…
128
+ Minted by the shop console when you fulfil the order.
129
+ --preview-note "<text>" What the WATERMARKED cut is (aspect, length, fps). It
130
+ describes the half they can already see.
131
+ --sealed Force the vidfarm json note even with none of the above.
132
+ Any of the three flags turns it on by itself. The note is
133
+ then json WHOLE — your prose moves into --private-note,
134
+ which vidfarm renders as your message on the pack.
119
135
  --mailbox <id> --tags a,b --note "<text>" (--note is the PUBLIC feedback field)
120
136
  --ask <usd> Quote your own price (machines with price offers on). An
121
137
  ask is a QUOTE — the buyer must approve at that number.
@@ -129,6 +145,8 @@ GIGWORKER — "help me earn on vidfarm" (free — bring your own key: export DOL
129
145
  gigs drafts <gig-id> Your unsent drafts on that machine
130
146
  gigs edit-draft <gig-id> <proof-id> [--proof <url>…] [--private-note "<t>"] [--ask <usd>]
131
147
  Change an unsent draft. --private-note "" clears it.
148
+ Takes --clean-master and the vidfarm delivery flags too, so
149
+ a missing master or a lost delivery note is repaired here.
132
150
  gigs send <gig-id> <proof-id> Send a draft. The price is snapshotted NOW, not when you
133
151
  saved it, and the review clock starts here.
134
152
  gigs withdraw <gig-id> <proof-id> THE UNDO. Pulls a pending proof back to draft, stops the
@@ -139,6 +157,10 @@ GIGWORKER — "help me earn on vidfarm" (free — bring your own key: export DOL
139
157
  inside a rollup or already paid is final.
140
158
  gigs discard <gig-id> <proof-id> Delete a draft and free its task. Drafts only — a
141
159
  submitted proof stays on the record. (alias: delete-draft)
160
+ gigs seal --clean-master-url <url> [--project <url>] [--delivery-url <url>]
161
+ Print the vidfarm delivery note as json and stop. No key
162
+ needed. Use it to check what submit would send, or to build
163
+ the note for a curl you write yourself. Never hand-roll it.
142
164
  gigs proof <gig-id> <proof-id> Read your own proof back: paid_out_at + the private note
143
165
  gigs earnings Your rollups + totals. (aliases: rollups, paid)
144
166
  gigs mine Your mailboxes across every gig
@@ -191,7 +213,7 @@ async function resolveAuth(values) {
191
213
  throw new GigsAuthMissingError([
192
214
  "No Dollar Platoon key found.",
193
215
  " Any plan: run `vidfarm login` — every vidfarm account (free included) holds a key.",
194
- " Gigworker: export DOLLARPLATOON_API_KEY=… from https://dollarplatoon.com/gigworker/settings",
216
+ " Gigworker: export DOLLARPLATOON_API_KEY=… from https://dollarplatoon.com/settings",
195
217
  " Or pass it directly: --dp-key <key>"
196
218
  ].join("\n"));
197
219
  }
@@ -412,8 +434,10 @@ const MACHINE_ALIASES = {
412
434
  };
413
435
  /** Both machines, found by their stable `vidfarm_vm_<slug>` tag. */
414
436
  async function listMachines(auth) {
415
- const body = await dp(auth, "/gigs/mine", { query: { tag: "vidfarm_vm_" } });
416
- const gigs = body.gigs ?? body.items ?? body.data ?? [];
437
+ // Paged. `?tag=` is matched INSIDE a page, so a buyer with a few order gigs
438
+ // can have a machine sitting behind the cursor — and a machine we cannot see
439
+ // is one `resolveGigId` reports as missing.
440
+ const { rows: gigs } = await pageAll(auth, "/gigs/mine", (body) => body.gigs ?? body.items ?? body.data ?? [], { tag: "vidfarm_vm_" });
417
441
  return gigs.map((gig) => {
418
442
  const tag = (gig.tags ?? []).find((t) => typeof t === "string" && t.startsWith("vidfarm_vm_")) ?? "";
419
443
  return {
@@ -453,8 +477,11 @@ async function resolveInviteUrl(auth, gigId) {
453
477
  return `https://dollarplatoon.com/gig/${encodeURIComponent(gigId)}/join?invite=${encodeURIComponent(String(invite.token))}`;
454
478
  return "";
455
479
  };
456
- const listed = await dp(auth, `/gigs/${gigId}/invites`).catch(() => ({}));
457
- const invites = Array.isArray(listed.invites) ? listed.invites : [];
480
+ // Paged: not finding the durable `default` invite MINTS ANOTHER, so stopping
481
+ // at page one is how a machine collects dead links it will never redeem.
482
+ const invites = await pageAll(auth, `/gigs/${gigId}/invites`, (body) => body.invites ?? [])
483
+ .then((r) => r.rows)
484
+ .catch(() => []);
458
485
  const reusable = invites.filter((i) => !i.revoked && !i.exhausted && !i.email && (i.max_uses === null || i.max_uses === undefined));
459
486
  const chosen = reusable.find((i) => i.label === "default") ?? reusable[0];
460
487
  if (chosen && pick(chosen))
@@ -1314,6 +1341,64 @@ export async function cmdVerifyProof(values) {
1314
1341
  if (blocked)
1315
1342
  process.exitCode = 2;
1316
1343
  }
1344
+ // ── the vidfarm delivery note ────────────────────────────────────────────────
1345
+ /**
1346
+ * Build the sealed note from submit's own flags — or return null, meaning this
1347
+ * proof is a plain-text delivery on somebody else's gig and must stay one.
1348
+ *
1349
+ * The trigger is any VIDFARM-ONLY flag. A worker on a stranger's machine never
1350
+ * passes one, so they never get a json blob where the buyer expected a sentence.
1351
+ *
1352
+ * ONE item, on purpose. A `gigs submit` call carries one task and one cut, and
1353
+ * the multi-item deliveries are made in the shop console, which builds the same
1354
+ * shape from its own form. A vendor who really is delivering a batch from the
1355
+ * terminal builds it with `gigs seal` and passes the json through.
1356
+ */
1357
+ function sealedFrom(values, proofs, masterUrl) {
1358
+ const project = values.project ? String(values.project) : "";
1359
+ const deliveryUrl = values["delivery-url"] ? String(values["delivery-url"]) : "";
1360
+ const previewNote = values["preview-note"] ? String(values["preview-note"]) : "";
1361
+ if (!values.sealed && !project && !deliveryUrl && !previewNote)
1362
+ return null;
1363
+ return buildSealedDeliverable({
1364
+ deliveryUrl,
1365
+ // In prose the private note is everything-after-the-master. In the sealed
1366
+ // shape it is the VENDOR'S MESSAGE, which is where vidfarm renders it on the
1367
+ // pack — so it moves here rather than being lost or duplicated per item.
1368
+ vendorNote: values["private-note"] ? String(values["private-note"]) : "",
1369
+ items: [{
1370
+ masterUrls: masterUrl ? [masterUrl] : [],
1371
+ previewUrls: proofs,
1372
+ previewNote,
1373
+ projectUrl: project,
1374
+ notes: values["master-note"] ? String(values["master-note"]) : ""
1375
+ }]
1376
+ });
1377
+ }
1378
+ /**
1379
+ * `vidfarm gigs seal` — print the note and stop. Needs NO key.
1380
+ *
1381
+ * The whole point of shipping this is that nobody hand-rolls `JSON.stringify`:
1382
+ * a note with the right fields spelled slightly wrong parses fine, opens to
1383
+ * nothing on the buyer's pack, and cannot be patched once the proof is sent.
1384
+ */
1385
+ function cmdSeal(values) {
1386
+ const proofs = [].concat(values.proof ?? []);
1387
+ const masterUrl = values["clean-master-url"] ? String(values["clean-master-url"]) : "";
1388
+ if (!masterUrl && !values.project) {
1389
+ throw new Error("gigs seal needs at least --clean-master-url <url> (upload it first: vidfarm gigs upload ./final-clean.mp4) or --project <url>.");
1390
+ }
1391
+ // Forced on: `seal` IS the sealed format, so it never asks for --sealed too.
1392
+ const sealed = sealedFrom({ ...values, sealed: true }, proofs, masterUrl);
1393
+ if (values.json) {
1394
+ console.log(sealDeliverable(sealed));
1395
+ return;
1396
+ }
1397
+ for (const line of describeSealedDeliverable(sealed))
1398
+ console.log(`${DIM}${line}${RESET}`);
1399
+ console.log("");
1400
+ console.log(sealDeliverable(sealed));
1401
+ }
1317
1402
  async function cmdSubmit(auth, values, positionals) {
1318
1403
  const gigId = positionals[0];
1319
1404
  if (!gigId)
@@ -1382,14 +1467,35 @@ async function cmdSubmit(auth, values, positionals) {
1382
1467
  // --clean-master uploads the file to DollarPlatoon's own s3 first, because the
1383
1468
  // note presigns that url at read time off a random key; a link you host is
1384
1469
  // reachable the moment anyone guesses it.
1385
- const noteParts = [];
1386
- if (values["clean-master"]) {
1387
- const url = await uploadToDollarPlatoon(auth, String(values["clean-master"]));
1388
- noteParts.push(`Clean master, no watermark: ${url}`);
1470
+ const masterUrl = values["clean-master"]
1471
+ ? await uploadToDollarPlatoon(auth, String(values["clean-master"]))
1472
+ : (values["clean-master-url"] ? String(values["clean-master-url"]) : "");
1473
+ // TWO NOTE FORMATS, and the difference is who reads it.
1474
+ //
1475
+ // A stranger's own vending machine reads the note as prose, so prose is the
1476
+ // default and nothing about that changed. A VIDFARM order reads it as json —
1477
+ // it is how the buyer's pack finds the master, the composition folder and
1478
+ // your message — and json must be the WHOLE string, so the two cannot be
1479
+ // mixed. Deciding by whether a vidfarm-only flag was passed means a worker on
1480
+ // someone else's gig never accidentally sends a buyer a blob to read.
1481
+ const sealedNote = sealedFrom(values, proofs, masterUrl);
1482
+ let privateNote;
1483
+ if (sealedNote) {
1484
+ privateNote = sealDeliverable(sealedNote);
1485
+ if (!values.json) {
1486
+ console.log(`${DIM}Sealing a vidfarm delivery note:${RESET}`);
1487
+ for (const line of describeSealedDeliverable(sealedNote))
1488
+ console.log(` ${DIM}${line}${RESET}`);
1489
+ }
1490
+ }
1491
+ else {
1492
+ const noteParts = [];
1493
+ if (masterUrl)
1494
+ noteParts.push(`Clean master, no watermark: ${masterUrl}`);
1495
+ if (values["private-note"])
1496
+ noteParts.push(String(values["private-note"]));
1497
+ privateNote = noteParts.join("\n");
1389
1498
  }
1390
- if (values["private-note"])
1391
- noteParts.push(String(values["private-note"]));
1392
- const privateNote = noteParts.join("\n");
1393
1499
  const result = await dp(auth, `/gigs/${encodeURIComponent(gigId)}/proofs`, {
1394
1500
  method: "POST",
1395
1501
  body: {
@@ -1482,10 +1588,37 @@ async function cmdEditDraft(auth, values, positionals) {
1482
1588
  const body = {};
1483
1589
  if (proofs.length)
1484
1590
  body.proofs = proofs;
1485
- // An empty string is the documented way to CLEAR either field, so the test is
1486
- // "was the flag present", never "is the value truthy".
1487
- if (values["private-note"] !== undefined)
1591
+ // Repair a VIDFARM delivery note the same way submit writes it. Without this
1592
+ // the obvious repair `edit-draft --private-note "…"` would replace a
1593
+ // sealed json note with prose, and the buyer's pack would open to nothing.
1594
+ // The trigger is the same: any vidfarm-only flag.
1595
+ // A missing master is the other half of the same repair, so --clean-master
1596
+ // uploads here too rather than sending the vendor back to `gigs upload`.
1597
+ const masterUrl = values["clean-master"]
1598
+ ? await uploadToDollarPlatoon(auth, String(values["clean-master"]))
1599
+ : (values["clean-master-url"] ? String(values["clean-master-url"]) : "");
1600
+ const sealedNote = sealedFrom(values, proofs, masterUrl);
1601
+ if (sealedNote) {
1602
+ body.private_note = sealDeliverable(sealedNote);
1603
+ if (!values.json) {
1604
+ console.log(`${DIM}Resealing the vidfarm delivery note:${RESET}`);
1605
+ for (const line of describeSealedDeliverable(sealedNote))
1606
+ console.log(` ${DIM}${line}${RESET}`);
1607
+ }
1608
+ }
1609
+ else if (masterUrl) {
1610
+ // Prose, in the same layout `gigs submit` writes, so a draft repaired here
1611
+ // reads identically to one that never needed repairing.
1612
+ const noteParts = [`Clean master, no watermark: ${masterUrl}`];
1613
+ if (values["private-note"])
1614
+ noteParts.push(String(values["private-note"]));
1615
+ body.private_note = noteParts.join("\n");
1616
+ }
1617
+ else if (values["private-note"] !== undefined) {
1618
+ // An empty string is the documented way to CLEAR either field, so the test
1619
+ // is "was the flag present", never "is the value truthy".
1488
1620
  body.private_note = String(values["private-note"]) || null;
1621
+ }
1489
1622
  if (values.ask !== undefined)
1490
1623
  body.asking_price = String(values.ask) === "" ? null : Number(values.ask);
1491
1624
  if (!Object.keys(body).length) {
@@ -1641,7 +1774,9 @@ function options() {
1641
1774
  tag: { type: "string" },
1642
1775
  title: { type: "string" },
1643
1776
  subtext: { type: "string" },
1644
- // Bid auction (request-bids / bids / award).
1777
+ // Bid auction (request-bids / bids / award) — a PRIVATE bid thread. On
1778
+ // `gigs submit` the same flag means the sealed vidfarm delivery note. They
1779
+ // never meet: one is a buyer command, the other a worker command.
1645
1780
  sealed: { type: "boolean" },
1646
1781
  bell: { type: "boolean" },
1647
1782
  opening: { type: "string" },
@@ -1660,6 +1795,13 @@ function options() {
1660
1795
  "private-note": { type: "string" },
1661
1796
  "clean-master": { type: "string" },
1662
1797
  "clean-in-proof": { type: "boolean" },
1798
+ // The vidfarm delivery note. Any ONE of project / delivery-url /
1799
+ // preview-note switches the private note from prose to the sealed json.
1800
+ project: { type: "string" },
1801
+ "delivery-url": { type: "string" },
1802
+ "preview-note": { type: "string" },
1803
+ "master-note": { type: "string" },
1804
+ "clean-master-url": { type: "string" },
1663
1805
  draft: { type: "boolean" },
1664
1806
  ask: { type: "string" },
1665
1807
  "skip-verify": { type: "boolean" },
@@ -1682,6 +1824,10 @@ export async function runGigsCommand(argv) {
1682
1824
  // share-link worker with no account can still check a proof before sending it.
1683
1825
  if (["verify-proof", "check-proof", "preflight", "verify"].includes(sub))
1684
1826
  return cmdVerifyProof(values);
1827
+ // Same reason: `seal` only formats a string. A share-link worker with no
1828
+ // account must be able to build the note they are about to paste into a curl.
1829
+ if (["seal", "delivery-note"].includes(sub))
1830
+ return cmdSeal(values);
1685
1831
  const auth = await resolveAuth(values);
1686
1832
  switch (sub) {
1687
1833
  case "machines":
package/marketplace.md CHANGED
@@ -14,13 +14,44 @@ trying to fund machines it does not own. Decide once, out loud, before anything
14
14
  | Your human… | You are | Load this harness and follow it |
15
15
  |---|---|---|
16
16
  | wants videos **made for them** — they have a product, a budget, a brand | the **buyer** | **`https://vidfarm.cc/marketplace-buyer.md`** |
17
- | wants to **earn money** making videos for other people | an **Agentic Clipper** | **`https://vidfarm.cc/agentic-clipper.md`** |
17
+ | wants to **earn money** making videos for other people | an **Agentic Clipper**, a.k.a. a **vendor** | **`https://vidfarm.cc/agentic-clipper.md`** |
18
+
19
+ **"Clipper" and "vendor" are the same person, and both words are correct.** *Clipper* is the role —
20
+ someone earning by making videos. *Vendor* is what that person is called once they open a **shop** on
21
+ `https://vidfarm.cc/marketplace` and put **vending machines** on the shelf for buyers to order packs
22
+ from. Nothing changes between the two: the same key, the same proofs, the same payouts. A vendor just
23
+ has a storefront and therefore an extra way to deliver — see § *delivering to a vidfarm order*.
18
24
 
19
25
  **"Agentic Clipper" means one specific thing on vidfarm: the earning mode in
20
26
  `agentic-clipper.md`** — one orchestrator agent running a long-horizon mission to earn money from
21
27
  this marketplace, spawning one subagent per task. When a human says *"run in agentic clipper mode"*,
22
28
  that file is what they are asking for. It is not a synonym for "a person who edits clips."
23
29
 
30
+ ### three verbs, and the difference between them
31
+
32
+ The shop half of this marketplace was a set of web pages until 2026-08-29 — an agent could read the
33
+ shelf and touch nothing on it. It is now a REST surface under `/api/v1/marketplace/`, and the devcli
34
+ wraps it. **Reach for the right verb first; they do not overlap.**
35
+
36
+ | Verb | What it drives | Use it when |
37
+ |---|---|---|
38
+ | `vidfarm shop` | **your storefront** — pack cards, orders, deliveries, reviews, payouts | you are the vendor and a buyer ordered from *your shop* |
39
+ | `vidfarm purchases` | **your orders as a buyer** — the shelf, buying, ruling on a pack, accepting | you are spending money to get videos made |
40
+ | `vidfarm gigs` | **Dollar Platoon underneath** — mailboxes, task queues, proofs, rollups | you are working someone else's gig, with or without a shop |
41
+
42
+ `gigs` knows nothing about shops or pack cards; `shop` and `purchases` know nothing about mailboxes.
43
+ A clipper with no storefront lives entirely in `gigs` and that is a complete, paid way to work — a
44
+ shop is how buyers come to *you* instead of you going to the queue.
45
+
46
+ ```bash
47
+ vidfarm shop help # the vendor surface, in full
48
+ vidfarm purchases help # the buyer surface, in full
49
+ ```
50
+
51
+ Both take `--json` on every subcommand, and both authenticate on the ordinary vidfarm key
52
+ (`vidfarm login`, `VIDFARM_API_KEY`, or `--api-key`). The marketplace is paid-only, so a free key
53
+ reaches the routes and is refused with `402`.
54
+
24
55
  **This document is the shared reference underneath both harnesses** — the keys, the REST surface,
25
56
  the machines, the proof rules, the payout rules. The harness tells you *what loop to run*; this file
26
57
  tells you *what each call does*. Read the harness for your side first, then use this as the manual.
@@ -161,6 +192,82 @@ export DOLLARPLATOON_API_KEY=$(curl -s -H "vidfarm-api-key: $VIDFARM_API_KEY" \
161
192
 
162
193
  Then read `https://dollarplatoon.com/skill.md`. It is the accurate live index over tasks, proofs, payouts, wallets and feeds, and it stops an agent guessing field names.
163
194
 
195
+ ## How you get told — the bell, and the two emails
196
+
197
+ Both sides of the marketplace share one inbox: **`https://vidfarm.cc/notifications`**. Buyer rows and
198
+ vendor rows sit in the same list, because one person is a buyer on one order and a vendor on the next,
199
+ and splitting the list would make them check two pages to learn one thing.
200
+
201
+ The bell that opens it sits on `/marketplace`, `/marketplace/shop` and `/shop/:slug`, and the gold
202
+ **Marketplace** / **Vendor** button in the sidebar wears a green dot while anything is unseen.
203
+
204
+ | you are | you are told about |
205
+ |---|---|
206
+ | **vendor** | a new order · a new review · a payout · the buyer asked for changes · a new comment · the buyer withdrew · a wallet withdrawal |
207
+ | **buyer** | your order was fulfilled · a new comment · the vendor refunded and closed it |
208
+
209
+ ### three words that look alike and are not
210
+
211
+ This is the one thing to get right before acting on a row.
212
+
213
+ | the row says | it actually means | what the deposit did |
214
+ |---|---|---|
215
+ | **vendor:** *Order rejected* | the BUYER asked for **changes** | nothing moved. still in escrow. **re-upload** |
216
+ | **vendor:** *Order withdrawn* | the buyer refunded | returned to the buyer. the order is dead |
217
+ | **buyer:** *Order rejected* | the VENDOR refunded and closed it | returned to you |
218
+
219
+ The first one is the trap. `request-changes` never reaches Dollar Platoon — their verdict is final in
220
+ both directions, so a wrong caption cannot be routed through their `reject` (see
221
+ *an order ends three ways*). A vendor who reads "rejected" and stops working has abandoned a fully
222
+ funded order.
223
+
224
+ ### the page
225
+
226
+ Not paid-gated — a free account can fulfil work, so it can be owed a payout. Every control is a form
227
+ post and works with JavaScript off.
228
+
229
+ ```
230
+ GET /notifications?q=<search>&filter=all|unseen&page=<n> newest first, 25 a page
231
+ POST /notifications/:notificationId/seen form: seen=1|0, q, filter, page
232
+ POST /notifications/mark-all-seen form: q, filter
233
+ ```
234
+
235
+ A row carries **two separate controls**: *Mark seen / Mark unseen*, and *Open ↗* — a plain link that
236
+ opens the order in a new tab, so middle-click and cmd-click behave. Clicking the row itself does
237
+ nothing, on purpose. There is a search box, an All / Unseen filter, *Mark all seen*, and paging.
238
+
239
+ The unseen **count** is not in the page HTML — `/marketplace` is shared-cached for anonymous visitors,
240
+ so a number baked in would be one buyer's badge served to whoever came through the cache next. It
241
+ rides the session's own access probe, which an agent can read directly:
242
+
243
+ ```bash
244
+ curl -s -b cookies.txt "https://vidfarm.cc/api/v1/user/me/marketplace-access"
245
+ # { "ok": true, "marketplace": true, "vendor": true, "shop_slug": "acme", "notifications_unseen": 3 }
246
+ ```
247
+
248
+ ### the two emails
249
+
250
+ `marketplace@vidfarm.cc` sends exactly two things. Nothing else ever comes from that address, which is
251
+ what makes a single on/off switch an honest offer.
252
+
253
+ | to | when | the button |
254
+ |---|---|---|
255
+ | the **vendor** | a buyer placed an order in their shop | *Open the order →* |
256
+ | the **buyer** | a vendor delivered — the pack is sealed and waiting | *Tear your pack →* |
257
+
258
+ **Default ON**, and on by *absence* of a preference rather than by a backfill, so every existing
259
+ account already gets them. The switch is **Settings → Marketplace → Marketplace email**
260
+ (`POST /settings/marketplace/email-prefs`).
261
+
262
+ Turning it off changes nothing about how orders work — the same events are still on `/notifications`.
263
+ **Do not suggest turning it off as a fix for anything.** Those two mails are how the other party
264
+ learns money moved, and a vendor who never sees an order ages past their delivery estimate and earns
265
+ a bad review for a shop that never knew.
266
+
267
+ > **Notifications are for the HUMAN. They are not a work queue.** An agent still polls
268
+ > `GET /work/available` and `POST /gigs/$GIG/queue/poll` — see *Gigworker Instructions*. The bell tells
269
+ > a person something happened; it does not claim tasks, and nothing here changes the polling loop.
270
+
164
271
  ## Client Instructions
165
272
 
166
273
  > **The operating harness for this side is `https://vidfarm.cc/marketplace-buyer.md`.** It is the
@@ -1574,6 +1681,173 @@ that `GET` returns the proof **flat** — read `.paid_out_at`, not `.proof.paid_
1574
1681
 
1575
1682
  **never put the clean master in the task's `upload_link` folder.** that folder belongs to the buyer, so anything you upload there is handed over before payout. upload the watermarked cut and the working files there; the master goes in the note.
1576
1683
 
1684
+ ### delivering to a vidfarm ORDER — the vending machine shops on /marketplace
1685
+
1686
+ everything above is the general case: a proof on somebody's gig, and a note the buyer reads as
1687
+ prose. **a vidfarm order is different, and it is the one case where the note has a required shape.**
1688
+
1689
+ **"clipper" and "vendor" are the same person.** a clipper who opens a **shop** on
1690
+ `https://vidfarm.cc/marketplace` is a vendor. the shop holds **vending machines** — a machine is one
1691
+ product on the shelf, with a lot size, a price per lot and its own examples — and a buyer buys a
1692
+ **pack** from a machine. underneath, every machine is a dollar platoon gig and **every order is a
1693
+ dollar platoon task**, which is why the order page and the task thread are the same conversation.
1694
+
1695
+ | vidfarm | dollar platoon |
1696
+ |---|---|
1697
+ | vending machine (in your shop) | gig, distribution `inbound_order` |
1698
+ | a buyer orders a pack | task, funded on publish |
1699
+ | you deliver | proof — watermarked evidence + **sealed note** |
1700
+ | the buyer's pack | `https://vidfarm.cc/delivery/<id>` |
1701
+
1702
+ #### the delivery url — vidfarm mints it, you never invent it
1703
+
1704
+ **you do not construct a `/delivery/` url.** it is minted when you fulfil the order, and it is the
1705
+ buyer's own pack: private to their account, `noindex`, never shared-cached.
1706
+
1707
+ **from the terminal — this is the path for an agent:**
1708
+
1709
+ ```bash
1710
+ vidfarm shop orders # your queue: who is waiting on you
1711
+ vidfarm shop order ord_… # the brief, their links, the thread
1712
+
1713
+ vidfarm shop deliver ord_… \
1714
+ --item "https://…/watermarked-1.mp4::https://…/clean-1.mp4::hook lands at 0:02" \
1715
+ --item "https://…/watermarked-2.mp4::https://…/clean-2.mp4"
1716
+ # → Sent 2 items to the buyer.
1717
+ # Pack: https://vidfarm.cc/delivery/del_…
1718
+ ```
1719
+
1720
+ one `--item` per video. the halves are `preview::master::note`, and either url half may be a
1721
+ `|`-separated list to make that item a **slideshow**. `vidfarm shop deliver` again to **revise** —
1722
+ what you send replaces the pack.
1723
+
1724
+ the web form at `https://vidfarm.cc/marketplace/shop/orders` does exactly the same thing, one url per
1725
+ line. both go through one call, and vidfarm does three things in it:
1726
+
1727
+ 1. creates the delivery and returns `https://vidfarm.cc/delivery/del_…`
1728
+ 2. attaches it to the order
1729
+ 3. submits the dollar platoon proof for you, with the note already sealed
1730
+
1731
+ **and it tells the buyer.** fulfilling puts an *Order fulfilled* row on their
1732
+ `https://vidfarm.cc/notifications` and mails them *"your pack is ready to tear"* from
1733
+ `marketplace@vidfarm.cc`. a revision does the same, worded as a revision. **so do not also chase them
1734
+ in the thread** — they have been told, twice, and the order page is where they rule on it. (the same
1735
+ machinery told YOU about the order in the first place; see *How you get told* above.)
1736
+
1737
+ so **when you deliver through `vidfarm shop deliver` or the console there is nothing to hand-roll.**
1738
+ the flags below are for the third route only: a task where you want the sealed note built before you
1739
+ paste it into your own `curl`, or a clipper with no shop delivering straight to a gig.
1740
+
1741
+ a delivery link is a **signpost**, not a page. whoever opens it is sent to their own side — the buyer
1742
+ to `/marketplace/orders/:orderId#delivery`, you to `/marketplace/shop/orders/:orderId`, anyone else
1743
+ to a 404 that does not confirm the id exists. so it is safe to put in the note, and it is the one
1744
+ link that survives being forwarded.
1745
+
1746
+ **a clipper with no shop has no delivery url, and that is fine.** send `deliveryUrl: ""` — an empty
1747
+ string is the honest answer. inventing one gives the buyer's console a link that 404s.
1748
+
1749
+ #### attach the proof to the TASK, not to the order
1750
+
1751
+ `task_identifier` is the **dollar platoon task id on that order** — the same id the order thread
1752
+ hangs off. it is what claims the work to you and what joins your proof to the money. never send the
1753
+ order id, never send the subject line. subjects collide and cause duplicate `409`s.
1754
+
1755
+ #### the note is JSON, and there is a supplied function — do not hand-roll it
1756
+
1757
+ vidfarm parses the **whole** `private_note` as json. nothing may be written before or after it: one
1758
+ sentence of greeting around the blob and the buyer's pack opens to nothing, and **a submitted proof's
1759
+ note cannot be patched.** your prose goes *inside*, in `vendorNote`.
1760
+
1761
+ **`vidfarm gigs seal` builds it.** it needs no api key, it prints the note in words before it prints
1762
+ the json, and it is the same builder the shop console uses — so the two can never drift:
1763
+
1764
+ ```bash
1765
+ vidfarm gigs upload ./final-clean.mp4 # → a durable dollarplatoon s3 url
1766
+
1767
+ vidfarm gigs seal \
1768
+ --clean-master-url "https://…/clean.mp4" \
1769
+ --proof "https://…/watermarked.mp4" \
1770
+ --project "https://vidfarm.cc/directory/preview/dsh_…/files/acme-01" \
1771
+ --delivery-url "https://vidfarm.cc/delivery/del_…" \
1772
+ --preview-note "watermarked, 1080x1920, 27s, 30fps" \
1773
+ --private-note "Second hook tested better. Fork the folder to swap the CTA." \
1774
+ --master-note "ProRes + 1:1 alt in the same folder. Music: Epidemic #1234."
1775
+ ```
1776
+
1777
+ `gigs submit` takes the same flags and seals the note itself. **any one of `--project`,
1778
+ `--delivery-url` or `--preview-note` switches it on**; with none of them the note stays prose, which
1779
+ is what a stranger's own vending machine wants:
1780
+
1781
+ ```bash
1782
+ vidfarm gigs submit $GIG --task $TASK \
1783
+ --proof https://…/watermarked.mp4 \
1784
+ --clean-master ./final-clean.mp4 \
1785
+ --project https://vidfarm.cc/directory/preview/dsh_…/files/acme-01 \
1786
+ --delivery-url https://vidfarm.cc/delivery/del_… \
1787
+ --preview-note "watermarked, 1080x1920, 27s, 30fps" \
1788
+ --private-note "Second hook tested better. Fork the folder to swap the CTA."
1789
+ ```
1790
+
1791
+ this is the shape it writes. it is versioned because the note is read back after the payout, weeks
1792
+ later, by code that has moved on — **v2 is additive, and every v1 note still opens**:
1793
+
1794
+ ```json
1795
+ {
1796
+ "v": 2,
1797
+ "deliveryUrl": "https://vidfarm.cc/delivery/del_…",
1798
+ "vendorNote": "Second hook tested better. Fork the folder to swap the CTA.",
1799
+ "items": [
1800
+ {
1801
+ "itemId": "item_1",
1802
+ "kind": "video",
1803
+ "urls": ["https://…/clean.mp4"],
1804
+ "previewUrls": ["https://…/watermarked.mp4"],
1805
+ "previewNote": "watermarked, 1080x1920, 27s, 30fps",
1806
+ "projectUrl": "https://vidfarm.cc/directory/preview/dsh_…/files/acme-01",
1807
+ "notes": "ProRes + 1:1 alt in the same folder. Music: Epidemic #1234."
1808
+ }
1809
+ ]
1810
+ }
1811
+ ```
1812
+
1813
+ | field | what belongs in it |
1814
+ |---|---|
1815
+ | `deliveryUrl` | the pack this settles. `""` if you have no shop. |
1816
+ | `vendorNote` | **your own message about the delivery** — what you changed, what to look at first, what you would do next. this is where prose lives now. |
1817
+ | `itemId` | the join. a verdict is recorded against it, and it is what matches a preview to the master that replaces it. **never the array index** — that moves the moment you re-upload. |
1818
+ | `kind` | `video` or `slideshow`. derived from how many urls you gave, never declared, so it cannot disagree with the content. |
1819
+ | `urls` | the **clean master**. the sealed half. empty means nothing unseals when they pay. |
1820
+ | `previewUrls` | the **watermarked** cut they already saw. repeating it here is what tells the note which master replaces which card. |
1821
+ | `previewNote` | what the PREVIEW is — aspect, length, fps. it describes the half they are judging. |
1822
+ | `projectUrl` | the **composition folder** — `composition.html` + `composition.json`. |
1823
+ | `notes` | what follows the MASTER: ProRes, alternate aspects, fonts, the music licence id. |
1824
+
1825
+ #### ship the composition folder, not just a file
1826
+
1827
+ **`projectUrl` is the field vendors forget, and it is the one that gets you rebooked.** a buyer with
1828
+ an mp4 has to come back and pay again for "make the CTA green". a buyer with the composition folder
1829
+ **forks it** — `composition.html` plus `composition.json` open in the vidfarm editor, they change the
1830
+ line themselves, and they re-render. that is the difference between selling a file and selling
1831
+ something they can keep.
1832
+
1833
+ put the folder in your own vidfarm file directory and share it. **do not put it in the task's
1834
+ `upload_link` folder** — that folder belongs to the buyer and everything in it is handed over
1835
+ *before* payout, which is exactly what the sealed note exists to prevent. the folder link goes in the
1836
+ note with the master, and it unseals with it.
1837
+
1838
+ #### so the four things a vendor sends, and where each one goes
1839
+
1840
+ | what | where |
1841
+ |---|---|
1842
+ | the **watermarked preview** | `proofs[]` — public, playable, what they rule on |
1843
+ | what that preview IS | `previewNote` |
1844
+ | the **clean unwatermarked HD master** | `urls` — sealed until `paid_out_at` |
1845
+ | the **composition.html / .json folder** to fork | `projectUrl` — sealed with it |
1846
+ | **your own notes** | `vendorNote` for the delivery, `notes` for one item |
1847
+
1848
+ the release condition is unchanged and it is not yours to trigger: the note opens on `paid_out_at`,
1849
+ not on approval, and the platform does it.
1850
+
1577
1851
  ### drafts, and the undo — a submitted proof is not one-way
1578
1852
 
1579
1853
  a proof has two halves, and for a long time this document only described the second one. the first