@nodetool-ai/agents 0.8.4-a24.2 → 0.8.4
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/dist/.nodetool-build-stamp +1 -1
- package/dist/_skills/LICENSE-iart-motion-design-skills +21 -0
- package/dist/_skills/README.md +211 -0
- package/dist/_skills/api-agents/SKILL.md +58 -0
- package/dist/_skills/api-apps/SKILL.md +54 -0
- package/dist/_skills/api-assets/SKILL.md +66 -0
- package/dist/_skills/api-collections/SKILL.md +33 -0
- package/dist/_skills/api-games/SKILL.md +81 -0
- package/dist/_skills/api-media/SKILL.md +131 -0
- package/dist/_skills/api-memory/SKILL.md +72 -0
- package/dist/_skills/api-models/SKILL.md +78 -0
- package/dist/_skills/api-scripts/SKILL.md +79 -0
- package/dist/_skills/api-settings/SKILL.md +49 -0
- package/dist/_skills/api-sketches/SKILL.md +61 -0
- package/dist/_skills/api-storyboards/SKILL.md +94 -0
- package/dist/_skills/api-timelines/SKILL.md +151 -0
- package/dist/_skills/api-web/SKILL.md +57 -0
- package/dist/_skills/api-workflows/SKILL.md +123 -0
- package/dist/_skills/beat-sync-editing/SKILL.md +208 -0
- package/dist/_skills/caption-titles/SKILL.md +94 -0
- package/dist/_skills/color-motion/SKILL.md +185 -0
- package/dist/_skills/commercial-beat-sheet/SKILL.md +338 -0
- package/dist/_skills/elevenlabs-audio-prompting/SKILL.md +163 -0
- package/dist/_skills/explainer-storyboard/SKILL.md +63 -0
- package/dist/_skills/flux-2-klein-prompting/SKILL.md +107 -0
- package/dist/_skills/frame-composition/SKILL.md +291 -0
- package/dist/_skills/game-direction/SKILL.md +99 -0
- package/dist/_skills/gpt-image-2-prompting/SKILL.md +161 -0
- package/dist/_skills/hailuo-prompting/SKILL.md +107 -0
- package/dist/_skills/kling-video-prompting/SKILL.md +188 -0
- package/dist/_skills/launch-commercial/SKILL.md +267 -0
- package/dist/_skills/launch-kit/SKILL.md +120 -0
- package/dist/_skills/logo-reveal/SKILL.md +225 -0
- package/dist/_skills/manimgx/SKILL.md +123 -0
- package/dist/_skills/minimax-h3-prompting/SKILL.md +134 -0
- package/dist/_skills/motion-ad/SKILL.md +281 -0
- package/dist/_skills/motion-background/SKILL.md +145 -0
- package/dist/_skills/motion-curves/SKILL.md +302 -0
- package/dist/_skills/motion-direction/SKILL.md +259 -0
- package/dist/_skills/motion-graphics/SKILL.md +217 -0
- package/dist/_skills/motion-principles/SKILL.md +255 -0
- package/dist/_skills/music-video-treatment/SKILL.md +40 -0
- package/dist/_skills/nano-banana-pro-prompting/SKILL.md +133 -0
- package/dist/_skills/native-game/SKILL.md +310 -0
- package/dist/_skills/native-game-reference/SKILL.md +879 -0
- package/dist/_skills/nodetool-3d-scene/SKILL.md +110 -0
- package/dist/_skills/nodetool-api-reference/SKILL.md +338 -0
- package/dist/_skills/nodetool-app-builder/SKILL.md +291 -0
- package/dist/_skills/nodetool-browser-agent/SKILL.md +119 -0
- package/dist/_skills/nodetool-chat-cli/SKILL.md +184 -0
- package/dist/_skills/nodetool-custom-node-developer/SKILL.md +294 -0
- package/dist/_skills/nodetool-deployment/SKILL.md +195 -0
- package/dist/_skills/nodetool-js-scripting/SKILL.md +223 -0
- package/dist/_skills/nodetool-model-provider-config/SKILL.md +348 -0
- package/dist/_skills/nodetool-rag-indexing/SKILL.md +153 -0
- package/dist/_skills/nodetool-sketch/SKILL.md +138 -0
- package/dist/_skills/nodetool-skill-author/SKILL.md +125 -0
- package/dist/_skills/nodetool-troubleshooter/SKILL.md +370 -0
- package/dist/_skills/nodetool-video-post/SKILL.md +149 -0
- package/dist/_skills/nodetool-workflow-builder/SKILL.md +295 -0
- package/dist/_skills/product-commercial/SKILL.md +132 -0
- package/dist/_skills/qwen-image-prompting/SKILL.md +122 -0
- package/dist/_skills/script-video/SKILL.md +106 -0
- package/dist/_skills/seedance-2-prompting/SKILL.md +194 -0
- package/dist/_skills/seedream-prompting/SKILL.md +117 -0
- package/dist/_skills/short-film/SKILL.md +123 -0
- package/dist/_skills/stable-audio-prompting/SKILL.md +130 -0
- package/dist/_skills/storyboard-core/SKILL.md +716 -0
- package/dist/_skills/timeline-edit-ops/SKILL.md +736 -0
- package/dist/_skills/trailer-template/SKILL.md +291 -0
- package/dist/_skills/ugc-video/SKILL.md +186 -0
- package/dist/_skills/veo-3-prompting/SKILL.md +140 -0
- package/dist/_skills/video-audio-continuity/SKILL.md +90 -0
- package/dist/_skills/video-clone/SKILL.md +96 -0
- package/dist/_skills/video-workflow/SKILL.md +119 -0
- package/dist/_skills/wan-2-6-prompting/SKILL.md +107 -0
- package/dist/app-build/bridge.d.ts.map +1 -1
- package/dist/app-build/bridge.js +44 -17
- package/dist/app-build/bridge.js.map +1 -1
- package/dist/capabilities/apps.d.ts.map +1 -1
- package/dist/capabilities/apps.js +55 -13
- package/dist/capabilities/apps.js.map +1 -1
- package/dist/capabilities/apps.specs.d.ts.map +1 -1
- package/dist/capabilities/apps.specs.js +5 -3
- package/dist/capabilities/apps.specs.js.map +1 -1
- package/dist/capabilities/game-asset-browser.d.ts.map +1 -1
- package/dist/capabilities/game-asset-browser.js +6 -1
- package/dist/capabilities/game-asset-browser.js.map +1 -1
- package/dist/capabilities/game.d.ts +1 -1
- package/dist/capabilities/game.d.ts.map +1 -1
- package/dist/capabilities/game.js +71 -56
- package/dist/capabilities/game.js.map +1 -1
- package/dist/capabilities/game.specs.js +1 -1
- package/dist/capabilities/game.specs.js.map +1 -1
- package/dist/capabilities/game3d.d.ts.map +1 -1
- package/dist/capabilities/game3d.js +14 -19
- package/dist/capabilities/game3d.js.map +1 -1
- package/dist/capabilities/gameAssertions.d.ts +52 -0
- package/dist/capabilities/gameAssertions.d.ts.map +1 -0
- package/dist/capabilities/gameAssertions.js +32 -0
- package/dist/capabilities/gameAssertions.js.map +1 -0
- package/dist/capabilities/models.d.ts.map +1 -1
- package/dist/capabilities/models.js +9 -0
- package/dist/capabilities/models.js.map +1 -1
- package/dist/capabilities/storyboards.d.ts.map +1 -1
- package/dist/capabilities/storyboards.js +36 -11
- package/dist/capabilities/storyboards.js.map +1 -1
- package/dist/capabilities/storyboards.specs.d.ts.map +1 -1
- package/dist/capabilities/storyboards.specs.js +11 -1
- package/dist/capabilities/storyboards.specs.js.map +1 -1
- package/dist/capabilities/workflows.d.ts.map +1 -1
- package/dist/capabilities/workflows.js +1 -0
- package/dist/capabilities/workflows.js.map +1 -1
- package/dist/capabilities/workflows.specs.d.ts.map +1 -1
- package/dist/capabilities/workflows.specs.js +4 -3
- package/dist/capabilities/workflows.specs.js.map +1 -1
- package/dist/evals/surfaces/game.d.ts.map +1 -1
- package/dist/evals/surfaces/game.js +29 -1
- package/dist/evals/surfaces/game.js.map +1 -1
- package/dist/game-authoring.d.ts +120 -0
- package/dist/game-authoring.d.ts.map +1 -1
- package/dist/game-build-integrity.d.ts +12 -0
- package/dist/game-build-integrity.d.ts.map +1 -0
- package/dist/game-build-integrity.js +78 -0
- package/dist/game-build-integrity.js.map +1 -0
- package/dist/game-examples.d.ts +44 -3
- package/dist/game-examples.d.ts.map +1 -1
- package/dist/game-examples.js +60 -10
- package/dist/game-examples.js.map +1 -1
- package/dist/system-skills.d.ts +1 -1
- package/dist/system-skills.js +1 -1
- package/dist/tools/builtin-tools.d.ts.map +1 -1
- package/dist/tools/builtin-tools.js +2 -0
- package/dist/tools/builtin-tools.js.map +1 -1
- package/package.json +19 -19
|
@@ -0,0 +1,716 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: storyboard-core
|
|
3
|
+
description: "Use NodeTool storyboard, entity-casting, rendering, and timeline-assembly tools for video production or debugging their contracts."
|
|
4
|
+
featured: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
The shared half of every NodeTool video job. The use-case skills carry the style,
|
|
8
|
+
the shot pattern and the briefs; this carries the machinery they all assume.
|
|
9
|
+
|
|
10
|
+
Direct video on the **storyboard** surface, then finish in the **timeline**. Use a node graph when the user requests one or needs a reusable template, as
|
|
11
|
+
described in `video-workflow`.
|
|
12
|
+
|
|
13
|
+
Read the relevant part of **Tool contract** below before the first call in
|
|
14
|
+
that tool family. Use the live tool schema when it differs
|
|
15
|
+
from an example. Load other sections only when the task reaches them.
|
|
16
|
+
|
|
17
|
+
## Pick the use-case skill
|
|
18
|
+
|
|
19
|
+
| Job | Skill |
|
|
20
|
+
|---|---|
|
|
21
|
+
| Phone-shot video with a person: UGC ad, testimonial, comedy, day-in-the-life, vlog | `/ugc-video` |
|
|
22
|
+
| Product film: pack shot, hero, brand spot, with or without talent | `/product-commercial` |
|
|
23
|
+
| Sound-off motion-graphics ad from real screenshots and product photos, no generated footage | `/motion-ad` |
|
|
24
|
+
| Rebuild an existing ad or clip you were given | `/video-clone` |
|
|
25
|
+
| Voiceover drives the picture: explainer, faceless video, narrated piece | `/script-video` |
|
|
26
|
+
| Narrative with dialogue, score and a title: short film, trailer, scene | `/short-film` |
|
|
27
|
+
| A campaign, not one video: entity sheets, still set, several cuts | `/launch-kit` |
|
|
28
|
+
| A template to re-run on new inputs | `/video-workflow` |
|
|
29
|
+
| Footage already exists and needs repair: cutout, upscale, outpaint, lip sync, generated sound | `/nodetool-video-post` |
|
|
30
|
+
| A screen someone clicks, over the workflows behind a piece | `/nodetool-app-builder` |
|
|
31
|
+
|
|
32
|
+
If none fits, run the loop below directly.
|
|
33
|
+
|
|
34
|
+
Those skills say what the picture looks like. A second set says where the beats
|
|
35
|
+
go, and the two compose: load the job skill for the look and the structure
|
|
36
|
+
skill for the spine.
|
|
37
|
+
|
|
38
|
+
| The structure question | Skill |
|
|
39
|
+
|---|---|
|
|
40
|
+
| Ad beats: hook, escalation, timecodes, VO discipline | `/commercial-beat-sheet` |
|
|
41
|
+
| A product page URL in, a finished cut and its cost out | `/launch-commercial` |
|
|
42
|
+
| A 30–120s explanation with a claim per beat | `/explainer-storyboard` |
|
|
43
|
+
| A treatment cut to a track's sections | `/music-video-treatment` |
|
|
44
|
+
| Eight audio-first beats, the drop at ~65%, a title card | `/trailer-template` |
|
|
45
|
+
|
|
46
|
+
These own their own briefs and beat budgets. The storyboard, entity and render
|
|
47
|
+
call shapes live once, in **Tool contract** below. Every job and structure skill
|
|
48
|
+
points here for them.
|
|
49
|
+
|
|
50
|
+
## Two tool families, one board
|
|
51
|
+
|
|
52
|
+
| | Headless | Browser |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| Names | `create_storyboard`, `get_storyboard`, `edit_storyboard`, `direct_storyboard`, `render_storyboard_stills`, `render_storyboard_clips`, `revise_storyboard_clip`, `assemble_storyboard_timeline`, and the rest of **Tool contract** | `ui_storyboard_*`: a browser twin for most headless ops, plus `set_screenplay` (table in **Tool contract**) |
|
|
55
|
+
| Needs | nothing open | the board open as a tab, listed in the `ui_context` system block |
|
|
56
|
+
| Shot target | id, 0-based index, or **slug** | id, 0-based index, or `"selected"` — **no slug** |
|
|
57
|
+
| Permission gate | yes | no (client tools bypass it) |
|
|
58
|
+
|
|
59
|
+
Every board tool in both families names its board explicitly (`storyboard_id`, or
|
|
60
|
+
`storyboardId` on the still-motion-graphics tools). There is no "act on whatever is
|
|
61
|
+
open" fallback.
|
|
62
|
+
|
|
63
|
+
Prefer headless. Reach for `ui_storyboard_*` when the user is watching the board fill
|
|
64
|
+
in, or when you need `set_screenplay` for `title`, `logline` or `style_bible`.
|
|
65
|
+
Headless `edit_storyboard` → `set_board` writes screenplay-level `narration`,
|
|
66
|
+
`music_prompt` and `motion_design` while preserving the other screenplay fields.
|
|
67
|
+
Bridge the two with `ui_open_document {type: "storyboard", id}`.
|
|
68
|
+
|
|
69
|
+
`ui_storyboard_generate_keyframe`, `ui_storyboard_generate_clip` and
|
|
70
|
+
`ui_storyboard_revise_shot` spend exactly what the headless renders spend, and no
|
|
71
|
+
permission prompt stands in front of them. Call them only inside a render the
|
|
72
|
+
user has already authorized.
|
|
73
|
+
|
|
74
|
+
## The loop
|
|
75
|
+
|
|
76
|
+
1. **Find the board.** `list_storyboards`, or read `ui_context`. No board yet →
|
|
77
|
+
`create_storyboard {name, brief, style, aspect_ratio}`.
|
|
78
|
+
2. **Cast the entities.** `list_entities` first. A new one needs an image asset —
|
|
79
|
+
`generate_image` or the user's upload — then
|
|
80
|
+
`create_entity {asset_id, kind, name, descriptor}`. **There is no create-from-text.**
|
|
81
|
+
3. **Attach the cast to the board:** `edit_storyboard` → `{op: "set_board", entity_ids: [...]}`,
|
|
82
|
+
or `ui_storyboard_set_entities {storyboard_id, entity_ids}` on an open board.
|
|
83
|
+
Name matching only searches the board cast, so an entity cast nowhere seasons
|
|
84
|
+
nothing, no matter what the shots say. An id in a shot's own `entity_ids` is
|
|
85
|
+
added to the board cast when the board is read.
|
|
86
|
+
4. **Pin the models.** `find_model` with `capability` `text_to_image`, then
|
|
87
|
+
`image_to_video` or `text_to_video`; write both through
|
|
88
|
+
`{op: "set_board", image_model, video_model}`. There is no default — an unset
|
|
89
|
+
model fails the render rather than spending on a model nobody chose.
|
|
90
|
+
5. **Direct.** One `{op: "add_shot", ...}` per shot. Report the shot count and
|
|
91
|
+
selected models. Continue to the stages authorized by the request. A board-only
|
|
92
|
+
request ends here. Honor any checkpoint the user requested.
|
|
93
|
+
6. **Stills.** `render_storyboard_stills {storyboard_id}` — omit `targets` for every
|
|
94
|
+
shot that still needs one. Cap 24 shots per call.
|
|
95
|
+
7. **Take selection, when requested.** Every render keeps the old still in
|
|
96
|
+
`keyframe_versions` and the old clip in `clip_versions`, and makes the new one
|
|
97
|
+
selected. `edit_storyboard` → `{op: "select_version", target, kind: "keyframe"|"clip",
|
|
98
|
+
version}` selects another (0-based, oldest first). Pause if the user reserved
|
|
99
|
+
the pick for themselves in the takes gallery.
|
|
100
|
+
8. **Clips.** `render_storyboard_clips {storyboard_id}` within the requested
|
|
101
|
+
generation scope and budget. Reuse prior authorization instead of asking again.
|
|
102
|
+
9. **Revise one shot:** `revise_storyboard_clip {target, instruction}`. Needs an
|
|
103
|
+
existing clip; it is video-to-video and touches no other shot.
|
|
104
|
+
10. **Cut.** `assemble_storyboard_timeline`, then `validate_timeline`. Re-running
|
|
105
|
+
rebuilds the same sequence in place and keeps tracks the board does not own.
|
|
106
|
+
11. **Finish** with `edit_timeline` ops (**Tool contract** § Timeline, below).
|
|
107
|
+
Load `timeline-edit-ops` with `load_skill` for the full op list, the presets
|
|
108
|
+
and the preview contract, and `motion-graphics` for the build loop. A repair
|
|
109
|
+
on a delivered clip belongs to `nodetool-video-post`.
|
|
110
|
+
|
|
111
|
+
**Stills-only mode.** When the user wants a board and frames but no motion — a spec
|
|
112
|
+
ad, a pitch, a look test — run steps 1 to 7 and stop. Adding the motion later is
|
|
113
|
+
`update_shot` on `motion` alone: action, wardrobe, set and cast stay put, so the
|
|
114
|
+
approved stills still describe the finished piece.
|
|
115
|
+
|
|
116
|
+
## What actually reaches the model
|
|
117
|
+
|
|
118
|
+
The prompt is assembled from named fields, comma-joined, not from the whole shot:
|
|
119
|
+
|
|
120
|
+
| Field | Still | Clip, `keyframe` mode | Clip, `direct` or `reference` mode |
|
|
121
|
+
|---|---|---|---|
|
|
122
|
+
| `action` | yes | yes | yes |
|
|
123
|
+
| `camera.framing` | `"<framing> shot"` | — | `"<framing> shot"` |
|
|
124
|
+
| `camera.angle` | yes | — | yes |
|
|
125
|
+
| `camera.lens` | `"<lens> lens"` | — | `"<lens> lens"` |
|
|
126
|
+
| the shot's scene `lighting` | yes | — | yes |
|
|
127
|
+
| `motion` | — | yes (first) | yes |
|
|
128
|
+
| `camera.movement`, `camera.equipment` | — | yes | yes |
|
|
129
|
+
| board `style` | yes | — | yes |
|
|
130
|
+
|
|
131
|
+
A keyframe-mode clip animates a still that already carries framing, lens,
|
|
132
|
+
lighting and style, so the clip prompt leaves them out. Write camera direction
|
|
133
|
+
into the `camera` object, not into `action` as well. `dialogue`, `notes`,
|
|
134
|
+
`duration_seconds`, `end_state`, `sound` and `graphics` never enter a per-shot
|
|
135
|
+
prompt (`end_state` and `sound` feed the one-take prompt only).
|
|
136
|
+
|
|
137
|
+
**Entities season by plain name.** A shot with `entity_ids` uses exactly those.
|
|
138
|
+
Otherwise: every style and location entity applies to every shot, and a character or
|
|
139
|
+
prop applies when its **name appears** in the shot's `action`, `motion`, `dialogue`,
|
|
140
|
+
`narration` or `slug`. Write `Nova reaches for the bottle`, not `@Nova reaches...`.
|
|
141
|
+
`@` is a chat-composer affordance that emits an `entity://<id>` token into *your
|
|
142
|
+
message*; it never becomes shot text.
|
|
143
|
+
|
|
144
|
+
## Locks
|
|
145
|
+
|
|
146
|
+
- Keep one `descriptor` per entity and let it do the work. Do not re-describe a face
|
|
147
|
+
in shot text.
|
|
148
|
+
- A state change (wet hair, a stained shirt, a second outfit) is a **second entity**,
|
|
149
|
+
not an instruction inside one prompt.
|
|
150
|
+
- Product geometry, label and colour come from the product entity's descriptor and
|
|
151
|
+
reference image. Never invent a logo.
|
|
152
|
+
- Give every shot a stable `slug` (`1a`, `2a`, …) at `add_shot` time. Headless targets
|
|
153
|
+
accept it, and "fix 3a" then means one shot.
|
|
154
|
+
- Render mode is per shot. `keyframe` (default) is cheaper to iterate and holds a look
|
|
155
|
+
steady. `direct` skips the still — worth it for heavy motion, where first-frame
|
|
156
|
+
conditioning stiffens the result, and for native-audio video models, which are
|
|
157
|
+
weakest on their image path.
|
|
158
|
+
- A native-audio model writes the sound per clip, so a board of one clip per shot
|
|
159
|
+
cuts the audio at every join. Before rendering, load the `video-audio-continuity`
|
|
160
|
+
system skill through `load_skill`: either the whole piece is one shot
|
|
161
|
+
carrying the cuts, or the
|
|
162
|
+
continuity comes from a narration or music track and the shot audio is muted.
|
|
163
|
+
A silent cut is also valid when sound is not requested. Reuse existing audio
|
|
164
|
+
or keep the cut silent when extra generation would exceed the authorized scope.
|
|
165
|
+
|
|
166
|
+
## Where the models will not do what the cut wants
|
|
167
|
+
|
|
168
|
+
Four constraints that are not bugs and have no tool to fix them. Each has a working
|
|
169
|
+
shape.
|
|
170
|
+
|
|
171
|
+
**A video model returns the lengths it offers.** The render sends
|
|
172
|
+
`duration_seconds`, and the model snaps it to its own steps — a shot directed at
|
|
173
|
+
1.5s comes back at 5.184s on a model whose shortest clip is that long. For a run of
|
|
174
|
+
beats shorter than the model's shortest clip, render one generation with bracketed
|
|
175
|
+
timecodes in the prompt and record the split with `covered_by`. **Tool contract**
|
|
176
|
+
§ Fusing shots, below, has the call and the rule for when to fuse and when to
|
|
177
|
+
render each beat long and trim.
|
|
178
|
+
|
|
179
|
+
**Moderation reads the words, not the story.** A prompt naming institutional force —
|
|
180
|
+
police grabbing someone, a raid, restraint — trips a safety filter that a shot of the
|
|
181
|
+
same threat does not. Reframe with non-trigger vocabulary and keep the stakes:
|
|
182
|
+
"two uniformed city officials approaching with a clipboard and caution tape" passes
|
|
183
|
+
where "two aggressive police officers grabbing her arms" does not. Reframe once; do
|
|
184
|
+
not retry the rejected prompt.
|
|
185
|
+
|
|
186
|
+
**A music model cannot leave a hole.** Ask for 1.5s of silence at 00:19.5 and the
|
|
187
|
+
decoder fills it with reverb tail and room tone — there is no envelope gate in the
|
|
188
|
+
prompt. Cut the silence on the timeline instead: generate the score in two parts,
|
|
189
|
+
lay them as two clips with the gap between them, and fade the first out (~300ms) and
|
|
190
|
+
the second in (~100ms) so the edges are not clicks.
|
|
191
|
+
|
|
192
|
+
**Speech does not fit a 1.5s shot.** An expressive take of a line runs 4s, and
|
|
193
|
+
compressing it to the picture beat makes it sound processed. Let the words cross the
|
|
194
|
+
cut: keep the voiceover clip on its own track and start it under the previous shot
|
|
195
|
+
(J-cut) or run it past the cut into the next (L-cut). The picture cuts on the beat;
|
|
196
|
+
the line finishes where it finishes. On a script-linked board that is automatic —
|
|
197
|
+
each shot runs as long as the lines it covers — so use this on an unlinked board or
|
|
198
|
+
where the picture has to stay on the beat.
|
|
199
|
+
|
|
200
|
+
## Gating and spend
|
|
201
|
+
|
|
202
|
+
Permission mode decides what runs without asking. `read` tools always run.
|
|
203
|
+
|
|
204
|
+
| Mode | write / execute / external |
|
|
205
|
+
|---|---|
|
|
206
|
+
| `auto` | runs |
|
|
207
|
+
| `default` | asks the user |
|
|
208
|
+
| `plan` | **blocked** |
|
|
209
|
+
|
|
210
|
+
`plan` blocks `create_storyboard` and `edit_storyboard` too, so the board cannot be
|
|
211
|
+
written in it. Respect the active permission mode and tool approval responses. For a board-only
|
|
212
|
+
request, write the board and omit render calls. Do not switch modes or use browser
|
|
213
|
+
tools to evade a denied headless action. A tool being callable does not authorize
|
|
214
|
+
spend beyond the user's request.
|
|
215
|
+
|
|
216
|
+
**You cannot price a render.** No tool estimates it, and `Shot.cost_estimate` is not
|
|
217
|
+
populated. Report the shot count, the clip count and the models without inventing a
|
|
218
|
+
dollar estimate, and confirm that scope with the user before fanning out a clip
|
|
219
|
+
pass the request did not already size. If a user
|
|
220
|
+
budget cannot be checked before spending, resolve that constraint first. Report
|
|
221
|
+
actual spend afterwards with `get_cost_summary`. If authorization is missing,
|
|
222
|
+
prepare the board and explain the specific render action that needs approval.
|
|
223
|
+
|
|
224
|
+
## Memory
|
|
225
|
+
|
|
226
|
+
`memory_save` is durable across every conversation — use it for the style bible, the
|
|
227
|
+
cast and the grade, with the entity and board ids in `resources`. `share_result` /
|
|
228
|
+
`read_shared` are **run-scoped and discarded**; they are for passing values inside one
|
|
229
|
+
turn, not for locking a look across sessions.
|
|
230
|
+
|
|
231
|
+
## Follow-ups this loop already handles
|
|
232
|
+
|
|
233
|
+
```
|
|
234
|
+
Fix 3a only. Leave the rest.
|
|
235
|
+
Drop the contre-jour across the whole board — soft even daylight. Update the
|
|
236
|
+
board style, do not rewrite the shots.
|
|
237
|
+
New still on 2a. Keep the old take.
|
|
238
|
+
revise_storyboard_clip 5a: darker, add rain, same blocking.
|
|
239
|
+
Add a reaction shot after 3a and re-slug from there. Do not re-render 1a-3a.
|
|
240
|
+
Collapse 8, 9 and 10 into one 8s shot, keeping the lean-in as the last second.
|
|
241
|
+
The face drifted on 4a. Select still take 2, then regenerate the clip.
|
|
242
|
+
The label is unreadable on 2a. New macro still, then a clip from it.
|
|
243
|
+
Cut it. Assemble, validate, narration on its own track, mute Shot Audio on 1-6.
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## What is below
|
|
247
|
+
|
|
248
|
+
- **Tool contract** — every tool, its arguments, and the fields it refuses.
|
|
249
|
+
- **Standing orders** — an optional brief for users who explicitly want
|
|
250
|
+
approval between stages. Its sample checkpoints do not add requirements to
|
|
251
|
+
a different request.
|
|
252
|
+
|
|
253
|
+
## Tool contract
|
|
254
|
+
|
|
255
|
+
The one copy of the storyboard contract; the job and structure skills point
|
|
256
|
+
here. Exact arguments. Every field named here is checked in the implementation;
|
|
257
|
+
an unrecognized field is **refused with an error**, not dropped.
|
|
258
|
+
|
|
259
|
+
### Storyboard — headless
|
|
260
|
+
|
|
261
|
+
Work with no editor open. Shot `target` = shot id, 0-based index as a string, or slug.
|
|
262
|
+
|
|
263
|
+
| Tool | Arguments |
|
|
264
|
+
|---|---|
|
|
265
|
+
| `list_storyboards` | `limit?` → id, name, shots, with_keyframe, with_clip, timeline_id, updated_at |
|
|
266
|
+
| `create_storyboard` | `name` (required), `brief?`, `style?`, `aspect_ratio?` (default `16:9`), `project_id?`, `id?` → `{ok, storyboard_id, id, name, project_id, shots, updated_at, revision}`; `storyboard_id` and `id` hold the same value |
|
|
267
|
+
| `get_storyboard` | `storyboard_id` → brief, style, aspect ratio, narration, music_prompt, motion_design, image_model, video_model, script link, revision, and every shot with id/index/slug/action/camera/motion/duration/status/has_keyframe/has_clip/covered_by |
|
|
268
|
+
| `edit_storyboard` | `storyboard_id`, `ops[]` — see below, `expected_revision?` (refused if the board changed since that revision) |
|
|
269
|
+
| `direct_storyboard` | `storyboard_id`, `redirect?`, `shot_count?` (default 6), `provider?`, `model?` — the Director writes scenes and shots from the board's brief, genre and style; set the brief with `set_setup` first. Renders nothing |
|
|
270
|
+
| `render_storyboard_stills` | `storyboard_id`, `targets?`, `provider?`, `model?`, `style?`, `stale_only?`, `concurrency?` |
|
|
271
|
+
| `render_storyboard_clips` | `storyboard_id`, `targets?`, `provider?`, `model?`, `resolution?`, `mode?` (`keyframe`\|`direct`\|`reference`), `stale_only?`, `concurrency?` |
|
|
272
|
+
| `revise_storyboard_clip` | `storyboard_id`, `target`, `instruction`, `provider?`, `model?` |
|
|
273
|
+
| `assemble_storyboard_timeline` | `storyboard_id`, `name?`, `fps?` (default 30) |
|
|
274
|
+
| `derive_shot_from_clip` | `timeline_id`, `clip_id`, `frame_count?` (1-8, default 3), `action?` — a new reference-mode shot after the source shot, conditioned on frames of that clip. Renders nothing |
|
|
275
|
+
| `extract_script_from_storyboard` | `storyboard_id`, `name?`, `relink?` |
|
|
276
|
+
| `delete_storyboard` | `storyboard_id` |
|
|
277
|
+
| `get_storyboard_one_take` | `storyboard_id` → `prompt` (the creator's), `compiled` (REFS, STEPS, AUDIO from the board), `full_prompt`, references with `[Image N]` (the shot stills, in shot order), shot windows, duration, `settings` (stored `duration_seconds`, `aspect_ratio`, `resolution`, `model`), `effective` (after board fallbacks), warnings |
|
|
278
|
+
| `update_storyboard_one_take` | `storyboard_id`, `prompt?`, `duration_seconds?`, `aspect_ratio?`, `resolution?`, `model?` `{provider, id, name?}` (null clears one), `shots?` `[{shot_id, end_state?, sound?}]`, `expected_revision?` |
|
|
279
|
+
|
|
280
|
+
Render limits: **24 shots per call**, `concurrency` default 3, max 8.
|
|
281
|
+
|
|
282
|
+
Omitting `targets` selects every shot that still needs that step. For stills that is
|
|
283
|
+
every shot with no keyframe that is not set to render directly; for clips, every shot
|
|
284
|
+
with no picture — its own or one it is covered by (§ Fusing shots). A `keyframe`-mode
|
|
285
|
+
shot that reaches `render_storyboard_clips` with no
|
|
286
|
+
still is reported in the results, not rendered — render its still first, or set its
|
|
287
|
+
`render_mode` to `direct`. Each render appends to `keyframe_versions` /
|
|
288
|
+
`clip_versions` and makes the new one selected.
|
|
289
|
+
|
|
290
|
+
#### `edit_storyboard` ops
|
|
291
|
+
|
|
292
|
+
Fifteen ops, applied in order, at most 60 per call:
|
|
293
|
+
|
|
294
|
+
| Op | Arguments |
|
|
295
|
+
|---|---|
|
|
296
|
+
| `add_shot` | shot fields below; `action` required; `index?` or `after_shot_id?` (inserts after that shot, in its scene) |
|
|
297
|
+
| `update_shot` | `target` + any shot fields |
|
|
298
|
+
| `remove_shot` | `target` |
|
|
299
|
+
| `reorder_shot` | `target`, `index` (board position) |
|
|
300
|
+
| `move_shot` | `target`, `scene_id?`, `position` (0-based inside the target scene) — how a shot changes scene |
|
|
301
|
+
| `duplicate_shot` | `target` — the copy lands right after it, without script links or coverage |
|
|
302
|
+
| `set_board` | board fields below |
|
|
303
|
+
| `set_setup` | `brief?`, `genre?`, `stage?` (the guided-setup answers `direct_storyboard` reads) |
|
|
304
|
+
| `update_scene` | `scene_id`, `slugline?`, `lighting?` |
|
|
305
|
+
| `create_scene` | `after_scene_id?`, `slugline?` — opens holding one blank shot |
|
|
306
|
+
| `merge_scene` | `scene_id` — folds it into the scene before it |
|
|
307
|
+
| `set_style` | `entity_id` (a style entity becomes the board preset and its descriptor the board style) or `style` (the descriptor alone) |
|
|
308
|
+
| `select_version` | `target`, `kind` (`keyframe`\|`clip`), `version` (0-based, oldest first) — nothing re-renders |
|
|
309
|
+
| `delete_version` | `target`, `kind`, `version` — the asset stays in the library |
|
|
310
|
+
| `add_keyframe_version` | `target`, `asset_id`, `flip_of?` — attaches a stored image as a new still and selects it |
|
|
311
|
+
|
|
312
|
+
`set_entities` is accepted as an alias of `set_board`, and a few other near-miss
|
|
313
|
+
names resolve the same way; an unknown op is refused.
|
|
314
|
+
|
|
315
|
+
Shot fields (add and update both): `action`, `slug`, `camera`, `motion`, `end_state`,
|
|
316
|
+
`sound`, `graphics`, `production`, `dialogue`, `narration`, `notes`, `duration_seconds`,
|
|
317
|
+
`duration_source` (`audio`\|`manual`), `render_mode` (`keyframe`\|`direct`\|`reference`),
|
|
318
|
+
`entity_ids`, `location_id`, `covered_by`, `scene_id`.
|
|
319
|
+
`graphics` is semantic design intent (`mode`, `direction`, exact text/assets in `elements`), not Timeline keyframes; pass `null` on `update_shot` to clear it.
|
|
320
|
+
`production` holds the shot's production requirements (`media_strategy`,
|
|
321
|
+
`protected_inputs`, …). A selection holding a shot with an asset in
|
|
322
|
+
`protected_inputs` makes `render_storyboard_stills` return an error, and one holding
|
|
323
|
+
a `media_strategy: "still_motion_graphics"` shot does the same to
|
|
324
|
+
`render_storyboard_clips`.
|
|
325
|
+
`end_state` (the picture the shot ends on) and `sound` (what is heard) are one-take
|
|
326
|
+
cues; `update_storyboard_one_take` sets them too.
|
|
327
|
+
|
|
328
|
+
`camera` is `{framing?, lens?, angle?, movement?, equipment?}`.
|
|
329
|
+
|
|
330
|
+
`covered_by` is `{shot_id, start_seconds?, end_seconds?}` — this shot's picture is a
|
|
331
|
+
window into another shot's clip. See § Fusing shots below. `null` undoes it.
|
|
332
|
+
|
|
333
|
+
Board fields (`set_board` only): `brief`, `style`, `aspect_ratio`, `entity_ids`,
|
|
334
|
+
`image_model`, `video_model`, `narration`, `music_prompt`, `motion_design`. Any other
|
|
335
|
+
key is refused with an error naming this set. The two model fields
|
|
336
|
+
take a model **object** from `find_model` (its `.ref`), or null, and become the
|
|
337
|
+
defaults for both render tools. `narration` and `music_prompt` take strings and
|
|
338
|
+
update the screenplay; an empty string clears one. `motion_design` is
|
|
339
|
+
`{direction?, transitions?: [{from_shot_id, to_shot_id, direction?}], continuities?:
|
|
340
|
+
[{id, shot_ids, direction}]}`, or null to clear it.
|
|
341
|
+
|
|
342
|
+
**An edit cannot set `keyframe`, `clip` or `status`.** Those belong to the render
|
|
343
|
+
tools; passing them raises an error naming the right tool. `covered_by` is the one
|
|
344
|
+
exception: coverage is a picture that arrived without a render, so setting it marks
|
|
345
|
+
the shot `rendered` and clearing it puts the shot back to `keyframe_ready`/`planned`.
|
|
346
|
+
|
|
347
|
+
**An edit cannot set `title`, `logline` or `style_bible` at the board level.**
|
|
348
|
+
Use `ui_storyboard_set_screenplay` for those fields. Per-shot `narration` remains
|
|
349
|
+
a shot field. Board narration and music are separate screenplay fields:
|
|
350
|
+
`assemble_storyboard_timeline` turns the music prompt into a draft music clip on
|
|
351
|
+
every board, and the board narration into a draft narration clip on a board with
|
|
352
|
+
no linked script (a linked board voices its script lines instead).
|
|
353
|
+
|
|
354
|
+
#### Fusing shots
|
|
355
|
+
|
|
356
|
+
A video model snaps the requested length to the ones it offers — 5.184s, 6s, 8s —
|
|
357
|
+
so a beat shorter than its shortest clip still buys the whole clip. A cut whose
|
|
358
|
+
beats run 1.5-2.5s is therefore cheaper to render as one generation covering a run
|
|
359
|
+
of shots and split afterwards, than as one generation per shot: fifteen 1.5-2.5s
|
|
360
|
+
beats bought one at a time is ~77s of footage for a 30s cut.
|
|
361
|
+
|
|
362
|
+
Fuse a run of consecutive short beats that one prompt can carry: the same
|
|
363
|
+
location and cast, cuts the model can make inside one take. Render a beat as its
|
|
364
|
+
own clip and trim it on the timeline when it needs a picture no neighbour can
|
|
365
|
+
share, a different place, or its own still. Each of those clips is footage the cut
|
|
366
|
+
throws away, so report how many clips the board will buy before the clip pass and
|
|
367
|
+
get the user's go-ahead when that is more than the request implied.
|
|
368
|
+
|
|
369
|
+
Direct the run as one prompt with bracketed timecodes
|
|
370
|
+
(`[00:00 to 00:02.5] Event … [00:02.5 to 00:05.2] Reception`), render it on the first
|
|
371
|
+
shot of the run, then tell the board which slice each sibling uses:
|
|
372
|
+
|
|
373
|
+
```json
|
|
374
|
+
{"op": "update_shot", "target": "06",
|
|
375
|
+
"covered_by": {"shot_id": "05", "start_seconds": 2.5, "end_seconds": 5.184}}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
The covered shot then reads `has_clip: true` with `status: "rendered"`, is skipped by
|
|
379
|
+
`render_storyboard_clips` (so it is never generated twice), and
|
|
380
|
+
`assemble_storyboard_timeline` lays it down as that window of the covering clip
|
|
381
|
+
instead of skipping it, and the covering shot ends where the first covered shot takes
|
|
382
|
+
over. Without `end_seconds` the covered shot runs to the end of the covering clip.
|
|
383
|
+
|
|
384
|
+
One hop only: `shot_id` must name a shot that owns its clip, not another covered shot.
|
|
385
|
+
Removing the covering shot clears the coverage and reports which shots it uncovered.
|
|
386
|
+
|
|
387
|
+
### Storyboard — browser (`ui_storyboard_*`)
|
|
388
|
+
|
|
389
|
+
The board must be open as a workspace tab; its id comes from the `ui_context` system
|
|
390
|
+
block. Not open → `edit_storyboard` edits the saved board. Use `ui_open_document {type: "storyboard", id}` to show it to the user. Target =
|
|
391
|
+
shot id, index as a string, or `"selected"`. **Slugs are not resolved here.**
|
|
392
|
+
|
|
393
|
+
| Tool | Arguments |
|
|
394
|
+
|---|---|
|
|
395
|
+
| `ui_storyboard_get_state` | `storyboard_id` |
|
|
396
|
+
| `ui_storyboard_set_screenplay` | `storyboard_id`, `screenplay` |
|
|
397
|
+
| `ui_storyboard_set_entities` | `storyboard_id`, `entity_ids` (replaces the cast; `[]` clears it) |
|
|
398
|
+
| `ui_storyboard_add_shot` | `storyboard_id`, `action`, `slug?`, `camera?`, `motion?`, `graphics?`, `production?`, `durationSeconds?`, `index?`, `afterShotId?` |
|
|
399
|
+
| `ui_storyboard_update_shot` | `storyboard_id`, `target`, `action?`, `slug?`, `camera?`, `motion?`, `graphics?`, `production?`, `dialogue?`, `notes?`, `durationSeconds?`, `durationSource?`, `status?` |
|
|
400
|
+
| `ui_storyboard_remove_shot` / `_duplicate_shot` | `storyboard_id`, `target` |
|
|
401
|
+
| `ui_storyboard_move_shot` | `storyboard_id`, `target`, `sceneId?`, `position` |
|
|
402
|
+
| `ui_storyboard_set_setup` | `storyboard_id`, `brief?`, `genre?`, `stage?`, `shotCount?`, `importSource?`, `directedFrom?` |
|
|
403
|
+
| `ui_storyboard_direct` | `storyboard_id`, `redirect`, `shotCount?` |
|
|
404
|
+
| `ui_storyboard_update_scene` | `storyboard_id`, `sceneId`, `slugline?`, `lighting?` |
|
|
405
|
+
| `ui_storyboard_create_scene` / `_merge_scene` | `storyboard_id`, `afterSceneId?` / `sceneId` |
|
|
406
|
+
| `ui_storyboard_set_style` | `storyboard_id`, `entityId?` or `descriptor?` |
|
|
407
|
+
| `ui_storyboard_select_version` / `_delete_version` | `storyboard_id`, `target`, `kind`, `version` |
|
|
408
|
+
| `ui_storyboard_add_keyframe_version` | `storyboard_id`, `target`, `assetId`, `flipOf?` — adds the still without selecting it |
|
|
409
|
+
| `ui_storyboard_generate_keyframe` | `storyboard_id`, `target`, `staleOnly?` |
|
|
410
|
+
| `ui_storyboard_generate_clip` | `storyboard_id`, `target`, `staleOnly?` |
|
|
411
|
+
| `ui_storyboard_revise_shot` | `storyboard_id`, `target`, `instruction` |
|
|
412
|
+
| `ui_storyboard_assemble_timeline` | `storyboard_id` |
|
|
413
|
+
| `ui_storyboard_select_shot` | `storyboard_id`, `target` (nullable) |
|
|
414
|
+
| `ui_storyboard_extract_script` / `_relink_script` / `_reproject_shots` | `storyboard_id` (+ `targets?`) |
|
|
415
|
+
| `ui_storyboard_set_duration_source` | `storyboard_id`, `targets`, `source` |
|
|
416
|
+
|
|
417
|
+
The browser shot tools take **no** `narration`, `entity_ids` or `render_mode`, and
|
|
418
|
+
`add_shot` takes no `dialogue`. In the browser those arrive through
|
|
419
|
+
`set_screenplay`; headless, through `edit_storyboard`.
|
|
420
|
+
|
|
421
|
+
Generation is asynchronous: `generate_keyframe` / `generate_clip` start the job and
|
|
422
|
+
return the shot. Poll `ui_storyboard_get_state` for `status`.
|
|
423
|
+
|
|
424
|
+
Statuses: `planned` → `keyframe_generating` → `keyframe_ready` → `clip_generating` →
|
|
425
|
+
`rendered`, or `failed`. (`approved` is legacy and reads as "still ready"; nothing
|
|
426
|
+
sets it. Do not use it as a gate.)
|
|
427
|
+
|
|
428
|
+
#### `screenplay` for `set_screenplay`
|
|
429
|
+
|
|
430
|
+
```json
|
|
431
|
+
{
|
|
432
|
+
"type": "screenplay",
|
|
433
|
+
"title": "…",
|
|
434
|
+
"logline": "…",
|
|
435
|
+
"brief": "…",
|
|
436
|
+
"styleBible": "…",
|
|
437
|
+
"aspectRatio": "9:16",
|
|
438
|
+
"narration": "…",
|
|
439
|
+
"musicPrompt": "…",
|
|
440
|
+
"entityIds": ["<asset id>"],
|
|
441
|
+
"shots": [
|
|
442
|
+
{
|
|
443
|
+
"slug": "1a",
|
|
444
|
+
"action": "Nova touches untreated hair, front-camera close",
|
|
445
|
+
"camera": { "framing": "close-up", "lens": "26mm", "angle": "eye", "movement": "handheld" },
|
|
446
|
+
"motion": "she lifts one section of hair and lets it fall, once",
|
|
447
|
+
"dialogue": "I wanted lighter hair…",
|
|
448
|
+
"durationSeconds": 4,
|
|
449
|
+
"entityIds": ["<asset id>"]
|
|
450
|
+
}
|
|
451
|
+
]
|
|
452
|
+
}
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
Ids, indexes and statuses are filled in. `style` is accepted as an alias of
|
|
456
|
+
`styleBible`. Only `action` is required per shot.
|
|
457
|
+
|
|
458
|
+
The top-level `entityIds` casts those entities on the **board** — that cast is what
|
|
459
|
+
seasons every shot's still and clip prompt. Use `ui_storyboard_set_entities` to
|
|
460
|
+
recast without replacing the shots; `ui_storyboard_get_state` reports the current
|
|
461
|
+
cast as `entityIds`. A shot's own `entityIds` overrides the board cast for that shot
|
|
462
|
+
(`[]` means "no entities on this shot"); without one, styles and locations apply to
|
|
463
|
+
every shot and characters and props apply to the shots whose text names them.
|
|
464
|
+
|
|
465
|
+
### Entities
|
|
466
|
+
|
|
467
|
+
| Tool | Arguments |
|
|
468
|
+
|---|---|
|
|
469
|
+
| `list_entities` | `kind?` (`character`\|`location`\|`style`\|`prop`), `query?`, `limit?` |
|
|
470
|
+
| `get_entity` | `entity_id` |
|
|
471
|
+
| `create_entity` | `asset_id`, `kind`, `name`, `descriptor` (all required), `description?`, `voice_id?`, `tags?`, `lora?`, `palette?` |
|
|
472
|
+
| `update_entity` | `entity_id` + any field, including `asset_id` to move it to a new photo |
|
|
473
|
+
| `delete_entity` | `entity_id` — clears the marker, keeps the image |
|
|
474
|
+
| `apply_entities` | `text`, `entity_ids?` → seasoned prompt + reference asset ids |
|
|
475
|
+
|
|
476
|
+
An entity **is** an image asset carrying a marker. Its id is the asset id. To make one
|
|
477
|
+
from nothing: `find_model` (`text_to_image`) → `generate_image` → `create_entity` with
|
|
478
|
+
the returned asset id. To make one from a photo the user attached: find it with
|
|
479
|
+
`asset_search` / `list_assets`, then `create_entity`.
|
|
480
|
+
|
|
481
|
+
`apply_entities` is for seasoning a prompt you are about to send yourself
|
|
482
|
+
(`generate_image`, `generate_video`). The storyboard render tools season internally —
|
|
483
|
+
do not pre-season shot text with it.
|
|
484
|
+
|
|
485
|
+
### Timeline
|
|
486
|
+
|
|
487
|
+
`assemble_storyboard_timeline` lays rendered clips end to end, stamps each with its
|
|
488
|
+
board and shot id, and gives every shot clip an **audio twin on a "Shot Audio" track**
|
|
489
|
+
(each surface mutes video elements and mixes audio clips only). Board narration and
|
|
490
|
+
music become draft clips on their own tracks.
|
|
491
|
+
|
|
492
|
+
**Each shot is as long as its render**, not as long as its `duration_seconds` — a shot
|
|
493
|
+
directed at 1.5s that came back at 5.184s occupies 5.184s of the cut. Shots that came
|
|
494
|
+
back off their directed length are returned in `retimed_shots` and warned about, so a
|
|
495
|
+
cut that runs longer than planned is visible without playing it. `duration_seconds`
|
|
496
|
+
only decides a shot whose footage has no measurable length. A board linked to a script
|
|
497
|
+
is the exception: there the words decide, and `trimmed_shots` names the shots whose
|
|
498
|
+
footage does not fill the slot its lines gave it.
|
|
499
|
+
|
|
500
|
+
`edit_timeline {timeline_id, ops[]}` carries the edit; `timeline-edit-ops` holds its
|
|
501
|
+
full op list. Start with `get_state` for ids.
|
|
502
|
+
|
|
503
|
+
To duck the shots' own sound under a voiceover: `set_clip_params` on the Shot Audio
|
|
504
|
+
clips with `muted: true`, or `volumeDb: -18`. `set_clip_params` also takes `name`,
|
|
505
|
+
`opacity`, `speedMultiplier`, `fadeInMs`, `fadeOutMs`, `blendMode`, `borderRadius`,
|
|
506
|
+
`hidden`, `locked`, `textStyle`, `shapeStyle`.
|
|
507
|
+
|
|
508
|
+
Every field goes on the op itself. A `params` / `props` wrapper is unwrapped, but the
|
|
509
|
+
flat form is the contract:
|
|
510
|
+
|
|
511
|
+
```json
|
|
512
|
+
{"op": "set_clip_params", "target": "clip_1",
|
|
513
|
+
"textStyle": {"fontFamily": "Space Grotesk", "fontWeight": 800}}
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
`textStyle` is **merged** over the clip's own, so send only what you are changing —
|
|
517
|
+
re-sending the whole object is how the fields you did not mean to touch get
|
|
518
|
+
overwritten. `fontWeight` is a number, and the CSS keywords (`bold`, `semibold`,
|
|
519
|
+
`extrabold`) are accepted and stored as one.
|
|
520
|
+
|
|
521
|
+
**Set a bundled family or none at all.** `sans-serif`, `serif`, `system-ui` and the
|
|
522
|
+
other CSS generics are refused where the clip is authored: they name no typeface, so
|
|
523
|
+
the editor preview, the render and `preview_timeline_frame` each pick a different one.
|
|
524
|
+
NodeTool ships Inter (the default), Space Grotesk, Bebas Neue, Playfair Display, Lora
|
|
525
|
+
and JetBrains Mono. A named system font is still allowed and `validate_timeline`
|
|
526
|
+
reports it as `font_not_portable`.
|
|
527
|
+
|
|
528
|
+
`validate_timeline {timeline_id | document, fps?}` after every edit pass. It catches
|
|
529
|
+
overlaps, fades longer than their clip, unknown presets and incomplete bindings.
|
|
530
|
+
|
|
531
|
+
Other timeline tools: `list_timelines`, `create_timeline`, `get_timeline`,
|
|
532
|
+
`delete_timeline`, and the version family (`list_`/`get_`/`create_`/`restore_`/
|
|
533
|
+
`delete_timeline_version`). Browser twins are `ui_timeline_*`.
|
|
534
|
+
|
|
535
|
+
### Script
|
|
536
|
+
|
|
537
|
+
Use a script when the words drive the cut. `extract_script_from_storyboard` projects a
|
|
538
|
+
board's dialogue and narration into a script and links the two: each shot keeps the
|
|
539
|
+
line ids it covers, and from then on the script owns the words. `derive_storyboard_from_script`
|
|
540
|
+
goes the other way, one shot per line, optionally with a director pass over the scaffold.
|
|
541
|
+
|
|
542
|
+
`voice_script_lines` synthesizes each line with its cast voice; `assemble_script_timeline`
|
|
543
|
+
lays the takes end to end.
|
|
544
|
+
|
|
545
|
+
**Give the cast their voices first, then voice with no override.** A line takes its
|
|
546
|
+
voice from its speaker: `add_line` without `speaker` leaves it with none, and
|
|
547
|
+
`extract_script_from_storyboard` creates the Narrator with no voice and each
|
|
548
|
+
character with at most its entity's `voice_id` and no provider or model. Either way
|
|
549
|
+
the line reads `no_voice` until its speaker is bound. A voice is the
|
|
550
|
+
triple `{provider, model, voice}` where `voice` is the voice's own name (`Aoede`,
|
|
551
|
+
`Charon`, `alloy`) — a provider and a model alone do not pick one, so
|
|
552
|
+
`voice_script_lines {provider, model}` is refused. Set each speaker once:
|
|
553
|
+
|
|
554
|
+
```json
|
|
555
|
+
{"op": "set_speaker_voice", "target": "MOM",
|
|
556
|
+
"provider": "gemini", "model": "gemini-2.5-flash-preview-tts", "voice": "Aoede"}
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
The three keys sit on the op, though a `voice: {provider, model, voice_id}` object —
|
|
560
|
+
the shape `get_script` reports a voice in — is flattened onto it. Then call
|
|
561
|
+
`voice_script_lines {script_id}` with no override and every speaker resolves its own. On a linked board, `assemble_storyboard_timeline` cuts words
|
|
562
|
+
and picture together: each shot runs as long as the takes it covers and every voiced
|
|
563
|
+
line gets its own voiceover clip.
|
|
564
|
+
|
|
565
|
+
Others: `list_scripts`, `get_script` (line status: `draft`, `stale`, `voiced`,
|
|
566
|
+
`no_voice`), `create_script`, `edit_script`, `delete_script`. Browser twins are
|
|
567
|
+
`ui_script_*`.
|
|
568
|
+
|
|
569
|
+
#### Writing the lines
|
|
570
|
+
|
|
571
|
+
`create_script` makes an empty one. `edit_script {script_id, ops[]}` ops:
|
|
572
|
+
`set_setup` (brief, format, length, pace, language, stage), `add_speaker`,
|
|
573
|
+
`set_speaker`, `set_speaker_voice`, `add_section`, `add_line`, `set_line_text`,
|
|
574
|
+
`set_line_speaker`, `remove_line`, `remove_speaker`. Call `get_script` first for
|
|
575
|
+
line and speaker ids.
|
|
576
|
+
|
|
577
|
+
`write_script {script_id, rewrite?, imported_text?}` drafts the lines from the
|
|
578
|
+
setup and saves the cast and lines it produces, replacing what was there. Set
|
|
579
|
+
the brief with `set_setup` first. `rewrite: true` keeps the ids of the lines it
|
|
580
|
+
keeps, so their takes and storyboard links survive. Words passed as
|
|
581
|
+
`imported_text` are only split into lines and attributed to speakers, never
|
|
582
|
+
reworded. It records no take: voice the result with `voice_script_lines`.
|
|
583
|
+
|
|
584
|
+
Rewriting a line leaves its takes in place as **stale**, so a following
|
|
585
|
+
`voice_script_lines` re-records exactly those and nothing else.
|
|
586
|
+
|
|
587
|
+
### Models
|
|
588
|
+
|
|
589
|
+
`find_model {capability, query?, task?, provider_hint?, model_hint?, prefer_local?,
|
|
590
|
+
limit?}` is the only way to name a model. **`capability` is required** and is one of
|
|
591
|
+
`text_to_image`, `image_to_image`, `segment_image`, `text_to_video`, `image_to_video`,
|
|
592
|
+
`reference_to_video`, `text_to_speech`, `text_to_music`, `audio_to_audio`,
|
|
593
|
+
`automatic_speech_recognition`, `generate_embedding`, `generate_message`. `query` is the search box (`"flux schnell"`); `task` is a hint
|
|
594
|
+
matched against a model's supported tasks, not a search.
|
|
595
|
+
|
|
596
|
+
NodeTool ships curated ids under the `nodetool` provider, which are the sane defaults
|
|
597
|
+
when the user has no preference:
|
|
598
|
+
|
|
599
|
+
| Id | Use |
|
|
600
|
+
|---|---|
|
|
601
|
+
| `nodetool/flux-schnell` | cheapest stills, text prompts only |
|
|
602
|
+
| `nodetool/nano-banana` | sharper stills, takes reference images |
|
|
603
|
+
| `nodetool/seedream` | most detail, slowest |
|
|
604
|
+
| `nodetool/hailuo-fast` | cheapest clips, for blocking out a cut |
|
|
605
|
+
| `nodetool/kling-turbo` | steadier motion, mid price |
|
|
606
|
+
| `nodetool/kling-standard` | most faithful motion |
|
|
607
|
+
| `nodetool/kokoro` | eight TTS voices |
|
|
608
|
+
|
|
609
|
+
### Media, outside the board
|
|
610
|
+
|
|
611
|
+
`generate_image {provider, model, prompt, negative_prompt?, width?, height?, quality?,
|
|
612
|
+
output_file?}`, plus `edit_image`, `generate_video`, `animate_image`, `generate_speech`,
|
|
613
|
+
`generate_music` and `transcribe_audio` — each takes a `find_model` provider+model and
|
|
614
|
+
saves an `asset://` result. `model` also accepts a whole `find_model` hit or its `.ref`,
|
|
615
|
+
in which case `provider` may be omitted.
|
|
616
|
+
|
|
617
|
+
`understand_video {provider, model, video, prompt?, max_tokens?}` reads a whole video
|
|
618
|
+
with a multimodal model (Gemini reads video natively); use it to break down a reference
|
|
619
|
+
cut. `video` takes an asset id, an `asset://` URI, a URL or a data URI. `view_image`
|
|
620
|
+
puts pixels in your own context; `read_media_bytes` returns the bytes.
|
|
621
|
+
|
|
622
|
+
### The Director node
|
|
623
|
+
|
|
624
|
+
`nodetool.creative.Director` is a **workflow node**, not a chat surface. It turns a
|
|
625
|
+
brief into a `Screenplay` (props: `model`, `brief`, `style`, `shot_count`, `aspect_ratio`,
|
|
626
|
+
`max_tokens`; outputs
|
|
627
|
+
`screenplay`, `narration`, `music_prompt`, `title`). Reach for it only inside a
|
|
628
|
+
reusable graph. In chat, `direct_storyboard` runs the same Director over a saved
|
|
629
|
+
board, or write the shots yourself with `edit_storyboard`.
|
|
630
|
+
|
|
631
|
+
### Still-motion-graphics boards
|
|
632
|
+
|
|
633
|
+
A board whose shots carry `production.media_strategy: "still_motion_graphics"` renders
|
|
634
|
+
no clips: its stills and `graphics` elements become timeline layers.
|
|
635
|
+
`preview_storyboard_design {storyboardId, expectedStoryboardRevision}` reads the design
|
|
636
|
+
as a timeline preview with no save. `layout_storyboard` places every layer at each
|
|
637
|
+
shot's hold frame with a vision model. `finish_storyboard` writes the editable
|
|
638
|
+
timeline: `strategy: "deterministic"` (default, no provider) returns an unreviewed
|
|
639
|
+
scaffold, `"agentic"` composes the motion and reviews rendered frames. All three
|
|
640
|
+
need the board's current `revision` from `get_storyboard`.
|
|
641
|
+
|
|
642
|
+
## Standing orders
|
|
643
|
+
|
|
644
|
+
Use this optional template only when the user wants a staged review with approval
|
|
645
|
+
between the board, stills, clips, and cut. Its checkpoints express that user's
|
|
646
|
+
chosen workflow. They do not apply to other requests or override authorization
|
|
647
|
+
already given to finish a piece.
|
|
648
|
+
|
|
649
|
+
Respect the thread's existing permission mode. Plan mode blocks writes. Do not
|
|
650
|
+
switch modes or use another tool family to evade a denial. The user can adapt the
|
|
651
|
+
brief to authorize the desired stages and set a budget.
|
|
652
|
+
|
|
653
|
+
```
|
|
654
|
+
You are directing on the storyboard surface, then finishing in the timeline.
|
|
655
|
+
Do not build a node graph unless I ask for a reusable template.
|
|
656
|
+
|
|
657
|
+
SURFACES
|
|
658
|
+
Headless: create_storyboard, get_storyboard, edit_storyboard,
|
|
659
|
+
render_storyboard_stills, render_storyboard_clips, revise_storyboard_clip,
|
|
660
|
+
assemble_storyboard_timeline, extract_script_from_storyboard.
|
|
661
|
+
Browser (only for a board open as a tab): ui_storyboard_get_state,
|
|
662
|
+
ui_storyboard_set_screenplay, ui_storyboard_generate_keyframe,
|
|
663
|
+
ui_storyboard_generate_clip, ui_storyboard_revise_shot,
|
|
664
|
+
ui_storyboard_assemble_timeline. Use ui_open_document to open a board.
|
|
665
|
+
The ui_ generate and revise calls spend like the headless renders: same
|
|
666
|
+
approval rules.
|
|
667
|
+
|
|
668
|
+
LOOP
|
|
669
|
+
1. list_storyboards, or read ui_context. No board -> create_storyboard.
|
|
670
|
+
2. Cast before anything renders. list_entities first. A new entity needs an
|
|
671
|
+
image asset: generate_image (or my attachment), then create_entity with
|
|
672
|
+
{asset_id, kind, name, descriptor}. There is no create-from-text.
|
|
673
|
+
Each entity: one name, one kind, one canonical descriptor sentence.
|
|
674
|
+
3. edit_storyboard set_board {entity_ids, image_model, video_model,
|
|
675
|
+
brief, style, aspect_ratio, narration, music_prompt}. Models come from find_model — there is no
|
|
676
|
+
default and an unset model fails the render.
|
|
677
|
+
4. Direct with edit_storyboard add_shot, one op per shot. Give every shot a
|
|
678
|
+
slug (1a, 2a, ...). Fields: action, slug, camera {framing, lens, angle,
|
|
679
|
+
movement}, motion, dialogue, narration, duration_seconds, render_mode.
|
|
680
|
+
5. STOP after the board. Report shot count and the two model ids. Do not
|
|
681
|
+
estimate dollars — no tool prices a render. Wait for "stills".
|
|
682
|
+
6. render_storyboard_stills. I pick takes in the board's takes gallery, or
|
|
683
|
+
tell you which one; you apply it with edit_storyboard select_version.
|
|
684
|
+
Wait for "clips".
|
|
685
|
+
7. render_storyboard_clips. revise_storyboard_clip touches one shot only.
|
|
686
|
+
8. On "cut": assemble_storyboard_timeline, then validate_timeline.
|
|
687
|
+
|
|
688
|
+
WRITING THE SHOTS
|
|
689
|
+
The still prompt is action, camera framing, angle and lens, the scene
|
|
690
|
+
lighting and the board style. A keyframe clip prompt is motion, action,
|
|
691
|
+
camera movement and equipment; a direct clip gets all of these. Write camera
|
|
692
|
+
direction into the camera object once, not into action as well.
|
|
693
|
+
Name entities in plain text — "Nova reaches for the bottle", never "@Nova".
|
|
694
|
+
Entities season a shot when their name appears in its action, motion, dialogue,
|
|
695
|
+
narration or slug; styles and locations season every shot. A shot's entity_ids,
|
|
696
|
+
if set, overrides that entirely.
|
|
697
|
+
|
|
698
|
+
LOCKS
|
|
699
|
+
- The descriptor is the face. Do not re-describe an entity in shot text.
|
|
700
|
+
- Product geometry, label, cap and colour come from the product entity. No new
|
|
701
|
+
logos.
|
|
702
|
+
- A state change (wet, stained, new outfit) is a second entity, not a morph
|
|
703
|
+
instruction inside one prompt.
|
|
704
|
+
- Slugs stay stable. "Fix 3a" edits 3a and renders 3a.
|
|
705
|
+
- UGC: handheld phone, grain, natural audio, no score, no beauty filter.
|
|
706
|
+
Hero: locked grade in the style, smooth camera, score allowed.
|
|
707
|
+
- render_mode "keyframe" (default) for anything where the look must hold;
|
|
708
|
+
"direct" for heavy motion or a native-audio video model.
|
|
709
|
+
|
|
710
|
+
MEMORY
|
|
711
|
+
memory_save the style, the cast and the grade, with the entity and board ids in
|
|
712
|
+
`resources` — that survives into later threads. share_result/read_shared are
|
|
713
|
+
run-scoped and get discarded; do not use them to lock a look.
|
|
714
|
+
|
|
715
|
+
If the shot count looks expensive, propose a collapse before rendering clips.
|
|
716
|
+
```
|