@officexapp/vidfarm-devcli 0.21.24 → 0.21.27
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 +35 -7
- package/.agents/skills/vidfarm/references/assets-and-sourcing.md +26 -0
- package/.agents/skills/vidfarm/references/automation-and-local-dev.md +37 -6
- package/.agents/skills/vidfarm/references/core-workflows.md +1 -1
- package/.agents/skills/vidfarm/references/editor-workflows.md +44 -2
- package/.agents/skills/vidfarm/references/primitives.md +28 -0
- package/.agents/skills/vidfarm-media/SKILL.md +3 -1
- package/.agents/skills/vidfarm-media/references/tts.md +1 -1
- package/SKILL.director.md +171 -16
- package/SKILL.md +14 -2
- package/clipper.md +3 -2
- package/dist/src/cli.js +412 -17
- package/dist/src/devcli/cost-mode.js +66 -26
- package/dist/src/devcli/sequence.js +619 -0
- package/dist/src/services/sequence-prompts.js +463 -0
- package/package.json +4 -2
- package/public/assets/file-directory-app.js +26 -26
- package/public/assets/homepage-client-app.js +12 -12
- package/public/serve-shells/editor.html +66 -3
- package/public/serve-shells/library-files.html +60 -3
- package/public/serve-shells/library-raws.html +60 -3
- package/public/serve-shells/tools-clipper.html +60 -3
- package/public/serve-shells/tools-image.html +1069 -483
- package/public/serve-shells/tools-video.html +140 -14
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
// ── Storyboard-driven AI sequence grammar ───────────────────────────────────
|
|
2
|
+
// The prompt architecture behind the "character card → storyboard sheet →
|
|
3
|
+
// Seedance/AI-video" workflow. This module is PURE (no I/O, no network): it
|
|
4
|
+
// turns a `sequence.json` plan into the three prompt strings the pipeline
|
|
5
|
+
// needs, so `vidfarm sequence` stays a thin orchestrator and the grammar has a
|
|
6
|
+
// single source of truth (also reused by docs + the local-agent task file).
|
|
7
|
+
//
|
|
8
|
+
// The grammar is distilled from the workflows that actually ship this look in
|
|
9
|
+
// public (aimikoda's THE WIND CALLS / Mei Lin boards, insMind's 9-grid
|
|
10
|
+
// commercial board, the ViralOps "board first, then burn credits" rule):
|
|
11
|
+
//
|
|
12
|
+
// 1. CHARACTER CARD — one identity sheet per character. Locks face, body,
|
|
13
|
+
// wardrobe and art style. Never regenerated once approved.
|
|
14
|
+
// 2. STORYBOARD SHEET — one grid image per part, written with an explicit
|
|
15
|
+
// block grammar ([SCENE PACKET], [IDENTITY CONSISTENCY], [SPATIAL
|
|
16
|
+
// CONTINUITY LOCK], [DIRECTOR STRIP], …). Cheap, and it is where you
|
|
17
|
+
// catch a bad sequence BEFORE paying for video.
|
|
18
|
+
// 3. SHOT PROMPT — the video-model prompt. It cites the character card as
|
|
19
|
+
// the identity authority and the board as the staging authority, then
|
|
20
|
+
// restates the panels as PANEL BEATS so the model can't drift off order.
|
|
21
|
+
//
|
|
22
|
+
// Two rules carry most of the quality and are enforced by the emitters below:
|
|
23
|
+
// • REFERENCE PRIORITY — identity reference controls identity; the
|
|
24
|
+
// storyboard controls staging/motion/geography/continuity. Stating the
|
|
25
|
+
// split stops the model from copying a face off the board's rough panels.
|
|
26
|
+
// • "Do not render the storyboard sheet itself" — without it, video models
|
|
27
|
+
// happily animate the grid, borders and all.
|
|
28
|
+
export const SEQUENCE_PLAN_VERSION = 1;
|
|
29
|
+
// ── small helpers ───────────────────────────────────────────────────────────
|
|
30
|
+
export function panelId(n) {
|
|
31
|
+
return `P${String(n).padStart(2, "0")}`;
|
|
32
|
+
}
|
|
33
|
+
function arrow(items) {
|
|
34
|
+
return items.filter(Boolean).join(" -> ");
|
|
35
|
+
}
|
|
36
|
+
function block(name, body) {
|
|
37
|
+
return `[${name}]\n${body}`;
|
|
38
|
+
}
|
|
39
|
+
/** "part-03" → 3. Falls back to 1 so a hand-written id never crashes an emit. */
|
|
40
|
+
export function partIndex(part) {
|
|
41
|
+
const m = /(\d+)\s*$/.exec(part.id);
|
|
42
|
+
return m ? Number(m[1]) : 1;
|
|
43
|
+
}
|
|
44
|
+
// ── 1. character card ───────────────────────────────────────────────────────
|
|
45
|
+
/**
|
|
46
|
+
* Identity sheet for one character. Deliberately close to the shared
|
|
47
|
+
* CHARACTER_CARD_PROMPT_SNIPPET used by /tools/image, but pinned to the plan's
|
|
48
|
+
* style lock so the card and the final video agree on art style — a card in a
|
|
49
|
+
* different style is the most common cause of drift at the video step.
|
|
50
|
+
*/
|
|
51
|
+
export function buildCharacterCardPrompt(character, plan) {
|
|
52
|
+
return [
|
|
53
|
+
`CHARACTER REFERENCE SHEET for ${character.name} (${character.code}).`,
|
|
54
|
+
`SUBJECT: ${character.appearance} Wardrobe: ${character.wardrobe} Movement quality: ${character.movement_quality}`,
|
|
55
|
+
`ART STYLE (must match the final film): ${plan.style_lock}`,
|
|
56
|
+
"LAYOUT: one single consistent character on a plain neutral light-gray studio background, laid out as a clean labeled grid — " +
|
|
57
|
+
"BODY SHOTS (full body, three-quarter, half body, head-and-shoulders neutral expression); " +
|
|
58
|
+
"a TURNAROUND row (front, side profile, back); " +
|
|
59
|
+
"an EXPRESSION SHEET of face close-ups (neutral, determined, joyful, strained); " +
|
|
60
|
+
"and a POSE SHEET of 3 dynamic action poses drawn from this character's movement quality.",
|
|
61
|
+
"CONSISTENCY: the SAME face, hairstyle, wardrobe, colors, body proportions and art style in EVERY view. " +
|
|
62
|
+
"Even flat studio lighting. Small text label under each view. No background scenery, no props beyond the character's own key prop, no logos, no watermark.",
|
|
63
|
+
"This sheet is an identity authority for downstream shots: prioritize readable face structure, silhouette and costume detail over mood or composition."
|
|
64
|
+
].join("\n\n");
|
|
65
|
+
}
|
|
66
|
+
// ── 2. storyboard sheet ─────────────────────────────────────────────────────
|
|
67
|
+
/**
|
|
68
|
+
* The block-grammar storyboard prompt. Every block earns its place:
|
|
69
|
+
* • PROJECT CARD / CONTINUITY HEADER — masthead + the reference-priority split.
|
|
70
|
+
* • SCENE PACKET — premise, geography, start→end, action chain, prop state.
|
|
71
|
+
* • CHARACTER SANITIZATION — strips un-drawable psychology and backstory.
|
|
72
|
+
* • IDENTITY CONSISTENCY / SPATIAL CONTINUITY LOCK — the two anti-drift rules.
|
|
73
|
+
* • STORYBOARD PURITY — keeps labels OUT of the panel art (the video model
|
|
74
|
+
* will animate any text it sees inside a frame).
|
|
75
|
+
* • DIRECTOR STRIP — rhythm/escalation tracks under the grid; this is what
|
|
76
|
+
* makes the board readable as timing rather than as nine unrelated stills.
|
|
77
|
+
*/
|
|
78
|
+
export function buildStoryboardPrompt(part, plan) {
|
|
79
|
+
const panels = part.panels;
|
|
80
|
+
const chars = plan.characters;
|
|
81
|
+
const purity = plan.board_style === "final"
|
|
82
|
+
? "Panel images are finished-look frames rendered in the final film style (this board doubles as a style fit-check). " +
|
|
83
|
+
"Put panel numbers, beat names and lens tags in the header strip OUTSIDE each panel image. " +
|
|
84
|
+
"No labels, arrows, captions, subtitles, logos, watermarks, timing marks, diagrams, UI, ghost poses, duplicate bodies or technical overlays INSIDE panels."
|
|
85
|
+
: "Panel images are visual-only low-detail monochrome light-gray rough sketches: fast gesture-drawing energy, simple anatomy construction, strong silhouette readability, unfinished previz feel. " +
|
|
86
|
+
"Put panel numbers, beat names and lens tags in the header strip OUTSIDE each panel image. " +
|
|
87
|
+
"No color, labels, arrows, captions, subtitles, logos, watermarks, timing marks, diagrams, UI, ghost poses, duplicate bodies or technical overlays INSIDE panels.";
|
|
88
|
+
return [
|
|
89
|
+
`Create a ${plan.aspect_ratio} image.`,
|
|
90
|
+
block("PROJECT CARD", [
|
|
91
|
+
"Create a compact designed masthead, not a table.",
|
|
92
|
+
`TITLE: ${part.title.toUpperCase()}`,
|
|
93
|
+
`META LINE: ${part.meta_line}`,
|
|
94
|
+
`PRIORITY: ${part.priority}`,
|
|
95
|
+
`MICRO BRIEF: ${part.micro_brief}`
|
|
96
|
+
].join("\n")),
|
|
97
|
+
block("CONTINUITY HEADER", [
|
|
98
|
+
`SEQUENCE ID: ${part.sequence_id}`,
|
|
99
|
+
"REFERENCE PRIORITY: identity reference controls identity; this storyboard controls staging, motion, geography, continuity"
|
|
100
|
+
].join("\n")),
|
|
101
|
+
block("SCENE PACKET", [
|
|
102
|
+
`PREMISE: ${part.premise}`,
|
|
103
|
+
`LOCATION: ${part.location}`,
|
|
104
|
+
`START -> END: ${part.start_end}`,
|
|
105
|
+
`ACTION CHAIN: ${arrow(part.action_chain)}`,
|
|
106
|
+
`PROP / EFFECT STATE: ${part.prop_effect_state}`,
|
|
107
|
+
`MUST READ: ${part.must_read}`
|
|
108
|
+
].join("\n")),
|
|
109
|
+
block("CHARACTER SANITIZATION", [
|
|
110
|
+
...chars.map((c) => `${c.code}: ${c.appearance} ${c.wardrobe} Movement quality: ${c.movement_quality}`),
|
|
111
|
+
"Remove contradictory traits, invisible psychology, excessive costume detail, and backstory that cannot appear in a panel."
|
|
112
|
+
].join("\n")),
|
|
113
|
+
block("IDENTITY CONSISTENCY", [
|
|
114
|
+
"Identity reference controls face, body, wardrobe, and proportions.",
|
|
115
|
+
`Keep ${chars.map((c) => c.code).join(", ")} on-model — silhouette, hair, wardrobe and key props consistent across all panels.`,
|
|
116
|
+
"Do not redesign, age-shift, or merge characters. Hold screen direction; do not flip travel direction between panels."
|
|
117
|
+
].join("\n")),
|
|
118
|
+
block("STORYBOARD PURITY", purity),
|
|
119
|
+
block("MASTER SHOT RULE", `Panel 01 is the master: ${part.master_shot}`),
|
|
120
|
+
block("EMOTIONAL ARC", part.emotional_arc),
|
|
121
|
+
block("STYLE LOCKS", [
|
|
122
|
+
`STYLE LOCK: final video style is ${plan.style_lock}; ${plan.board_style === "final"
|
|
123
|
+
? "render panels in that same style"
|
|
124
|
+
: "inside storyboard panels render only monochrome light-gray rough sketch"}.`,
|
|
125
|
+
`EFFECT LOCK: ${plan.effect_lock}`,
|
|
126
|
+
`ENVIRONMENT LOCK: ${plan.environment_lock}`
|
|
127
|
+
].join("\n")),
|
|
128
|
+
block("SPATIAL CONTINUITY LOCK", part.spatial_continuity_lock),
|
|
129
|
+
block("DIRECTOR STRIP", [
|
|
130
|
+
"Bottom animatic track board aligned to panel columns. Tracks: BEAT LINE, CAMERA PATH, ACTION PATH, RHYTHM TRACK, ESCALATION MAP, STATE TRACK, STYLE TRACK.",
|
|
131
|
+
"Use shot chips, thin lines, rhythm blocks, small intensity bars, one-to-three-word labels. No seconds or timestamps.",
|
|
132
|
+
"RHYTHM TRACK format: `RHY P##: [hold|slow reveal|build|burst|impact|pause|recover|final hit] / [short block|medium block|long block] / [clean beat|match beat|smash beat|held beat|whip beat]`.",
|
|
133
|
+
"ESCALATION MAP format: `ESC P##: [L1 calm|L2 tension|L3 rise|L4 surge|L5 peak] / [flat|rise|spike|drop|release|unresolved]`.",
|
|
134
|
+
`PANEL HEADERS: ${arrow(panels.map((p) => `${panelId(p.n)} / ${p.lens} / ${p.beat}`))}`
|
|
135
|
+
].join("\n")),
|
|
136
|
+
`CAMERA + LENS PLAN: ${arrow(panels.map((p) => `${panelId(p.n)} ${p.camera}`))}`,
|
|
137
|
+
`ACTION PATH: ${arrow(panels.map((p) => `${panelId(p.n)} ${p.action}`))}`,
|
|
138
|
+
`RHYTHM TRACK: ${arrow(panels.map((p) => `${panelId(p.n)} RHY ${panelId(p.n)}: ${p.rhythm.kind} / ${p.rhythm.block} / ${p.rhythm.beat}`))}`,
|
|
139
|
+
`ESCALATION MAP: ${arrow(panels.map((p) => `${panelId(p.n)} ESC ${panelId(p.n)}: ${p.escalation.level} / ${p.escalation.shape}`))}`,
|
|
140
|
+
`STATE TRACK: ${arrow(panels.map((p) => `${panelId(p.n)} ${p.state}`))}`,
|
|
141
|
+
`STYLE TRACK: ${arrow(panels.map((p) => `${panelId(p.n)} ${p.style}`))}`,
|
|
142
|
+
block("SEQUENCE", `Grid: ${panels.length} panels in a ${part.grid.cols}x${part.grid.rows} grid; read left-to-right, top-to-bottom as one continuous sequence.`)
|
|
143
|
+
].join("\n\n");
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* The video-model prompt. Ordered the way the models actually weight input:
|
|
147
|
+
* authority statements first, then hard negatives, then look, then the beat
|
|
148
|
+
* list. PANEL BEATS restates the board in words so the sequence survives even
|
|
149
|
+
* when the model reads the board loosely.
|
|
150
|
+
*/
|
|
151
|
+
export function buildShotPrompt(part, plan, refs) {
|
|
152
|
+
const lines = [];
|
|
153
|
+
if (part.extend_from) {
|
|
154
|
+
lines.push(`extend ${refs.extendFromUrl ? `@[${refs.extendFromUrl}]` : `@[${part.extend_from} video]`} by ${part.duration_seconds}s, continuing the same shot world without restating the setup.`);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
lines.push(`Create a ${part.duration_seconds}-second cinematic sequence.`);
|
|
158
|
+
}
|
|
159
|
+
lines.push(`Use ${refs.boardUrl ? `@[${refs.boardUrl}]` : "@[storyboard reference]"} as the authoritative director-approved storyboard blueprint for this sequence. ` +
|
|
160
|
+
"Treat every storyboard panel as a consecutive shot within a single continuous sequence. Follow panel order exactly and do not invent alternative coverage. " +
|
|
161
|
+
"DO NOT RENDER THE STORYBOARD SHEET ITSELF — recreate the filmed sequence implied by the panels, not the physical board artwork, and never treat the board as a single image. " +
|
|
162
|
+
"Preserve camera placement, framing, lens intent, shot scale, character staging, screen direction, environmental geography, prop placement, action choreography, continuity and emotional escalation shown by the board. The storyboard is the primary source of truth for visual storytelling.");
|
|
163
|
+
for (const card of refs.characterCards) {
|
|
164
|
+
lines.push(`Use ${card.url ? `@[${card.url}]` : `@[${card.name} character card]`} as the fixed character-sheet reference for ${card.name} (${card.code}). ` +
|
|
165
|
+
"The character must strictly match the sheet: face, hair, proportions, wardrobe and art style. Do not redesign the character, change the costume, or alter the face.");
|
|
166
|
+
}
|
|
167
|
+
lines.push(`VISUAL STYLE: ${plan.style_lock}`);
|
|
168
|
+
lines.push(`ENVIRONMENT: ${part.location} ${plan.environment_lock}`);
|
|
169
|
+
lines.push(`EFFECTS: ${plan.effect_lock}`);
|
|
170
|
+
lines.push(`EMOTIONAL GUIDANCE: ${part.emotional_arc} Camera rhythm follows the board's escalation map; the piece must read as ${part.priority}.`);
|
|
171
|
+
lines.push(`AUDIO: ${plan.audio_lock}`);
|
|
172
|
+
if (plan.laban) {
|
|
173
|
+
lines.push([
|
|
174
|
+
"Use Laban movement logic throughout:",
|
|
175
|
+
`weight: ${plan.laban.weight}`,
|
|
176
|
+
`time: ${plan.laban.time}`,
|
|
177
|
+
`space: ${plan.laban.space}`,
|
|
178
|
+
`flow: ${plan.laban.flow}`
|
|
179
|
+
].join("\n"));
|
|
180
|
+
}
|
|
181
|
+
lines.push("PANEL BEATS:\n" +
|
|
182
|
+
part.panels.map((p) => `${panelId(p.n)}: ${p.camera}. ${p.action}. ${p.state}.`).join("\n"));
|
|
183
|
+
lines.push("Do not add text, captions, storyboard labels, arrows, panel borders, grids, UI, logos, subtitles or watermarks. " +
|
|
184
|
+
"Do not cut to unrelated coverage, add characters that are not in the board, or resolve the sequence early.");
|
|
185
|
+
return lines.join("\n\n");
|
|
186
|
+
}
|
|
187
|
+
// ── deterministic starter plan (headless / --auto path) ─────────────────────
|
|
188
|
+
const DEFAULT_RHYTHM = [
|
|
189
|
+
{ kind: "hold", block: "long block", beat: "held beat" },
|
|
190
|
+
{ kind: "slow reveal", block: "medium block", beat: "clean beat" },
|
|
191
|
+
{ kind: "build", block: "medium block", beat: "clean beat" },
|
|
192
|
+
{ kind: "burst", block: "short block", beat: "match beat" },
|
|
193
|
+
{ kind: "build", block: "medium block", beat: "whip beat" },
|
|
194
|
+
{ kind: "impact", block: "short block", beat: "smash beat" },
|
|
195
|
+
{ kind: "recover", block: "short block", beat: "held beat" },
|
|
196
|
+
{ kind: "final hit", block: "long block", beat: "whip beat" }
|
|
197
|
+
];
|
|
198
|
+
const DEFAULT_ESCALATION = [
|
|
199
|
+
{ level: "L1 calm", shape: "flat" },
|
|
200
|
+
{ level: "L2 tension", shape: "rise" },
|
|
201
|
+
{ level: "L3 rise", shape: "rise" },
|
|
202
|
+
{ level: "L3 rise", shape: "spike" },
|
|
203
|
+
{ level: "L4 surge", shape: "rise" },
|
|
204
|
+
{ level: "L5 peak", shape: "spike" },
|
|
205
|
+
{ level: "L4 surge", shape: "drop" },
|
|
206
|
+
{ level: "L5 peak", shape: "release" }
|
|
207
|
+
];
|
|
208
|
+
const DEFAULT_LENSES = [
|
|
209
|
+
"24mm wide", "85mm portrait", "macro insert", "50mm", "35mm", "24mm wide", "50mm", "24mm wide",
|
|
210
|
+
"35mm", "macro insert", "overhead", "24mm wide"
|
|
211
|
+
];
|
|
212
|
+
/** Panel-count → the grid the board should be laid out on. */
|
|
213
|
+
export function gridForPanelCount(count) {
|
|
214
|
+
if (count <= 4)
|
|
215
|
+
return { cols: 2, rows: 2 };
|
|
216
|
+
if (count <= 6)
|
|
217
|
+
return { cols: 3, rows: 2 };
|
|
218
|
+
if (count <= 9)
|
|
219
|
+
return { cols: 3, rows: 3 };
|
|
220
|
+
if (count <= 12)
|
|
221
|
+
return { cols: 4, rows: 3 };
|
|
222
|
+
return { cols: 4, rows: Math.ceil(count / 4) };
|
|
223
|
+
}
|
|
224
|
+
export function slugify(value) {
|
|
225
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "sequence";
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* A structurally-complete plan built from the brief alone. It is intentionally
|
|
229
|
+
* generic in its prose — the point is that every required field exists and every
|
|
230
|
+
* emitter produces a valid prompt, so `sequence run` works with no agent in the
|
|
231
|
+
* loop. `sequence plan` (agent-assisted) overwrites this with real direction.
|
|
232
|
+
*/
|
|
233
|
+
export function buildStarterPlan(opts) {
|
|
234
|
+
const brief = opts.brief.trim();
|
|
235
|
+
const title = (opts.title ?? brief.split(/[.!?\n]/)[0] ?? brief).trim().slice(0, 70) || "Untitled sequence";
|
|
236
|
+
const partCount = Math.max(1, Math.min(6, opts.parts ?? 1));
|
|
237
|
+
const panelCount = Math.max(4, Math.min(16, opts.panels ?? 8));
|
|
238
|
+
// Short, human-readable continuity stem (BREE-S01-…), not the whole title.
|
|
239
|
+
const stem = slugify(title).split("-").filter((w) => w.length > 2).slice(0, 2).join("-").toUpperCase() || "SEQ";
|
|
240
|
+
const characterName = opts.characterName ?? "Lead";
|
|
241
|
+
const characters = [{
|
|
242
|
+
slug: slugify(characterName),
|
|
243
|
+
code: "C1",
|
|
244
|
+
name: characterName,
|
|
245
|
+
appearance: opts.characterDescription
|
|
246
|
+
?? "Single lead subject with a clear, readable silhouette; consistent face and proportions across every panel.",
|
|
247
|
+
wardrobe: "One consistent outfit throughout; no costume changes between panels.",
|
|
248
|
+
movement_quality: "Purposeful and physically grounded; weight visible in every pose."
|
|
249
|
+
}];
|
|
250
|
+
const parts = Array.from({ length: partCount }, (_unused, i) => {
|
|
251
|
+
const n = i + 1;
|
|
252
|
+
const panels = Array.from({ length: panelCount }, (_p, j) => ({
|
|
253
|
+
n: j + 1,
|
|
254
|
+
lens: DEFAULT_LENSES[j % DEFAULT_LENSES.length],
|
|
255
|
+
beat: j === 0 ? "Master" : j === panelCount - 1 ? "Final beat" : `Beat ${j + 1}`,
|
|
256
|
+
camera: j === 0
|
|
257
|
+
? "High wide master establishing the full geography of the location"
|
|
258
|
+
: `${DEFAULT_LENSES[j % DEFAULT_LENSES.length]} coverage continuing the same action`,
|
|
259
|
+
action: j === 0
|
|
260
|
+
? `${characters[0].code} is introduced in the situation described by the brief`
|
|
261
|
+
: j === panelCount - 1
|
|
262
|
+
? `${characters[0].code} lands the final beat of the sequence`
|
|
263
|
+
: `${characters[0].code} escalates the action one clear step`,
|
|
264
|
+
rhythm: DEFAULT_RHYTHM[Math.min(j, DEFAULT_RHYTHM.length - 1)],
|
|
265
|
+
escalation: DEFAULT_ESCALATION[Math.min(j, DEFAULT_ESCALATION.length - 1)],
|
|
266
|
+
state: "Continuity holds: same location, same wardrobe, same travel direction as the previous panel",
|
|
267
|
+
style: "Consistent lighting and palette with the panel before it"
|
|
268
|
+
}));
|
|
269
|
+
return {
|
|
270
|
+
id: `part-${String(n).padStart(2, "0")}`,
|
|
271
|
+
sequence_id: `${stem}-S${String(n).padStart(2, "0")}`,
|
|
272
|
+
title: partCount === 1 ? title : `${title} — part ${n}`,
|
|
273
|
+
meta_line: "single continuous sequence / escalating action / clear start-to-finish read",
|
|
274
|
+
priority: "one readable escalation from the opening state to the final beat",
|
|
275
|
+
micro_brief: brief,
|
|
276
|
+
premise: brief,
|
|
277
|
+
location: "The single location implied by the brief, held for the whole part; no location changes mid-sequence.",
|
|
278
|
+
start_end: "Opening state established in panel 01 -> the sequence's strongest beat in the final panel.",
|
|
279
|
+
action_chain: [
|
|
280
|
+
"establish the situation",
|
|
281
|
+
"the first change lands",
|
|
282
|
+
"the action escalates",
|
|
283
|
+
"the strongest beat hits",
|
|
284
|
+
"the sequence resolves or exits on forward motion"
|
|
285
|
+
],
|
|
286
|
+
prop_effect_state: "Key props stay physically consistent across panels; effects grow with the escalation map.",
|
|
287
|
+
must_read: "The escalation must read without any text on screen.",
|
|
288
|
+
master_shot: "full geography of the location with the lead subject clearly placed inside it.",
|
|
289
|
+
emotional_arc: "settled -> alert -> committed -> peak -> release; shown only through posture, pace and framing.",
|
|
290
|
+
spatial_continuity_lock: "All panels share one location and one screen direction. Allowed changes between panels: camera distance, camera height, subject pose and position, effect intensity. Not allowed: new locations, flipped travel direction, redesigned props.",
|
|
291
|
+
grid: gridForPanelCount(panelCount),
|
|
292
|
+
duration_seconds: Math.max(4, Math.min(30, opts.durationSeconds ?? 15)),
|
|
293
|
+
panels,
|
|
294
|
+
...(n > 1 ? { extend_from: `part-${String(n - 1).padStart(2, "0")}` } : {})
|
|
295
|
+
};
|
|
296
|
+
});
|
|
297
|
+
return {
|
|
298
|
+
version: SEQUENCE_PLAN_VERSION,
|
|
299
|
+
title,
|
|
300
|
+
brief,
|
|
301
|
+
aspect_ratio: opts.aspectRatio ?? "16:9",
|
|
302
|
+
style_lock: opts.style
|
|
303
|
+
?? "stylized cinematic realism, rich cinematic lighting, controlled color palette, natural motion blur, premium feature-film aesthetic",
|
|
304
|
+
effect_lock: "Effects are motivated by physical movement and stay grounded in the environment; no glow, no superhero energy, no motion-blur smears.",
|
|
305
|
+
environment_lock: "Hold one consistent environment, light direction and palette for the whole part; no texture or style drift.",
|
|
306
|
+
audio_lock: "No background music or score. Diegetic ambience, foley, impacts, texture and silence only.",
|
|
307
|
+
board_style: opts.boardStyle ?? "rough",
|
|
308
|
+
...(opts.laban === false ? {} : {
|
|
309
|
+
laban: {
|
|
310
|
+
weight: "strong and grounded on impacts, brief lightness through jumps and lifts",
|
|
311
|
+
time: "quick through strikes, turns and drops; sustained through holds and recoveries",
|
|
312
|
+
space: "direct during committed action; indirect during turns and transitions",
|
|
313
|
+
flow: "bound in rooted stances and precise beats; free in aerial and release moments"
|
|
314
|
+
}
|
|
315
|
+
}),
|
|
316
|
+
characters,
|
|
317
|
+
parts
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
// ── plan validation ─────────────────────────────────────────────────────────
|
|
321
|
+
/** Structural check that every emitter's required field is present. */
|
|
322
|
+
export function validatePlan(plan) {
|
|
323
|
+
const errors = [];
|
|
324
|
+
const p = plan;
|
|
325
|
+
if (!p || typeof p !== "object")
|
|
326
|
+
return { ok: false, errors: ["plan is not an object"] };
|
|
327
|
+
if (p.version !== SEQUENCE_PLAN_VERSION)
|
|
328
|
+
errors.push(`version must be ${SEQUENCE_PLAN_VERSION}`);
|
|
329
|
+
for (const key of ["title", "brief", "aspect_ratio", "style_lock", "environment_lock", "audio_lock"]) {
|
|
330
|
+
if (!p[key] || typeof p[key] !== "string")
|
|
331
|
+
errors.push(`missing ${key}`);
|
|
332
|
+
}
|
|
333
|
+
if (!Array.isArray(p.characters) || p.characters.length === 0)
|
|
334
|
+
errors.push("characters must be a non-empty array");
|
|
335
|
+
else {
|
|
336
|
+
const codes = new Set();
|
|
337
|
+
for (const c of p.characters) {
|
|
338
|
+
if (!c?.code || !c?.name || !c?.slug)
|
|
339
|
+
errors.push("each character needs slug, code and name");
|
|
340
|
+
else if (codes.has(c.code))
|
|
341
|
+
errors.push(`duplicate character code ${c.code}`);
|
|
342
|
+
else
|
|
343
|
+
codes.add(c.code);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
if (!Array.isArray(p.parts) || p.parts.length === 0)
|
|
347
|
+
errors.push("parts must be a non-empty array");
|
|
348
|
+
else {
|
|
349
|
+
const ids = new Set();
|
|
350
|
+
for (const part of p.parts) {
|
|
351
|
+
const where = part?.id ?? "<unnamed part>";
|
|
352
|
+
if (!part?.id)
|
|
353
|
+
errors.push("each part needs an id");
|
|
354
|
+
else if (ids.has(part.id))
|
|
355
|
+
errors.push(`duplicate part id ${part.id}`);
|
|
356
|
+
else
|
|
357
|
+
ids.add(part.id);
|
|
358
|
+
if (!Array.isArray(part?.panels) || part.panels.length < 2)
|
|
359
|
+
errors.push(`${where}: needs at least 2 panels`);
|
|
360
|
+
else {
|
|
361
|
+
part.panels.forEach((panel, i) => {
|
|
362
|
+
if (panel?.n !== i + 1)
|
|
363
|
+
errors.push(`${where}: panel ${i + 1} has n=${panel?.n}; panels must be numbered 1..N in order`);
|
|
364
|
+
for (const key of ["lens", "beat", "camera", "action", "state", "style"]) {
|
|
365
|
+
if (!panel?.[key])
|
|
366
|
+
errors.push(`${where}: panel ${i + 1} missing ${key}`);
|
|
367
|
+
}
|
|
368
|
+
if (!panel?.rhythm?.kind || !panel?.escalation?.level)
|
|
369
|
+
errors.push(`${where}: panel ${i + 1} missing rhythm/escalation`);
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
if (part?.extend_from && !ids.has(part.extend_from)) {
|
|
373
|
+
errors.push(`${where}: extend_from "${part.extend_from}" must name an EARLIER part`);
|
|
374
|
+
}
|
|
375
|
+
if (!part?.duration_seconds || part.duration_seconds <= 0)
|
|
376
|
+
errors.push(`${where}: duration_seconds must be > 0`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return { ok: errors.length === 0, errors };
|
|
380
|
+
}
|
|
381
|
+
// ── agent task file (the "plan" step) ───────────────────────────────────────
|
|
382
|
+
/**
|
|
383
|
+
* Task doc staged into the project folder so a desktop coding agent (Claude
|
|
384
|
+
* Code / Codex) can upgrade the starter plan into real direction. Same model as
|
|
385
|
+
* `vidfarm decompose --local`: the devcli owns the deterministic + billed
|
|
386
|
+
* steps, the agent owns the writing.
|
|
387
|
+
*/
|
|
388
|
+
export function buildPlanTaskMarkdown(plan, planPath) {
|
|
389
|
+
return `# Sequence plan task — ${plan.title}
|
|
390
|
+
|
|
391
|
+
You are the director. Rewrite \`${planPath}\` in place so it stops being a
|
|
392
|
+
skeleton and becomes real direction for this brief:
|
|
393
|
+
|
|
394
|
+
> ${plan.brief}
|
|
395
|
+
|
|
396
|
+
Then stop. Do not generate images or video — \`vidfarm sequence\` does that.
|
|
397
|
+
|
|
398
|
+
## What you are writing
|
|
399
|
+
|
|
400
|
+
\`sequence.json\` drives three generated prompts, so every field is load-bearing:
|
|
401
|
+
|
|
402
|
+
| Plan field | Ends up in |
|
|
403
|
+
|---|---|
|
|
404
|
+
| \`characters[]\` | the CHARACTER CARD image prompt (one identity sheet each) |
|
|
405
|
+
| \`parts[].*\` + \`parts[].panels[]\` | the STORYBOARD SHEET image prompt (block grammar) |
|
|
406
|
+
| \`style_lock\`, \`effect_lock\`, \`environment_lock\`, \`audio_lock\`, \`laban\` | both the board and the video prompt |
|
|
407
|
+
| \`parts[].panels[]\` again | the video model's PANEL BEATS list |
|
|
408
|
+
|
|
409
|
+
Preview any of them without spending a credit:
|
|
410
|
+
|
|
411
|
+
\`\`\`bash
|
|
412
|
+
vidfarm sequence prompts --dir . --print board:part-01
|
|
413
|
+
vidfarm sequence prompts --dir . --print shot:part-01
|
|
414
|
+
vidfarm sequence prompts --dir . --print card:<character-slug>
|
|
415
|
+
\`\`\`
|
|
416
|
+
|
|
417
|
+
## Rules that carry the quality
|
|
418
|
+
|
|
419
|
+
1. **Panels are motion, not mood.** Every \`action\` must describe a body or a
|
|
420
|
+
camera doing something. "She feels nervous" is not a panel; "she backpedals
|
|
421
|
+
and plants, keeping the blade between them" is.
|
|
422
|
+
2. **One location per part.** Write \`spatial_continuity_lock\` as an explicit
|
|
423
|
+
list of what may change between panels (camera distance/height, pose, effect
|
|
424
|
+
intensity) and what may not (location, screen direction, prop design).
|
|
425
|
+
3. **Hold screen direction.** If the subject travels left-to-right, say so in
|
|
426
|
+
\`state\` on every panel. Flipped direction is the most visible drift there is.
|
|
427
|
+
4. **Escalate deliberately.** \`escalation\` should climb to exactly one L5 peak
|
|
428
|
+
per part. A board where everything is a peak animates as noise.
|
|
429
|
+
5. **Start in motion** unless the brief wants a slow open. Panel 01 is the
|
|
430
|
+
master shot — full geography — but it can still be moving.
|
|
431
|
+
6. **Characters are codes.** Panels refer to C1/C2, never to names. Keep
|
|
432
|
+
\`appearance\` to what a drawing can show: silhouette, hair, build, wardrobe.
|
|
433
|
+
Delete backstory and psychology.
|
|
434
|
+
7. **\`audio_lock\` stays diegetic** unless the brief asks for score. Music is
|
|
435
|
+
added at the assemble step, where you control it.
|
|
436
|
+
8. **Multi-part = \`extend_from\`.** Part 2 continuing part 1 must set
|
|
437
|
+
\`extend_from: "part-01"\`; the video prompt then opens with \`extend\` instead
|
|
438
|
+
of re-establishing the world. Budget ~${plan.parts[0]?.duration_seconds ?? 15}s per part.
|
|
439
|
+
|
|
440
|
+
## Panel field cheatsheet
|
|
441
|
+
|
|
442
|
+
- \`lens\` — coverage tag: \`24mm wide\`, \`35mm\`, \`50mm\`, \`85mm portrait\`, \`macro insert\`, \`overhead\`
|
|
443
|
+
- \`camera\` — placement + move ("low side track at board level")
|
|
444
|
+
- \`action\` — what physically happens
|
|
445
|
+
- \`state\` — continuity: prop state, effect intensity, travel direction
|
|
446
|
+
- \`style\` — one-line look note ("crest backlight", "impact spray burst")
|
|
447
|
+
- \`rhythm.kind\` — hold | slow reveal | build | burst | impact | pause | recover | final hit
|
|
448
|
+
- \`rhythm.block\` — short block | medium block | long block
|
|
449
|
+
- \`rhythm.beat\` — clean beat | match beat | smash beat | held beat | whip beat
|
|
450
|
+
- \`escalation.level\` — L1 calm | L2 tension | L3 rise | L4 surge | L5 peak
|
|
451
|
+
- \`escalation.shape\` — flat | rise | spike | drop | release | unresolved
|
|
452
|
+
|
|
453
|
+
## When you are done
|
|
454
|
+
|
|
455
|
+
\`\`\`bash
|
|
456
|
+
vidfarm sequence plan --dir . --check # structural validation
|
|
457
|
+
\`\`\`
|
|
458
|
+
|
|
459
|
+
Fix anything it reports, then hand back to the user (or run
|
|
460
|
+
\`vidfarm sequence run --dir .\` to execute the whole pipeline).
|
|
461
|
+
`;
|
|
462
|
+
}
|
|
463
|
+
//# sourceMappingURL=sequence-prompts.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@officexapp/vidfarm-devcli",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.27",
|
|
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": {
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"dist/src/devcli/local-render.js",
|
|
25
25
|
"dist/src/devcli/port-utils.js",
|
|
26
26
|
"dist/src/devcli/process-scan.js",
|
|
27
|
+
"dist/src/devcli/sequence.js",
|
|
27
28
|
"dist/src/devcli/skills.js",
|
|
28
29
|
"dist/src/devcli/speech.js",
|
|
29
30
|
"dist/src/devcli/stills.js",
|
|
@@ -35,6 +36,7 @@
|
|
|
35
36
|
"dist/src/services/captions.js",
|
|
36
37
|
"dist/src/services/composition-lint.js",
|
|
37
38
|
"dist/src/services/provider-errors.js",
|
|
39
|
+
"dist/src/services/sequence-prompts.js",
|
|
38
40
|
"dist/src/services/speech.js",
|
|
39
41
|
"dist/src/services/clip-curation/**/*.js",
|
|
40
42
|
"dist/src/services/clip-curation/**/*.json",
|
|
@@ -66,7 +68,7 @@
|
|
|
66
68
|
"node": ">=22.0.0"
|
|
67
69
|
},
|
|
68
70
|
"scripts": {
|
|
69
|
-
"
|
|
71
|
+
"prepare": "patch-package || true",
|
|
70
72
|
"dev": "tsx --import ./src/instrument.ts watch src/index.ts",
|
|
71
73
|
"dev:frontend": "node scripts/build-homepage-client.mjs --watch",
|
|
72
74
|
"dev:cli": "tsx src/cli.ts",
|