@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,705 @@
1
+ // src/cli/verbs.ts
2
+ var graph = { type: "string", description: "Path to the .cascade file, relative to the project directory" };
3
+ var dryRun = { type: "boolean", description: "Validate without writing" };
4
+ var outNodes = { type: "array", items: { type: "string" }, description: "Mid-graph node ids whose image outputs are saved as well (--out-node)" };
5
+ var env = { type: "object", additionalProperties: { type: "string" }, description: "Environment variables for this run, its Python stages and workers (--env NAME=value)" };
6
+ var envArgs = (args) => Object.entries(args.env ?? {}).flatMap(([name, value]) => ["--env", `${name}=${String(value)}`]);
7
+ var object = (properties, required2 = []) => ({ type: "object", properties, required: required2, additionalProperties: false });
8
+ var edit = (operation) => ({ kind: "edit", operations: (args) => [operation(args)] });
9
+ var studio = { handler: "studio", help: "main", version: "main", section: "studio" };
10
+ var graphVerb = { handler: "graph", json: "envelope", help: "main", version: "handler", section: "graph" };
11
+ var lockVerb = { handler: "lock", json: "keep", help: "own", version: "handler", section: "locks" };
12
+ var runVerb = { handler: "run", help: "main", version: "main", section: "run" };
13
+ var editorVerb = { handler: "editor", json: "envelope", help: "main", version: "handler", section: "share" };
14
+ var VERBS = [
15
+ // Projects and Studio
16
+ { ...studio, name: "new", usage: "<name>", summary: "Create a Cascade project", json: "studio" },
17
+ { ...studio, name: "node", usage: "<Name> [project-directory]", summary: "Create a deterministic custom node", json: "envelope" },
18
+ { ...studio, name: "projects", usage: "[directory]", summary: "Show or set the default projects directory", json: "envelope" },
19
+ { ...studio, name: "serve", usage: "start|stop|restart|status|logs|install [project] [options]", summary: "Run Studio as a managed background service (cascade serve help)", json: "keep", help: "own", version: "handler" },
20
+ // Running
21
+ {
22
+ ...runVerb,
23
+ name: "run",
24
+ usage: "<graph-file> [options]",
25
+ summary: "Execute a graph file",
26
+ json: "keep",
27
+ mcp: [
28
+ {
29
+ name: "cook",
30
+ description: "Run a graph headlessly, optionally only the given node (cascade run). `data.images` lists each image output's file; `out` copies them there as <node>[.<port>].<ext>, `outNodes` adds mid-graph nodes.",
31
+ inputSchema: object({ graph, node: { type: "string" }, out: { type: "string", description: "Directory to save image outputs to, project-relative" }, outNodes, env, timeoutMs: { type: "number" } }, ["graph"]),
32
+ call: {
33
+ kind: "cli",
34
+ argv: (args, resolve) => [
35
+ "run",
36
+ resolve(args.graph),
37
+ ...args.node ? ["--node", args.node] : [],
38
+ ...args.out ? ["--out", resolve(args.out)] : [],
39
+ ...(args.outNodes ?? []).flatMap((node) => ["--out-node", node]),
40
+ ...envArgs(args)
41
+ ],
42
+ timeoutMs: (args) => args.timeoutMs
43
+ }
44
+ },
45
+ {
46
+ name: "render",
47
+ description: "Render frames to image files and return the manifest (cascade run --frames). `frames` is 1-100, 1-100x2 or 42.",
48
+ inputSchema: object({
49
+ graph,
50
+ frames: { type: "string" },
51
+ fps: { type: "number" },
52
+ out: { type: "string", description: "Output directory, project-relative (default renders)" },
53
+ nodes: { type: "array", items: { type: "string" } },
54
+ outNodes,
55
+ env,
56
+ scene: { type: "string", description: "A scene file to apply first" },
57
+ timeoutMs: { type: "number" }
58
+ }, ["graph", "frames"]),
59
+ call: {
60
+ kind: "cli",
61
+ argv: (args, resolve) => [
62
+ "run",
63
+ resolve(args.graph),
64
+ "--frames",
65
+ String(args.frames),
66
+ ...args.fps ? ["--fps", String(args.fps)] : [],
67
+ ...args.out ? ["--out", resolve(args.out)] : [],
68
+ ...args.scene ? ["--scene", resolve(args.scene)] : [],
69
+ ...(args.nodes ?? []).flatMap((node) => ["--node", node]),
70
+ ...(args.outNodes ?? []).flatMap((node) => ["--out-node", node]),
71
+ ...envArgs(args)
72
+ ],
73
+ timeoutMs: (args) => args.timeoutMs ?? 30 * 6e4
74
+ }
75
+ }
76
+ ]
77
+ },
78
+ { name: "render", usage: "<graph-file> [--frames <range>] [--node <id>] [--workers N] [--final] [--prores]", summary: "Render an animation across worker processes, then a preview video (cascade render --help)", section: "run", handler: "render", json: "keep", help: "own", version: "handler" },
79
+ { ...runVerb, name: "validate", usage: "<graph-file> [options]", summary: "Validate a graph file without executing", json: "envelope" },
80
+ {
81
+ ...runVerb,
82
+ name: "check",
83
+ usage: "<graph-file> [options]",
84
+ summary: "Statically check node definitions, types, and graph structure",
85
+ json: "envelope",
86
+ mcp: [{
87
+ name: "check",
88
+ description: "Statically check a graph: node definitions, port types and the host capabilities a run needs (cascade check).",
89
+ inputSchema: object({ graph, strict: { type: "boolean" } }, ["graph"]),
90
+ call: { kind: "cli", argv: (args, resolve) => ["check", resolve(args.graph), ...args.strict ? ["--strict"] : []] }
91
+ }]
92
+ },
93
+ {
94
+ ...runVerb,
95
+ name: "inspect",
96
+ usage: "<graph-file>",
97
+ summary: "Print a machine-readable graph and node-definition summary",
98
+ json: "envelope",
99
+ mcp: [{
100
+ name: "inspect",
101
+ description: "Every node with its classification and full definition (cascade inspect).",
102
+ inputSchema: object({ graph }, ["graph"]),
103
+ call: { kind: "cli", argv: (args, resolve) => ["inspect", resolve(args.graph)] }
104
+ }]
105
+ },
106
+ { name: "parity", usage: "<graph-file> [--node <id>] [--seed N] [--tolerance N] [--no-studio]", summary: "Cook headless twice and through Studio's path; report per-node differences", section: "run", handler: "parity", json: "keep", help: "main", version: "handler" },
107
+ // Sharing
108
+ { name: "build", usage: "<graph-file> --out <new-directory> [--asset <file>]...", summary: "Export a standalone browser player for static hosting or embedding", section: "share", handler: "build", json: "envelope", help: "main", version: "handler" },
109
+ {
110
+ ...editorVerb,
111
+ name: "export",
112
+ usage: "<graph-file> (--plan | --out <dir>) [--deploy vercel]",
113
+ summary: "Export the graph's published editor as a static site (doc/EDITOR_EXPORT.md)",
114
+ mcp: [{
115
+ name: "export",
116
+ description: "Export the graph's shareable editor (cascade export): `plan` lists what would ship, `out` writes it.",
117
+ inputSchema: object({ graph, plan: { type: "boolean" }, out: { type: "string" } }, ["graph"]),
118
+ call: { kind: "cli", argv: (args, resolve) => ["export", resolve(args.graph), ...args.out ? ["--out", resolve(args.out)] : ["--plan"]] }
119
+ }]
120
+ },
121
+ {
122
+ ...editorVerb,
123
+ name: "share",
124
+ json: "keep",
125
+ usage: "<graph-file | site-directory> [--name <slug>] [--scope <team>] [--protected]",
126
+ summary: "Export and deploy to Vercel as cascade-<slug>; print the link and record it in shares.json",
127
+ mcp: [{
128
+ name: "share",
129
+ description: "Share a graph's editor, or a static directory with an index.html, as one link (cascade share): export, deploy to the Vercel project cascade-<name> and return `url`, recorded in the project's shares.json. Public unless `protected`.",
130
+ inputSchema: object({
131
+ graph: { type: "string", description: "The .cascade file or site directory, relative to the project directory" },
132
+ name: { type: "string", description: "Slug; the Vercel project is cascade-<name> (default: the editor id or directory name)" },
133
+ scope: { type: "string", description: "Vercel team (default field-io)" },
134
+ protected: { type: "boolean", description: "Require Vercel Authentication instead of a public link" }
135
+ }, ["graph"]),
136
+ call: {
137
+ kind: "cli",
138
+ argv: (args, resolve) => [
139
+ "share",
140
+ resolve(args.graph),
141
+ ...args.name ? ["--name", String(args.name)] : [],
142
+ ...args.scope ? ["--scope", String(args.scope)] : [],
143
+ ...args.protected ? ["--protected"] : []
144
+ ],
145
+ timeoutMs: () => 10 * 6e4
146
+ }
147
+ }]
148
+ },
149
+ { ...editorVerb, name: "publish", usage: "<graph-file> <node>.<prop> [--label ...]", summary: "Publish a node prop as an editor parameter" },
150
+ { ...editorVerb, name: "freeze", usage: "<graph-file> <node> --fields <a,b>", summary: "Snapshot chosen fields of a live data source for export" },
151
+ { ...editorVerb, name: "scene", usage: "<graph-file> <scene.json> --out <new-graph-file>", summary: "Apply a scene file to a copy of the graph" },
152
+ // Agents
153
+ { name: "mcp", usage: "[project-directory]", summary: "Serve these commands as MCP tools over stdio (doc/MCP.md)", section: "agents", handler: "mcp", json: "envelope", help: "main", version: "handler" },
154
+ // Graph editing
155
+ {
156
+ ...graphVerb,
157
+ name: "graph create",
158
+ usage: "<graph> [--name <title>]",
159
+ summary: "Create a new, empty graph file; never overwrites",
160
+ options: { name: "value" },
161
+ mcp: [{
162
+ name: "graph_create",
163
+ description: "Create a new, empty .cascade graph file. Refuses to overwrite.",
164
+ inputSchema: object({ graph, name: { type: "string" } }, ["graph"]),
165
+ call: { kind: "graph", argv: (args, resolve) => ["graph", "create", resolve(args.graph), ...args.name ? ["--name", args.name] : []] }
166
+ }]
167
+ },
168
+ {
169
+ ...graphVerb,
170
+ name: "graph read",
171
+ usage: "<graph> [--in <subnet>]",
172
+ summary: "Nodes with stored parameters, subnets with their ports, and wires",
173
+ options: { in: "value" },
174
+ mcp: [{
175
+ name: "graph_read",
176
+ description: 'Read a graph: nodes with their stored parameters, subnets with their ports, and wires as "a.out -> b.in". Pass `in` to read one subnet.',
177
+ inputSchema: object({ graph, in: { type: "string", description: "A subnet id" } }, ["graph"]),
178
+ call: { kind: "graph", argv: (args, resolve) => ["graph", "read", resolve(args.graph), ...args.in ? ["--in", args.in] : []] }
179
+ }]
180
+ },
181
+ {
182
+ ...graphVerb,
183
+ name: "graph list",
184
+ usage: "[directory]",
185
+ summary: "Every .cascade under a directory, with node and wire counts",
186
+ mcp: [{
187
+ name: "graph_list",
188
+ description: "List the .cascade graphs in the project (or a subdirectory), with node and wire counts.",
189
+ inputSchema: object({ directory: { type: "string" } }),
190
+ call: { kind: "graph", argv: (args, resolve) => ["graph", "list", resolve(args.directory ?? ".")] }
191
+ }]
192
+ },
193
+ {
194
+ ...graphVerb,
195
+ name: "node add",
196
+ usage: "<graph> <type> [--name <id>] [--in <subnet>] [--set k=v]... [--at x,y] [--comment <text>]",
197
+ summary: "Add a node by module id or short name",
198
+ options: { name: "value", in: "value", set: "list", at: "value", comment: "value" },
199
+ operation: (operands, { values, lists }) => {
200
+ const module = required(operands[0], "cascade node add <graph> <type>");
201
+ const set = Object.fromEntries((lists.set ?? []).map((pair) => splitPair(pair, "--set", "=")));
202
+ return {
203
+ op: "node.add",
204
+ module,
205
+ ...values.name ? { id: values.name } : {},
206
+ ...values.in ? { in: values.in } : {},
207
+ ...values.at ? { position: point(values.at) } : {},
208
+ ...values.comment ? { comment: values.comment } : {},
209
+ ...Object.keys(set).length ? { set, fromText: true } : {}
210
+ };
211
+ },
212
+ mcp: [{
213
+ name: "node_add",
214
+ description: "Add a node. `type` is a module id (cascade.geo.Circle, project.InkVocabulary) or a short name (Circle, InkVocabulary). The node gets a stable kebab-case id unless `id` is given, and is placed next to what it is later wired to.",
215
+ inputSchema: object({
216
+ graph,
217
+ type: { type: "string" },
218
+ id: { type: "string" },
219
+ in: { type: "string", description: "Subnet to create the node in" },
220
+ set: { type: "object", description: "Parameter values by name, as JSON values" },
221
+ position: object({ x: { type: "number" }, y: { type: "number" } }, ["x", "y"]),
222
+ comment: { type: "string" },
223
+ dryRun
224
+ }, ["graph", "type"]),
225
+ call: edit((args) => ({
226
+ op: "node.add",
227
+ module: args.type,
228
+ ...args.id ? { id: args.id } : {},
229
+ ...args.in ? { in: args.in } : {},
230
+ ...args.set ? { set: args.set } : {},
231
+ ...args.position ? { position: args.position } : {},
232
+ ...args.comment ? { comment: args.comment } : {}
233
+ }))
234
+ }]
235
+ },
236
+ {
237
+ ...graphVerb,
238
+ name: "node rm",
239
+ aliases: ["node remove"],
240
+ usage: "<graph> <node> [--recursive]",
241
+ summary: "Remove a node and its wires",
242
+ options: { recursive: "flag" },
243
+ operation: (operands, { flags }) => ({ op: "node.remove", id: required(operands[0], "cascade node rm <graph> <node>"), recursive: flags.has("recursive") }),
244
+ mcp: [{
245
+ name: "node_remove",
246
+ description: "Remove a node and the wires touching it. A subnet with children needs `recursive`.",
247
+ inputSchema: object({ graph, id: { type: "string" }, recursive: { type: "boolean" }, dryRun }, ["graph", "id"]),
248
+ call: edit((args) => ({ op: "node.remove", id: args.id, recursive: args.recursive === true }))
249
+ }]
250
+ },
251
+ {
252
+ ...graphVerb,
253
+ name: "connect",
254
+ usage: "<graph> <node.output> <node.input> [--no-layout]",
255
+ summary: "Wire two ports",
256
+ options: { "no-layout": "flag" },
257
+ operation: (operands, { flags }) => ({
258
+ op: "connect",
259
+ from: required(operands[0], "cascade connect <graph> <node.output> <node.input>"),
260
+ to: required(operands[1], "cascade connect <graph> <node.output> <node.input>"),
261
+ ...flags.has("no-layout") ? { layout: false } : {}
262
+ }),
263
+ mcp: [{
264
+ name: "connect",
265
+ description: "Wire node.output to node.input. A port name may be omitted when the node has only one on that side. An input already fed is rewired. A node wired for the first time is moved next to its neighbour unless `layout` is false.",
266
+ inputSchema: object({ graph, from: { type: "string" }, to: { type: "string" }, layout: { type: "boolean" }, dryRun }, ["graph", "from", "to"]),
267
+ call: edit((args) => ({ op: "connect", from: args.from, to: args.to, ...args.layout === false ? { layout: false } : {} }))
268
+ }]
269
+ },
270
+ {
271
+ ...graphVerb,
272
+ name: "disconnect",
273
+ usage: "<graph> <node.output> <node.input> | <node.input>",
274
+ summary: "Remove a wire",
275
+ operation: (operands) => operands[1] ? { op: "disconnect", from: operands[0], to: operands[1] } : { op: "disconnect", to: required(operands[0], "cascade disconnect <graph> <node.output> <node.input>") },
276
+ mcp: [{
277
+ name: "disconnect",
278
+ description: "Remove the wire from `from` to `to`, or with only `to`, whatever feeds that input.",
279
+ inputSchema: object({ graph, from: { type: "string" }, to: { type: "string" }, dryRun }, ["graph", "to"]),
280
+ call: edit((args) => args.from ? { op: "disconnect", from: args.from, to: args.to } : { op: "disconnect", to: args.to })
281
+ }]
282
+ },
283
+ {
284
+ ...graphVerb,
285
+ name: "set",
286
+ usage: "<graph> <node.param> [<value>] [--expr <e> | --clear-expr]\n [--key <frame>=<value>]... [--remove-key <frame>]... [--interp constant|linear|smooth] [--unset]",
287
+ summary: "Set a parameter value, expression or keyframes",
288
+ options: { expr: "value", "clear-expr": "flag", key: "list", "remove-key": "list", interp: "value", unset: "flag" },
289
+ operation: (operands, { values, lists, flags }) => {
290
+ const target = required(operands[0], "cascade set <graph> <node.param> <value>");
291
+ const keys = (lists.key ?? []).map((pair) => {
292
+ const [frame, value] = splitPair(pair, "--key", "=");
293
+ return { frame: Number(frame), value: Number(value) };
294
+ });
295
+ const operation = {
296
+ op: "set",
297
+ target,
298
+ ...operands[1] !== void 0 ? { value: operands[1], fromText: true } : {},
299
+ ...values.expr !== void 0 ? { expression: values.expr } : {},
300
+ ...flags.has("clear-expr") ? { expression: null } : {},
301
+ ...keys.length ? { keys } : {},
302
+ ...lists["remove-key"]?.length ? { removeKeys: lists["remove-key"].map(Number) } : {},
303
+ ...values.interp ? { interpolation: interpolation(values.interp) } : {},
304
+ ...flags.has("unset") ? { unset: true } : {}
305
+ };
306
+ if (Object.keys(operation).length === 2) throw new Error("cascade set needs a value, --expr, --clear-expr, --key, --remove-key or --unset");
307
+ return operation;
308
+ },
309
+ mcp: [{
310
+ name: "set",
311
+ description: "Set node.param: a value (JSON, checked against the declared type), an expression (null removes it), keyframes, or `unset` to return to the default. A value equal to the default is not stored.",
312
+ inputSchema: object({
313
+ graph,
314
+ target: { type: "string", description: "node.param" },
315
+ value: {},
316
+ expression: { type: ["string", "null"] },
317
+ keys: { type: "array", items: object({ frame: { type: "number" }, value: { type: "number" } }, ["frame", "value"]) },
318
+ removeKeys: { type: "array", items: { type: "number" } },
319
+ interpolation: { enum: ["constant", "linear", "smooth"] },
320
+ unset: { type: "boolean" },
321
+ dryRun
322
+ }, ["graph", "target"]),
323
+ call: edit((args) => {
324
+ const { graph: _graph, dryRun: _dryRun, ...change } = args;
325
+ return { op: "set", ...change };
326
+ })
327
+ }]
328
+ },
329
+ {
330
+ ...graphVerb,
331
+ name: "subnet create",
332
+ usage: "<graph> <node>... [--name <id>] [--label <text>]",
333
+ summary: "Group sibling nodes into a subnet",
334
+ options: { name: "value", label: "value" },
335
+ operation: (operands, { values }) => ({
336
+ op: "subnet.create",
337
+ nodes: nonEmpty(operands, "cascade subnet create <graph> <node>..."),
338
+ ...values.name ? { id: values.name } : {},
339
+ ...values.label ? { label: values.label } : {}
340
+ }),
341
+ mcp: [{
342
+ name: "subnet_create",
343
+ description: "Group sibling nodes into a new subnet; wires crossing its boundary go through new Input/Output nodes named after the port they carry.",
344
+ inputSchema: object({ graph, nodes: { type: "array", items: { type: "string" } }, id: { type: "string" }, label: { type: "string" }, dryRun }, ["graph", "nodes"]),
345
+ call: edit((args) => ({ op: "subnet.create", nodes: args.nodes, ...args.id ? { id: args.id } : {}, ...args.label ? { label: args.label } : {} }))
346
+ }]
347
+ },
348
+ {
349
+ ...graphVerb,
350
+ name: "subnet move",
351
+ usage: "<graph> <node>... --to <subnet|root>",
352
+ summary: "Move sibling nodes into a subnet or to the top level",
353
+ options: { to: "value" },
354
+ operation: (operands, { values }) => ({ op: "subnet.move", nodes: nonEmpty(operands, "cascade subnet move <graph> <node>... --to <subnet|root>"), to: required(values.to, "--to <subnet|root>") }),
355
+ mcp: [{
356
+ name: "subnet_move",
357
+ description: 'Move sibling nodes into another subnet, or to "root", rewiring through the subnet boundaries on the way.',
358
+ inputSchema: object({ graph, nodes: { type: "array", items: { type: "string" } }, to: { type: "string" }, dryRun }, ["graph", "nodes", "to"]),
359
+ call: edit((args) => ({ op: "subnet.move", nodes: args.nodes, to: args.to }))
360
+ }]
361
+ },
362
+ {
363
+ ...graphVerb,
364
+ name: "subnet dissolve",
365
+ usage: "<graph> <subnet>",
366
+ summary: "Lift a subnet's nodes back out and remove it",
367
+ operation: (operands) => ({ op: "subnet.dissolve", id: required(operands[0], "cascade subnet dissolve <graph> <subnet>") }),
368
+ mcp: [{
369
+ name: "subnet_dissolve",
370
+ description: "Lift a subnet's nodes back into its parent and remove the subnet.",
371
+ inputSchema: object({ graph, id: { type: "string" }, dryRun }, ["graph", "id"]),
372
+ call: edit((args) => ({ op: "subnet.dissolve", id: args.id }))
373
+ }]
374
+ },
375
+ {
376
+ ...graphVerb,
377
+ name: "edit",
378
+ usage: "<graph> <operations.json | -> apply a batch, all or nothing",
379
+ summary: "Apply a batch of operations, all or nothing",
380
+ mcp: [{
381
+ name: "edit",
382
+ description: 'Apply several operations at once, all or nothing. Each is { "op": "node.add" | "node.remove" | "connect" | "disconnect" | "set" | "subnet.create" | "subnet.move" | "subnet.dissolve", ... } with the fields of the matching tool (node.add takes `module` and `in`).',
383
+ inputSchema: object({ graph, operations: { type: "array", items: { type: "object" } }, dryRun }, ["graph", "operations"]),
384
+ call: { kind: "edit", operations: (args) => args.operations }
385
+ }]
386
+ },
387
+ // Locks
388
+ {
389
+ ...lockVerb,
390
+ name: "lock",
391
+ usage: "<graph-file> <node> --name <name> [--note ...]",
392
+ summary: "Record a render (graph, params, seeds, inputs, code) in locks/<id>.json",
393
+ mcp: [{
394
+ name: "lock",
395
+ description: "Record a render as a lock (cascade lock): cooks `node`, writes locks/<id>.json with the graph, params, seeds, input hashes, stored code and output fingerprints.",
396
+ inputSchema: object({
397
+ graph,
398
+ node: { type: "string" },
399
+ name: { type: "string" },
400
+ note: { type: "string" },
401
+ scene: { type: "string" },
402
+ seed: { type: "integer" },
403
+ supersedes: { type: "string" },
404
+ preview: { type: "string" },
405
+ live: { type: "boolean" }
406
+ }, ["graph", "node", "name"]),
407
+ call: {
408
+ kind: "cli",
409
+ argv: (args, resolve) => [
410
+ "lock",
411
+ resolve(args.graph),
412
+ args.node,
413
+ "--name",
414
+ args.name,
415
+ ...args.note ? ["--note", args.note] : [],
416
+ ...args.scene ? ["--scene", resolve(args.scene)] : [],
417
+ ...args.seed !== void 0 ? ["--seed", String(args.seed)] : [],
418
+ ...args.supersedes ? ["--supersedes", args.supersedes] : [],
419
+ ...args.preview ? ["--preview", resolve(args.preview)] : [],
420
+ ...args.live ? ["--live"] : []
421
+ ],
422
+ timeoutMs: () => 60 * 6e4
423
+ }
424
+ }]
425
+ },
426
+ {
427
+ ...lockVerb,
428
+ name: "replay",
429
+ usage: "<lock-id> [--out <dir>]",
430
+ summary: "Re-render a lock from its stored code and compare fingerprints",
431
+ mcp: [{
432
+ name: "replay",
433
+ description: "Re-render a lock from its stored code and compare fingerprints (cascade replay). `data.status` is match or mismatch; `live` uses the current code.",
434
+ inputSchema: object({ lock: { type: "string" }, out: { type: "string" }, live: { type: "boolean" } }, ["lock"]),
435
+ call: {
436
+ kind: "cli",
437
+ argv: (args, resolve) => [
438
+ "replay",
439
+ String(args.lock).endsWith(".json") ? resolve(args.lock) : args.lock,
440
+ "--project",
441
+ resolve("."),
442
+ ...args.out ? ["--out", resolve(args.out)] : [],
443
+ ...args.live ? ["--live"] : []
444
+ ],
445
+ timeoutMs: () => 60 * 6e4
446
+ }
447
+ }]
448
+ },
449
+ { ...lockVerb, name: "lock verify", usage: "[<lock-id>...]", summary: "Replay locks and report which still match" },
450
+ {
451
+ ...lockVerb,
452
+ name: "locks",
453
+ usage: "",
454
+ summary: "List the project's locks (cascade lock --help for all options)",
455
+ mcp: [{
456
+ name: "locks",
457
+ description: "List the project's locks (cascade locks).",
458
+ inputSchema: object({}, []),
459
+ call: { kind: "cli", argv: (_args, resolve) => ["locks", resolve(".")] }
460
+ }]
461
+ },
462
+ {
463
+ ...lockVerb,
464
+ name: "ladder",
465
+ usage: "<graph-file> <node> --name <name> --vary <node.prop>=<a,b,c> | --seeds <a,b,c> [--cairn <vault dir>]",
466
+ summary: "Cook 3-5 variants, write a labelled contact sheet and ladders/<id>.json; --cairn makes a tap-to-pick card",
467
+ mcp: [{
468
+ name: "ladder",
469
+ description: 'Cook `node` once per variant (cascade ladder) and return the contact sheet, each variant image and the record ladders/<id>.json. `vary` maps node.prop to a list of values (zipped; one value applies to all); `seeds` varies the graph seed. Three to five variants is the usual. `cairn` is a directory in the Substrate vault (the space\'s _attachments/ladders/<id>): each variant is copied there and `data.cairnChoice` is a cairn:choice fence to post as is (at most 8 variants); a tap comes back as "Picked: <label> (pick:<id>:<n>)", then call `pick` with ladder <id> and variant <n>.',
470
+ inputSchema: object({
471
+ graph,
472
+ node: { type: "string" },
473
+ name: { type: "string" },
474
+ vary: { type: "object", description: "node.prop to an array of values, one per variant" },
475
+ seeds: { type: "array", items: { type: "integer" } },
476
+ columns: { type: "integer" },
477
+ note: { type: "string" },
478
+ live: { type: "boolean" },
479
+ cairn: { type: "string", description: "Absolute vault directory for the variant images; returns data.cairnChoice" },
480
+ vault: { type: "string", description: "Absolute vault root the cairnChoice image paths are relative to (default: nearest .obsidian or .git above `cairn`)" }
481
+ }, ["graph", "node", "name"]),
482
+ call: {
483
+ kind: "cli",
484
+ argv: (args, resolve) => [
485
+ "ladder",
486
+ resolve(args.graph),
487
+ args.node,
488
+ "--name",
489
+ args.name,
490
+ // Outside the project by design (the vault), so passed as given: absolute.
491
+ ...args.cairn ? ["--cairn", String(args.cairn)] : [],
492
+ ...args.vault ? ["--vault", String(args.vault)] : [],
493
+ ...Object.entries(args.vary ?? {}).flatMap(([key, values]) => [
494
+ "--vary",
495
+ `${key}=${(Array.isArray(values) ? values : [values]).map((value) => JSON.stringify(value)).join(",")}`
496
+ ]),
497
+ ...args.seeds?.length ? ["--seeds", args.seeds.join(",")] : [],
498
+ ...args.columns ? ["--columns", String(args.columns)] : [],
499
+ ...args.note ? ["--note", args.note] : [],
500
+ ...args.live ? ["--live"] : []
501
+ ],
502
+ timeoutMs: () => 60 * 6e4
503
+ }
504
+ }]
505
+ },
506
+ {
507
+ ...lockVerb,
508
+ name: "pick",
509
+ usage: "<ladder-id> <n> [--name <lock name>] [--note ...]",
510
+ summary: "Lock variant <n> of a ladder at approval (commit, seed, params, image)",
511
+ mcp: [{
512
+ name: "pick",
513
+ description: "Write the lock for variant `variant` of a ladder (cascade pick), from what the ladder recorded, without cooking again. `cascade replay` then reproduces that image.",
514
+ inputSchema: object({
515
+ ladder: { type: "string" },
516
+ variant: { type: "integer" },
517
+ name: { type: "string" },
518
+ note: { type: "string" },
519
+ supersedes: { type: "string" }
520
+ }, ["ladder", "variant"]),
521
+ call: {
522
+ kind: "cli",
523
+ argv: (args, resolve) => [
524
+ "pick",
525
+ String(args.ladder).endsWith(".json") ? resolve(args.ladder) : args.ladder,
526
+ String(args.variant),
527
+ "--project",
528
+ resolve("."),
529
+ ...args.name ? ["--name", args.name] : [],
530
+ ...args.note ? ["--note", args.note] : [],
531
+ ...args.supersedes ? ["--supersedes", args.supersedes] : []
532
+ ]
533
+ }
534
+ }]
535
+ },
536
+ // One of `cascade render --workers`' processes, forked by it with its spec.
537
+ { name: "__render-worker", usage: "<spec>", summary: "internal", section: "internal", handler: "renderWorker", json: "studio", help: "own", version: "handler" },
538
+ // A node's declaration from the installed package (helpCommand.ts).
539
+ {
540
+ name: "help node",
541
+ usage: "[<type>] [--project <dir>]",
542
+ summary: "Print a node type's inputs, props, outputs and description, or list every node type",
543
+ section: "agents",
544
+ handler: "help",
545
+ json: "keep",
546
+ help: "own",
547
+ version: "handler",
548
+ mcp: [{
549
+ name: "node_help",
550
+ description: "A node type's declaration: inputs, props and outputs with types, defaults and ranges, and its description (cascade help node). `type` is a module id or short name; without it, every node type this build and the project know.",
551
+ inputSchema: object({ type: { type: "string" } }, []),
552
+ call: { kind: "cli", argv: (args, resolve) => ["help", "node", ...args.type ? [String(args.type)] : [], "--project", resolve(".")] }
553
+ }]
554
+ },
555
+ // What a headless run here will find; --fix builds the declared Python environment (doctorCommand.ts).
556
+ {
557
+ name: "doctor",
558
+ usage: "[project-directory] [--fix]",
559
+ summary: "Check the project's Python environment, stage interpreter, ffmpeg and canvas; --fix builds the declared environment",
560
+ section: "run",
561
+ handler: "doctor",
562
+ json: "keep",
563
+ help: "own",
564
+ version: "handler",
565
+ mcp: [{
566
+ name: "doctor",
567
+ description: 'Check what a headless run in the project will find (cascade doctor): the declared Python environment (cascade.json "python"), the stage interpreter, ffmpeg and the canvas. `fix` creates or updates the environment in .cascade-cache/python/venv.',
568
+ inputSchema: object({ fix: { type: "boolean" } }, []),
569
+ call: { kind: "cli", argv: (args, resolve) => ["doctor", resolve("."), ...args.fix ? ["--fix"] : []], timeoutMs: () => 30 * 6e4 }
570
+ }]
571
+ }
572
+ ];
573
+ function verbFor(args) {
574
+ const [first, second] = args;
575
+ if (!first) return void 0;
576
+ const two = `${first} ${second ?? ""}`;
577
+ return VERBS.find((verb) => verb.name === two || verb.aliases?.includes(two)) ?? VERBS.find((verb) => verb.name === first) ?? VERBS.find((verb) => verb.name.split(" ")[0] === first);
578
+ }
579
+ function isGraphCommand(args) {
580
+ return verbFor(args)?.handler === "graph";
581
+ }
582
+ function graphOptions() {
583
+ const sets = { value: /* @__PURE__ */ new Set(), list: /* @__PURE__ */ new Set(), flag: /* @__PURE__ */ new Set(["dry-run", "json"]) };
584
+ for (const verb of VERBS) {
585
+ if (verb.handler !== "graph") continue;
586
+ for (const [name, kind] of Object.entries(verb.options ?? {})) sets[kind].add(name);
587
+ }
588
+ return sets;
589
+ }
590
+ function mcpTools() {
591
+ return VERBS.flatMap((verb) => (verb.mcp ?? []).map((tool) => ({ verb, tool })));
592
+ }
593
+ var usageLine = (verb) => ` cascade ${verb.name}${verb.usage ? ` ${verb.usage}` : ""}`;
594
+ function graphCommandsHelp() {
595
+ return `
596
+ Graph editing (every command takes --json; edits take --dry-run):
597
+ ${VERBS.filter((verb) => verb.section === "graph").map(usageLine).join("\n")}
598
+ `;
599
+ }
600
+ function mainHelp() {
601
+ const listed = VERBS.filter((verb) => verb.section !== "internal");
602
+ const before = listed.filter((verb) => verb.section !== "graph" && verb.section !== "locks" && verb.name !== "parity");
603
+ const after = listed.filter((verb) => verb.section === "locks" || verb.name === "parity");
604
+ const summaries = listed.filter((verb) => verb.section !== "graph");
605
+ const width = Math.max(...summaries.map((verb) => verb.name.length)) + 2;
606
+ return `
607
+ Cascade Graph Execution CLI
608
+
609
+ Usage:
610
+ cascade [project-directory | graph-file] [Studio options]
611
+ ${before.map(usageLine).join("\n")}
612
+ ${graphCommandsHelp()}
613
+ ${after.map(usageLine).join("\n")}
614
+
615
+ Commands:
616
+ ${summaries.map((verb) => ` ${verb.name.padEnd(width)}${verb.summary}`).join("\n")}
617
+
618
+ Options:
619
+ --host <address> Bind Studio to an address or hostname
620
+ --port <number> Bind Studio HTTP to an explicit port
621
+ --trusted-host <name> Allow an exact remote browser hostname (repeatable)
622
+ --fallback-host <addr> Bind this instead when --host does not resolve or is
623
+ not on this machine (repeatable; default 127.0.0.1).
624
+ The preferred host is retried and rebound when it returns
625
+ --no-open Do not open Studio in a browser
626
+ --node <id> Render only this node's output. Repeatable.
627
+ --entry-node <id> Older spelling of --node, single valued
628
+ --frames <range> Render a frame sequence: 1-100, 1-100x2 (step), 42, or
629
+ timeline (the range saved with the graph)
630
+ --fps <number> Frame rate to evaluate the range at (default: the graph's timeline fps)
631
+ --mp4 <file> After --frames, encode the sequence as H.264 yuv420p (needs ffmpeg)
632
+ --crf <n> Quality for --mp4, 0-51, lower is better (default 18)
633
+ --out <dir> Save image outputs here, project-relative or absolute: the
634
+ sequence with --frames (default: renders), else the still
635
+ --out-node <id> Also save this (mid-graph) node's image outputs. Repeatable.
636
+ --env NAME=value Set a variable for the run and its Python stages. Repeatable.
637
+ --json Print one { ok, data, warnings } envelope (any command)
638
+ --seed <n> Cook with this graph seed instead of the document's
639
+ --fresh With --frames, cook every node on every frame (default: only what the frame changed)
640
+ --timeout <ms> Bound a run in a supervised child process (exit 124 on timeout)
641
+ --validate-only Validate only (same as 'validate' command)
642
+ --strict Fail when a node names a module with no file (default: warn)
643
+ --verbose, -v Show verbose output
644
+ --version Show version
645
+ --help, -h Show this help message
646
+
647
+ Examples:
648
+ cascade .
649
+ cascade . --host KURO --port 3030
650
+ cascade new my-artwork
651
+ cascade node Multiply ./my-artwork
652
+ cascade run graph.cascade
653
+ cascade run graph.cascade --entry-node node_123
654
+ cascade validate graph.cascade
655
+ cascade run graph.cascade --frames 1-100
656
+ cascade run graph.cascade --frames 1-100x2 --fps 25 --out frames
657
+ cascade run graph.cascade --verbose
658
+
659
+ A frame sequence is written as <out>/<node id>.<frame>.<ext> \u2014 one file per
660
+ image output nothing downstream consumes (a Null passing an image counts), or
661
+ per output of --node, plus those of each --out-node. A node with several image
662
+ outputs adds the port: <node id>.<port>.<frame>.<ext>. Without --frames, --out
663
+ writes <out>/<node id>[.<port>].<ext>, and run --json lists every image output
664
+ in data.images either way.
665
+
666
+ Every run writes a trace to .cascade/runs/: each node's time and every Python
667
+ stage call with its cache key and state (hit, miss, off), and what each node
668
+ and its stages printed while it cooked (also in run --json as data.logs). A
669
+ failed run writes its trace too, and names it in the error. --verbose prints
670
+ the stage lines as they happen.
671
+ `;
672
+ }
673
+ function required(value, usage) {
674
+ if (!value) throw new Error(`usage: ${usage}`);
675
+ return value;
676
+ }
677
+ function nonEmpty(values, usage) {
678
+ if (!values.length) throw new Error(`usage: ${usage}`);
679
+ return [...values];
680
+ }
681
+ function splitPair(pair, flag, separator) {
682
+ const at = pair.indexOf(separator);
683
+ if (at <= 0) throw new Error(`${flag} takes name${separator}value (got "${pair}")`);
684
+ return [pair.slice(0, at), pair.slice(at + 1)];
685
+ }
686
+ function point(text) {
687
+ const [x, y] = text.split(",").map(Number);
688
+ if (!Number.isFinite(x) || !Number.isFinite(y)) throw new Error(`--at takes x,y (got "${text}")`);
689
+ return { x, y };
690
+ }
691
+ function interpolation(text) {
692
+ if (text === "constant" || text === "linear" || text === "smooth") return text;
693
+ throw new Error(`--interp is constant, linear or smooth (got "${text}")`);
694
+ }
695
+
696
+ export {
697
+ VERBS,
698
+ verbFor,
699
+ isGraphCommand,
700
+ graphOptions,
701
+ mcpTools,
702
+ graphCommandsHelp,
703
+ mainHelp
704
+ };
705
+ //# sourceMappingURL=chunk-G23A2BZZ.js.map