@nodetool-ai/agents 0.8.4-a24.3 → 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.
Files changed (135) hide show
  1. package/dist/.nodetool-build-stamp +1 -1
  2. package/dist/_skills/LICENSE-iart-motion-design-skills +21 -0
  3. package/dist/_skills/README.md +211 -0
  4. package/dist/_skills/api-agents/SKILL.md +58 -0
  5. package/dist/_skills/api-apps/SKILL.md +54 -0
  6. package/dist/_skills/api-assets/SKILL.md +66 -0
  7. package/dist/_skills/api-collections/SKILL.md +33 -0
  8. package/dist/_skills/api-games/SKILL.md +81 -0
  9. package/dist/_skills/api-media/SKILL.md +131 -0
  10. package/dist/_skills/api-memory/SKILL.md +72 -0
  11. package/dist/_skills/api-models/SKILL.md +78 -0
  12. package/dist/_skills/api-scripts/SKILL.md +79 -0
  13. package/dist/_skills/api-settings/SKILL.md +49 -0
  14. package/dist/_skills/api-sketches/SKILL.md +61 -0
  15. package/dist/_skills/api-storyboards/SKILL.md +94 -0
  16. package/dist/_skills/api-timelines/SKILL.md +151 -0
  17. package/dist/_skills/api-web/SKILL.md +57 -0
  18. package/dist/_skills/api-workflows/SKILL.md +123 -0
  19. package/dist/_skills/beat-sync-editing/SKILL.md +208 -0
  20. package/dist/_skills/caption-titles/SKILL.md +94 -0
  21. package/dist/_skills/color-motion/SKILL.md +185 -0
  22. package/dist/_skills/commercial-beat-sheet/SKILL.md +338 -0
  23. package/dist/_skills/elevenlabs-audio-prompting/SKILL.md +163 -0
  24. package/dist/_skills/explainer-storyboard/SKILL.md +63 -0
  25. package/dist/_skills/flux-2-klein-prompting/SKILL.md +107 -0
  26. package/dist/_skills/frame-composition/SKILL.md +291 -0
  27. package/dist/_skills/game-direction/SKILL.md +99 -0
  28. package/dist/_skills/gpt-image-2-prompting/SKILL.md +161 -0
  29. package/dist/_skills/hailuo-prompting/SKILL.md +107 -0
  30. package/dist/_skills/kling-video-prompting/SKILL.md +188 -0
  31. package/dist/_skills/launch-commercial/SKILL.md +267 -0
  32. package/dist/_skills/launch-kit/SKILL.md +120 -0
  33. package/dist/_skills/logo-reveal/SKILL.md +225 -0
  34. package/dist/_skills/manimgx/SKILL.md +123 -0
  35. package/dist/_skills/minimax-h3-prompting/SKILL.md +134 -0
  36. package/dist/_skills/motion-ad/SKILL.md +281 -0
  37. package/dist/_skills/motion-background/SKILL.md +145 -0
  38. package/dist/_skills/motion-curves/SKILL.md +302 -0
  39. package/dist/_skills/motion-direction/SKILL.md +259 -0
  40. package/dist/_skills/motion-graphics/SKILL.md +217 -0
  41. package/dist/_skills/motion-principles/SKILL.md +255 -0
  42. package/dist/_skills/music-video-treatment/SKILL.md +40 -0
  43. package/dist/_skills/nano-banana-pro-prompting/SKILL.md +133 -0
  44. package/dist/_skills/native-game/SKILL.md +310 -0
  45. package/dist/_skills/native-game-reference/SKILL.md +879 -0
  46. package/dist/_skills/nodetool-3d-scene/SKILL.md +110 -0
  47. package/dist/_skills/nodetool-api-reference/SKILL.md +338 -0
  48. package/dist/_skills/nodetool-app-builder/SKILL.md +291 -0
  49. package/dist/_skills/nodetool-browser-agent/SKILL.md +119 -0
  50. package/dist/_skills/nodetool-chat-cli/SKILL.md +184 -0
  51. package/dist/_skills/nodetool-custom-node-developer/SKILL.md +294 -0
  52. package/dist/_skills/nodetool-deployment/SKILL.md +195 -0
  53. package/dist/_skills/nodetool-js-scripting/SKILL.md +223 -0
  54. package/dist/_skills/nodetool-model-provider-config/SKILL.md +348 -0
  55. package/dist/_skills/nodetool-rag-indexing/SKILL.md +153 -0
  56. package/dist/_skills/nodetool-sketch/SKILL.md +138 -0
  57. package/dist/_skills/nodetool-skill-author/SKILL.md +125 -0
  58. package/dist/_skills/nodetool-troubleshooter/SKILL.md +370 -0
  59. package/dist/_skills/nodetool-video-post/SKILL.md +149 -0
  60. package/dist/_skills/nodetool-workflow-builder/SKILL.md +295 -0
  61. package/dist/_skills/product-commercial/SKILL.md +132 -0
  62. package/dist/_skills/qwen-image-prompting/SKILL.md +122 -0
  63. package/dist/_skills/script-video/SKILL.md +106 -0
  64. package/dist/_skills/seedance-2-prompting/SKILL.md +194 -0
  65. package/dist/_skills/seedream-prompting/SKILL.md +117 -0
  66. package/dist/_skills/short-film/SKILL.md +123 -0
  67. package/dist/_skills/stable-audio-prompting/SKILL.md +130 -0
  68. package/dist/_skills/storyboard-core/SKILL.md +716 -0
  69. package/dist/_skills/timeline-edit-ops/SKILL.md +736 -0
  70. package/dist/_skills/trailer-template/SKILL.md +291 -0
  71. package/dist/_skills/ugc-video/SKILL.md +186 -0
  72. package/dist/_skills/veo-3-prompting/SKILL.md +140 -0
  73. package/dist/_skills/video-audio-continuity/SKILL.md +90 -0
  74. package/dist/_skills/video-clone/SKILL.md +96 -0
  75. package/dist/_skills/video-workflow/SKILL.md +119 -0
  76. package/dist/_skills/wan-2-6-prompting/SKILL.md +107 -0
  77. package/dist/app-build/bridge.d.ts.map +1 -1
  78. package/dist/app-build/bridge.js +44 -17
  79. package/dist/app-build/bridge.js.map +1 -1
  80. package/dist/capabilities/apps.d.ts.map +1 -1
  81. package/dist/capabilities/apps.js +55 -13
  82. package/dist/capabilities/apps.js.map +1 -1
  83. package/dist/capabilities/apps.specs.d.ts.map +1 -1
  84. package/dist/capabilities/apps.specs.js +5 -3
  85. package/dist/capabilities/apps.specs.js.map +1 -1
  86. package/dist/capabilities/game-asset-browser.d.ts.map +1 -1
  87. package/dist/capabilities/game-asset-browser.js +6 -1
  88. package/dist/capabilities/game-asset-browser.js.map +1 -1
  89. package/dist/capabilities/game.d.ts +1 -1
  90. package/dist/capabilities/game.d.ts.map +1 -1
  91. package/dist/capabilities/game.js +71 -56
  92. package/dist/capabilities/game.js.map +1 -1
  93. package/dist/capabilities/game.specs.js +1 -1
  94. package/dist/capabilities/game.specs.js.map +1 -1
  95. package/dist/capabilities/game3d.d.ts.map +1 -1
  96. package/dist/capabilities/game3d.js +14 -19
  97. package/dist/capabilities/game3d.js.map +1 -1
  98. package/dist/capabilities/gameAssertions.d.ts +52 -0
  99. package/dist/capabilities/gameAssertions.d.ts.map +1 -0
  100. package/dist/capabilities/gameAssertions.js +32 -0
  101. package/dist/capabilities/gameAssertions.js.map +1 -0
  102. package/dist/capabilities/models.d.ts.map +1 -1
  103. package/dist/capabilities/models.js +9 -0
  104. package/dist/capabilities/models.js.map +1 -1
  105. package/dist/capabilities/storyboards.d.ts.map +1 -1
  106. package/dist/capabilities/storyboards.js +36 -11
  107. package/dist/capabilities/storyboards.js.map +1 -1
  108. package/dist/capabilities/storyboards.specs.d.ts.map +1 -1
  109. package/dist/capabilities/storyboards.specs.js +11 -1
  110. package/dist/capabilities/storyboards.specs.js.map +1 -1
  111. package/dist/capabilities/workflows.d.ts.map +1 -1
  112. package/dist/capabilities/workflows.js +1 -0
  113. package/dist/capabilities/workflows.js.map +1 -1
  114. package/dist/capabilities/workflows.specs.d.ts.map +1 -1
  115. package/dist/capabilities/workflows.specs.js +4 -3
  116. package/dist/capabilities/workflows.specs.js.map +1 -1
  117. package/dist/evals/surfaces/game.d.ts.map +1 -1
  118. package/dist/evals/surfaces/game.js +29 -1
  119. package/dist/evals/surfaces/game.js.map +1 -1
  120. package/dist/game-authoring.d.ts +120 -0
  121. package/dist/game-authoring.d.ts.map +1 -1
  122. package/dist/game-build-integrity.d.ts +12 -0
  123. package/dist/game-build-integrity.d.ts.map +1 -0
  124. package/dist/game-build-integrity.js +78 -0
  125. package/dist/game-build-integrity.js.map +1 -0
  126. package/dist/game-examples.d.ts +44 -3
  127. package/dist/game-examples.d.ts.map +1 -1
  128. package/dist/game-examples.js +60 -10
  129. package/dist/game-examples.js.map +1 -1
  130. package/dist/system-skills.d.ts +1 -1
  131. package/dist/system-skills.js +1 -1
  132. package/dist/tools/builtin-tools.d.ts.map +1 -1
  133. package/dist/tools/builtin-tools.js +2 -0
  134. package/dist/tools/builtin-tools.js.map +1 -1
  135. 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
+ ```