@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
package/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: vidfarm
|
|
3
|
-
skill_version: 0.21.
|
|
3
|
+
skill_version: 0.21.60
|
|
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
|
|
|
@@ -123,6 +123,8 @@ A capable agent can replicate a template entirely on brush 1 and 2, on the free
|
|
|
123
123
|
|
|
124
124
|
**There is a fourth source, and for a weak model it is the FIRST one: a template somebody already decomposed.** `/discover` holds thousands of viral videos that have been taken apart into their scene-by-scene DNA — hook, retention, payoff, cut rhythm, typography, per-beat paintbrush assignments — and forking one is free. That decomposition costs $1+ in AI spend to reproduce yourself, so always check for an existing one before paying to re-run it. `vidfarm discover --query "<the client's problem, in their words>" --json`; a row carrying `summary`, `keywords` and `promotions` is decomposed. **Weak models: this is not an optimisation, it is the method** — see `vidfarm.cc/flash-harness.md`.
|
|
125
125
|
|
|
126
|
+
**A fork is not a half-finished video. It is somebody else's finished one.** Every element in it was chosen for their product, their customer, their face. So the moment you fork, audit the composition element by element and give each one a verdict — KEEP, REPLACE or CUT — before you plan or source anything. See the standard in §6.
|
|
127
|
+
|
|
126
128
|
## 3. Worked example — one video, end to end
|
|
127
129
|
|
|
128
130
|
```bash
|
|
@@ -215,6 +217,7 @@ Say the phase out loud when you switch. Deciding to source mid-build is how a $0
|
|
|
215
217
|
|
|
216
218
|
The standards themselves are not optional. `vidfarm qa <dir>` is — it is a free local checker for most of them, feedback rather than a gate. Nothing calls it, skipping it is fine, and it allows one fix round by default. **Watching the render is the review that counts.** Full reasoning: `references/hooks-and-virality.md` and `references/reviewing-renders.md`.
|
|
217
219
|
|
|
220
|
+
- **Forked a decomposed template? Audit every element, and KEEP is the verdict that needs a reason.** List the layers (`vidfarm pull <forkId> --json` → `.layers[]`, plus `grep -o 'data-src="[^"]*"' composition.html | sort -u`) and write `./work/ADAPTATION.md`: one row per element, verdict KEEP / REPLACE / CUT, why, and what replaced it. **Structure is DNA and stays** — beat count and order, `start`/`duration`, cut rhythm, transitions, caption identity (font, size, placement, background). **Meaning is content and goes** — voiceover, music, caption words, on-screen titles and stats and prices, actor and talking-head clips, product shots and screenshots, stickers that name a thing, the picture behind a background, and every logo, wordmark, @handle, URL and watermark (those are never a KEEP). Generic b-roll may stay when it is still true in the client's world. Run the audit **before** sourcing — every REPLACE row is a line on the procurement list — and report the counts: kept / replaced / cut. Swapping the captions and nothing else is the most common rejected video there is. Full table: `vidfarm.cc/flash-harness.md` §3½.
|
|
218
221
|
- **Write the four charges before the timeline.** Hook: first line is a complete clause naming a situation, on screen at `start:0`. Loop: one open question by 0:10, closing inside this video. Payoff: shown, not summarized. Bait: one ask, final beat. Banned openers — throat-clearing, a logo, a title card, a fade from black.
|
|
219
222
|
- **No HTML slop.** Test: could you have made this element inside TikTok's own editor? If you reached past that — a padded capsule, border, gradient fill, blur panel, card, CTA "button", benefit chips, feature grid — cut it. Nothing in a video is clickable. Emphasize with size, weight, caps, an accent colour, or a drawn mark.
|
|
220
223
|
- **No layout templates — judge the whole frame.** The stack is the tell, not the box: headline + subheadline + CTA centred in a well is a landing page even with every border, fill and capsule stripped off. Banned at frame level — a modal/dialog floating over a dimmed or blurred backdrop, a full-frame wash used to stage a floating block, nav/hero/feature-row sections, a blurred website screenshot as the background plate, document margins. Unstack it into time: hook at t=0, support line on the next cut, CTA spoken or a bare caption. Are you arranging words relative to each other, or relative to the picture? Two text runs at once is the ceiling.
|
|
@@ -271,6 +274,10 @@ Also served at `vidfarm.cc`: `/flash-harness.md` (**the weak-model process harne
|
|
|
271
274
|
|
|
272
275
|
**The two marketplace harnesses never mix.** A buyer's agent must never load the clipper loop and a clipper must never load the buyer loop. If the user's side is unclear, ask: *"Do you want videos made for you, or do you want to get paid to make videos for other people?"*
|
|
273
276
|
|
|
274
|
-
**But an ask about EARNING has already answered it — do not ask.** *"help me earn on vidfarm marketplace"*, *"help me make money on vidfarm"*, *"find me video gigs"*, *"any work today?"*, *"check the vending machines"*, *"run in agentic clipper mode"* all mean the **clipper** side: load `/agentic-clipper.md`, then run the four moves — read the feed's recent notifications for who is asking right now, pull the `GIG_…` id out of each notification's join link and check `available_funds` before committing, join the funded ones with a Base L2 `wallet_address` in the same call, then `GET /work/available` and poll a queue for a task. The copy-paste version is in `/marketplace.md` → *"help me earn on vidfarm" — the whole first session*.
|
|
277
|
+
**But an ask about EARNING has already answered it — do not ask.** *"help me earn on vidfarm marketplace"*, *"help me make money on vidfarm"*, *"find me video gigs"*, *"any work today?"*, *"check the vending machines"*, *"run in agentic clipper mode"* all mean the **clipper** side: load `/agentic-clipper.md`, then run the four moves — read the feed's recent notifications for who is asking right now, pull the `GIG_…` id out of each notification's join link and check `available_funds` before committing, join the funded ones with a Base L2 `wallet_address` in the same call, then `GET /work/available` and poll a queue for a task. The copy-paste version is in `/marketplace.md` → *"help me earn on vidfarm" — the whole first session*. **Those four moves are the first session; a loop that runs for weeks is paced.** `GET /work/available` is the MAP (one call, every machine) and `POST /gigs/:id/queue/poll` is the CLAIM (per machine) — so walk only the machines the map lit up, one at a time, until your build slots (`max_subagents`) are full, stop there, and resume the walk from that machine when a slot opens. **Never re-poll a machine inside 12 hours** unless a new bell rang for it, the map says it holds work, a proof of yours there was approved or paid, or the gigworker asks; **sweep the notifications twice a day**, not every cycle, and join the funded machines it turns up. A task you cannot deliver is **declined** (`POST /gigs/:id/queue/:msgId/decline` — free, per-worker, and without it the same task heads your FIFO forever), never held and never substituted; a task that names no format is where the clipper's own specialty goes. Full apparatus: `/agentic-clipper.md` § 3⅞ and § 4 ①–②.
|
|
275
278
|
|
|
276
279
|
**Then ask the gigworker one more question: the RUN MODE.** It decides which stages of every task stop for them — `full-interactive` (shortlist · plan · raws · cut), `quick-interactive` (shortlist · cut), `auto-batch` (cut only — build the batch unattended, review it in one sitting), `auto-submit` (nothing; opt-in, in their own words). The middle two are the common ones. `vidfarm clipper-run mode` explains all four and saves the answer, `vidfarm clipper-run next` says what to do right now, and `vidfarm clipper-run gate <task>` exits non-zero while a human gate is open — so `gate && gigs submit …` cannot send an unreviewed cut. Full section: `/marketplace.md` → *The four run modes*.
|
|
280
|
+
|
|
281
|
+
**Then offer the CLIPPER DASHBOARD — `vidfarm dashboard` — and expect a yes.** Three of the four run modes stop to make a human *watch a video*, and a terminal cannot play one; without the dashboard the gigworker reads a path out of `REVIEW_QUEUE.md`, opens the file by hand, and types a verb, once per task. `vidfarm dashboard` is that queue as one localhost page over the same `CLIPPER/` folder — no key, no network, no account: the cut plays and seeks, the buyer's brief sits beside it, the checks are tickable and the unticked ones pre-fill the rejection note, the shortlist candidates play side by side (`1`/`2`/`3` to pick), and `A`/`C`/`D`/`U` answer the gate. Every button calls the same `clipper-run` transition the CLI calls, so the page and the terminal can never disagree. Ask once, record it in `MISSION.md` as `dashboard: yes | no`, and never depend on it — a gigworker who declines keeps `REVIEW_QUEUE.md`, `clipper-run review` and the `gate` rail exactly as they were. Full section: `/agentic-clipper.md` § 3⅘.
|
|
282
|
+
|
|
283
|
+
**Before ANY proof is submitted, run the sanity check — every time, on every side of the loop.** `vidfarm gigs verify-proof --proof <url> --clean-master ./final-clean.mp4` needs no api key. It loads each proof url the way a stranger's browser would (no key, no cookie) and exits non-zero on a 403 bucket, a 404, an s3 AccessDenied body served as `video/mp4`, an **expiring presigned url** (read access dies before the buyer swipes), a `localhost`/Drive/Dropbox link, or a 0-byte file. It then checks the delivery is split the right way: the **watermarked** cut public in `proofs[]`, the **clean master** sealed in `private_note` (`--clean-master`) — recommended, not required, so putting the unwatermarked final in the proof itself only warns, and `--clean-in-proof` says you meant it. `vidfarm gigs submit` runs the same check and refuses a failing proof. The two gates are different questions: `clipper-run gate` = *did a human watch it*, `gigs verify-proof` = *can the buyer open it*. Full section: `/marketplace.md` → *the sanity check before you send*.
|
package/clipper.md
CHANGED
|
@@ -144,7 +144,9 @@ Read and report back to the user, in plain language:
|
|
|
144
144
|
- **`available_funds` / `contract_address`** — whether the gig can pay on-chain (see Phase 5).
|
|
145
145
|
- **`default_rate_limit_count` / `default_rate_limit_minutes`** — your ceiling. Never poll past it.
|
|
146
146
|
- **`task_timeout`** — how long you may hold a task before it expires on you.
|
|
147
|
-
- **`review_timeout`** —
|
|
147
|
+
- **`review_timeout`** — seconds until an unreviewed proof auto-approves. **`-1` means never**:
|
|
148
|
+
the client reviews every proof by hand, so nothing pays until they say so. Every vidfarm
|
|
149
|
+
vending machine is `-1`. Read this field before you count on a deadline.
|
|
148
150
|
|
|
149
151
|
**Then judge honestly, out loud:** can you actually deliver this brief with Vidfarm on this
|
|
150
152
|
machine? If the gig wants 4K motion-graphics work and cost mode is `minimize`, say so now. A gig
|
|
@@ -402,8 +404,10 @@ Check the response:
|
|
|
402
404
|
- **`409`** — duplicate `task_identifier`. You already submitted this one; don't resubmit.
|
|
403
405
|
- **`410`** — the task expired while you were rendering. Ask the client for an extension, or move on.
|
|
404
406
|
|
|
405
|
-
Proofs auto-approve after `review_timeout`
|
|
406
|
-
|
|
407
|
+
Proofs auto-approve after `review_timeout` if the client doesn't review — unless it is `-1`,
|
|
408
|
+
which is manual review and never auto-approves. Every vidfarm vending machine is `-1`, so on
|
|
409
|
+
those your proof waits for a human verdict however long that takes. Price is locked at submission
|
|
410
|
+
time, so a client lowering the price later can't reduce what they already owe.
|
|
407
411
|
|
|
408
412
|
---
|
|
409
413
|
|
package/dist/src/cli.js
CHANGED
|
@@ -26,10 +26,10 @@ import { dedupeMediaLocal, localDedupeAvailable, defaultDedupeOutPath, describeL
|
|
|
26
26
|
import { segmentAlphaComponents, encodeTransparentGif, encodeTransparentAnimatedGif, pickPlateColor, detectPlateColor, keySafeArtInstruction, connectivitySafeArtInstruction, analyzeKeyedArt, measureOpaqueShare, HOLE_WARN_PCT } from "./devcli/sticker-pack.js";
|
|
27
27
|
import { smartKeyPlate, planZonedSheet, zonedSheetInstruction } from "./devcli/plate-key.js";
|
|
28
28
|
import { runDoctorCommand } from "./devcli/doctor.js";
|
|
29
|
-
import { findFreePort } from "./devcli/port-utils.js";
|
|
29
|
+
import { findFreePort, isPortFree } from "./devcli/port-utils.js";
|
|
30
30
|
import { scanLocalServers } from "./devcli/process-scan.js";
|
|
31
31
|
import { runSkillsCommand } from "./devcli/skills.js";
|
|
32
|
-
import { AmbiguousDocRef, DEFAULT_PACK, bundledPackDir, listPackDocs, listPackTopics, loadAngleBank, loadAwarenessLadder, loadIdeaBank, readPackDoc, readPackTopic, resolvePackTopic, searchPackDocs } from "./devcli/skill-docs.js";
|
|
32
|
+
import { AmbiguousDocRef, DEFAULT_PACK, bundledPackDir, listPackDocs, listPackTopics, loadAdFormatBank, loadAngleBank, loadAwarenessLadder, loadIdeaBank, readPackDoc, readPackTopic, resolvePackTopic, searchPackDocs } from "./devcli/skill-docs.js";
|
|
33
33
|
import { initTelemetry, reportCliCrash } from "./devcli/telemetry.js";
|
|
34
34
|
import { resolveLocalDataDir, localBackendAvailable, LocalModeUnavailableError, localApiRequest } from "./devcli/local-backend.js";
|
|
35
35
|
import { NativeModuleUnavailableError } from "./devcli/clip-store.js";
|
|
@@ -1049,11 +1049,13 @@ Account:
|
|
|
1049
1049
|
(iconscout takes BOTH values as one secret: <client_id>:<client_secret>)
|
|
1050
1050
|
|
|
1051
1051
|
Agent skill (the director knowledge — a full copy SHIPS INSIDE this CLI):
|
|
1052
|
-
ideas [topic] "What should I post?" — the
|
|
1052
|
+
ideas [topic] "What should I post?" — the FOUR IDEA BANKS (local — offline, free, no AI)
|
|
1053
1053
|
50 frames (the rise of · then vs now · …), 5
|
|
1054
1054
|
AWARENESS STAGES (what the viewer already knows,
|
|
1055
|
-
and what the video may ask for),
|
|
1056
|
-
ANGLES (which side of the problem it comes from)
|
|
1055
|
+
and what the video may ask for), 44 PROBLEM
|
|
1056
|
+
ANGLES (which side of the problem it comes from),
|
|
1057
|
+
and 40 AD FORMATS (what the finished video IS —
|
|
1058
|
+
iphone notes, tier list, warning, greenscreen).
|
|
1057
1059
|
An idea is one point in that grid; vary TWO axes
|
|
1058
1060
|
and 30 videos never read as repeats. It hands over
|
|
1059
1061
|
the banks — you fill them with the director's
|
|
@@ -1068,7 +1070,9 @@ Agent skill (the director knowledge — a full copy SHIPS INSIDE this CLI):
|
|
|
1068
1070
|
--stage <name|n> One stage (unaware · problem-aware · … · most-aware)
|
|
1069
1071
|
--angles The 44 problem angles, by family
|
|
1070
1072
|
--angle <name> One angle family or one named angle
|
|
1071
|
-
--
|
|
1073
|
+
--formats The 40 ad formats — what the video LOOKS like
|
|
1074
|
+
--format <name> One format family or one named format
|
|
1075
|
+
--grid Stage × angle × frame × format — one row per video
|
|
1072
1076
|
--count <n> Sample N, spread across families (default 20 with --topic)
|
|
1073
1077
|
--json Machine-readable banks (+ starter lines)
|
|
1074
1078
|
skill topics The craft this pack knows, by SPOKEN name — (local — offline, no account)
|
|
@@ -1195,7 +1199,7 @@ Marketplace (paid, cloud-only — the bazaar never renders locally):
|
|
|
1195
1199
|
gigs <sub> Your vending machines, BOTH sides (client + gigworker)
|
|
1196
1200
|
machines · tasks · add-task · proofs · approve · reject · ring-bell (client, paid)
|
|
1197
1201
|
earn · feed · check · join · work · claim · submit · proof · earnings (gigworker, free — bring DOLLARPLATOON_API_KEY)
|
|
1198
|
-
upload · mine · join-feed
|
|
1202
|
+
upload · verify-proof · mine · join-feed
|
|
1199
1203
|
"HELP ME EARN ON VIDFARM" IS ONE COMMAND: vidfarm gigs earn. It joins the
|
|
1200
1204
|
feed, reads who is asking for videos right now, checks every one of those
|
|
1201
1205
|
machines for MONEY, and ranks them best-payer-first with the join command.
|
|
@@ -1203,7 +1207,13 @@ Marketplace (paid, cloud-only — the bazaar never renders locally):
|
|
|
1203
1207
|
spend an hour, because approval does not create money. 'gigs submit
|
|
1204
1208
|
--clean-master <file>' seals the unwatermarked cut in the proof's private
|
|
1205
1209
|
note, which the buyer cannot open until a rollup has actually paid you.
|
|
1210
|
+
'gigs verify-proof --proof <url>' is the PRE-SUBMIT sanity check and needs no
|
|
1211
|
+
key: it loads every url the way a stranger's browser would and refuses a dead
|
|
1212
|
+
link, a 403 bucket or an expiring presigned url, then checks the watermarked
|
|
1213
|
+
cut and the clean master are not the wrong way round. 'gigs submit' runs it
|
|
1214
|
+
for you and will not send a broken proof.
|
|
1206
1215
|
e.g. vidfarm gigs earn
|
|
1216
|
+
vidfarm gigs verify-proof --proof https://…/final-watermarked.mp4 --clean-master ./final-clean.mp4
|
|
1207
1217
|
vidfarm gigs add-task --task "60s explainer for acme.com" --price 0.50 --upload-link <share url>
|
|
1208
1218
|
vidfarm gigs proofs --status pending · vidfarm gigs approve PRF_01H…
|
|
1209
1219
|
(see: vidfarm gigs help · the loop: vidfarm.cc/agentic-clipper.md)
|
|
@@ -1223,6 +1233,14 @@ Marketplace (paid, cloud-only — the bazaar never renders locally):
|
|
|
1223
1233
|
e.g. vidfarm clipper-run mode quick-interactive
|
|
1224
1234
|
vidfarm clipper-run next
|
|
1225
1235
|
(see: vidfarm clipper-run help)
|
|
1236
|
+
dashboard THE CLIPPER DASHBOARD for a mission folder, on localhost
|
|
1237
|
+
One page: what waits on you (with the cut PLAYING, not a path to open by hand),
|
|
1238
|
+
what each agent is mid-way through, what was submitted, and the money owed.
|
|
1239
|
+
Every button calls the same clipper-run verb, so the page and the terminal
|
|
1240
|
+
can never disagree. Loopback only, disk only — no key, no network.
|
|
1241
|
+
OFFER IT to a marketplace clipper before the first task; most want it.
|
|
1242
|
+
e.g. vidfarm dashboard · vidfarm dashboard --dir ./CLIPPER --port 4400
|
|
1243
|
+
(vidfarm panel is the same command)
|
|
1226
1244
|
shared <sub> <link> USE a link someone shared with you — NO account, NO API key
|
|
1227
1245
|
info · ls --tree · search · grab · note · mkdir · put · get
|
|
1228
1246
|
grab is the one to reach for: it finds footage by MEANING and downloads it in one step.
|
|
@@ -1700,10 +1718,24 @@ async function main() {
|
|
|
1700
1718
|
case "clipper-run":
|
|
1701
1719
|
case "clipper-mode":
|
|
1702
1720
|
case "run-mode": {
|
|
1721
|
+
// `clipper-run panel` is the same command as `vidfarm panel` — a
|
|
1722
|
+
// gigworker who lives in `clipper-run` should not have to learn a
|
|
1723
|
+
// second top-level verb to find the screen.
|
|
1724
|
+
if ((rest[0] ?? "").toLowerCase() === "panel" || (rest[0] ?? "").toLowerCase() === "dashboard") {
|
|
1725
|
+
await runClipperPanelCommand(rest.slice(1));
|
|
1726
|
+
return;
|
|
1727
|
+
}
|
|
1703
1728
|
const { runClipperRunCommand } = await import("./devcli/clipper-run.js");
|
|
1704
1729
|
await runClipperRunCommand(rest);
|
|
1705
1730
|
return;
|
|
1706
1731
|
}
|
|
1732
|
+
// THE CLIPPER DASHBOARD — the visual half of the clipper loop: watch the
|
|
1733
|
+
// cut, answer the gate, see what is owed.
|
|
1734
|
+
case "dashboard":
|
|
1735
|
+
case "panel":
|
|
1736
|
+
case "clipper-panel":
|
|
1737
|
+
await runClipperPanelCommand(rest);
|
|
1738
|
+
return;
|
|
1707
1739
|
case "get-file":
|
|
1708
1740
|
await runGetFileCommand(rest);
|
|
1709
1741
|
return;
|
|
@@ -2479,6 +2511,83 @@ function printServeBanner(input) {
|
|
|
2479
2511
|
console.log(line);
|
|
2480
2512
|
console.log("");
|
|
2481
2513
|
}
|
|
2514
|
+
// `vidfarm dashboard` (aka `vidfarm panel`) — the CLIPPER DASHBOARD over a
|
|
2515
|
+
// mission folder.
|
|
2516
|
+
// Backend-free and disk-only: no key, no network. It exists because the CUT
|
|
2517
|
+
// gate means "watch this video", and a terminal cannot play one.
|
|
2518
|
+
async function runClipperPanelCommand(argv) {
|
|
2519
|
+
const { ClipperUserError: PanelArgError } = await import("./devcli/clipper-run.js");
|
|
2520
|
+
let parsed;
|
|
2521
|
+
try {
|
|
2522
|
+
parsed = parseArgs({
|
|
2523
|
+
args: argv,
|
|
2524
|
+
allowPositionals: true,
|
|
2525
|
+
options: {
|
|
2526
|
+
dir: { type: "string" },
|
|
2527
|
+
port: { type: "string", default: "4400" },
|
|
2528
|
+
open: { type: "boolean", default: true },
|
|
2529
|
+
"no-open": { type: "boolean", default: false }
|
|
2530
|
+
}
|
|
2531
|
+
});
|
|
2532
|
+
}
|
|
2533
|
+
catch (error) {
|
|
2534
|
+
// Node's own parser rejects things like `--port -5` ("ambiguous") before any
|
|
2535
|
+
// of our validation runs, and its message names neither the flag nor the fix.
|
|
2536
|
+
throw new PanelArgError(`${error instanceof Error ? error.message.replace(/^\w+ \[[^\]]+\]: /, "") : String(error)}\n` +
|
|
2537
|
+
`Usage: vidfarm panel [--dir <mission folder>] [--port <1-65535>] [--no-open]`);
|
|
2538
|
+
}
|
|
2539
|
+
const { resolveMissionRoot, listTaskStates, readMissionMode } = await import("./devcli/clipper-run.js");
|
|
2540
|
+
const { startClipperPanel, mintPanelToken } = await import("./devcli/clipper-panel.js");
|
|
2541
|
+
const { ClipperUserError } = await import("./devcli/clipper-run.js");
|
|
2542
|
+
const root = resolveMissionRoot(parsed.values.dir ?? parsed.positionals[0]);
|
|
2543
|
+
if (!existsSync(root)) {
|
|
2544
|
+
throw new ClipperUserError(`No mission folder at ${root}.\nCreate one first: vidfarm clipper-run init${parsed.values.dir ? ` --dir ${parsed.values.dir}` : ""}`);
|
|
2545
|
+
}
|
|
2546
|
+
// `--dir MISSION.md` used to boot a panel that looked fine and showed nothing.
|
|
2547
|
+
if (!statSync(root).isDirectory()) {
|
|
2548
|
+
throw new ClipperUserError(`${root} is a file, not a mission folder. Point --dir at the folder that contains it.`);
|
|
2549
|
+
}
|
|
2550
|
+
const rawPort = String(parsed.values.port);
|
|
2551
|
+
const requestedPort = Number(rawPort);
|
|
2552
|
+
if (!Number.isInteger(requestedPort) || requestedPort < 1 || requestedPort > 65535) {
|
|
2553
|
+
throw new ClipperUserError(`Invalid --port "${rawPort}" — give a whole number between 1 and 65535.`);
|
|
2554
|
+
}
|
|
2555
|
+
// An EXPLICIT port that is busy must fail. Silently binding the next one free
|
|
2556
|
+
// meant a bookmarked tab on the old port showed a DIFFERENT mission, and
|
|
2557
|
+
// approvals there went to the wrong gig. With no --port given, moving on is
|
|
2558
|
+
// fine: nobody has been told a number yet.
|
|
2559
|
+
// `--port=4492` is the same intent as `--port 4492`; an exact-string match
|
|
2560
|
+
// meant the `=` form silently fell back to auto-advancing.
|
|
2561
|
+
const portWasChosen = argv.concat(process.argv)
|
|
2562
|
+
.some((a) => a === "--port" || a.startsWith("--port="));
|
|
2563
|
+
const port = portWasChosen ? requestedPort : await findFreePort(requestedPort);
|
|
2564
|
+
if (portWasChosen && !(await isPortFree(port))) {
|
|
2565
|
+
throw new ClipperUserError(`Port ${port} is already in use — another panel or local job is on it.\n` +
|
|
2566
|
+
`Pass a different --port, or run \`vidfarm doctor --kill-orphans\` to reclaim it.`);
|
|
2567
|
+
}
|
|
2568
|
+
const token = mintPanelToken();
|
|
2569
|
+
const { url } = await startClipperPanel({ root, port, token });
|
|
2570
|
+
const tasks = listTaskStates(root);
|
|
2571
|
+
const waiting = tasks.filter((task) => task.gate).length;
|
|
2572
|
+
const open = tasks.filter((task) => !task.gate && task.stage !== "done" && task.stage !== "discarded").length;
|
|
2573
|
+
const mode = readMissionMode(root);
|
|
2574
|
+
const line = `${DIM}${"─".repeat(74)}${RESET}`;
|
|
2575
|
+
console.log("");
|
|
2576
|
+
console.log(line);
|
|
2577
|
+
console.log(`${BOLD}${GREEN} Clipper dashboard${RESET} ${DIM}(local files only — no key, no network)${RESET}`);
|
|
2578
|
+
console.log(line);
|
|
2579
|
+
console.log(` mission ${root}`);
|
|
2580
|
+
console.log(` run mode ${mode.mode}${mode.isSet ? "" : `${DIM} (not set — assuming this)${RESET}`}`);
|
|
2581
|
+
console.log(` tasks ${waiting ? `${YELLOW}${waiting} waiting on you${RESET}` : `${DIM}none waiting on you${RESET}`}${DIM} · ${open} in progress · ${tasks.length} total${RESET}`);
|
|
2582
|
+
console.log("");
|
|
2583
|
+
console.log(` ${BOLD}Open:${RESET} ${url}`);
|
|
2584
|
+
console.log(` ${DIM}The token in that URL is minted per run. Loopback only; do not share the link.${RESET}`);
|
|
2585
|
+
console.log(line);
|
|
2586
|
+
console.log("");
|
|
2587
|
+
if (parsed.values.open && !parsed.values["no-open"])
|
|
2588
|
+
openInBrowser(url);
|
|
2589
|
+
// The HTTP server keeps the process alive.
|
|
2590
|
+
}
|
|
2482
2591
|
function openInBrowser(url) {
|
|
2483
2592
|
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
2484
2593
|
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
@@ -12997,6 +13106,8 @@ async function runIdeasCommand(argv) {
|
|
|
12997
13106
|
angle: { type: "string" },
|
|
12998
13107
|
stages: { type: "boolean", default: false },
|
|
12999
13108
|
stage: { type: "string" },
|
|
13109
|
+
formats: { type: "boolean", default: false },
|
|
13110
|
+
format: { type: "string" },
|
|
13000
13111
|
grid: { type: "boolean", default: false }
|
|
13001
13112
|
}
|
|
13002
13113
|
});
|
|
@@ -13005,13 +13116,14 @@ async function runIdeasCommand(argv) {
|
|
|
13005
13116
|
const { frames, families } = loadIdeaBank();
|
|
13006
13117
|
if (!frames.length)
|
|
13007
13118
|
throw new Error("No bundled content-ideas reference found next to this devcli install. Fetch it with `vidfarm skills add vidfarm`.");
|
|
13008
|
-
//
|
|
13119
|
+
// Every axis of an idea lives in one reference, so they live behind one
|
|
13009
13120
|
// command. A director who only ever sees the frames posts 30 videos at one
|
|
13010
13121
|
// awareness stage from one angle, which is the exact failure this expansion
|
|
13011
|
-
// exists to prevent — so the stage and
|
|
13012
|
-
// separate command nobody discovers.
|
|
13122
|
+
// exists to prevent — so the stage, angle and format banks are flags here,
|
|
13123
|
+
// not a separate command nobody discovers.
|
|
13013
13124
|
const ladder = loadAwarenessLadder();
|
|
13014
13125
|
const angleBank = loadAngleBank();
|
|
13126
|
+
const formatBank = loadAdFormatBank();
|
|
13015
13127
|
if (parsed.values.stages || parsed.values.stage) {
|
|
13016
13128
|
const wanted = String(parsed.values.stage ?? "").trim().toLowerCase();
|
|
13017
13129
|
const picked = wanted
|
|
@@ -13070,6 +13182,51 @@ async function runIdeasCommand(argv) {
|
|
|
13070
13182
|
console.log(`${DIM}Pair angles with frames and stages: vidfarm ideas --grid --topic "<offer>"${RESET}`);
|
|
13071
13183
|
return;
|
|
13072
13184
|
}
|
|
13185
|
+
// The format is the LAST field of an idea and the cheapest one to change:
|
|
13186
|
+
// the subject, the angle and the script all survive a format swap, so one
|
|
13187
|
+
// idea that worked becomes four posts that do not read as reposts. Printed
|
|
13188
|
+
// here rather than left in the doc because "shoot another talking head" is
|
|
13189
|
+
// what a director defaults to when nobody hands them the alternatives.
|
|
13190
|
+
if (parsed.values.formats || parsed.values.format) {
|
|
13191
|
+
if (!formatBank.formats.length) {
|
|
13192
|
+
throw new Error("The bundled content-ideas reference has no ad-format bank. Update it with `vidfarm skills add vidfarm`.");
|
|
13193
|
+
}
|
|
13194
|
+
const wanted = String(parsed.values.format ?? "").trim().toLowerCase();
|
|
13195
|
+
const picked = wanted
|
|
13196
|
+
? formatBank.formats.filter((entry) => entry.family.toLowerCase().includes(wanted) || entry.frame.toLowerCase().includes(wanted))
|
|
13197
|
+
: formatBank.formats;
|
|
13198
|
+
if (wanted && !picked.length) {
|
|
13199
|
+
throw new Error(`No format matches "${wanted}". Format families: ${formatBank.families.join(", ")} (see \`vidfarm ideas --formats\`).`);
|
|
13200
|
+
}
|
|
13201
|
+
if (json) {
|
|
13202
|
+
return printJson({
|
|
13203
|
+
ok: true,
|
|
13204
|
+
offline: true,
|
|
13205
|
+
topic: topic || null,
|
|
13206
|
+
total_formats: formatBank.formats.length,
|
|
13207
|
+
families: formatBank.families,
|
|
13208
|
+
formats: picked,
|
|
13209
|
+
method: "references/content-ideas.md"
|
|
13210
|
+
});
|
|
13211
|
+
}
|
|
13212
|
+
console.log(`${BOLD}Ad formats${RESET} ${DIM}(${picked.length} of ${formatBank.formats.length} · what the finished video IS)${RESET}`);
|
|
13213
|
+
if (topic)
|
|
13214
|
+
console.log(`${DIM}Topic: ${RESET}${topic}`);
|
|
13215
|
+
console.log("");
|
|
13216
|
+
let formatGroup = "";
|
|
13217
|
+
for (const entry of picked) {
|
|
13218
|
+
if (entry.family !== formatGroup) {
|
|
13219
|
+
formatGroup = entry.family;
|
|
13220
|
+
console.log(` ${BOLD}${formatGroup}${RESET}`);
|
|
13221
|
+
}
|
|
13222
|
+
console.log(` ${GREEN}${entry.frame}${RESET}${entry.note ? ` ${DIM}— ${entry.note}${RESET}` : ""}`);
|
|
13223
|
+
}
|
|
13224
|
+
console.log("");
|
|
13225
|
+
console.log(`${DIM}The format is the LAST field, not the first. "Let's do a tier list" is a shoot with no idea in it.${RESET}`);
|
|
13226
|
+
console.log(`${YELLOW}Formats that imitate a record${RESET}${DIM} — reviews, an inbox, a search, a stock count — must not fabricate it.${RESET}`);
|
|
13227
|
+
console.log(`${DIM}Use the real screenshot, or keep every claim inside a staged one true: vidfarm skill show ad-formats${RESET}`);
|
|
13228
|
+
return;
|
|
13229
|
+
}
|
|
13073
13230
|
if (parsed.values.grid) {
|
|
13074
13231
|
// The grid is the honest answer to "give me 30 videos": vary TWO axes.
|
|
13075
13232
|
// Walking frames, angles and stages at different strides keeps every row a
|
|
@@ -13113,24 +13270,47 @@ async function runIdeasCommand(argv) {
|
|
|
13113
13270
|
frame = usableFrames[(i + round + bump + 1) % usableFrames.length];
|
|
13114
13271
|
}
|
|
13115
13272
|
gridSeen.add(`${stage.index}|${frame.frame}|${angle.frame}`);
|
|
13273
|
+
// The format walks the bank on its own stride, so a balanced month is
|
|
13274
|
+
// also a varied SHOOT list instead of 30 rows that all get filmed the
|
|
13275
|
+
// same way. It is advisory: unlike the frame and the angle, the stage
|
|
13276
|
+
// does not name its formats, so a director may swap it freely.
|
|
13277
|
+
//
|
|
13278
|
+
// The one pairing that is actually WRONG: a two-column format (us vs
|
|
13279
|
+
// them, myth vs fact) under a frame that is not itself a contrast gives
|
|
13280
|
+
// the video two comparisons, which is the reference's "do not stack two
|
|
13281
|
+
// frames" defect. Walk past those rather than print a row the doc bans.
|
|
13282
|
+
const twoColumn = (entry) => /two-column/i.test(entry?.family ?? "");
|
|
13283
|
+
const frameIsContrast = /contrast/i.test(frame.family);
|
|
13284
|
+
let format = null;
|
|
13285
|
+
for (let step = 0; step < (formatBank.formats.length || 1); step += 1) {
|
|
13286
|
+
const candidate = formatBank.formats[(i * 7 + round + step) % (formatBank.formats.length || 1)];
|
|
13287
|
+
if (!candidate)
|
|
13288
|
+
break;
|
|
13289
|
+
if (twoColumn(candidate) && !frameIsContrast)
|
|
13290
|
+
continue;
|
|
13291
|
+
format = candidate;
|
|
13292
|
+
break;
|
|
13293
|
+
}
|
|
13116
13294
|
return {
|
|
13117
13295
|
stage: `${stage.index} · ${stage.stage}`,
|
|
13118
13296
|
angle: angle.frame,
|
|
13119
13297
|
angle_family: angle.family,
|
|
13120
13298
|
frame: frame.frame,
|
|
13121
13299
|
frame_family: frame.family,
|
|
13300
|
+
format: format?.frame ?? null,
|
|
13301
|
+
format_family: format?.family ?? null,
|
|
13122
13302
|
starter: topic ? joinIdeaFrame(frame.frame, topic) : null
|
|
13123
13303
|
};
|
|
13124
13304
|
});
|
|
13125
13305
|
if (json)
|
|
13126
13306
|
return printJson({ ok: true, offline: true, topic: topic || null, rows: grid, method: "references/content-ideas.md" });
|
|
13127
|
-
console.log(`${BOLD}Idea grid${RESET} ${DIM}(${total} combinations · stage × angle × frame · offline, free, no AI call)${RESET}`);
|
|
13307
|
+
console.log(`${BOLD}Idea grid${RESET} ${DIM}(${total} combinations · stage × angle × frame × format · offline, free, no AI call)${RESET}`);
|
|
13128
13308
|
if (topic)
|
|
13129
13309
|
console.log(`${DIM}Topic: ${RESET}${topic}`);
|
|
13130
13310
|
console.log("");
|
|
13131
13311
|
for (const row of grid) {
|
|
13132
13312
|
console.log(` ${DIM}${row.stage.padEnd(18)}${RESET}${GREEN}${row.angle}${RESET}`);
|
|
13133
|
-
console.log(` ${row.starter ?? row.frame}`);
|
|
13313
|
+
console.log(` ${row.starter ?? row.frame}${row.format ? ` ${DIM}· as: ${row.format}${RESET}` : ""}`);
|
|
13134
13314
|
}
|
|
13135
13315
|
console.log("");
|
|
13136
13316
|
console.log(`${DIM}Each row is a DIFFERENT video, not a rewrite — the stage and the angle both moved.${RESET}`);
|
|
@@ -13211,11 +13391,12 @@ async function runIdeasCommand(argv) {
|
|
|
13211
13391
|
else {
|
|
13212
13392
|
console.log(`${DIM}Pour ONE topic into a frame to get a video: 'the rise of' + your niche. Add --topic "<offer>" to see it.${RESET}`);
|
|
13213
13393
|
}
|
|
13214
|
-
console.log(`${DIM}A frame is only ONE axis. The
|
|
13394
|
+
console.log(`${DIM}A frame is only ONE axis. The others are here too, and a set that freezes them repeats:${RESET}`);
|
|
13215
13395
|
console.log(`${DIM} vidfarm ideas --stages ${RESET}${ladder.length} awareness stages ${DIM}— what the viewer knows, and what you may ask for${RESET}`);
|
|
13216
13396
|
console.log(`${DIM} vidfarm ideas --angles ${RESET}${angleBank.angles.length} problem angles ${DIM}— which side of the problem the video comes from${RESET}`);
|
|
13217
|
-
console.log(`${DIM} vidfarm ideas --
|
|
13218
|
-
console.log(`${DIM}
|
|
13397
|
+
console.log(`${DIM} vidfarm ideas --formats ${RESET}${formatBank.formats.length} ad formats ${DIM}— what the finished video IS (iphone notes, tier list, warning)${RESET}`);
|
|
13398
|
+
console.log(`${DIM} vidfarm ideas --grid --topic "<offer>"${RESET} ${DIM}— stage × angle × frame × format, one row per video${RESET}`);
|
|
13399
|
+
console.log(`${DIM}Method + build notes: vidfarm skill show content-ideas · families: vidfarm ideas --families${RESET}`);
|
|
13219
13400
|
console.log(`${DIM}Never ship a frame as a hook — the four charges still apply: vidfarm skill show hooks${RESET}`);
|
|
13220
13401
|
console.log(`${DIM}Producing the whole set is a batch: vidfarm skill show bulk (one frame per video).${RESET}`);
|
|
13221
13402
|
// The natural upsell, in the right order: ideas are the easy win, and the
|
|
@@ -13404,7 +13585,11 @@ void main().catch(async (error) => {
|
|
|
13404
13585
|
|| error instanceof NativeModuleUnavailableError
|
|
13405
13586
|
// marketplace-gigs is imported lazily, so match its "no key yet" error by
|
|
13406
13587
|
// name rather than pulling the module into the startup path just for this.
|
|
13407
|
-
|| (error instanceof Error && error.name === "GigsAuthMissingError")
|
|
13588
|
+
|| (error instanceof Error && error.name === "GigsAuthMissingError")
|
|
13589
|
+
// clipper-run / panel conditions: a wrong task id, an id already taken, a
|
|
13590
|
+
// gate that has to be answered first. All of these are the CLI talking to a
|
|
13591
|
+
// human, and a stack trace reads as "vidfarm is broken".
|
|
13592
|
+
|| (error instanceof Error && error.name === "ClipperUserError")) {
|
|
13408
13593
|
console.error(error.message);
|
|
13409
13594
|
process.exit(1);
|
|
13410
13595
|
}
|