@field/cascade 0.7.1 → 0.10.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.
- package/AGENTS.md +17 -1
- package/CHANGELOG.md +260 -0
- package/DESIGN.md +14 -0
- package/README.md +41 -25
- package/dist/assets/CodeEditor-DSZBpzdy.js +623 -0
- package/dist/assets/CodePanel-sY6sZa7B.js +2 -0
- package/dist/assets/CustomNodeDialog-BRKnqZhp.js +1 -0
- package/dist/assets/{ExportDialog-BJu2Bpvv.js → ExportDialog-C7dVGua4.js} +2 -2
- package/dist/assets/OpenGraphDialog-ZsgL-iwr.js +1 -0
- package/dist/assets/ProjectSettings-Cw17nNe3.js +2 -0
- package/dist/assets/SettingsDialog-p25h3tj0.js +1 -0
- package/dist/assets/VersionHistoryDialog-DzFLUotB.js +3 -0
- package/dist/assets/{core-DlkL34JF.js → core-BICrEt0y.js} +8 -8
- package/dist/assets/{cssMode-DaSSFZYi.js → cssMode-D7TXw57I.js} +1 -1
- package/dist/assets/definitions-DUww2Rt-.js +1 -0
- package/dist/assets/{editor.api-CHUMRaMm.js → editor.api-DvkCO3ko.js} +1 -1
- package/dist/assets/{freemarker2-DQ604q2A.js → freemarker2-BA2lxsF1.js} +1 -1
- package/dist/assets/geo-C2RHOVHC.js +1 -0
- package/dist/assets/{handlebars-09pcWz8x.js → handlebars-BcDiyPd8.js} +1 -1
- package/dist/assets/{html-DtIKrZiD.js → html-C7O4TyhX.js} +1 -1
- package/dist/assets/{htmlMode-DgJ_4m1_.js → htmlMode-WaDGF3Mj.js} +1 -1
- package/dist/assets/{image-CtUE4_I-.js → image-DR4oeyJB.js} +3 -3
- package/dist/assets/index-7vae6YFC.css +1 -0
- package/dist/assets/index-DyvDC6pS.js +219 -0
- package/dist/assets/index-client-DqYmKstU.js +1 -0
- package/dist/assets/{javascript-kbIBYYGo.js → javascript-CpIEK7tz.js} +1 -1
- package/dist/assets/{jsonMode-DblCG64t.js → jsonMode-B2FltW0f.js} +1 -1
- package/dist/assets/legacy-CTEeXeqF.js +2 -0
- package/dist/assets/{liquid-D-qdTGhQ.js → liquid-CN6FFHZE.js} +1 -1
- package/dist/assets/{lspLanguageFeatures-Bc6azcFQ.js → lspLanguageFeatures-QhV9XtLp.js} +1 -1
- package/dist/assets/math-DaDEgWKt.js +1 -0
- package/dist/assets/{mdx-TXg4xsyD.js → mdx-DE89pafV.js} +1 -1
- package/dist/assets/pop-pLH7L0PW.js +1 -0
- package/dist/assets/{python-C4SGyXJW.js → python-ByTllTB9.js} +1 -1
- package/dist/assets/{razor-BZ-lEgY9.js → razor-USYlRRCE.js} +1 -1
- package/dist/assets/studioScenes-8sq9FfFg.js +1 -0
- package/dist/assets/{tsMode-BoaO1D7x.js → tsMode-BXpN8iUz.js} +1 -1
- package/dist/assets/{typescript-D87dYk9k.js → typescript-DInBjGQQ.js} +1 -1
- package/dist/assets/{workers-ZrST7klJ.js → workers-C7Ye5B7D.js} +1 -1
- package/dist/assets/{xml-DT6r4F7G.js → xml-U0Uocqyo.js} +1 -1
- package/dist/assets/{yaml-DxmQ8XYb.js → yaml-CBw_PVYf.js} +1 -1
- package/dist/cli/index.js +20070 -3487
- package/dist/cli/runtime/stage.ts +70 -10
- package/dist/contracts/bundle.d.ts +75 -0
- package/dist/contracts/bundle.js +112 -0
- package/dist/contracts/definition.d.ts +67 -4
- package/dist/contracts/document.d.ts +29 -0
- package/dist/contracts/editor.d.ts +172 -0
- package/dist/contracts/editor.js +2 -0
- package/dist/contracts/index.d.ts +2 -0
- package/dist/contracts/index.js +2 -0
- package/dist/contracts/validate.js +59 -3
- package/dist/contracts/values.d.ts +3 -3
- package/dist/contracts/values.js +1 -0
- package/dist/index.html +6 -4
- package/dist/player/app.js +17 -5
- package/dist/player/editor.js +1894 -0
- package/dist/player/editorEngine.js +477 -0
- package/dist/runtime/builtins/core/bundle.d.ts +70 -0
- package/dist/runtime/builtins/core/bundle.js +81 -0
- package/dist/runtime/builtins/core/index.d.ts +3 -2
- package/dist/runtime/builtins/core/index.js +6 -2
- package/dist/runtime/builtins/core/routing.d.ts +27 -0
- package/dist/runtime/builtins/core/routing.js +28 -0
- package/dist/runtime/edit/document.d.ts +94 -0
- package/dist/runtime/edit/document.js +194 -0
- package/dist/runtime/edit/graphEditor.d.ts +223 -0
- package/dist/runtime/edit/graphEditor.js +893 -0
- package/dist/runtime/edit/index.d.ts +12 -0
- package/dist/runtime/edit/index.js +6 -0
- package/dist/runtime/edit/layout.d.ts +50 -0
- package/dist/runtime/edit/layout.js +126 -0
- package/dist/runtime/edit/operations.d.ts +65 -0
- package/dist/runtime/edit/operations.js +57 -0
- package/dist/runtime/edit/parameters.d.ts +75 -0
- package/dist/runtime/edit/parameters.js +284 -0
- package/dist/runtime/edit/read.d.ts +35 -0
- package/dist/runtime/edit/read.js +65 -0
- package/dist/runtime/editor/index.d.ts +104 -0
- package/dist/runtime/editor/index.js +439 -0
- package/dist/runtime/expressions/bundlePaths.d.ts +37 -0
- package/dist/runtime/expressions/bundlePaths.js +55 -0
- package/dist/runtime/expressions/index.d.ts +2 -0
- package/dist/runtime/expressions/index.js +1 -0
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/params/animator.d.ts +5 -0
- package/dist/runtime/params/animator.js +16 -0
- package/dist/runtime/runtime.js +238 -12
- package/dist/runtime/seed.d.ts +29 -0
- package/dist/runtime/seed.js +52 -0
- package/dist/runtime/types.d.ts +12 -0
- package/dist/stage.js +40 -9
- package/dist/studio/panel.d.ts +2 -0
- package/doc/CLI_REFERENCE.md +100 -0
- package/doc/EDITOR_EXPORT.md +226 -0
- package/doc/HEADLESS_GPU.md +27 -0
- package/doc/MCP.md +53 -0
- package/doc/NODE_REFERENCE.md +66 -0
- package/doc/PROJECT_AUTHORING.md +137 -0
- package/package.json +15 -6
- package/dist/assets/CodeEditor-D-N0C2jT.js +0 -623
- package/dist/assets/CodePanel-EN411mp5.js +0 -2
- package/dist/assets/CustomNodeDialog-6z2n27VV.js +0 -1
- package/dist/assets/OpenGraphDialog-fMZjzIR3.js +0 -1
- package/dist/assets/ProjectSettings-BQ8PGNqo.js +0 -2
- package/dist/assets/SettingsDialog-IbYLm8Bx.js +0 -1
- package/dist/assets/VersionHistoryDialog-Bgh-q5Jg.js +0 -3
- package/dist/assets/definitions-CQBdsJYJ.js +0 -1
- package/dist/assets/geo-DcmMAcqM.js +0 -1
- package/dist/assets/index-CnylT98t.js +0 -212
- package/dist/assets/index-client-BoJgsN9M.js +0 -1
- package/dist/assets/index-ilgbOu6G.css +0 -1
- package/dist/assets/legacy-CAj2_7QE.js +0 -2
- package/dist/assets/math-CY11tkoN.js +0 -1
- package/dist/assets/pop-Dr5-ARqQ.js +0 -1
- package/dist/assets/projectSettingsApi-C1WvIwfP.js +0 -1
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seeds: one number per graph, one derived number per node, the same in every
|
|
3
|
+
* host.
|
|
4
|
+
*
|
|
5
|
+
* A node that wants randomness gets `context.seed` and `context.random` from
|
|
6
|
+
* whichever host cooks it — the headless runtime here, Studio through its
|
|
7
|
+
* definition adapter — and both compute them with these functions. So the
|
|
8
|
+
* same graph draws the same dots headless and in Studio, and changing the
|
|
9
|
+
* graph's `seed` reshuffles every node at once while each keeps its own
|
|
10
|
+
* stream (doc/PRODUCTION_READINESS.md, item 5).
|
|
11
|
+
*
|
|
12
|
+
* The node id is part of the derivation, not its position: ids are stable
|
|
13
|
+
* across edits, so adding a node never reshuffles its neighbours.
|
|
14
|
+
*/
|
|
15
|
+
/** The graph seed when a document does not state one. */
|
|
16
|
+
export const DEFAULT_GRAPH_SEED = 0;
|
|
17
|
+
/** A document's `seed`, as an unsigned 32-bit integer; the default when absent
|
|
18
|
+
* or not an integer. */
|
|
19
|
+
export function graphSeed(document) {
|
|
20
|
+
const value = document?.seed;
|
|
21
|
+
return typeof value === "number" && Number.isInteger(value) ? value >>> 0 : DEFAULT_GRAPH_SEED;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A node's seed: FNV-1a over `<graph seed>:<node id>`, finished with
|
|
25
|
+
* MurmurHash3's avalanche so neighbouring ids give unrelated seeds.
|
|
26
|
+
*/
|
|
27
|
+
export function nodeSeed(seedOfGraph, nodeId) {
|
|
28
|
+
const text = `${seedOfGraph >>> 0}:${nodeId}`;
|
|
29
|
+
let hash = 0x811c9dc5;
|
|
30
|
+
for (let index = 0; index < text.length; index++) {
|
|
31
|
+
hash ^= text.charCodeAt(index);
|
|
32
|
+
hash = Math.imul(hash, 0x01000193);
|
|
33
|
+
}
|
|
34
|
+
hash ^= hash >>> 16;
|
|
35
|
+
hash = Math.imul(hash, 0x85ebca6b);
|
|
36
|
+
hash ^= hash >>> 13;
|
|
37
|
+
hash = Math.imul(hash, 0xc2b2ae35);
|
|
38
|
+
hash ^= hash >>> 16;
|
|
39
|
+
return hash >>> 0;
|
|
40
|
+
}
|
|
41
|
+
/** Mulberry32: a small, fast stream of floats in [0, 1) from one seed. */
|
|
42
|
+
export function seededRandom(seed) {
|
|
43
|
+
let state = seed >>> 0;
|
|
44
|
+
return () => {
|
|
45
|
+
state = (state + 0x6d2b79f5) >>> 0;
|
|
46
|
+
let value = state;
|
|
47
|
+
value = Math.imul(value ^ (value >>> 15), value | 1);
|
|
48
|
+
value ^= value + Math.imul(value ^ (value >>> 7), value | 61);
|
|
49
|
+
return ((value ^ (value >>> 14)) >>> 0) / 0x1_0000_0000;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=seed.js.map
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -100,6 +100,18 @@ export interface RunOptions {
|
|
|
100
100
|
readonly frame?: number;
|
|
101
101
|
/** Frames per second, for `$FPS` and for `$T`. Default 30. */
|
|
102
102
|
readonly fps?: number;
|
|
103
|
+
/**
|
|
104
|
+
* Keep the outputs of nodes whose inputs and props did not change since
|
|
105
|
+
* their last cook, instead of cooking the whole selection again. For hosts
|
|
106
|
+
* that re-run on every edit, such as an exported editor.
|
|
107
|
+
*
|
|
108
|
+
* `"strict"` also cooks every node downstream of one that cooked: equal
|
|
109
|
+
* inputs are not trusted when they come from a node that just ran, because
|
|
110
|
+
* a node that writes a fixed path and outputs the same path string would
|
|
111
|
+
* otherwise leave its consumers on the previous file. `cascade run
|
|
112
|
+
* --frames` uses it, so a frame re-cooks only what that frame changed.
|
|
113
|
+
*/
|
|
114
|
+
readonly reuse?: boolean | "strict";
|
|
103
115
|
}
|
|
104
116
|
export interface RunResult {
|
|
105
117
|
readonly runId: string;
|
package/dist/stage.js
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
// server/src/runtime/stage.ts
|
|
2
2
|
var BRIDGE_KEY = "__cascadeStageBridge";
|
|
3
|
+
var COOK_SIGNAL_KEY = "__cascadeCookSignal";
|
|
4
|
+
function ambientCookSignal() {
|
|
5
|
+
const read = globalThis[COOK_SIGNAL_KEY];
|
|
6
|
+
if (typeof read !== "function") return void 0;
|
|
7
|
+
const signal = read();
|
|
8
|
+
return signal ?? void 0;
|
|
9
|
+
}
|
|
10
|
+
function cancelledError(stage, signal) {
|
|
11
|
+
const reason = signal.reason instanceof Error ? signal.reason.message : "superseded";
|
|
12
|
+
const error = new Error(`stage "${stage}" was cancelled: ${reason}`);
|
|
13
|
+
error.name = "AbortError";
|
|
14
|
+
return error;
|
|
15
|
+
}
|
|
3
16
|
function installStageBridge(bridge) {
|
|
4
17
|
const globals = globalThis;
|
|
5
18
|
const previous = globals[BRIDGE_KEY];
|
|
@@ -36,18 +49,26 @@ async function capability() {
|
|
|
36
49
|
if (token === null) throw new Error("Cascade exec capability is unavailable (404)");
|
|
37
50
|
return token;
|
|
38
51
|
}
|
|
39
|
-
async function overHttp(stage, args) {
|
|
52
|
+
async function overHttp(stage, args, signal) {
|
|
40
53
|
const postStage = async () => fetch("/api/exec", {
|
|
41
54
|
method: "POST",
|
|
42
55
|
headers: { "Content-Type": "application/json", "X-Cascade-Exec-Capability": await capability() },
|
|
43
|
-
body: JSON.stringify({ stage, args })
|
|
56
|
+
body: JSON.stringify({ stage, args }),
|
|
57
|
+
signal
|
|
44
58
|
});
|
|
45
|
-
let response
|
|
46
|
-
|
|
47
|
-
|
|
59
|
+
let response;
|
|
60
|
+
let body;
|
|
61
|
+
try {
|
|
48
62
|
response = await postStage();
|
|
63
|
+
if (response.status === 403) {
|
|
64
|
+
capabilityPromise = void 0;
|
|
65
|
+
response = await postStage();
|
|
66
|
+
}
|
|
67
|
+
body = await response.json();
|
|
68
|
+
} catch (error) {
|
|
69
|
+
if (signal?.aborted) throw cancelledError(stage, signal);
|
|
70
|
+
throw error;
|
|
49
71
|
}
|
|
50
|
-
const body = await response.json();
|
|
51
72
|
if (!response.ok || !body.ok) {
|
|
52
73
|
throw new Error(`stage "${stage}" failed: ${body.stderr || body.error || "unknown error"}`);
|
|
53
74
|
}
|
|
@@ -62,20 +83,30 @@ async function stageAvailable() {
|
|
|
62
83
|
if (typeof document === "undefined") return false;
|
|
63
84
|
return await discoverCapability() !== null;
|
|
64
85
|
}
|
|
65
|
-
async function runStage(stage, args = {}) {
|
|
86
|
+
async function runStage(stage, args = {}, options = {}) {
|
|
87
|
+
const signal = options.signal ?? ambientCookSignal();
|
|
88
|
+
if (signal?.aborted) throw cancelledError(stage, signal);
|
|
66
89
|
const bridge = localBridge();
|
|
67
|
-
if (bridge)
|
|
90
|
+
if (bridge) {
|
|
91
|
+
try {
|
|
92
|
+
return await bridge(stage, args, signal ? { signal } : void 0);
|
|
93
|
+
} catch (error) {
|
|
94
|
+
if (signal?.aborted) throw cancelledError(stage, signal);
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
68
98
|
if (typeof document === "undefined") {
|
|
69
99
|
throw new Error(
|
|
70
100
|
`Cannot run stage "${stage}": no host bridge is installed and there is no page to post from. A headless host must call installStageBridge() from cascade/stage before executing the graph.`
|
|
71
101
|
);
|
|
72
102
|
}
|
|
73
|
-
return overHttp(stage, args);
|
|
103
|
+
return overHttp(stage, args, signal);
|
|
74
104
|
}
|
|
75
105
|
function cachePath(nodeId, suffix) {
|
|
76
106
|
return `.cascade-cache/${nodeId.replace(/[^a-zA-Z0-9_-]/g, "_")}${suffix}`;
|
|
77
107
|
}
|
|
78
108
|
export {
|
|
109
|
+
COOK_SIGNAL_KEY,
|
|
79
110
|
cachePath,
|
|
80
111
|
installStageBridge,
|
|
81
112
|
runStage,
|
package/dist/studio/panel.d.ts
CHANGED
|
@@ -78,6 +78,8 @@ export interface ProjectPanelApi {
|
|
|
78
78
|
* through the series API instead.
|
|
79
79
|
*/
|
|
80
80
|
cook(options?: { readonly nodeId?: string }): Promise<void>;
|
|
81
|
+
/** Open the Agent panel with a draft for the user to review and send. */
|
|
82
|
+
openAgent(prompt: string): void;
|
|
81
83
|
selectedNodeId(): string | null;
|
|
82
84
|
readonly sourceNodeId: string | null;
|
|
83
85
|
readonly signal: AbortSignal;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# CLI reference: editing graphs
|
|
2
|
+
|
|
3
|
+
Everything an agent needs to build or change a `.cascade` graph without hand-editing JSON. Each command is one graph concept (node, wire, parameter, subnet), checks the edit with the same code as `cascade check`, and writes the file atomically. For running, rendering and exporting, see `cascade --help`, `doc/EDITOR_EXPORT.md` and `doc/WEB_PLAYER.md`.
|
|
4
|
+
|
|
5
|
+
## The guarantees
|
|
6
|
+
|
|
7
|
+
- **Minimal diffs.** Only the keys an edit is about change. Node ids are stable, untouched nodes keep their place, a new key goes where Studio would write it, and the file keeps its own formatting: Studio's compact arrays, or plain `json.dump` indentation with `1.0` and `—` left as they were. An edit followed by its inverse leaves the file byte-identical (`tests/graph-edit.test.ts`).
|
|
8
|
+
- **Tidy placement.** A new node waits in a lane beside the graph until it is wired; the first `connect` moves it one step along the existing flow from its neighbour, beside anything already there. Studio flows top to bottom, and a graph laid out some other way (cloud-plots' diagonal stages) is followed instead. `--no-layout` leaves positions alone; `--at x,y` places a node exactly.
|
|
9
|
+
- **Closed subnets.** No wire crosses a subnet boundary directly. Connecting across one, moving nodes in or out, or grouping nodes routes the wire through the subnet's `Input` and `Output` nodes, named after what they carry (`motif-out-geometry`), reusing one that already carries the same value and removing one that is no longer used.
|
|
10
|
+
- **Refused, not broken.** An edit that would make `cascade check` fail (a cycle, a wrong type, a misspelt parameter, a value of the wrong type) is refused and nothing is written. A graph that already failed can still be repaired one edit at a time: only new failures count. A file that changed on disk while the edit ran (Studio saved, another agent wrote) is not overwritten.
|
|
11
|
+
|
|
12
|
+
## Commands
|
|
13
|
+
|
|
14
|
+
| Command | What it does |
|
|
15
|
+
| --- | --- |
|
|
16
|
+
| `cascade graph create <graph> [--name <title>]` | A new empty graph. Never overwrites. |
|
|
17
|
+
| `cascade graph read <graph> [--in <subnet>]` | Nodes with stored parameters, subnets with their ports, wires as `a.out -> b.in`. |
|
|
18
|
+
| `cascade graph list [dir]` | Every `.cascade` under a directory, with node and wire counts. |
|
|
19
|
+
| `cascade node add <graph> <type> [--name <id>] [--in <subnet>] [--set k=v]... [--at x,y] [--comment <text>]` | Add a node. `<type>` is a module id or a short name: `Circle` is `cascade.geo.Circle`, `InkVocabulary` is `project.InkVocabulary`. The id defaults to the type in kebab case (`copy-to-points`, then `copy-to-points-2`). |
|
|
20
|
+
| `cascade node rm <graph> <node> [--recursive]` | Remove a node and its wires. A subnet with children needs `--recursive`. |
|
|
21
|
+
| `cascade connect <graph> <node.output> <node.input> [--no-layout]` | Wire two ports. A port can be left off when the node has one on that side. An input that was fed is rewired, and the old wire is reported. |
|
|
22
|
+
| `cascade disconnect <graph> <node.output> <node.input>` | Remove that wire. With one argument, `<node.input>` removes whatever feeds it, `<node.output>` everything it feeds. |
|
|
23
|
+
| `cascade set <graph> <node.param> <value>` | Store a value, read by the parameter's declared type: `0.5`, `true`, `640,480`, `#f4f1ea`, `"300"`. A value equal to the default is not stored. |
|
|
24
|
+
| `cascade set <graph> <node.param> --expr '<e>'` / `--clear-expr` | Bind or remove an expression (`$T * 0.1`). Props only. |
|
|
25
|
+
| `cascade set <graph> <node.param> --key 0=1 --key 120=1.4 [--interp linear]` / `--remove-key 120` | Set or remove keyframes on the prop's channel. Interpolation is `constant`, `linear` or `smooth` (the default). |
|
|
26
|
+
| `cascade set <graph> <node.param> --unset` | Forget the stored value and its bindings. |
|
|
27
|
+
| `cascade subnet create <graph> <node>... [--name <id>] [--label <text>]` | Group sibling nodes. The subnet takes the place of the first of them in the flow. |
|
|
28
|
+
| `cascade subnet move <graph> <node>... --to <subnet\|root>` | Move sibling nodes into a subnet or out to the top level, rewiring at each boundary. |
|
|
29
|
+
| `cascade subnet dissolve <graph> <subnet>` | Lift the nodes back out and remove the subnet. The inverse of `create`. |
|
|
30
|
+
| `cascade edit <graph> <operations.json \| ->` | A batch, all or nothing. Each operation is the JSON below. |
|
|
31
|
+
|
|
32
|
+
Every edit takes `--dry-run` (check, write nothing) and `--json`.
|
|
33
|
+
|
|
34
|
+
## `--json`, on every command
|
|
35
|
+
|
|
36
|
+
Every command, not only the editing ones, prints one envelope on stdout and nothing else:
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{ "ok": true, "data": { "file": "/…/index.cascade", "changed": true, "results": [
|
|
40
|
+
{ "op": "set", "node": "render", "param": "strokeWidth", "field": "props", "before": 2, "after": 3 } ] },
|
|
41
|
+
"warnings": [] }
|
|
42
|
+
{ "ok": false, "error": { "message": "render has no parameter \"strokewidth\"; props: size, filename, …" }, "warnings": [] }
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`data` is the command's structured result where it has one (the editing commands, `inspect`, `run`, `parity`), and `{ "output": [lines] }` for a command that only prints. What a command warns becomes `warnings`. The exit code is 0 for `ok` and 1 otherwise. `run --json` puts its manifest in `data`: the status, the seed, the path of the run trace in `.cascade/runs/`, the frames and files with `--frames`, and `stages`, one entry per Python stage call with its node, key and cache state (`hit`, `miss`, `off` and why). `parity --json` puts its report in `data`, with `data.ok` false when outputs differ; see [Seeds and parity](PROJECT_AUTHORING.md#seeds-and-parity).
|
|
46
|
+
|
|
47
|
+
## Operations, for `cascade edit` and the MCP server
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
[
|
|
51
|
+
{ "op": "node.add", "module": "Circle", "id": "dot", "in": "motif", "set": { "radius": [0.2, 0.2] } },
|
|
52
|
+
{ "op": "connect", "from": "dot.geometry", "to": "copy.source" },
|
|
53
|
+
{ "op": "disconnect", "from": "a.out", "to": "b.in" },
|
|
54
|
+
{ "op": "set", "target": "render.opacity", "keys": [{ "frame": 0, "value": 0 }, { "frame": 48, "value": 1 }], "interpolation": "linear" },
|
|
55
|
+
{ "op": "set", "target": "render.strokeWidth", "expression": "1 + $T * 0.1" },
|
|
56
|
+
{ "op": "node.remove", "id": "old", "recursive": false },
|
|
57
|
+
{ "op": "subnet.create", "nodes": ["a", "b"], "label": "Motif" },
|
|
58
|
+
{ "op": "subnet.move", "nodes": ["c"], "to": "root" },
|
|
59
|
+
{ "op": "subnet.dissolve", "id": "motif" }
|
|
60
|
+
]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Values in operations are JSON values, used as given and checked against the declared type.
|
|
64
|
+
|
|
65
|
+
## The TypeScript API
|
|
66
|
+
|
|
67
|
+
The CLI and the MCP server are thin layers over one API, so a script can use it directly:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import { GraphFile } from './src/cli/graphFile.js';
|
|
71
|
+
|
|
72
|
+
const graph = await GraphFile.open('index.cascade');
|
|
73
|
+
await graph.apply({ op: 'node.add', module: 'Circle', id: 'dot' });
|
|
74
|
+
await graph.apply({ op: 'connect', from: 'dot.geometry', to: 'render.scene' });
|
|
75
|
+
const { results, warnings } = await graph.save();
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The editor itself (`GraphEditor`, `readGraph`, the operation types) is environment-neutral and exported from `@field/cascade/runtime`: it edits a parsed document in memory and asks a callback for node definitions, so a browser host can use the same code.
|
|
79
|
+
|
|
80
|
+
## A graph built from nothing
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
cascade graph create index.cascade --name "Built from the CLI"
|
|
84
|
+
cascade node add index.cascade Circle --set radius=0.18,0.18 --set divisions=6
|
|
85
|
+
cascade node add index.cascade Rectangle --set size=2,2
|
|
86
|
+
cascade node add index.cascade CopyToPoints
|
|
87
|
+
cascade connect index.cascade circle copy-to-points.source
|
|
88
|
+
cascade connect index.cascade rectangle copy-to-points.target
|
|
89
|
+
cascade subnet create index.cascade circle rectangle copy-to-points --label Motif
|
|
90
|
+
cascade node add index.cascade Transform --set rotate=0.4
|
|
91
|
+
cascade connect index.cascade copy-to-points transform.geometry # routed out through motif-out-geometry
|
|
92
|
+
cascade node add index.cascade Camera
|
|
93
|
+
cascade node add index.cascade Render --set size=800,800 --set strokeWidth=3 --set 'background=#f4f1ea'
|
|
94
|
+
cascade connect index.cascade transform render.scene
|
|
95
|
+
cascade connect index.cascade camera render.camera
|
|
96
|
+
cascade set index.cascade render.opacity --key 1=0.25 --key 48=1 --interp linear
|
|
97
|
+
cascade check index.cascade && cascade run index.cascade --frames 1 --json
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Opened in Studio, the top level reads `motif → transform → render`, with the camera beside `transform`, and `motif` holds the circle and rectangle side by side above `copy-to-points` and its output.
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# Shareable editors
|
|
2
|
+
|
|
3
|
+
`cascade export` turns a graph into a small static website: a viewer, the
|
|
4
|
+
parameters you chose to publish, presets, scene files and export buttons. Send
|
|
5
|
+
the link to someone and they can change your scene, make new ones and send them
|
|
6
|
+
back, without Studio, an account or a server.
|
|
7
|
+
|
|
8
|
+
## The boundary: what leaves the project
|
|
9
|
+
|
|
10
|
+
An export is an allowlist. Read this before sharing anything.
|
|
11
|
+
|
|
12
|
+
- **Only the selected subgraph ships.** That is the nodes your published
|
|
13
|
+
outputs and parameters depend on. Every other node, all annotations and
|
|
14
|
+
comments, and the graph's metadata stay home. `--plan` lists both sides.
|
|
15
|
+
- **Only selected assets ship.** These are the files in `editor.assets`, the
|
|
16
|
+
literal files a shipped prop points at, and the files of frozen snapshots.
|
|
17
|
+
Nothing else is copied: not the project directory, `.cascade-cache`,
|
|
18
|
+
credentials or Python environments.
|
|
19
|
+
- **Live data sources never ship.** A node that reads Observatory, a CLI, a
|
|
20
|
+
cache, body data, the filesystem or a network is live. That means a
|
|
21
|
+
`runsOn: 'server'` node, a node declaring `files`, `python` or `shell`, or a
|
|
22
|
+
node declaring `source: 'live'`. Freeze it with `cascade freeze` into a
|
|
23
|
+
snapshot of exactly the fields you name (one photo and a title, say). The
|
|
24
|
+
export ships the snapshot in place of the node and drops everything upstream.
|
|
25
|
+
If a live source is still wired in, the export fails and names it.
|
|
26
|
+
- **Personal fields are flagged.** Field names that look like notes,
|
|
27
|
+
transcripts, calendar, body or health data, locations or contacts are marked
|
|
28
|
+
PERSONAL in the plan, and so is any field you pass to `--personal`. Nothing is
|
|
29
|
+
frozen unless you name it, so the default is the minimum.
|
|
30
|
+
- **Licensed, hidden and ignored files are refused.** These are anything under
|
|
31
|
+
a `licensed/` directory, dotfiles such as `.env`, and anything git ignores.
|
|
32
|
+
Licensed fonts are dropped into the editor by the person using it. They are
|
|
33
|
+
read in their browser and never uploaded or bundled.
|
|
34
|
+
- **The built site is scanned before it is written.** The export stops if the
|
|
35
|
+
site contains Turso/libSQL URLs, tokens, API keys, private keys, JWTs,
|
|
36
|
+
`/Users/…`, `/home/…` or `~/` paths, Observatory endpoints, your home
|
|
37
|
+
directory, or the value of any secret-looking environment variable or
|
|
38
|
+
project `.env` entry.
|
|
39
|
+
- **The site makes no external calls.** It has no analytics. Its
|
|
40
|
+
`vercel.json` sets a Content Security Policy that only allows its own
|
|
41
|
+
origin, and it is marked noindex.
|
|
42
|
+
- **Scenes carry values, not project data.** A scene holds published parameter
|
|
43
|
+
values and the files the person dropped. A file parameter marked
|
|
44
|
+
`embed: "never"` (a licensed font) records only its name, size and SHA-256.
|
|
45
|
+
|
|
46
|
+
Every export writes `export-manifest.json`, listing each file, its size and
|
|
47
|
+
its source, each frozen field, and every PERSONAL flag. `cascade export --plan`
|
|
48
|
+
prints the same manifest without building anything.
|
|
49
|
+
|
|
50
|
+
## Make a graph shareable in 5 steps
|
|
51
|
+
|
|
52
|
+
1. **Publish the parameters** someone else may change:
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
cascade publish index.cascade spec.capMm --label "Cap height" --group Type --min 3 --max 60 --unit mm
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
This adds an entry to the graph's `editor.parameters`. In Studio, the
|
|
59
|
+
`share` toggle beside a parameter in the Inspector does the same, with
|
|
60
|
+
label, group and range fields. You can also edit the JSON directly (see the
|
|
61
|
+
format below).
|
|
62
|
+
|
|
63
|
+
2. **Declare the outputs.** Add them to `editor.outputs`: one with `view: true`
|
|
64
|
+
is shown in the viewer, and each one gets an export button (`png`, `svg`,
|
|
65
|
+
`json` or `text`). Name the parameters that belong beside a button (dpi,
|
|
66
|
+
ground) in its `parameters`.
|
|
67
|
+
|
|
68
|
+
3. **Freeze any live source** on the published path. Name exactly the fields to
|
|
69
|
+
keep:
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
cascade freeze index.cascade moment --output moment --fields photos[0],instant --personal instant
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
This writes `frozen/moment.json` plus any files those fields refer to.
|
|
76
|
+
Review them and commit them.
|
|
77
|
+
|
|
78
|
+
4. **Review the plan.** Check the nodes, files and PERSONAL flags:
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
cascade export index.cascade --plan
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
5. **Build, and deploy if you want to:**
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
cascade export index.cascade --out export/site
|
|
88
|
+
cascade export index.cascade --out export/site --deploy vercel --project my-editor --scope field-io
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`--out` replaces only a previous export. `--deploy vercel` links in a scratch
|
|
92
|
+
directory, so no `.env.local` can land in the site. It deploys to
|
|
93
|
+
production, then checks that an anonymous request to
|
|
94
|
+
`https://<project>.vercel.app` is refused. If it is not, it says to turn on
|
|
95
|
+
Vercel Authentication.
|
|
96
|
+
|
|
97
|
+
## The `editor` section
|
|
98
|
+
|
|
99
|
+
It sits beside `nodes` in the `.cascade` file. The runtime ignores it, and
|
|
100
|
+
Studio keeps it verbatim when it saves. The types are in
|
|
101
|
+
`packages/contracts/src/editor.ts`.
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
"editor": {
|
|
105
|
+
"id": "cloud-type",
|
|
106
|
+
"title": "Cloud Type",
|
|
107
|
+
"groups": ["Input", "Focus", "Export"],
|
|
108
|
+
"parameters": [
|
|
109
|
+
{ "id": "text", "node": "type-set", "prop": "text", "label": "Text", "group": "Input", "control": "textarea" },
|
|
110
|
+
{ "id": "font", "node": "type-set", "prop": "font", "control": "file", "accept": [".otf", ".ttf"], "embed": "never",
|
|
111
|
+
"help": "Drop your licensed font. It stays in your browser." },
|
|
112
|
+
{ "id": "angle", "node": "focus", "prop": "angle", "control": "slider", "min": -90, "max": 90, "unit": "°",
|
|
113
|
+
"visibleWhen": { "param": "field", "equals": "tilt" } },
|
|
114
|
+
{ "id": "seed", "node": "spec", "prop": "seed", "control": "seed" }
|
|
115
|
+
],
|
|
116
|
+
"outputs": [
|
|
117
|
+
{ "id": "preview", "node": "preview-svg", "port": "svg", "format": "svg", "view": true },
|
|
118
|
+
{ "id": "png", "node": "png", "port": "image", "format": "png", "parameters": ["dpi", "pngGround"] }
|
|
119
|
+
],
|
|
120
|
+
"presets": [
|
|
121
|
+
{ "id": "soft", "label": "Soft", "values": { "strength": 1 }, "keep": ["colour", "font"] }
|
|
122
|
+
],
|
|
123
|
+
"assets": ["assets/fonts/InterDisplay-Medium.otf"],
|
|
124
|
+
"freeze": [{ "node": "moment", "output": "moment", "snapshot": "frozen/moment.json" }],
|
|
125
|
+
"panel": { "module": "editor/panel.ts", "placement": "extend" },
|
|
126
|
+
"inspect": true
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
- **Parameter `id`** is the key a scene uses. It survives renaming the node
|
|
131
|
+
or the prop, so keep it stable.
|
|
132
|
+
- **`control`** is one of `number`, `slider`, `int`, `bool`, `select`
|
|
133
|
+
(`options`, bare or `{ value, label }`), `text`, `textarea`, `color`, `file`
|
|
134
|
+
(`accept`, `embed`), `seed` (with a Reroll button) and `vec2`. If omitted,
|
|
135
|
+
it comes from the prop's type.
|
|
136
|
+
- **`visibleWhen`** takes `{ param, equals | in | not }`, or a list of them
|
|
137
|
+
that must all hold.
|
|
138
|
+
- **Presets** set the values they name and reset everything else to the graph's
|
|
139
|
+
value, except the ids listed in `keep`. The editor also offers "Graph
|
|
140
|
+
defaults".
|
|
141
|
+
- **`panel`** is optional project code for the editor. It exports
|
|
142
|
+
`mount(target, api)` and may return a disposer. The `api` has `get`, `set`,
|
|
143
|
+
`values`, `subscribe` and `output`. A Svelte component works if the project
|
|
144
|
+
has `svelte` installed, because the export compiles `.svelte` imports with
|
|
145
|
+
the project's own compiler. `replace` hides the generated parameters.
|
|
146
|
+
- **Inspect** shows every stage and each of its outputs, read-only: geometry
|
|
147
|
+
drawn, images and SVG shown, and anything else as JSON.
|
|
148
|
+
- **`peek`** lists the parameter ids a phone keeps in reach at the bottom
|
|
149
|
+
sheet's lowest snap point, such as `["text", "seed"]`. By default these are
|
|
150
|
+
the first text and seed parameters.
|
|
151
|
+
- **`draft`** sets values that are cooked while someone drags a slider on a
|
|
152
|
+
phone, such as `{ "density": 0.5 }`. The preview is quicker during the drag,
|
|
153
|
+
and the real values cook again on release. Draft values never reach a scene
|
|
154
|
+
or an export.
|
|
155
|
+
|
|
156
|
+
## On a phone
|
|
157
|
+
|
|
158
|
+
The same export works on a phone without any extra setup. The preview fills the
|
|
159
|
+
top of the screen. Pinch to zoom, drag to pan, and double-tap to fit. The
|
|
160
|
+
controls sit in a bottom sheet with a handle and three snap points (peek, half
|
|
161
|
+
and full), and the groups start folded. Every control is at least 44 px with
|
|
162
|
+
16 px text, and numbers have − and + steppers. The header shrinks to the title
|
|
163
|
+
and a ⋯ menu, which holds the presets, the scene buttons, Inspect stages, the
|
|
164
|
+
exports and the last cook time. A pulsing dot shows the graph is cooking. Exports and
|
|
165
|
+
saved scenes open the share sheet (Save to Files, Photos, AirDrop) and fall back to a
|
|
166
|
+
download. File controls open the file picker. In landscape, the controls move to a side
|
|
167
|
+
panel. Desktop is unchanged.
|
|
168
|
+
|
|
169
|
+
## Scene files
|
|
170
|
+
|
|
171
|
+
A scene is what someone saved in an editor (`*.scene.json`):
|
|
172
|
+
|
|
173
|
+
```json
|
|
174
|
+
{
|
|
175
|
+
"format": "cascade.scene",
|
|
176
|
+
"version": 1,
|
|
177
|
+
"graph": { "id": "cloud-type", "version": "h77d97e88f213c05d" },
|
|
178
|
+
"name": "Derya, cover v2",
|
|
179
|
+
"values": { "text": "CLOUD\nOBSERVATORY", "strength": 0.3, "seed": 11 },
|
|
180
|
+
"files": {
|
|
181
|
+
"font": { "name": "HelveticaNowDisplay-Md.otf", "size": 81234, "sha256": "…" },
|
|
182
|
+
"image": { "name": "sky.png", "mediaType": "image/png", "size": 50211, "sha256": "…", "data": "<base64>" }
|
|
183
|
+
},
|
|
184
|
+
"savedAt": "2026-10-11T15:00:00Z"
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
- **`graph.version`** is a hash of the editor id, the parameter interface and
|
|
189
|
+
the structure of the exported subgraph. Tuning a value in Studio leaves it
|
|
190
|
+
unchanged. Rewiring the graph or renaming a published parameter changes it.
|
|
191
|
+
- **A mismatch degrades rather than failing.** Another version or editor id is
|
|
192
|
+
a warning. Unknown parameters are ignored with a warning. A value of the
|
|
193
|
+
wrong kind is skipped with a warning. Missing parameters take the graph's
|
|
194
|
+
values.
|
|
195
|
+
- **Files:** a file up to 2 MB is embedded unless its parameter says
|
|
196
|
+
`embed: "never"`. A referenced file is matched by name and hash. In the
|
|
197
|
+
editor, that is a file the person dropped before in the same browser. In
|
|
198
|
+
Studio or the CLI, it is a file of that name under `assets/` (including
|
|
199
|
+
`assets/fonts/licensed/`) or `licensed/`. If no match is found, the graph's
|
|
200
|
+
file is kept and the person is told what to supply.
|
|
201
|
+
|
|
202
|
+
**Where scenes open:**
|
|
203
|
+
|
|
204
|
+
- **Exported editor:** "Open scene…" and "Save scene", or drop a
|
|
205
|
+
`.json` file on the viewer.
|
|
206
|
+
- **Studio:** File › Open Scene… applies a scene to the open graph as an
|
|
207
|
+
unsaved edit. Embedded files go into the project's `assets/`. File › Save
|
|
208
|
+
Scene… writes one.
|
|
209
|
+
- **CLI:**
|
|
210
|
+
- `cascade scene index.cascade s.scene.json --out new.cascade` writes a copy of the graph with the scene applied.
|
|
211
|
+
- `cascade run index.cascade --scene s.scene.json --write preview-svg.svg=out.svg` renders with a scene and writes a chosen output.
|
|
212
|
+
|
|
213
|
+
The same scene gives byte-identical output in the editor and under `cascade run`.
|
|
214
|
+
|
|
215
|
+
## Limits
|
|
216
|
+
|
|
217
|
+
- Only browser-runnable definition-v1 nodes ship. Python, shell and dynamic
|
|
218
|
+
nodes are listed together as "not exportable". Port them to TypeScript, or
|
|
219
|
+
freeze their output.
|
|
220
|
+
- The editor cooks in a module worker. Unchanged stages are reused
|
|
221
|
+
(`run({ reuse: true })`), so only the stages after an edit re-cook, and the
|
|
222
|
+
page stays responsive while they do. Values cross to the page by structured
|
|
223
|
+
clone; a value that cannot be cloned is shown as a description.
|
|
224
|
+
- In Studio, a parameter's `share` toggle in the Inspector publishes it, with
|
|
225
|
+
label, group and range fields. Presets, outputs, `visibleWhen` and help text
|
|
226
|
+
are still edited with `cascade publish` or in the JSON.
|
package/doc/HEADLESS_GPU.md
CHANGED
|
@@ -109,6 +109,33 @@ It does not encode PNGs or perform colour conversion. Encode through the existin
|
|
|
109
109
|
image/IO host and publish an `image` value. Graph-wide texture transport, HDR
|
|
110
110
|
readback, and browser video uploads are outside this first implementation.
|
|
111
111
|
|
|
112
|
+
## Determinism
|
|
113
|
+
|
|
114
|
+
A render pass is repeatable on one adapter: draws, and the blending between
|
|
115
|
+
them, are applied in submission order. `cascade parity` checks it
|
|
116
|
+
(`tests/parity-gpu.test.ts` draws 400 alpha-blended triangles and gets the same
|
|
117
|
+
bytes headless twice and through Studio's executor on Dawn). Three things break
|
|
118
|
+
it, and each has a fix:
|
|
119
|
+
|
|
120
|
+
- **Unordered float accumulation.** WGSL has integer atomics only, so a
|
|
121
|
+
Cascade compute shader cannot add floats atomically; accumulate in fixed
|
|
122
|
+
point (`atomicAdd` on `u32`/`i32` of a scaled value), which adds the same in
|
|
123
|
+
any order. The same hazard outside WGSL is the one that hit cloud-plots:
|
|
124
|
+
PyTorch's `index_add` on MPS adds floats with atomics, and on 12 Oct a
|
|
125
|
+
colliding scatter gave 40 different sums in 40 runs while fixed point gave
|
|
126
|
+
one. The fix there is the same (`src/cloud_plots/detsplat.py` in cloud-plots, opt-in per graph with
|
|
127
|
+
the `deterministic` prop).
|
|
128
|
+
- **Unsynchronised writes.** Two invocations writing one texel or buffer
|
|
129
|
+
element without a barrier or atomic leave whichever lands last.
|
|
130
|
+
- **A different adapter.** Rasterisation rules, `fma` contraction and
|
|
131
|
+
transcendental precision differ between GPUs and between Dawn and a browser's
|
|
132
|
+
WebGPU. Studio in a browser is a different adapter from headless Dawn, so
|
|
133
|
+
compare those with a tolerance: `cascade parity --tolerance <0-255>` counts a
|
|
134
|
+
channel difference up to that value as the same and still reports it.
|
|
135
|
+
|
|
136
|
+
Render into `rgba8unorm`, state the blend state on every pipeline rather than
|
|
137
|
+
relying on a default, and seed anything random from `context.seed`.
|
|
138
|
+
|
|
112
139
|
## Verification and limits
|
|
113
140
|
|
|
114
141
|
```sh
|
package/doc/MCP.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# MCP server
|
|
2
|
+
|
|
3
|
+
`cascade mcp [project-directory]` serves the CLI as MCP tools over stdio, so an agent calls `set` instead of shelling out and parsing text. The tools are the CLI's commands under the same names, and every result is the same `--json` envelope (`{ ok, data, warnings }`, or `{ ok: false, error, warnings }` with `isError` set). Graph paths are relative to the project directory and may not leave it.
|
|
4
|
+
|
|
5
|
+
## Set it up in Claude Code
|
|
6
|
+
|
|
7
|
+
From the project directory:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
claude mcp add cascade -- npx --no-install cascade mcp .
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or in the project's `.mcp.json`, so every session in the project has it:
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"mcpServers": {
|
|
18
|
+
"cascade": { "command": "npx", "args": ["--no-install", "cascade", "mcp", "."] }
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
With a global install (`npm i -g @field/cascade`), use `"command": "cascade", "args": ["mcp", "."]`. The tools then appear as `mcp__cascade__set`, `mcp__cascade__render` and so on.
|
|
24
|
+
|
|
25
|
+
## Tools
|
|
26
|
+
|
|
27
|
+
| Tool | CLI equivalent |
|
|
28
|
+
| --- | --- |
|
|
29
|
+
| `graph_list`, `graph_read`, `graph_create` | `cascade graph list \| read \| create` |
|
|
30
|
+
| `node_add`, `node_remove` | `cascade node add \| rm` |
|
|
31
|
+
| `connect`, `disconnect` | `cascade connect \| disconnect` |
|
|
32
|
+
| `set` | `cascade set` (value, `expression`, `keys`, `removeKeys`, `interpolation`, `unset`) |
|
|
33
|
+
| `subnet_create`, `subnet_move`, `subnet_dissolve` | `cascade subnet create \| move \| dissolve` |
|
|
34
|
+
| `edit` | `cascade edit` (a batch of operations, all or nothing) |
|
|
35
|
+
| `check`, `inspect` | `cascade check \| inspect` |
|
|
36
|
+
| `cook` | `cascade run <graph> [--node <id>]` |
|
|
37
|
+
| `render` | `cascade run <graph> --frames … [--fps] [--out] [--node]… [--scene]` |
|
|
38
|
+
| `export` | `cascade export <graph> --plan \| --out <dir>` |
|
|
39
|
+
| `lock`, `replay`, `locks` | `cascade lock <graph> <node> --name`, `cascade replay <id> [--live]`, `cascade locks` |
|
|
40
|
+
|
|
41
|
+
The editing tools take `dryRun`. Reads and edits run inside the server through the same functions the CLI commands call. Cooking, rendering, checking and exporting run `cascade <command> --json` as a child process, so a long render leaves nothing behind in the server and returns exactly what the CLI would print. Calls run one at a time, so two edits to one file never interleave.
|
|
42
|
+
|
|
43
|
+
## A session
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
graph_read { "graph": "index.cascade" }
|
|
47
|
+
node_add { "graph": "index.cascade", "type": "Transform", "set": { "rotate": 0.4 } }
|
|
48
|
+
connect { "graph": "index.cascade", "from": "motif.output_0", "to": "transform.geometry" }
|
|
49
|
+
set { "graph": "index.cascade", "target": "render.opacity", "keys": [{ "frame": 1, "value": 0.25 }, { "frame": 48, "value": 1 }] }
|
|
50
|
+
render { "graph": "index.cascade", "frames": "1-48", "out": "renders/check" }
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
See `doc/CLI_REFERENCE.md` for what each operation guarantees about diffs, placement and subnet boundaries.
|
package/doc/NODE_REFERENCE.md
CHANGED
|
@@ -79,6 +79,24 @@ Runs on `portable`.
|
|
|
79
79
|
| `result` | `any` | | |
|
|
80
80
|
| `history` | `array` | | |
|
|
81
81
|
|
|
82
|
+
### `cascade.core.Frozen`
|
|
83
|
+
|
|
84
|
+
Frozen — Emits a frozen snapshot of a data source's chosen values
|
|
85
|
+
|
|
86
|
+
Runs on `portable`.
|
|
87
|
+
|
|
88
|
+
**Props**
|
|
89
|
+
|
|
90
|
+
| name | type | default | range |
|
|
91
|
+
| --- | --- | --- | --- |
|
|
92
|
+
| `value` | `any` | `null` | |
|
|
93
|
+
|
|
94
|
+
**Outputs**
|
|
95
|
+
|
|
96
|
+
| name | type | default | range |
|
|
97
|
+
| --- | --- | --- | --- |
|
|
98
|
+
| `value` | `any` | | |
|
|
99
|
+
|
|
82
100
|
### `cascade.core.Input`
|
|
83
101
|
|
|
84
102
|
Input — Defines a public graph or subnet input
|
|
@@ -173,6 +191,54 @@ Runs on `portable`.
|
|
|
173
191
|
| --- | --- | --- | --- |
|
|
174
192
|
| `output` | `any` | | |
|
|
175
193
|
|
|
194
|
+
### `cascade.core.Pack`
|
|
195
|
+
|
|
196
|
+
Pack — Collects inputs into a bundle, one named field each
|
|
197
|
+
|
|
198
|
+
Runs on `portable`.
|
|
199
|
+
|
|
200
|
+
**Inputs**
|
|
201
|
+
|
|
202
|
+
| name | type | default | range |
|
|
203
|
+
| --- | --- | --- | --- |
|
|
204
|
+
| `inputs` | `any` | | |
|
|
205
|
+
|
|
206
|
+
**Props**
|
|
207
|
+
|
|
208
|
+
| name | type | default | range |
|
|
209
|
+
| --- | --- | --- | --- |
|
|
210
|
+
| `names` | `string` | `""` | |
|
|
211
|
+
|
|
212
|
+
**Outputs**
|
|
213
|
+
|
|
214
|
+
| name | type | default | range |
|
|
215
|
+
| --- | --- | --- | --- |
|
|
216
|
+
| `bundle` | `bundle` | | |
|
|
217
|
+
|
|
218
|
+
### `cascade.core.Pick`
|
|
219
|
+
|
|
220
|
+
Pick — Reads one field of a bundle
|
|
221
|
+
|
|
222
|
+
Runs on `portable`.
|
|
223
|
+
|
|
224
|
+
**Inputs**
|
|
225
|
+
|
|
226
|
+
| name | type | default | range |
|
|
227
|
+
| --- | --- | --- | --- |
|
|
228
|
+
| `bundle` | `bundle` | | |
|
|
229
|
+
|
|
230
|
+
**Props**
|
|
231
|
+
|
|
232
|
+
| name | type | default | range |
|
|
233
|
+
| --- | --- | --- | --- |
|
|
234
|
+
| `field` | `string` | `""` | |
|
|
235
|
+
|
|
236
|
+
**Outputs**
|
|
237
|
+
|
|
238
|
+
| name | type | default | range |
|
|
239
|
+
| --- | --- | --- | --- |
|
|
240
|
+
| `value` | `any` | | |
|
|
241
|
+
|
|
176
242
|
### `cascade.core.Previous`
|
|
177
243
|
|
|
178
244
|
Previous — The previous step's state, inside a Feedback container. The first step gets the container's initial value.
|