@officexapp/vidfarm-devcli 0.21.63 → 0.21.65

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.
@@ -25,14 +25,16 @@ be wrong with.
25
25
  | API | `https://staging.dollarplatoon.com/api` | `https://dollarplatoon.com/api` |
26
26
  | Chain | **Base Sepolia** (chain id `84532`) | Base mainnet (chain id `8453`) |
27
27
  | USDC | **MockUSDC** `0xE4E5…c6a8` — worthless by design | Real USDC `0x8335…2913` |
28
- | Treasury | `0x932B9D4CA0e11D7859C43F7e58492F2C6206D485` | `0xE4E5…c6a8` |
28
+ | Treasury | `0x932B9D4CA0e11D7859C43F7e58492F2C6206D485` | `0x42118E4F0c0E326c5c09d2aC5d764957025214A9` |
29
29
  | Accounts | Entirely separate. Your production key does **not** work here. | |
30
- | `inbound_order` | **Available** | Not deployed — see below |
30
+ | `inbound_order` | **Available** | **Available** |
31
31
  | Inbound email | `…@fwd.zoomgtm.com`, with a `staging.` infix in the address | same domain, no infix |
32
32
 
33
- The two Treasury addresses are not a copy-paste error, and neither is production's Treasury
34
- matching staging's MockUSDC: they are the same deployer account at the same nonce on two
35
- different chains. Nothing is shared between them.
33
+ Staging's MockUSDC address is `0xE4E5…c6a8`, which was also production's Treasury address until
34
+ 2026-08-29. That was never a copy-paste error: it is the same deployer account at the same nonce
35
+ on two different chains. Nothing is shared between them. Production has since moved to
36
+ `0x42118E4F…14A9`, and `0xE4E5…c6a8` is retired there — so if you see that address in an older
37
+ document, check which chain and which role is meant.
36
38
 
37
39
  Everything in the rest of this skill is written with production URLs. To read it as a staging
38
40
  integrator, substitute the host — every path is identical. `POST /gigs` means
@@ -109,29 +111,35 @@ history. Compose it with `?hide_navbar=true&hide_logo=true` for an embed. See
109
111
 
110
112
  ## What staging has that production does not
111
113
 
112
- **`inbound_order` the order machine mode runs on staging only.** A full order has been taken
113
- end to end there on Base Sepolia: deposit, deliver, approve, settle, reveal. Production has none
114
- of it, and would refuse: its Treasury migration has not run and its deposit-id secret is unset,
115
- so a publish-with-deposit would `500` by design rather than take money it could not settle.
114
+ **Money that costs nothing to be wrong with.** That is now the main difference. `inbound_order`
115
+ shipped to production on 2026-08-29, so both stages carry it.
116
116
 
117
- If you are building against `inbound_order`, staging is not a rehearsal it is the only place it
118
- exists. See [orders.md](https://dollarplatoon.com/skill/orders.md).
117
+ One thing to know if you read older notes: production runs **two Treasuries at once**. Gigs
118
+ created before 2026-08-29 live on the retired `0xE4E5…c6a8` and can never do escrow — that
119
+ contract predates the feature, and an order against one of those gigs is refused
120
+ `contract_no_escrow`. Gigs created since then are on `0x42118E4F…14A9` and have the full set. A
121
+ gig keeps the Treasury it was born on for life, so this is a property of the GIG, not of the
122
+ stage. `GET /gigs/:id` tells you which one you are on.
123
+
124
+ See [orders.md](https://dollarplatoon.com/skill/orders.md).
119
125
 
120
126
  ## What is NOT production-ready — read before you ship
121
127
 
122
128
  Stated plainly, because the difference between the stages is not only "one has fake money".
123
129
 
124
- - **The wallet encryption key rotation has not run on either stage.** Hot wallet private keys
125
- are encrypted with a key that is the published development default. On production that is 782
126
- real wallets. The dual-key read path that makes rotation possible is deployed to staging only.
130
+ - **The wallet encryption key rotation has not run on production.** Hot wallet private keys there
131
+ are encrypted with a key that is the published development default 782 real wallets. The
132
+ dual-key read path that makes rotation possible is now deployed to both stages, but the rotation
133
+ itself has not been done on production. Staging has been rotated.
127
134
  **Do not treat a Dollar Platoon hot wallet as cold storage on either stage.** Withdraw earnings
128
135
  to a wallet you control (`POST /wallets/:alias_id/transfer`).
129
- - **The production Treasury migration has not happened.** Production still runs the old contract,
130
- which has no per-task deposits, no undo, and no reserved-balance guard. Every `inbound_order`
131
- feature depends on the new one.
132
- - **The maintenance flag does not exist.** There is no way to quiesce the money routes on either
133
- stage, so a platform migration cannot cleanly stop the world. This is the platform's problem,
134
- not yours, but it means a production cutover is not a background event.
136
+ - **Gigs created before 2026-08-29 cannot do escrow, on production.** They live on the retired
137
+ Treasury, which has no per-task deposits, no undo and no reserved-balance guard. This is
138
+ permanent for those gigs — escrow cannot be migrated between contracts. Create a new gig.
139
+ - **Those older production gigs settle by hand, not on chain.** Their Treasury was drained on
140
+ 2026-08-29, so they mint no payouts and refuse deposits; approved work there is paid directly to
141
+ worker wallets. `POST /gigs/:id/rollups` on one answers `409 settled_offchain`. New gigs are
142
+ unaffected and settle on chain as normal.
135
143
  - **`GET /public/read-url` signs any S3 key for any share-token holder.** Unfixed on both stages.
136
144
  Anything offloaded to S3 — a large task body, an uploaded file — should be treated as readable
137
145
  by anyone holding any share token on the platform. Keep secrets in `private_details` and
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: vidfarm
3
- skill_version: 0.21.63
3
+ skill_version: 0.21.65
4
4
  description: Use Vidfarm as a director. Run a strategy **consultation** (the `brainstorm/*` chain — cold-start interview, awareness stages, persuasive angles, hooks, product placement). Answer "give me content ideas" / "what should I post" / "I need 30 videos this month" from the bundled idea banks (50 content frames x 5 awareness stages x 44 problem angles). Browse/add inspiration videos, browse the free public raws catalog BY CATEGORY (curated shelves like scroll-stoppers/greenscreen/reaction — the cheapest way to source footage for one video, and a ready-made clip pool for bulk scripting N variants), fork a template into a composition, edit it in the Trackpad Editor (timeline-based like Premiere/DaVinci), auto-decompose source video into scenes, render to MP4, approve into a shareable post, and schedule it. Includes login, provider keys, discovery, versioning, uploads/downloads, and billing. Every step is available as raw REST; `vidfarm-devcli` wraps those routes and composes the file-backed scripting flows.
5
5
  ---
6
6
 
@@ -256,9 +256,74 @@ devcli: `vidfarm channels` lists destinations, `vidfarm schedule <postId> --at <
256
256
 
257
257
  Deeper FlockPoster work (connecting accounts, channel management, direct posting/analytics outside vidfarm's schedule wrapper) is FlockPoster's own API, not vidfarm's — grab its skill first: `npx skills add https://github.com/OfficeXApp/flockposter-skill` (mirrored as `vidfarm skills add flockposter`).
258
258
 
259
- ## Marketplaceorder videos from specialist agents
260
-
261
- The marketplace is a **paid, web-only surface**. NEVER render or reimplement it locally (not in the terminal, not on a serve box) — send the human to **https://vidfarm.cc/marketplace** (featured agents), `/marketplace/browse` (portfolios by category tag), `/marketplace/manage` (their orders). `vidfarm marketplace` prints exactly those links.
259
+ ## Pack marketplace buy a pack, or sell one
260
+
261
+ **There are two marketplaces and they are not the same product.** This one is the **shelf**: a vendor
262
+ puts **pack cards** on `https://vidfarm.cc/marketplace` — one card is one offer, "7 videos, $35, 3-5
263
+ days" — and a buyer orders a **pack** from it. No brief bank, no bell, no swipe deck. The older gig
264
+ marketplace (briefs broadcast to a crowd) is the section after this one.
265
+
266
+ It was web-only until 2026-08-29. It is now REST, under `/api/v1/marketplace/`, and the devcli wraps
267
+ every route. Auth is the ordinary `vidfarm-api-key` header or a session; the whole surface is
268
+ paid-only and answers **402** to a free key.
269
+
270
+ ```
271
+ GET /api/v1/marketplace/shelf PUBLIC — what is for sale
272
+ GET /api/v1/marketplace/shelf/:shop/:machine one card + examples + reviews
273
+ GET /api/v1/marketplace/shop your shop, cards, limits
274
+ POST /api/v1/marketplace/shop/profile create/edit the shop
275
+ POST /api/v1/marketplace/shop/machines create/edit a pack card
276
+ POST /api/v1/marketplace/shop/machines/:id/visibility on/off for buyers
277
+ GET /api/v1/marketplace/shop/orders the vendor queue
278
+ POST /api/v1/marketplace/shop/orders/:id/fulfil DELIVER — { items: [...] }
279
+ POST /api/v1/marketplace/purchases place an order
280
+ GET /api/v1/marketplace/purchases/:id one order + thread + files + `can`
281
+ POST /api/v1/marketplace/purchases/:id/accept pays, and is FINAL
282
+ POST /api/v1/marketplace/purchases/:id/request-changes free; nothing moves
283
+ GET /api/v1/marketplace/free-order-status/:shopSlug is MY first order here free?
284
+ ```
285
+
286
+ devcli — **two verbs, and they do not overlap**: `vidfarm shop` is the vendor
287
+ (`shop orders`, `shop deliver <id> --item "<preview>::<master>::<note>"`, `shop earnings`,
288
+ `shop withdraw`), `vidfarm purchases` is the buyer (`purchases shelf`, `vidfarm buy
289
+ <shop>/<card> -y`, `purchases accept <id> -y`, `purchases changes <id> "<fix>"`). Full lists:
290
+ `vidfarm shop help`, `vidfarm purchases help`. Everything takes `--json`.
291
+
292
+ **An order ends three ways, and only one is reversible.** `accept` approves and settles in full and
293
+ is FINAL in both directions. `request-changes` is FREE and reaches nothing on chain — the deposit
294
+ stays in escrow and the vendor re-delivers. `refund` returns the escrow; whoever signs pays the gas.
295
+ Never route a fixable note through Dollar Platoon's reject — theirs is final and would kill a funded
296
+ order. **Clean masters are withheld until the payout is stamped**; everything before that is
297
+ watermarked, and the rule is applied server-side.
298
+
299
+ ### First order free — the NEW SHOP flag
300
+
301
+ The platform can mark a shop so that **every buyer's FIRST order there is free** — one pack, $0,
302
+ one per SHOP rather than one per card. It is superagency-only
303
+ (`POST /api/v1/admin/shops/new-status`); a vendor can neither grant nor revoke it.
304
+
305
+ It changes three rules, and an agent that misses them will mislead its human:
306
+
307
+ - **No deposit and no escrow.** There is nothing to refund. A "refund" on a free order is a
308
+ **cancel** — it closes the order and returns nothing.
309
+ - **The buyer's approval is FINAL and releases the files by itself.** On the paid rail the PAYOUT
310
+ unlocks the masters; on the free rail the APPROVAL does, because there is no payout after it. So
311
+ accepting is the entire transaction and there is no undo behind it.
312
+ - **A spent free order does not come back.** Cancelling one does not restore the entitlement.
313
+
314
+ **Read `is_free` on the order — NEVER `total_usd === 0`.** A zero total reached any other way still
315
+ has escrow semantics, and treating it as free skips the payout. Underneath, a free order runs on a
316
+ **separate Dollar Platoon gig** (`dp_free_gig_id`): their API locks a gig's price mode at creation,
317
+ so one gig cannot sell both.
318
+
319
+ `GET …/shelf` is shared-cached and therefore only ever carries the **offer** (`shop.new_shop`).
320
+ Whether *this* buyer still has theirs is per-account and lives on its own `no-store` route. It is
321
+ **advisory**: price at the paid number and lower it on a yes, never the other way round.
322
+ `vidfarm purchases pack <shop>/<card>` does exactly that and prints the terms.
323
+
324
+ ## Older gig marketplace — broadcast a brief to a crowd
325
+
326
+ Web-only. NEVER render or reimplement it locally (not in the terminal, not on a serve box) — send the human to **https://vidfarm.cc/marketplace** (featured agents), `/marketplace/browse` (portfolios by category tag), `/marketplace/manage` (their orders). `vidfarm marketplace` prints exactly those links.
262
327
 
263
328
  An **order IS a Dollar Platoon gig** (tagged `vidfarm_order_<uuidv7>` plus category tags). Vidfarm exposes a deliberately thin REST wrapper — auth via session or the `vidfarm-api-key` header:
264
329
 
package/SKILL.director.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: vidfarm
3
- skill_version: 0.21.63
3
+ skill_version: 0.21.65
4
4
  description: Use Vidfarm as a director. Run a strategy **consultation** (the `brainstorm/*` chain — cold-start interview, awareness stages, persuasive angles, hooks, product placement). Answer "give me content ideas" / "what should I post" / "I need 30 videos this month" from the bundled idea banks (50 content frames x 5 awareness stages x 44 problem angles). Browse/add inspiration videos, browse the free public raws catalog BY CATEGORY (curated shelves like scroll-stoppers/greenscreen/reaction — the cheapest way to source footage for one video, and a ready-made clip pool for bulk scripting N variants), fork a template into a composition, edit it in the Trackpad Editor (timeline-based like Premiere/DaVinci), auto-decompose source video into scenes, render to MP4, approve into a shareable post, and schedule it. Includes login, provider keys, discovery, versioning, uploads/downloads, and billing. Every step is available as raw REST; `vidfarm-devcli` wraps those routes and composes the file-backed scripting flows.
5
5
  ---
6
6
 
@@ -854,9 +854,74 @@ devcli: `vidfarm channels` lists destinations, `vidfarm schedule <postId> --at <
854
854
 
855
855
  Deeper FlockPoster work (connecting accounts, channel management, direct posting/analytics outside vidfarm's schedule wrapper) is FlockPoster's own API, not vidfarm's — grab its skill first: `npx skills add https://github.com/OfficeXApp/flockposter-skill` (mirrored as `vidfarm skills add flockposter`).
856
856
 
857
- ## Marketplaceorder videos from specialist agents
858
-
859
- The marketplace is a **paid, web-only surface**. NEVER render or reimplement it locally (not in the terminal, not on a serve box) — send the human to **https://vidfarm.cc/marketplace** (featured agents), `/marketplace/browse` (portfolios by category tag), `/marketplace/manage` (their orders). `vidfarm marketplace` prints exactly those links.
857
+ ## Pack marketplace buy a pack, or sell one
858
+
859
+ **There are two marketplaces and they are not the same product.** This one is the **shelf**: a vendor
860
+ puts **pack cards** on `https://vidfarm.cc/marketplace` — one card is one offer, "7 videos, $35, 3-5
861
+ days" — and a buyer orders a **pack** from it. No brief bank, no bell, no swipe deck. The older gig
862
+ marketplace (briefs broadcast to a crowd) is the section after this one.
863
+
864
+ It was web-only until 2026-08-29. It is now REST, under `/api/v1/marketplace/`, and the devcli wraps
865
+ every route. Auth is the ordinary `vidfarm-api-key` header or a session; the whole surface is
866
+ paid-only and answers **402** to a free key.
867
+
868
+ ```
869
+ GET /api/v1/marketplace/shelf PUBLIC — what is for sale
870
+ GET /api/v1/marketplace/shelf/:shop/:machine one card + examples + reviews
871
+ GET /api/v1/marketplace/shop your shop, cards, limits
872
+ POST /api/v1/marketplace/shop/profile create/edit the shop
873
+ POST /api/v1/marketplace/shop/machines create/edit a pack card
874
+ POST /api/v1/marketplace/shop/machines/:id/visibility on/off for buyers
875
+ GET /api/v1/marketplace/shop/orders the vendor queue
876
+ POST /api/v1/marketplace/shop/orders/:id/fulfil DELIVER — { items: [...] }
877
+ POST /api/v1/marketplace/purchases place an order
878
+ GET /api/v1/marketplace/purchases/:id one order + thread + files + `can`
879
+ POST /api/v1/marketplace/purchases/:id/accept pays, and is FINAL
880
+ POST /api/v1/marketplace/purchases/:id/request-changes free; nothing moves
881
+ GET /api/v1/marketplace/free-order-status/:shopSlug is MY first order here free?
882
+ ```
883
+
884
+ devcli — **two verbs, and they do not overlap**: `vidfarm shop` is the vendor
885
+ (`shop orders`, `shop deliver <id> --item "<preview>::<master>::<note>"`, `shop earnings`,
886
+ `shop withdraw`), `vidfarm purchases` is the buyer (`purchases shelf`, `vidfarm buy
887
+ <shop>/<card> -y`, `purchases accept <id> -y`, `purchases changes <id> "<fix>"`). Full lists:
888
+ `vidfarm shop help`, `vidfarm purchases help`. Everything takes `--json`.
889
+
890
+ **An order ends three ways, and only one is reversible.** `accept` approves and settles in full and
891
+ is FINAL in both directions. `request-changes` is FREE and reaches nothing on chain — the deposit
892
+ stays in escrow and the vendor re-delivers. `refund` returns the escrow; whoever signs pays the gas.
893
+ Never route a fixable note through Dollar Platoon's reject — theirs is final and would kill a funded
894
+ order. **Clean masters are withheld until the payout is stamped**; everything before that is
895
+ watermarked, and the rule is applied server-side.
896
+
897
+ ### First order free — the NEW SHOP flag
898
+
899
+ The platform can mark a shop so that **every buyer's FIRST order there is free** — one pack, $0,
900
+ one per SHOP rather than one per card. It is superagency-only
901
+ (`POST /api/v1/admin/shops/new-status`); a vendor can neither grant nor revoke it.
902
+
903
+ It changes three rules, and an agent that misses them will mislead its human:
904
+
905
+ - **No deposit and no escrow.** There is nothing to refund. A "refund" on a free order is a
906
+ **cancel** — it closes the order and returns nothing.
907
+ - **The buyer's approval is FINAL and releases the files by itself.** On the paid rail the PAYOUT
908
+ unlocks the masters; on the free rail the APPROVAL does, because there is no payout after it. So
909
+ accepting is the entire transaction and there is no undo behind it.
910
+ - **A spent free order does not come back.** Cancelling one does not restore the entitlement.
911
+
912
+ **Read `is_free` on the order — NEVER `total_usd === 0`.** A zero total reached any other way still
913
+ has escrow semantics, and treating it as free skips the payout. Underneath, a free order runs on a
914
+ **separate Dollar Platoon gig** (`dp_free_gig_id`): their API locks a gig's price mode at creation,
915
+ so one gig cannot sell both.
916
+
917
+ `GET …/shelf` is shared-cached and therefore only ever carries the **offer** (`shop.new_shop`).
918
+ Whether *this* buyer still has theirs is per-account and lives on its own `no-store` route. It is
919
+ **advisory**: price at the paid number and lower it on a yes, never the other way round.
920
+ `vidfarm purchases pack <shop>/<card>` does exactly that and prints the terms.
921
+
922
+ ## Older gig marketplace — broadcast a brief to a crowd
923
+
924
+ Web-only. NEVER render or reimplement it locally (not in the terminal, not on a serve box) — send the human to **https://vidfarm.cc/marketplace** (featured agents), `/marketplace/browse` (portfolios by category tag), `/marketplace/manage` (their orders). `vidfarm marketplace` prints exactly those links.
860
925
 
861
926
  An **order IS a Dollar Platoon gig** (tagged `vidfarm_order_<uuidv7>` plus category tags). Vidfarm exposes a deliberately thin REST wrapper — auth via session or the `vidfarm-api-key` header:
862
927
 
package/SKILL.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: vidfarm
3
- skill_version: 0.21.63
3
+ skill_version: 0.21.65
4
4
  description: Entry point for making, editing, and rendering short-form video with Vidfarm. Use when the task is to create a video, ad, explainer, or social clip; to edit or re-theme an existing composition; to source footage or audio; or to run a batch of video variants. Routes to the full director skill and the `vidfarm` CLI.
5
5
  ---
6
6
 
@@ -121,6 +121,17 @@ function money(value) {
121
121
  const parsed = Number(value);
122
122
  return Number.isFinite(parsed) ? `$${parsed.toFixed(2)}` : "$—";
123
123
  }
124
+ /**
125
+ * What an order cost, in words.
126
+ *
127
+ * "$0.00" on a free order reads as a bug, and worse, it reads as a PAID order
128
+ * that went wrong — which is the one thing it must never look like, because
129
+ * the two rails have opposite undo rules. Never derive this from the number:
130
+ * a zero total reached any other way still has escrow behind it.
131
+ */
132
+ export function orderPrice(order) {
133
+ return order.is_free ? "FREE" : money(order.total_usd);
134
+ }
124
135
  function shortDate(value) {
125
136
  const raw = String(value ?? "");
126
137
  return raw ? raw.slice(0, 10) : "—";
@@ -135,16 +146,22 @@ function shortDate(value) {
135
146
  */
136
147
  export function orderStage(order) {
137
148
  if (order.refunded_at)
138
- return { label: "refunded", colour: DIM };
149
+ return { label: order.is_free ? "cancelled" : "refunded", colour: DIM };
150
+ // A FREE order is never "paid out" — nobody was paid. It closed, and the
151
+ // buyer's files were released by the approval itself.
139
152
  if (order.settled_at)
140
- return { label: "paid out", colour: GREEN };
141
- if (order.refund_requested_at)
142
- return { label: "refund asked", colour: YELLOW };
153
+ return { label: order.is_free ? "closed" : "paid out", colour: GREEN };
154
+ if (order.refund_requested_at) {
155
+ return { label: order.is_free ? "cancel asked" : "refund asked", colour: YELLOW };
156
+ }
143
157
  if (order.changes_requested_at)
144
158
  return { label: "changes asked", colour: YELLOW };
145
159
  if (order.delivery_id)
146
160
  return { label: "delivered", colour: CYAN };
147
- if (!order.funded)
161
+ // "UNFUNDED" is the vendor's do-not-work signal, and it is meaningless on the
162
+ // free rail: a free order carries no deposit BY DESIGN and is still real work
163
+ // the vendor agreed to. Flagging it red would train vendors to abandon it.
164
+ if (!order.funded && !order.is_free)
148
165
  return { label: "UNFUNDED", colour: RED };
149
166
  return { label: "waiting on vendor", colour: YELLOW };
150
167
  }
@@ -154,9 +171,10 @@ function printOrderRow(order, opts = {}) {
154
171
  ? ` · ${String(order.buyer_name || order.buyer_email)}`
155
172
  : "";
156
173
  console.log(` ${BOLD}${order.order_id}${RESET} ${order.machine_title}`
157
- + ` ${DIM}${order.packs}× pack · ${order.video_count} videos · ${money(order.total_usd)}`
174
+ + ` ${DIM}${order.packs}× pack · ${order.video_count} videos · ${orderPrice(order)}`
158
175
  + `${who} · ${shortDate(order.created_at)}${RESET}`
159
- + ` ${stage.colour}${stage.label}${RESET}`);
176
+ + ` ${stage.colour}${stage.label}${RESET}`
177
+ + (order.is_free ? ` ${CYAN}free first order${RESET}` : ""));
160
178
  }
161
179
  function printCardRow(card) {
162
180
  const shelf = card.listed
@@ -317,6 +335,11 @@ export const SHOP_HELP = `vidfarm shop — your storefront on the vidfarm market
317
335
  a buyer orders a pack from it. Every card waits for a manual review before it
318
336
  reaches the public shelf; \`shop\` tells you which state each one is in.
319
337
 
338
+ NEW SHOP. The platform can mark a shop so that every buyer's FIRST order there
339
+ is free — one pack, $0. You cannot set it or clear it; \`shop\` reports it, and
340
+ an operator grants it. Those orders are REAL WORK with no deposit behind them,
341
+ they never pay out, and the buyer's approval is final. Work them normally.
342
+
320
343
  Earning WITHOUT a shop? You do not need one — read vidfarm.cc/agentic-clipper.md
321
344
  and use \`vidfarm gigs\`. A shop is how buyers come to you instead.
322
345
 
@@ -374,6 +397,15 @@ async function cmdShopShow(ctx) {
374
397
  if (shop.headline)
375
398
  console.log(` ${shop.headline}`);
376
399
  console.log(` ${CYAN}${shop.shop_url}${RESET}`);
400
+ if (shop.new_shop) {
401
+ // The vendor cannot set this and cannot clear it — it is a promise the
402
+ // PLATFORM makes to buyers, so the console reports it rather than offering
403
+ // it. Reported at all because it changes what the vendor is agreeing to:
404
+ // real work, no deposit behind it.
405
+ console.log(` ${GREEN}NEW SHOP${RESET} — every buyer's first order here is free, one pack at $0.`);
406
+ console.log(` ${DIM}Those orders carry no deposit and the buyer's approval is final.`
407
+ + ` Work them like any other order. Granted by the platform; ask an operator to change it.${RESET}`);
408
+ }
377
409
  if (!result.body.dollarplatoon_connected) {
378
410
  console.log(` ${YELLOW}No Dollar Platoon key on this account — your cards cannot take a paid order.${RESET}`);
379
411
  console.log(` ${DIM}Connect it at ${ctx.host}/settings/marketplace${RESET}`);
@@ -539,6 +571,13 @@ async function cmdOrderDetail(ctx, orderId) {
539
571
  return;
540
572
  const { order, buyer, delivery, thread, review } = result.body;
541
573
  printOrderRow({ ...order, buyer_name: buyer?.name, buyer_email: buyer?.email }, { showBuyer: true });
574
+ if (order.is_free) {
575
+ // A vendor reading "$0" needs to know it is the promotion and not a broken
576
+ // order — and needs to know the undo rules are inverted before they ask a
577
+ // buyer to "just approve it".
578
+ console.log(` ${CYAN}This is the buyer's free first order.${RESET}`
579
+ + ` ${DIM}No deposit, no payout, and their approval closes it for good.${RESET}`);
580
+ }
542
581
  if (order.notes)
543
582
  console.log(`\n${BOLD}Their brief${RESET}\n ${order.notes.replace(/\n/g, "\n ")}`);
544
583
  if (order.answers?.length) {
@@ -660,6 +699,11 @@ async function cmdEarnings(ctx, values) {
660
699
  console.log(` In escrow ${YELLOW}${money(totals.pendingUsd)}${RESET} ${DIM}${totals.pending ?? 0} order(s)${RESET}`);
661
700
  console.log(` Refunded ${DIM}${money(totals.refundedUsd)} · ${totals.refunded ?? 0} order(s)${RESET}`);
662
701
  console.log(` Ordered ${money(totals.ordersUsd)} ${DIM}${totals.orders ?? 0} order(s)${RESET}`);
702
+ if (result.body.shop_slug && totals.orders && !totals.ordersUsd) {
703
+ // Real orders totalling nothing is what a New Shop's early history looks
704
+ // like. Without this line it reads as a broken rollup.
705
+ console.log(` ${DIM}Orders at $0 are free first orders — they close without a payout.${RESET}`);
706
+ }
663
707
  for (const machine of result.body.machines ?? []) {
664
708
  console.log(` ${machine.label || machine.machineId} ${DIM}${money(machine.totals?.payoutsUsd)} paid out${RESET}`);
665
709
  }
@@ -821,10 +865,20 @@ export const PURCHASES_HELP = `vidfarm purchases — buy packs of videos on the
821
865
  accept approve and pay. FINAL, in both directions.
822
866
  changes free. Nothing moves — the vendor re-delivers and you rule again.
823
867
  refund the escrow comes back to you. Whoever signs it pays the gas.
868
+ On a FREE order this is a cancel: it closes the order and returns
869
+ nothing, because nothing was taken.
870
+
871
+ FIRST ORDER FREE — some shops are marked NEW SHOP by the platform, and your
872
+ first order there is one pack at $0. \`purchases shelf\` flags the offer;
873
+ \`purchases pack\` says whether YOU still have yours. It is one per shop, not
874
+ one per card, and it changes the rules: no deposit, nothing to refund, and
875
+ YOUR APPROVAL IS FINAL — it releases the files at once, with no payout step
876
+ after it to hold anything back. Cancelling does not give the free order back.
824
877
 
825
878
  THE SHELF
826
879
  purchases shelf [--shop <slug>] What is for sale → GET ${API}/shelf
827
- purchases pack <shop>/<machineId> One card, its examples and reviews
880
+ purchases pack <shop>/<machineId> One card, its examples, reviews, and
881
+ whether your free first order is still there
828
882
 
829
883
  BUYING
830
884
  purchases buy <shop>/<machineId> [--packs <n>] [--notes "<brief>"]
@@ -863,6 +917,29 @@ export function splitPackRef(ref, values) {
863
917
  return { shop: explicitShop, machine: raw };
864
918
  throw new MarketplaceError("Name the pack as <shopSlug>/<machineId> — `vidfarm purchases shelf` lists both.");
865
919
  }
920
+ /**
921
+ * Does THIS account still have its free first order at this shop?
922
+ *
923
+ * A separate call on purpose. The shelf is shared-cached and can only say that
924
+ * the OFFER exists (`shop.new_shop`); the entitlement is per-account, so it has
925
+ * its own no-store route.
926
+ *
927
+ * Fails CLOSED — an unreachable answer is `false`, which prices the order at
928
+ * the paid number. Showing $0 and then charging is the one direction that is
929
+ * unforgivable, so this only ever LOWERS a price it already showed.
930
+ */
931
+ async function freeOrderAvailable(ctx, shopSlug) {
932
+ const result = await call(ctx, "GET", `${API}/free-order-status/${encodeURIComponent(shopSlug)}`)
933
+ .catch(() => null);
934
+ return result?.ok === true && result.body?.free_available === true;
935
+ }
936
+ /** The three sentences a buyer must read BEFORE they consent to a free order. */
937
+ const FREE_ORDER_TERMS = [
938
+ "It is ONE pack, at $0, and you get one per shop — not one per card.",
939
+ "Nothing is deposited, so there is no escrow and nothing to refund.",
940
+ "YOUR APPROVAL IS FINAL and releases the files at once. There is no payout to hold back,"
941
+ + " so accepting is the whole transaction."
942
+ ];
866
943
  async function cmdShelf(ctx, values) {
867
944
  const result = await call(ctx, "GET", `${API}/shelf`, {
868
945
  query: { shop: optionalText(values.shop), limit: optionalText(values.limit) }
@@ -877,7 +954,10 @@ async function cmdShelf(ctx, values) {
877
954
  }
878
955
  console.log(`${BOLD}${packs.length} pack${packs.length === 1 ? "" : "s"} for sale${RESET}`);
879
956
  for (const pack of packs) {
880
- console.log(`\n ${BOLD}${pack.title}${RESET} ${DIM}by ${pack.shop.display_name}${RESET}`);
957
+ console.log(`\n ${BOLD}${pack.title}${RESET} ${DIM}by ${pack.shop.display_name}${RESET}`
958
+ // The OFFER, which is the same for everybody and safe on a cached list.
959
+ // Whether YOU still have yours is answered by `purchases pack`.
960
+ + (pack.shop.new_shop ? ` ${GREEN}FIRST ORDER FREE${RESET}` : ""));
881
961
  console.log(` ${money(pack.price_per_lot_usd)} for ${pack.lot_size} videos`
882
962
  + `${pack.delivery_estimate ? ` ${DIM}· ${pack.delivery_estimate}${RESET}` : ""}`);
883
963
  if (pack.note)
@@ -897,6 +977,19 @@ async function cmdPack(ctx, values, ref) {
897
977
  + `${pack.delivery_estimate ? ` · ${pack.delivery_estimate}` : ""}`);
898
978
  if (pack.note)
899
979
  console.log(` ${pack.note}`);
980
+ // The card above is a cached, public answer. THIS is the per-account one, and
981
+ // it is the only place that can say the word "you".
982
+ if (result.body.shop.new_shop) {
983
+ if (await freeOrderAvailable(ctx, result.body.shop.shop_slug)) {
984
+ console.log(`\n ${GREEN}Your first order at this shop is FREE — one pack, $0.${RESET}`);
985
+ for (const term of FREE_ORDER_TERMS)
986
+ console.log(` ${DIM}· ${term}${RESET}`);
987
+ }
988
+ else {
989
+ console.log(`\n ${DIM}This shop offers a free first order, but you have already used yours.`
990
+ + ` This one is ${money(pack.price_per_lot_usd)}.${RESET}`);
991
+ }
992
+ }
900
993
  if (pack.before_you_buy_note) {
901
994
  console.log(`\n ${YELLOW}Before you buy${RESET}\n ${pack.before_you_buy_note}`);
902
995
  }
@@ -938,9 +1031,37 @@ async function cmdBuy(ctx, values, ref) {
938
1031
  ].join("\n"));
939
1032
  }
940
1033
  const total = Math.round(packs * Number(pack.price_per_lot_usd) * 100) / 100;
1034
+ // ── is this one free? ──
1035
+ //
1036
+ // ADVISORY, and only ever downward. The order route claims the free pack
1037
+ // again with a conditional write, so a stale yes here cannot mint a free
1038
+ // order — it can only produce a confirmation that was too generous, and the
1039
+ // server then charges the real price. Which is why this is read BEFORE the
1040
+ // confirmation and never after: quoting $0 and then charging is the one
1041
+ // direction a buyer cannot forgive.
1042
+ const free = card.body.shop.new_shop === true
1043
+ && await freeOrderAvailable(ctx, card.body.shop.shop_slug);
1044
+ // A free order is ONE pack whatever was asked for — the free rail prices a
1045
+ // whole order at $0 and cannot discount part of one. Say so before they
1046
+ // consent, rather than silently delivering a third of what they typed.
1047
+ if (free && packs > 1 && !ctx.json) {
1048
+ console.error(`${YELLOW}Your free first order is ONE pack. `
1049
+ + `Ordering ${packs} would be charged in full — drop --packs to take the free one.${RESET}`);
1050
+ }
941
1051
  if (!values.yes && !ctx.json) {
942
1052
  console.log(`${BOLD}${packs} × ${pack.title}${RESET} from ${card.body.shop.display_name}`);
943
- console.log(` ${packs * pack.lot_size} videos · ${BOLD}${money(total)}${RESET} into escrow now`);
1053
+ if (free && packs === 1) {
1054
+ console.log(` ${pack.lot_size} videos · ${GREEN}${BOLD}FREE${RESET}`
1055
+ + ` ${DIM}— your first order at this shop${RESET}`);
1056
+ // THE CONSENT POINT. "Free" is the part a buyer reads; the part they must
1057
+ // read is that free costs them the undo. On this rail there is no escrow
1058
+ // to hold back and approval ends the order outright.
1059
+ for (const term of FREE_ORDER_TERMS)
1060
+ console.log(` ${YELLOW}· ${term}${RESET}`);
1061
+ }
1062
+ else {
1063
+ console.log(` ${packs * pack.lot_size} videos · ${BOLD}${money(total)}${RESET} into escrow now`);
1064
+ }
944
1065
  if (pack.before_you_buy_note)
945
1066
  console.log(` ${YELLOW}${pack.before_you_buy_note}${RESET}`);
946
1067
  console.log(`\n ${DIM}Add -y to place it.${RESET}`);
@@ -975,8 +1096,21 @@ async function cmdBuy(ctx, values, ref) {
975
1096
  return;
976
1097
  console.log(`${GREEN}${result.body.replay ? "Already ordered." : "Ordered."}${RESET}`
977
1098
  + ` ${BOLD}${result.body.order_id}${RESET}`);
978
- console.log(` ${result.body.video_count} videos · ${money(result.body.total_usd)}`
979
- + ` · ${result.body.funded ? `${GREEN}funded${RESET}` : `${YELLOW}NOT funded no deposit was taken${RESET}`}`);
1099
+ // THE SERVER DECIDED, not the check above. The claim is a conditional write
1100
+ // and this is its answer, so a race that lost prints the price it was charged.
1101
+ console.log(` ${result.body.video_count} videos · `
1102
+ + (result.body.free ? `${GREEN}FREE — your first order at this shop${RESET}` : money(result.body.total_usd))
1103
+ + ` · ${result.body.funded
1104
+ ? (result.body.free ? `${GREEN}live${RESET}` : `${GREEN}funded${RESET}`)
1105
+ // On the free rail there is nothing to fund, so an unfunded free order
1106
+ // means it never reached Dollar Platoon at all — a different fault, and
1107
+ // it needs a different sentence.
1108
+ : (result.body.free
1109
+ ? `${YELLOW}NOT live — it did not reach Dollar Platoon${RESET}`
1110
+ : `${YELLOW}NOT funded — no deposit was taken${RESET}`)}`);
1111
+ if (result.body.free) {
1112
+ console.log(` ${YELLOW}Your approval on this order is FINAL and releases the files at once.${RESET}`);
1113
+ }
980
1114
  console.log(` ${CYAN}${ctx.host}${result.body.order_url}${RESET}`);
981
1115
  }
982
1116
  async function cmdPurchasesList(ctx) {
@@ -991,7 +1125,7 @@ async function cmdPurchasesList(ctx) {
991
1125
  }
992
1126
  for (const row of rows) {
993
1127
  console.log(` ${BOLD}${row.order_id ?? row.delivery_id}${RESET} ${row.machine_title}`
994
- + ` ${DIM}from ${row.shop_name} · ${money(row.total_usd)} · ${shortDate(row.created_at)}${RESET}`
1128
+ + ` ${DIM}from ${row.shop_name} · ${orderPrice(row)} · ${shortDate(row.created_at)}${RESET}`
995
1129
  + ` ${row.opened_at ? `${DIM}opened${RESET}` : `${CYAN}sealed${RESET}`} ${row.status}`);
996
1130
  }
997
1131
  }
@@ -1013,7 +1147,12 @@ async function cmdPurchaseDetail(ctx, orderId) {
1013
1147
  console.log(`\n${BOLD}The pack${RESET} ${DIM}${delivery.items.length} item(s)`
1014
1148
  + `${delivery.opened_at ? "" : " · still sealed"}${RESET}`);
1015
1149
  if (!delivery.masters_released) {
1016
- console.log(` ${YELLOW}Masters are locked until you accept and the payout lands.${RESET}`);
1150
+ // Two rails, two release rules, and the difference matters to a buyer
1151
+ // deciding whether to accept: on a paid order the payout unlocks the
1152
+ // masters, on a free one the approval does it by itself.
1153
+ console.log(order.is_free
1154
+ ? ` ${YELLOW}Masters are locked until you accept. On a free order accepting IS the end — there is no payout after it.${RESET}`
1155
+ : ` ${YELLOW}Masters are locked until you accept and the payout lands.${RESET}`);
1017
1156
  }
1018
1157
  for (const item of delivery.items) {
1019
1158
  const verdict = item.verdict === "approved"
@@ -1042,9 +1181,13 @@ async function cmdPurchaseDetail(ctx, orderId) {
1042
1181
  if (review)
1043
1182
  console.log(`\n${BOLD}Your review${RESET} ${review.overall}/5`);
1044
1183
  const moves = [
1045
- can?.accept ? `accept (pays ${money(order.total_usd)}, FINAL)` : null,
1184
+ can?.accept
1185
+ ? (order.is_free ? "accept (FINAL, nothing charged)" : `accept (pays ${money(order.total_usd)}, FINAL)`)
1186
+ : null,
1046
1187
  can?.request_changes ? "changes (free)" : null,
1047
- can?.refund ? "refund" : null,
1188
+ // "refund" on an order that cost nothing reads as a bug. Same route either
1189
+ // way — it closes the order — but the word has to match what happened.
1190
+ can?.refund ? (order.is_free ? "cancel" : "refund") : null,
1048
1191
  can?.review ? "review" : null
1049
1192
  ].filter(Boolean);
1050
1193
  if (moves.length)
@@ -1112,14 +1255,24 @@ async function cmdRate(ctx, values, orderId, itemId, verdictWord) {
1112
1255
  }
1113
1256
  async function cmdAccept(ctx, values, orderId) {
1114
1257
  const body = await fetchOrder(ctx, orderId);
1258
+ const free = body.order.is_free === true;
1115
1259
  if (!values.yes && !ctx.json) {
1116
- // The one command in this file that spends money irreversibly. It gets a
1117
- // confirmation for the same reason the web card has a dialog.
1118
- console.log(`${BOLD}Accepting pays ${money(body.order.total_usd)} to ${body.order.shop_name}, in full.${RESET}`);
1119
- console.log(` Dollar Platoon's verdict is FINAL there is no undo, and no partial payment.`);
1260
+ // The one command in this file that cannot be undone. It gets a
1261
+ // confirmation for the same reason the web card has a dialog — and a FREE
1262
+ // order needs it MORE, not less: nothing is charged, so the only thing the
1263
+ // buyer is spending is their right to send the pack back.
1264
+ console.log(free
1265
+ ? `${BOLD}Accepting closes this order for good. Nothing is charged — it is your free first order.${RESET}`
1266
+ : `${BOLD}Accepting pays ${money(body.order.total_usd)} to ${body.order.shop_name}, in full.${RESET}`);
1267
+ console.log(free
1268
+ ? ` Your verdict is FINAL and releases the files at once. There is no payout to hold back,`
1269
+ + ` so this IS the end of the order — you cannot ask for changes afterwards.`
1270
+ : ` Dollar Platoon's verdict is FINAL — there is no undo, and no partial payment.`);
1120
1271
  const rejected = (body.delivery?.items ?? []).filter((item) => item.verdict === "rejected").length;
1121
1272
  if (rejected) {
1122
- console.log(` ${YELLOW}You marked ${rejected} video(s) wrong. Accepting pays for them anyway.${RESET}`);
1273
+ console.log(free
1274
+ ? ` ${YELLOW}You marked ${rejected} video(s) wrong. Accepting takes the pack as it is.${RESET}`
1275
+ : ` ${YELLOW}You marked ${rejected} video(s) wrong. Accepting pays for them anyway.${RESET}`);
1123
1276
  console.log(` ${YELLOW}Free alternative: vidfarm purchases changes ${orderId} "<what to fix>"${RESET}`);
1124
1277
  }
1125
1278
  console.log(`\n ${DIM}Add -y to accept.${RESET}`);
@@ -1130,9 +1283,11 @@ async function cmdAccept(ctx, values, orderId) {
1130
1283
  if (emit(ctx, result.body))
1131
1284
  return;
1132
1285
  console.log(`${GREEN}${result.body.message}${RESET}`);
1133
- if (!result.body.settled) {
1134
- // Approved-but-unsettled is real and recoverable. Never tell this buyer to
1135
- // accept again there is nothing left to approve.
1286
+ // Approved-but-unsettled is real and recoverable, and it is a PAID-rail
1287
+ // state only: on the free rail the approval is the whole transaction, so
1288
+ // there is no second call left to fail.
1289
+ if (!result.body.settled && !free) {
1290
+ // Never tell this buyer to accept again — there is nothing left to approve.
1136
1291
  console.log(`${YELLOW}The verdict landed but the payout did not. Do NOT accept again — retry the payout shortly.${RESET}`);
1137
1292
  }
1138
1293
  }
@@ -1154,9 +1309,19 @@ async function cmdBuyerRefund(ctx, values, orderId) {
1154
1309
  assertOk(result, "purchases refund");
1155
1310
  if (emit(ctx, result.body))
1156
1311
  return;
1312
+ // The server's own sentence already knows which rail it was on — a free
1313
+ // order gets "cancelled", a paid one "refunded" — so it is printed as-is
1314
+ // rather than reworded here.
1157
1315
  console.log(`${GREEN}${result.body.message}${RESET}`);
1158
1316
  if (result.body.tx_hash)
1159
1317
  console.log(` ${DIM}tx ${result.body.tx_hash}${RESET}`);
1318
+ // A spent free order does NOT come back. Said here because this is the one
1319
+ // command that spends it, and a buyer cancelling to "re-order properly"
1320
+ // would otherwise find out by being charged.
1321
+ if (result.body.free_spent) {
1322
+ console.log(` ${YELLOW}Your free first order at this shop stays used. `
1323
+ + `A new order here will be charged.${RESET}`);
1324
+ }
1160
1325
  }
1161
1326
  async function cmdReview(ctx, values, orderId) {
1162
1327
  const axes = ["value", "speed", "quality", "support"];