@dpantani/tdmcp 0.1.0 → 0.2.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/README.md +150 -143
- package/dist/cli/agent.d.ts +24 -1
- package/dist/cli/agent.js +423 -365
- package/dist/cli/agent.js.map +1 -1
- package/dist/index.js +1723 -491
- package/dist/index.js.map +1 -1
- package/dist/knowledge/data/meta.json +1 -1
- package/dist/recipes/feedback_tunnel.json +4 -4
- package/dist/recipes/noise_landscape.json +102 -1
- package/dist/recipes/performable_feedback_tunnel.json +92 -0
- package/dist/recipes/webcam_glitch.json +5 -1
- package/package.json +1 -1
- package/recipes/feedback_tunnel.json +4 -4
- package/recipes/noise_landscape.json +102 -1
- package/recipes/performable_feedback_tunnel.json +92 -0
- package/recipes/webcam_glitch.json +5 -1
- package/td/modules/mcp/controllers/api_controller.py +29 -1
- package/td/modules/mcp/services/analysis_service.py +7 -2
- package/td/modules/mcp/services/api_service.py +25 -3
- package/td/modules/mcp/services/batch_service.py +36 -2
- package/td/modules/mcp/services/preview_service.py +84 -11
- package/td/tests/test_api_controller.py +34 -0
- package/td/tests/test_services.py +209 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"source":"bottobot","bottobotVersion":"2.8.0","importedAt":"2026-05-
|
|
1
|
+
{"source":"bottobot","bottobotVersion":"2.8.0","importedAt":"2026-05-26T08:21:03.449Z","counts":{"operators":629,"pythonClasses":68,"tutorials":14,"patterns":32,"glsl":7}}
|
|
@@ -21,14 +21,14 @@
|
|
|
21
21
|
{
|
|
22
22
|
"name": "transform1",
|
|
23
23
|
"type": "transformTOP",
|
|
24
|
-
"parameters": { "
|
|
24
|
+
"parameters": { "sx": 1.03, "sy": 1.03, "rotate": 1.5 },
|
|
25
25
|
"comment": "Zoom + rotate each frame to create the tunnel motion"
|
|
26
26
|
},
|
|
27
27
|
{ "name": "blur1", "type": "blurTOP", "parameters": { "size": 2 } },
|
|
28
28
|
{
|
|
29
29
|
"name": "level1",
|
|
30
30
|
"type": "levelTOP",
|
|
31
|
-
"parameters": { "
|
|
31
|
+
"parameters": { "brightness1": 0.96 },
|
|
32
32
|
"comment": "Decay / feedback gain"
|
|
33
33
|
},
|
|
34
34
|
{ "name": "out1", "type": "nullTOP", "comment": "Output of the system" }
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
{
|
|
54
54
|
"name": "feedback_gain",
|
|
55
55
|
"node": "level1",
|
|
56
|
-
"param": "
|
|
56
|
+
"param": "brightness1",
|
|
57
57
|
"value": 0.96,
|
|
58
58
|
"min": 0,
|
|
59
59
|
"max": 1,
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
{
|
|
63
63
|
"name": "zoom",
|
|
64
64
|
"node": "transform1",
|
|
65
|
-
"param": "
|
|
65
|
+
"param": "sx",
|
|
66
66
|
"value": 1.03,
|
|
67
67
|
"min": 1,
|
|
68
68
|
"max": 1.2,
|
|
@@ -70,7 +70,108 @@
|
|
|
70
70
|
"min": 0,
|
|
71
71
|
"max": 2,
|
|
72
72
|
"label": "Terrain Height"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"name": "sky_r",
|
|
76
|
+
"node": "render1",
|
|
77
|
+
"param": "bgcolorr",
|
|
78
|
+
"value": 0.06,
|
|
79
|
+
"description": "Background sky red — deep indigo atmosphere instead of empty white void."
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"name": "sky_g",
|
|
83
|
+
"node": "render1",
|
|
84
|
+
"param": "bgcolorg",
|
|
85
|
+
"value": 0.07,
|
|
86
|
+
"description": "Background sky green."
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"name": "sky_b",
|
|
90
|
+
"node": "render1",
|
|
91
|
+
"param": "bgcolorb",
|
|
92
|
+
"value": 0.14,
|
|
93
|
+
"description": "Background sky blue."
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"name": "sky_a",
|
|
97
|
+
"node": "render1",
|
|
98
|
+
"param": "bgcolora",
|
|
99
|
+
"value": 1,
|
|
100
|
+
"description": "Background alpha = 1 so the sky is opaque (not transparent/white)."
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"name": "terrain_diffuse_r",
|
|
104
|
+
"node": "phong1",
|
|
105
|
+
"param": "diffr",
|
|
106
|
+
"value": 0.3,
|
|
107
|
+
"description": "Terrain diffuse red — mossy green so the landscape is coloured, not default grey."
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"name": "terrain_diffuse_g",
|
|
111
|
+
"node": "phong1",
|
|
112
|
+
"param": "diffg",
|
|
113
|
+
"value": 0.58,
|
|
114
|
+
"description": "Terrain diffuse green."
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"name": "terrain_diffuse_b",
|
|
118
|
+
"node": "phong1",
|
|
119
|
+
"param": "diffb",
|
|
120
|
+
"value": 0.42,
|
|
121
|
+
"description": "Terrain diffuse blue."
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"name": "terrain_ambient_r",
|
|
125
|
+
"node": "phong1",
|
|
126
|
+
"param": "ambr",
|
|
127
|
+
"value": 0.07,
|
|
128
|
+
"description": "Ambient red — cool indigo fill matching the sky so shadow sides read as atmospheric depth, not black."
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"name": "terrain_ambient_g",
|
|
132
|
+
"node": "phong1",
|
|
133
|
+
"param": "ambg",
|
|
134
|
+
"value": 0.11,
|
|
135
|
+
"description": "Ambient green."
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"name": "terrain_ambient_b",
|
|
139
|
+
"node": "phong1",
|
|
140
|
+
"param": "ambb",
|
|
141
|
+
"value": 0.2,
|
|
142
|
+
"description": "Ambient blue (indigo fill)."
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"name": "terrain_specular_r",
|
|
146
|
+
"node": "phong1",
|
|
147
|
+
"param": "specr",
|
|
148
|
+
"value": 0.7,
|
|
149
|
+
"description": "Specular red — warm sun glint catching the hill tops."
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
"name": "terrain_specular_g",
|
|
153
|
+
"node": "phong1",
|
|
154
|
+
"param": "specg",
|
|
155
|
+
"value": 0.62,
|
|
156
|
+
"description": "Specular green."
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"name": "terrain_specular_b",
|
|
160
|
+
"node": "phong1",
|
|
161
|
+
"param": "specb",
|
|
162
|
+
"value": 0.4,
|
|
163
|
+
"description": "Specular blue (warm)."
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
"name": "terrain_shininess",
|
|
167
|
+
"node": "phong1",
|
|
168
|
+
"param": "shininess",
|
|
169
|
+
"value": 30,
|
|
170
|
+
"min": 0,
|
|
171
|
+
"max": 100,
|
|
172
|
+
"label": "Highlight Tightness",
|
|
173
|
+
"description": "Phong shininess — tighter sun highlight on the ridges."
|
|
73
174
|
}
|
|
74
175
|
],
|
|
75
|
-
"preview_description": "A
|
|
176
|
+
"preview_description": "A green-teal rolling 3D terrain, sun-lit with warm specular highlights on the ridges and cool indigo ambient in the valleys, set against a deep indigo atmospheric sky."
|
|
76
177
|
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "performable_feedback_tunnel",
|
|
3
|
+
"name": "Performable Feedback Tunnel",
|
|
4
|
+
"description": "A feedback tunnel that ships with live controls: knobs for feedback decay, zoom, spin and blur are exposed on the container so you can perform it, animate the knobs with an LFO, or snapshot looks as presets.",
|
|
5
|
+
"tags": [
|
|
6
|
+
"feedback",
|
|
7
|
+
"generative",
|
|
8
|
+
"tunnel",
|
|
9
|
+
"abstract",
|
|
10
|
+
"noise",
|
|
11
|
+
"performable",
|
|
12
|
+
"controls",
|
|
13
|
+
"vj"
|
|
14
|
+
],
|
|
15
|
+
"difficulty": "intermediate",
|
|
16
|
+
"td_version_min": "2022",
|
|
17
|
+
"nodes": [
|
|
18
|
+
{
|
|
19
|
+
"name": "noise1",
|
|
20
|
+
"type": "noiseTOP",
|
|
21
|
+
"parameters": { "period": 3, "mono": 1 },
|
|
22
|
+
"comment": "Seed texture that feeds the loop (mono=1 keeps it grayscale; the param is 'mono', not 'monochrome')"
|
|
23
|
+
},
|
|
24
|
+
{ "name": "feedback1", "type": "feedbackTOP", "comment": "Holds the previous frame" },
|
|
25
|
+
{
|
|
26
|
+
"name": "comp1",
|
|
27
|
+
"type": "compositeTOP",
|
|
28
|
+
"parameters": { "operand": "maximum" },
|
|
29
|
+
"comment": "Combines seed + fed-back frame. 'maximum' stays bounded under feedback gain (the default multiply collapses to black)."
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"name": "transform1",
|
|
33
|
+
"type": "transformTOP",
|
|
34
|
+
"parameters": { "sx": 1.03, "sy": 1.03, "rotate": 1.5 },
|
|
35
|
+
"comment": "Zoom + rotate each frame to create the tunnel motion (sx/sy are the scale params, not scalex/scaley)"
|
|
36
|
+
},
|
|
37
|
+
{ "name": "blur1", "type": "blurTOP", "parameters": { "size": 2 } },
|
|
38
|
+
{
|
|
39
|
+
"name": "level1",
|
|
40
|
+
"type": "levelTOP",
|
|
41
|
+
"parameters": { "brightness1": 0.96 },
|
|
42
|
+
"comment": "Decay / feedback gain. levelTOP multiplies RGB via brightness1 (it has no 'gain' parameter)."
|
|
43
|
+
},
|
|
44
|
+
{ "name": "out1", "type": "nullTOP", "comment": "Output of the system" }
|
|
45
|
+
],
|
|
46
|
+
"connections": [
|
|
47
|
+
{ "from": "noise1", "to": "feedback1" },
|
|
48
|
+
{ "from": "noise1", "to": "comp1", "to_input": 0 },
|
|
49
|
+
{ "from": "feedback1", "to": "comp1", "to_input": 1 },
|
|
50
|
+
{ "from": "comp1", "to": "transform1" },
|
|
51
|
+
{ "from": "transform1", "to": "blur1" },
|
|
52
|
+
{ "from": "blur1", "to": "level1" },
|
|
53
|
+
{ "from": "level1", "to": "out1" }
|
|
54
|
+
],
|
|
55
|
+
"parameters": [
|
|
56
|
+
{
|
|
57
|
+
"name": "feedback_target",
|
|
58
|
+
"node": "feedback1",
|
|
59
|
+
"param": "top",
|
|
60
|
+
"value": "level1",
|
|
61
|
+
"description": "feedbackTOP samples the loop output (level1). Value resolves to the created node path."
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"controls": [
|
|
65
|
+
{
|
|
66
|
+
"name": "Feedback",
|
|
67
|
+
"type": "float",
|
|
68
|
+
"min": 0,
|
|
69
|
+
"max": 1,
|
|
70
|
+
"default": 0.96,
|
|
71
|
+
"bind_to": ["level1.brightness1"]
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"name": "Zoom",
|
|
75
|
+
"type": "float",
|
|
76
|
+
"min": 1,
|
|
77
|
+
"max": 1.2,
|
|
78
|
+
"default": 1.03,
|
|
79
|
+
"bind_to": ["transform1.sx", "transform1.sy"]
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"name": "Spin",
|
|
83
|
+
"type": "float",
|
|
84
|
+
"min": -5,
|
|
85
|
+
"max": 5,
|
|
86
|
+
"default": 1.5,
|
|
87
|
+
"bind_to": ["transform1.rotate"]
|
|
88
|
+
},
|
|
89
|
+
{ "name": "Blur", "type": "float", "min": 0, "max": 8, "default": 2, "bind_to": ["blur1.size"] }
|
|
90
|
+
],
|
|
91
|
+
"preview_description": "An endless rotating tunnel of evolving colored noise that folds into its own center, with live knobs for feedback, zoom, spin and blur."
|
|
92
|
+
}
|
|
@@ -10,7 +10,11 @@
|
|
|
10
10
|
{ "name": "edge", "type": "edgeTOP" },
|
|
11
11
|
{ "name": "rgbsplit", "type": "glslTOP", "comment": "Chromatic RGB offset" },
|
|
12
12
|
{ "name": "feedback1", "type": "feedbackTOP" },
|
|
13
|
-
{
|
|
13
|
+
{
|
|
14
|
+
"name": "comp1",
|
|
15
|
+
"type": "compositeTOP",
|
|
16
|
+
"parameters": { "outputresolution": "custom", "resolutionw": 1280, "resolutionh": 720 }
|
|
17
|
+
},
|
|
14
18
|
{ "name": "glitch", "type": "glslTOP", "comment": "Per-band horizontal displacement" },
|
|
15
19
|
{ "name": "out1", "type": "nullTOP" }
|
|
16
20
|
],
|
package/package.json
CHANGED
|
@@ -21,14 +21,14 @@
|
|
|
21
21
|
{
|
|
22
22
|
"name": "transform1",
|
|
23
23
|
"type": "transformTOP",
|
|
24
|
-
"parameters": { "
|
|
24
|
+
"parameters": { "sx": 1.03, "sy": 1.03, "rotate": 1.5 },
|
|
25
25
|
"comment": "Zoom + rotate each frame to create the tunnel motion"
|
|
26
26
|
},
|
|
27
27
|
{ "name": "blur1", "type": "blurTOP", "parameters": { "size": 2 } },
|
|
28
28
|
{
|
|
29
29
|
"name": "level1",
|
|
30
30
|
"type": "levelTOP",
|
|
31
|
-
"parameters": { "
|
|
31
|
+
"parameters": { "brightness1": 0.96 },
|
|
32
32
|
"comment": "Decay / feedback gain"
|
|
33
33
|
},
|
|
34
34
|
{ "name": "out1", "type": "nullTOP", "comment": "Output of the system" }
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
{
|
|
54
54
|
"name": "feedback_gain",
|
|
55
55
|
"node": "level1",
|
|
56
|
-
"param": "
|
|
56
|
+
"param": "brightness1",
|
|
57
57
|
"value": 0.96,
|
|
58
58
|
"min": 0,
|
|
59
59
|
"max": 1,
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
{
|
|
63
63
|
"name": "zoom",
|
|
64
64
|
"node": "transform1",
|
|
65
|
-
"param": "
|
|
65
|
+
"param": "sx",
|
|
66
66
|
"value": 1.03,
|
|
67
67
|
"min": 1,
|
|
68
68
|
"max": 1.2,
|
|
@@ -70,7 +70,108 @@
|
|
|
70
70
|
"min": 0,
|
|
71
71
|
"max": 2,
|
|
72
72
|
"label": "Terrain Height"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"name": "sky_r",
|
|
76
|
+
"node": "render1",
|
|
77
|
+
"param": "bgcolorr",
|
|
78
|
+
"value": 0.06,
|
|
79
|
+
"description": "Background sky red — deep indigo atmosphere instead of empty white void."
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"name": "sky_g",
|
|
83
|
+
"node": "render1",
|
|
84
|
+
"param": "bgcolorg",
|
|
85
|
+
"value": 0.07,
|
|
86
|
+
"description": "Background sky green."
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"name": "sky_b",
|
|
90
|
+
"node": "render1",
|
|
91
|
+
"param": "bgcolorb",
|
|
92
|
+
"value": 0.14,
|
|
93
|
+
"description": "Background sky blue."
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"name": "sky_a",
|
|
97
|
+
"node": "render1",
|
|
98
|
+
"param": "bgcolora",
|
|
99
|
+
"value": 1,
|
|
100
|
+
"description": "Background alpha = 1 so the sky is opaque (not transparent/white)."
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"name": "terrain_diffuse_r",
|
|
104
|
+
"node": "phong1",
|
|
105
|
+
"param": "diffr",
|
|
106
|
+
"value": 0.3,
|
|
107
|
+
"description": "Terrain diffuse red — mossy green so the landscape is coloured, not default grey."
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"name": "terrain_diffuse_g",
|
|
111
|
+
"node": "phong1",
|
|
112
|
+
"param": "diffg",
|
|
113
|
+
"value": 0.58,
|
|
114
|
+
"description": "Terrain diffuse green."
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"name": "terrain_diffuse_b",
|
|
118
|
+
"node": "phong1",
|
|
119
|
+
"param": "diffb",
|
|
120
|
+
"value": 0.42,
|
|
121
|
+
"description": "Terrain diffuse blue."
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"name": "terrain_ambient_r",
|
|
125
|
+
"node": "phong1",
|
|
126
|
+
"param": "ambr",
|
|
127
|
+
"value": 0.07,
|
|
128
|
+
"description": "Ambient red — cool indigo fill matching the sky so shadow sides read as atmospheric depth, not black."
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"name": "terrain_ambient_g",
|
|
132
|
+
"node": "phong1",
|
|
133
|
+
"param": "ambg",
|
|
134
|
+
"value": 0.11,
|
|
135
|
+
"description": "Ambient green."
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"name": "terrain_ambient_b",
|
|
139
|
+
"node": "phong1",
|
|
140
|
+
"param": "ambb",
|
|
141
|
+
"value": 0.2,
|
|
142
|
+
"description": "Ambient blue (indigo fill)."
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"name": "terrain_specular_r",
|
|
146
|
+
"node": "phong1",
|
|
147
|
+
"param": "specr",
|
|
148
|
+
"value": 0.7,
|
|
149
|
+
"description": "Specular red — warm sun glint catching the hill tops."
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
"name": "terrain_specular_g",
|
|
153
|
+
"node": "phong1",
|
|
154
|
+
"param": "specg",
|
|
155
|
+
"value": 0.62,
|
|
156
|
+
"description": "Specular green."
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"name": "terrain_specular_b",
|
|
160
|
+
"node": "phong1",
|
|
161
|
+
"param": "specb",
|
|
162
|
+
"value": 0.4,
|
|
163
|
+
"description": "Specular blue (warm)."
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
"name": "terrain_shininess",
|
|
167
|
+
"node": "phong1",
|
|
168
|
+
"param": "shininess",
|
|
169
|
+
"value": 30,
|
|
170
|
+
"min": 0,
|
|
171
|
+
"max": 100,
|
|
172
|
+
"label": "Highlight Tightness",
|
|
173
|
+
"description": "Phong shininess — tighter sun highlight on the ridges."
|
|
73
174
|
}
|
|
74
175
|
],
|
|
75
|
-
"preview_description": "A
|
|
176
|
+
"preview_description": "A green-teal rolling 3D terrain, sun-lit with warm specular highlights on the ridges and cool indigo ambient in the valleys, set against a deep indigo atmospheric sky."
|
|
76
177
|
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "performable_feedback_tunnel",
|
|
3
|
+
"name": "Performable Feedback Tunnel",
|
|
4
|
+
"description": "A feedback tunnel that ships with live controls: knobs for feedback decay, zoom, spin and blur are exposed on the container so you can perform it, animate the knobs with an LFO, or snapshot looks as presets.",
|
|
5
|
+
"tags": [
|
|
6
|
+
"feedback",
|
|
7
|
+
"generative",
|
|
8
|
+
"tunnel",
|
|
9
|
+
"abstract",
|
|
10
|
+
"noise",
|
|
11
|
+
"performable",
|
|
12
|
+
"controls",
|
|
13
|
+
"vj"
|
|
14
|
+
],
|
|
15
|
+
"difficulty": "intermediate",
|
|
16
|
+
"td_version_min": "2022",
|
|
17
|
+
"nodes": [
|
|
18
|
+
{
|
|
19
|
+
"name": "noise1",
|
|
20
|
+
"type": "noiseTOP",
|
|
21
|
+
"parameters": { "period": 3, "mono": 1 },
|
|
22
|
+
"comment": "Seed texture that feeds the loop (mono=1 keeps it grayscale; the param is 'mono', not 'monochrome')"
|
|
23
|
+
},
|
|
24
|
+
{ "name": "feedback1", "type": "feedbackTOP", "comment": "Holds the previous frame" },
|
|
25
|
+
{
|
|
26
|
+
"name": "comp1",
|
|
27
|
+
"type": "compositeTOP",
|
|
28
|
+
"parameters": { "operand": "maximum" },
|
|
29
|
+
"comment": "Combines seed + fed-back frame. 'maximum' stays bounded under feedback gain (the default multiply collapses to black)."
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"name": "transform1",
|
|
33
|
+
"type": "transformTOP",
|
|
34
|
+
"parameters": { "sx": 1.03, "sy": 1.03, "rotate": 1.5 },
|
|
35
|
+
"comment": "Zoom + rotate each frame to create the tunnel motion (sx/sy are the scale params, not scalex/scaley)"
|
|
36
|
+
},
|
|
37
|
+
{ "name": "blur1", "type": "blurTOP", "parameters": { "size": 2 } },
|
|
38
|
+
{
|
|
39
|
+
"name": "level1",
|
|
40
|
+
"type": "levelTOP",
|
|
41
|
+
"parameters": { "brightness1": 0.96 },
|
|
42
|
+
"comment": "Decay / feedback gain. levelTOP multiplies RGB via brightness1 (it has no 'gain' parameter)."
|
|
43
|
+
},
|
|
44
|
+
{ "name": "out1", "type": "nullTOP", "comment": "Output of the system" }
|
|
45
|
+
],
|
|
46
|
+
"connections": [
|
|
47
|
+
{ "from": "noise1", "to": "feedback1" },
|
|
48
|
+
{ "from": "noise1", "to": "comp1", "to_input": 0 },
|
|
49
|
+
{ "from": "feedback1", "to": "comp1", "to_input": 1 },
|
|
50
|
+
{ "from": "comp1", "to": "transform1" },
|
|
51
|
+
{ "from": "transform1", "to": "blur1" },
|
|
52
|
+
{ "from": "blur1", "to": "level1" },
|
|
53
|
+
{ "from": "level1", "to": "out1" }
|
|
54
|
+
],
|
|
55
|
+
"parameters": [
|
|
56
|
+
{
|
|
57
|
+
"name": "feedback_target",
|
|
58
|
+
"node": "feedback1",
|
|
59
|
+
"param": "top",
|
|
60
|
+
"value": "level1",
|
|
61
|
+
"description": "feedbackTOP samples the loop output (level1). Value resolves to the created node path."
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"controls": [
|
|
65
|
+
{
|
|
66
|
+
"name": "Feedback",
|
|
67
|
+
"type": "float",
|
|
68
|
+
"min": 0,
|
|
69
|
+
"max": 1,
|
|
70
|
+
"default": 0.96,
|
|
71
|
+
"bind_to": ["level1.brightness1"]
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"name": "Zoom",
|
|
75
|
+
"type": "float",
|
|
76
|
+
"min": 1,
|
|
77
|
+
"max": 1.2,
|
|
78
|
+
"default": 1.03,
|
|
79
|
+
"bind_to": ["transform1.sx", "transform1.sy"]
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"name": "Spin",
|
|
83
|
+
"type": "float",
|
|
84
|
+
"min": -5,
|
|
85
|
+
"max": 5,
|
|
86
|
+
"default": 1.5,
|
|
87
|
+
"bind_to": ["transform1.rotate"]
|
|
88
|
+
},
|
|
89
|
+
{ "name": "Blur", "type": "float", "min": 0, "max": 8, "default": 2, "bind_to": ["blur1.size"] }
|
|
90
|
+
],
|
|
91
|
+
"preview_description": "An endless rotating tunnel of evolving colored noise that folds into its own center, with live knobs for feedback, zoom, spin and blur."
|
|
92
|
+
}
|
|
@@ -10,7 +10,11 @@
|
|
|
10
10
|
{ "name": "edge", "type": "edgeTOP" },
|
|
11
11
|
{ "name": "rgbsplit", "type": "glslTOP", "comment": "Chromatic RGB offset" },
|
|
12
12
|
{ "name": "feedback1", "type": "feedbackTOP" },
|
|
13
|
-
{
|
|
13
|
+
{
|
|
14
|
+
"name": "comp1",
|
|
15
|
+
"type": "compositeTOP",
|
|
16
|
+
"parameters": { "outputresolution": "custom", "resolutionw": 1280, "resolutionh": 720 }
|
|
17
|
+
},
|
|
14
18
|
{ "name": "glitch", "type": "glslTOP", "comment": "Per-band horizontal displacement" },
|
|
15
19
|
{ "name": "out1", "type": "nullTOP" }
|
|
16
20
|
],
|
|
@@ -74,6 +74,31 @@ def _check_auth(request):
|
|
|
74
74
|
raise PermissionError("Unauthorized: missing or invalid bearer token.")
|
|
75
75
|
|
|
76
76
|
|
|
77
|
+
_LOOPBACK_HOSTS = ("127.0.0.1", "localhost", "::1")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _check_origin(request):
|
|
81
|
+
"""Reject browser-originated cross-origin requests (CSRF / DNS-rebinding).
|
|
82
|
+
|
|
83
|
+
The Node MCP server — the only legitimate caller — never sends an `Origin`
|
|
84
|
+
header. Browsers always attach one on cross-site requests, so a request
|
|
85
|
+
bearing a non-loopback `Origin` can only be a web page trying to drive the
|
|
86
|
+
bridge (e.g. a malicious site POSTing to http://127.0.0.1:9980/api/exec).
|
|
87
|
+
Under the default zero-auth + exec-on config that would be drive-by remote
|
|
88
|
+
code execution, so refuse it. Loopback origins stay allowed (a locally
|
|
89
|
+
served tool page still works) and same-origin / no-Origin callers are
|
|
90
|
+
unaffected. Holds even against a direct caller and independent of the
|
|
91
|
+
optional bearer token, mirroring the Node HTTP transport's DNS-rebinding
|
|
92
|
+
guard on its own port.
|
|
93
|
+
"""
|
|
94
|
+
origin = _find_header(request, "origin")
|
|
95
|
+
if not origin:
|
|
96
|
+
return
|
|
97
|
+
host = urlparse(origin).hostname
|
|
98
|
+
if host not in _LOOPBACK_HOSTS:
|
|
99
|
+
raise PermissionError("Forbidden: cross-origin request rejected (origin %r)." % origin)
|
|
100
|
+
|
|
101
|
+
|
|
77
102
|
def _qs(query, key, default=None):
|
|
78
103
|
values = query.get(key)
|
|
79
104
|
return values[0] if values else default
|
|
@@ -160,7 +185,9 @@ def _route(method, path, query, body):
|
|
|
160
185
|
if kind == "topology":
|
|
161
186
|
return analysis_service.topology(node_path, recursive=_qs(query, "recursive") == "true")
|
|
162
187
|
if kind == "performance":
|
|
163
|
-
return analysis_service.performance(
|
|
188
|
+
return analysis_service.performance(
|
|
189
|
+
node_path, recursive=_qs(query, "recursive") == "true"
|
|
190
|
+
)
|
|
164
191
|
|
|
165
192
|
raise ValueError("Unsupported %s %s" % (method, path))
|
|
166
193
|
|
|
@@ -226,6 +253,7 @@ def _emit_event(webserver, method, path, data):
|
|
|
226
253
|
|
|
227
254
|
def handle(request, response, webserver=None):
|
|
228
255
|
try:
|
|
256
|
+
_check_origin(request)
|
|
229
257
|
_check_auth(request)
|
|
230
258
|
method = (request.get("method") or "GET").upper()
|
|
231
259
|
parsed = urlparse(request.get("uri", "/"))
|
|
@@ -44,13 +44,18 @@ def topology(path, recursive=False):
|
|
|
44
44
|
return {"nodes": nodes, "connections": connections}
|
|
45
45
|
|
|
46
46
|
|
|
47
|
-
def performance(path):
|
|
47
|
+
def performance(path, recursive=False):
|
|
48
48
|
root = op(path) # noqa: F821
|
|
49
49
|
if root is None:
|
|
50
50
|
return {"nodes": [], "total_cook_time_ms": 0.0}
|
|
51
51
|
nodes = []
|
|
52
52
|
total = 0.0
|
|
53
|
-
|
|
53
|
+
if not hasattr(root, "findChildren"):
|
|
54
|
+
children = []
|
|
55
|
+
elif recursive:
|
|
56
|
+
children = root.findChildren() # all descendants (cook time of nested nodes too)
|
|
57
|
+
else:
|
|
58
|
+
children = root.findChildren(depth=1) # direct children only
|
|
54
59
|
for child in children:
|
|
55
60
|
cook_time = float(getattr(child, "cookTime", 0.0) or 0.0)
|
|
56
61
|
total += cook_time
|
|
@@ -71,9 +71,15 @@ def create_node(parent_path, type_name, name=None, parameters=None):
|
|
|
71
71
|
raise LookupError("Parent not found: %s" % parent_path)
|
|
72
72
|
cls = _resolve_type(type_name)
|
|
73
73
|
node = parent.create(cls, name) if name else parent.create(cls)
|
|
74
|
+
ref = node_ref(node)
|
|
74
75
|
if parameters:
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
# The node is created regardless; surface any params that did not apply
|
|
77
|
+
# (unknown name or bad value) as a non-fatal warning rather than dropping
|
|
78
|
+
# them silently. The caller (create_td_node) relays these to the user.
|
|
79
|
+
_applied, failed = apply_parameters(node, parameters)
|
|
80
|
+
if failed:
|
|
81
|
+
ref["parameter_warnings"] = sorted(failed)
|
|
82
|
+
return ref
|
|
77
83
|
|
|
78
84
|
|
|
79
85
|
def delete_node(path):
|
|
@@ -120,7 +126,23 @@ def update_parameters(path, parameters):
|
|
|
120
126
|
node = op(path) # noqa: F821
|
|
121
127
|
if node is None:
|
|
122
128
|
raise LookupError("Node not found: %s" % path)
|
|
123
|
-
|
|
129
|
+
params = parameters or {}
|
|
130
|
+
# Reject unknown parameter names up front (atomic: apply nothing) so a typo
|
|
131
|
+
# like `gain` on a levelTOP fails loudly instead of being silently dropped.
|
|
132
|
+
unknown = [k for k in params if getattr(node.par, k, None) is None]
|
|
133
|
+
if unknown:
|
|
134
|
+
raise ValueError(
|
|
135
|
+
"Unknown parameter(s) on %s (%s): %s. "
|
|
136
|
+
"Use get_td_node_parameters to see the valid parameter names."
|
|
137
|
+
% (path, op_type(node), ", ".join(sorted(unknown)))
|
|
138
|
+
)
|
|
139
|
+
applied, failed = apply_parameters(node, params)
|
|
140
|
+
if failed:
|
|
141
|
+
raise ValueError(
|
|
142
|
+
"Could not set parameter(s) on %s (%s): %s "
|
|
143
|
+
"(wrong value type or out of range?). Applied: %s."
|
|
144
|
+
% (path, op_type(node), ", ".join(sorted(failed)), ", ".join(sorted(applied)) or "none")
|
|
145
|
+
)
|
|
124
146
|
return node_detail(node)
|
|
125
147
|
|
|
126
148
|
|