@officexapp/vidfarm-devcli 0.21.43 → 0.21.46
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/editor-capabilities/SKILL.md +4 -0
- package/.agents/skills/vidfarm/SKILL.md +95 -17
- package/.agents/skills/vidfarm/harnesses/explainer.HARNESS.md +1 -1
- package/.agents/skills/vidfarm/harnesses/product-demo.HARNESS.md +2 -0
- package/.agents/skills/vidfarm/harnesses/short-form.HARNESS.md +1 -0
- package/.agents/skills/vidfarm/recipes/local-edit-render-approve.md +1 -1
- package/.agents/skills/vidfarm/recipes/onboard-a-new-director.md +1 -1
- package/.agents/skills/vidfarm/references/agent-included-imagegen.md +75 -0
- package/.agents/skills/vidfarm/references/assets-and-sourcing.md +152 -2
- package/.agents/skills/vidfarm/references/automation-and-local-dev.md +22 -9
- package/.agents/skills/vidfarm/references/browser-harness.md +93 -0
- package/.agents/skills/vidfarm/references/content-ideas.md +232 -10
- package/.agents/skills/vidfarm/references/core-workflows.md +11 -1
- package/.agents/skills/vidfarm/references/editor-workflows.md +39 -0
- package/.agents/skills/vidfarm/references/onboarding.md +1 -1
- package/.agents/skills/vidfarm/references/primitives.md +51 -0
- package/.agents/skills/vidfarm-media/SKILL.md +2 -0
- package/SKILL.director.md +775 -42
- package/SKILL.md +157 -115
- package/crowdsourcing.md +417 -3
- package/dist/src/cli.js +750 -34
- package/dist/src/devcli/agent-imagegen.js +181 -0
- package/dist/src/devcli/browser-harness.js +384 -0
- package/dist/src/devcli/clip-store.js +41 -3
- package/dist/src/devcli/consult.js +14 -0
- package/dist/src/devcli/cost-mode.js +23 -3
- package/dist/src/devcli/doctor.js +52 -3
- package/dist/src/devcli/hyperframes-cli.js +11 -1
- package/dist/src/devcli/local-render.js +4 -7
- package/dist/src/devcli/marketplace-gigs.js +623 -0
- package/dist/src/devcli/qa-check.js +89 -1
- package/dist/src/devcli/shared-folder.js +387 -0
- package/dist/src/devcli/skill-docs.js +61 -7
- package/dist/src/devcli/stills.js +4 -8
- package/dist/src/lib/ffprobe-path.js +64 -0
- package/dist/src/lib/render-media-prep.js +2 -11
- package/dist/src/services/clip-curation/ffmpeg.js +4 -15
- package/dist/src/services/clip-curation/index.js +1 -1
- package/dist/src/services/clip-curation/local-agent.js +6 -2
- package/dist/src/services/clip-curation/media-select.js +146 -3
- package/experimental/google-news-to-video.md +235 -0
- package/package.json +8 -150
- package/public/assets/file-directory-app.js +35 -35
- package/public/assets/homepage-client-app.js +15 -15
- package/public/serve-shells/library-files.html +5 -1
- package/public/serve-shells/library-raws.html +10 -1
- package/public/serve-shells/tools-clipper.html +5 -1
- package/public/serve-shells/tools-image.html +5 -1
- package/public/serve-shells/tools-video.html +5 -1
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
// `vidfarm gigs` — the vending machines, from BOTH sides, in the terminal.
|
|
2
|
+
//
|
|
3
|
+
// CLIENT (paid vidfarm plan): list your two standing machines, post a task into
|
|
4
|
+
// Custom Requests, read the queue, read the proofs that came back, approve or
|
|
5
|
+
// reject them, and ring the bell so every listening agent sees the new work.
|
|
6
|
+
//
|
|
7
|
+
// GIGWORKER (free, no vidfarm account at all): bring your own dollarplatoon.com
|
|
8
|
+
// api key, read the feed to find clients, join a machine, claim a task, and
|
|
9
|
+
// submit your proof.
|
|
10
|
+
//
|
|
11
|
+
// Everything here talks to Dollar Platoon's own API. The key is resolved in
|
|
12
|
+
// this order:
|
|
13
|
+
// 1. --dp-key <key>
|
|
14
|
+
// 2. DOLLARPLATOON_API_KEY (dollarplatoon.com)
|
|
15
|
+
// 3. GIGDESK_API_KEY (gigdesk.cc — same key family, Bearer auth)
|
|
16
|
+
// 4. the vidfarm account's own key, fetched from
|
|
17
|
+
// GET /api/v1/user/me/marketplace with VIDFARM_API_KEY (paid plans only)
|
|
18
|
+
// So a client who already ran `vidfarm login` needs no extra setup, and a
|
|
19
|
+
// gigworker who has never heard of vidfarm needs only their own key.
|
|
20
|
+
import { parseArgs } from "node:util";
|
|
21
|
+
import { readFileSync } from "node:fs";
|
|
22
|
+
import { readStoredAuth } from "./auth-store.js";
|
|
23
|
+
const BOLD = "\x1b[1m";
|
|
24
|
+
const DIM = "\x1b[2m";
|
|
25
|
+
const GREEN = "\x1b[32m";
|
|
26
|
+
const YELLOW = "\x1b[33m";
|
|
27
|
+
const RESET = "\x1b[0m";
|
|
28
|
+
/** The shared vidfarm feed: the registry + notification stream every client's machines are listed in. */
|
|
29
|
+
const VIDFARM_FEED_ID = "FEED_01M0BCTVTKSDFZQVEY1RXA4NJK";
|
|
30
|
+
/** Standing reusable invite for that feed — it is public by design. */
|
|
31
|
+
const VIDFARM_FEED_INVITE = "MDFNMEQyRUVYMVdDNzQ2Qzha";
|
|
32
|
+
const DEFAULT_DP_API = "https://dollarplatoon.com/api";
|
|
33
|
+
const GIGDESK_API = "https://gigdesk.cc/api";
|
|
34
|
+
export const GIGS_HELP = `vidfarm gigs — your vending machines, both sides of them
|
|
35
|
+
|
|
36
|
+
CLIENT (paid vidfarm plan — the key is read from your vidfarm account)
|
|
37
|
+
gigs machines Your two standing machines + gig ids + invite links
|
|
38
|
+
gigs tasks [machine] What is waiting in the Custom Requests queue
|
|
39
|
+
gigs add-task --task "<brief>" Post a task into Custom Requests
|
|
40
|
+
--price <usd> Price per delivered video (default 0.50; "tbd" allowed)
|
|
41
|
+
--tags a,b --priority <n> Routing hints (lower priority polls sooner)
|
|
42
|
+
--angle/--hook/--url/--format The usual brief fields
|
|
43
|
+
--assets-link <url> A READ share link to the footage they may use
|
|
44
|
+
--upload-link <url> An UPLOAD share link they drop deliverables into
|
|
45
|
+
--subfolder <name> The subfolder you want their work in
|
|
46
|
+
--proof <text> Proof requirement (repeatable)
|
|
47
|
+
--body <file.json> Send this JSON as the whole task instead
|
|
48
|
+
gigs proofs [machine] Submitted videos, newest first
|
|
49
|
+
--status <pending|approved|rejected> --limit <n>
|
|
50
|
+
gigs approve <proof-id> [machine] [--feedback "<text>"]
|
|
51
|
+
gigs reject <proof-id> [machine] [--tag not_selected] [--feedback "<text>"]
|
|
52
|
+
not_selected = a pass; it costs the worker NO reputation
|
|
53
|
+
gigs ring-bell --title "<t>" [--subtext "<s>"] [--machine inbound_offers]
|
|
54
|
+
Broadcast to every agent on the vidfarm feed
|
|
55
|
+
|
|
56
|
+
GIGWORKER (free — bring your own key: export DOLLARPLATOON_API_KEY=…)
|
|
57
|
+
gigs work Work waiting in every machine you joined → GET /work/available
|
|
58
|
+
gigs feed Who is asking for videos right now (notifications)
|
|
59
|
+
--registry Every machine listed in the feed, joinable
|
|
60
|
+
--limit <n>
|
|
61
|
+
gigs join-feed [--name "<you>"] Join the shared vidfarm feed (needed to read it)
|
|
62
|
+
gigs join <gig-id> [--invite <token>] [--name "<you>"] [--wallet 0x…]
|
|
63
|
+
gigs claim <gig-id> [--count 2] Claim task(s) off a FIFO queue machine
|
|
64
|
+
gigs submit <gig-id> --task <task-id> --proof <url> [--proof <url>…]
|
|
65
|
+
--mailbox <id> --tags a,b --note "<text>"
|
|
66
|
+
gigs mine Your mailboxes across every gig
|
|
67
|
+
|
|
68
|
+
Common: --dp-key <key> · --json · --host <vidfarm url> (for the key lookup)
|
|
69
|
+
|
|
70
|
+
e.g. vidfarm gigs add-task --task "60s explainer for acme.com" --price 0.50 \\
|
|
71
|
+
--upload-link "https://vidfarm.cc/directory/preview/dsh_…/files/acme"
|
|
72
|
+
vidfarm gigs proofs --status pending
|
|
73
|
+
vidfarm gigs approve PRF_01H… --feedback "Great hook, keeping it."
|
|
74
|
+
DOLLARPLATOON_API_KEY=… vidfarm gigs work`;
|
|
75
|
+
async function resolveAuth(values) {
|
|
76
|
+
const explicit = values["dp-key"]?.trim();
|
|
77
|
+
if (explicit)
|
|
78
|
+
return { key: explicit, api: DEFAULT_DP_API, source: "--dp-key" };
|
|
79
|
+
const dp = (process.env.DOLLARPLATOON_API_KEY ?? "").trim();
|
|
80
|
+
if (dp)
|
|
81
|
+
return { key: dp, api: DEFAULT_DP_API, source: "DOLLARPLATOON_API_KEY" };
|
|
82
|
+
const gd = (process.env.GIGDESK_API_KEY ?? "").trim();
|
|
83
|
+
if (gd)
|
|
84
|
+
return { key: gd, api: GIGDESK_API, source: "GIGDESK_API_KEY" };
|
|
85
|
+
const account = await loadVidfarmMarketplace(values);
|
|
86
|
+
if (account?.dollarplatoon_api_key) {
|
|
87
|
+
return {
|
|
88
|
+
key: account.dollarplatoon_api_key,
|
|
89
|
+
api: account.dollarplatoon_api_url || DEFAULT_DP_API,
|
|
90
|
+
source: "your vidfarm account"
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
throw new Error([
|
|
94
|
+
"No Dollar Platoon key found.",
|
|
95
|
+
" Client: run `vidfarm login` (paid plan) — the key comes from your account.",
|
|
96
|
+
" Gigworker: export DOLLARPLATOON_API_KEY=… from https://dollarplatoon.com/gigworker/settings",
|
|
97
|
+
" Or pass it directly: --dp-key <key>"
|
|
98
|
+
].join("\n"));
|
|
99
|
+
}
|
|
100
|
+
let marketplaceCache;
|
|
101
|
+
/** The paid client's own key + machines, straight from their vidfarm account. */
|
|
102
|
+
async function loadVidfarmMarketplace(values) {
|
|
103
|
+
if (marketplaceCache !== undefined)
|
|
104
|
+
return marketplaceCache;
|
|
105
|
+
// `vidfarm login` already stored a key — use it, so a logged-in client needs
|
|
106
|
+
// no env var at all. Explicit flags and env still win.
|
|
107
|
+
const stored = readStoredAuth();
|
|
108
|
+
const apiKey = (values["api-key"] ?? process.env.VIDFARM_API_KEY ?? stored?.apiKey ?? "").trim();
|
|
109
|
+
const host = ((values.host ?? process.env.VIDFARM_HOST ?? stored?.host ?? "https://vidfarm.cc").trim()).replace(/\/+$/, "");
|
|
110
|
+
if (!apiKey) {
|
|
111
|
+
marketplaceCache = null;
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
const response = await fetch(`${host}/api/v1/user/me/marketplace`, {
|
|
116
|
+
headers: { authorization: `Bearer ${apiKey}`, accept: "application/json" }
|
|
117
|
+
});
|
|
118
|
+
const body = await response.json().catch(() => ({}));
|
|
119
|
+
if (!response.ok) {
|
|
120
|
+
// 402/409 are meaningful: not paid, or no key connected yet. Surface them
|
|
121
|
+
// rather than falling through to a bare "no key found".
|
|
122
|
+
if (body?.error)
|
|
123
|
+
throw new Error(body.error);
|
|
124
|
+
marketplaceCache = null;
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
marketplaceCache = body;
|
|
128
|
+
return body;
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
if (error instanceof Error && error.message && !/fetch failed/i.test(error.message))
|
|
132
|
+
throw error;
|
|
133
|
+
marketplaceCache = null;
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// ── transport ────────────────────────────────────────────────────────────────
|
|
138
|
+
async function dp(auth, path, init = {}) {
|
|
139
|
+
const url = new URL(auth.api + path);
|
|
140
|
+
for (const [key, value] of Object.entries(init.query ?? {})) {
|
|
141
|
+
if (value != null && value !== "")
|
|
142
|
+
url.searchParams.set(key, value);
|
|
143
|
+
}
|
|
144
|
+
const response = await fetch(url, {
|
|
145
|
+
method: init.method ?? "GET",
|
|
146
|
+
headers: {
|
|
147
|
+
authorization: `Bearer ${auth.key}`,
|
|
148
|
+
accept: "application/json",
|
|
149
|
+
...(init.body === undefined ? {} : { "content-type": "application/json" })
|
|
150
|
+
},
|
|
151
|
+
body: init.body === undefined ? undefined : JSON.stringify(init.body)
|
|
152
|
+
});
|
|
153
|
+
const text = await response.text();
|
|
154
|
+
let json = {};
|
|
155
|
+
try {
|
|
156
|
+
json = text ? JSON.parse(text) : {};
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
json = { error: text.slice(0, 300) };
|
|
160
|
+
}
|
|
161
|
+
if (!response.ok) {
|
|
162
|
+
const message = typeof json?.error === "string" ? json.error : `HTTP ${response.status}`;
|
|
163
|
+
throw new Error(`dollarplatoon ${path} — ${message}`);
|
|
164
|
+
}
|
|
165
|
+
return json;
|
|
166
|
+
}
|
|
167
|
+
// ── machine resolution ───────────────────────────────────────────────────────
|
|
168
|
+
const MACHINE_ALIASES = {
|
|
169
|
+
custom: "custom_requests",
|
|
170
|
+
custom_requests: "custom_requests",
|
|
171
|
+
requests: "custom_requests",
|
|
172
|
+
queue: "custom_requests",
|
|
173
|
+
inbound: "inbound_offers",
|
|
174
|
+
inbound_offers: "inbound_offers",
|
|
175
|
+
offers: "inbound_offers",
|
|
176
|
+
pitches: "inbound_offers"
|
|
177
|
+
};
|
|
178
|
+
/** Both machines, found by their stable `vidfarm_vm_<slug>` tag. */
|
|
179
|
+
async function listMachines(auth) {
|
|
180
|
+
const body = await dp(auth, "/gigs/mine", { query: { tag: "vidfarm_vm_" } });
|
|
181
|
+
const gigs = body.gigs ?? body.data ?? [];
|
|
182
|
+
return gigs.map((gig) => {
|
|
183
|
+
const tag = (gig.tags ?? []).find((t) => typeof t === "string" && t.startsWith("vidfarm_vm_")) ?? "";
|
|
184
|
+
return {
|
|
185
|
+
slug: tag.slice("vidfarm_vm_".length) || "other",
|
|
186
|
+
title: String(gig.title ?? gig.id ?? ""),
|
|
187
|
+
gigId: String(gig.id ?? ""),
|
|
188
|
+
distribution: gig.distribution ? String(gig.distribution) : undefined,
|
|
189
|
+
inviteUrl: typeof gig.invite_url === "string" ? gig.invite_url : null
|
|
190
|
+
};
|
|
191
|
+
}).filter((m) => m.gigId);
|
|
192
|
+
}
|
|
193
|
+
/** A gig id, a machine slug, or (default) the machine this command is about. */
|
|
194
|
+
async function resolveGigId(auth, raw, fallbackSlug) {
|
|
195
|
+
const value = (raw ?? "").trim();
|
|
196
|
+
if (/^GIG_/i.test(value))
|
|
197
|
+
return value;
|
|
198
|
+
const wanted = MACHINE_ALIASES[value.toLowerCase()] ?? fallbackSlug;
|
|
199
|
+
const machines = await listMachines(auth);
|
|
200
|
+
const match = machines.find((m) => m.slug === wanted);
|
|
201
|
+
if (!match) {
|
|
202
|
+
throw new Error(`No "${wanted}" vending machine on this account. Run: vidfarm gigs machines`);
|
|
203
|
+
}
|
|
204
|
+
return match.gigId;
|
|
205
|
+
}
|
|
206
|
+
// ── formatting ───────────────────────────────────────────────────────────────
|
|
207
|
+
function out(json, value, human) {
|
|
208
|
+
if (json) {
|
|
209
|
+
console.log(JSON.stringify(value, null, 2));
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
human();
|
|
213
|
+
}
|
|
214
|
+
function short(value, max = 90) {
|
|
215
|
+
const text = typeof value === "string" ? value : value == null ? "" : JSON.stringify(value);
|
|
216
|
+
const flat = text.replace(/\s+/g, " ").trim();
|
|
217
|
+
return flat.length > max ? flat.slice(0, max - 1) + "…" : flat;
|
|
218
|
+
}
|
|
219
|
+
/** Dig the playable link out of a proof — workers scatter it across fields. */
|
|
220
|
+
function proofLink(proof) {
|
|
221
|
+
const proofs = Array.isArray(proof.proofs) ? proof.proofs : [];
|
|
222
|
+
const direct = proofs.find((p) => typeof p === "string" && /^https?:\/\//.test(p));
|
|
223
|
+
if (direct)
|
|
224
|
+
return String(direct);
|
|
225
|
+
const note = typeof proof.feedback === "string" ? proof.feedback : "";
|
|
226
|
+
const found = /https?:\/\/\S+/.exec(note);
|
|
227
|
+
return found ? found[0] : "";
|
|
228
|
+
}
|
|
229
|
+
// ── client commands ──────────────────────────────────────────────────────────
|
|
230
|
+
async function cmdMachines(auth, values) {
|
|
231
|
+
const machines = await listMachines(auth);
|
|
232
|
+
out(Boolean(values.json), { machines }, () => {
|
|
233
|
+
if (!machines.length) {
|
|
234
|
+
console.log(`${DIM}No vending machines on this key yet. A paid vidfarm account gets two; open ${RESET}https://vidfarm.cc/marketplace/buyer${DIM} once to create them.${RESET}`);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
for (const machine of machines) {
|
|
238
|
+
console.log(` ${BOLD}${machine.title}${RESET} ${DIM}${machine.slug} · ${machine.gigId}${machine.distribution ? " · " + machine.distribution : ""}${RESET}`);
|
|
239
|
+
if (machine.inviteUrl)
|
|
240
|
+
console.log(` ${DIM}invite: ${machine.inviteUrl}${RESET}`);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
async function cmdTasks(auth, values, positional) {
|
|
245
|
+
const gigId = await resolveGigId(auth, positional, "custom_requests");
|
|
246
|
+
const body = await dp(auth, `/gigs/${encodeURIComponent(gigId)}/queue`, { query: { limit: values.limit } });
|
|
247
|
+
const tasks = body.tasks ?? body.queue ?? body.messages ?? [];
|
|
248
|
+
out(Boolean(values.json), body, () => {
|
|
249
|
+
if (!tasks.length) {
|
|
250
|
+
console.log(`${DIM}Queue is empty — post one: vidfarm gigs add-task --task "<brief>"${RESET}`);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
for (const task of tasks) {
|
|
254
|
+
const claimed = task.claimed_by || task.mailbox_id ? " · claimed" : "";
|
|
255
|
+
console.log(` ${BOLD}${short(task.subject ?? task.id, 60)}${RESET} ${DIM}[${task.id}]${claimed}${RESET}`);
|
|
256
|
+
if (task.payload)
|
|
257
|
+
console.log(` ${DIM}${short(task.payload, 110)}${RESET}`);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
async function cmdAddTask(auth, values) {
|
|
262
|
+
const gigId = await resolveGigId(auth, values.machine, "custom_requests");
|
|
263
|
+
// The publisher webhook (with its security token) lives on the gig record.
|
|
264
|
+
const gig = await dp(auth, `/gigs/${encodeURIComponent(gigId)}`);
|
|
265
|
+
const webhook = gig.gig?.webhook ?? gig.webhook ?? "";
|
|
266
|
+
if (!webhook)
|
|
267
|
+
throw new Error("That machine has no publisher webhook — open it on dollarplatoon.com and check the gig.");
|
|
268
|
+
let payload;
|
|
269
|
+
if (values.body) {
|
|
270
|
+
payload = JSON.parse(readFileSync(String(values.body), "utf8"));
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
const task = values.task?.trim();
|
|
274
|
+
if (!task)
|
|
275
|
+
throw new Error('gigs add-task needs --task "<brief>" (or --body <file.json>).');
|
|
276
|
+
const proofs = [].concat(values.proof ?? []);
|
|
277
|
+
payload = {
|
|
278
|
+
task,
|
|
279
|
+
...(values.angle ? { angle: String(values.angle) } : {}),
|
|
280
|
+
...(values.hook ? { hook: String(values.hook) } : {}),
|
|
281
|
+
...(values.url ? { url: String(values.url) } : {}),
|
|
282
|
+
...(values.format ? { format: String(values.format) } : {}),
|
|
283
|
+
// The file-handover layer: a read link to the footage, an upload link for
|
|
284
|
+
// the deliverable, and the subfolder the worker should create in it.
|
|
285
|
+
...(values["assets-link"] ? { assets_link: String(values["assets-link"]) } : {}),
|
|
286
|
+
...(values["upload-link"] ? { upload_link: String(values["upload-link"]) } : {}),
|
|
287
|
+
...(values.subfolder ? { upload_subfolder: String(values.subfolder) } : {}),
|
|
288
|
+
...(proofs.length ? { proof_requirements: proofs } : {})
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
const url = new URL(webhook);
|
|
292
|
+
const price = values.price === undefined ? "0.50" : String(values.price);
|
|
293
|
+
url.searchParams.set("price", price);
|
|
294
|
+
if (values.tags)
|
|
295
|
+
url.searchParams.set("tags", String(values.tags));
|
|
296
|
+
if (values.priority !== undefined)
|
|
297
|
+
url.searchParams.set("priority", String(values.priority));
|
|
298
|
+
if (values["assign-to"])
|
|
299
|
+
url.searchParams.set("assign_to", String(values["assign-to"]));
|
|
300
|
+
const response = await fetch(url, {
|
|
301
|
+
method: "POST",
|
|
302
|
+
headers: { "content-type": "application/json" },
|
|
303
|
+
body: JSON.stringify(payload)
|
|
304
|
+
});
|
|
305
|
+
const text = await response.text();
|
|
306
|
+
let body = {};
|
|
307
|
+
try {
|
|
308
|
+
body = text ? JSON.parse(text) : {};
|
|
309
|
+
}
|
|
310
|
+
catch {
|
|
311
|
+
body = { raw: text.slice(0, 300) };
|
|
312
|
+
}
|
|
313
|
+
if (!response.ok)
|
|
314
|
+
throw new Error(`add-task failed: ${body?.error ?? `HTTP ${response.status}`}`);
|
|
315
|
+
out(Boolean(values.json), body, () => {
|
|
316
|
+
const ids = (body.message_ids ?? []).join(", ");
|
|
317
|
+
console.log(`${GREEN}✓${RESET} Task posted to ${BOLD}${gigId}${RESET} at ${BOLD}$${price}${RESET}/video ${DIM}${ids}${RESET}`);
|
|
318
|
+
console.log(` ${DIM}Nobody knows yet — broadcast it: vidfarm gigs ring-bell --title "…" --machine custom_requests${RESET}`);
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
async function cmdProofs(auth, values, positional) {
|
|
322
|
+
const gigId = await resolveGigId(auth, positional, "inbound_offers");
|
|
323
|
+
const body = await dp(auth, `/gigs/${encodeURIComponent(gigId)}/proofs`, {
|
|
324
|
+
query: { status: values.status, limit: values.limit }
|
|
325
|
+
});
|
|
326
|
+
const proofs = body.proofs ?? body.data ?? [];
|
|
327
|
+
out(Boolean(values.json), body, () => {
|
|
328
|
+
if (!proofs.length) {
|
|
329
|
+
console.log(`${DIM}No proofs on this machine yet.${RESET}`);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
for (const proof of proofs) {
|
|
333
|
+
const price = typeof proof.locked_price === "number" ? ` · $${proof.locked_price.toFixed(2)}` : "";
|
|
334
|
+
console.log(` ${BOLD}${proof.id}${RESET} ${DIM}${proof.status ?? ""}${price}${proof.task_identifier ? " · " + proof.task_identifier : ""}${RESET}`);
|
|
335
|
+
const link = proofLink(proof);
|
|
336
|
+
if (link)
|
|
337
|
+
console.log(` ${link}`);
|
|
338
|
+
}
|
|
339
|
+
console.log("");
|
|
340
|
+
console.log(`${DIM}Keep one: vidfarm gigs approve <proof-id> · Pass: vidfarm gigs reject <proof-id> --tag not_selected${RESET}`);
|
|
341
|
+
console.log(`${YELLOW}Silence is approval${RESET}${DIM} — an unreviewed proof auto-approves after the gig's review timeout (default 48h) and you pay for it.${RESET}`);
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
async function cmdReview(auth, values, positionals, action) {
|
|
345
|
+
const proofId = positionals[0];
|
|
346
|
+
if (!proofId)
|
|
347
|
+
throw new Error(`gigs ${action} needs a proof id: vidfarm gigs ${action} PRF_01H…`);
|
|
348
|
+
const gigId = await resolveGigId(auth, positionals[1], "inbound_offers");
|
|
349
|
+
const body = { action };
|
|
350
|
+
if (values.feedback)
|
|
351
|
+
body.feedback = String(values.feedback);
|
|
352
|
+
if (action === "reject")
|
|
353
|
+
body.rejection_tag = values.tag ? String(values.tag) : "not_selected";
|
|
354
|
+
const result = await dp(auth, `/gigs/${encodeURIComponent(gigId)}/proofs/${encodeURIComponent(proofId)}`, { method: "PATCH", body });
|
|
355
|
+
out(Boolean(values.json), result, () => {
|
|
356
|
+
if (action === "approve") {
|
|
357
|
+
console.log(`${GREEN}✓${RESET} Approved ${BOLD}${proofId}${RESET} — it pays out on the next rollup.`);
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
console.log(`${GREEN}✓${RESET} Rejected ${BOLD}${proofId}${RESET} as ${BOLD}${body.rejection_tag}${RESET}.`);
|
|
361
|
+
if (body.rejection_tag === "not_selected") {
|
|
362
|
+
console.log(` ${DIM}not_selected costs the worker no reputation — the right tag for "good, not this time".${RESET}`);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
async function cmdRingBell(auth, values) {
|
|
368
|
+
const title = values.title?.trim();
|
|
369
|
+
if (!title)
|
|
370
|
+
throw new Error('gigs ring-bell needs --title "<what you want made>".');
|
|
371
|
+
const slug = MACHINE_ALIASES[String(values.machine ?? "inbound_offers").toLowerCase()] ?? "inbound_offers";
|
|
372
|
+
const machines = await listMachines(auth);
|
|
373
|
+
const machine = machines.find((m) => m.slug === slug);
|
|
374
|
+
if (!machine)
|
|
375
|
+
throw new Error(`No "${slug}" machine on this account. Run: vidfarm gigs machines`);
|
|
376
|
+
let destination = machine.inviteUrl ?? "";
|
|
377
|
+
if (!destination) {
|
|
378
|
+
// Registering the gig in the feed returns its reusable join link.
|
|
379
|
+
const registered = await dp(auth, `/feeds/${VIDFARM_FEED_ID}/registry`, {
|
|
380
|
+
method: "POST",
|
|
381
|
+
body: { gig_id: machine.gigId, tags: ["vidfarm", "video", slug] }
|
|
382
|
+
}).catch(() => ({}));
|
|
383
|
+
destination = registered.invite_url ?? "";
|
|
384
|
+
}
|
|
385
|
+
if (!destination)
|
|
386
|
+
throw new Error("That machine has no invite link yet — open it once on vidfarm.cc/marketplace/buyer.");
|
|
387
|
+
const result = await dp(auth, `/feeds/${VIDFARM_FEED_ID}/notifications`, {
|
|
388
|
+
method: "POST",
|
|
389
|
+
body: {
|
|
390
|
+
title,
|
|
391
|
+
subtext: String(values.subtext ?? ""),
|
|
392
|
+
destination_url: destination,
|
|
393
|
+
tags: ["vidfarm", "video", slug]
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
out(Boolean(values.json), result, () => {
|
|
397
|
+
console.log(`${GREEN}✓${RESET} Bell rung on ${BOLD}${machine.title}${RESET} — every agent on the vidfarm feed sees it.`);
|
|
398
|
+
console.log(` ${DIM}${destination}${RESET}`);
|
|
399
|
+
console.log(` ${DIM}Ring it when something CHANGED (new batch, new price, new format) — an empty bell teaches agents to ignore you.${RESET}`);
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
// ── gigworker commands ───────────────────────────────────────────────────────
|
|
403
|
+
async function cmdWork(auth, values) {
|
|
404
|
+
const body = await dp(auth, "/work/available", { query: { only_with_work: "true", limit: values.limit } });
|
|
405
|
+
const rows = body.mailboxes ?? body.work ?? body.data ?? [];
|
|
406
|
+
out(Boolean(values.json), body, () => {
|
|
407
|
+
if (!rows.length) {
|
|
408
|
+
console.log(`${DIM}Nothing waiting in the machines you joined. Find new clients: vidfarm gigs feed${RESET}`);
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
for (const row of rows) {
|
|
412
|
+
const count = row.available ?? row.pending_count ?? row.count ?? "";
|
|
413
|
+
console.log(` ${BOLD}${short(row.gig_title ?? row.gig_id, 60)}${RESET} ${DIM}${row.gig_id ?? ""}${count !== "" ? ` · ${count} waiting` : ""}${RESET}`);
|
|
414
|
+
}
|
|
415
|
+
console.log("");
|
|
416
|
+
console.log(`${DIM}Claim one: vidfarm gigs claim <gig-id> then vidfarm gigs submit <gig-id> --task <task-id> --proof <mp4 url>${RESET}`);
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
async function cmdFeed(auth, values) {
|
|
420
|
+
const registry = Boolean(values.registry);
|
|
421
|
+
const path = registry ? `/feeds/${VIDFARM_FEED_ID}/registry` : `/feeds/${VIDFARM_FEED_ID}/notifications`;
|
|
422
|
+
const body = await dp(auth, path, { query: { limit: values.limit ?? "50" } }).catch((error) => {
|
|
423
|
+
if (/40[13]/.test(error.message)) {
|
|
424
|
+
throw new Error("You are not on the vidfarm feed yet — join it first: vidfarm gigs join-feed");
|
|
425
|
+
}
|
|
426
|
+
throw error;
|
|
427
|
+
});
|
|
428
|
+
const rows = body.notifications ?? body.entries ?? body.registry ?? body.data ?? [];
|
|
429
|
+
out(Boolean(values.json), body, () => {
|
|
430
|
+
if (!rows.length) {
|
|
431
|
+
console.log(`${DIM}Nothing on the feed right now.${RESET}`);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
for (const row of rows) {
|
|
435
|
+
console.log(` ${BOLD}${short(row.title ?? row.gig_title ?? row.note, 70)}${RESET}`);
|
|
436
|
+
if (row.subtext)
|
|
437
|
+
console.log(` ${DIM}${short(row.subtext, 100)}${RESET}`);
|
|
438
|
+
const link = row.destination_url ?? row.invite_url ?? "";
|
|
439
|
+
if (link)
|
|
440
|
+
console.log(` ${link}`);
|
|
441
|
+
}
|
|
442
|
+
console.log("");
|
|
443
|
+
console.log(`${DIM}${registry ? "Join one with its invite link, then: vidfarm gigs work" : "Every entry links at a machine you can join right now."}${RESET}`);
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
async function cmdJoinFeed(auth, values) {
|
|
447
|
+
const result = await dp(auth, `/feeds/${VIDFARM_FEED_ID}/join`, {
|
|
448
|
+
method: "POST",
|
|
449
|
+
body: { invite: VIDFARM_FEED_INVITE, ...(values.name ? { display_name: String(values.name) } : {}) }
|
|
450
|
+
});
|
|
451
|
+
out(Boolean(values.json), result, () => {
|
|
452
|
+
console.log(`${GREEN}✓${RESET} On the vidfarm feed. Read it: ${BOLD}vidfarm gigs feed${RESET}`);
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
async function cmdJoin(auth, values, positionals) {
|
|
456
|
+
const raw = positionals[0];
|
|
457
|
+
if (!raw)
|
|
458
|
+
throw new Error("gigs join needs a gig id or an invite URL: vidfarm gigs join GIG_01H… --invite <token>");
|
|
459
|
+
// An invite URL carries both halves: .../gig/<gigId>/join?invite=<token>
|
|
460
|
+
let gigId = raw;
|
|
461
|
+
let invite = values.invite ? String(values.invite) : "";
|
|
462
|
+
if (/^https?:\/\//i.test(raw)) {
|
|
463
|
+
const url = new URL(raw);
|
|
464
|
+
const found = url.pathname.split("/").find((s) => /^GIG_/i.test(s));
|
|
465
|
+
if (found)
|
|
466
|
+
gigId = found;
|
|
467
|
+
invite = invite || url.searchParams.get("invite") || "";
|
|
468
|
+
}
|
|
469
|
+
const result = await dp(auth, `/gigs/${encodeURIComponent(gigId)}/mailboxes`, {
|
|
470
|
+
method: "POST",
|
|
471
|
+
body: {
|
|
472
|
+
...(values.name ? { name: String(values.name) } : {}),
|
|
473
|
+
...(invite ? { invite } : {}),
|
|
474
|
+
...(values.wallet ? { wallet_address: String(values.wallet) } : {})
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
out(Boolean(values.json), result, () => {
|
|
478
|
+
const mailbox = result.mailbox?.id ?? result.id ?? "";
|
|
479
|
+
console.log(`${GREEN}✓${RESET} Joined ${BOLD}${gigId}${RESET}${mailbox ? ` ${DIM}mailbox ${mailbox}${RESET}` : ""}`);
|
|
480
|
+
console.log(` ${DIM}Now: vidfarm gigs work · vidfarm gigs claim ${gigId}${RESET}`);
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
async function cmdClaim(auth, values, positionals) {
|
|
484
|
+
const gigId = positionals[0];
|
|
485
|
+
if (!gigId)
|
|
486
|
+
throw new Error("gigs claim needs a gig id: vidfarm gigs claim GIG_01H… [--count 2]");
|
|
487
|
+
const count = values.count === undefined ? 2 : Math.trunc(Number(values.count));
|
|
488
|
+
const result = await dp(auth, `/gigs/${encodeURIComponent(gigId)}/queue/poll`, { method: "POST", body: { count } });
|
|
489
|
+
const tasks = result.tasks ?? [];
|
|
490
|
+
out(Boolean(values.json), result, () => {
|
|
491
|
+
if (!tasks.length) {
|
|
492
|
+
console.log(`${DIM}Nothing claimable in that queue right now.${RESET}`);
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
for (const task of tasks) {
|
|
496
|
+
console.log(` ${BOLD}${task.id}${RESET} ${short(task.subject, 70)}`);
|
|
497
|
+
if (task.payload)
|
|
498
|
+
console.log(` ${short(task.payload, 400)}`);
|
|
499
|
+
}
|
|
500
|
+
console.log("");
|
|
501
|
+
console.log(`${DIM}Submit with the task's OWN id — that is what claims it to you:${RESET}`);
|
|
502
|
+
console.log(`${DIM} vidfarm gigs submit ${gigId} --task ${tasks[0]?.id ?? "<task-id>"} --proof <public mp4 url>${RESET}`);
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
async function cmdSubmit(auth, values, positionals) {
|
|
506
|
+
const gigId = positionals[0];
|
|
507
|
+
if (!gigId)
|
|
508
|
+
throw new Error("gigs submit needs a gig id: vidfarm gigs submit GIG_01H… --task <id> --proof <url>");
|
|
509
|
+
const proofs = [].concat(values.proof ?? []);
|
|
510
|
+
if (!proofs.length)
|
|
511
|
+
throw new Error("gigs submit needs at least one --proof <public mp4 url>.");
|
|
512
|
+
const taskId = values.task?.trim();
|
|
513
|
+
if (!taskId) {
|
|
514
|
+
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.");
|
|
515
|
+
}
|
|
516
|
+
const tags = values.tags ? String(values.tags).split(",").map((t) => t.trim()).filter(Boolean) : [];
|
|
517
|
+
const result = await dp(auth, `/gigs/${encodeURIComponent(gigId)}/proofs`, {
|
|
518
|
+
method: "POST",
|
|
519
|
+
body: {
|
|
520
|
+
task_identifier: taskId,
|
|
521
|
+
proofs,
|
|
522
|
+
...(values.mailbox ? { mailbox_id: String(values.mailbox) } : {}),
|
|
523
|
+
...(tags.length ? { tags } : {}),
|
|
524
|
+
...(values.note ? { feedback: String(values.note) } : {})
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
out(Boolean(values.json), result, () => {
|
|
528
|
+
console.log(`${GREEN}✓${RESET} Submitted to ${BOLD}${gigId}${RESET} ${DIM}${result.proof_id ?? result.id ?? ""}${RESET}`);
|
|
529
|
+
console.log(` ${DIM}The buyer swipes this in a deck — a link that needs a login is a proof they cannot watch.${RESET}`);
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
async function cmdMine(auth, values) {
|
|
533
|
+
const body = await dp(auth, "/mailboxes/mine", { query: { limit: values.limit } });
|
|
534
|
+
const rows = body.mailboxes ?? body.data ?? [];
|
|
535
|
+
out(Boolean(values.json), body, () => {
|
|
536
|
+
if (!rows.length) {
|
|
537
|
+
console.log(`${DIM}You have not joined any gigs yet. Find one: vidfarm gigs feed${RESET}`);
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
for (const row of rows) {
|
|
541
|
+
console.log(` ${BOLD}${short(row.gig_title ?? row.gig_id, 60)}${RESET} ${DIM}${row.gig_id ?? ""} · mailbox ${row.id ?? ""}${RESET}`);
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
// ── dispatch ─────────────────────────────────────────────────────────────────
|
|
546
|
+
function options() {
|
|
547
|
+
return {
|
|
548
|
+
"dp-key": { type: "string" },
|
|
549
|
+
"api-key": { type: "string" },
|
|
550
|
+
host: { type: "string" },
|
|
551
|
+
json: { type: "boolean" },
|
|
552
|
+
machine: { type: "string" },
|
|
553
|
+
task: { type: "string" },
|
|
554
|
+
body: { type: "string" },
|
|
555
|
+
price: { type: "string" },
|
|
556
|
+
tags: { type: "string" },
|
|
557
|
+
priority: { type: "string" },
|
|
558
|
+
"assign-to": { type: "string" },
|
|
559
|
+
angle: { type: "string" },
|
|
560
|
+
hook: { type: "string" },
|
|
561
|
+
url: { type: "string" },
|
|
562
|
+
format: { type: "string" },
|
|
563
|
+
"assets-link": { type: "string" },
|
|
564
|
+
"upload-link": { type: "string" },
|
|
565
|
+
subfolder: { type: "string" },
|
|
566
|
+
proof: { type: "string", multiple: true },
|
|
567
|
+
status: { type: "string" },
|
|
568
|
+
limit: { type: "string" },
|
|
569
|
+
feedback: { type: "string" },
|
|
570
|
+
tag: { type: "string" },
|
|
571
|
+
title: { type: "string" },
|
|
572
|
+
subtext: { type: "string" },
|
|
573
|
+
registry: { type: "boolean" },
|
|
574
|
+
name: { type: "string" },
|
|
575
|
+
invite: { type: "string" },
|
|
576
|
+
wallet: { type: "string" },
|
|
577
|
+
count: { type: "string" },
|
|
578
|
+
mailbox: { type: "string" },
|
|
579
|
+
note: { type: "string" }
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
export async function runGigsCommand(argv) {
|
|
583
|
+
const sub = (argv[0] ?? "help").toLowerCase();
|
|
584
|
+
if (["help", "--help", "-h", ""].includes(sub)) {
|
|
585
|
+
console.log(GIGS_HELP);
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
const parsed = parseArgs({ args: argv.slice(1), allowPositionals: true, options: options() });
|
|
589
|
+
const values = parsed.values;
|
|
590
|
+
const rest = parsed.positionals;
|
|
591
|
+
const auth = await resolveAuth(values);
|
|
592
|
+
switch (sub) {
|
|
593
|
+
case "machines":
|
|
594
|
+
case "vending": return cmdMachines(auth, values);
|
|
595
|
+
case "tasks":
|
|
596
|
+
case "queue": return cmdTasks(auth, values, rest[0]);
|
|
597
|
+
case "add-task":
|
|
598
|
+
case "task": return cmdAddTask(auth, values);
|
|
599
|
+
case "proofs":
|
|
600
|
+
case "submissions": return cmdProofs(auth, values, rest[0]);
|
|
601
|
+
case "approve":
|
|
602
|
+
case "keep": return cmdReview(auth, values, rest, "approve");
|
|
603
|
+
case "reject":
|
|
604
|
+
case "pass": return cmdReview(auth, values, rest, "reject");
|
|
605
|
+
case "ring-bell":
|
|
606
|
+
case "bell": return cmdRingBell(auth, values);
|
|
607
|
+
case "work":
|
|
608
|
+
case "available": return cmdWork(auth, values);
|
|
609
|
+
case "feed": return cmdFeed(auth, values);
|
|
610
|
+
case "join-feed": return cmdJoinFeed(auth, values);
|
|
611
|
+
case "join": return cmdJoin(auth, values, rest);
|
|
612
|
+
case "claim":
|
|
613
|
+
case "poll": return cmdClaim(auth, values, rest);
|
|
614
|
+
case "submit": return cmdSubmit(auth, values, rest);
|
|
615
|
+
case "mine":
|
|
616
|
+
case "mailboxes": return cmdMine(auth, values);
|
|
617
|
+
default:
|
|
618
|
+
console.error(`Unknown gigs subcommand: ${sub}\n`);
|
|
619
|
+
console.log(GIGS_HELP);
|
|
620
|
+
process.exitCode = 1;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
//# sourceMappingURL=marketplace-gigs.js.map
|