@officexapp/vidfarm-devcli 0.21.58 → 0.21.60
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/vidfarm/SKILL.md +3 -3
- package/.agents/skills/vidfarm/recipes/retheme-template.md +6 -5
- package/.agents/skills/vidfarm/references/content-ideas.md +97 -14
- package/.agents/skills/vidfarm/references/core-workflows.md +4 -0
- package/.agents/skills/vidfarm/references/editor-workflows.md +25 -0
- package/SKILL.director.md +135 -22
- package/SKILL.md +9 -2
- package/clipper.md +7 -3
- package/dist/src/cli.js +201 -16
- package/dist/src/devcli/clipper-panel.js +1828 -0
- package/dist/src/devcli/clipper-run.js +643 -126
- package/dist/src/devcli/marketplace-gigs.js +307 -4
- package/dist/src/devcli/proof-verify.js +465 -0
- package/dist/src/devcli/skill-docs.js +14 -0
- package/experimental/flash-harness.md +142 -18
- package/experimental/meme-recaption.md +1044 -79
- package/experimental/sticker-slideshow-tips.md +511 -86
- package/marketplace.md +427 -12
- package/package.json +6 -1
- package/update.md +21 -1
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
import { parseArgs } from "node:util";
|
|
21
21
|
import { readFileSync } from "node:fs";
|
|
22
22
|
import { readStoredAuth } from "./auth-store.js";
|
|
23
|
+
import { verifyProof } from "./proof-verify.js";
|
|
23
24
|
const BOLD = "\x1b[1m";
|
|
24
25
|
const DIM = "\x1b[2m";
|
|
25
26
|
const GREEN = "\x1b[32m";
|
|
@@ -82,15 +83,47 @@ GIGWORKER — "help me earn on vidfarm" (free — bring your own key: export DOL
|
|
|
82
83
|
|
|
83
84
|
delivering, and getting paid:
|
|
84
85
|
gigs upload <file> [--prefix p] Put a file on DollarPlatoon's own s3, get a durable url
|
|
86
|
+
gigs verify-proof --proof <url> [--proof <url>…] RUN THIS BEFORE YOU SUBMIT. No key needed.
|
|
87
|
+
Loads every url the way a stranger's browser would (no cookie,
|
|
88
|
+
no login) and refuses a dead link, a 403 bucket, an HTML error
|
|
89
|
+
body, or a presigned url whose read access expires. Also checks
|
|
90
|
+
you did not swap the two halves of the delivery.
|
|
91
|
+
--clean-master <file> …the master you are about to seal (checked on disk)
|
|
92
|
+
--private-note "<text>" …the note you are about to send
|
|
93
|
+
--clean-in-proof "the proof url IS my final cut and I meant that" — silences it
|
|
94
|
+
--strict warnings fail too Exits non-zero on any failure.
|
|
85
95
|
gigs submit <gig-id> --task <task-id> --proof <url> [--proof <url>…]
|
|
96
|
+
Runs verify-proof first and REFUSES to send a broken proof
|
|
97
|
+
(--skip-verify overrides, at your own risk).
|
|
86
98
|
--clean-master <file> Upload it and seal it in the private note. THE WATERMARKED
|
|
87
99
|
cut goes in --proof; the clean master goes here, and the
|
|
88
100
|
buyer cannot read it until a rollup has actually paid you.
|
|
89
101
|
--private-note "<text>" Anything else that follows the master (ProRes, project
|
|
90
|
-
files, music licence id). 8000 chars,
|
|
102
|
+
files, music licence id). 8000 chars, and on a SUBMITTED
|
|
103
|
+
proof it cannot be changed — withdraw it first, or draft it.
|
|
91
104
|
--mailbox <id> --tags a,b --note "<text>" (--note is the PUBLIC feedback field)
|
|
105
|
+
--ask <usd> Quote your own price (machines with price offers on). An
|
|
106
|
+
ask is a QUOTE — the buyer must approve at that number.
|
|
107
|
+
--draft Save it WITHOUT sending. No review clock, no webhook, the
|
|
108
|
+
buyer sees nothing. (same as: gigs draft <gig-id> …)
|
|
92
109
|
Watch the cut and approve it BEFORE this call — a rejection scores on your
|
|
93
110
|
wallet and clients read it as a trust score on your public profile.
|
|
111
|
+
|
|
112
|
+
drafts, and the undo (a proof is NOT one-way while it is still pending):
|
|
113
|
+
gigs draft <gig-id> --task <id> --proof <url> Save without sending. Claims the task.
|
|
114
|
+
gigs drafts <gig-id> Your unsent drafts on that machine
|
|
115
|
+
gigs edit-draft <gig-id> <proof-id> [--proof <url>…] [--private-note "<t>"] [--ask <usd>]
|
|
116
|
+
Change an unsent draft. --private-note "" clears it.
|
|
117
|
+
gigs send <gig-id> <proof-id> Send a draft. The price is snapshotted NOW, not when you
|
|
118
|
+
saved it, and the review clock starts here.
|
|
119
|
+
gigs withdraw <gig-id> <proof-id> THE UNDO. Pulls a pending proof back to draft, stops the
|
|
120
|
+
review clock, and hides it from the buyer again.
|
|
121
|
+
(aliases: undo, unsend)
|
|
122
|
+
Works ONLY while the proof is pending. Once the buyer has
|
|
123
|
+
approved or rejected it the verdict is theirs, and anything
|
|
124
|
+
inside a rollup or already paid is final.
|
|
125
|
+
gigs discard <gig-id> <proof-id> Delete a draft and free its task. Drafts only — a
|
|
126
|
+
submitted proof stays on the record. (alias: delete-draft)
|
|
94
127
|
gigs proof <gig-id> <proof-id> Read your own proof back: paid_out_at + the private note
|
|
95
128
|
gigs earnings Your rollups + totals. (aliases: rollups, paid)
|
|
96
129
|
gigs mine Your mailboxes across every gig
|
|
@@ -100,12 +133,14 @@ GIGWORKER — "help me earn on vidfarm" (free — bring your own key: export DOL
|
|
|
100
133
|
|
|
101
134
|
e.g. DOLLARPLATOON_API_KEY=… vidfarm gigs earn
|
|
102
135
|
vidfarm gigs check "https://dollarplatoon.com/gig/GIG_01H…/join?invite=abc"
|
|
136
|
+
vidfarm gigs verify-proof --proof https://…/final-watermarked.mp4 --clean-master ./final-clean.mp4
|
|
103
137
|
vidfarm gigs submit GIG_01H… --task TASK_01H… \\
|
|
104
138
|
--proof https://…/final-watermarked.mp4 --clean-master ./final-clean.mp4
|
|
105
139
|
vidfarm gigs add-task --task "60s explainer for acme.com" --price 0.25 \\
|
|
106
140
|
--upload-link "https://vidfarm.cc/directory/preview/dsh_…/files/acme"
|
|
107
141
|
vidfarm gigs proofs --status pending
|
|
108
142
|
vidfarm gigs approve PRF_01H… --feedback "Great hook, keeping it."
|
|
143
|
+
vidfarm gigs withdraw GIG_01H… PRF_01H… # sent the wrong cut — take it back
|
|
109
144
|
|
|
110
145
|
Building the video is a separate job: https://vidfarm.cc/agentic-clipper.md is the loop,
|
|
111
146
|
and https://vidfarm.cc/flash-harness.md is how to build it on a weak model.`;
|
|
@@ -1019,6 +1054,55 @@ async function cmdClaim(auth, values, positionals) {
|
|
|
1019
1054
|
console.log(`${DIM} vidfarm gigs submit ${gigId} --task ${tasks[0]?.id ?? "<task-id>"} --proof <public mp4 url>${RESET}`);
|
|
1020
1055
|
});
|
|
1021
1056
|
}
|
|
1057
|
+
// ── the pre-submit sanity check ──────────────────────────────────────────────
|
|
1058
|
+
/** Print one verify result the same way from `verify-proof` and from `submit`. */
|
|
1059
|
+
function printVerify(result, strict) {
|
|
1060
|
+
const paint = (check) => {
|
|
1061
|
+
const badge = check.level === "fail" ? `${RED}✗ FAIL${RESET}` : check.level === "warn" ? `${YELLOW}! WARN${RESET}` : `${GREEN}✓ OK ${RESET}`;
|
|
1062
|
+
console.log(` ${badge} ${BOLD}${check.code}${RESET} ${DIM}${short(check.target, 70)}${RESET}`);
|
|
1063
|
+
console.log(` ${check.message}`);
|
|
1064
|
+
if (check.hint)
|
|
1065
|
+
console.log(` ${DIM}${check.hint}${RESET}`);
|
|
1066
|
+
};
|
|
1067
|
+
for (const check of result.checks)
|
|
1068
|
+
paint(check);
|
|
1069
|
+
console.log("");
|
|
1070
|
+
if (result.failures.length) {
|
|
1071
|
+
console.log(`${RED}DO NOT SUBMIT${RESET} — ${result.failures.length} failure${result.failures.length === 1 ? "" : "s"}. A buyer who cannot play it passes on it, and once they have ruled you cannot take it back.`);
|
|
1072
|
+
}
|
|
1073
|
+
else if (result.warnings.length && strict) {
|
|
1074
|
+
console.log(`${YELLOW}HOLD${RESET} — --strict, so ${result.warnings.length} warning${result.warnings.length === 1 ? "" : "s"} count as failures.`);
|
|
1075
|
+
}
|
|
1076
|
+
else if (result.warnings.length) {
|
|
1077
|
+
console.log(`${GREEN}✓ The links load.${RESET} ${DIM}Read the ${result.warnings.length} warning${result.warnings.length === 1 ? "" : "s"} above before you send it.${RESET}`);
|
|
1078
|
+
}
|
|
1079
|
+
else {
|
|
1080
|
+
console.log(`${GREEN}✓ CLEARED${RESET} ${DIM}— every link plays for a stranger and the two halves of the delivery are the right way round.${RESET}`);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
/**
|
|
1084
|
+
* `vidfarm gigs verify-proof` — needs NO api key on purpose. A share-link worker
|
|
1085
|
+
* with no account, and an agent that has not logged in yet, both have to be able
|
|
1086
|
+
* to check a proof before it is sent. Exits 2 on any failure so it chains:
|
|
1087
|
+
* `verify-proof … && gigs submit …`.
|
|
1088
|
+
*/
|
|
1089
|
+
export async function cmdVerifyProof(values) {
|
|
1090
|
+
const proofs = [].concat(values.proof ?? []);
|
|
1091
|
+
if (!proofs.length) {
|
|
1092
|
+
throw new Error("gigs verify-proof needs at least one --proof <url>: vidfarm gigs verify-proof --proof https://…/final-watermarked.mp4");
|
|
1093
|
+
}
|
|
1094
|
+
const strict = Boolean(values.strict);
|
|
1095
|
+
const result = await verifyProof({
|
|
1096
|
+
proofs,
|
|
1097
|
+
privateNote: values["private-note"] ? String(values["private-note"]) : undefined,
|
|
1098
|
+
cleanMaster: values["clean-master"] ? String(values["clean-master"]) : undefined,
|
|
1099
|
+
cleanInProof: Boolean(values["clean-in-proof"])
|
|
1100
|
+
}, { probe: values["no-network"] !== true });
|
|
1101
|
+
const blocked = !result.ok || (strict && result.warnings.length > 0);
|
|
1102
|
+
out(Boolean(values.json), { ...result, may_submit: !blocked, strict }, () => printVerify(result, strict));
|
|
1103
|
+
if (blocked)
|
|
1104
|
+
process.exitCode = 2;
|
|
1105
|
+
}
|
|
1022
1106
|
async function cmdSubmit(auth, values, positionals) {
|
|
1023
1107
|
const gigId = positionals[0];
|
|
1024
1108
|
if (!gigId)
|
|
@@ -1031,6 +1115,55 @@ async function cmdSubmit(auth, values, positionals) {
|
|
|
1031
1115
|
throw new Error("gigs submit needs --task <task-id>. On a queue machine that is the POLLED task's id (never the subject line) — it is what claims the task to you.");
|
|
1032
1116
|
}
|
|
1033
1117
|
const tags = values.tags ? String(values.tags).split(",").map((t) => t.trim()).filter(Boolean) : [];
|
|
1118
|
+
// `gigs draft` is this same command with the flag forced on. A draft is stored
|
|
1119
|
+
// and claims its task, but nothing is delivered: no review clock, no webhook,
|
|
1120
|
+
// and the buyer's dashboard and proof list both filter it out.
|
|
1121
|
+
const asDraft = Boolean(values.draft);
|
|
1122
|
+
// THE SANITY CHECK, BEFORE THE BUYER SEES IT. A pending proof CAN be pulled
|
|
1123
|
+
// back (`gigs withdraw`), but only until the buyer rules on it — and the two
|
|
1124
|
+
// ways a finished video loses the payout are both mechanical and both faster
|
|
1125
|
+
// than a rejection: the link does not load for the buyer, or the public half
|
|
1126
|
+
// and the escrowed half were swapped. Both are checkable, so neither is left
|
|
1127
|
+
// to memory. This runs BEFORE the clean-master upload so a broken proof costs
|
|
1128
|
+
// no bytes. A draft delivers nothing, so there it only warns.
|
|
1129
|
+
if (!values["skip-verify"] && !asDraft) {
|
|
1130
|
+
const check = await verifyProof({
|
|
1131
|
+
proofs,
|
|
1132
|
+
privateNote: values["private-note"] ? String(values["private-note"]) : undefined,
|
|
1133
|
+
cleanMaster: values["clean-master"] ? String(values["clean-master"]) : undefined,
|
|
1134
|
+
cleanInProof: Boolean(values["clean-in-proof"])
|
|
1135
|
+
});
|
|
1136
|
+
if (!check.ok) {
|
|
1137
|
+
if (values.json)
|
|
1138
|
+
console.log(JSON.stringify({ ok: false, submitted: false, verify: check }, null, 2));
|
|
1139
|
+
else
|
|
1140
|
+
printVerify(check, false);
|
|
1141
|
+
throw new Error("Refusing to submit: the pre-submit check failed (see above). Fix it, or re-run with --skip-verify if you are certain the buyer can play that url.");
|
|
1142
|
+
}
|
|
1143
|
+
if (check.warnings.length && !values.json) {
|
|
1144
|
+
for (const warning of check.warnings) {
|
|
1145
|
+
console.log(`${YELLOW}!${RESET} ${BOLD}${warning.code}${RESET} ${warning.message}`);
|
|
1146
|
+
if (warning.hint)
|
|
1147
|
+
console.log(` ${DIM}${warning.hint}${RESET}`);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
else if (asDraft && !values["skip-verify"] && !values.json) {
|
|
1152
|
+
// A draft is the one place a broken link is not a mistake — it is usually a
|
|
1153
|
+
// placeholder for a render that has not finished. So the check still runs
|
|
1154
|
+
// and still prints, but it never refuses. `gigs send` is the gate.
|
|
1155
|
+
const check = await verifyProof({
|
|
1156
|
+
proofs,
|
|
1157
|
+
privateNote: values["private-note"] ? String(values["private-note"]) : undefined,
|
|
1158
|
+
cleanMaster: values["clean-master"] ? String(values["clean-master"]) : undefined,
|
|
1159
|
+
cleanInProof: Boolean(values["clean-in-proof"])
|
|
1160
|
+
});
|
|
1161
|
+
for (const problem of [...check.failures, ...check.warnings]) {
|
|
1162
|
+
console.log(`${YELLOW}!${RESET} ${BOLD}${problem.code}${RESET} ${problem.message}`);
|
|
1163
|
+
}
|
|
1164
|
+
if (check.failures.length)
|
|
1165
|
+
console.log(`${DIM}Draft saved anyway — fix these before ${BOLD}gigs send${RESET}${DIM}, which does refuse.${RESET}`);
|
|
1166
|
+
}
|
|
1034
1167
|
// The escrowed half of the delivery. `proofs[]` is a PUBLIC playable url — the
|
|
1035
1168
|
// buyer can download it and so can anyone they forward it to — so the
|
|
1036
1169
|
// watermarked cut goes there and the clean master goes in `private_note`,
|
|
@@ -1054,11 +1187,21 @@ async function cmdSubmit(auth, values, positionals) {
|
|
|
1054
1187
|
...(values.mailbox ? { mailbox_id: String(values.mailbox) } : {}),
|
|
1055
1188
|
...(tags.length ? { tags } : {}),
|
|
1056
1189
|
...(values.note ? { feedback: String(values.note) } : {}),
|
|
1057
|
-
...(privateNote ? { private_note: privateNote } : {})
|
|
1190
|
+
...(privateNote ? { private_note: privateNote } : {}),
|
|
1191
|
+
...(values.ask ? { asking_price: Number(values.ask) } : {}),
|
|
1192
|
+
...(asDraft ? { draft: true } : {})
|
|
1058
1193
|
}
|
|
1059
1194
|
});
|
|
1060
1195
|
const proof = result.proof ?? result;
|
|
1061
1196
|
out(Boolean(values.json), result, () => {
|
|
1197
|
+
if (asDraft) {
|
|
1198
|
+
console.log(`${GREEN}✓${RESET} Draft saved on ${BOLD}${gigId}${RESET} ${DIM}${proof.id ?? result.proof_id ?? ""}${RESET}`);
|
|
1199
|
+
console.log(` ${DIM}The buyer sees nothing. No review clock, no webhook, and it counts as no submission.${RESET}`);
|
|
1200
|
+
console.log(` ${DIM}It DOES hold the task against your open-task cap until you send or discard it.${RESET}`);
|
|
1201
|
+
console.log(` ${DIM}Edit it: vidfarm gigs edit-draft ${gigId} ${proof.id ?? "<proof-id>"} --proof <url>${RESET}`);
|
|
1202
|
+
console.log(` ${DIM}Send it: vidfarm gigs send ${gigId} ${proof.id ?? "<proof-id>"} ${RESET}${DIM}— the price is snapshotted then, not now.${RESET}`);
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1062
1205
|
console.log(`${GREEN}✓${RESET} Submitted to ${BOLD}${gigId}${RESET} ${DIM}${proof.id ?? result.proof_id ?? ""}${RESET}`);
|
|
1063
1206
|
if (privateNote) {
|
|
1064
1207
|
// The response field is the ONLY confirmation the note was stored, and a
|
|
@@ -1069,14 +1212,151 @@ async function cmdSubmit(auth, values, positionals) {
|
|
|
1069
1212
|
}
|
|
1070
1213
|
else {
|
|
1071
1214
|
console.log(` ${RED}!${RESET} ${BOLD}private_note_locked was not returned — the note may not have saved.${RESET}`);
|
|
1072
|
-
console.log(` ${DIM}
|
|
1215
|
+
console.log(` ${DIM}A SUBMITTED proof's note cannot be patched. Check it now: vidfarm gigs proof ${gigId} ${proof.id ?? "<proof-id>"}${RESET}`);
|
|
1216
|
+
console.log(` ${DIM}If it is missing, withdraw and re-send: vidfarm gigs withdraw ${gigId} ${proof.id ?? "<proof-id>"}${RESET}`);
|
|
1073
1217
|
}
|
|
1074
1218
|
}
|
|
1075
1219
|
else {
|
|
1076
1220
|
console.log(` ${DIM}No private note sent. If that url is your CLEAN master, the buyer now has it before paying — see --clean-master.${RESET}`);
|
|
1077
1221
|
}
|
|
1078
1222
|
console.log(` ${DIM}The buyer swipes this in a deck — a link that needs a login is a proof they cannot watch.${RESET}`);
|
|
1079
|
-
console.log(` ${DIM}
|
|
1223
|
+
console.log(` ${DIM}Wrong cut? vidfarm gigs withdraw ${gigId} ${proof.id ?? "<proof-id>"} takes it back — but only until the buyer rules on it.${RESET}`);
|
|
1224
|
+
console.log(` ${DIM}Agents: only submit a cut the gigworker approved, unless they asked for auto-submit. The undo is a repair, not a licence.${RESET}`);
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
// ── drafts and the undo ──────────────────────────────────────────────────────
|
|
1228
|
+
//
|
|
1229
|
+
// A submitted proof is NOT one-way. Dollar Platoon models delivery in two
|
|
1230
|
+
// halves, and vidfarm used to expose only the second one:
|
|
1231
|
+
//
|
|
1232
|
+
// draft ──send──▶ pending ──buyer rules──▶ approved / rejected
|
|
1233
|
+
// ▲ │
|
|
1234
|
+
// └────withdraw──────┘ (pending only, and never once a rollup carries it)
|
|
1235
|
+
//
|
|
1236
|
+
// The line the worker actually needs is where the undo STOPS: the buyer's
|
|
1237
|
+
// verdict is theirs, so a rejection cannot be withdrawn — that would erase it
|
|
1238
|
+
// from the reputation record it belongs to. Everything before that is revisable.
|
|
1239
|
+
/** Shared arg shape for the four proof-level draft commands. */
|
|
1240
|
+
function draftTarget(positionals, verb) {
|
|
1241
|
+
const [gigRef, proofId] = positionals;
|
|
1242
|
+
if (!gigRef || !proofId) {
|
|
1243
|
+
throw new Error(`gigs ${verb} needs a gig id and a proof id: vidfarm gigs ${verb} GIG_01H… PRF_01H…`);
|
|
1244
|
+
}
|
|
1245
|
+
return { gigId: parseGigRef(gigRef).gigId, proofId };
|
|
1246
|
+
}
|
|
1247
|
+
/**
|
|
1248
|
+
* `gigs withdraw` — the undo. The web app labels this button "Undo" so it does
|
|
1249
|
+
* not read as a withdrawal of money, and this command is the same endpoint.
|
|
1250
|
+
*/
|
|
1251
|
+
async function cmdWithdraw(auth, values, positionals) {
|
|
1252
|
+
const { gigId, proofId } = draftTarget(positionals, "withdraw");
|
|
1253
|
+
const body = await dp(auth, `/gigs/${encodeURIComponent(gigId)}/proofs/${encodeURIComponent(proofId)}/withdraw`, { method: "POST" });
|
|
1254
|
+
out(Boolean(values.json), body, () => {
|
|
1255
|
+
if (body.already_draft) {
|
|
1256
|
+
console.log(`${GREEN}✓${RESET} ${BOLD}${proofId}${RESET} was already a draft — nothing was sent, so there was nothing to take back.`);
|
|
1257
|
+
}
|
|
1258
|
+
else {
|
|
1259
|
+
console.log(`${GREEN}✓${RESET} Withdrew ${BOLD}${proofId}${RESET} back to ${BOLD}draft${RESET}.`);
|
|
1260
|
+
console.log(` ${DIM}The review clock stopped and the buyer can no longer see it.${RESET}`);
|
|
1261
|
+
console.log(` ${DIM}Re-sending restarts that clock from zero — the buyer gets a full window on the new cut.${RESET}`);
|
|
1262
|
+
}
|
|
1263
|
+
console.log(` ${DIM}Fix it: vidfarm gigs edit-draft ${gigId} ${proofId} --proof <url>${RESET}`);
|
|
1264
|
+
console.log(` ${DIM}Send it: vidfarm gigs send ${gigId} ${proofId}${RESET}`);
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
/** `gigs edit-draft` — change an unsent draft. Submitted proofs are frozen. */
|
|
1268
|
+
async function cmdEditDraft(auth, values, positionals) {
|
|
1269
|
+
const { gigId, proofId } = draftTarget(positionals, "edit-draft");
|
|
1270
|
+
const proofs = [].concat(values.proof ?? []);
|
|
1271
|
+
const body = {};
|
|
1272
|
+
if (proofs.length)
|
|
1273
|
+
body.proofs = proofs;
|
|
1274
|
+
// An empty string is the documented way to CLEAR either field, so the test is
|
|
1275
|
+
// "was the flag present", never "is the value truthy".
|
|
1276
|
+
if (values["private-note"] !== undefined)
|
|
1277
|
+
body.private_note = String(values["private-note"]) || null;
|
|
1278
|
+
if (values.ask !== undefined)
|
|
1279
|
+
body.asking_price = String(values.ask) === "" ? null : Number(values.ask);
|
|
1280
|
+
if (!Object.keys(body).length) {
|
|
1281
|
+
throw new Error("gigs edit-draft needs something to change: --proof <url>, --private-note \"<text>\", or --ask <usd>. Pass an empty string to clear a note or an ask.");
|
|
1282
|
+
}
|
|
1283
|
+
const result = await dp(auth, `/gigs/${encodeURIComponent(gigId)}/proofs/${encodeURIComponent(proofId)}/draft`, { method: "PATCH", body });
|
|
1284
|
+
out(Boolean(values.json), result, () => {
|
|
1285
|
+
console.log(`${GREEN}✓${RESET} Updated draft ${BOLD}${proofId}${RESET} ${DIM}${(result.updated ?? Object.keys(body)).join(", ")}${RESET}`);
|
|
1286
|
+
console.log(` ${DIM}The task it fulfils cannot be changed. To move it, discard this draft and start again.${RESET}`);
|
|
1287
|
+
console.log(` ${DIM}Send it: vidfarm gigs send ${gigId} ${proofId}${RESET}`);
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
/**
|
|
1291
|
+
* `gigs send` — put a draft in front of the buyer. This is the gate the draft
|
|
1292
|
+
* itself is not: it runs the same pre-send check as `gigs submit`, because this
|
|
1293
|
+
* is the call that starts the review clock and fires the buyer's webhook.
|
|
1294
|
+
*/
|
|
1295
|
+
async function cmdSendDraft(auth, values, positionals) {
|
|
1296
|
+
const { gigId, proofId } = draftTarget(positionals, "send");
|
|
1297
|
+
if (!values["skip-verify"]) {
|
|
1298
|
+
const current = await dp(auth, `/gigs/${encodeURIComponent(gigId)}/proofs/${encodeURIComponent(proofId)}`);
|
|
1299
|
+
const proof = current.proof ?? current;
|
|
1300
|
+
const links = (Array.isArray(proof.proofs) ? proof.proofs : []).filter((p) => typeof p === "string" && /^https?:\/\//i.test(String(p)));
|
|
1301
|
+
if (links.length) {
|
|
1302
|
+
const check = await verifyProof({ proofs: links, cleanInProof: Boolean(values["clean-in-proof"]) });
|
|
1303
|
+
if (!check.ok) {
|
|
1304
|
+
if (values.json)
|
|
1305
|
+
console.log(JSON.stringify({ ok: false, sent: false, verify: check }, null, 2));
|
|
1306
|
+
else
|
|
1307
|
+
printVerify(check, false);
|
|
1308
|
+
throw new Error("Refusing to send: the draft's links failed the pre-send check (see above). Fix them with gigs edit-draft, or re-run with --skip-verify.");
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
const body = await dp(auth, `/gigs/${encodeURIComponent(gigId)}/proofs/${encodeURIComponent(proofId)}/submit`, { method: "POST" });
|
|
1313
|
+
out(Boolean(values.json), body, () => {
|
|
1314
|
+
console.log(`${GREEN}✓${RESET} Sent ${BOLD}${proofId}${RESET} to ${BOLD}${gigId}${RESET}${body.resubmitted ? ` ${DIM}(re-sent)${RESET}` : ""}`);
|
|
1315
|
+
if (typeof body.locked_price === "number") {
|
|
1316
|
+
// Snapshotted HERE, not when the draft was saved — the buyer may have
|
|
1317
|
+
// repriced the task while the draft sat, so this is the number that pays.
|
|
1318
|
+
console.log(` ${DIM}Locked price: $${body.locked_price.toFixed(2)} — snapshotted now, not when you saved the draft.${RESET}`);
|
|
1319
|
+
}
|
|
1320
|
+
else if (body.locked_price === null) {
|
|
1321
|
+
console.log(` ${DIM}The task is TBD, so the amount is set at approval. That is not $0.${RESET}`);
|
|
1322
|
+
}
|
|
1323
|
+
console.log(` ${DIM}Changed your mind? vidfarm gigs withdraw ${gigId} ${proofId} — until the buyer rules on it.${RESET}`);
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
/** `gigs discard` — delete a draft and free its task_identifier. */
|
|
1327
|
+
async function cmdDiscard(auth, values, positionals) {
|
|
1328
|
+
const { gigId, proofId } = draftTarget(positionals, "discard");
|
|
1329
|
+
const body = await dp(auth, `/gigs/${encodeURIComponent(gigId)}/proofs/${encodeURIComponent(proofId)}`, { method: "DELETE" });
|
|
1330
|
+
out(Boolean(values.json), body, () => {
|
|
1331
|
+
console.log(`${GREEN}✓${RESET} Deleted draft ${BOLD}${proofId}${RESET}.`);
|
|
1332
|
+
// The duplicate guard counts drafts, so an abandoned one blocks its own task
|
|
1333
|
+
// for good. Deleting it is what unblocks a fresh proof against that task.
|
|
1334
|
+
console.log(` ${DIM}Its task is free again — you can start a new proof against it.${RESET}`);
|
|
1335
|
+
console.log(` ${DIM}Your claim on the task is untouched. Walk away and task_timeout recycles it.${RESET}`);
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1338
|
+
/** `gigs drafts <gig-id>` — the unsent work the buyer cannot see. */
|
|
1339
|
+
async function cmdDrafts(auth, values, positionals) {
|
|
1340
|
+
const gigRef = positionals[0];
|
|
1341
|
+
if (!gigRef)
|
|
1342
|
+
throw new Error("gigs drafts needs a gig id: vidfarm gigs drafts GIG_01H…");
|
|
1343
|
+
const { gigId } = parseGigRef(gigRef);
|
|
1344
|
+
const body = await dp(auth, `/gigs/${encodeURIComponent(gigId)}/proofs`, { query: { status: "draft" } });
|
|
1345
|
+
const rows = body.proofs ?? body.items ?? body.data ?? [];
|
|
1346
|
+
out(Boolean(values.json), body, () => {
|
|
1347
|
+
if (!rows.length) {
|
|
1348
|
+
console.log(`${DIM}No drafts on this machine. Save one: vidfarm gigs draft ${gigId} --task <task-id> --proof <url>${RESET}`);
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
for (const row of rows) {
|
|
1352
|
+
console.log(` ${BOLD}${row.id}${RESET} ${DIM}draft${row.task_identifier ? " · " + row.task_identifier : ""}${RESET}`);
|
|
1353
|
+
const link = proofLink(row);
|
|
1354
|
+
if (link)
|
|
1355
|
+
console.log(` ${DIM}${short(link, 100)}${RESET}`);
|
|
1356
|
+
}
|
|
1357
|
+
console.log("");
|
|
1358
|
+
console.log(`${DIM}Each one holds its task against your open-task cap. Send it, or discard it.${RESET}`);
|
|
1359
|
+
console.log(`${DIM} vidfarm gigs send ${gigId} ${rows[0]?.id ?? "<proof-id>"} · vidfarm gigs discard ${gigId} ${rows[0]?.id ?? "<proof-id>"}${RESET}`);
|
|
1080
1360
|
});
|
|
1081
1361
|
}
|
|
1082
1362
|
/**
|
|
@@ -1160,6 +1440,12 @@ function options() {
|
|
|
1160
1440
|
note: { type: "string" },
|
|
1161
1441
|
"private-note": { type: "string" },
|
|
1162
1442
|
"clean-master": { type: "string" },
|
|
1443
|
+
"clean-in-proof": { type: "boolean" },
|
|
1444
|
+
draft: { type: "boolean" },
|
|
1445
|
+
ask: { type: "string" },
|
|
1446
|
+
"skip-verify": { type: "boolean" },
|
|
1447
|
+
"no-network": { type: "boolean" },
|
|
1448
|
+
strict: { type: "boolean" },
|
|
1163
1449
|
prefix: { type: "string" }
|
|
1164
1450
|
};
|
|
1165
1451
|
}
|
|
@@ -1172,6 +1458,11 @@ export async function runGigsCommand(argv) {
|
|
|
1172
1458
|
const parsed = parseArgs({ args: argv.slice(1), allowPositionals: true, options: options() });
|
|
1173
1459
|
const values = parsed.values;
|
|
1174
1460
|
const rest = parsed.positionals;
|
|
1461
|
+
// The pre-submit check talks to nobody's API — it fetches the worker's own
|
|
1462
|
+
// links the way a stranger would. Dispatch it BEFORE the key is resolved, so a
|
|
1463
|
+
// share-link worker with no account can still check a proof before sending it.
|
|
1464
|
+
if (["verify-proof", "check-proof", "preflight", "verify"].includes(sub))
|
|
1465
|
+
return cmdVerifyProof(values);
|
|
1175
1466
|
const auth = await resolveAuth(values);
|
|
1176
1467
|
switch (sub) {
|
|
1177
1468
|
case "machines":
|
|
@@ -1200,6 +1491,18 @@ export async function runGigsCommand(argv) {
|
|
|
1200
1491
|
case "claim":
|
|
1201
1492
|
case "poll": return cmdClaim(auth, values, rest);
|
|
1202
1493
|
case "submit": return cmdSubmit(auth, values, rest);
|
|
1494
|
+
// `draft` is `submit --draft`, so the flag is forced rather than duplicated.
|
|
1495
|
+
case "draft": return cmdSubmit(auth, { ...values, draft: true }, rest);
|
|
1496
|
+
case "drafts": return cmdDrafts(auth, values, rest);
|
|
1497
|
+
case "edit-draft":
|
|
1498
|
+
case "revise": return cmdEditDraft(auth, values, rest);
|
|
1499
|
+
case "send":
|
|
1500
|
+
case "send-draft": return cmdSendDraft(auth, values, rest);
|
|
1501
|
+
case "withdraw":
|
|
1502
|
+
case "undo":
|
|
1503
|
+
case "unsend": return cmdWithdraw(auth, values, rest);
|
|
1504
|
+
case "discard":
|
|
1505
|
+
case "delete-draft": return cmdDiscard(auth, values, rest);
|
|
1203
1506
|
case "proof": return cmdProof(auth, values, rest);
|
|
1204
1507
|
case "upload": return cmdUpload(auth, values, rest);
|
|
1205
1508
|
case "earnings":
|