@officexapp/vidfarm-devcli 0.21.38 → 0.21.42
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 +16 -0
- package/.agents/skills/vidfarm/SKILL.md +28 -4
- package/.agents/skills/vidfarm/harnesses/README.md +1 -0
- package/.agents/skills/vidfarm/harnesses/explainer.HARNESS.md +11 -0
- package/.agents/skills/vidfarm/harnesses/product-demo.HARNESS.md +2 -0
- package/.agents/skills/vidfarm/harnesses/product-explainer.HARNESS.md +242 -0
- package/.agents/skills/vidfarm/recipes/bulk-scripting-with-a-harness.md +1 -1
- package/.agents/skills/vidfarm/recipes/cutout-graphics-for-explainers.md +2 -0
- package/.agents/skills/vidfarm/recipes/onboard-a-new-director.md +1 -0
- package/.agents/skills/vidfarm/references/automation-and-local-dev.md +21 -9
- package/.agents/skills/vidfarm/references/content-ideas.md +111 -0
- package/.agents/skills/vidfarm/references/editor-workflows.md +36 -3
- package/.agents/skills/vidfarm/references/onboarding.md +2 -0
- package/.agents/skills/vidfarm/references/primitives.md +67 -0
- package/.agents/skills/vidfarm-media/SKILL.md +50 -0
- package/SKILL.director.md +270 -16
- package/SKILL.md +3 -3
- package/crowdsourcing.md +49 -1
- package/dist/src/cli.js +461 -35
- package/dist/src/devcli/cost-mode.js +8 -0
- package/dist/src/devcli/experiments.js +257 -31
- package/dist/src/devcli/qa-check.js +74 -0
- package/dist/src/devcli/skill-docs.js +103 -0
- package/{experiment.md → experiments.md} +129 -31
- package/package.json +5 -2
|
@@ -106,6 +106,109 @@ export function readPackDoc(ref, name = DEFAULT_PACK) {
|
|
|
106
106
|
const doc = resolvePackDoc(ref, name);
|
|
107
107
|
return { doc, contents: readFileSync(doc.abs, "utf8") };
|
|
108
108
|
}
|
|
109
|
+
export const PACK_TOPICS = [
|
|
110
|
+
{ topic: "content-ideas", aliases: ["ideas", "idea", "angles", "what-to-post", "content"], doc: "references/content-ideas.md",
|
|
111
|
+
blurb: "The 50-frame angle bank — answer \"what should I post?\" for a whole month" },
|
|
112
|
+
{ topic: "meme-recaption", aliases: ["meme", "recaption", "meme-caption"], doc: "references/editor-workflows.md", heading: "Writing a meme recaption",
|
|
113
|
+
blurb: "Recaption a meme at a pain or a win the niche knows — the cold-viewer test" },
|
|
114
|
+
{ topic: "product-explainer", aliases: ["product-explainers"], doc: "harnesses/product-explainer.HARNESS.md",
|
|
115
|
+
blurb: "The product-explainer harness — the bundled base for explaining what a product does" },
|
|
116
|
+
{ topic: "product-demo", aliases: ["demo"], doc: "harnesses/product-demo.HARNESS.md",
|
|
117
|
+
blurb: "The product-demo harness — showing the product in use" },
|
|
118
|
+
{ topic: "explainer-cutouts", aliases: ["cutout", "cutouts", "stickers", "sticker", "explainer"], doc: "recipes/cutout-graphics-for-explainers.md",
|
|
119
|
+
blurb: "Explainers built from transparent cutout stickers — house style, sheets, keying, dark stages" },
|
|
120
|
+
{ topic: "hooks", aliases: ["hook", "virality", "viral", "charges"], doc: "references/hooks-and-virality.md",
|
|
121
|
+
blurb: "The four charges — hook, loop, payoff, bait — written BEFORE the timeline" },
|
|
122
|
+
{ topic: "density", aliases: ["cut", "cutting", "too-long", "pacing"], doc: "references/hooks-and-virality.md", heading: "Density",
|
|
123
|
+
blurb: "Every second must earn its place — the deletion test and cut-on-sight list" },
|
|
124
|
+
{ topic: "captions", aliases: ["caption", "subtitles", "safe-zone"], doc: "references/editor-workflows.md", heading: "TikTok-native caption standard",
|
|
125
|
+
blurb: "The caption standard — safe zone, negative space, font regime, the four valid backgrounds" },
|
|
126
|
+
{ topic: "first-frame", aliases: ["thumbnail", "poster"], doc: "references/editor-workflows.md", heading: "The first frame is the thumbnail",
|
|
127
|
+
blurb: "t=0 is the thumbnail everywhere — compose it as a designed still" },
|
|
128
|
+
{ topic: "no-slop", aliases: ["slop", "visual-standard", "html-slop"], doc: "references/editor-workflows.md", heading: "Social-native visual standard",
|
|
129
|
+
blurb: "No HTML slop — what a social-native frame may and may not contain" },
|
|
130
|
+
{ topic: "blurred-plate", aliases: ["letterbox", "landscape-vertical", "bars"], doc: "references/editor-workflows.md", heading: "The blurred plate",
|
|
131
|
+
blurb: "Landscape footage fullscreen on a vertical canvas, without black bars" },
|
|
132
|
+
{ topic: "ken-burns", aliases: ["pan-zoom", "still-motion"], doc: "references/editor-workflows.md", heading: "Ken Burns",
|
|
133
|
+
blurb: "Animate a still image instead of paying for AI video" },
|
|
134
|
+
{ topic: "paintbrushes", aliases: ["three-paintbrushes", "replication"], doc: "references/editor-workflows.md", heading: "The three paintbrushes",
|
|
135
|
+
blurb: "Clip reuse vs. image gen vs. AI video — and the two replication harnesses" },
|
|
136
|
+
{ topic: "avatar", aliases: ["talking-head", "spokesperson", "presenter"], doc: "references/primitives.md", heading: "Primitive: talking_avatar",
|
|
137
|
+
blurb: "\"Create an avatar\" = a talking head WITH audio, keyed off greenscreen" },
|
|
138
|
+
{ topic: "dedupe", aliases: ["deduplicate", "repost", "variants"], doc: "references/primitives.md", heading: "Primitive: media_dedupe",
|
|
139
|
+
blurb: "Deduplicate an exported MP4 before posting it again or to another platform" },
|
|
140
|
+
{ topic: "review", aliases: ["reviewing", "judge", "holistic", "qa-review"], doc: "references/reviewing-renders.md",
|
|
141
|
+
blurb: "Judge the WHOLE video before you report it done — never by one frame" },
|
|
142
|
+
{ topic: "harness", aliases: ["harnesses", "regime"], doc: "harnesses/README.md",
|
|
143
|
+
blurb: "What a HARNESS.md is, its format, and the three phrasings that mean one" },
|
|
144
|
+
{ topic: "bulk", aliases: ["batch", "volume", "scripting", "daily-posting"], doc: "recipes/bulk-scripting-with-a-harness.md",
|
|
145
|
+
blurb: "Produce N different videos in one run — scripting mode with a harness" },
|
|
146
|
+
{ topic: "onboarding", aliases: ["consultation", "coldstart", "offer", "strategy"], doc: "references/onboarding.md",
|
|
147
|
+
blurb: "The cold-start interview and the brainstorm/* consultation chain" }
|
|
148
|
+
];
|
|
149
|
+
export function listPackTopics() {
|
|
150
|
+
return [...PACK_TOPICS].sort((a, b) => (a.topic < b.topic ? -1 : 1));
|
|
151
|
+
}
|
|
152
|
+
/** Match a spoken topic name. Returns null (not an error) so callers can fall back to file lookup. */
|
|
153
|
+
export function resolvePackTopic(ref) {
|
|
154
|
+
const needle = ref.trim().toLowerCase().replace(/[\s_]+/g, "-").replace(/\.md$/, "");
|
|
155
|
+
return PACK_TOPICS.find((entry) => entry.topic === needle || entry.aliases.includes(needle)) ?? null;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Slice one Markdown section out of a doc: from the heading that contains
|
|
159
|
+
* `heading` down to the next heading of the same or higher level. Returns null
|
|
160
|
+
* when the heading is gone (the pack was edited) so the caller can fall back to
|
|
161
|
+
* the whole file rather than printing nothing.
|
|
162
|
+
*/
|
|
163
|
+
export function extractSection(contents, heading) {
|
|
164
|
+
const needle = heading.trim().toLowerCase();
|
|
165
|
+
const lines = contents.split("\n");
|
|
166
|
+
const level = (line) => (line.match(/^(#{1,6})\s/)?.[1].length ?? 0);
|
|
167
|
+
const start = lines.findIndex((line) => level(line) > 0 && line.toLowerCase().includes(needle));
|
|
168
|
+
if (start < 0)
|
|
169
|
+
return null;
|
|
170
|
+
const depth = level(lines[start]);
|
|
171
|
+
let end = lines.length;
|
|
172
|
+
for (let i = start + 1; i < lines.length; i += 1) {
|
|
173
|
+
const at = level(lines[i]);
|
|
174
|
+
if (at > 0 && at <= depth) {
|
|
175
|
+
end = i;
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return { heading: lines[start].replace(/^#+\s*/, "").trim(), body: lines.slice(start, end).join("\n").trimEnd() };
|
|
180
|
+
}
|
|
181
|
+
/** A topic's text, already sliced to its section when it names one. */
|
|
182
|
+
export function readPackTopic(topic, name = DEFAULT_PACK) {
|
|
183
|
+
const { doc, contents } = readPackDoc(topic.doc, name);
|
|
184
|
+
if (!topic.heading)
|
|
185
|
+
return { doc, contents, whole: true };
|
|
186
|
+
const section = extractSection(contents, topic.heading);
|
|
187
|
+
if (!section)
|
|
188
|
+
return { doc, contents, whole: true };
|
|
189
|
+
return { doc, heading: section.heading, contents: section.body, whole: false };
|
|
190
|
+
}
|
|
191
|
+
export function loadIdeaBank(name = DEFAULT_PACK) {
|
|
192
|
+
const { contents } = readPackDoc("references/content-ideas.md", name);
|
|
193
|
+
const section = extractSection(contents, "The 50 frames");
|
|
194
|
+
const lines = (section?.body ?? contents).split("\n");
|
|
195
|
+
const frames = [];
|
|
196
|
+
const families = [];
|
|
197
|
+
let family = "";
|
|
198
|
+
for (const line of lines) {
|
|
199
|
+
const label = line.match(/^\*\*(.+?)\*\*\s*$/);
|
|
200
|
+
if (label) {
|
|
201
|
+
family = label[1].split(/\s+[—-]\s+/)[0].trim();
|
|
202
|
+
if (family && !families.includes(family))
|
|
203
|
+
families.push(family);
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
const bullet = line.match(/^-\s+(.+?)\s*$/);
|
|
207
|
+
if (bullet && family)
|
|
208
|
+
frames.push({ frame: bullet[1].trim(), family });
|
|
209
|
+
}
|
|
210
|
+
return { frames, families };
|
|
211
|
+
}
|
|
109
212
|
/**
|
|
110
213
|
* Grep the pack. This is the affordance that makes a local copy genuinely
|
|
111
214
|
* better than the network one: "where does it say anything about greenscreen"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Experiments.md
|
|
2
2
|
|
|
3
|
-
How to run ad experiments on vidfarm.cc.
|
|
3
|
+
How to run ad experiments on vidfarm.cc. The canonical URL is
|
|
4
|
+
<https://vidfarm.cc/experiments.md>; `/experiment.md` redirects here.
|
|
4
5
|
|
|
5
6
|
**You are an AI agent, and from here on you are a viral short-form scientist and a direct-response ads practitioner.** Your job is not "make a nice video." Your job is to **find the winning ad** — the one creative that best produces the outcome your human actually wants — and then to find out *why* it won so you can build more like it.
|
|
6
7
|
|
|
@@ -36,28 +37,49 @@ Bookmarks, shares, watch-through, follows and profile visits are all good suppor
|
|
|
36
37
|
|
|
37
38
|
**b) Which platforms and how many channels?** Ask for a list, not a yes/no. Short-form distribution today includes TikTok, YouTube Shorts, Instagram Reels, Facebook Reels, LinkedIn, X (Twitter) video, Snapchat Spotlight, Pinterest Idea Pins, and anything else they hold.
|
|
38
39
|
|
|
39
|
-
**Channel
|
|
40
|
+
**Channel capacity is the single most important number in this whole document, because it is your testing capacity.** The starting assumption is one test video per channel per day — more than that on a young account reads as spam.
|
|
41
|
+
|
|
42
|
+
But **channels are not equal, so ask each one's posting frequency and record it.** A warmed daily TikTok may take two a day; a company LinkedIn page may take three a week; an account may be paused entirely. Capacity is the *sum of the rates*, not the count of the accounts:
|
|
40
43
|
|
|
41
44
|
```
|
|
42
|
-
capacity per epoch
|
|
45
|
+
capacity per epoch = Σ (posts per epoch, per channel)
|
|
43
46
|
epochs per round = ceil(videos in the round / capacity per epoch)
|
|
44
47
|
```
|
|
45
48
|
|
|
46
|
-
|
|
49
|
+
Write the rate next to the channel in the diary — that record is what makes the schedule reproducible:
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
- Channels (capacity 4.43/epoch): tiktok_a x2, tiktok_b, yt_a 1/day, li_a 3/week, fb_a paused
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
A bare name means once per epoch. `x2` means twice. `3/week` and `2/month` are converted for you, and a channel slower than one-per-epoch **skips epochs on a real cadence** rather than being over-posted or dropped. `paused` keeps the account listed while contributing nothing.
|
|
56
|
+
|
|
57
|
+
**Never schedule a channel above its own frequency.** An over-posted account gets throttled, and a throttled account poisons every number measured on it for weeks — which is the account-health problem in §4 arriving by your own hand.
|
|
58
|
+
|
|
59
|
+
Example — four channels (2× TikTok, 1× YouTube Shorts, 1× Instagram), all daily:
|
|
47
60
|
|
|
48
61
|
- capacity = **4 videos/day**
|
|
49
62
|
- a round of 8 angles = 8 / 4 = **2 epochs (2 days)**
|
|
50
63
|
|
|
64
|
+
Same four accounts, but one TikTok is warmed enough for two a day and the Instagram only takes three a week:
|
|
65
|
+
|
|
66
|
+
- capacity = 2 + 1 + 1 + 0.43 = **4.43 videos/day**
|
|
67
|
+
- the same round of 8 still finishes in **2 epochs**, but the schedule is different: the warmed TikTok carries two slots every epoch and Instagram picks up a slot roughly every other epoch instead of one daily.
|
|
68
|
+
|
|
51
69
|
Example — twelve channels, same round of 8 angles:
|
|
52
70
|
|
|
53
71
|
- 8 slots go to the angle experiment
|
|
54
72
|
- **4 slots are still empty**, so run a second experiment in parallel the same day
|
|
55
73
|
|
|
74
|
+
**b-ii) How healthy is each channel, and how often can it post?** While you have them listing channels, get age, follower count, whether it posts consistently, and **how many posts a day or week each one can take** — one line each. The frequency feeds the epoch math directly (capacity is the sum of the rates), and it belongs in the diary next to the channel. **Account health moves numbers by multiples and is the biggest confounder in this method** (see §4), so you need it before you can read a single result. Flag any brand-new account: it is not yet a measuring instrument.
|
|
75
|
+
|
|
56
76
|
**c) What are we selling, and to whom?** If they cannot answer in one sentence, do not guess — run the Vidfarm consultation first (`https://vidfarm.cc/skill.md` → the `brainstorm/*` chain: cold-start interview → awareness stages → angles → hooks). That chain exists precisely to produce the raw material this document then tests.
|
|
57
77
|
|
|
58
|
-
**d)
|
|
78
|
+
**d) Which video format do we start on?** Offer the menu in §5 — the copywriting-led formats — and take a pick, or take the default (b-roll with kinetic captions) if they have no opinion. **Ask this in the interview, not at build time**, because the format is what the first round holds constant while it varies the angle. Ask the talking-head question explicitly here too: *"are you willing to be on camera?"* — the answer changes the whole menu, and it is cheap to ask once.
|
|
59
79
|
|
|
60
|
-
**e)
|
|
80
|
+
**e) Who does the editing?** Their own agent (you), or a distributed task force of gigworkers. This decides the mode — see §3.
|
|
81
|
+
|
|
82
|
+
**f) How long is this campaign?** Experiments run for weeks or months, not one afternoon. Say so up front so the user expects an evolution, not a single delivery.
|
|
61
83
|
|
|
62
84
|
---
|
|
63
85
|
|
|
@@ -65,7 +87,7 @@ Example — twelve channels, same round of 8 angles:
|
|
|
65
87
|
|
|
66
88
|
- **epoch** — one posting cycle. **Typically a day.** For slower or higher-stakes tests it can be a week or a month. One epoch = one batch of videos going live across the available channels.
|
|
67
89
|
- **round** — one experiment on one variable, start to finish. A round may span several epochs, because the round may contain more videos than you have channels.
|
|
68
|
-
- **capacity** — videos you can post per epoch =
|
|
90
|
+
- **capacity** — videos you can post per epoch = **the sum of every channel's posting frequency** (not simply the channel count — a 2/day account contributes 2, a 3/week account 0.43). Your evolutionary speed is capacity, nothing else.
|
|
69
91
|
- **composition params** — the knobs a short-form ad is built out of (see §2). Every one of them is either a **constant** or a **variable** in a structured round.
|
|
70
92
|
- **baseline checkpoint** — the current best-known configuration. Every round starts from it and, if the round wins, replaces it.
|
|
71
93
|
- **outlier** — a video that beat its round's median by a wide margin on the north-star metric. Outliers are the raw material of the next structured round.
|
|
@@ -162,10 +184,37 @@ Short-form results are noisy. Guardrails:
|
|
|
162
184
|
|
|
163
185
|
- **Never** promote a winner off one post. Two posts minimum per variant before you believe it, three is better.
|
|
164
186
|
- Compare within the **same epoch and same platform** where you can — the algorithm's mood is not constant across days or apps.
|
|
187
|
+
- **Normalize per account before you rank.** Account health can outweigh the variable you are testing — see the next section. A number is only comparable to that account's own median.
|
|
165
188
|
- A variant with 3× the median on the north star is an outlier worth pursuing. A variant 20% above median is noise. Say which one you are looking at.
|
|
166
189
|
- Record **losers** as carefully as winners. "Problem-unaware never worked here" is a real, reusable finding.
|
|
167
190
|
- Report what you **measured** separately from what you **judge**. Never dress up a hunch as a result.
|
|
168
191
|
|
|
192
|
+
### Account health — the confounder that can be bigger than your effect
|
|
193
|
+
|
|
194
|
+
**Two videos on two different accounts are not a fair comparison.** An account carries its own health: age, follower count, past performance, niche coherence, posting consistency, and whatever standing the platform privately assigns it. That health moves views by **multiples**, not percentages — routinely more than the difference between two decent angles. So a variant can "win" purely because it landed on the stronger account.
|
|
195
|
+
|
|
196
|
+
That gives you a real trade-off, and you should state which side of it you are on:
|
|
197
|
+
|
|
198
|
+
| | Same account, sequential | Multiple accounts, parallel |
|
|
199
|
+
|---|---|---|
|
|
200
|
+
| **Account health** | held constant — it cancels out | varies, and can dominate the result |
|
|
201
|
+
| **Speed** | slow: ~1 test/day, a round takes as many days as videos | fast: capacity = channel count |
|
|
202
|
+
| **Best for** | the deciding test between 2–3 finalists | wide search, early rounds, creative mode |
|
|
203
|
+
|
|
204
|
+
**Same account is the cleanest instrument you have.** Posting every variant to one account eliminates account health as a variable entirely — the only thing that changed is the thing you changed. The cost is pure sequencing: eight variants is eight days, and that is your whole evolution stalled on one channel.
|
|
205
|
+
|
|
206
|
+
**Themed accounts are still worth having** — several accounts, each coherent to one niche, usually beat one account posting scattershot, because coherence is itself a health input. But the moment you have more than one, their numbers stop being directly comparable.
|
|
207
|
+
|
|
208
|
+
**So compute the true signal like this:**
|
|
209
|
+
|
|
210
|
+
1. **Normalize against the account, not the fleet.** Judge a video against *that account's own recent median*, not the round's raw median. "3× its own account's median" is a signal; "more views than a video on a bigger account" is not.
|
|
211
|
+
2. **Never rank across accounts on raw numbers.** If the leader and the laggard sat on different accounts, you have measured the accounts at least as much as the videos.
|
|
212
|
+
3. **Retest across accounts — this is what buys confidence.** Re-post the apparent winner on a *different* account (deduped), and post the apparent loser on the winner's account. If the ordering holds after the swap, the effect is real. If it flips, you measured account health. This is the single most valuable extra data point in the whole method, and it is why **two posts minimum, three better** exists as a rule.
|
|
213
|
+
4. **Build per-account baselines early.** After a couple of epochs each account has its own median. Record it in the diary Setup. Everything after that is cheap to normalize.
|
|
214
|
+
5. **A brand-new account is not a measuring instrument.** Fresh accounts swing wildly in both directions. Don't hand one a decisive test until it has a baseline.
|
|
215
|
+
|
|
216
|
+
This is also the honest answer to "why retest at all?" Retesting is not bureaucracy. With account health in play, one post is barely evidence — the second and third posts are what separate a finding from a coincidence.
|
|
217
|
+
|
|
169
218
|
### Publishing hygiene
|
|
170
219
|
|
|
171
220
|
Posting one render to several channels is exactly the case platform de-duplication punishes — the second copy gets suppressed and your experiment records a false loser. So for any video going to more than one channel:
|
|
@@ -178,19 +227,58 @@ One variant per channel, and never the same variant on two accounts. Ask about t
|
|
|
178
227
|
|
|
179
228
|
---
|
|
180
229
|
|
|
181
|
-
## 5.
|
|
230
|
+
## 5. Pick the video format — in the plan, not at build time
|
|
182
231
|
|
|
183
|
-
**
|
|
232
|
+
**The format is a planning decision, and it belongs in the plan you get approved.** Do not discover it while editing, and do not leave it implicit. Every round records the format it holds constant (or, if format *is* the variable, the list it varies across). A round with no named format is a round whose "constants" were never actually constant.
|
|
184
233
|
|
|
185
|
-
|
|
186
|
-
- talking head (only if the user is willing to film themselves)
|
|
187
|
-
- process footage
|
|
188
|
-
- loop background footage
|
|
189
|
-
- satisfying footage
|
|
190
|
-
- lifestyle footage
|
|
191
|
-
- POV quote aesthetic
|
|
234
|
+
**The default family is kinetic captions over easy visuals — the copy does the work.** These are deliberately copywriting-led: the words carry the persuasion and the footage only has to hold attention. That is what makes them fast, cheap and repeatable, and it is why a weak visual with a strong hook still wins.
|
|
192
235
|
|
|
193
|
-
|
|
236
|
+
Pick one for the round:
|
|
237
|
+
|
|
238
|
+
| Format | Visual | Note |
|
|
239
|
+
|---|---|---|
|
|
240
|
+
| **b-roll footage** | generic relevant footage under the words | the workhorse — start here |
|
|
241
|
+
| **talking head** | the user on camera | only if they will film themselves; strongest trust signal |
|
|
242
|
+
| **process footage** | something being made/done | high watch-through, needs no narration |
|
|
243
|
+
| **loop background footage** | one seamless looping plate | cheapest of all; the copy is the entire video |
|
|
244
|
+
| **satisfying footage** | oddly-satisfying visuals | strong retention, weak topical fit |
|
|
245
|
+
| **lifestyle footage** | aspirational day-in-the-life | best for identity and status angles |
|
|
246
|
+
| **POV quote aesthetic** | static/slow plate, one quoted line | pure copywriting; a natural fit for hook tests |
|
|
247
|
+
| **meme recaption** | a greenscreen reaction clip over a background image | for 3rd-person copy ("when you try to…"); see the person rule below |
|
|
248
|
+
|
|
249
|
+
All of these are sourceable for ~$0 from the free public raws catalog — `vidfarm public-raws --categories`, then `--category <shelf>`. A shelf is also a ready-made clip pool for fanning one composition into N variants.
|
|
250
|
+
|
|
251
|
+
### Pick the PERSON first — it picks the visual for you
|
|
252
|
+
|
|
253
|
+
Before you argue about footage, read the script's opening words. **The grammatical person the copy is written in decides which visual family works**, and getting this backwards is why a good line lands flat.
|
|
254
|
+
|
|
255
|
+
**First person — "I tried to…", "I spent 3 months…", "here's what I learned"**
|
|
256
|
+
|
|
257
|
+
The viewer is being told a personal story, so the visual has to be *the speaker's world*:
|
|
258
|
+
|
|
259
|
+
- **POV b-roll** — hands, screen, desk, walking shots: what the narrator would actually see
|
|
260
|
+
- **talking head** — the strongest version, if the user will film themselves
|
|
261
|
+
- **process footage** — the thing being done, from the doer's angle
|
|
262
|
+
- Lifestyle and satisfying footage work here too, as long as they read as *someone's* footage
|
|
263
|
+
|
|
264
|
+
**Third person — "when you try to…", "nobody tells you that…", "POV: your first…"**
|
|
265
|
+
|
|
266
|
+
This is observed, not confessed, so it wants a **meme recaption**: a reaction/character clip that plays the situation out, with your line written over it.
|
|
267
|
+
|
|
268
|
+
- Pull the clip from the Vidfarm **greenscreen raws** — `vidfarm public-raws --category greenscreen`
|
|
269
|
+
- Key it and place it over a **background image** (`vidfarm remove-greenscreen`, then composite). A screenshot, product shot, or scene photo all work
|
|
270
|
+
- **Blur the background or don't** — blur when the subject must pop or the plate is busy; skip it when the background *is* the joke and needs to be readable
|
|
271
|
+
- The character carries the emotion; your caption carries the claim
|
|
272
|
+
|
|
273
|
+
**Both families share the same finish, every time:**
|
|
274
|
+
|
|
275
|
+
- **TikTok-native type** — TikTok Sans or the Montserrat 700–900 caption regime, never a "designed" web font
|
|
276
|
+
- **Kinetic captions** — 3–5-word cues paged in time, not a static wall of text
|
|
277
|
+
- **Free Kokoro voiceover** — `vidfarm tts "…"` on the local voice, $0 in `minimize`/`hybrid`. Add it by default: a voice raises watch-through even over silent footage, and it costs nothing. Swap in a premium voice or the user's own recording only when a round is specifically testing voice
|
|
278
|
+
|
|
279
|
+
The quick rule: **if the copy says "I", show the speaker's world. If it says "you", show someone else's reaction.**
|
|
280
|
+
|
|
281
|
+
**Speed to productivity beats polish at the start.** If the director has no opinion, do not stall: take **b-roll footage with kinetic captions**, write it into the plan as the constant, and move. The format is cheap to change in a later round — and "which format?" is itself one of the two best second variables to test (§4).
|
|
194
282
|
|
|
195
283
|
Audio, in ascending order of quality and effort:
|
|
196
284
|
|
|
@@ -211,6 +299,7 @@ This is the sequence. Do not skip forward.
|
|
|
211
299
|
- the **north-star metric**, and why (including the push-back if they said "sales")
|
|
212
300
|
- the **channel inventory** and the resulting capacity per epoch
|
|
213
301
|
- the **mode** (creative or structured) and why
|
|
302
|
+
- the **starting video format** from the §5 menu (and the audio choice: silent captions, AI voiceover, or their own voice)
|
|
214
303
|
- the **epochs and rounds**: what runs on which day, in which slots
|
|
215
304
|
- per round: the **variable**, the **constants**, and the **justification for the priority** — why this variable is worth the capacity before the others
|
|
216
305
|
- what a **win** looks like numerically, decided *before* posting
|
|
@@ -248,7 +337,7 @@ Because sources vary, always record **where a number came from and when it was r
|
|
|
248
337
|
|
|
249
338
|
## Setup
|
|
250
339
|
- North-star metric: comments (secondary: views, clicks)
|
|
251
|
-
- Channels (capacity 4/epoch): tiktok_a, tiktok_b, yt_shorts_a, ig_a
|
|
340
|
+
- Channels (capacity 4.43/epoch): tiktok_a x2, tiktok_b, yt_shorts_a 1/day, ig_a 3/week
|
|
252
341
|
- Mode: creative
|
|
253
342
|
- Baseline checkpoint: kinetic captions over b-roll, no VO, 22s, "wrong answers only" bait
|
|
254
343
|
- Analytics source: flockposter (connected 2026-08-15)
|
|
@@ -268,34 +357,43 @@ Because sources vary, always record **where a number came from and when it was r
|
|
|
268
357
|
| 4 | v004 | most-aware: "the $0 plan does this" | ig_a | ✅ |
|
|
269
358
|
|
|
270
359
|
### Results — read 2026-08-18, source: flockposter, age: 48h
|
|
271
|
-
| video | views | comments | clicks | buys | note |
|
|
272
|
-
|
|
273
|
-
| v001 | 14,200 | 61 | 38 | 0 | **outlier** —
|
|
274
|
-
| v002 | 3,100 | 12 | 9 | 0 | |
|
|
275
|
-
| v003 | 2,800 | 9 | 14 | 1 | |
|
|
276
|
-
| v004 | 1,900 | 4 | 3 | 0 | weakest |
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
360
|
+
| video | channel | views | comments | clicks | buys | note |
|
|
361
|
+
|---|---|---|---|---|---|---|
|
|
362
|
+
| v001 | tiktok_a | 14,200 | 61 | 38 | 0 | **outlier** — 4.1× that account's median |
|
|
363
|
+
| v002 | tiktok_b | 3,100 | 12 | 9 | 0 | |
|
|
364
|
+
| v003 | yt_shorts_a | 2,800 | 9 | 14 | 1 | |
|
|
365
|
+
| v004 | ig_a | 1,900 | 4 | 3 | 0 | weakest |
|
|
366
|
+
|
|
367
|
+
Per-account medians (comments, 48h): tiktok_a 15 · tiktok_b 11 · yt_shorts_a 8 · ig_a 5
|
|
368
|
+
|
|
369
|
+
**Finding:** problem-unaware wins decisively on comments — 4.1× against its OWN account, so it is not just the healthier channel. Most-aware is dead; stop spending capacity on it.
|
|
370
|
+
**Next:** retest v001 on ig_a (the weakest account) to confirm the effect survives the swap, then Round 2 = structured on v001 — hold the angle, vary the written hook ×6.
|
|
280
371
|
````
|
|
281
372
|
|
|
282
|
-
Anything is fine as long as every entry answers: what did we post, what varied, what was held constant, what came back, from where, when, and what did we decide.
|
|
373
|
+
Anything is fine as long as every entry answers: what did we post, **on which account**, what varied, what was held constant, what came back, from where, when, and what did we decide. The account column is not optional bookkeeping — without it you cannot separate a good video from a good account.
|
|
283
374
|
|
|
284
375
|
### If the devcli is installed, do not hand-maintain this file
|
|
285
376
|
|
|
286
377
|
`vidfarm experiment` owns the ledger, the arithmetic and the method lint — nothing else. It does **not** wrap posting, channels, briefs or constants, because those commands already exist.
|
|
287
378
|
|
|
288
379
|
```bash
|
|
289
|
-
vidfarm experiment --init --metric comments --
|
|
380
|
+
vidfarm experiment --init --metric comments --format "POV quote aesthetic" \
|
|
381
|
+
--channels "tiktok_a x2, tiktok_b, yt_a 1/day, li_a 3/week, fb_a paused"
|
|
290
382
|
vidfarm experiment round --videos 8 --variable angle --mode structured \
|
|
291
383
|
--constants "format,hooks,loop,payoff,bait" --why "awareness is the largest unknown"
|
|
292
384
|
vidfarm experiment log v001 --posted --channel tiktok_a
|
|
293
|
-
vidfarm experiment log v001 --views 14200 --comments 61 --source flockposter --age 48h
|
|
385
|
+
vidfarm experiment log v001 --views 14200 --comments 61 --channel tiktok_a --source flockposter --age 48h
|
|
294
386
|
vidfarm experiment # sizing, ranking vs median, outliers, findings
|
|
295
387
|
```
|
|
296
388
|
|
|
297
389
|
Reading is the default; there are exactly two writes, `round` and `log`. `--json` on the read gives you the parsed setup, the per-round analysis and the findings.
|
|
298
390
|
|
|
391
|
+
Channel rates go straight into `--channels` and the CLI does the rest: capacity becomes the **sum of the rates**, `round` deals each epoch's slots out in proportion (the 2/day account gets two rows an epoch, the 3/week account picks one up every other epoch), and `channel-overposted` / `paused-channel-scheduled` fire if a plan asks an account for more than it stated.
|
|
392
|
+
|
|
393
|
+
**Always pass `--channel` when you log a result.** It is what makes account health computable: the CLI then keeps a **per-account median** and ranks each video against *its own account* (`1.3×acct`) instead of the fleet. It also treats a video read on a second account as a **retest**, which is what clears the `account-health-confound` finding. Without `--channel` you get a raw cross-account ranking, which is the exact mistake §4 warns about.
|
|
394
|
+
|
|
395
|
+
Omit `--format` and it prints the whole §5 menu and defaults to b-roll, so the format gets **chosen in the plan** instead of drifting per video. Both the Setup block and every round record it, and `no-format` / `round-no-format` fire when they don't.
|
|
396
|
+
|
|
299
397
|
It sizes the round for you (`8 videos ÷ 4 channels = 2 epochs`), computes the median and marks anything at ≥3× as an outlier, and lints the method: two variables in one structured round, a winner promoted off one post, results read at mixed ages, a structured round handed to gigworkers, unspent capacity, an end-of-funnel north star with no traffic behind it. Feedback, not a gate — it exits 0, like `vidfarm qa`.
|
|
300
398
|
|
|
301
399
|
The rest of the loop stays where it already lives: **`vidfarm channels`** (the channel list, therefore capacity) · **`vidfarm harness`** + **`vidfarm qa`** (the constants, and holding a batch to them) · **`vidfarm handoff`** (the per-video briefs) · **`vidfarm dedupe`** (one variant per channel) · **`vidfarm approve`** + **`vidfarm schedule`** (the actual posting). `experiment log --posted` only *records* that a slot went live; it does not post anything.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@officexapp/vidfarm-devcli",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.42",
|
|
4
4
|
"description": "Local bridge for the Vidfarm Trackpad Editor. `vidfarm serve <template_id>` boots the FULL editor on localhost (disk-backed records/storage, free in-process render); edit composition.html on disk (Claude Code, Codex, etc.) and the browser live-morphs it.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"SKILL.director.md",
|
|
66
66
|
"clipper.md",
|
|
67
67
|
"crowdsourcing.md",
|
|
68
|
-
"
|
|
68
|
+
"experiments.md",
|
|
69
69
|
"update.md",
|
|
70
70
|
"!readme.secret.md",
|
|
71
71
|
"!**/*.secret.*"
|
|
@@ -104,6 +104,9 @@
|
|
|
104
104
|
"test:stickers": "node --import tsx --test test/sticker-pack.test.ts test/plate-key.test.ts",
|
|
105
105
|
"test:social-recycle": "node --import tsx --test test/social-recycle.test.ts",
|
|
106
106
|
"test:dedupe": "node --import tsx --test test/dedupe-recipe.test.ts",
|
|
107
|
+
"test:iconscout": "node --import tsx --test test/iconscout.test.ts",
|
|
108
|
+
"test:billing": "node --import tsx --test test/billing-wallet-cas.test.ts",
|
|
109
|
+
"test:skill-docs": "node --import tsx --test test/skill-docs.test.ts",
|
|
107
110
|
"check:skills": "node scripts/build-director-skill-rollup.mjs --check && node scripts/check-skill-routes.mjs && node scripts/check-skill-nav.mjs",
|
|
108
111
|
"benchmark:editor-chat": "node --import tsx scripts/benchmark-editor-chat-harness.mjs",
|
|
109
112
|
"cdk:deploy:prod-serverless": "npm run build && dotenv -e .env.production -- npx aws-cdk deploy --app 'node dist/infra/cdk/bin/vidfarm-prod.js'",
|