@officexapp/vidfarm-devcli 0.21.43 → 0.21.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/editor-capabilities/SKILL.md +4 -0
- package/.agents/skills/vidfarm/SKILL.md +95 -17
- package/.agents/skills/vidfarm/harnesses/explainer.HARNESS.md +1 -1
- package/.agents/skills/vidfarm/harnesses/product-demo.HARNESS.md +2 -0
- package/.agents/skills/vidfarm/harnesses/short-form.HARNESS.md +1 -0
- package/.agents/skills/vidfarm/recipes/local-edit-render-approve.md +1 -1
- package/.agents/skills/vidfarm/recipes/onboard-a-new-director.md +1 -1
- package/.agents/skills/vidfarm/references/agent-included-imagegen.md +75 -0
- package/.agents/skills/vidfarm/references/assets-and-sourcing.md +152 -2
- package/.agents/skills/vidfarm/references/automation-and-local-dev.md +22 -9
- package/.agents/skills/vidfarm/references/browser-harness.md +93 -0
- package/.agents/skills/vidfarm/references/content-ideas.md +232 -10
- package/.agents/skills/vidfarm/references/core-workflows.md +11 -1
- package/.agents/skills/vidfarm/references/editor-workflows.md +39 -0
- package/.agents/skills/vidfarm/references/onboarding.md +1 -1
- package/.agents/skills/vidfarm/references/primitives.md +51 -0
- package/.agents/skills/vidfarm-media/SKILL.md +2 -0
- package/SKILL.director.md +775 -42
- package/SKILL.md +157 -115
- package/crowdsourcing.md +417 -3
- package/dist/src/cli.js +750 -34
- package/dist/src/devcli/agent-imagegen.js +181 -0
- package/dist/src/devcli/browser-harness.js +384 -0
- package/dist/src/devcli/clip-store.js +41 -3
- package/dist/src/devcli/consult.js +14 -0
- package/dist/src/devcli/cost-mode.js +23 -3
- package/dist/src/devcli/doctor.js +52 -3
- package/dist/src/devcli/hyperframes-cli.js +11 -1
- package/dist/src/devcli/local-render.js +4 -7
- package/dist/src/devcli/marketplace-gigs.js +623 -0
- package/dist/src/devcli/qa-check.js +89 -1
- package/dist/src/devcli/shared-folder.js +387 -0
- package/dist/src/devcli/skill-docs.js +61 -7
- package/dist/src/devcli/stills.js +4 -8
- package/dist/src/lib/ffprobe-path.js +64 -0
- package/dist/src/lib/render-media-prep.js +2 -11
- package/dist/src/services/clip-curation/ffmpeg.js +4 -15
- package/dist/src/services/clip-curation/index.js +1 -1
- package/dist/src/services/clip-curation/local-agent.js +6 -2
- package/dist/src/services/clip-curation/media-select.js +146 -3
- package/experimental/google-news-to-video.md +235 -0
- package/package.json +8 -150
- package/public/assets/file-directory-app.js +35 -35
- package/public/assets/homepage-client-app.js +15 -15
- package/public/serve-shells/library-files.html +5 -1
- package/public/serve-shells/library-raws.html +10 -1
- package/public/serve-shells/tools-clipper.html +5 -1
- package/public/serve-shells/tools-image.html +5 -1
- package/public/serve-shells/tools-video.html +5 -1
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// Agent-included image generation — the $0 rung between "free stock" and "pay
|
|
2
|
+
// Vidfarm to generate it".
|
|
3
|
+
//
|
|
4
|
+
// The premise: Vidfarm does not need to broker an image model when the agent
|
|
5
|
+
// driving the terminal ALREADY has one included in its own subscription. Google
|
|
6
|
+
// Antigravity / Gemini CLI ship image generation (Nano Banana / Imagen) at no
|
|
7
|
+
// extra charge. OpenAI Codex / ChatGPT ship gpt-image. Claude does not — there
|
|
8
|
+
// is no Anthropic image model, so Claude Code has nothing to fall back on here.
|
|
9
|
+
//
|
|
10
|
+
// That distinction is the whole feature. In cost mode `minimize` the old answer
|
|
11
|
+
// to "I need a graphic" was: free stock, IconScout, or hand the human a prompt.
|
|
12
|
+
// If the agent's own runtime includes image generation, there is a better answer
|
|
13
|
+
// that is still $0 — the agent generates the file itself and imports it. Same
|
|
14
|
+
// house pattern as `vidfarm consult`: the devcli writes the brief, the agent in
|
|
15
|
+
// the terminal executes it. See references/agent-included-imagegen.md.
|
|
16
|
+
//
|
|
17
|
+
// Availability is asserted by the AGENT, not sniffed by us. Env detection here
|
|
18
|
+
// is a hint for the opening line only; the authoritative check is the agent
|
|
19
|
+
// looking at its own tool list. Never claim a capability the agent has to prove.
|
|
20
|
+
//
|
|
21
|
+
// Backend-free (Node built-ins + pure sibling modules) so it ships in the public
|
|
22
|
+
// cloud-only package.
|
|
23
|
+
import { buildImageHandoff } from "./handoff.js";
|
|
24
|
+
/**
|
|
25
|
+
* Hosts we can say something concrete about. Kept deliberately short: a wrong
|
|
26
|
+
* "yes" costs the user a wasted turn, a wrong "no" costs them money, so anything
|
|
27
|
+
* we are not sure of stays `unknown` and the agent checks its own tools.
|
|
28
|
+
*/
|
|
29
|
+
export const KNOWN_HOST_AGENTS = [
|
|
30
|
+
{
|
|
31
|
+
id: "claude-code",
|
|
32
|
+
name: "Claude Code",
|
|
33
|
+
availability: "none",
|
|
34
|
+
envMarkers: ["CLAUDECODE", "CLAUDE_CODE", "CLAUDE_CODE_ENTRYPOINT"],
|
|
35
|
+
note: "Anthropic ships no image model, so there is nothing included here. Use free stock / IconScout, hand the user a prompt (`vidfarm handoff image`), or spend on `vidfarm generate image`."
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: "antigravity",
|
|
39
|
+
name: "Google Antigravity",
|
|
40
|
+
availability: "included",
|
|
41
|
+
tool: "the built-in Gemini image generation (Nano Banana / Imagen)",
|
|
42
|
+
envMarkers: ["ANTIGRAVITY", "ANTIGRAVITY_SESSION", "ANTIGRAVITY_WORKSPACE"],
|
|
43
|
+
note: "Image generation is part of the Gemini subscription — generating a graphic costs $0 extra."
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: "gemini-cli",
|
|
47
|
+
name: "Gemini CLI",
|
|
48
|
+
availability: "included",
|
|
49
|
+
tool: "the built-in Gemini image generation (Nano Banana / Imagen)",
|
|
50
|
+
envMarkers: ["GEMINI_CLI", "GEMINI_CLI_SESSION", "GEMINI_SANDBOX"],
|
|
51
|
+
note: "Image generation is part of the Gemini subscription — generating a graphic costs $0 extra."
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: "codex",
|
|
55
|
+
name: "OpenAI Codex",
|
|
56
|
+
availability: "included",
|
|
57
|
+
tool: "the built-in OpenAI image generation (gpt-image)",
|
|
58
|
+
envMarkers: ["CODEX_SANDBOX", "CODEX_HOME", "CODEX_CLI"],
|
|
59
|
+
note: "Image generation is part of the ChatGPT/Codex subscription — generating a graphic costs $0 extra."
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: "opencode",
|
|
63
|
+
name: "OpenCode",
|
|
64
|
+
availability: "unknown",
|
|
65
|
+
envMarkers: ["OPENCODE", "OPENCODE_SESSION"],
|
|
66
|
+
note: "Depends entirely on which model is configured — check your own tool list."
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: "cursor",
|
|
70
|
+
name: "Cursor",
|
|
71
|
+
availability: "unknown",
|
|
72
|
+
envMarkers: ["CURSOR_TRACE_ID", "CURSOR_SESSION_ID"],
|
|
73
|
+
note: "Depends on the selected model — check your own tool list."
|
|
74
|
+
}
|
|
75
|
+
];
|
|
76
|
+
/**
|
|
77
|
+
* Guess which agent runtime is driving this terminal from its env markers.
|
|
78
|
+
* A miss is normal and harmless — `availability: "unknown"` just means the brief
|
|
79
|
+
* asks the agent to check its own tools instead of asserting anything.
|
|
80
|
+
*/
|
|
81
|
+
export function detectHostAgent(env = process.env) {
|
|
82
|
+
for (const host of KNOWN_HOST_AGENTS) {
|
|
83
|
+
const matched = host.envMarkers.filter((k) => {
|
|
84
|
+
const v = env[k];
|
|
85
|
+
return typeof v === "string" && v.trim() !== "" && v.trim() !== "0" && v.trim().toLowerCase() !== "false";
|
|
86
|
+
});
|
|
87
|
+
if (matched.length)
|
|
88
|
+
return { host, matched, availability: host.availability };
|
|
89
|
+
}
|
|
90
|
+
return { host: null, matched: [], availability: "unknown" };
|
|
91
|
+
}
|
|
92
|
+
/** One line naming this rung, for cost-mode block messages and help text. */
|
|
93
|
+
export const AGENT_IMAGEGEN_ALTERNATIVE = "generate it YOURSELF for $0 if your own agent subscription includes image generation " +
|
|
94
|
+
"(Google Antigravity / Gemini CLI and OpenAI Codex / ChatGPT do; Claude Code does NOT — " +
|
|
95
|
+
'Anthropic ships no image model) — run `vidfarm agent-image "<subject>" --items "a,b,c"` for ' +
|
|
96
|
+
"the prompt, the plate rules and the import command, then `vidfarm put-file` the result";
|
|
97
|
+
/** The shorter form used inside prose blurbs. */
|
|
98
|
+
export const AGENT_IMAGEGEN_SHORT = 'if YOUR agent subscription includes image generation (Gemini/Antigravity, Codex/ChatGPT — not Claude), generate it yourself: `vidfarm agent-image "<subject>"`';
|
|
99
|
+
/**
|
|
100
|
+
* Build the brief the AGENT executes itself. The prompt body is shared verbatim
|
|
101
|
+
* with `vidfarm handoff image` (`buildImageHandoff`) — a plate that keys cleanly
|
|
102
|
+
* and art that survives the cut are the same craft whoever runs the generator —
|
|
103
|
+
* only the STEPS change, because here the runner is the agent, not the human.
|
|
104
|
+
*/
|
|
105
|
+
export function buildAgentImageBrief(input) {
|
|
106
|
+
const base = buildImageHandoff(input);
|
|
107
|
+
const detection = input.detection ?? detectHostAgent();
|
|
108
|
+
const items = input.items.map((s) => s.trim()).filter(Boolean);
|
|
109
|
+
const pack = input.pack ?? items.length > 1;
|
|
110
|
+
const outDir = input.outDir ?? "./stickers";
|
|
111
|
+
const outFile = input.outFile ?? (pack ? "./sheet.png" : "./art.png");
|
|
112
|
+
const folderFlag = input.folder ? ` --folder ${input.folder}` : "";
|
|
113
|
+
const cutCmd = pack
|
|
114
|
+
? `vidfarm sticker-pack ${outFile}${items.length ? ` --items "${items.join(",")}"` : ""} --out-dir ${outDir}`
|
|
115
|
+
: `vidfarm cutout ${outFile}`;
|
|
116
|
+
const steps = [
|
|
117
|
+
"Check YOUR OWN tool list for an image-generation tool included in this subscription — " +
|
|
118
|
+
"Antigravity / Gemini CLI have one (Nano Banana / Imagen), Codex / ChatGPT have one (gpt-image), " +
|
|
119
|
+
"Claude Code has none. If you have none, use the fallbacks below instead; do not pretend.",
|
|
120
|
+
`Generate the image with that tool using the prompt below, and save it to ${outFile}. ` +
|
|
121
|
+
"This spends nothing on Vidfarm and nothing extra on the provider — it is already in the subscription.",
|
|
122
|
+
"Look at the result before you use it: a flat even plate, crisp edges, nothing touching or " +
|
|
123
|
+
"cropped at the frame edge. Re-generate rather than shipping a sheet that will cut badly.",
|
|
124
|
+
`Cut it locally for $0: \`${cutCmd}\`.`,
|
|
125
|
+
`Bring the asset into the library so later videos reuse it: \`vidfarm put-file ${outDir}/<file>.png${folderFlag}\`.`
|
|
126
|
+
];
|
|
127
|
+
const fallbacks = [
|
|
128
|
+
'Free stock first, always — `vidfarm media search "<meaning>" --type image|vector` (Openverse is keyless).',
|
|
129
|
+
'Any icon, sticker, illustration, 3D prop or Lottie — `vidfarm iconscout "<meaning>" --free` beats generation on price AND quality.',
|
|
130
|
+
'Hand the human the prompt — `vidfarm handoff image --theme "<what>" --items "a,b,c"`; they run it in a free web generator.',
|
|
131
|
+
"Only then spend: `vidfarm generate image --prompt \"…\"` (~$0.01–$0.05, billed to the user's own provider key)."
|
|
132
|
+
];
|
|
133
|
+
return {
|
|
134
|
+
...base,
|
|
135
|
+
outFile,
|
|
136
|
+
availability: detection.availability,
|
|
137
|
+
hostName: detection.host?.name ?? null,
|
|
138
|
+
steps,
|
|
139
|
+
followUp: cutCmd,
|
|
140
|
+
fallbacks,
|
|
141
|
+
tools: detection.host?.tool
|
|
142
|
+
? [detection.host.tool]
|
|
143
|
+
: ["your own runtime's image-generation tool, if this subscription includes one"]
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/** Render the brief as the block an agent reads (and may show the user). */
|
|
147
|
+
export function formatAgentImageBrief(brief, detection) {
|
|
148
|
+
const lines = [];
|
|
149
|
+
const host = detection.host;
|
|
150
|
+
if (host && host.availability === "included") {
|
|
151
|
+
lines.push(`Detected host: ${host.name} — image generation IS included in this subscription.`);
|
|
152
|
+
lines.push(host.note);
|
|
153
|
+
}
|
|
154
|
+
else if (host && host.availability === "none") {
|
|
155
|
+
lines.push(`Detected host: ${host.name} — image generation is NOT included.`);
|
|
156
|
+
lines.push(host.note);
|
|
157
|
+
}
|
|
158
|
+
else if (host) {
|
|
159
|
+
lines.push(`Detected host: ${host.name} — unclear whether image generation is included.`);
|
|
160
|
+
lines.push(host.note);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
lines.push("Host agent not detected from the environment.");
|
|
164
|
+
}
|
|
165
|
+
lines.push("The env check is only a hint. YOU are the authority: look at your own tool list.");
|
|
166
|
+
lines.push("");
|
|
167
|
+
brief.steps.forEach((s, i) => lines.push(`${i + 1}. ${s}`));
|
|
168
|
+
lines.push("");
|
|
169
|
+
lines.push("─── the generation prompt ───");
|
|
170
|
+
lines.push(brief.prompt);
|
|
171
|
+
lines.push("─────────────────────────────");
|
|
172
|
+
if (brief.keyColorNote) {
|
|
173
|
+
lines.push("");
|
|
174
|
+
lines.push(`Plate: ${brief.keyColorNote}`);
|
|
175
|
+
}
|
|
176
|
+
lines.push("");
|
|
177
|
+
lines.push("If you have NO image tool of your own, in this order:");
|
|
178
|
+
brief.fallbacks.forEach((f) => lines.push(` • ${f}`));
|
|
179
|
+
return lines.join("\n");
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=agent-imagegen.js.map
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
// `vidfarm browser` — wire the agent's OWN Chrome up as a FREE substitute for
|
|
2
|
+
// the paid cloud search/download primitives.
|
|
3
|
+
//
|
|
4
|
+
// WHY THIS EXISTS. The sourcing ladder in SKILL.md already starts at "your own
|
|
5
|
+
// browser control, if you have it" — but nothing in the devcli ever made that
|
|
6
|
+
// rung real, so every agent fell straight through to `video-search` /
|
|
7
|
+
// `image-search` / `news-search` / `download-video`, all of which are paid
|
|
8
|
+
// plans only. A free-tier director therefore got a 402 for the single most
|
|
9
|
+
// common ask in the product ("find me a clip of X").
|
|
10
|
+
//
|
|
11
|
+
// browser-harness (https://github.com/browser-use/browser-harness) closes that
|
|
12
|
+
// gap: it attaches an LLM to the user's already-running Chrome over one CDP
|
|
13
|
+
// websocket, so the agent can drive Google Images / Google Videos / Google News
|
|
14
|
+
// itself. The session is the user's own logged-in browser, on the user's own
|
|
15
|
+
// IP, at $0. That is a genuine substitute for the paid primitives when the mode
|
|
16
|
+
// is `minimize`, and a cost saver in `hybrid`.
|
|
17
|
+
//
|
|
18
|
+
// This module does NOT reimplement browser-harness. It does three things:
|
|
19
|
+
// 1. detect / install it (`setup`), so "can easily set it up" is one command;
|
|
20
|
+
// 2. report it in `vidfarm doctor` as an optional free capability;
|
|
21
|
+
// 3. mint the exact browse RECIPE for a given sourcing job (`browse`), in the
|
|
22
|
+
// same house pattern as `vidfarm consult` and `vidfarm handoff` — the
|
|
23
|
+
// devcli writes the brief, the agent already in this terminal executes it.
|
|
24
|
+
//
|
|
25
|
+
// Nothing here is billed and nothing here needs a Vidfarm account.
|
|
26
|
+
import { spawnSync } from "node:child_process";
|
|
27
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
28
|
+
import { homedir } from "node:os";
|
|
29
|
+
import path from "node:path";
|
|
30
|
+
import { parseArgs } from "node:util";
|
|
31
|
+
import { resolveSkillsRoot } from "./skills.js";
|
|
32
|
+
const GREEN = "\x1b[32m";
|
|
33
|
+
const YELLOW = "\x1b[33m";
|
|
34
|
+
const DIM = "\x1b[2m";
|
|
35
|
+
const BOLD = "\x1b[1m";
|
|
36
|
+
const RESET = "\x1b[0m";
|
|
37
|
+
export const BROWSER_HARNESS_REPO = "https://github.com/browser-use/browser-harness";
|
|
38
|
+
/** Where `uv tool install` drops console scripts, per platform. browser-harness
|
|
39
|
+
* lands on PATH for most users, but a fresh `uv` install in the same shell has
|
|
40
|
+
* not re-hashed PATH yet — so check the well-known bin dirs too rather than
|
|
41
|
+
* telling a user who just installed it that it isn't there. */
|
|
42
|
+
function candidateBins() {
|
|
43
|
+
const home = homedir();
|
|
44
|
+
const exe = process.platform === "win32" ? "browser-harness.exe" : "browser-harness";
|
|
45
|
+
const dirs = process.platform === "win32"
|
|
46
|
+
? [path.join(home, ".local", "bin"), path.join(home, "AppData", "Roaming", "uv", "tools", "browser-harness", "Scripts")]
|
|
47
|
+
: [path.join(home, ".local", "bin"), "/usr/local/bin", "/opt/homebrew/bin"];
|
|
48
|
+
return dirs.map((dir) => path.join(dir, exe));
|
|
49
|
+
}
|
|
50
|
+
function which(command) {
|
|
51
|
+
const probe = process.platform === "win32" ? "where" : "which";
|
|
52
|
+
const res = spawnSync(probe, [command], { encoding: "utf8" });
|
|
53
|
+
if (res.status !== 0)
|
|
54
|
+
return null;
|
|
55
|
+
const first = String(res.stdout ?? "").split(/\r?\n/).map((line) => line.trim()).find(Boolean);
|
|
56
|
+
return first || null;
|
|
57
|
+
}
|
|
58
|
+
export function detectBrowserHarness() {
|
|
59
|
+
const uv = which("uv");
|
|
60
|
+
let bin = which("browser-harness");
|
|
61
|
+
if (!bin)
|
|
62
|
+
bin = candidateBins().find((candidate) => existsSync(candidate)) ?? null;
|
|
63
|
+
let version = null;
|
|
64
|
+
if (bin) {
|
|
65
|
+
const res = spawnSync(bin, ["--version"], { encoding: "utf8", timeout: 20_000 });
|
|
66
|
+
const out = `${res.stdout ?? ""}${res.stderr ?? ""}`.trim();
|
|
67
|
+
version = out ? out.split(/\r?\n/)[0].trim() : null;
|
|
68
|
+
}
|
|
69
|
+
return { bin, version, uv };
|
|
70
|
+
}
|
|
71
|
+
/** One line for `vidfarm doctor`. Never a failure — this is an OPTIONAL free
|
|
72
|
+
* capability, and a box without it is a perfectly healthy box. */
|
|
73
|
+
export function browserHarnessDoctorCheck() {
|
|
74
|
+
const found = detectBrowserHarness();
|
|
75
|
+
if (found.bin) {
|
|
76
|
+
return {
|
|
77
|
+
level: "ok",
|
|
78
|
+
detail: `${found.bin}${found.version ? ` (${found.version})` : ""} — free browser sourcing available; \`vidfarm browse videos "<shot>"\` for the recipe`
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
level: "warn",
|
|
83
|
+
detail: found.uv
|
|
84
|
+
? "not installed — the FREE substitute for the paid video-search/image-search/news-search/download-video primitives. Install: `vidfarm browser setup`"
|
|
85
|
+
: "not installed, and `uv` is missing too — run `vidfarm browser setup` (it prints the one-line uv install first)"
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
const RECIPES = {
|
|
89
|
+
videos: {
|
|
90
|
+
replaces: "vidfarm video-search (paid, $0.0003/call)",
|
|
91
|
+
udm: "7",
|
|
92
|
+
tbm: "vid",
|
|
93
|
+
extract: "every off-Google result link with its title and host — YouTube, TikTok, Pexels, Pixabay, Mixkit, archive.org, news sites",
|
|
94
|
+
next: [
|
|
95
|
+
"vidfarm raws scan \"<result url>\" --clips 8 # mine short raws out of the best hit",
|
|
96
|
+
"vidfarm clipper \"<result url>\" --start <t> --end <t> # one surgical cut instead"
|
|
97
|
+
]
|
|
98
|
+
},
|
|
99
|
+
images: {
|
|
100
|
+
replaces: "vidfarm image-search (paid, $0.0003/call)",
|
|
101
|
+
udm: "2",
|
|
102
|
+
tbm: "isch",
|
|
103
|
+
extract: "the SOURCE PAGE url behind each thumbnail (Google lazy-loads full-res, so the thumbnail src is a data: URI and not what you want) — click a result, then read the full-size image url off the detail panel",
|
|
104
|
+
next: [
|
|
105
|
+
"vidfarm put-file ./downloaded.png --folder brand-assets --notes \"<what it is, so files --search finds it later>\""
|
|
106
|
+
]
|
|
107
|
+
},
|
|
108
|
+
news: {
|
|
109
|
+
replaces: "vidfarm news-search (paid, $0.0003/call)",
|
|
110
|
+
udm: "12",
|
|
111
|
+
tbm: "nws",
|
|
112
|
+
extract: "headline, publisher, timestamp and article url for each story — this is the STORY stage, not the visuals stage",
|
|
113
|
+
next: [
|
|
114
|
+
"# then a SECOND pass for the visuals — never one query for both:",
|
|
115
|
+
"vidfarm browse videos \"<the thing the story is about>\""
|
|
116
|
+
]
|
|
117
|
+
},
|
|
118
|
+
page: {
|
|
119
|
+
replaces: "vidfarm download-video / download-audio (paid, wallet-billed resolver)",
|
|
120
|
+
udm: null,
|
|
121
|
+
tbm: null,
|
|
122
|
+
extract: "the media element itself — `document.querySelectorAll('video source, video')` for the src, or CDP `Network.getResponseBody` on the media request; a logged-in session sees what an anonymous resolver cannot",
|
|
123
|
+
next: [
|
|
124
|
+
"vidfarm put-file ./saved.mp4 --folder raws-inbox # bring the local file in for $0",
|
|
125
|
+
"vidfarm clipper ./saved.mp4 --start <t> --end <t> # or clip it straight away"
|
|
126
|
+
]
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
function googleUrl(mode, query, legacy) {
|
|
130
|
+
const recipe = RECIPES[mode];
|
|
131
|
+
const q = encodeURIComponent(query);
|
|
132
|
+
if (!recipe.udm && !recipe.tbm)
|
|
133
|
+
return query.startsWith("http") ? query : `https://www.google.com/search?q=${q}`;
|
|
134
|
+
const surface = legacy ? `tbm=${recipe.tbm}` : `udm=${recipe.udm}`;
|
|
135
|
+
return `https://www.google.com/search?q=${q}&${surface}`;
|
|
136
|
+
}
|
|
137
|
+
/** The heredoc an agent runs. Deliberately short: browser-harness's own SKILL.md
|
|
138
|
+
* teaches the AX-tree-first workflow, and we do not want to fight it with a
|
|
139
|
+
* brittle selector script that Google will break next month. */
|
|
140
|
+
function browseScript(mode, query, legacy, limit) {
|
|
141
|
+
const url = googleUrl(mode, query, legacy);
|
|
142
|
+
if (mode === "page") {
|
|
143
|
+
return [
|
|
144
|
+
"browser-harness <<'PY'",
|
|
145
|
+
`new_tab(${JSON.stringify(query)})`,
|
|
146
|
+
"wait_for_load()",
|
|
147
|
+
"print(page_info())",
|
|
148
|
+
"print(js(\"JSON.stringify([...document.querySelectorAll('video, video source')].map(n => n.currentSrc || n.src).filter(Boolean))\"))",
|
|
149
|
+
"PY"
|
|
150
|
+
].join("\n");
|
|
151
|
+
}
|
|
152
|
+
return [
|
|
153
|
+
"browser-harness <<'PY'",
|
|
154
|
+
`new_tab(${JSON.stringify(url)})`,
|
|
155
|
+
"wait_for_load()",
|
|
156
|
+
"# Result links, deduped, Google's own chrome stripped out.",
|
|
157
|
+
"print(js(\"\"\"JSON.stringify(",
|
|
158
|
+
" [...document.querySelectorAll('a[href^=\\\"http\\\"]')]",
|
|
159
|
+
" .map(a => ({ url: a.href, title: (a.innerText || '').trim().split('\\\\n')[0] }))",
|
|
160
|
+
" .filter(r => !/(^|\\\\.)google\\\\.com$/.test(new URL(r.url).hostname) && r.title)",
|
|
161
|
+
" .filter((r, i, all) => all.findIndex(o => o.url === r.url) === i)",
|
|
162
|
+
` .slice(0, ${limit})`,
|
|
163
|
+
")\"\"\"))",
|
|
164
|
+
"PY"
|
|
165
|
+
].join("\n");
|
|
166
|
+
}
|
|
167
|
+
function renderBrowseRecipe(mode, query, legacy, limit, installed) {
|
|
168
|
+
const recipe = RECIPES[mode];
|
|
169
|
+
const lines = [];
|
|
170
|
+
lines.push(`${BOLD}Free browser sourcing — ${mode}${RESET} ${DIM}(replaces ${recipe.replaces})${RESET}`);
|
|
171
|
+
lines.push("");
|
|
172
|
+
if (!installed) {
|
|
173
|
+
lines.push(`${YELLOW}browser-harness is not installed yet.${RESET} Run ${BOLD}vidfarm browser setup${RESET} first — one command, no account, no key.`);
|
|
174
|
+
lines.push("");
|
|
175
|
+
}
|
|
176
|
+
lines.push(`${BOLD}1. Open it${RESET}`);
|
|
177
|
+
lines.push("");
|
|
178
|
+
lines.push(browseScript(mode, query, legacy, limit));
|
|
179
|
+
lines.push("");
|
|
180
|
+
lines.push(`${BOLD}2. Pull off the page${RESET}`);
|
|
181
|
+
lines.push(` ${recipe.extract}`);
|
|
182
|
+
lines.push("");
|
|
183
|
+
lines.push(`${BOLD}3. Feed it forward${RESET}`);
|
|
184
|
+
for (const step of recipe.next)
|
|
185
|
+
lines.push(` ${step}`);
|
|
186
|
+
lines.push("");
|
|
187
|
+
lines.push(`${DIM}Prefer the accessibility tree over screenshots — cdp("Accessibility.getFullAXTree"). If a`);
|
|
188
|
+
lines.push(`selector above comes back empty, Google changed its markup: read the AX tree and adapt,`);
|
|
189
|
+
lines.push(`don't retry the same query. Legacy surface param: rerun with --legacy (tbm=… instead of udm=…).${RESET}`);
|
|
190
|
+
lines.push("");
|
|
191
|
+
lines.push(`${DIM}A public result is not a licensed asset. Keep the source URL and the licence page for`);
|
|
192
|
+
lines.push(`anything that reaches a client render.${RESET}`);
|
|
193
|
+
return lines.join("\n");
|
|
194
|
+
}
|
|
195
|
+
// ── setup ───────────────────────────────────────────────────────────────────
|
|
196
|
+
function runSetup(opts) {
|
|
197
|
+
const before = detectBrowserHarness();
|
|
198
|
+
console.log(`${BOLD}vidfarm browser setup${RESET} — free browser sourcing via browser-harness`);
|
|
199
|
+
console.log(`${DIM}${BROWSER_HARNESS_REPO}${RESET}`);
|
|
200
|
+
console.log("");
|
|
201
|
+
if (!before.uv) {
|
|
202
|
+
console.log(`${YELLOW}Step 0 — install uv (the Python tool installer browser-harness ships through):${RESET}`);
|
|
203
|
+
console.log(process.platform === "win32"
|
|
204
|
+
? ' powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"'
|
|
205
|
+
: " curl -LsSf https://astral.sh/uv/install.sh | sh");
|
|
206
|
+
console.log("");
|
|
207
|
+
console.log("Then re-run `vidfarm browser setup`.");
|
|
208
|
+
return 1;
|
|
209
|
+
}
|
|
210
|
+
const install = ["tool", "install", "--python", "3.12", "--upgrade", "--force", "browser-harness"];
|
|
211
|
+
console.log(`${BOLD}Step 1 — install/upgrade browser-harness${RESET}`);
|
|
212
|
+
console.log(` uv ${install.join(" ")}`);
|
|
213
|
+
if (opts.dryRun) {
|
|
214
|
+
console.log(`${DIM} (--dry-run: not executed)${RESET}`);
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
const res = spawnSync(before.uv, install, { stdio: "inherit" });
|
|
218
|
+
if (res.status !== 0) {
|
|
219
|
+
console.error(`${YELLOW}uv tool install failed (exit ${res.status}). Run the command above by hand to see why.${RESET}`);
|
|
220
|
+
return 1;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
console.log("");
|
|
224
|
+
const found = opts.dryRun ? before : detectBrowserHarness();
|
|
225
|
+
const bin = found.bin ?? "browser-harness";
|
|
226
|
+
// Step 2 — register browser-harness's OWN skill next to the vidfarm pack, so
|
|
227
|
+
// the agent reading .agents/skills finds the browser workflow without a
|
|
228
|
+
// network fetch. `browser-harness skill` prints the canonical body; we never
|
|
229
|
+
// author or paraphrase it here.
|
|
230
|
+
const skillRoot = opts.skillDir ? path.resolve(process.cwd(), opts.skillDir) : resolveSkillsRoot();
|
|
231
|
+
const skillDir = path.join(skillRoot, ".agents", "skills", "browser-harness");
|
|
232
|
+
const skillFile = path.join(skillDir, "SKILL.md");
|
|
233
|
+
console.log(`${BOLD}Step 2 — register the browser-harness skill${RESET}`);
|
|
234
|
+
console.log(` ${skillFile}`);
|
|
235
|
+
if (opts.dryRun) {
|
|
236
|
+
console.log(`${DIM} (--dry-run: not written)${RESET}`);
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
const res = spawnSync(bin, ["skill"], { encoding: "utf8", timeout: 60_000 });
|
|
240
|
+
const body = String(res.stdout ?? "").trim();
|
|
241
|
+
if (res.status === 0 && body) {
|
|
242
|
+
mkdirSync(skillDir, { recursive: true });
|
|
243
|
+
writeFileSync(skillFile, `${body}\n`, "utf8");
|
|
244
|
+
console.log(` ${GREEN}written${RESET}`);
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
console.log(` ${YELLOW}\`${bin} skill\` produced nothing — register it by hand: \`${bin} skill > ${skillFile}\`${RESET}`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
console.log("");
|
|
251
|
+
// Step 3 — the one manual step. Chrome will not expose CDP until the user
|
|
252
|
+
// ticks the box, and no amount of tooling can do it for them.
|
|
253
|
+
console.log(`${BOLD}Step 3 — let Chrome be driven (one-time, the user must do this)${RESET}`);
|
|
254
|
+
console.log(" 1. Open Chrome → chrome://inspect/#remote-debugging");
|
|
255
|
+
console.log(' 2. Tick "Allow remote debugging for this browser instance"');
|
|
256
|
+
console.log(` 3. macOS only, if a permission sheet appears: ${bin} mac-approve`);
|
|
257
|
+
console.log("");
|
|
258
|
+
console.log(`${BOLD}Step 4 — verify${RESET}`);
|
|
259
|
+
console.log(` ${bin} <<'PY'`);
|
|
260
|
+
console.log(" print(page_info())");
|
|
261
|
+
console.log(" PY");
|
|
262
|
+
console.log(` ${DIM}(if it fails: ${bin} --doctor)${RESET}`);
|
|
263
|
+
console.log("");
|
|
264
|
+
console.log(`${GREEN}Then this is free:${RESET}`);
|
|
265
|
+
console.log(' vidfarm browse videos "warehouse worker packing orders POV 4K" # was: video-search, paid');
|
|
266
|
+
console.log(' vidfarm browse images "manila street market wide shot" # was: image-search, paid');
|
|
267
|
+
console.log(' vidfarm browse news "AI startup funding announced" # was: news-search, paid');
|
|
268
|
+
console.log(' vidfarm browse page "https://…" # was: download-video, paid');
|
|
269
|
+
console.log("");
|
|
270
|
+
console.log(`${DIM}Recordings are OFF by default and stay off unless the user asks — they capture page`);
|
|
271
|
+
console.log(`content to disk. \`${bin} recordings\` shows the current preference.${RESET}`);
|
|
272
|
+
return 0;
|
|
273
|
+
}
|
|
274
|
+
function printStatus() {
|
|
275
|
+
const found = detectBrowserHarness();
|
|
276
|
+
console.log(`${BOLD}browser-harness${RESET} ${DIM}— free substitute for the paid search/download primitives${RESET}`);
|
|
277
|
+
console.log("");
|
|
278
|
+
console.log(` uv ${found.uv ? `${GREEN}${found.uv}${RESET}` : `${YELLOW}not found${RESET}`}`);
|
|
279
|
+
console.log(` browser-harness ${found.bin ? `${GREEN}${found.bin}${RESET}${found.version ? ` ${DIM}(${found.version})${RESET}` : ""}` : `${YELLOW}not installed${RESET}`}`);
|
|
280
|
+
console.log("");
|
|
281
|
+
if (!found.bin) {
|
|
282
|
+
console.log(`Install it with ${BOLD}vidfarm browser setup${RESET} — no account, no API key, ~1 minute.`);
|
|
283
|
+
console.log(`${DIM}It replaces video-search / image-search / news-search / download-video, all of which are`);
|
|
284
|
+
console.log(`paid plans only. In cost mode \`minimize\` this is the difference between sourcing footage`);
|
|
285
|
+
console.log(`and telling the user you can't.${RESET}`);
|
|
286
|
+
return 0;
|
|
287
|
+
}
|
|
288
|
+
console.log(`Connection check: ${BOLD}${found.bin} --doctor${RESET}`);
|
|
289
|
+
console.log(`Browse recipes: ${BOLD}vidfarm browse <videos|images|news|page> "<query>"${RESET}`);
|
|
290
|
+
return 0;
|
|
291
|
+
}
|
|
292
|
+
export async function runBrowserHarnessCommand(argv) {
|
|
293
|
+
const sub = (argv[0] ?? "status").toLowerCase();
|
|
294
|
+
const rest = argv.slice(1);
|
|
295
|
+
if (sub === "setup" || sub === "install") {
|
|
296
|
+
const parsed = parseArgs({
|
|
297
|
+
args: rest,
|
|
298
|
+
allowPositionals: false,
|
|
299
|
+
options: { "dry-run": { type: "boolean", default: false }, dir: { type: "string" } }
|
|
300
|
+
});
|
|
301
|
+
const code = runSetup({ dryRun: Boolean(parsed.values["dry-run"]), skillDir: parsed.values.dir });
|
|
302
|
+
if (code !== 0)
|
|
303
|
+
process.exitCode = code;
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if (sub === "doctor") {
|
|
307
|
+
const found = detectBrowserHarness();
|
|
308
|
+
if (!found.bin) {
|
|
309
|
+
printStatus();
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
const res = spawnSync(found.bin, ["--doctor"], { stdio: "inherit" });
|
|
313
|
+
if (res.status !== 0)
|
|
314
|
+
process.exitCode = res.status ?? 1;
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
if (sub === "status" || sub === "" || sub === "--help" || sub === "-h") {
|
|
318
|
+
printStatus();
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
// `vidfarm browser videos "…"` is the same thing as `vidfarm browse videos "…"`.
|
|
322
|
+
if (sub === "videos" || sub === "images" || sub === "news" || sub === "page") {
|
|
323
|
+
await runBrowseCommand(argv);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
throw new Error(`Unknown browser subcommand "${sub}". Use: status | setup | doctor, or \`vidfarm browse <videos|images|news|page> "<query>"\`.`);
|
|
327
|
+
}
|
|
328
|
+
export async function runBrowseCommand(argv) {
|
|
329
|
+
const parsed = parseArgs({
|
|
330
|
+
args: argv,
|
|
331
|
+
allowPositionals: true,
|
|
332
|
+
options: {
|
|
333
|
+
limit: { type: "string", default: "40" },
|
|
334
|
+
legacy: { type: "boolean", default: false },
|
|
335
|
+
// Pipe the script straight into browser-harness instead of printing it.
|
|
336
|
+
// Off by default: the recipe is meant to be ADAPTED (Google's markup
|
|
337
|
+
// moves), and an agent that reads it first gets better results than one
|
|
338
|
+
// that runs it blind.
|
|
339
|
+
run: { type: "boolean", default: false },
|
|
340
|
+
json: { type: "boolean", default: false }
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
const positionals = parsed.positionals ?? [];
|
|
344
|
+
const mode = String(positionals[0] ?? "").toLowerCase();
|
|
345
|
+
const query = positionals.slice(1).join(" ").trim();
|
|
346
|
+
if (!["videos", "images", "news", "page"].includes(mode) || !query) {
|
|
347
|
+
throw new Error('Usage: vidfarm browse <videos|images|news|page> "<query or url>" [--limit 40] [--legacy] [--run]\n' +
|
|
348
|
+
' videos → replaces the paid video-search · images → image-search · news → news-search · page → download-video');
|
|
349
|
+
}
|
|
350
|
+
const limit = Math.max(1, Math.min(200, Number(parsed.values.limit) || 40));
|
|
351
|
+
const legacy = Boolean(parsed.values.legacy);
|
|
352
|
+
const found = detectBrowserHarness();
|
|
353
|
+
if (parsed.values.json) {
|
|
354
|
+
console.log(JSON.stringify({
|
|
355
|
+
mode,
|
|
356
|
+
query,
|
|
357
|
+
installed: Boolean(found.bin),
|
|
358
|
+
bin: found.bin,
|
|
359
|
+
replaces: RECIPES[mode].replaces,
|
|
360
|
+
url: googleUrl(mode, query, legacy),
|
|
361
|
+
script: browseScript(mode, query, legacy, limit),
|
|
362
|
+
extract: RECIPES[mode].extract,
|
|
363
|
+
next: RECIPES[mode].next
|
|
364
|
+
}, null, 2));
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
if (parsed.values.run) {
|
|
368
|
+
if (!found.bin) {
|
|
369
|
+
throw new Error("--run needs browser-harness. Install it with `vidfarm browser setup`, or drop --run to print the recipe.");
|
|
370
|
+
}
|
|
371
|
+
// Feed the PY body (not the wrapper) to the binary on stdin, exactly as the
|
|
372
|
+
// heredoc would.
|
|
373
|
+
const script = browseScript(mode, query, legacy, limit)
|
|
374
|
+
.split("\n")
|
|
375
|
+
.slice(1, -1)
|
|
376
|
+
.join("\n");
|
|
377
|
+
const res = spawnSync(found.bin, [], { input: `${script}\n`, stdio: ["pipe", "inherit", "inherit"] });
|
|
378
|
+
if (res.status !== 0)
|
|
379
|
+
process.exitCode = res.status ?? 1;
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
console.log(renderBrowseRecipe(mode, query, legacy, limit, Boolean(found.bin)));
|
|
383
|
+
}
|
|
384
|
+
//# sourceMappingURL=browser-harness.js.map
|
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
// ~/.vidfarm/clips/<id>.mp4 clip files
|
|
7
7
|
// ~/.vidfarm/thumbs/<id>.jpg thumbnails
|
|
8
8
|
// Override the base dir with VIDFARM_HOME.
|
|
9
|
-
import
|
|
10
|
-
import * as sqliteVec from "sqlite-vec";
|
|
9
|
+
import { createRequire } from "node:module";
|
|
11
10
|
import { existsSync, mkdirSync, rmSync } from "node:fs";
|
|
12
11
|
import path from "node:path";
|
|
13
12
|
import { homedir } from "node:os";
|
|
@@ -27,6 +26,44 @@ export function resolveClipStorePaths(baseOverride) {
|
|
|
27
26
|
workDir: path.join(base, "scan-tmp")
|
|
28
27
|
};
|
|
29
28
|
}
|
|
29
|
+
const requireFrom = createRequire(import.meta.url);
|
|
30
|
+
let cachedSqlite;
|
|
31
|
+
/**
|
|
32
|
+
* A missing OPTIONAL native module is an expected environment condition, not a
|
|
33
|
+
* bug — cli.ts prints the message without a stack and skips the crash report,
|
|
34
|
+
* the same treatment LocalModeUnavailableError gets.
|
|
35
|
+
*/
|
|
36
|
+
export class NativeModuleUnavailableError extends Error {
|
|
37
|
+
constructor(message) {
|
|
38
|
+
super(message);
|
|
39
|
+
this.name = "NativeModuleUnavailableError";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Load the optional native SQLite stack, or throw a message that names the
|
|
44
|
+
* actual fix. Called from the ClipStore constructor, never at import time.
|
|
45
|
+
*/
|
|
46
|
+
function loadSqlite() {
|
|
47
|
+
if (cachedSqlite)
|
|
48
|
+
return cachedSqlite;
|
|
49
|
+
try {
|
|
50
|
+
const mod = requireFrom("better-sqlite3");
|
|
51
|
+
const ctor = (typeof mod === "function" ? mod : mod.default);
|
|
52
|
+
const sqliteVec = requireFrom("sqlite-vec");
|
|
53
|
+
return (cachedSqlite = { Database: ctor, sqliteVec });
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
const detail = error instanceof Error ? error.message.split("\n")[0] : String(error);
|
|
57
|
+
throw new NativeModuleUnavailableError("The local clip library needs the optional native module `better-sqlite3`, which is not built on this machine.\n" +
|
|
58
|
+
" Everything else in vidfarm works without it — only `vidfarm clips` does not.\n" +
|
|
59
|
+
" To enable it, install a C++ toolchain and reinstall:\n" +
|
|
60
|
+
" Windows : npm i -g windows-build-tools (or install \"Desktop development with C++\" from the Visual Studio Installer)\n" +
|
|
61
|
+
" macOS : xcode-select --install\n" +
|
|
62
|
+
" Linux : apt install build-essential python3\n" +
|
|
63
|
+
" Then: npm i -g @officexapp/vidfarm-devcli\n" +
|
|
64
|
+
` Underlying load error: ${detail}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
30
67
|
export class ClipStore {
|
|
31
68
|
paths;
|
|
32
69
|
db;
|
|
@@ -36,7 +73,8 @@ export class ClipStore {
|
|
|
36
73
|
mkdirSync(this.paths.base, { recursive: true });
|
|
37
74
|
mkdirSync(this.paths.clipsDir, { recursive: true });
|
|
38
75
|
mkdirSync(this.paths.thumbsDir, { recursive: true });
|
|
39
|
-
|
|
76
|
+
const { Database: DatabaseCtor, sqliteVec } = loadSqlite();
|
|
77
|
+
this.db = new DatabaseCtor(this.paths.db);
|
|
40
78
|
this.db.pragma("journal_mode = WAL");
|
|
41
79
|
sqliteVec.load(this.db);
|
|
42
80
|
this.dim = this.initSchema();
|
|
@@ -63,6 +63,18 @@ const STEP_ORDER = ["coldstart", "awareness", "angles", "hooks"];
|
|
|
63
63
|
* answered. It lives in the working folder next to the other artifacts.
|
|
64
64
|
*/
|
|
65
65
|
export const CONSULT_CONTEXT_FILE = "CONTEXT.md";
|
|
66
|
+
/**
|
|
67
|
+
* CONTENT IDEAS COME BEFORE THE INTERVIEW.
|
|
68
|
+
*
|
|
69
|
+
* An interview asks the director to work before they have seen anything, which
|
|
70
|
+
* is the worst trade a first session can offer. `vidfarm ideas --topic "<their
|
|
71
|
+
* offer in a line>"` inverts it: one sentence in, 20+ titled video ideas out,
|
|
72
|
+
* offline and free. The director gets a win first, and the ideas they react to
|
|
73
|
+
* ("this one, not that one") are better interview answers than the ones they
|
|
74
|
+
* would have typed cold. So every interview brief opens by offering the ideas
|
|
75
|
+
* pass, and the interview becomes the thing they opt INTO once they want depth.
|
|
76
|
+
*/
|
|
77
|
+
const IDEAS_FIRST = 'START WITH CONTENT IDEAS, NOT THIS INTERVIEW, unless the director already ran them or explicitly asked for a consultation. Get their offer in ONE line, run `vidfarm ideas --topic "<that line>"`, sharpen the frames into 20+ real titles and save `content-ideas.md`. It is offline, free, and gives them something to react to. Then say the interview is available when they want the strategy behind the ideas — and run it now only if they say yes.';
|
|
66
78
|
/**
|
|
67
79
|
* The SHORT interview. The full cold-start prompt asks a model to invent 12-30
|
|
68
80
|
* questions; that is the right depth for a director who wants a real strategy
|
|
@@ -244,6 +256,7 @@ export function buildConsultBrief(input) {
|
|
|
244
256
|
prompt: buildShortInterviewPrompt(),
|
|
245
257
|
steps: [
|
|
246
258
|
contextRead,
|
|
259
|
+
IDEAS_FIRST,
|
|
247
260
|
`Ask the human these ${SHORT_INTERVIEW.length} questions in two batches of three. Ask them as written. Do not add questions, do not invent answers, and do not skip ahead to advice.`,
|
|
248
261
|
`Keep their phrasing for the pain and the desire answers word for word: the angles and hooks steps quote them back, and a paraphrase is what makes an ad sound generic.`,
|
|
249
262
|
"TELL THEM UP FRONT that every question is optional: they can skip any one, or say stop and end the interview on the spot. Then work with whatever they gave — do not push, do not re-ask, and do not make finishing the set a condition of getting a video.",
|
|
@@ -264,6 +277,7 @@ export function buildConsultBrief(input) {
|
|
|
264
277
|
prompt: buildColdstartPrompt(input.userMessage?.trim() || "The director asked for a consultation and has given no offer context yet.", count),
|
|
265
278
|
steps: [
|
|
266
279
|
...common,
|
|
280
|
+
IDEAS_FIRST,
|
|
267
281
|
`This one is an INTERVIEW, not a report. Answer the prompt to get the question set, then ASK THE HUMAN those questions conversationally, a few at a time — never dump all ${count} at once, and never invent their answers.`,
|
|
268
282
|
`Assemble their real words into \`${artifact}\`: keep their phrasing for pain and desire, because the later angle and hook steps quote it back.`,
|
|
269
283
|
"Stop and wait for the human between batches. If they go quiet on a question, mark it UNKNOWN in the file rather than guessing — an invented customer psyche poisons every later step.",
|