@nodetool-ai/node-sdk 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.
Files changed (256) hide show
  1. package/dist/.nodetool-build-stamp +1 -1
  2. package/dist/_sandbox/README.md +176 -0
  3. package/dist/_sandbox/sandbox-aws/SKILL.md +98 -0
  4. package/dist/_sandbox/sandbox-aws/package.json +40 -0
  5. package/dist/_sandbox/sandbox-chrono/SKILL.md +39 -0
  6. package/dist/_sandbox/sandbox-chrono/package.json +40 -0
  7. package/dist/_sandbox/sandbox-color/SKILL.md +35 -0
  8. package/dist/_sandbox/sandbox-color/package.json +43 -0
  9. package/dist/_sandbox/sandbox-csv/SKILL.md +50 -0
  10. package/dist/_sandbox/sandbox-csv/package.json +39 -0
  11. package/dist/_sandbox/sandbox-dates/SKILL.md +47 -0
  12. package/dist/_sandbox/sandbox-dates/package.json +41 -0
  13. package/dist/_sandbox/sandbox-decimal/SKILL.md +36 -0
  14. package/dist/_sandbox/sandbox-decimal/package.json +43 -0
  15. package/dist/_sandbox/sandbox-diff/SKILL.md +36 -0
  16. package/dist/_sandbox/sandbox-diff/package.json +39 -0
  17. package/dist/_sandbox/sandbox-docx/SKILL.md +53 -0
  18. package/dist/_sandbox/sandbox-docx/package.json +40 -0
  19. package/dist/_sandbox/sandbox-dsl/SKILL.md +137 -0
  20. package/dist/_sandbox/sandbox-dsl/package.json +413 -0
  21. package/dist/_sandbox/sandbox-dsl/sandbox/authoring.js +382 -0
  22. package/dist/_sandbox/sandbox-dsl/sandbox/core.js +382 -0
  23. package/dist/_sandbox/sandbox-dsl/sandbox/generated/gemini.audio.js +12 -0
  24. package/dist/_sandbox/sandbox-dsl/sandbox/generated/gemini.image.js +8 -0
  25. package/dist/_sandbox/sandbox-dsl/sandbox/generated/gemini.text.js +12 -0
  26. package/dist/_sandbox/sandbox-dsl/sandbox/generated/gemini.video.js +12 -0
  27. package/dist/_sandbox/sandbox-dsl/sandbox/generated/index.js +149 -0
  28. package/dist/_sandbox/sandbox-dsl/sandbox/generated/kie.dynamic_schema.js +8 -0
  29. package/dist/_sandbox/sandbox-dsl/sandbox/generated/lib.audio.js +68 -0
  30. package/dist/_sandbox/sandbox-dsl/sandbox/generated/lib.browser.js +8 -0
  31. package/dist/_sandbox/sandbox-dsl/sandbox/generated/lib.charts.js +8 -0
  32. package/dist/_sandbox/sandbox-dsl/sandbox/generated/lib.comfy.js +16 -0
  33. package/dist/_sandbox/sandbox-dsl/sandbox/generated/lib.grid.js +8 -0
  34. package/dist/_sandbox/sandbox-dsl/sandbox/generated/lib.image.channel.js +12 -0
  35. package/dist/_sandbox/sandbox-dsl/sandbox/generated/lib.image.color.js +32 -0
  36. package/dist/_sandbox/sandbox-dsl/sandbox/generated/lib.image.color_grading.js +44 -0
  37. package/dist/_sandbox/sandbox-dsl/sandbox/generated/lib.image.draw.js +36 -0
  38. package/dist/_sandbox/sandbox-dsl/sandbox/generated/lib.image.effects.js +24 -0
  39. package/dist/_sandbox/sandbox-dsl/sandbox/generated/lib.image.enhance.js +28 -0
  40. package/dist/_sandbox/sandbox-dsl/sandbox/generated/lib.image.filter.js +64 -0
  41. package/dist/_sandbox/sandbox-dsl/sandbox/generated/lib.image.js +8 -0
  42. package/dist/_sandbox/sandbox-dsl/sandbox/generated/lib.image.keyer.js +12 -0
  43. package/dist/_sandbox/sandbox-dsl/sandbox/generated/lib.image.mask.js +16 -0
  44. package/dist/_sandbox/sandbox-dsl/sandbox/generated/lib.image.warp.js +36 -0
  45. package/dist/_sandbox/sandbox-dsl/sandbox/generated/lib.pdf.js +32 -0
  46. package/dist/_sandbox/sandbox-dsl/sandbox/generated/lib.sqlite.js +8 -0
  47. package/dist/_sandbox/sandbox-dsl/sandbox/generated/lib.stable_diffusion_cpp.js +8 -0
  48. package/dist/_sandbox/sandbox-dsl/sandbox/generated/lib.svg.js +12 -0
  49. package/dist/_sandbox/sandbox-dsl/sandbox/generated/lib.video.download.js +8 -0
  50. package/dist/_sandbox/sandbox-dsl/sandbox/generated/messaging.discord.js +8 -0
  51. package/dist/_sandbox/sandbox-dsl/sandbox/generated/messaging.telegram.js +8 -0
  52. package/dist/_sandbox/sandbox-dsl/sandbox/generated/mistral.embeddings.js +8 -0
  53. package/dist/_sandbox/sandbox-dsl/sandbox/generated/mistral.text.js +12 -0
  54. package/dist/_sandbox/sandbox-dsl/sandbox/generated/mistral.vision.js +12 -0
  55. package/dist/_sandbox/sandbox-dsl/sandbox/generated/nodetool.agents.js +32 -0
  56. package/dist/_sandbox/sandbox-dsl/sandbox/generated/nodetool.audio.js +104 -0
  57. package/dist/_sandbox/sandbox-dsl/sandbox/generated/nodetool.audio.realtime.js +28 -0
  58. package/dist/_sandbox/sandbox-dsl/sandbox/generated/nodetool.audio.synth.js +40 -0
  59. package/dist/_sandbox/sandbox-dsl/sandbox/generated/nodetool.blender.js +28 -0
  60. package/dist/_sandbox/sandbox-dsl/sandbox/generated/nodetool.code.js +8 -0
  61. package/dist/_sandbox/sandbox-dsl/sandbox/generated/nodetool.compare.js +8 -0
  62. package/dist/_sandbox/sandbox-dsl/sandbox/generated/nodetool.constant.js +132 -0
  63. package/dist/_sandbox/sandbox-dsl/sandbox/generated/nodetool.control.js +96 -0
  64. package/dist/_sandbox/sandbox-dsl/sandbox/generated/nodetool.creative.js +24 -0
  65. package/dist/_sandbox/sandbox-dsl/sandbox/generated/nodetool.data.js +12 -0
  66. package/dist/_sandbox/sandbox-dsl/sandbox/generated/nodetool.document.js +16 -0
  67. package/dist/_sandbox/sandbox-dsl/sandbox/generated/nodetool.entity.js +16 -0
  68. package/dist/_sandbox/sandbox-dsl/sandbox/generated/nodetool.fake.js +12 -0
  69. package/dist/_sandbox/sandbox-dsl/sandbox/generated/nodetool.game.js +36 -0
  70. package/dist/_sandbox/sandbox-dsl/sandbox/generated/nodetool.generators.js +24 -0
  71. package/dist/_sandbox/sandbox-dsl/sandbox/generated/nodetool.image.js +120 -0
  72. package/dist/_sandbox/sandbox-dsl/sandbox/generated/nodetool.input.js +140 -0
  73. package/dist/_sandbox/sandbox-dsl/sandbox/generated/nodetool.manimgx.js +16 -0
  74. package/dist/_sandbox/sandbox-dsl/sandbox/generated/nodetool.model3d.js +76 -0
  75. package/dist/_sandbox/sandbox-dsl/sandbox/generated/nodetool.output.js +8 -0
  76. package/dist/_sandbox/sandbox-dsl/sandbox/generated/nodetool.script.js +28 -0
  77. package/dist/_sandbox/sandbox-dsl/sandbox/generated/nodetool.sketch.js +16 -0
  78. package/dist/_sandbox/sandbox-dsl/sandbox/generated/nodetool.storyboard.js +32 -0
  79. package/dist/_sandbox/sandbox-dsl/sandbox/generated/nodetool.text.js +56 -0
  80. package/dist/_sandbox/sandbox-dsl/sandbox/generated/nodetool.timeline.js +24 -0
  81. package/dist/_sandbox/sandbox-dsl/sandbox/generated/nodetool.triggers.js +24 -0
  82. package/dist/_sandbox/sandbox-dsl/sandbox/generated/nodetool.variable.js +12 -0
  83. package/dist/_sandbox/sandbox-dsl/sandbox/generated/nodetool.video.js +136 -0
  84. package/dist/_sandbox/sandbox-dsl/sandbox/generated/nodetool.workflows.app_node.js +8 -0
  85. package/dist/_sandbox/sandbox-dsl/sandbox/generated/nodetool.workflows.base_node.js +8 -0
  86. package/dist/_sandbox/sandbox-dsl/sandbox/generated/nodetool.workflows.subgraph.js +8 -0
  87. package/dist/_sandbox/sandbox-dsl/sandbox/generated/nodetool.workflows.workflow_node.js +8 -0
  88. package/dist/_sandbox/sandbox-dsl/sandbox/generated/openai.agents.js +16 -0
  89. package/dist/_sandbox/sandbox-dsl/sandbox/generated/openai.audio.js +16 -0
  90. package/dist/_sandbox/sandbox-dsl/sandbox/generated/openai.image.js +16 -0
  91. package/dist/_sandbox/sandbox-dsl/sandbox/generated/openai.text.js +16 -0
  92. package/dist/_sandbox/sandbox-dsl/sandbox/generated/vector.js +56 -0
  93. package/dist/_sandbox/sandbox-dsl/sandbox/generated/whisper_cpp.js +8 -0
  94. package/dist/_sandbox/sandbox-dsl/sandbox/generated/xai.image.js +8 -0
  95. package/dist/_sandbox/sandbox-dsl/sandbox/generated/xai.text.js +12 -0
  96. package/dist/_sandbox/sandbox-dsl/sandbox/generated/xai.vision.js +8 -0
  97. package/dist/_sandbox/sandbox-dsl/sandbox/index.js +13 -0
  98. package/dist/_sandbox/sandbox-dsl/scripts/build.mjs +116 -0
  99. package/dist/_sandbox/sandbox-epub/SKILL.md +61 -0
  100. package/dist/_sandbox/sandbox-epub/package.json +40 -0
  101. package/dist/_sandbox/sandbox-exif/SKILL.md +30 -0
  102. package/dist/_sandbox/sandbox-exif/package.json +40 -0
  103. package/dist/_sandbox/sandbox-expr/SKILL.md +28 -0
  104. package/dist/_sandbox/sandbox-expr/package.json +40 -0
  105. package/dist/_sandbox/sandbox-fabric/SKILL.md +113 -0
  106. package/dist/_sandbox/sandbox-fabric/package.json +40 -0
  107. package/dist/_sandbox/sandbox-flow/SKILL.md +131 -0
  108. package/dist/_sandbox/sandbox-flow/package.json +411 -0
  109. package/dist/_sandbox/sandbox-flow/sandbox/generated/gemini.audio.js +12 -0
  110. package/dist/_sandbox/sandbox-flow/sandbox/generated/gemini.image.js +8 -0
  111. package/dist/_sandbox/sandbox-flow/sandbox/generated/gemini.text.js +12 -0
  112. package/dist/_sandbox/sandbox-flow/sandbox/generated/gemini.video.js +12 -0
  113. package/dist/_sandbox/sandbox-flow/sandbox/generated/kie.dynamic_schema.js +8 -0
  114. package/dist/_sandbox/sandbox-flow/sandbox/generated/lib.audio.js +68 -0
  115. package/dist/_sandbox/sandbox-flow/sandbox/generated/lib.browser.js +8 -0
  116. package/dist/_sandbox/sandbox-flow/sandbox/generated/lib.charts.js +8 -0
  117. package/dist/_sandbox/sandbox-flow/sandbox/generated/lib.comfy.js +25 -0
  118. package/dist/_sandbox/sandbox-flow/sandbox/generated/lib.grid.js +8 -0
  119. package/dist/_sandbox/sandbox-flow/sandbox/generated/lib.image.channel.js +12 -0
  120. package/dist/_sandbox/sandbox-flow/sandbox/generated/lib.image.color.js +32 -0
  121. package/dist/_sandbox/sandbox-flow/sandbox/generated/lib.image.color_grading.js +44 -0
  122. package/dist/_sandbox/sandbox-flow/sandbox/generated/lib.image.draw.js +36 -0
  123. package/dist/_sandbox/sandbox-flow/sandbox/generated/lib.image.effects.js +24 -0
  124. package/dist/_sandbox/sandbox-flow/sandbox/generated/lib.image.enhance.js +28 -0
  125. package/dist/_sandbox/sandbox-flow/sandbox/generated/lib.image.filter.js +64 -0
  126. package/dist/_sandbox/sandbox-flow/sandbox/generated/lib.image.js +8 -0
  127. package/dist/_sandbox/sandbox-flow/sandbox/generated/lib.image.keyer.js +12 -0
  128. package/dist/_sandbox/sandbox-flow/sandbox/generated/lib.image.mask.js +16 -0
  129. package/dist/_sandbox/sandbox-flow/sandbox/generated/lib.image.warp.js +36 -0
  130. package/dist/_sandbox/sandbox-flow/sandbox/generated/lib.pdf.js +32 -0
  131. package/dist/_sandbox/sandbox-flow/sandbox/generated/lib.sqlite.js +8 -0
  132. package/dist/_sandbox/sandbox-flow/sandbox/generated/lib.stable_diffusion_cpp.js +8 -0
  133. package/dist/_sandbox/sandbox-flow/sandbox/generated/lib.svg.js +12 -0
  134. package/dist/_sandbox/sandbox-flow/sandbox/generated/lib.video.download.js +8 -0
  135. package/dist/_sandbox/sandbox-flow/sandbox/generated/messaging.discord.js +11 -0
  136. package/dist/_sandbox/sandbox-flow/sandbox/generated/messaging.telegram.js +11 -0
  137. package/dist/_sandbox/sandbox-flow/sandbox/generated/mistral.embeddings.js +8 -0
  138. package/dist/_sandbox/sandbox-flow/sandbox/generated/mistral.text.js +12 -0
  139. package/dist/_sandbox/sandbox-flow/sandbox/generated/mistral.vision.js +12 -0
  140. package/dist/_sandbox/sandbox-flow/sandbox/generated/nodetool.agents.js +41 -0
  141. package/dist/_sandbox/sandbox-flow/sandbox/generated/nodetool.audio.js +110 -0
  142. package/dist/_sandbox/sandbox-flow/sandbox/generated/nodetool.audio.realtime.js +46 -0
  143. package/dist/_sandbox/sandbox-flow/sandbox/generated/nodetool.audio.synth.js +67 -0
  144. package/dist/_sandbox/sandbox-flow/sandbox/generated/nodetool.blender.js +28 -0
  145. package/dist/_sandbox/sandbox-flow/sandbox/generated/nodetool.code.js +11 -0
  146. package/dist/_sandbox/sandbox-flow/sandbox/generated/nodetool.compare.js +8 -0
  147. package/dist/_sandbox/sandbox-flow/sandbox/generated/nodetool.constant.js +132 -0
  148. package/dist/_sandbox/sandbox-flow/sandbox/generated/nodetool.control.js +165 -0
  149. package/dist/_sandbox/sandbox-flow/sandbox/generated/nodetool.creative.js +27 -0
  150. package/dist/_sandbox/sandbox-flow/sandbox/generated/nodetool.data.js +18 -0
  151. package/dist/_sandbox/sandbox-flow/sandbox/generated/nodetool.document.js +19 -0
  152. package/dist/_sandbox/sandbox-flow/sandbox/generated/nodetool.entity.js +19 -0
  153. package/dist/_sandbox/sandbox-flow/sandbox/generated/nodetool.fake.js +12 -0
  154. package/dist/_sandbox/sandbox-flow/sandbox/generated/nodetool.game.js +39 -0
  155. package/dist/_sandbox/sandbox-flow/sandbox/generated/nodetool.generators.js +30 -0
  156. package/dist/_sandbox/sandbox-flow/sandbox/generated/nodetool.image.js +126 -0
  157. package/dist/_sandbox/sandbox-flow/sandbox/generated/nodetool.input.js +143 -0
  158. package/dist/_sandbox/sandbox-flow/sandbox/generated/nodetool.manimgx.js +16 -0
  159. package/dist/_sandbox/sandbox-flow/sandbox/generated/nodetool.model3d.js +76 -0
  160. package/dist/_sandbox/sandbox-flow/sandbox/generated/nodetool.output.js +11 -0
  161. package/dist/_sandbox/sandbox-flow/sandbox/generated/nodetool.script.js +28 -0
  162. package/dist/_sandbox/sandbox-flow/sandbox/generated/nodetool.sketch.js +16 -0
  163. package/dist/_sandbox/sandbox-flow/sandbox/generated/nodetool.storyboard.js +35 -0
  164. package/dist/_sandbox/sandbox-flow/sandbox/generated/nodetool.text.js +68 -0
  165. package/dist/_sandbox/sandbox-flow/sandbox/generated/nodetool.timeline.js +24 -0
  166. package/dist/_sandbox/sandbox-flow/sandbox/generated/nodetool.triggers.js +33 -0
  167. package/dist/_sandbox/sandbox-flow/sandbox/generated/nodetool.variable.js +15 -0
  168. package/dist/_sandbox/sandbox-flow/sandbox/generated/nodetool.video.js +145 -0
  169. package/dist/_sandbox/sandbox-flow/sandbox/generated/nodetool.workflows.app_node.js +11 -0
  170. package/dist/_sandbox/sandbox-flow/sandbox/generated/nodetool.workflows.base_node.js +8 -0
  171. package/dist/_sandbox/sandbox-flow/sandbox/generated/nodetool.workflows.subgraph.js +11 -0
  172. package/dist/_sandbox/sandbox-flow/sandbox/generated/nodetool.workflows.workflow_node.js +11 -0
  173. package/dist/_sandbox/sandbox-flow/sandbox/generated/openai.agents.js +25 -0
  174. package/dist/_sandbox/sandbox-flow/sandbox/generated/openai.audio.js +16 -0
  175. package/dist/_sandbox/sandbox-flow/sandbox/generated/openai.image.js +16 -0
  176. package/dist/_sandbox/sandbox-flow/sandbox/generated/openai.text.js +16 -0
  177. package/dist/_sandbox/sandbox-flow/sandbox/generated/vector.js +56 -0
  178. package/dist/_sandbox/sandbox-flow/sandbox/generated/whisper_cpp.js +11 -0
  179. package/dist/_sandbox/sandbox-flow/sandbox/generated/xai.image.js +8 -0
  180. package/dist/_sandbox/sandbox-flow/sandbox/generated/xai.text.js +12 -0
  181. package/dist/_sandbox/sandbox-flow/sandbox/generated/xai.vision.js +8 -0
  182. package/dist/_sandbox/sandbox-flow/sandbox/guest-core.js +35 -0
  183. package/dist/_sandbox/sandbox-flow/sandbox/index.js +10 -0
  184. package/dist/_sandbox/sandbox-flow/scripts/build.mjs +120 -0
  185. package/dist/_sandbox/sandbox-game/SKILL.md +116 -0
  186. package/dist/_sandbox/sandbox-game/package.json +36 -0
  187. package/dist/_sandbox/sandbox-game/sandbox/index.js +208 -0
  188. package/dist/_sandbox/sandbox-gif/SKILL.md +36 -0
  189. package/dist/_sandbox/sandbox-gif/package.json +43 -0
  190. package/dist/_sandbox/sandbox-html/SKILL.md +107 -0
  191. package/dist/_sandbox/sandbox-html/package.json +40 -0
  192. package/dist/_sandbox/sandbox-ics/SKILL.md +41 -0
  193. package/dist/_sandbox/sandbox-ics/package.json +40 -0
  194. package/dist/_sandbox/sandbox-jmespath/SKILL.md +26 -0
  195. package/dist/_sandbox/sandbox-jmespath/package.json +43 -0
  196. package/dist/_sandbox/sandbox-mammoth/SKILL.md +46 -0
  197. package/dist/_sandbox/sandbox-mammoth/package.json +41 -0
  198. package/dist/_sandbox/sandbox-markdown/SKILL.md +123 -0
  199. package/dist/_sandbox/sandbox-markdown/package.json +41 -0
  200. package/dist/_sandbox/sandbox-notion/SKILL.md +101 -0
  201. package/dist/_sandbox/sandbox-notion/package.json +38 -0
  202. package/dist/_sandbox/sandbox-ocr/SKILL.md +47 -0
  203. package/dist/_sandbox/sandbox-ocr/package.json +40 -0
  204. package/dist/_sandbox/sandbox-pdf/SKILL.md +47 -0
  205. package/dist/_sandbox/sandbox-pdf/package.json +39 -0
  206. package/dist/_sandbox/sandbox-pdflib/SKILL.md +56 -0
  207. package/dist/_sandbox/sandbox-pdflib/package.json +38 -0
  208. package/dist/_sandbox/sandbox-pptx/SKILL.md +42 -0
  209. package/dist/_sandbox/sandbox-pptx/package.json +40 -0
  210. package/dist/_sandbox/sandbox-pptxgen/SKILL.md +49 -0
  211. package/dist/_sandbox/sandbox-pptxgen/package.json +38 -0
  212. package/dist/_sandbox/sandbox-qr/SKILL.md +43 -0
  213. package/dist/_sandbox/sandbox-qr/package.json +42 -0
  214. package/dist/_sandbox/sandbox-rrule/SKILL.md +37 -0
  215. package/dist/_sandbox/sandbox-rrule/package.json +43 -0
  216. package/dist/_sandbox/sandbox-stats/SKILL.md +42 -0
  217. package/dist/_sandbox/sandbox-stats/package.json +43 -0
  218. package/dist/_sandbox/sandbox-subtitle/SKILL.md +38 -0
  219. package/dist/_sandbox/sandbox-subtitle/package.json +41 -0
  220. package/dist/_sandbox/sandbox-supabase/SKILL.md +108 -0
  221. package/dist/_sandbox/sandbox-supabase/package.json +39 -0
  222. package/dist/_sandbox/sandbox-tfjs/SKILL.md +76 -0
  223. package/dist/_sandbox/sandbox-tfjs/package.json +42 -0
  224. package/dist/_sandbox/sandbox-timeline/SKILL.md +647 -0
  225. package/dist/_sandbox/sandbox-timeline/package.json +46 -0
  226. package/dist/_sandbox/sandbox-timeline/sandbox/charts.js +356 -0
  227. package/dist/_sandbox/sandbox-timeline/sandbox/components.js +171 -0
  228. package/dist/_sandbox/sandbox-timeline/sandbox/index.d.ts +700 -0
  229. package/dist/_sandbox/sandbox-timeline/sandbox/index.js +1658 -0
  230. package/dist/_sandbox/sandbox-tokens/SKILL.md +46 -0
  231. package/dist/_sandbox/sandbox-tokens/package.json +40 -0
  232. package/dist/_sandbox/sandbox-twilio/SKILL.md +93 -0
  233. package/dist/_sandbox/sandbox-twilio/package.json +39 -0
  234. package/dist/_sandbox/sandbox-xlsx/SKILL.md +74 -0
  235. package/dist/_sandbox/sandbox-xlsx/package.json +40 -0
  236. package/dist/_sandbox/sandbox-xml/SKILL.md +36 -0
  237. package/dist/_sandbox/sandbox-xml/package.json +40 -0
  238. package/dist/_sandbox/sandbox-yaml/SKILL.md +48 -0
  239. package/dist/_sandbox/sandbox-yaml/package.json +41 -0
  240. package/dist/_sandbox/sandbox-zip/SKILL.md +54 -0
  241. package/dist/_sandbox/sandbox-zip/package.json +40 -0
  242. package/dist/code-node-validation.d.ts +2 -0
  243. package/dist/code-node-validation.d.ts.map +1 -1
  244. package/dist/code-node-validation.js +1 -1
  245. package/dist/code-node-validation.js.map +1 -1
  246. package/dist/pack-loader.d.ts +1 -0
  247. package/dist/pack-loader.d.ts.map +1 -1
  248. package/dist/pack-loader.js +1 -0
  249. package/dist/pack-loader.js.map +1 -1
  250. package/dist/sandbox-catalog-host.js +1 -1
  251. package/dist/sandbox-catalog-host.js.map +1 -1
  252. package/dist/sandbox-pack-discovery.d.ts +3 -0
  253. package/dist/sandbox-pack-discovery.d.ts.map +1 -1
  254. package/dist/sandbox-pack-discovery.js +7 -3
  255. package/dist/sandbox-pack-discovery.js.map +1 -1
  256. package/package.json +5 -5
@@ -0,0 +1,647 @@
1
+ ---
2
+ name: sandbox-timeline
3
+ description: Author a motion-graphics timeline as code — scenes, layout containers, motion primitives and transitions, saved in one call
4
+ ---
5
+
6
+ # Timelines as code
7
+
8
+ Specifier: `@nodetool-ai/sandbox-timeline`. One module. It builds a whole
9
+ timeline document in plain JavaScript and saves it through
10
+ `nodetool.timelines`, so a cut with hundreds of keyframed clips costs a
11
+ handful of API calls instead of one `edit` op per clip. NodeTool's shipped
12
+ example timelines (Cadence, Kite, Prism, Voltra, Tidewater, T minus 30) are built with
13
+ it.
14
+
15
+ Use it for authored motion graphics: kinetic type, shapes, charts, placed
16
+ stills, transitions between scenes. For a few changes to a cut that already
17
+ exists, `nodetool.timelines.edit(id, ops)` is still the better tool.
18
+
19
+ **Units are seconds, everywhere, local to the scope that created them.** A
20
+ scene's own clock starts at 0. There is no absolute-time option — `v.series()`
21
+ is the only place a scene's position in the whole video is decided, and it is
22
+ the only function that ever shifts a time. Author a moment once, at the time
23
+ it happens in its own scene, and it is correct wherever that scene lands.
24
+
25
+ ## A two-scene piece
26
+
27
+ ```js
28
+ import { video } from "@nodetool-ai/sandbox-timeline";
29
+
30
+ const v = video({
31
+ width: 1920, height: 1080, fps: 30,
32
+ palette: { ink: "#06110e", ink2: "#0c2a21", text: "#f4fbf8", accent: "#34d399" },
33
+ fonts: { display: "Space Grotesk", body: "Inter" }
34
+ });
35
+
36
+ const hello = v.scene("Hello", 2.5, (s) => {
37
+ s.backdrop();
38
+ const title = s.text("Hello, timeline", { size: 120, weight: 700, font: "display" });
39
+ const sub = s.text("built from code", { size: 40, color: "#8fb3a6" });
40
+ s.stack([title, sub], { gap: 20 });
41
+ title.enter({ from: { scale: 1.2, opacity: 0 }, at: 0, dur: 0.3, ease: "outExpo" });
42
+ sub.enter({ from: { offsetY: 24, opacity: 0 }, at: 0.15, dur: 0.25 });
43
+ title.exit({ to: { opacity: 0 }, dur: 0.2 });
44
+ });
45
+
46
+ const outro = v.scene("Outro", 1.5, (s) => {
47
+ s.backdrop({ colors: ["#0f3b2e", "#06110e"] });
48
+ const cta = s.pill("Try it", { fillColor: "#f4fbf8", color: "#06110e" });
49
+ cta.enter({ from: { scale: 0.6, opacity: 0 }, dur: 0.25 });
50
+ });
51
+
52
+ v.series([hello, v.transition("wipe", 0.3, { direction: "left" }), outro]);
53
+ v.adjust([{ type: "vignette", amount: 0.2 }], { name: "finish", trackId: "t_finish" });
54
+
55
+ const saved = await v.save(nodetool.timelines, { name: "Hello", showcase: true });
56
+ await output("timeline", saved); // { timeline_id, errors, warnings }
57
+ ```
58
+
59
+ Then look at it: `nodetool.timelines.preview(saved.timeline_id, {count: 3})`
60
+ returns composited frames, and `render(id, {preview_scale: 0.5})` a draft
61
+ video.
62
+
63
+ ## Study a reference and reuse scene helpers
64
+
65
+ `nodetool.timelines.examples.list()` lists shipped cuts with structural stats.
66
+ `nodetool.timelines.examples.get("kite", {clip_limit: 12})` returns its scene
67
+ catalog and a bounded scene excerpt. Select another `scene_id` or page with
68
+ `clip_offset` to read more layers. Inspect the clip hierarchy, curve windows
69
+ and effects before choosing your own scene structure.
70
+
71
+ `nodetool.timelines.examples.source("kite")` returns the script that made
72
+ that excerpt: full JavaScript, imports included.
73
+
74
+ ## `video({width, height, fps, palette, fonts})`
75
+
76
+ The entry point. `palette` supplies colours the craft helpers fall back to:
77
+ `ink`, `ink2`, `text`, `dim`, `accent`. `fonts` maps short keys (`display`,
78
+ `body`) to real family names; a text element's `font` option takes either a
79
+ key or a literal family string.
80
+
81
+ ## Scenes and series
82
+
83
+ `v.scene(name, seconds, fn)` builds one scene at its own local clock, `fn(s)`
84
+ receiving the scene's authoring object. It returns a scene descriptor — nothing
85
+ is placed in the document yet.
86
+
87
+ `v.series([scene, transition?, scene, transition?, scene, ...])` lays scenes
88
+ end to end and assigns every clip's absolute position. A `v.transition(type,
89
+ seconds, opts)` item between two scenes overlaps them by its own duration and
90
+ sets `transitionIn` on the incoming scene's group; omitting one between a pair
91
+ makes a hard cut. `type` is one of the document's transition types (`"wipe"`,
92
+ `"whip"`, `"iris"`, `"gradientWipe"`, `"crossfade"`, `"push"`, `"slide"`,
93
+ `"zoom"`, `"zoomBlur"`, `"glitch"`, `"dipToColor"`, `"lightLeak"`); `opts`
94
+ carries that type's own fields (`direction`, `easing`, `color`, `softness`,
95
+ `map`, `seed`, …). Scenes are banked onto two alternating track sets so an
96
+ overlapping pair never shares a track — you never see or name a track.
97
+
98
+ `v.series` must run before `v.adjust`, `v.document` or `v.save`.
99
+
100
+ ## Local time nests
101
+
102
+ Everything inside a scene builder is measured from that scene's own start.
103
+ `s.seq(atSec, durSec, fn)` opens a nested local clock: `fn(q)` receives a
104
+ scoped authoring object whose own `atSec`/`durationSec` for elements and
105
+ motion are relative to `atSec` *inside the enclosing scope*, not to the whole
106
+ video. Nest as deep as you like — the shift out to absolute time happens once,
107
+ in `v.series()`.
108
+
109
+ ```js
110
+ // fragment
111
+ v.scene("Beat", 4, (s) => {
112
+ s.seq(1.2, 1.0, (q) => {
113
+ const chip = q.text("+$0.60", {});
114
+ chip.enter({ from: { offsetY: 20, opacity: 0 }, at: 0, dur: 0.2 }); // 1.2s into the scene
115
+ });
116
+ });
117
+ ```
118
+
119
+ ## Elements
120
+
121
+ Every element is a scene method returning a clip object with motion methods
122
+ attached. `at`/`dur` (seconds, defaulting to the whole enclosing scope) narrow
123
+ where in that scope the clip lives; everything else is the clip's own fields.
124
+
125
+ | Call | Makes |
126
+ |---|---|
127
+ | `s.text(str, o)` | a text clip. `o.size`, `o.weight`, `o.color`, `o.font` (a `fonts` key or a literal family), `o.tracking` (em, a fraction of size: `-0.04` tight, `0.2` wide caps), `o.italic`, `o.anchor` `"left"`/`"right"`/`"center"`, `o.mw` max width as a frame fraction, `o.path` (text-on-path: the same point commands `s.path()` takes), `o.fill` a colour or a gradient (same as `style.fill`, below), `o.style` — passthrough for any other `textStyle` field (`lineHeight`, `verticalAlign`, `stroke`, `shadow`, `background`) — an unrecognized top-level option throws, naming it and pointing at `style` |
128
+ | `s.rect(w, h, fill, o)`, `s.ellipse(d, fill, o)` | shape clips; `fill` a colour or a gradient `{type, angle\|stops, ...}`; `o.r` corner radius, `o.stroke`, `o.sw` |
129
+ | `s.path(points, o)` | an SVG path from `[["M", x, y], ["L", x, y], …]` in px from the frame centre. `H x`/`V y` take one coordinate on their own axis; `A rx ry xRotDeg largeArc sweep x y` scales the two radii as sizes and passes the angle and the two flags through unscaled |
130
+ | `s.image(assetId, o)` | a placed still. `assetId` takes what the asset tools print (`asset://<id>.<ext>`, a 12-character id, or the full id); the clip stores the bare id and `v.save()` resolves it to the full one. `package://` and `https://` locators pass through |
131
+ | `s.video(assetId, o)`, `s.audio(assetId, o)` | a placed video or audio clip. `o.in` (seconds into the source), `o.volume` (dB), `o.fadeIn`/`o.fadeOut` (seconds), `o.speed` (playback rate), `o.mute`, `o.remap` (a time remap: `[{t, sourceAt, ease?}, …]`, `t` 0..1 across the clip, `sourceAt` **seconds into the source media**, not scene-local time) |
132
+ | `s.group(o)` | a nested group; children reparent onto it with `{parent: g.id}` |
133
+ | `s.adjust(effects, o)` | an adjustment layer scoped to this scene — its clip's parent is the scene's own group, so it treats that scene's surface and nothing outside it (an adjustment is group-scoped by construction). This is the way to grade one scene: a per-scene duotone, a vignette that only applies to one beat |
134
+ | `s.fill(color, o)` | a full-frame rect, same as `s.rect(W, H, color, {x:0, y:0, ...o})` |
135
+
136
+ `x`/`y` are px from the frame centre, or from the parent's own position when
137
+ the clip is reparented into a group. `rotation` (degrees) and `anchor` ({x, y}
138
+ pivot fraction, default `{0.5, 0.5}`) are friendly options for the two
139
+ `transform` fields authored most; `tx: {extra fields}` is the raw escape
140
+ hatch for the rest (`rotationX`/`rotationY`, `perspective`, `depthPx`, …), and
141
+ a full `transform: {position, scale, rotation, anchor, depthPx, ...}` override
142
+ still works when nothing else fits. Every element option also takes clip
143
+ fields as-is: `effects` (an `id`/`enabled: true` an entry leaves out is filled
144
+ in — write `{type: "glow", radius: 30, ...}`, not `{id: "...", enabled: true,
145
+ type: "glow", ...}`), `opacity`, `blendMode`, `mask` (a static crop mask —
146
+ `ClipMask`, `{kind: "rect"|..., ...}` — not the wipe mask `enter()`/`animate()`
147
+ take, below), `matte`, `crop`, `motionBlur`, `steppedTime`, `repeater`,
148
+ `inPointMs`, `outPointMs`, `speedBaked`, `fadeInShape`, `fadeOutShape`. There
149
+ is no field a clip carries that needs mutating after creation — anything
150
+ `video`/`audio`/`text` don't name a friendly option for still passes through
151
+ by its own document field name.
152
+
153
+ ## Layout containers
154
+
155
+ `s.stack(children, o)` and `s.row(children, o)` build a real CSS/Yoga flex
156
+ container (`layout: {display: "flex", flexDirection: "column" | "row", gap,
157
+ alignItems, justifyContent, padding, width, height}`) and reparent `children`
158
+ onto it (`child.parentId = container.id`) — real nesting, not a sibling list.
159
+ Nest freely: a container can be a child of another container (a `row` of
160
+ `stack`s, a `stack` holding a nested `row`), and the renderer's Yoga resolver
161
+ lays out the whole tree.
162
+
163
+ The container's own placement in the frame is `o.at` (`{x, y}`, default the
164
+ frame centre) plus `o.anchor` (default `"center"`). `at` is canvas-centre-relative
165
+ — the same coordinate space every clip's own position already is: `{x: 0, y: 0}`
166
+ is frame centre, negative `x` is left of centre, negative `y` is above it.
167
+ `anchor` names the point of the container's own *computed* box that lands at
168
+ `at` — `{x: 0, y: 0.5}` or the name `"left"` put that box's left edge at
169
+ `at.x`, no measuring a sibling's width required. Example: on a 1920-wide
170
+ frame, `anchor: "left"` with `at: {x: -860, y: -212}` puts that left edge
171
+ near the frame's own left margin (-860 is 960px left of centre, i.e. near
172
+ x=100 on screen); `at: {x: 110, ...}` would instead land it just right of
173
+ centre. Named anchors: `"top-left"`, `"top"`, `"top-right"`, `"left"`,
174
+ `"center"`, `"right"`, `"bottom-left"`, `"bottom"`, `"bottom-right"`, or pass
175
+ `{x, y}` fractions directly. A container's own *time* window is `o.start`/
176
+ `o.dur` (seconds) — the one place in this API `at` doesn't mean seconds,
177
+ because the container's placement already claims it.
178
+
179
+ `o.gap` (px), `o.padding` (px, or `{top, right, bottom, left}`), `o.width`/
180
+ `o.height` (px or `"N%"`) size the container. `o.parent` nests it inside
181
+ another clip (e.g. a phone mockup) instead of the scene root. `o.wrap` turns
182
+ on `flexWrap`.
183
+
184
+ `o.align` is `"start"` (default), `"center"`, `"end"`, `"stretch"` or
185
+ `"baseline"` — the cross axis (`alignItems`): a row's shared y, a stack's
186
+ shared x. `o.justify` is `"start"`, `"center"`, `"end"`, `"between"`,
187
+ `"around"` or `"evenly"` — the main axis (`justifyContent`), unset by default
188
+ (children pack at the start with the authored gap). A `stack`/`row` call with
189
+ an unknown `align`/`justify` throws, naming the value.
190
+
191
+ A child's own flex sizing/placement is `o.flexItem` on that child's own call
192
+ (`grow`, `shrink`, `basis`, `alignSelf`, `width`, `height`, `margin`,
193
+ `position: "absolute"`, `inset`) — pass it through any element's own options,
194
+ e.g. `s.rect(..., { flexItem: { grow: 1 } })`. `s.rect`/`s.ellipse` also take
195
+ `absolute: true` as sugar for `flexItem: {position: "absolute", inset: 0}` —
196
+ taken out of flow and resized to fill its flex parent's box exactly, which is
197
+ how a pill's plate or a card's backdrop sits behind its content: create the
198
+ background *before* the foreground children (so its `_z` draws below), then
199
+ reparent it onto the container.
200
+
201
+ **A leaf's flex-measured size is its own content, never a guessed line
202
+ height** — a text child reflows to whatever width Yoga offers, and a text's
203
+ own `mw` does nothing once it sits inside a `stack`/`row`: its width comes
204
+ from the container, not from itself, so don't pass `mw` to a text child of a
205
+ container — leave it unset there. `mw` still matters on a standalone text (one
206
+ placed directly with `at`/`x`/`y`, no enclosing container); a shape/ellipse's
207
+ size is its
208
+ own `shapeStyle.width`/`height` (unset defaults to 0, not the frame, so an
209
+ undecorated plate doesn't force its siblings into a shrink negotiation); a
210
+ **plain (non-flex) group nested as a leaf measures as its children's own union
211
+ box** — give a plain group wrapping e.g. an icon + a drawn path (each a leaf
212
+ with its own small authored size) an explicit `flexItem: {width, height}`
213
+ when any of its children is a bare `path()` (no authored `shapeStyle.width`/
214
+ `height` of its own — `path()` never sets one), since that child's
215
+ un-measured intrinsic size falls back to the whole frame and the group would
216
+ measure as canvas-sized.
217
+
218
+ `s.center(child, o)` repositions `child` directly at `o.at` (default the frame
219
+ centre) — for a single element, no container needed.
220
+
221
+ ## Motion primitives
222
+
223
+ All local to the clip's own start (`at: 0` is the moment the clip appears),
224
+ unless `stagger()` set a default `at` for it.
225
+
226
+ | Call | Does |
227
+ |---|---|
228
+ | `el.enter({from, at, dur, ease, mask})` | animates from `from`'s values to each property's rest pose (`opacity`/`scale`/`scaleX`/`scaleY`/`trimEnd`/`wipeProgress` rest at `1`, everything else at `0`). Always an "in" — there is no flip to solve, because the end value is always rest. |
229
+ | `el.exit({to, at, dur, ease})` | animates from each property's rest pose to `to`'s values. `at` defaults to the clip's own end minus `dur`. Always an "out". |
230
+ | `el.animate(props, {at, dur, ease, role, mask})` | a general keyframed animation. Each `props` value is `[from, to]`, `[from, to, easing]`, or a waypoint list `[[t, value], [t, value, easing], …]` with `t` from 0 to 1 across the window. |
231
+ | `el.loop(props, periodSec, {ease})` | a looping animation with a cycle of `periodSec`; `ease` defaults `"linear"` (a spin or a scroll needs constant speed). |
232
+ | `el.count({from, to, at, dur, prefix, suffix, decimals, padTo, groupSeparator, ease})` | a ticker text animator; `padTo` zero-pads the digit count (`padTo: 2` → `"09"`) |
233
+ | `el.scramble({at, dur, charset, seed, ease})` | a scramble-in text reveal (the ticker's sibling `textAnimator`) |
234
+ | `el.tween(target, keyframes, {at, dur, by, staggerMs})` | a style track, `target` like `"shape.fill"`, `"effect.<id>.amount"` or a `glyph.*` field (`glyph.color`, `glyph.blurPx`, `glyph.trackingPx`, …); `keyframes` like `animate`'s waypoint list |
235
+ | `el.draw({at, dur, ease})` | draw a path or stroke on, via `trimEnd` — `enter({from: {trimEnd: 0}, ...})` |
236
+ | `el.typewriter({at, dur, caret})` | a typewriter reveal; `set_timeline_document` resolves `dur` to one reveal per character. **Replaces** the clip's animations — a typewriter is the whole entrance. `caret`: `{color, widthPx, blinkPeriodMs}` |
237
+ | `el.preset(name, {at, dur, ease, role, ...params})` | a catalog animation baked server-side from its own curve generator, not hand-authored keyframes: `"fade"`, `"slide"`, `"pop"`, `"spin"`, `"wipe"`, `"blur"`, `"colorFade"`, `"pulse"`, `"flash"`, `"shake"`, `"bounce"`, `"squash"`, `"kenBurns"`, `"float"`, `"breathe"`, `"rotate"`, `"hueShift"`, `"orbit"`, `"followPath"`, `"typewriter"`. Every key in `o` besides `at`/`dur`/`ease`/`role`/`by`/`staggerMs` is a preset param — see `PresetParamSpec[]` per preset in `packages/timeline/src/animation/presets.ts` for names, defaults and ranges |
238
+
239
+ `enter`/`animate`/`count`/`tween` take `by` (`"word"`/`"character"`/`"line"`)
240
+ and `staggerMs` to stagger across the units of one text clip — a headline that
241
+ rises in word by word, a wordmark that types in letter by letter. **A
242
+ `glyph.*` `tween()` target needs a stagger** — the document validator refuses
243
+ one without a unit, since animating per-glyph style with no split is the
244
+ un-staggered block case `enter`/`animate` already cover. `tween()` left with
245
+ no `by` of its own inherits the stagger of the last `enter()`/`animate()` call
246
+ on the *same* clip, so a `glyph.color` tween riding the same word-by-word
247
+ reveal as its `enter()` needs no repeated `by`:
248
+
249
+ ```js
250
+ // fragment
251
+ const title = s.text("Every purchase", { anchor: "left" });
252
+ title.enter({ from: { offsetY: 40, opacity: 0 }, by: "word", staggerMs: 90 });
253
+ title.tween("glyph.color", [[0, "#8fb3a6"], [1, "#f4fbf8"]]); // inherits by: "word"
254
+ ```
255
+
256
+ **A `wipeProgress` animation needs `mask: {direction, softness}`** — a wipe
257
+ reveal has nowhere else to say which edge it sweeps from. Pass it on the same
258
+ `enter()`/`animate()` call that drives `wipeProgress`, not as a follow-up
259
+ mutation:
260
+
261
+ ```js
262
+ // fragment
263
+ area.animate({ wipeProgress: [0, 1] }, { at: 0.4, dur: 1.3, mask: { direction: "left", softness: 0.02 } });
264
+ ```
265
+
266
+ `s.stagger(items, offsetSec, fn)` offsets several **already-created**
267
+ elements' default motion `at` by `index * offsetSec`, then calls `fn(item,
268
+ index)`:
269
+
270
+ ```js
271
+ // fragment
272
+ const words = ["Round-ups", "on", "every", "card"].map((w) => s.text(w, { anchor: "left" }));
273
+ s.stagger(words, 0.06, (w) => w.enter({ from: { offsetY: 40, opacity: 0 } }));
274
+ ```
275
+
276
+ `ease` names: `"in"`, `"out"`, `"inOut"`, `"outExpo"`, `"inExpo"`, `"linear"`,
277
+ or a raw curve string such as `"spring(170,16,1)"` or
278
+ `"cubic-bezier(0.65,0,0.35,1)"`.
279
+
280
+ ## Craft
281
+
282
+ Scene methods for the recurring structural moves. Every helper takes seconds
283
+ and px like everything else, and every option has a default, so
284
+ `s.backdrop()` alone renders a full bleed. `palette` supplies the colours a
285
+ call does not override.
286
+
287
+ | Helper | Signature | Makes |
288
+ |---|---|---|
289
+ | `s.backdrop(o?)` | `{colors: [a, b], opacity, seed}` | full-frame ink base + an animated `gradientField` wash |
290
+ | `s.glow(o?)` | `{size, alpha, color, ...}` | radial glow fading to transparent |
291
+ | `s.flash(o?)` | `{at, dur, peak, color}` | full-frame screen-blend flash, peak to 0 |
292
+ | `s.kicker(str, o?)` | `{x, y, at, dur, size, weight, color, font}` | uppercase label, left-wipe entrance |
293
+ | `s.pill(label, o?)` | `{size, weight, color, fillColor, stroke, dot, shadow, padX, padY, x, y, anchor, at, dur, font}` | a flex row (optional leading dot + label) over an absolute inset-0 background plate, optional leading dot |
294
+ | `s.streaks(o?)` | `{count, colors, rotationDeg, opacity}` | phase-shifted repeater bars racing off frame |
295
+ | `s.finish(o?)` | `{vignette, softness, grain, seed, saturation, contrast}` | scene-scoped grain + vignette adjustment |
296
+
297
+ **`s.backdrop({colors: [a, b]})` uses `a` twice** — as the solid base rect
298
+ underneath, and as the first stop of the animated `gradientField` wash on top
299
+ of it. There is no separate base colour: pass `colors: [a, b]` to mean "base
300
+ and wash both start at `a`, wash drifts toward `b`", not "a solid `a` behind a
301
+ `b`-to-something wash".
302
+
303
+ A slam, a rise, or a grow are `el.enter()`/`el.animate()` calls with the right
304
+ `from`/props — there is no separate name for them:
305
+
306
+ ```js
307
+ // fragment
308
+ title.enter({ from: { scale: 1.35, blur: 26, opacity: 0 }, dur: 0.27, ease: "outExpo" }); // slam
309
+ sub.enter({ from: { offsetY: 24, opacity: 0 }, dur: 0.3 }); // rise
310
+ bar.animate({ scaleY: [0, 1], offsetY: [h / 2, 0] }, { dur: 0.4, ease: "out" }); // grow from its base
311
+ ```
312
+
313
+ ## Components
314
+
315
+ `component(name, {props, duration}, (s, props) => {...})` — a reusable piece
316
+ (a lower third, a stat card, a CTA end card) with typed props, used inside a
317
+ scene with `s.use(Comp, {...props}, {at, dur})`. `props` maps a name to
318
+ `{type: "string"|"number"|"color"|"boolean", default, description?}`;
319
+ `duration` (seconds) is the piece's own natural length, used when `use()`
320
+ gets no `dur`. `build(s, props)` is a scene builder exactly like `scene()`'s
321
+ own — it adds clips with `s.text`/`s.rect`/craft helpers/nested `use()` the
322
+ usual way; `use()` folds everything the build left at the enclosing scope
323
+ into one group, the way `pill()` folds its own dot+label+plate.
324
+
325
+ ```js
326
+ // fragment
327
+ const Badge = component("Badge", {
328
+ props: {
329
+ label: { type: "string", default: "New" },
330
+ tone: { type: "color", default: "#34d399" }
331
+ },
332
+ duration: 1
333
+ }, (s, p) => {
334
+ s.pill(p.label, { fillColor: p.tone });
335
+ });
336
+
337
+ v.scene("Hero", 3, (s) => {
338
+ s.use(Badge, { label: "Live now" }, { at: 0.5 });
339
+ });
340
+ ```
341
+
342
+ `use()` checks `propsIn` against `Comp.props` and fills defaults, throwing
343
+ and naming the offending prop on a type mismatch or an unknown name — the
344
+ same failure a bad `caret`/`align` value gets from the type declarations
345
+ below.
346
+
347
+ ### Saving a component as a composition
348
+
349
+ `Comp.saveAsComposition(nodetool.timelines, {width, height, fps, palette?,
350
+ fonts?, name?, description?})` bridges a component into the editor's
351
+ composition system (`list_compositions`/`get_composition`/
352
+ `insert_composition`): it builds `Comp` at its prop defaults into a scratch
353
+ timeline, then rebuilds it once per prop with a value distinct from that
354
+ default and diffs the two builds to find the one JSON pointer
355
+ (`packages/timeline/src/composition.ts`'s `path`) that prop reaches — the
356
+ same field a hand-written `save_composition` call would have to name. It
357
+ requires `{width, height, fps}` because a bare `component()` descriptor
358
+ carries none of its own; a call with no matching field found (unused,
359
+ reaching more than one field, or the prop only toggles whether a field
360
+ exists at all rather than setting its value) leaves that prop out of the
361
+ saved composition and lists it in `skippedParams` rather than saving a
362
+ pointer that would write the wrong thing.
363
+
364
+ **A nested `stack`/`row` inside a component round-trips too.** A composition
365
+ captures the group's whole descendant subtree, not just its direct
366
+ children, so a card holding a row of icons saves and re-inserts with the
367
+ row's own children intact and correctly nested. `saveAsComposition`'s prop
368
+ probing walks the same subtree, so a prop reaching into a nested container's
369
+ field still gets a pointer — only a prop with no single-field effect
370
+ (unused, structural, or toggling whether a field exists rather than setting
371
+ it) ends up in `skippedParams`.
372
+
373
+ ## Charts
374
+
375
+ Motion graphics are often data. Every chart is built from the same shapes
376
+ and text everything else uses — bars, a line, a filled area, a ring — with
377
+ growth, staggering, and a value that counts up baked in, so it composes with
378
+ `s.stack`/`s.row`/parenting like any other element. Each returns `{group,
379
+ ...pieces}`: move/parent `group` like any other clip; the pieces (`bars`,
380
+ `line`, `segments`, …) are the real shapes and labels for anything you want
381
+ to touch individually.
382
+
383
+ | Call | Makes |
384
+ |---|---|
385
+ | `s.barChart(data, o?)` | bars that grow from the baseline (staggered), each with a value label that counts up and finishes inside its own bar's growth window. `data`: `[number, ...]` or `[{value, label}, ...]`. `o`: `w`, `h`, `orientation` `"vertical"\|"horizontal"`, `gap`, `rounded`, `labels`/`axisLabels` (on/off), `stagger`, `at`, `dur`, `ease`, `highlight` (index), `color`, `palette`, `format` |
386
+ | `s.lineChart(series, o?)` | a line that draws on (`trimEnd`), points scaled into a `w`×`h` box, optional dot markers, an end value that counts from the first point to the last. `series`: `[number, ...]` or `[{x, y}, ...]` |
387
+ | `s.areaChart(series, o?)` | `lineChart`'s filled sibling — the same points close into a baseline-filled shape that grows up from the bottom, with an optional stroked line drawn on top |
388
+ | `s.donut(data, o?)` | a ring, one stroked arc per datum sweeping in from 12 o'clock, staggered; a center label counts up to the total. `o`: `d` (diameter), `thickness`, `gap` (degrees between segments), `highlight`, `palette` |
389
+ | `s.statCounter(value, o?)` | a single number that counts up — `barChart`'s value-label building block, standalone for a callout number that isn't part of a chart |
390
+
391
+ ```js
392
+ // fragment
393
+ s.barChart([420, 680, 1150], { at: 0.2, stagger: 0.08, highlight: 2, format: { prefix: "$" } });
394
+ s.donut([{ value: 62, label: "Rent" }, { value: 38, label: "Everything else" }], { highlight: 0 });
395
+ ```
396
+
397
+ ## Top-level adjustments and audio
398
+
399
+ `v.adjust(effects, o)` runs after `v.series()`. It places a timed adjustment
400
+ layer in absolute video seconds (`o.at`, `o.dur`, default the whole video) on
401
+ its own track above every scene bank, so it always sees the full composite —
402
+ a whole-video finish (grain/vignette), or a short local burst at a cut (a
403
+ glitch spanning both sides of a hard cut). Give two calls that can overlap in
404
+ time distinct `o.trackId` values; one video track cannot hold two overlapping
405
+ clips.
406
+
407
+ `v.audio(assetId, o)` places an audio clip in absolute video seconds (`o.at`,
408
+ `o.dur`, default the whole video) on its own audio track (default `t_audio`)
409
+ — a score, a countdown, a narration bed spanning (part of) the series. Same
410
+ friendly fields as a scene's `s.audio()`: `o.volume`, `o.fadeIn`, `o.fadeOut`,
411
+ `o.mute`. `v.music(assetId, o)` is `v.audio` on its own `t_music` track, so a
412
+ score and a narration bed placed separately never collide; give either call
413
+ `o.trackId` to land somewhere else. Both also run after `v.series()`.
414
+
415
+ `v.document({camera2d, trackFolders, tempo})` sets extra top-level document
416
+ fields verbatim, applied before save.
417
+
418
+ `v.durationMs` is the video's total duration in ms, set by `v.series()` —
419
+ read it instead of computing your own total from scene durations.
420
+
421
+ ## Audio-reactive motion
422
+
423
+ `el.react(audioClip, o)` drives one property from a piece of audio, with no
424
+ per-frame JS: it records the request, and `v.save()` bakes it onto the clip
425
+ right after the document is written, through `bake_audio_animation` (the
426
+ same op `nodetool.timelines.bakeAudioAnimation` exposes directly). It
427
+ composes with a hand-picked entrance the way two animations on the same
428
+ property always do (they multiply), never replacing it. `audioClip` is the
429
+ clip `s.audio()`/`v.audio()` returned — pass the element, not an asset id.
430
+
431
+ ```js
432
+ // fragment
433
+ const kick = s.audio(kickAssetId, { name: "kick" });
434
+ const title = s.text("DROP", { size: 120 });
435
+ title.react(kick, { prop: "scale", mode: "beats", range: [1, 1.3], smooth: 0.05 });
436
+ ```
437
+
438
+ `o.prop`: `"scale"|"opacity"|"offsetX"|"offsetY"`. `o.mode`: `"envelope"`
439
+ (follows loudness continuously, the default) or `"beats"` (one pulse per
440
+ detected onset). `o.range`: `[quiet, loud]`, defaulted per property.
441
+ `o.gain` (sensitivity), `o.smooth` (seconds, sets both `attack`/`release`),
442
+ `o.offset` (seconds, shifts the whole curve), `o.replace` (default true).
443
+
444
+ **No frequency band.** `bake_audio_animation` measures the full mix
445
+ (`envelope`) or its onsets (`beats`) — there is no band-pass filter behind
446
+ it, so `o.band` throws rather than silently reacting to the wrong thing.
447
+ Isolate that range in the source audio first if a cut needs it.
448
+
449
+ ## Hand-written motion and shape morphs
450
+
451
+ `el.expr(fn, {at, dur, role})` bakes a hand-written motion function to a
452
+ keyframe-per-frame custom animation at authoring time — nothing runs at
453
+ render time. `fn(t, {p, frame, fps})` is sampled once per frame across the
454
+ window: `t` is seconds local to the window, `p` its 0..1 progress. `fn`
455
+ returns an object of animatable properties (`opacity`, `offsetX`, `scale`,
456
+ …) and/or style-track targets (any key with a `.`, e.g. `"text.color"`,
457
+ `"shape.fill"`). A property whose samples never change emits no curve;
458
+ a curve that does change is reduced to the fewest keyframes a straight-line
459
+ interpolation still reproduces within a small tolerance — a linear ramp
460
+ collapses to two keyframes, a sine keeps enough to stay recognizably a sine.
461
+
462
+ ```js
463
+ // fragment
464
+ bar.expr((t) => ({ offsetY: Math.sin(t * Math.PI * 2) * 12 }), { at: 0, dur: 2 }); // a gentle bob
465
+ ```
466
+
467
+ `s.noise(seed)` returns a deterministic 1D value-noise function — no
468
+ `Math.random`, so the same script produces the same motion every run.
469
+ `s.noise(seed)(x)` is a smooth 0..1 value; feed it an increasing `x` (`t`, or
470
+ `frame`) inside an `expr()` for organic jitter that never repeats:
471
+
472
+ ```js
473
+ // fragment
474
+ const jitter = s.noise(7);
475
+ leaf.expr((t) => ({ rotation: (jitter(t * 4) - 0.5) * 0.2 }), { dur: 3 });
476
+ ```
477
+
478
+ `el.morph(toPoints, {at, dur, ease})` morphs a path shape's outline toward
479
+ `toPoints` (the same point commands `s.path()` takes) via a `shape.d` style
480
+ track. Only straight (`M`/`L`/`Z`) paths morph — the document only
481
+ interpolates a style track when both paths share the same command sequence
482
+ and token count, so `morph()` resamples the clip's own current outline and
483
+ the target to the same vertex count by even arc length first, then rewrites
484
+ the clip's own `shapeStyle.d` to that resampled start shape so the morph's
485
+ first frame matches what was already on screen. A curved path (`C`/`A`/…)
486
+ throws rather than authoring an incompatible morph — approximate it with more
487
+ `L` segments first:
488
+
489
+ ```js
490
+ // fragment
491
+ const mark = s.path([["M", -50, -50], ["L", 50, -50], ["L", 0, 50], ["Z"]], { fill: MINT }); // a triangle
492
+ mark.morph(starPoints, { at: 0.2, dur: 0.5, ease: "out" }); // -> a six-point star
493
+ ```
494
+
495
+ ## Midi and a beat grid
496
+
497
+ `v.midi(trackId, notes, o)` places a midi clip in absolute video seconds
498
+ (`o.at`, `o.dur`, default the whole video) on its own midi track, creating
499
+ that track the first time `trackId` is seen. `o.instrument` is a preset id
500
+ (`"bl1-acid"`, `"dr1-tr-void"`) or a full instrument object;
501
+ `set_timeline_document` resolves the shorthand. `notes` is
502
+ `[beat, pitch, lenBeats?, velocity?]` tuples — musical time, the way a score
503
+ is written, independent of where the clip itself sits:
504
+
505
+ ```js
506
+ // fragment
507
+ v.midi("t_drums", [[0, 36, 0.25, 110], [1, 39, 0.25, 90]], { instrument: "dr1-tr-void" });
508
+ v.midi("t_bass", bassNotes, { instrument: "bl1-acid" });
509
+ ```
510
+
511
+ `v.beats({bpm, offset, count, snap, timeSignature})` sets the document's
512
+ `tempo` (so midi and beat-driven motion play at `bpm`) and returns edit ops
513
+ for `v.save`'s `ops`: a marker per beat, then — only when `snap` names clip
514
+ ids — those clips' starts snapped onto the grid:
515
+
516
+ ```js
517
+ // fragment
518
+ const saved = await v.save(nodetool.timelines, { name: "Cut", ops: v.beats({ bpm: 120, snap: ["t_drums"] }) });
519
+ ```
520
+
521
+ **`v._document` is not part of this API.** It is the assembled document
522
+ `v.series()` builds and `v.save()` sends — read-only bookkeeping, not an
523
+ extension point. `v.adjust()`/`v.audio()`/`v.music()`/`v.document()` are the
524
+ only sanctioned ways to add anything after `v.series()`; mutating `v._document`
525
+ directly (pushing a clip, editing a track) skips the field-filling and
526
+ validation the rest of this API relies on, and the field can be renamed or
527
+ removed without notice.
528
+
529
+ ## Saving
530
+
531
+ `v.save(timelines, {name, showcase, ops, timeline_id})` writes the document
532
+ with one `setDocument` (which snapshots the prior state first, so the write
533
+ is undoable), runs `ops` with one `edit`, validates (with `tier: "showcase"`
534
+ when `showcase: true`), and returns `{timeline_id, errors, warnings}`. It
535
+ throws on a refused write, a failed op, or a hard validation error; once the
536
+ timeline row exists, the thrown message names its id — repair that timeline
537
+ with `setDocument`/`edit` instead of calling `save()` again, which would leave
538
+ a duplicate row behind. Pass the script's own `nodetool.timelines` as
539
+ `timelines`: a module cannot see the belt the script body has.
540
+
541
+ Pass `timeline_id` to write into a timeline that already exists instead of
542
+ making another — the way to revise a cut without duplicating it.
543
+
544
+ ## Revise by editing the script
545
+
546
+ `v.save()` stores the code that rebuilds the timeline on its
547
+ `document.source`, whatever else the action called. It is not the script as
548
+ written: it is the **retained program** — the scene callbacks and the
549
+ declarations they use, as source, and every value they read from anything
550
+ else (a research result, a generated asset id, a loop variable) as a literal.
551
+ A fetch, a generation or a memory write ran once, in your action; the stored
552
+ program holds only its result, so a rebake never repeats it. `v.save()`
553
+ attaches the program only when baking it gives exactly the saved document,
554
+ and answers `saved.code: {embedded, warnings}`.
555
+
556
+ The program is not attached, and `saved.code.warnings` says why, when a scene
557
+ callback:
558
+
559
+ - calls a capability (`nodetool.*`). Call it at the top level, before the
560
+ scene, and use its result.
561
+ - uses `Math.random()` or `Date.now()`. Use `hash(n)` or `noise(seed)`, or
562
+ compute the value at the top level.
563
+ - reads a value that cannot be written as data (a class instance, a
564
+ function declared inside another function).
565
+
566
+ Keep helpers at the top level as `function` or `const` declarations; a
567
+ helper can read and change a `let` counter, and each scene gets the value the
568
+ counter had when it was built. When the program is not attached, or to attach
569
+ code after the fact, call `nodetool.timelines.code.set(timeline_id, code)`;
570
+ that code must build the timeline without calling a capability.
571
+
572
+ Revise it by editing that stored code, not by resending the whole body:
573
+ `code.get(id)` reads it back, `code.edit(id, edits)` applies a few small exact
574
+ string replacements (each `old` must match exactly once) and re-merges.
575
+ A rebake runs no capability: to use a new research result or a new asset,
576
+ make the call in an action and edit the value into the code. Read the
577
+ `conflicts` and
578
+ `warnings` a rebake answers, then look at frames. `api-timelines` owns the
579
+ full call contract; `code.rebake` reruns the stored code unchanged, and
580
+ `code.detach` stops tracking scenes.
581
+
582
+ **Scene names are the identity the merge keys on.** A rebake matches this
583
+ build's scenes against the timeline's current ones by `v.scene()`'s own
584
+ `name`, not by position or content — renaming a scene is a remove of the old
585
+ name plus an add of the new one, so anything hand-edited under the old name is
586
+ gone and the new one reflows in as an ordinary addition. Every clip's id is
587
+ deterministic from its scene's name plus its order of creation in the script
588
+ (`${sceneName}_${prefix}${n}`, `nid()` in this pack's own source) — never from
589
+ its screen position — so two bakes of unchanged code mint identical ids and a
590
+ change confined to one scene never shifts another's. That is what lets a
591
+ rebake survive without breaking the editor's selection.
592
+
593
+ A hand edit — in the editor, or through `edit_timeline` — inside a scene the
594
+ code tracks marks that scene "edited": the next `code.set`/`code.edit`/
595
+ `code.rebake` keeps it and reports it in `conflicts` rather than silently
596
+ overwriting it, unless `force` (`true`, or that scene's name in an array)
597
+ says otherwise.
598
+
599
+ Showcase warnings ride the successful return, not an exception — check
600
+ `saved.warnings`, or a `code.*` call's own `warnings`, and render frames to
601
+ judge what they flag.
602
+
603
+ ## Types
604
+
605
+ The pack ships `sandbox/index.d.ts` (its `package.json` `types` field points
606
+ at it) — precise option types for `video`/`scene`/`series`/`transition`,
607
+ every element and craft helper, the motion primitives (`typewriter`'s
608
+ `caret` is an object, `stack`/`row`'s `align` a union), and `component`/`use`.
609
+ `nodetool jsscript validate` type-checks a script against it when the pack is
610
+ installed, so `typewriter({ caret: true })` fails validation naming `caret`
611
+ before the script ever runs.
612
+
613
+ ## Gotchas
614
+
615
+ - **Every time value is seconds, local to the scope that created it.** There
616
+ is no absolute-time option and nothing to convert by hand; `v.series()` is
617
+ the one place a time is ever shifted.
618
+ - **`enter` and `exit` are always "in"/"out"; there is no flip to reason
619
+ about.** `enter`'s target is always the property's rest pose, `exit`'s start
620
+ is always rest — declare the one you mean.
621
+ - **`animate()`'s `at`/`dur` are always clip-local seconds from the clip's own
622
+ start, whatever role it ends up with.** A curve shaped like an exit
623
+ (trailing away from every property's rest pose) auto-picks `role: "out"`,
624
+ the same as passing it explicitly; either way, `at`/`dur` still mean "starts
625
+ `at` seconds in, plays for `dur`" — the pack converts that to the
626
+ backward-from-the-clip's-end delay the document format wants, the same
627
+ conversion `exit()` already does. Author it exactly like `enter()`; don't
628
+ count backward by hand.
629
+ - **A `rotation` curve is degrees, the same unit as the friendly `rotation`
630
+ option on element creation.** `enter({from: {rotation: 90}})`,
631
+ `exit({to: {rotation: -20}})`, `animate({rotation: [0, 180]})` and
632
+ `loop({rotation: [0, 360]}, period)` all take degrees; the pack converts to
633
+ the document's radians internally. `rotationX`/`rotationY` curves are
634
+ already degrees and need no conversion. `tx: {rotation: ...}` — the raw
635
+ escape hatch — stays radians, since it writes the document field directly.
636
+ - **`stack`/`row` take `align`/`justify`** (Yoga `alignItems`/`justifyContent`;
637
+ default `align: "start"`, `justify` unset) and place their own box in the
638
+ frame via `at`/`anchor`, not a sibling's measured size; an unknown
639
+ `align`/`justify` throws.
640
+ - **`setDocument` keeps the whole document, including a field this API
641
+ didn't name.** Render settings such as motion blur are the exception —
642
+ those belong on `render(id, {…})`, not in the document.
643
+ - **`save()` creates a new timeline only the first time.** Pass `timeline_id`
644
+ to write into one that already exists — or, for a code-backed timeline, run
645
+ the change through `code.edit`/`code.rebake` instead of calling `save()`
646
+ again (below). Without either, every call still makes a new row; it never
647
+ overwrites a cut the user has open uninvited.