@field/cascade 0.10.0 → 0.12.0

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 (229) hide show
  1. package/AGENTS.md +25 -5
  2. package/ARCHITECTURE.md +5 -4
  3. package/CHANGELOG.md +382 -0
  4. package/README.md +1 -2
  5. package/dist/assets/AgentPanel-1JugFjE4.css +1 -0
  6. package/dist/assets/AgentPanel-BDL5MddA.js +5 -0
  7. package/dist/assets/{CodeEditor-DSZBpzdy.js → CodeEditor-C-xYG-Wn.js} +120 -120
  8. package/dist/assets/CodePanel-CtXd50m6.js +2 -0
  9. package/dist/assets/CookInfoPanel-B3NMhKJF.css +1 -0
  10. package/dist/assets/CookInfoPanel-DG4_TRsX.js +2 -0
  11. package/dist/assets/CustomNodeDialog-CHfZUs-r.js +1 -0
  12. package/dist/assets/DefinitionNode-CaRTXQ_b.js +1 -0
  13. package/dist/assets/DefinitionPanel-C3CxZY9r.js +1 -0
  14. package/dist/assets/DefinitionPanel-VNLnAiQD.css +1 -0
  15. package/dist/assets/OpenGraphDialog-BLdr6k_q.js +1 -0
  16. package/dist/assets/ProjectSettings-ZnvmIAir.js +2 -0
  17. package/dist/assets/{SettingsDialog-p25h3tj0.js → SettingsDialog-CBX3Wa87.js} +1 -1
  18. package/dist/assets/TimelinePanel-CDeR0arY.css +1 -0
  19. package/dist/assets/TimelinePanel-CWgnFH1e.js +1 -0
  20. package/dist/assets/{VersionHistoryDialog-DzFLUotB.js → VersionHistoryDialog-BOSoW1R-.js} +1 -1
  21. package/dist/assets/assert-BO2_erPD.js +1 -0
  22. package/dist/assets/assert-hvykGCnP.js +1 -0
  23. package/dist/assets/builder-BmBKcDix.js +1 -0
  24. package/dist/assets/camera-DFF7YA3F.js +1 -0
  25. package/dist/assets/checks-DAvyO9Gf.js +1 -0
  26. package/dist/assets/contactSheet-N1NcaWxw.js +2 -0
  27. package/dist/assets/{core-BICrEt0y.js → core-DB-MkGjj.js} +3 -4
  28. package/dist/assets/coreTypes-DtnDATZ7.js +1 -0
  29. package/dist/assets/{cssMode-D7TXw57I.js → cssMode-DPVckSOc.js} +1 -1
  30. package/dist/assets/definitions-B227e7f_.js +1 -0
  31. package/dist/assets/definitions-D0WOUJd7.js +1 -0
  32. package/dist/assets/{editor.api-DvkCO3ko.js → editor.api-Dkutg_Z1.js} +1 -1
  33. package/dist/assets/expressions-X-Z-GrgW.js +5 -0
  34. package/dist/assets/file-DAL6IfjV.js +1 -0
  35. package/dist/assets/filters-D3k1LRMa.js +1 -0
  36. package/dist/assets/{freemarker2-BA2lxsF1.js → freemarker2-CLiLKk3B.js} +1 -1
  37. package/dist/assets/generators-BhDxRAFC.js +1 -0
  38. package/dist/assets/geo-BsMImJIJ.js +1 -0
  39. package/dist/assets/geo-C4YngDob.js +2 -0
  40. package/dist/assets/{handlebars-BcDiyPd8.js → handlebars-CcD3aMeL.js} +1 -1
  41. package/dist/assets/{html-C7O4TyhX.js → html-DvZWGfqb.js} +1 -1
  42. package/dist/assets/{htmlMode-WaDGF3Mj.js → htmlMode-DUD3VaoN.js} +1 -1
  43. package/dist/assets/image-0SfH-snh.js +1 -0
  44. package/dist/assets/image-Ck-RdGU4.js +2 -0
  45. package/dist/assets/index-BWnrmIB9.js +137 -0
  46. package/dist/assets/index-DIaxEONI.css +1 -0
  47. package/dist/assets/io-BGmt2g0D.js +1 -0
  48. package/dist/assets/{javascript-CpIEK7tz.js → javascript-Cap2ahgh.js} +1 -1
  49. package/dist/assets/{jsonMode-B2FltW0f.js → jsonMode-B0uLjp9C.js} +1 -1
  50. package/dist/assets/{liquid-CN6FFHZE.js → liquid-CnxwhfLn.js} +1 -1
  51. package/dist/assets/{lspLanguageFeatures-QhV9XtLp.js → lspLanguageFeatures-Dn9hlKX6.js} +1 -1
  52. package/dist/assets/math-GeULS-sk.js +1 -0
  53. package/dist/assets/{mdx-DE89pafV.js → mdx-BV3jbrVa.js} +1 -1
  54. package/dist/assets/pop-KMF_qloi.js +1 -0
  55. package/dist/assets/preload-helper-Czpn1I53.js +1 -0
  56. package/dist/assets/{python-ByTllTB9.js → python-CGyFk4XK.js} +1 -1
  57. package/dist/assets/{razor-USYlRRCE.js → razor-CQfzX3-N.js} +1 -1
  58. package/dist/assets/remap-5SjXQU-q.js +1 -0
  59. package/dist/assets/renderMarkdown-ork0Oir7.js +67 -0
  60. package/dist/assets/studioImages-CAyH-0Pl.js +1 -0
  61. package/dist/assets/text-ZpX7qNkx.js +2 -0
  62. package/dist/assets/time-P_OiBhcf.js +1 -0
  63. package/dist/assets/{tsMode-BXpN8iUz.js → tsMode-BOeXzO9H.js} +1 -1
  64. package/dist/assets/{typescript-DInBjGQQ.js → typescript-J1SnE_Tu.js} +1 -1
  65. package/dist/assets/{workers-C7Ye5B7D.js → workers-CYwwTiVF.js} +1 -1
  66. package/dist/assets/{xml-U0Uocqyo.js → xml-DnPRNzK9.js} +1 -1
  67. package/dist/assets/{yaml-CBw_PVYf.js → yaml-BmmYBNa9.js} +1 -1
  68. package/dist/cli/DefinitionNode-P7AA4TAU.js +15 -0
  69. package/dist/cli/Graph-OB6EWSGL.js +2975 -0
  70. package/dist/cli/assert-GROGQNCQ.js +25 -0
  71. package/dist/cli/checks-3WB44XBL.js +189 -0
  72. package/dist/cli/chunk-2JEU3MGB.js +1125 -0
  73. package/dist/cli/chunk-2KXB47FP.js +115 -0
  74. package/dist/cli/chunk-3RXL5GHB.js +816 -0
  75. package/dist/cli/chunk-53LGFPWX.js +138 -0
  76. package/dist/cli/chunk-5ORUQ6ES.js +267 -0
  77. package/dist/cli/chunk-73S4JZXK.js +336 -0
  78. package/dist/cli/chunk-7QE2ZNSN.js +121 -0
  79. package/dist/cli/chunk-BP7UKF36.js +376 -0
  80. package/dist/cli/chunk-BPODRH52.js +201 -0
  81. package/dist/cli/chunk-BZ63XQ3I.js +1301 -0
  82. package/dist/cli/chunk-CKS2FQWR.js +89 -0
  83. package/dist/cli/chunk-CO22XHNG.js +593 -0
  84. package/dist/cli/chunk-CTPLRSWF.js +2197 -0
  85. package/dist/cli/chunk-DEQSEFFH.js +153 -0
  86. package/dist/cli/chunk-EU4ZFZ7Y.js +122 -0
  87. package/dist/cli/chunk-EZRWJ4G2.js +104 -0
  88. package/dist/cli/chunk-FWMSGKEH.js +71 -0
  89. package/dist/cli/chunk-G23A2BZZ.js +705 -0
  90. package/dist/cli/chunk-GRIN2DDP.js +1465 -0
  91. package/dist/cli/chunk-HFHWYL55.js +139 -0
  92. package/dist/cli/chunk-HSNOBIVT.js +263 -0
  93. package/dist/cli/chunk-ICH7N5PN.js +26 -0
  94. package/dist/cli/chunk-J7VK7MNF.js +418 -0
  95. package/dist/cli/chunk-JVOCK5W4.js +83 -0
  96. package/dist/cli/chunk-KBGUO27Q.js +15 -0
  97. package/dist/cli/chunk-KH4MUBKF.js +1551 -0
  98. package/dist/cli/chunk-L6B6FUQP.js +330 -0
  99. package/dist/cli/chunk-LTXWBXOM.js +438 -0
  100. package/dist/cli/chunk-MI5JLQYA.js +220 -0
  101. package/dist/cli/chunk-MP653A5O.js +3766 -0
  102. package/dist/cli/chunk-NJFMDXHR.js +320 -0
  103. package/dist/cli/chunk-OM6BEME6.js +267 -0
  104. package/dist/cli/chunk-OWWBYLHC.js +136 -0
  105. package/dist/cli/chunk-QQPA2ZHH.js +273 -0
  106. package/dist/cli/chunk-RUU3YWXU.js +99 -0
  107. package/dist/cli/chunk-RXDIMXN5.js +98 -0
  108. package/dist/cli/chunk-ST6LROUG.js +267 -0
  109. package/dist/cli/chunk-TJW67QSI.js +225 -0
  110. package/dist/cli/chunk-WD62JIP6.js +118 -0
  111. package/dist/cli/chunk-WGKPJZDY.js +364 -0
  112. package/dist/cli/chunk-X4WKAOQP.js +634 -0
  113. package/dist/cli/chunk-X566FWBV.js +156 -0
  114. package/dist/cli/chunk-YH637PDL.js +317 -0
  115. package/dist/cli/chunk-ZL4JTG5Y.js +37 -0
  116. package/dist/cli/chunk-ZSCUZE6I.js +112 -0
  117. package/dist/cli/cliCommands-JJN5Z3ZC.js +3470 -0
  118. package/dist/cli/commands-PHYFYA3F.js +1374 -0
  119. package/dist/cli/contactSheet-2PQVRUFW.js +11 -0
  120. package/dist/cli/definitions-BPOPW52L.js +32 -0
  121. package/dist/cli/doctorCommand-ORWQRNYP.js +106 -0
  122. package/dist/cli/edit-S5KEQ5RA.js +28 -0
  123. package/dist/cli/editorCommands-NFR32JOY.js +41 -0
  124. package/dist/cli/encode-ICPEPMH5.js +17 -0
  125. package/dist/cli/file-3HTQHPYQ.js +41 -0
  126. package/dist/cli/filters-6ODFWYK4.js +379 -0
  127. package/dist/cli/frames-LSAUBZ7P.js +16 -0
  128. package/dist/cli/generators-YU7GB4MD.js +210 -0
  129. package/dist/cli/geo-6ZPGKZRU.js +43 -0
  130. package/dist/cli/graphCommands-EOUOBOIJ.js +184 -0
  131. package/dist/cli/graphFile-YTGJB5F5.js +292 -0
  132. package/dist/cli/headlessCanvas-ENRHC7GS.js +16 -0
  133. package/dist/cli/helpCommand-PULH6ZYB.js +157 -0
  134. package/dist/cli/image-TXELZZK5.js +55 -0
  135. package/dist/cli/index.js +100 -30756
  136. package/dist/cli/math-RSYRQ3C6.js +16 -0
  137. package/dist/cli/mcp-JVZFQOEX.js +164 -0
  138. package/dist/cli/parity-X6DEH2DV.js +334 -0
  139. package/dist/cli/pop-B7SIIYKN.js +39 -0
  140. package/dist/cli/project-VYGIA7JX.js +12 -0
  141. package/dist/cli/projectDefinition-F3D7UIHJ.js +27 -0
  142. package/dist/cli/projectRuntime-2Q4JKEH3.js +55 -0
  143. package/dist/cli/registerStandardNodes-BXJCB6E4.js +744 -0
  144. package/dist/cli/render-PT4MWI6U.js +438 -0
  145. package/dist/cli/runCommand-HNPQVZMS.js +198 -0
  146. package/dist/cli/runner-5XPXVW7J.js +43 -0
  147. package/dist/cli/runtime/image.ts +302 -0
  148. package/dist/cli/runtime/io.ts +78 -0
  149. package/dist/cli/runtime/stage.ts +4 -1
  150. package/dist/cli/share-MHF6TNRK.js +166 -0
  151. package/dist/cli/stageCache-TY2BFZJR.js +15 -0
  152. package/dist/cli/stageRunner-KY2EJP7X.js +10 -0
  153. package/dist/cli/text-MB3EKR4H.js +126 -0
  154. package/dist/cli/verbs-PI2JH5FI.js +19 -0
  155. package/dist/cli/webBuild-WPPMGGW7.js +44 -0
  156. package/dist/contracts/capabilities.d.ts +18 -1
  157. package/dist/contracts/color.d.ts +8 -0
  158. package/dist/contracts/color.js +23 -0
  159. package/dist/contracts/definition.d.ts +10 -0
  160. package/dist/contracts/documentFormat.d.ts +18 -0
  161. package/dist/contracts/documentFormat.js +195 -0
  162. package/dist/contracts/editor.d.ts +17 -0
  163. package/dist/contracts/index.d.ts +1 -0
  164. package/dist/contracts/index.js +1 -0
  165. package/dist/contracts/schema.d.ts +1 -1
  166. package/dist/contracts/schema.js +1 -0
  167. package/dist/contracts/validate.js +11 -1
  168. package/dist/contracts/values.d.ts +24 -0
  169. package/dist/image.d.ts +156 -0
  170. package/dist/image.js +161 -0
  171. package/dist/index.html +8 -2
  172. package/dist/io.d.ts +29 -0
  173. package/dist/io.js +40 -0
  174. package/dist/player/app.js +3 -2
  175. package/dist/player/editor.js +35 -9
  176. package/dist/player/editorEngine.js +3 -2
  177. package/dist/runtime/builtins/assert/checks.d.ts +18 -0
  178. package/dist/runtime/builtins/assert/checks.js +207 -0
  179. package/dist/runtime/builtins/assert/definitions.d.ts +130 -0
  180. package/dist/runtime/builtins/assert/definitions.js +54 -0
  181. package/dist/runtime/builtins/assert/index.d.ts +152 -0
  182. package/dist/runtime/builtins/assert/index.js +36 -0
  183. package/dist/runtime/builtins/image/contactSheet.d.ts +64 -0
  184. package/dist/runtime/builtins/image/contactSheet.js +150 -0
  185. package/dist/runtime/builtins/image/definitions.d.ts +950 -0
  186. package/dist/runtime/builtins/image/definitions.js +343 -0
  187. package/dist/runtime/builtins/image/file.d.ts +8 -0
  188. package/dist/runtime/builtins/image/file.js +37 -0
  189. package/dist/runtime/builtins/image/filters.d.ts +8 -0
  190. package/dist/runtime/builtins/image/filters.js +394 -0
  191. package/dist/runtime/builtins/image/generators.d.ts +7 -0
  192. package/dist/runtime/builtins/image/generators.js +144 -0
  193. package/dist/runtime/builtins/image/index.d.ts +952 -0
  194. package/dist/runtime/builtins/image/index.js +44 -0
  195. package/dist/runtime/builtins/image/io.d.ts +100 -0
  196. package/dist/runtime/builtins/image/io.js +142 -0
  197. package/dist/runtime/builtins/image/planar.d.ts +55 -0
  198. package/dist/runtime/builtins/image/planar.js +274 -0
  199. package/dist/runtime/builtins/image/simplex.d.ts +22 -0
  200. package/dist/runtime/builtins/image/simplex.js +92 -0
  201. package/dist/runtime/builtins/image/text.d.ts +4 -0
  202. package/dist/runtime/builtins/image/text.js +141 -0
  203. package/dist/runtime/builtins/index.d.ts +3 -0
  204. package/dist/runtime/builtins/index.js +7 -0
  205. package/dist/runtime/gpu.d.ts +25 -0
  206. package/dist/runtime/gpu.js +40 -0
  207. package/dist/runtime/runtime.d.ts +2 -0
  208. package/dist/runtime/runtime.js +268 -44
  209. package/dist/runtime/types.d.ts +30 -0
  210. package/dist/stage.js +4 -1
  211. package/doc/CLI_REFERENCE.md +91 -2
  212. package/doc/EDITOR_EXPORT.md +37 -2
  213. package/doc/MCP.md +18 -1
  214. package/doc/NODE_REFERENCE.md +424 -10
  215. package/doc/PROJECT_AUTHORING.md +222 -7
  216. package/package.json +19 -4
  217. package/dist/assets/CodePanel-sY6sZa7B.js +0 -2
  218. package/dist/assets/CustomNodeDialog-BRKnqZhp.js +0 -1
  219. package/dist/assets/ExportDialog-C7dVGua4.js +0 -358
  220. package/dist/assets/ExportDialog-DOq0flMV.css +0 -1
  221. package/dist/assets/OpenGraphDialog-ZsgL-iwr.js +0 -1
  222. package/dist/assets/ProjectSettings-Cw17nNe3.js +0 -2
  223. package/dist/assets/definitions-DUww2Rt-.js +0 -1
  224. package/dist/assets/geo-C2RHOVHC.js +0 -1
  225. package/dist/assets/image-DR4oeyJB.js +0 -2062
  226. package/dist/assets/index-7vae6YFC.css +0 -1
  227. package/dist/assets/index-DyvDC6pS.js +0 -219
  228. package/dist/assets/math-DaDEgWKt.js +0 -1
  229. package/dist/assets/pop-pLH7L0PW.js +0 -1
@@ -0,0 +1,336 @@
1
+ import {
2
+ installHeadlessCanvas
3
+ } from "./chunk-EZRWJ4G2.js";
4
+ import {
5
+ applyScene,
6
+ exportEditor,
7
+ formatPlan,
8
+ fromBase64,
9
+ parseScene
10
+ } from "./chunk-3RXL5GHB.js";
11
+ import {
12
+ cookProjectGraph,
13
+ installProjectIo
14
+ } from "./chunk-MP653A5O.js";
15
+ import {
16
+ ProjectRoot
17
+ } from "./chunk-2JEU3MGB.js";
18
+
19
+ // src/cli/editorCommands.ts
20
+ import fs from "node:fs/promises";
21
+ import path from "node:path";
22
+ var EDITOR_COMMANDS = ["export", "share", "freeze", "publish", "scene"];
23
+ var EDITOR_HELP = `
24
+ Shareable editors (see doc/EDITOR_EXPORT.md):
25
+ cascade export <graph> --plan Print what would ship, build nothing
26
+ cascade export <graph> --out <dir> [--deploy vercel [--project <name>] [--scope <team>] [--public]]
27
+ cascade share <graph | site-dir> [--name <slug>] [--scope <team>] [--protected] Deploy as cascade-<slug>, print the link
28
+ cascade publish <graph> <node>.<prop> [--id <id>] [--label <text>] [--group <name>]
29
+ [--control <kind>] [--min n] [--max n] [--step n] [--help <text>] [--remove]
30
+ cascade freeze <graph> <node> --fields <a,b.c> [--output <port>] [--personal <a,..>]
31
+ cascade scene <graph> <scene.json> --out <new-graph.cascade>
32
+ cascade run <graph> --scene <scene.json> [--write <node>.<port>=<file>]...
33
+ `;
34
+ async function runEditorCommand(args) {
35
+ const [command, graph] = args;
36
+ if (command === "share") {
37
+ const { runShareCommand } = await import("./share-MHF6TNRK.js");
38
+ return runShareCommand(args);
39
+ }
40
+ if (!graph || graph.startsWith("-")) throw new Error(`cascade ${command} requires a graph file`);
41
+ const flags = parseFlags(args.slice(2));
42
+ switch (command) {
43
+ case "export":
44
+ return exportCommand(graph, flags);
45
+ case "publish":
46
+ return publishCommand(graph, flags);
47
+ case "freeze":
48
+ return freezeCommand(graph, flags);
49
+ case "scene":
50
+ return sceneCommand(graph, flags);
51
+ case "run":
52
+ return runWithScene(graph, flags);
53
+ default:
54
+ throw new Error(`Unknown command ${command}`);
55
+ }
56
+ }
57
+ var SWITCHES = /* @__PURE__ */ new Set(["--plan", "--remove", "--json", "--public"]);
58
+ function parseFlags(args) {
59
+ const positional = [];
60
+ const values = /* @__PURE__ */ new Map();
61
+ const switches = /* @__PURE__ */ new Set();
62
+ for (let index = 0; index < args.length; index++) {
63
+ const arg = args[index];
64
+ if (!arg.startsWith("--")) {
65
+ positional.push(arg);
66
+ continue;
67
+ }
68
+ if (SWITCHES.has(arg)) {
69
+ switches.add(arg);
70
+ continue;
71
+ }
72
+ const value = args[index + 1];
73
+ if (value === void 0 || value.startsWith("--") && !/^--?\d/.test(value)) throw new Error(`${arg} needs a value`);
74
+ values.set(arg, [...values.get(arg) ?? [], value]);
75
+ index++;
76
+ }
77
+ return { positional, values, switches };
78
+ }
79
+ var one = (flags, name) => {
80
+ const list = flags.values.get(name);
81
+ return list?.[list.length - 1];
82
+ };
83
+ async function exportCommand(graph, flags) {
84
+ const deploy = one(flags, "--deploy");
85
+ if (deploy && deploy !== "vercel") throw new Error("--deploy supports vercel only");
86
+ const result = await exportEditor(graph, {
87
+ plan: flags.switches.has("--plan"),
88
+ out: one(flags, "--out"),
89
+ ...deploy ? { deploy: "vercel" } : {},
90
+ project: one(flags, "--project"),
91
+ scope: one(flags, "--scope"),
92
+ ...flags.switches.has("--public") ? { public: true } : {}
93
+ });
94
+ if (flags.switches.has("--json")) console.log(JSON.stringify(result.plan, null, 2));
95
+ else console.log(formatPlan(result.plan));
96
+ if (result.directory) console.log(`
97
+ Editor exported: ${result.directory}
98
+ Manifest: ${path.join(result.directory, "export-manifest.json")}`);
99
+ if (result.url) console.log(`Deployed: ${result.url}`);
100
+ }
101
+ async function readGraph(file) {
102
+ return JSON.parse(await fs.readFile(file, "utf8"));
103
+ }
104
+ async function writeEditor(file, document, editor) {
105
+ const next = { ...document, editor };
106
+ await fs.writeFile(file, `${JSON.stringify(next, null, 2)}
107
+ `);
108
+ }
109
+ async function publishCommand(graph, flags) {
110
+ const target = flags.positional[0];
111
+ const match = target ? /^([^.]+)\.(.+)$/.exec(target) : null;
112
+ if (!match) throw new Error("cascade publish needs <node>.<prop>");
113
+ const [, node, prop] = match;
114
+ const document = await readGraph(graph);
115
+ if (!document.nodes.some((item) => item.id === node)) throw new Error(`No node ${node} in ${graph}`);
116
+ const editor = document.editor ?? { id: path.basename(graph).replace(/\.cascade$/, "") };
117
+ const parameters = [...editor.parameters ?? []];
118
+ const id = one(flags, "--id") ?? prop;
119
+ const existing = parameters.findIndex((item) => item.node === node && item.prop === prop);
120
+ if (flags.switches.has("--remove")) {
121
+ if (existing < 0) throw new Error(`${node}.${prop} is not published`);
122
+ parameters.splice(existing, 1);
123
+ await writeEditor(graph, document, { ...editor, parameters });
124
+ console.log(`Unpublished ${node}.${prop}`);
125
+ return;
126
+ }
127
+ const number = (name) => {
128
+ const value = one(flags, name);
129
+ if (value === void 0) return void 0;
130
+ const parsed = Number(value);
131
+ if (!Number.isFinite(parsed)) throw new Error(`${name} must be a number`);
132
+ return parsed;
133
+ };
134
+ const previous = existing >= 0 ? parameters[existing] : void 0;
135
+ const parameter = {
136
+ ...previous,
137
+ id: previous?.id ?? id,
138
+ node,
139
+ prop,
140
+ ...one(flags, "--label") ? { label: one(flags, "--label") } : {},
141
+ ...one(flags, "--group") ? { group: one(flags, "--group") } : {},
142
+ ...one(flags, "--control") ? { control: one(flags, "--control") } : {},
143
+ ...number("--min") !== void 0 ? { min: number("--min") } : {},
144
+ ...number("--max") !== void 0 ? { max: number("--max") } : {},
145
+ ...number("--step") !== void 0 ? { step: number("--step") } : {},
146
+ ...number("--order") !== void 0 ? { order: number("--order") } : {},
147
+ ...one(flags, "--help") ? { help: one(flags, "--help") } : {},
148
+ ...one(flags, "--unit") ? { unit: one(flags, "--unit") } : {}
149
+ };
150
+ if (existing >= 0) parameters[existing] = parameter;
151
+ else {
152
+ if (parameters.some((item) => item.id === parameter.id)) throw new Error(`Parameter id ${parameter.id} is taken; pass --id`);
153
+ parameters.push(parameter);
154
+ }
155
+ await writeEditor(graph, document, { ...editor, parameters });
156
+ console.log(`${existing >= 0 ? "Updated" : "Published"} ${node}.${prop} as "${parameter.id}"`);
157
+ }
158
+ async function freezeCommand(graph, flags) {
159
+ const node = flags.positional[0];
160
+ if (!node) throw new Error("cascade freeze needs a node id");
161
+ const fields = (one(flags, "--fields") ?? "").split(",").map((item) => item.trim()).filter(Boolean);
162
+ if (!fields.length) throw new Error("cascade freeze needs --fields: name exactly the values to keep");
163
+ const personal = (one(flags, "--personal") ?? "").split(",").map((item) => item.trim()).filter(Boolean);
164
+ const document = await readGraph(graph);
165
+ if (!document.nodes.some((item) => item.id === node)) throw new Error(`No node ${node} in ${graph}`);
166
+ const graphFile = path.resolve(graph);
167
+ const project = new ProjectRoot(path.dirname(graphFile));
168
+ await installHeadlessCanvas().catch(() => void 0);
169
+ const removeIo = installProjectIo(project);
170
+ const { outputs } = await cookProjectGraph(graphFile, document, node).finally(removeIo);
171
+ const produced = outputs.get(node);
172
+ if (!produced) throw new Error(`${node} produced nothing`);
173
+ const output = one(flags, "--output") ?? [...produced.keys()][0];
174
+ if (!produced.has(output)) throw new Error(`${node} has no output ${output}; it has ${[...produced.keys()].join(", ")}`);
175
+ const full = toJson(produced.get(output));
176
+ const directory = path.join("frozen", node);
177
+ await fs.mkdir(project.resolve(directory), { recursive: true });
178
+ const files = [];
179
+ const pick = async (value) => {
180
+ if (value && typeof value === "object" && !Array.isArray(value) && typeof value.path === "string") {
181
+ const from = value.path;
182
+ const source = project.resolve(from.replace(/^\.\//, ""));
183
+ const name = `${directory}/${path.basename(from)}`;
184
+ await fs.copyFile(source, project.resolve(name));
185
+ files.push(name);
186
+ return { ...value, path: name };
187
+ }
188
+ if (Array.isArray(value)) return Promise.all(value.map(pick));
189
+ if (value && typeof value === "object")
190
+ return Object.fromEntries(await Promise.all(Object.entries(value).map(async ([key, item]) => [key, await pick(item)])));
191
+ return value;
192
+ };
193
+ const chosen = {};
194
+ for (const field of fields) {
195
+ const value = getPath(full, field);
196
+ if (value === void 0) throw new Error(`${node}.${output} has no field ${field}`);
197
+ setPath(chosen, field, await pick(value));
198
+ }
199
+ if (fields.includes("path") && typeof chosen.path === "string" && full && typeof full === "object" && !Array.isArray(full)) {
200
+ const { path: copied } = await pick({ path: chosen.path });
201
+ chosen.path = copied;
202
+ }
203
+ const snapshot = {
204
+ format: "cascade.frozen",
205
+ version: 1,
206
+ node,
207
+ output,
208
+ fields,
209
+ ...personal.length ? { personal } : {},
210
+ value: chosen,
211
+ ...files.length ? { files } : {},
212
+ frozenAt: (/* @__PURE__ */ new Date()).toISOString()
213
+ };
214
+ const snapshotFile = `${directory}.json`;
215
+ await fs.writeFile(project.resolve(snapshotFile), `${JSON.stringify(snapshot, null, 2)}
216
+ `);
217
+ const editor = document.editor ?? { id: path.basename(graph).replace(/\.cascade$/, "") };
218
+ const freeze = (editor.freeze ?? []).filter((item) => item.node !== node);
219
+ freeze.push({ node, output, snapshot: snapshotFile });
220
+ await writeEditor(graph, document, { ...editor, freeze });
221
+ console.log(`Froze ${node}.${output}: ${fields.join(", ")} \u2192 ${snapshotFile}${files.length ? ` (+${files.length} file${files.length > 1 ? "s" : ""})` : ""}`);
222
+ }
223
+ async function sceneCommand(graph, flags) {
224
+ const sceneFile = flags.positional[0];
225
+ const out = one(flags, "--out");
226
+ if (!sceneFile || !out) throw new Error("cascade scene needs <scene.json> --out <new-graph.cascade>");
227
+ if (path.resolve(out) === path.resolve(graph)) throw new Error("--out must be a new file; the source graph is never overwritten");
228
+ const { document, warnings } = await sceneDocument(graph, sceneFile);
229
+ await fs.writeFile(out, `${JSON.stringify(document, null, 2)}
230
+ `);
231
+ for (const warning of warnings) console.warn(`Warning: ${warning}`);
232
+ console.log(`Scene applied: ${out}`);
233
+ }
234
+ async function sceneDocument(graph, sceneFile) {
235
+ const document = await readGraph(graph);
236
+ const scene = parseScene(await fs.readFile(sceneFile, "utf8"));
237
+ const project = new ProjectRoot(path.dirname(path.resolve(graph)));
238
+ const located = /* @__PURE__ */ new Map();
239
+ for (const [id, file] of Object.entries(scene.files ?? {})) {
240
+ const found = await locateSceneFile(project, file);
241
+ if (found) located.set(id, { path: found, ...file.mediaType ? { mediaType: file.mediaType } : {} });
242
+ }
243
+ return applyScene(document, scene, { resolveFile: (parameter) => located.get(parameter.id) });
244
+ }
245
+ async function locateSceneFile(project, file) {
246
+ const { createHash } = await import("node:crypto");
247
+ const safe = path.basename(file.name).replace(/[^A-Za-z0-9._-]/g, "_");
248
+ if (file.data) {
249
+ const bytes = fromBase64(file.data);
250
+ const digest = createHash("sha256").update(bytes).digest("hex");
251
+ if (digest !== file.sha256) throw new Error(`Scene file ${file.name} does not match its hash`);
252
+ const name = `assets/scenes/${digest.slice(0, 12)}-${safe}`;
253
+ await fs.mkdir(path.dirname(project.resolve(name)), { recursive: true });
254
+ await fs.writeFile(project.resolve(name), bytes);
255
+ return name;
256
+ }
257
+ for (const candidate of await filesNamed(project.root, path.basename(file.name))) {
258
+ const digest = createHash("sha256").update(await fs.readFile(candidate)).digest("hex");
259
+ if (digest === file.sha256) return path.relative(project.root, candidate).split(path.sep).join("/");
260
+ }
261
+ return void 0;
262
+ }
263
+ async function filesNamed(root, name) {
264
+ const found = [];
265
+ const walk = async (directory, depth) => {
266
+ if (depth > 6) return;
267
+ for (const entry of await fs.readdir(directory, { withFileTypes: true }).catch(() => [])) {
268
+ if (entry.name === "node_modules" || entry.name === ".git" || entry.name === ".cascade-cache") continue;
269
+ const file = path.join(directory, entry.name);
270
+ if (entry.isDirectory()) await walk(file, depth + 1);
271
+ else if (entry.name === name) found.push(file);
272
+ }
273
+ };
274
+ await walk(path.join(root, "assets"), 0);
275
+ await walk(path.join(root, "licensed"), 0);
276
+ return found;
277
+ }
278
+ async function runWithScene(graph, flags) {
279
+ const graphFile = path.resolve(graph);
280
+ let document = await readGraph(graph);
281
+ const scene = one(flags, "--scene");
282
+ if (scene) {
283
+ const applied = await sceneDocument(graph, scene);
284
+ document = applied.document;
285
+ for (const warning of applied.warnings) console.warn(`Warning: ${warning}`);
286
+ }
287
+ await installHeadlessCanvas().catch(() => void 0);
288
+ const project = new ProjectRoot(path.dirname(graphFile));
289
+ const removeIo = installProjectIo(project);
290
+ const { outputs } = await cookProjectGraph(graphFile, document).finally(removeIo);
291
+ for (const spec of flags.values.get("--write") ?? []) {
292
+ const match = /^([^.=]+)\.([^=]+)=(.+)$/.exec(spec);
293
+ if (!match) throw new Error(`--write expects <node>.<port>=<file>, got ${spec}`);
294
+ const [, node, port, file] = match;
295
+ const value = outputs.get(node)?.get(port);
296
+ if (value === void 0) throw new Error(`${node}.${port} produced nothing`);
297
+ if (typeof value === "string") await fs.writeFile(file, value);
298
+ else if (value && typeof value === "object" && typeof value.path === "string")
299
+ await fs.copyFile(project.resolve(value.path.replace(/^\.\//, "")), file);
300
+ else await fs.writeFile(file, JSON.stringify(toJson(value)));
301
+ console.log(`Wrote ${node}.${port} \u2192 ${file}`);
302
+ }
303
+ }
304
+ function toJson(value) {
305
+ return JSON.parse(JSON.stringify(value, (_key, item) => ArrayBuffer.isView(item) ? Array.from(item) : item) ?? "null");
306
+ }
307
+ function getPath(value, field) {
308
+ let current = value;
309
+ for (const part of field.split(".").flatMap((item) => item.split(/\[(\d+)\]/).filter(Boolean))) {
310
+ if (current === null || typeof current !== "object") return void 0;
311
+ current = current[part];
312
+ }
313
+ return current;
314
+ }
315
+ function setPath(target, field, value) {
316
+ const parts = field.split(".").flatMap((item) => item.split(/\[(\d+)\]/).filter(Boolean));
317
+ let current = target;
318
+ parts.forEach((part, index) => {
319
+ const last = index === parts.length - 1;
320
+ const container = current;
321
+ if (last) {
322
+ container[part] = value;
323
+ return;
324
+ }
325
+ if (container[part] === void 0) container[part] = /^\d+$/.test(parts[index + 1]) ? [] : {};
326
+ current = container[part];
327
+ });
328
+ }
329
+
330
+ export {
331
+ EDITOR_COMMANDS,
332
+ EDITOR_HELP,
333
+ runEditorCommand,
334
+ sceneDocument
335
+ };
336
+ //# sourceMappingURL=chunk-73S4JZXK.js.map
@@ -0,0 +1,121 @@
1
+ // src/cli/encode.ts
2
+ import { execFile } from "node:child_process";
3
+ import fs from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { promisify } from "node:util";
6
+ var run = promisify(execFile);
7
+ var DEFAULT_CRF = 18;
8
+ function timelineFrameSpec(document) {
9
+ const start = document?.timeline?.start;
10
+ const end = document?.timeline?.end;
11
+ if (typeof start !== "number" || typeof end !== "number") {
12
+ throw new Error("--frames timeline: the graph has no saved timeline range (set it in Studio's Timeline and save, or name the range: --frames 1-250)");
13
+ }
14
+ if (end < start) throw new Error(`--frames timeline: the saved end (${end}) is before the start (${start})`);
15
+ return { start, end };
16
+ }
17
+ function frameSequences(files) {
18
+ const sequences = /* @__PURE__ */ new Map();
19
+ for (const file of files) {
20
+ const match = /^(.*)\.(\d+)(\.[^./]+)$/.exec(file);
21
+ if (!match) throw new Error(`${file} is not a numbered frame`);
22
+ const key = `${match[1]}|${match[2].length}|${match[3]}`;
23
+ const entry = sequences.get(key) ?? { prefix: match[1], width: match[2].length, extension: match[3], files: [] };
24
+ entry.files.push(file);
25
+ sequences.set(key, entry);
26
+ }
27
+ return [...sequences.values()];
28
+ }
29
+ async function encodeMp4(options) {
30
+ const sequences = frameSequences(options.files);
31
+ if (sequences.length !== 1) {
32
+ const names = sequences.map((sequence) => path.basename(sequence.prefix));
33
+ throw new Error(`--mp4 encodes one sequence, and this run wrote ${sequences.length} (${names.join(", ")}). Name the one to encode with --node.`);
34
+ }
35
+ const { scale: _scale, ...video } = await encodeSequence({ ...options, sequence: sequences[0], codec: "h264" });
36
+ return video;
37
+ }
38
+ async function hasFfmpeg() {
39
+ try {
40
+ await run("ffmpeg", ["-version"]);
41
+ return true;
42
+ } catch {
43
+ return false;
44
+ }
45
+ }
46
+ async function encodeSequence(options) {
47
+ const frames = options.frames;
48
+ const scale = options.scale ?? 1;
49
+ if (!(scale > 0 && scale <= 1)) throw new Error(`video scale must be greater than 0 and at most 1 (got ${scale})`);
50
+ if (!frames.length) throw new Error("--mp4: no frames were rendered");
51
+ for (let index = 1; index < frames.length; index += 1) {
52
+ if (frames[index] !== frames[index - 1] + 1) throw new Error("--mp4 needs a contiguous range of whole frames (no step)");
53
+ }
54
+ const { prefix, width, extension } = options.sequence;
55
+ const pattern = path.resolve(options.root, `${prefix}.%0${width}d${extension}`);
56
+ const output = path.resolve(options.root, options.file);
57
+ await fs.mkdir(path.dirname(output), { recursive: true });
58
+ const size = scale === 1 ? "pad=ceil(iw/2)*2:ceil(ih/2)*2" : `scale=trunc(iw*${scale}/2)*2:trunc(ih*${scale}/2)*2:flags=lanczos`;
59
+ const crf = options.crf ?? DEFAULT_CRF;
60
+ let pixelFormat;
61
+ let codecArgs;
62
+ if (options.codec === "prores") {
63
+ const first = path.resolve(options.root, `${prefix}.${String(frames[0]).padStart(width, "0")}${extension}`);
64
+ const alpha = await hasTransparency(first);
65
+ pixelFormat = alpha ? "yuva444p10le" : "yuv422p10le";
66
+ codecArgs = ["-c:v", "prores_ks", "-profile:v", alpha ? "4" : "3", "-vendor", "apl0", "-pix_fmt", pixelFormat];
67
+ } else {
68
+ pixelFormat = "yuv420p";
69
+ codecArgs = ["-c:v", "libx264", "-pix_fmt", pixelFormat, "-crf", String(crf), "-movflags", "+faststart"];
70
+ }
71
+ try {
72
+ await run("ffmpeg", [
73
+ "-nostdin",
74
+ "-y",
75
+ "-loglevel",
76
+ "error",
77
+ "-framerate",
78
+ String(options.fps),
79
+ "-start_number",
80
+ String(frames[0]),
81
+ "-i",
82
+ pattern,
83
+ "-frames:v",
84
+ String(frames.length),
85
+ "-vf",
86
+ size,
87
+ ...codecArgs,
88
+ "-r",
89
+ String(options.fps),
90
+ output
91
+ ], { maxBuffer: 16 * 1024 * 1024 });
92
+ } catch (error) {
93
+ const err = error;
94
+ if (err.code === "ENOENT") throw new Error("Encoding needs ffmpeg on the PATH (brew install ffmpeg)");
95
+ throw new Error(`ffmpeg failed: ${(err.stderr || err.message).trim()}`);
96
+ }
97
+ return {
98
+ file: path.relative(options.root, output).split(path.sep).join("/"),
99
+ codec: options.codec,
100
+ pixelFormat,
101
+ ...options.codec === "h264" ? { crf } : {},
102
+ fps: options.fps,
103
+ frames: frames.length,
104
+ scale
105
+ };
106
+ }
107
+ async function hasTransparency(file) {
108
+ const sharp = (await import("sharp")).default;
109
+ const stats = await sharp(file).stats();
110
+ return !stats.isOpaque;
111
+ }
112
+
113
+ export {
114
+ DEFAULT_CRF,
115
+ timelineFrameSpec,
116
+ frameSequences,
117
+ encodeMp4,
118
+ hasFfmpeg,
119
+ encodeSequence
120
+ };
121
+ //# sourceMappingURL=chunk-7QE2ZNSN.js.map