@officexapp/vidfarm-devcli 0.21.59 → 0.21.61
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 +2 -2
- package/.agents/skills/vidfarm/references/content-ideas.md +97 -14
- package/SKILL.director.md +99 -16
- package/SKILL.md +3 -1
- package/clipper.md +7 -3
- package/dist/src/cli.js +194 -15
- package/dist/src/devcli/clipper-panel.js +1828 -0
- package/dist/src/devcli/clipper-run.js +635 -126
- package/dist/src/devcli/marketplace-gigs.js +226 -2
- package/dist/src/devcli/skill-docs.js +14 -0
- package/experimental/meme-recaption.md +355 -54
- package/marketplace.md +67 -1
- package/package.json +3 -1
- package/public/assets/homepage-client-app.js +14 -14
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)
|
|
@@ -1229,6 +1233,14 @@ Marketplace (paid, cloud-only — the bazaar never renders locally):
|
|
|
1229
1233
|
e.g. vidfarm clipper-run mode quick-interactive
|
|
1230
1234
|
vidfarm clipper-run next
|
|
1231
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)
|
|
1232
1244
|
shared <sub> <link> USE a link someone shared with you — NO account, NO API key
|
|
1233
1245
|
info · ls --tree · search · grab · note · mkdir · put · get
|
|
1234
1246
|
grab is the one to reach for: it finds footage by MEANING and downloads it in one step.
|
|
@@ -1706,10 +1718,24 @@ async function main() {
|
|
|
1706
1718
|
case "clipper-run":
|
|
1707
1719
|
case "clipper-mode":
|
|
1708
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
|
+
}
|
|
1709
1728
|
const { runClipperRunCommand } = await import("./devcli/clipper-run.js");
|
|
1710
1729
|
await runClipperRunCommand(rest);
|
|
1711
1730
|
return;
|
|
1712
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;
|
|
1713
1739
|
case "get-file":
|
|
1714
1740
|
await runGetFileCommand(rest);
|
|
1715
1741
|
return;
|
|
@@ -2485,6 +2511,83 @@ function printServeBanner(input) {
|
|
|
2485
2511
|
console.log(line);
|
|
2486
2512
|
console.log("");
|
|
2487
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
|
+
}
|
|
2488
2591
|
function openInBrowser(url) {
|
|
2489
2592
|
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
2490
2593
|
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
@@ -13003,6 +13106,8 @@ async function runIdeasCommand(argv) {
|
|
|
13003
13106
|
angle: { type: "string" },
|
|
13004
13107
|
stages: { type: "boolean", default: false },
|
|
13005
13108
|
stage: { type: "string" },
|
|
13109
|
+
formats: { type: "boolean", default: false },
|
|
13110
|
+
format: { type: "string" },
|
|
13006
13111
|
grid: { type: "boolean", default: false }
|
|
13007
13112
|
}
|
|
13008
13113
|
});
|
|
@@ -13011,13 +13116,14 @@ async function runIdeasCommand(argv) {
|
|
|
13011
13116
|
const { frames, families } = loadIdeaBank();
|
|
13012
13117
|
if (!frames.length)
|
|
13013
13118
|
throw new Error("No bundled content-ideas reference found next to this devcli install. Fetch it with `vidfarm skills add vidfarm`.");
|
|
13014
|
-
//
|
|
13119
|
+
// Every axis of an idea lives in one reference, so they live behind one
|
|
13015
13120
|
// command. A director who only ever sees the frames posts 30 videos at one
|
|
13016
13121
|
// awareness stage from one angle, which is the exact failure this expansion
|
|
13017
|
-
// exists to prevent — so the stage and
|
|
13018
|
-
// 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.
|
|
13019
13124
|
const ladder = loadAwarenessLadder();
|
|
13020
13125
|
const angleBank = loadAngleBank();
|
|
13126
|
+
const formatBank = loadAdFormatBank();
|
|
13021
13127
|
if (parsed.values.stages || parsed.values.stage) {
|
|
13022
13128
|
const wanted = String(parsed.values.stage ?? "").trim().toLowerCase();
|
|
13023
13129
|
const picked = wanted
|
|
@@ -13076,6 +13182,51 @@ async function runIdeasCommand(argv) {
|
|
|
13076
13182
|
console.log(`${DIM}Pair angles with frames and stages: vidfarm ideas --grid --topic "<offer>"${RESET}`);
|
|
13077
13183
|
return;
|
|
13078
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
|
+
}
|
|
13079
13230
|
if (parsed.values.grid) {
|
|
13080
13231
|
// The grid is the honest answer to "give me 30 videos": vary TWO axes.
|
|
13081
13232
|
// Walking frames, angles and stages at different strides keeps every row a
|
|
@@ -13119,24 +13270,47 @@ async function runIdeasCommand(argv) {
|
|
|
13119
13270
|
frame = usableFrames[(i + round + bump + 1) % usableFrames.length];
|
|
13120
13271
|
}
|
|
13121
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
|
+
}
|
|
13122
13294
|
return {
|
|
13123
13295
|
stage: `${stage.index} · ${stage.stage}`,
|
|
13124
13296
|
angle: angle.frame,
|
|
13125
13297
|
angle_family: angle.family,
|
|
13126
13298
|
frame: frame.frame,
|
|
13127
13299
|
frame_family: frame.family,
|
|
13300
|
+
format: format?.frame ?? null,
|
|
13301
|
+
format_family: format?.family ?? null,
|
|
13128
13302
|
starter: topic ? joinIdeaFrame(frame.frame, topic) : null
|
|
13129
13303
|
};
|
|
13130
13304
|
});
|
|
13131
13305
|
if (json)
|
|
13132
13306
|
return printJson({ ok: true, offline: true, topic: topic || null, rows: grid, method: "references/content-ideas.md" });
|
|
13133
|
-
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}`);
|
|
13134
13308
|
if (topic)
|
|
13135
13309
|
console.log(`${DIM}Topic: ${RESET}${topic}`);
|
|
13136
13310
|
console.log("");
|
|
13137
13311
|
for (const row of grid) {
|
|
13138
13312
|
console.log(` ${DIM}${row.stage.padEnd(18)}${RESET}${GREEN}${row.angle}${RESET}`);
|
|
13139
|
-
console.log(` ${row.starter ?? row.frame}`);
|
|
13313
|
+
console.log(` ${row.starter ?? row.frame}${row.format ? ` ${DIM}· as: ${row.format}${RESET}` : ""}`);
|
|
13140
13314
|
}
|
|
13141
13315
|
console.log("");
|
|
13142
13316
|
console.log(`${DIM}Each row is a DIFFERENT video, not a rewrite — the stage and the angle both moved.${RESET}`);
|
|
@@ -13217,11 +13391,12 @@ async function runIdeasCommand(argv) {
|
|
|
13217
13391
|
else {
|
|
13218
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}`);
|
|
13219
13393
|
}
|
|
13220
|
-
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}`);
|
|
13221
13395
|
console.log(`${DIM} vidfarm ideas --stages ${RESET}${ladder.length} awareness stages ${DIM}— what the viewer knows, and what you may ask for${RESET}`);
|
|
13222
13396
|
console.log(`${DIM} vidfarm ideas --angles ${RESET}${angleBank.angles.length} problem angles ${DIM}— which side of the problem the video comes from${RESET}`);
|
|
13223
|
-
console.log(`${DIM} vidfarm ideas --
|
|
13224
|
-
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}`);
|
|
13225
13400
|
console.log(`${DIM}Never ship a frame as a hook — the four charges still apply: vidfarm skill show hooks${RESET}`);
|
|
13226
13401
|
console.log(`${DIM}Producing the whole set is a batch: vidfarm skill show bulk (one frame per video).${RESET}`);
|
|
13227
13402
|
// The natural upsell, in the right order: ideas are the easy win, and the
|
|
@@ -13410,7 +13585,11 @@ void main().catch(async (error) => {
|
|
|
13410
13585
|
|| error instanceof NativeModuleUnavailableError
|
|
13411
13586
|
// marketplace-gigs is imported lazily, so match its "no key yet" error by
|
|
13412
13587
|
// name rather than pulling the module into the startup path just for this.
|
|
13413
|
-
|| (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")) {
|
|
13414
13593
|
console.error(error.message);
|
|
13415
13594
|
process.exit(1);
|
|
13416
13595
|
}
|