@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.
- package/.agents/skills/dollarplatoon-skill/SKILL.md +26 -4
- package/.agents/skills/dollarplatoon-skill/SOURCE.md +27 -1
- package/.agents/skills/dollarplatoon-skill/skill/clients.md +13 -1
- package/.agents/skills/dollarplatoon-skill/skill/gigs.md +153 -6
- package/.agents/skills/dollarplatoon-skill/skill/gigworkers.md +29 -2
- package/.agents/skills/dollarplatoon-skill/skill/orders.md +191 -5
- package/.agents/skills/dollarplatoon-skill/skill/quickstart.md +3 -3
- package/.agents/skills/dollarplatoon-skill/skill/staging.md +28 -20
- package/.agents/skills/vidfarm/SKILL.md +1 -1
- package/.agents/skills/vidfarm/references/core-workflows.md +68 -3
- package/SKILL.director.md +69 -4
- package/SKILL.md +1 -1
- package/dist/src/devcli/marketplace-console.js +189 -24
- package/marketplace.md +24 -0
- package/package.json +3 -1
|
@@ -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` | `
|
|
28
|
+
| Treasury | `0x932B9D4CA0e11D7859C43F7e58492F2C6206D485` | `0x42118E4F0c0E326c5c09d2aC5d764957025214A9` |
|
|
29
29
|
| Accounts | Entirely separate. Your production key does **not** work here. | |
|
|
30
|
-
| `inbound_order` | **Available** |
|
|
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
|
-
|
|
34
|
-
|
|
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
|
-
|
|
113
|
-
|
|
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
|
-
|
|
118
|
-
|
|
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
|
|
125
|
-
are encrypted with a key that is the published development default
|
|
126
|
-
|
|
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
|
-
- **
|
|
130
|
-
which has no per-task deposits, no undo
|
|
131
|
-
|
|
132
|
-
- **
|
|
133
|
-
|
|
134
|
-
|
|
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.
|
|
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
|
-
##
|
|
260
|
-
|
|
261
|
-
|
|
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.
|
|
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
|
-
##
|
|
858
|
-
|
|
859
|
-
|
|
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.
|
|
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
|
-
|
|
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 · ${
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
979
|
-
|
|
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} · ${
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
1117
|
-
// confirmation for the same reason the web card has a dialog
|
|
1118
|
-
|
|
1119
|
-
|
|
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(
|
|
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
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
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"];
|