@dpantani/tdmcp 0.1.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/LICENSE +21 -0
- package/README.md +413 -0
- package/dist/cli/agent.d.ts +466 -0
- package/dist/cli/agent.js +1902 -0
- package/dist/cli/agent.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4375 -0
- package/dist/index.js.map +1 -0
- package/dist/knowledge/data/glsl.json +1 -0
- package/dist/knowledge/data/meta.json +1 -0
- package/dist/knowledge/data/operators/ableton_link_chop.json +1322 -0
- package/dist/knowledge/data/operators/accumulate_pop.json +912 -0
- package/dist/knowledge/data/operators/actor_comp.json +7477 -0
- package/dist/knowledge/data/operators/add_sop.json +946 -0
- package/dist/knowledge/data/operators/add_top.json +1653 -0
- package/dist/knowledge/data/operators/alembic_sop.json +521 -0
- package/dist/knowledge/data/operators/align_sop.json +949 -0
- package/dist/knowledge/data/operators/ambient_light_comp.json +1698 -0
- package/dist/knowledge/data/operators/analyze_chop.json +710 -0
- package/dist/knowledge/data/operators/analyze_pop.json +698 -0
- package/dist/knowledge/data/operators/analyze_top.json +1276 -0
- package/dist/knowledge/data/operators/angle_chop.json +711 -0
- package/dist/knowledge/data/operators/animation_comp.json +2448 -0
- package/dist/knowledge/data/operators/annotate_comp.json +2913 -0
- package/dist/knowledge/data/operators/anti_alias_top.json +1278 -0
- package/dist/knowledge/data/operators/arm_sop.json +1747 -0
- package/dist/knowledge/data/operators/art-net_dat.json +472 -0
- package/dist/knowledge/data/operators/attribute_chop.json +617 -0
- package/dist/knowledge/data/operators/attribute_combine_pop.json +596 -0
- package/dist/knowledge/data/operators/attribute_convert_pop.json +462 -0
- package/dist/knowledge/data/operators/attribute_create_sop.json +191 -0
- package/dist/knowledge/data/operators/attribute_pop.json +1078 -0
- package/dist/knowledge/data/operators/attribute_sop.json +429 -0
- package/dist/knowledge/data/operators/audio_band_eq_chop.json +1228 -0
- package/dist/knowledge/data/operators/audio_binaural_chop.json +711 -0
- package/dist/knowledge/data/operators/audio_device_in_chop.json +1793 -0
- package/dist/knowledge/data/operators/audio_device_out_chop.json +1840 -0
- package/dist/knowledge/data/operators/audio_devices_dat.json +616 -0
- package/dist/knowledge/data/operators/audio_dynamics_chop.json +1132 -0
- package/dist/knowledge/data/operators/audio_file_in_chop.json +1605 -0
- package/dist/knowledge/data/operators/audio_file_out_chop.json +945 -0
- package/dist/knowledge/data/operators/audio_filter_chop.json +850 -0
- package/dist/knowledge/data/operators/audio_movie_chop.json +851 -0
- package/dist/knowledge/data/operators/audio_ndi_chop.json +526 -0
- package/dist/knowledge/data/operators/audio_oscillator_chop.json +1087 -0
- package/dist/knowledge/data/operators/audio_para_eq_chop.json +1276 -0
- package/dist/knowledge/data/operators/audio_play_chop.json +1744 -0
- package/dist/knowledge/data/operators/audio_render_chop.json +754 -0
- package/dist/knowledge/data/operators/audio_spectrum_chop.json +850 -0
- package/dist/knowledge/data/operators/audio_stream_in_chop.json +1039 -0
- package/dist/knowledge/data/operators/audio_stream_out_chop.json +806 -0
- package/dist/knowledge/data/operators/audio_vst_chop.json +1419 -0
- package/dist/knowledge/data/operators/audio_web_render_chop.json +523 -0
- package/dist/knowledge/data/operators/base_comp.json +1180 -0
- package/dist/knowledge/data/operators/basis_sop.json +805 -0
- package/dist/knowledge/data/operators/beat_chop.json +1745 -0
- package/dist/knowledge/data/operators/bind_chop.json +613 -0
- package/dist/knowledge/data/operators/blacktrax_chop.json +1180 -0
- package/dist/knowledge/data/operators/blend_chop.json +616 -0
- package/dist/knowledge/data/operators/blend_comp.json +3626 -0
- package/dist/knowledge/data/operators/blend_pop.json +618 -0
- package/dist/knowledge/data/operators/blend_sop.json +477 -0
- package/dist/knowledge/data/operators/blob_track_chop.json +1275 -0
- package/dist/knowledge/data/operators/blob_track_top.json +1931 -0
- package/dist/knowledge/data/operators/bloom_top.json +1370 -0
- package/dist/knowledge/data/operators/blur_top.json +1428 -0
- package/dist/knowledge/data/operators/body_track_chop.json +1233 -0
- package/dist/knowledge/data/operators/bone_comp.json +3672 -0
- package/dist/knowledge/data/operators/bone_group_sop.json +148 -0
- package/dist/knowledge/data/operators/boolean_sop.json +336 -0
- package/dist/knowledge/data/operators/box_pop.json +827 -0
- package/dist/knowledge/data/operators/box_sop.json +994 -0
- package/dist/knowledge/data/operators/bridge_sop.json +806 -0
- package/dist/knowledge/data/operators/bullet_solver_chop.json +898 -0
- package/dist/knowledge/data/operators/bullet_solver_comp.json +3720 -0
- package/dist/knowledge/data/operators/button_comp.json +6636 -0
- package/dist/knowledge/data/operators/cache_blend_pop.json +292 -0
- package/dist/knowledge/data/operators/cache_pop.json +412 -0
- package/dist/knowledge/data/operators/cache_select_pop.json +280 -0
- package/dist/knowledge/data/operators/cache_select_top.json +946 -0
- package/dist/knowledge/data/operators/cache_sop.json +478 -0
- package/dist/knowledge/data/operators/cache_top.json +1559 -0
- package/dist/knowledge/data/operators/camera_blend_comp.json +4847 -0
- package/dist/knowledge/data/operators/camera_comp.json +215 -0
- package/dist/knowledge/data/operators/cap_sop.json +527 -0
- package/dist/knowledge/data/operators/capture_region_sop.json +568 -0
- package/dist/knowledge/data/operators/capture_sop.json +619 -0
- package/dist/knowledge/data/operators/carve_sop.json +902 -0
- package/dist/knowledge/data/operators/channel_mix_top.json +1088 -0
- package/dist/knowledge/data/operators/chop_execute_dat.json +1277 -0
- package/dist/knowledge/data/operators/chop_to_dat.json +571 -0
- package/dist/knowledge/data/operators/chop_to_pop.json +1236 -0
- package/dist/knowledge/data/operators/chop_to_sop.json +714 -0
- package/dist/knowledge/data/operators/chop_to_top.json +1231 -0
- package/dist/knowledge/data/operators/chroma_key_top.json +1653 -0
- package/dist/knowledge/data/operators/circle_pop.json +821 -0
- package/dist/knowledge/data/operators/circle_sop.json +991 -0
- package/dist/knowledge/data/operators/circle_top.json +2307 -0
- package/dist/knowledge/data/operators/clay_sop.json +1231 -0
- package/dist/knowledge/data/operators/clip_blender_chop.json +1931 -0
- package/dist/knowledge/data/operators/clip_chop.json +1366 -0
- package/dist/knowledge/data/operators/clip_dat.json +849 -0
- package/dist/knowledge/data/operators/clip_sop.json +475 -0
- package/dist/knowledge/data/operators/clock_chop.json +1792 -0
- package/dist/knowledge/data/operators/composite_chop.json +1414 -0
- package/dist/knowledge/data/operators/composite_top.json +2131 -0
- package/dist/knowledge/data/operators/connectivity_pop.json +11 -0
- package/dist/knowledge/data/operators/constant_chop.json +1238 -0
- package/dist/knowledge/data/operators/constant_mat.json +2727 -0
- package/dist/knowledge/data/operators/constant_top.json +1323 -0
- package/dist/knowledge/data/operators/constraint_comp.json +2076 -0
- package/dist/knowledge/data/operators/container_comp.json +6299 -0
- package/dist/knowledge/data/operators/convert_dat.json +569 -0
- package/dist/knowledge/data/operators/convert_pop.json +322 -0
- package/dist/knowledge/data/operators/convert_sop.json +807 -0
- package/dist/knowledge/data/operators/convolve_top.json +1041 -0
- package/dist/knowledge/data/operators/copy_chop.json +993 -0
- package/dist/knowledge/data/operators/copy_pop.json +1799 -0
- package/dist/knowledge/data/operators/copy_sop.json +1887 -0
- package/dist/knowledge/data/operators/corner_pin_top.json +1512 -0
- package/dist/knowledge/data/operators/count_chop.json +1369 -0
- package/dist/knowledge/data/operators/cplusplus_chop.json +569 -0
- package/dist/knowledge/data/operators/cplusplus_dat.json +477 -0
- package/dist/knowledge/data/operators/cplusplus_pop.json +308 -0
- package/dist/knowledge/data/operators/cplusplus_sop.json +195 -0
- package/dist/knowledge/data/operators/cplusplus_top.json +1276 -0
- package/dist/knowledge/data/operators/creep_sop.json +385 -0
- package/dist/knowledge/data/operators/crop_top.json +1323 -0
- package/dist/knowledge/data/operators/cross_chop.json +478 -0
- package/dist/knowledge/data/operators/cross_top.json +1559 -0
- package/dist/knowledge/data/operators/cube_map_top.json +946 -0
- package/dist/knowledge/data/operators/curve_pop.json +1525 -0
- package/dist/knowledge/data/operators/curveclay_sop.json +618 -0
- package/dist/knowledge/data/operators/curvesect_sop.json +665 -0
- package/dist/knowledge/data/operators/cycle_chop.json +1040 -0
- package/dist/knowledge/data/operators/dat_execute_dat.json +1230 -0
- package/dist/knowledge/data/operators/dat_to_chop.json +1510 -0
- package/dist/knowledge/data/operators/dat_to_pop.json +756 -0
- package/dist/knowledge/data/operators/dat_to_sop.json +902 -0
- package/dist/knowledge/data/operators/deform_sop.json +291 -0
- package/dist/knowledge/data/operators/delay_chop.json +615 -0
- package/dist/knowledge/data/operators/delete_chop.json +1556 -0
- package/dist/knowledge/data/operators/delete_pop.json +1422 -0
- package/dist/knowledge/data/operators/delete_sop.json +1230 -0
- package/dist/knowledge/data/operators/depth_mat.json +1933 -0
- package/dist/knowledge/data/operators/depth_top.json +1322 -0
- package/dist/knowledge/data/operators/difference_top.json +1647 -0
- package/dist/knowledge/data/operators/dimension_pop.json +280 -0
- package/dist/knowledge/data/operators/direct_display_out_top.json +997 -0
- package/dist/knowledge/data/operators/directx_in_top.json +901 -0
- package/dist/knowledge/data/operators/directx_out_top.json +948 -0
- package/dist/knowledge/data/operators/displace_top.json +1322 -0
- package/dist/knowledge/data/operators/divide_sop.json +713 -0
- package/dist/knowledge/data/operators/dmx_fixture_pop.json +772 -0
- package/dist/knowledge/data/operators/dmx_in_chop.json +1420 -0
- package/dist/knowledge/data/operators/dmx_map_dat.json +11 -0
- package/dist/knowledge/data/operators/dmx_out_chop.json +1843 -0
- package/dist/knowledge/data/operators/dmx_out_pop.json +668 -0
- package/dist/knowledge/data/operators/edge_top.json +1370 -0
- package/dist/knowledge/data/operators/emboss_top.json +1275 -0
- package/dist/knowledge/data/operators/engine_comp.json +2685 -0
- package/dist/knowledge/data/operators/envelope_chop.json +946 -0
- package/dist/knowledge/data/operators/environment_light_comp.json +2121 -0
- package/dist/knowledge/data/operators/error_dat.json +949 -0
- package/dist/knowledge/data/operators/etherdream_dat.json +472 -0
- package/dist/knowledge/data/operators/evaluate_dat.json +1650 -0
- package/dist/knowledge/data/operators/event_chop.json +1653 -0
- package/dist/knowledge/data/operators/examine_dat.json +1182 -0
- package/dist/knowledge/data/operators/execute_dat.json +1184 -0
- package/dist/knowledge/data/operators/experimental +0 -0
- package/dist/knowledge/data/operators/expression_chop.json +617 -0
- package/dist/knowledge/data/operators/extend_chop.json +617 -0
- package/dist/knowledge/data/operators/extrude_pop.json +316 -0
- package/dist/knowledge/data/operators/extrude_sop.json +1081 -0
- package/dist/knowledge/data/operators/face_track_chop.json +998 -0
- package/dist/knowledge/data/operators/face_track_sop.json +244 -0
- package/dist/knowledge/data/operators/facet_pop.json +480 -0
- package/dist/knowledge/data/operators/facet_sop.json +619 -0
- package/dist/knowledge/data/operators/fan_chop.json +804 -0
- package/dist/knowledge/data/operators/fbx_comp.json +4942 -0
- package/dist/knowledge/data/operators/feedback_chop.json +664 -0
- package/dist/knowledge/data/operators/feedback_pop.json +368 -0
- package/dist/knowledge/data/operators/feedback_top.json +191 -0
- package/dist/knowledge/data/operators/field_pop.json +2304 -0
- package/dist/knowledge/data/operators/fifo_dat.json +711 -0
- package/dist/knowledge/data/operators/file_in_chop.json +1181 -0
- package/dist/knowledge/data/operators/file_in_dat.json +527 -0
- package/dist/knowledge/data/operators/file_in_pop.json +298 -0
- package/dist/knowledge/data/operators/file_in_sop.json +289 -0
- package/dist/knowledge/data/operators/file_out_chop.json +618 -0
- package/dist/knowledge/data/operators/file_out_dat.json +524 -0
- package/dist/knowledge/data/operators/fillet_sop.json +949 -0
- package/dist/knowledge/data/operators/filter_chop.json +1134 -0
- package/dist/knowledge/data/operators/fit_sop.json +1087 -0
- package/dist/knowledge/data/operators/fit_top.json +1559 -0
- package/dist/knowledge/data/operators/flip_top.json +1040 -0
- package/dist/knowledge/data/operators/folder_dat.json +1888 -0
- package/dist/knowledge/data/operators/force_comp.json +1701 -0
- package/dist/knowledge/data/operators/force_pop.json +878 -0
- package/dist/knowledge/data/operators/force_sop.json +429 -0
- package/dist/knowledge/data/operators/fractal_sop.json +479 -0
- package/dist/knowledge/data/operators/freed_in_chop.json +808 -0
- package/dist/knowledge/data/operators/freed_out_chop.json +714 -0
- package/dist/knowledge/data/operators/function_chop.json +1086 -0
- package/dist/knowledge/data/operators/function_top.json +1558 -0
- package/dist/knowledge/data/operators/geo_text_comp.json +7338 -0
- package/dist/knowledge/data/operators/geometry_comp.json +199 -0
- package/dist/knowledge/data/operators/gesture_chop.json +1133 -0
- package/dist/knowledge/data/operators/glsl_advanced_pop.json +3539 -0
- package/dist/knowledge/data/operators/glsl_comp.json +6962 -0
- package/dist/knowledge/data/operators/glsl_copy_pop.json +2128 -0
- package/dist/knowledge/data/operators/glsl_mat.json +3720 -0
- package/dist/knowledge/data/operators/glsl_multi_top.json +2922 -0
- package/dist/knowledge/data/operators/glsl_pop.json +2317 -0
- package/dist/knowledge/data/operators/glsl_select_pop.json +280 -0
- package/dist/knowledge/data/operators/glsl_top.json +2922 -0
- package/dist/knowledge/data/operators/grid_pop.json +1029 -0
- package/dist/knowledge/data/operators/grid_sop.json +1041 -0
- package/dist/knowledge/data/operators/group_pop.json +1423 -0
- package/dist/knowledge/data/operators/group_sop.json +2075 -0
- package/dist/knowledge/data/operators/handle_chop.json +712 -0
- package/dist/knowledge/data/operators/handle_comp.json +2171 -0
- package/dist/knowledge/data/operators/histogram_pop.json +533 -0
- package/dist/knowledge/data/operators/hog_chop.json +620 -0
- package/dist/knowledge/data/operators/hokuyo_chop.json +941 -0
- package/dist/knowledge/data/operators/hold_chop.json +664 -0
- package/dist/knowledge/data/operators/hole_sop.json +291 -0
- package/dist/knowledge/data/operators/hsv_adjust_top.json +1370 -0
- package/dist/knowledge/data/operators/hsv_to_rgb_top.json +847 -0
- package/dist/knowledge/data/operators/import_select_chop.json +1838 -0
- package/dist/knowledge/data/operators/import_select_pop.json +808 -0
- package/dist/knowledge/data/operators/import_select_sop.json +1463 -0
- package/dist/knowledge/data/operators/import_select_top.json +993 -0
- package/dist/knowledge/data/operators/impulse_force_comp.json +1415 -0
- package/dist/knowledge/data/operators/in_chop.json +572 -0
- package/dist/knowledge/data/operators/in_dat.json +386 -0
- package/dist/knowledge/data/operators/in_mat.json +1559 -0
- package/dist/knowledge/data/operators/in_pop.json +280 -0
- package/dist/knowledge/data/operators/in_sop.json +102 -0
- package/dist/knowledge/data/operators/in_top.json +903 -0
- package/dist/knowledge/data/operators/index.json +1 -0
- package/dist/knowledge/data/operators/indices_dat.json +526 -0
- package/dist/knowledge/data/operators/info_chop.json +757 -0
- package/dist/knowledge/data/operators/info_dat.json +430 -0
- package/dist/knowledge/data/operators/insert_dat.json +896 -0
- package/dist/knowledge/data/operators/inside_top.json +1652 -0
- package/dist/knowledge/data/operators/interpolate_chop.json +711 -0
- package/dist/knowledge/data/operators/inverse_curve_chop.json +898 -0
- package/dist/knowledge/data/operators/inverse_curve_sop.json +97 -0
- package/dist/knowledge/data/operators/inverse_kin_chop.json +850 -0
- package/dist/knowledge/data/operators/iso_surface_sop.json +337 -0
- package/dist/knowledge/data/operators/join_chop.json +1368 -0
- package/dist/knowledge/data/operators/join_sop.json +667 -0
- package/dist/knowledge/data/operators/joint_sop.json +573 -0
- package/dist/knowledge/data/operators/joystick_chop.json +1558 -0
- package/dist/knowledge/data/operators/json_dat.json +619 -0
- package/dist/knowledge/data/operators/keyboard_in_chop.json +1041 -0
- package/dist/knowledge/data/operators/keyboard_in_dat.json +950 -0
- package/dist/knowledge/data/operators/keyframe_chop.json +711 -0
- package/dist/knowledge/data/operators/kinect_azure_chop.json +1092 -0
- package/dist/knowledge/data/operators/kinect_azure_select_top.json +1187 -0
- package/dist/knowledge/data/operators/kinect_azure_top.json +2737 -0
- package/dist/knowledge/data/operators/kinect_chop.json +1700 -0
- package/dist/knowledge/data/operators/kinect_sop.json +379 -0
- package/dist/knowledge/data/operators/kinect_top.json +1700 -0
- package/dist/knowledge/data/operators/lag_chop.json +1040 -0
- package/dist/knowledge/data/operators/laser_chop.json +1746 -0
- package/dist/knowledge/data/operators/laser_device_chop.json +1228 -0
- package/dist/knowledge/data/operators/lattice_sop.json +432 -0
- package/dist/knowledge/data/operators/layout_top.json +1936 -0
- package/dist/knowledge/data/operators/leap_motion_chop.json +1699 -0
- package/dist/knowledge/data/operators/leap_motion_top.json +1323 -0
- package/dist/knowledge/data/operators/lens_distort_top.json +2262 -0
- package/dist/knowledge/data/operators/leuze_rod4_chop.json +1371 -0
- package/dist/knowledge/data/operators/level_top.json +2273 -0
- package/dist/knowledge/data/operators/lfo_chop.json +1088 -0
- package/dist/knowledge/data/operators/light_comp.json +5410 -0
- package/dist/knowledge/data/operators/limit_chop.json +1133 -0
- package/dist/knowledge/data/operators/limit_pop.json +1243 -0
- package/dist/knowledge/data/operators/limit_sop.json +2075 -0
- package/dist/knowledge/data/operators/limit_top.json +1557 -0
- package/dist/knowledge/data/operators/line_break_pop.json +447 -0
- package/dist/knowledge/data/operators/line_divide_pop.json +688 -0
- package/dist/knowledge/data/operators/line_mat.json +5551 -0
- package/dist/knowledge/data/operators/line_metrics_pop.json +548 -0
- package/dist/knowledge/data/operators/line_pop.json +1343 -0
- package/dist/knowledge/data/operators/line_smooth_pop.json +1028 -0
- package/dist/knowledge/data/operators/line_sop.json +330 -0
- package/dist/knowledge/data/operators/line_thick_sop.json +478 -0
- package/dist/knowledge/data/operators/list_comp.json +6729 -0
- package/dist/knowledge/data/operators/lod_sop.json +431 -0
- package/dist/knowledge/data/operators/logic_chop.json +946 -0
- package/dist/knowledge/data/operators/lookup_attribute_pop.json +684 -0
- package/dist/knowledge/data/operators/lookup_channel_pop.json +1140 -0
- package/dist/knowledge/data/operators/lookup_chop.json +992 -0
- package/dist/knowledge/data/operators/lookup_dat.json +664 -0
- package/dist/knowledge/data/operators/lookup_texture_pop.json +1494 -0
- package/dist/knowledge/data/operators/lookup_top.json +1510 -0
- package/dist/knowledge/data/operators/lsystem_sop.json +1699 -0
- package/dist/knowledge/data/operators/ltc_in_chop.json +760 -0
- package/dist/knowledge/data/operators/ltc_out_chop.json +1512 -0
- package/dist/knowledge/data/operators/luma_blur_top.json +1316 -0
- package/dist/knowledge/data/operators/luma_level_top.json +1746 -0
- package/dist/knowledge/data/operators/magnet_sop.json +759 -0
- package/dist/knowledge/data/operators/material_sop.json +98 -0
- package/dist/knowledge/data/operators/math_chop.json +1193 -0
- package/dist/knowledge/data/operators/math_combine_pop.json +4283 -0
- package/dist/knowledge/data/operators/math_mix_pop.json +2641 -0
- package/dist/knowledge/data/operators/math_pop.json +2163 -0
- package/dist/knowledge/data/operators/math_top.json +1885 -0
- package/dist/knowledge/data/operators/matte_top.json +994 -0
- package/dist/knowledge/data/operators/media_file_info_dat.json +571 -0
- package/dist/knowledge/data/operators/merge_chop.json +627 -0
- package/dist/knowledge/data/operators/merge_dat.json +620 -0
- package/dist/knowledge/data/operators/merge_pop.json +764 -0
- package/dist/knowledge/data/operators/merge_sop.json +100 -0
- package/dist/knowledge/data/operators/metaball_sop.json +476 -0
- package/dist/knowledge/data/operators/midi_event_dat.json +1135 -0
- package/dist/knowledge/data/operators/midi_in_chop.json +3297 -0
- package/dist/knowledge/data/operators/midi_in_dat.json +1183 -0
- package/dist/knowledge/data/operators/midi_in_map_chop.json +946 -0
- package/dist/knowledge/data/operators/midi_out_chop.json +1793 -0
- package/dist/knowledge/data/operators/mirror_top.json +1271 -0
- package/dist/knowledge/data/operators/model_sop.json +52 -0
- package/dist/knowledge/data/operators/monitors_dat.json +617 -0
- package/dist/knowledge/data/operators/monochrome_top.json +1041 -0
- package/dist/knowledge/data/operators/mosys_chop.json +804 -0
- package/dist/knowledge/data/operators/mosys_top.json +896 -0
- package/dist/knowledge/data/operators/mouse_in_chop.json +1275 -0
- package/dist/knowledge/data/operators/mouse_out_chop.json +711 -0
- package/dist/knowledge/data/operators/movie_file_in_top.json +3546 -0
- package/dist/knowledge/data/operators/movie_file_out_top.json +3390 -0
- package/dist/knowledge/data/operators/mpcdi_dat.json +711 -0
- package/dist/knowledge/data/operators/mpcdi_top.json +1369 -0
- package/dist/knowledge/data/operators/mqtt_client_dat.json +1229 -0
- package/dist/knowledge/data/operators/multi_touch_in_dat.json +1185 -0
- package/dist/knowledge/data/operators/multiply_top.json +1647 -0
- package/dist/knowledge/data/operators/ncam_chop.json +1039 -0
- package/dist/knowledge/data/operators/ncam_top.json +1039 -0
- package/dist/knowledge/data/operators/ndi_dat.json +478 -0
- package/dist/knowledge/data/operators/ndi_in_top.json +1326 -0
- package/dist/knowledge/data/operators/ndi_out_top.json +1420 -0
- package/dist/knowledge/data/operators/neighbor_pop.json +534 -0
- package/dist/knowledge/data/operators/noise_chop.json +2230 -0
- package/dist/knowledge/data/operators/noise_pop.json +2107 -0
- package/dist/knowledge/data/operators/noise_sop.json +1136 -0
- package/dist/knowledge/data/operators/noise_top.json +2356 -0
- package/dist/knowledge/data/operators/normal_map_top.json +1182 -0
- package/dist/knowledge/data/operators/normal_pop.json +1488 -0
- package/dist/knowledge/data/operators/normalize_pop.json +1298 -0
- package/dist/knowledge/data/operators/notch_top.json +1511 -0
- package/dist/knowledge/data/operators/null_chop.json +673 -0
- package/dist/knowledge/data/operators/null_comp.json +2921 -0
- package/dist/knowledge/data/operators/null_dat.json +344 -0
- package/dist/knowledge/data/operators/null_mat.json +1511 -0
- package/dist/knowledge/data/operators/null_pop.json +18 -0
- package/dist/knowledge/data/operators/null_sop.json +100 -0
- package/dist/knowledge/data/operators/null_top.json +862 -0
- package/dist/knowledge/data/operators/nvidia_background_top.json +996 -0
- package/dist/knowledge/data/operators/nvidia_denoise_top.json +1089 -0
- package/dist/knowledge/data/operators/nvidia_flex_solver_comp.json +5367 -0
- package/dist/knowledge/data/operators/nvidia_flex_top.json +995 -0
- package/dist/knowledge/data/operators/nvidia_flow_emitter_comp.json +7812 -0
- package/dist/knowledge/data/operators/nvidia_flow_top.json +3675 -0
- package/dist/knowledge/data/operators/nvidia_upscaler_top.json +1089 -0
- package/dist/knowledge/data/operators/oak_device_chop.json +1372 -0
- package/dist/knowledge/data/operators/oak_select_chop.json +947 -0
- package/dist/knowledge/data/operators/oak_select_top.json +1323 -0
- package/dist/knowledge/data/operators/object_chop.json +2356 -0
- package/dist/knowledge/data/operators/object_merge_sop.json +194 -0
- package/dist/knowledge/data/operators/oculus_audio_chop.json +1371 -0
- package/dist/knowledge/data/operators/oculus_rift_chop.json +901 -0
- package/dist/knowledge/data/operators/oculus_rift_sop.json +147 -0
- package/dist/knowledge/data/operators/oculus_rift_top.json +995 -0
- package/dist/knowledge/data/operators/op_execute_dat.json +1463 -0
- package/dist/knowledge/data/operators/op_find_dat.json +2827 -0
- package/dist/knowledge/data/operators/op_viewer_comp.json +6586 -0
- package/dist/knowledge/data/operators/op_viewer_top.json +993 -0
- package/dist/knowledge/data/operators/opencolorio_top.json +1980 -0
- package/dist/knowledge/data/operators/openvr_chop.json +1135 -0
- package/dist/knowledge/data/operators/openvr_sop.json +99 -0
- package/dist/knowledge/data/operators/openvr_top.json +900 -0
- package/dist/knowledge/data/operators/optical_flow_top.json +1230 -0
- package/dist/knowledge/data/operators/optitrack_in_chop.json +854 -0
- package/dist/knowledge/data/operators/orbbec_select_top.json +1042 -0
- package/dist/knowledge/data/operators/orbbec_top.json +1558 -0
- package/dist/knowledge/data/operators/osc_in_chop.json +1371 -0
- package/dist/knowledge/data/operators/osc_in_dat.json +1280 -0
- package/dist/knowledge/data/operators/osc_out_chop.json +1138 -0
- package/dist/knowledge/data/operators/osc_out_dat.json +1280 -0
- package/dist/knowledge/data/operators/ouster_select_top.json +1228 -0
- package/dist/knowledge/data/operators/ouster_top.json +2404 -0
- package/dist/knowledge/data/operators/out_chop.json +476 -0
- package/dist/knowledge/data/operators/out_dat.json +386 -0
- package/dist/knowledge/data/operators/out_mat.json +1982 -0
- package/dist/knowledge/data/operators/out_pop.json +286 -0
- package/dist/knowledge/data/operators/out_sop.json +93 -0
- package/dist/knowledge/data/operators/out_top.json +903 -0
- package/dist/knowledge/data/operators/outside_top.json +1651 -0
- package/dist/knowledge/data/operators/over_top.json +1651 -0
- package/dist/knowledge/data/operators/override_chop.json +663 -0
- package/dist/knowledge/data/operators/pack_top.json +946 -0
- package/dist/knowledge/data/operators/pan_tilt_chop.json +11 -0
- package/dist/knowledge/data/operators/panel_chop.json +663 -0
- package/dist/knowledge/data/operators/panel_execute_dat.json +1182 -0
- package/dist/knowledge/data/operators/pangolin_chop.json +1135 -0
- package/dist/knowledge/data/operators/parameter_chop.json +803 -0
- package/dist/knowledge/data/operators/parameter_comp.json +6962 -0
- package/dist/knowledge/data/operators/parameter_dat.json +1698 -0
- package/dist/knowledge/data/operators/parameter_execute_dat.json +1371 -0
- package/dist/knowledge/data/operators/pargroup_execute_dat.json +1271 -0
- package/dist/knowledge/data/operators/particle_pop.json +1130 -0
- package/dist/knowledge/data/operators/particle_sop.json +2214 -0
- package/dist/knowledge/data/operators/pattern_chop.json +1697 -0
- package/dist/knowledge/data/operators/pattern_pop.json +1767 -0
- package/dist/knowledge/data/operators/pbr_mat.json +4990 -0
- package/dist/knowledge/data/operators/perform_chop.json +1651 -0
- package/dist/knowledge/data/operators/perform_dat.json +1184 -0
- package/dist/knowledge/data/operators/phaser_chop.json +618 -0
- package/dist/knowledge/data/operators/phaser_pop.json +1043 -0
- package/dist/knowledge/data/operators/phong_mat.json +5458 -0
- package/dist/knowledge/data/operators/photoshop_in_top.json +1418 -0
- package/dist/knowledge/data/operators/pipe_in_chop.json +1323 -0
- package/dist/knowledge/data/operators/pipe_out_chop.json +1182 -0
- package/dist/knowledge/data/operators/point_file_in_pop.json +1246 -0
- package/dist/knowledge/data/operators/point_file_in_top.json +3343 -0
- package/dist/knowledge/data/operators/point_file_select_top.json +1086 -0
- package/dist/knowledge/data/operators/point_generator_pop.json +1407 -0
- package/dist/knowledge/data/operators/point_pop.json +752 -0
- package/dist/knowledge/data/operators/point_sop.json +2171 -0
- package/dist/knowledge/data/operators/point_sprite_mat.json +2920 -0
- package/dist/knowledge/data/operators/point_transform_top.json +2779 -0
- package/dist/knowledge/data/operators/polygonize_pop.json +386 -0
- package/dist/knowledge/data/operators/polyloft_sop.json +759 -0
- package/dist/knowledge/data/operators/polypatch_sop.json +761 -0
- package/dist/knowledge/data/operators/polyreduce_sop.json +806 -0
- package/dist/knowledge/data/operators/polyspline_sop.json +667 -0
- package/dist/knowledge/data/operators/polystitch_sop.json +336 -0
- package/dist/knowledge/data/operators/pop_to_chop.json +11 -0
- package/dist/knowledge/data/operators/pop_to_dat.json +11 -0
- package/dist/knowledge/data/operators/pop_to_sop.json +11 -0
- package/dist/knowledge/data/operators/pop_to_top.json +11 -0
- package/dist/knowledge/data/operators/posistagenet_chop.json +900 -0
- package/dist/knowledge/data/operators/prefilter_map_top.json +947 -0
- package/dist/knowledge/data/operators/primitive_pop.json +1254 -0
- package/dist/knowledge/data/operators/primitive_sop.json +2075 -0
- package/dist/knowledge/data/operators/profile_sop.json +756 -0
- package/dist/knowledge/data/operators/project_sop.json +1276 -0
- package/dist/knowledge/data/operators/projection_pop.json +1060 -0
- package/dist/knowledge/data/operators/projection_top.json +1135 -0
- package/dist/knowledge/data/operators/proximity_pop.json +505 -0
- package/dist/knowledge/data/operators/pulse_chop.json +1744 -0
- package/dist/knowledge/data/operators/quantize_pop.json +1052 -0
- package/dist/knowledge/data/operators/rails_sop.json +853 -0
- package/dist/knowledge/data/operators/ramp_top.json +1899 -0
- package/dist/knowledge/data/operators/random_pop.json +1219 -0
- package/dist/knowledge/data/operators/raster_sop.json +291 -0
- package/dist/knowledge/data/operators/ray_pop.json +497 -0
- package/dist/knowledge/data/operators/ray_sop.json +856 -0
- package/dist/knowledge/data/operators/realsense_top.json +1608 -0
- package/dist/knowledge/data/operators/record_chop.json +1040 -0
- package/dist/knowledge/data/operators/rectangle_pop.json +800 -0
- package/dist/knowledge/data/operators/rectangle_sop.json +758 -0
- package/dist/knowledge/data/operators/rectangle_top.json +2259 -0
- package/dist/knowledge/data/operators/refine_sop.json +667 -0
- package/dist/knowledge/data/operators/remap_top.json +1182 -0
- package/dist/knowledge/data/operators/rename_chop.json +522 -0
- package/dist/knowledge/data/operators/render_pass_top.json +3108 -0
- package/dist/knowledge/data/operators/render_pick_chop.json +2119 -0
- package/dist/knowledge/data/operators/render_pick_dat.json +1840 -0
- package/dist/knowledge/data/operators/render_select_top.json +1086 -0
- package/dist/knowledge/data/operators/render_top.json +218 -0
- package/dist/knowledge/data/operators/renderstream_in_chop.json +617 -0
- package/dist/knowledge/data/operators/renderstream_in_top.json +950 -0
- package/dist/knowledge/data/operators/renderstream_out_top.json +997 -0
- package/dist/knowledge/data/operators/reorder_chop.json +946 -0
- package/dist/knowledge/data/operators/reorder_dat.json +711 -0
- package/dist/knowledge/data/operators/reorder_top.json +1229 -0
- package/dist/knowledge/data/operators/replace_chop.json +568 -0
- package/dist/knowledge/data/operators/replicator_comp.json +2208 -0
- package/dist/knowledge/data/operators/rerange_pop.json +892 -0
- package/dist/knowledge/data/operators/resample_chop.json +1134 -0
- package/dist/knowledge/data/operators/resample_sop.json +619 -0
- package/dist/knowledge/data/operators/resolution_top.json +892 -0
- package/dist/knowledge/data/operators/revolve_pop.json +607 -0
- package/dist/knowledge/data/operators/revolve_sop.json +714 -0
- package/dist/knowledge/data/operators/rgb_key_top.json +1654 -0
- package/dist/knowledge/data/operators/rgb_to_hsv_top.json +853 -0
- package/dist/knowledge/data/operators/s_curve_chop.json +1176 -0
- package/dist/knowledge/data/operators/scalable_display_top.json +1042 -0
- package/dist/knowledge/data/operators/screen_grab_top.json +1465 -0
- package/dist/knowledge/data/operators/screen_top.json +1652 -0
- package/dist/knowledge/data/operators/script_chop.json +520 -0
- package/dist/knowledge/data/operators/script_dat.json +429 -0
- package/dist/knowledge/data/operators/script_sop.json +146 -0
- package/dist/knowledge/data/operators/script_top.json +947 -0
- package/dist/knowledge/data/operators/select_chop.json +899 -0
- package/dist/knowledge/data/operators/select_comp.json +5645 -0
- package/dist/knowledge/data/operators/select_dat.json +1371 -0
- package/dist/knowledge/data/operators/select_mat.json +1977 -0
- package/dist/knowledge/data/operators/select_pop.json +347 -0
- package/dist/knowledge/data/operators/select_sop.json +100 -0
- package/dist/knowledge/data/operators/select_top.json +901 -0
- package/dist/knowledge/data/operators/sequence_blend_sop.json +336 -0
- package/dist/knowledge/data/operators/serial_chop.json +1182 -0
- package/dist/knowledge/data/operators/serial_dat.json +1418 -0
- package/dist/knowledge/data/operators/shared_mem_in_chop.json +620 -0
- package/dist/knowledge/data/operators/shared_mem_in_comp.json +1323 -0
- package/dist/knowledge/data/operators/shared_mem_in_top.json +994 -0
- package/dist/knowledge/data/operators/shared_mem_out_chop.json +620 -0
- package/dist/knowledge/data/operators/shared_mem_out_comp.json +1372 -0
- package/dist/knowledge/data/operators/shared_mem_out_top.json +1137 -0
- package/dist/knowledge/data/operators/shift_chop.json +899 -0
- package/dist/knowledge/data/operators/shuffle_chop.json +612 -0
- package/dist/knowledge/data/operators/sick_top.json +1322 -0
- package/dist/knowledge/data/operators/skin_deform_pop.json +972 -0
- package/dist/knowledge/data/operators/skin_pop.json +330 -0
- package/dist/knowledge/data/operators/skin_sop.json +711 -0
- package/dist/knowledge/data/operators/slider_comp.json +6918 -0
- package/dist/knowledge/data/operators/slope_chop.json +661 -0
- package/dist/knowledge/data/operators/slope_top.json +1370 -0
- package/dist/knowledge/data/operators/socketio_dat.json +945 -0
- package/dist/knowledge/data/operators/sop_to_chop.json +1137 -0
- package/dist/knowledge/data/operators/sop_to_dat.json +622 -0
- package/dist/knowledge/data/operators/sop_to_pop.json +409 -0
- package/dist/knowledge/data/operators/sort_chop.json +805 -0
- package/dist/knowledge/data/operators/sort_dat.json +894 -0
- package/dist/knowledge/data/operators/sort_pop.json +1023 -0
- package/dist/knowledge/data/operators/sort_sop.json +569 -0
- package/dist/knowledge/data/operators/spectrum_top.json +1182 -0
- package/dist/knowledge/data/operators/speed_chop.json +1180 -0
- package/dist/knowledge/data/operators/sphere_pop.json +1141 -0
- package/dist/knowledge/data/operators/sphere_sop.json +1370 -0
- package/dist/knowledge/data/operators/splice_chop.json +1226 -0
- package/dist/knowledge/data/operators/spring_chop.json +899 -0
- package/dist/knowledge/data/operators/spring_sop.json +1463 -0
- package/dist/knowledge/data/operators/sprinkle_pop.json +420 -0
- package/dist/knowledge/data/operators/sprinkle_sop.json +337 -0
- package/dist/knowledge/data/operators/sprite_sop.json +571 -0
- package/dist/knowledge/data/operators/ssao_top.json +1510 -0
- package/dist/knowledge/data/operators/st2110_device_chop.json +11 -0
- package/dist/knowledge/data/operators/stitch_sop.json +807 -0
- package/dist/knowledge/data/operators/stretch_chop.json +945 -0
- package/dist/knowledge/data/operators/stype_in_chop.json +756 -0
- package/dist/knowledge/data/operators/stype_out_chop.json +852 -0
- package/dist/knowledge/data/operators/stype_top.json +943 -0
- package/dist/knowledge/data/operators/subdivide_pop.json +292 -0
- package/dist/knowledge/data/operators/subdivide_sop.json +618 -0
- package/dist/knowledge/data/operators/substance_select_top.json +945 -0
- package/dist/knowledge/data/operators/substance_top.json +1182 -0
- package/dist/knowledge/data/operators/substitute_dat.json +1605 -0
- package/dist/knowledge/data/operators/subtract_top.json +1650 -0
- package/dist/knowledge/data/operators/superquad_sop.json +1135 -0
- package/dist/knowledge/data/operators/surfsect_sop.json +853 -0
- package/dist/knowledge/data/operators/sweep_sop.json +947 -0
- package/dist/knowledge/data/operators/switch_chop.json +523 -0
- package/dist/knowledge/data/operators/switch_dat.json +383 -0
- package/dist/knowledge/data/operators/switch_mat.json +1553 -0
- package/dist/knowledge/data/operators/switch_pop.json +496 -0
- package/dist/knowledge/data/operators/switch_sop.json +101 -0
- package/dist/knowledge/data/operators/switch_top.json +959 -0
- package/dist/knowledge/data/operators/sync_in_chop.json +617 -0
- package/dist/knowledge/data/operators/sync_out_chop.json +993 -0
- package/dist/knowledge/data/operators/syphon_spout_in_top.json +946 -0
- package/dist/knowledge/data/operators/syphon_spout_out_top.json +948 -0
- package/dist/knowledge/data/operators/table_comp.json +7009 -0
- package/dist/knowledge/data/operators/table_dat.json +1080 -0
- package/dist/knowledge/data/operators/tablet_chop.json +1367 -0
- package/dist/knowledge/data/operators/text_comp.json +8981 -0
- package/dist/knowledge/data/operators/text_dat.json +675 -0
- package/dist/knowledge/data/operators/text_sop.json +1512 -0
- package/dist/knowledge/data/operators/text_top.json +4236 -0
- package/dist/knowledge/data/operators/texture_3d_top.json +1417 -0
- package/dist/knowledge/data/operators/texture_map_pop.json +1744 -0
- package/dist/knowledge/data/operators/texture_sop.json +992 -0
- package/dist/knowledge/data/operators/threshold_top.json +1227 -0
- package/dist/knowledge/data/operators/tile_top.json +1840 -0
- package/dist/knowledge/data/operators/time_comp.json +1745 -0
- package/dist/knowledge/data/operators/time_machine_top.json +1041 -0
- package/dist/knowledge/data/operators/time_slice_chop.json +568 -0
- package/dist/knowledge/data/operators/timecode_chop.json +1981 -0
- package/dist/knowledge/data/operators/timeline_chop.json +1038 -0
- package/dist/knowledge/data/operators/timer_chop.json +4236 -0
- package/dist/knowledge/data/operators/top_to_chop.json +2077 -0
- package/dist/knowledge/data/operators/top_to_pop.json +2144 -0
- package/dist/knowledge/data/operators/topology_pop.json +1526 -0
- package/dist/knowledge/data/operators/torus_pop.json +901 -0
- package/dist/knowledge/data/operators/torus_sop.json +1368 -0
- package/dist/knowledge/data/operators/touch_in_chop.json +1182 -0
- package/dist/knowledge/data/operators/touch_in_dat.json +618 -0
- package/dist/knowledge/data/operators/touch_in_top.json +1182 -0
- package/dist/knowledge/data/operators/touch_out_chop.json +947 -0
- package/dist/knowledge/data/operators/touch_out_dat.json +712 -0
- package/dist/knowledge/data/operators/touch_out_top.json +1134 -0
- package/dist/knowledge/data/operators/trace_sop.json +759 -0
- package/dist/knowledge/data/operators/trail_chop.json +901 -0
- package/dist/knowledge/data/operators/trail_pop.json +827 -0
- package/dist/knowledge/data/operators/trail_sop.json +616 -0
- package/dist/knowledge/data/operators/transform_chop.json +2168 -0
- package/dist/knowledge/data/operators/transform_pop.json +2659 -0
- package/dist/knowledge/data/operators/transform_sop.json +1466 -0
- package/dist/knowledge/data/operators/transform_top.json +1803 -0
- package/dist/knowledge/data/operators/transform_xyz_chop.json +1369 -0
- package/dist/knowledge/data/operators/transpose_dat.json +334 -0
- package/dist/knowledge/data/operators/trig_pop.json +1135 -0
- package/dist/knowledge/data/operators/trigger_chop.json +2214 -0
- package/dist/knowledge/data/operators/trim_chop.json +805 -0
- package/dist/knowledge/data/operators/trim_sop.json +336 -0
- package/dist/knowledge/data/operators/tristrip_sop.json +194 -0
- package/dist/knowledge/data/operators/tube_pop.json +861 -0
- package/dist/knowledge/data/operators/tube_sop.json +1323 -0
- package/dist/knowledge/data/operators/tuio_in_dat.json +806 -0
- package/dist/knowledge/data/operators/twist_pop.json +1127 -0
- package/dist/knowledge/data/operators/twist_sop.json +533 -0
- package/dist/knowledge/data/operators/udp_in_dat.json +1090 -0
- package/dist/knowledge/data/operators/udp_out_dat.json +1280 -0
- package/dist/knowledge/data/operators/under_top.json +1652 -0
- package/dist/knowledge/data/operators/usd_comp.json +4800 -0
- package/dist/knowledge/data/operators/vertex_sop.json +667 -0
- package/dist/knowledge/data/operators/video_device_in_top.json +3062 -0
- package/dist/knowledge/data/operators/video_device_out_top.json +1936 -0
- package/dist/knowledge/data/operators/video_devices_dat.json +616 -0
- package/dist/knowledge/data/operators/video_stream_in_top.json +1842 -0
- package/dist/knowledge/data/operators/video_stream_out_top.json +2548 -0
- package/dist/knowledge/data/operators/vioso_top.json +943 -0
- package/dist/knowledge/data/operators/warp_chop.json +570 -0
- package/dist/knowledge/data/operators/wave_chop.json +1510 -0
- package/dist/knowledge/data/operators/web_client_dat.json +1514 -0
- package/dist/knowledge/data/operators/web_render_top.json +1749 -0
- package/dist/knowledge/data/operators/web_server_dat.json +714 -0
- package/dist/knowledge/data/operators/webrtc_dat.json +851 -0
- package/dist/knowledge/data/operators/websocket_dat.json +901 -0
- package/dist/knowledge/data/operators/widget_comp.json +6299 -0
- package/dist/knowledge/data/operators/window_comp.json +3061 -0
- package/dist/knowledge/data/operators/wireframe_mat.json +2165 -0
- package/dist/knowledge/data/operators/wireframe_sop.json +338 -0
- package/dist/knowledge/data/operators/wrnchai_chop.json +1084 -0
- package/dist/knowledge/data/operators/xml_dat.json +1230 -0
- package/dist/knowledge/data/operators/zed_chop.json +1840 -0
- package/dist/knowledge/data/operators/zed_sop.json +712 -0
- package/dist/knowledge/data/operators/zed_top.json +1981 -0
- package/dist/knowledge/data/patterns.json +1 -0
- package/dist/knowledge/data/python-api/App.json +78 -0
- package/dist/knowledge/data/python-api/Attribute.json +43 -0
- package/dist/knowledge/data/python-api/Attributes.json +41 -0
- package/dist/knowledge/data/python-api/Bounds.json +10 -0
- package/dist/knowledge/data/python-api/CHOP.json +683 -0
- package/dist/knowledge/data/python-api/COMP.json +1019 -0
- package/dist/knowledge/data/python-api/Cell.json +80 -0
- package/dist/knowledge/data/python-api/Channel.json +100 -0
- package/dist/knowledge/data/python-api/Connector.json +43 -0
- package/dist/knowledge/data/python-api/DAT.json +1289 -0
- package/dist/knowledge/data/python-api/Dependency.json +10 -0
- package/dist/knowledge/data/python-api/Error_DAT.json +9 -0
- package/dist/knowledge/data/python-api/Group.json +54 -0
- package/dist/knowledge/data/python-api/License.json +25 -0
- package/dist/knowledge/data/python-api/MAT.json +621 -0
- package/dist/knowledge/data/python-api/MOD.json +83 -0
- package/dist/knowledge/data/python-api/Matrix.json +556 -0
- package/dist/knowledge/data/python-api/Monitor.json +25 -0
- package/dist/knowledge/data/python-api/Network.json +10 -0
- package/dist/knowledge/data/python-api/NetworkEditor.json +154 -0
- package/dist/knowledge/data/python-api/Node.json +10 -0
- package/dist/knowledge/data/python-api/OP.json +621 -0
- package/dist/knowledge/data/python-api/OP_Viewer_COMP.json +9 -0
- package/dist/knowledge/data/python-api/ObjectCOMP.json +1150 -0
- package/dist/knowledge/data/python-api/Page.json +797 -0
- package/dist/knowledge/data/python-api/Pane.json +62 -0
- package/dist/knowledge/data/python-api/Panel.json +25 -0
- package/dist/knowledge/data/python-api/PanelCOMP.json +1167 -0
- package/dist/knowledge/data/python-api/PanelValue.json +25 -0
- package/dist/knowledge/data/python-api/Par.json +115 -0
- package/dist/knowledge/data/python-api/ParGroup.json +101 -0
- package/dist/knowledge/data/python-api/Parameter.json +10 -0
- package/dist/knowledge/data/python-api/Parent.json +10 -0
- package/dist/knowledge/data/python-api/Pattern_CHOP.json +9 -0
- package/dist/knowledge/data/python-api/Peer.json +85 -0
- package/dist/knowledge/data/python-api/Point.json +32 -0
- package/dist/knowledge/data/python-api/Position.json +92 -0
- package/dist/knowledge/data/python-api/Prim.json +63 -0
- package/dist/knowledge/data/python-api/Project.json +118 -0
- package/dist/knowledge/data/python-api/PythonOp.json +140 -0
- package/dist/knowledge/data/python-api/Quaternion.json +24 -0
- package/dist/knowledge/data/python-api/Replicator_COMP.json +9 -0
- package/dist/knowledge/data/python-api/Run.json +32 -0
- package/dist/knowledge/data/python-api/Runs.json +10 -0
- package/dist/knowledge/data/python-api/SOP.json +642 -0
- package/dist/knowledge/data/python-api/ScriptOp.json +119 -0
- package/dist/knowledge/data/python-api/Segment.json +25 -0
- package/dist/knowledge/data/python-api/Sequence.json +48 -0
- package/dist/knowledge/data/python-api/Stype.json +9 -0
- package/dist/knowledge/data/python-api/SysInfo.json +25 -0
- package/dist/knowledge/data/python-api/TOP.json +724 -0
- package/dist/knowledge/data/python-api/Timecode.json +200 -0
- package/dist/knowledge/data/python-api/UI.json +73 -0
- package/dist/knowledge/data/python-api/Vertex.json +25 -0
- package/dist/knowledge/data/python-api/baseCOMP.json +1019 -0
- package/dist/knowledge/data/python-api/evaluateDAT.json +1289 -0
- package/dist/knowledge/data/python-api/examineDAT.json +1289 -0
- package/dist/knowledge/data/python-api/glslTOP.json +724 -0
- package/dist/knowledge/data/python-api/index.json +1 -0
- package/dist/knowledge/data/python-api/listCOMP.json +1516 -0
- package/dist/knowledge/data/python-api/midiinDAT.json +1328 -0
- package/dist/knowledge/data/python-api/scriptCHOP.json +24 -0
- package/dist/knowledge/data/python-api/scriptDAT.json +1335 -0
- package/dist/knowledge/data/python-api/scriptSOP.json +24 -0
- package/dist/knowledge/data/python-api/scriptTOP.json +24 -0
- package/dist/knowledge/data/python-api/serialDAT.json +1323 -0
- package/dist/knowledge/data/python-api/tcpipDAT.json +1357 -0
- package/dist/knowledge/data/python-api/td.json +646 -0
- package/dist/knowledge/data/python-api/td_Module.json +24 -0
- package/dist/knowledge/data/tutorials/anatomy_of_a_chop.json +275 -0
- package/dist/knowledge/data/tutorials/build_a_list_comp.json +268 -0
- package/dist/knowledge/data/tutorials/index.json +1 -0
- package/dist/knowledge/data/tutorials/introduction_to_python_tutorial.json +1598 -0
- package/dist/knowledge/data/tutorials/tdbitwig_user_guide.json +1986 -0
- package/dist/knowledge/data/tutorials/touchdesigner_video_server_specification_guide.json +555 -0
- package/dist/knowledge/data/tutorials/video_streaming_user_guide.json +653 -0
- package/dist/knowledge/data/tutorials/write_a_cplusplus_chop.json +139 -0
- package/dist/knowledge/data/tutorials/write_a_cplusplus_plugin.json +796 -0
- package/dist/knowledge/data/tutorials/write_a_cplusplus_top.json +104 -0
- package/dist/knowledge/data/tutorials/write_a_cuda_dll.json +259 -0
- package/dist/knowledge/data/tutorials/write_a_glsl_material.json +2353 -0
- package/dist/knowledge/data/tutorials/write_a_glsl_top.json +818 -0
- package/dist/knowledge/data/tutorials/write_a_shared_memory_chop.json +261 -0
- package/dist/knowledge/data/tutorials/write_a_shared_memory_top.json +193 -0
- package/dist/recipes/audio_spectrum_bars.json +26 -0
- package/dist/recipes/data_sonification.json +37 -0
- package/dist/recipes/feedback_tunnel.json +73 -0
- package/dist/recipes/kinect_silhouette.json +45 -0
- package/dist/recipes/led_strip_mapper.json +32 -0
- package/dist/recipes/noise_landscape.json +76 -0
- package/dist/recipes/particle_galaxy.json +52 -0
- package/dist/recipes/projection_mapping.json +25 -0
- package/dist/recipes/reaction_diffusion.json +90 -0
- package/dist/recipes/webcam_glitch.json +40 -0
- package/package.json +74 -0
- package/recipes/audio_spectrum_bars.json +26 -0
- package/recipes/data_sonification.json +37 -0
- package/recipes/feedback_tunnel.json +73 -0
- package/recipes/kinect_silhouette.json +45 -0
- package/recipes/led_strip_mapper.json +32 -0
- package/recipes/noise_landscape.json +76 -0
- package/recipes/particle_galaxy.json +52 -0
- package/recipes/projection_mapping.json +25 -0
- package/recipes/reaction_diffusion.json +90 -0
- package/recipes/webcam_glitch.json +40 -0
- package/td/README.md +160 -0
- package/td/bootstrap.py +73 -0
- package/td/modules/mcp/__init__.py +1 -0
- package/td/modules/mcp/controllers/__init__.py +1 -0
- package/td/modules/mcp/controllers/api_controller.py +240 -0
- package/td/modules/mcp/dev.py +40 -0
- package/td/modules/mcp/events.py +25 -0
- package/td/modules/mcp/install.py +155 -0
- package/td/modules/mcp/services/__init__.py +1 -0
- package/td/modules/mcp/services/analysis_service.py +64 -0
- package/td/modules/mcp/services/api_service.py +195 -0
- package/td/modules/mcp/services/batch_service.py +49 -0
- package/td/modules/mcp/services/preview_service.py +40 -0
- package/td/modules/utils/__init__.py +1 -0
- package/td/modules/utils/version.py +3 -0
- package/td/startup.py +34 -0
- package/td/templates/webserver_callbacks.py +50 -0
- package/td/tests/test_api_controller.py +242 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,4375 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli/installBridge.ts
|
|
4
|
+
import { cpSync, existsSync as existsSync2 } from "fs";
|
|
5
|
+
import { homedir } from "os";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
|
|
8
|
+
// src/utils/paths.ts
|
|
9
|
+
import { existsSync } from "fs";
|
|
10
|
+
import { dirname, resolve } from "path";
|
|
11
|
+
import { fileURLToPath } from "url";
|
|
12
|
+
var moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
function firstExisting(candidates, fallback) {
|
|
14
|
+
for (const candidate of candidates) {
|
|
15
|
+
if (existsSync(candidate)) return candidate;
|
|
16
|
+
}
|
|
17
|
+
return fallback;
|
|
18
|
+
}
|
|
19
|
+
function knowledgeDataDir() {
|
|
20
|
+
return firstExisting(
|
|
21
|
+
[resolve(moduleDir, "../knowledge/data"), resolve(moduleDir, "knowledge/data")],
|
|
22
|
+
resolve(moduleDir, "../knowledge/data")
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
function recipesDir() {
|
|
26
|
+
return firstExisting(
|
|
27
|
+
[resolve(moduleDir, "../../recipes"), resolve(moduleDir, "recipes")],
|
|
28
|
+
resolve(moduleDir, "../../recipes")
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
function bridgeModulesDir() {
|
|
32
|
+
return firstExisting(
|
|
33
|
+
[resolve(moduleDir, "../td/modules"), resolve(moduleDir, "../../td/modules")],
|
|
34
|
+
resolve(moduleDir, "../td/modules")
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
function bottobotPackageDir() {
|
|
38
|
+
const candidates = [
|
|
39
|
+
resolve(moduleDir, "../../node_modules/@bottobot/td-mcp"),
|
|
40
|
+
resolve(moduleDir, "../node_modules/@bottobot/td-mcp"),
|
|
41
|
+
resolve(process.cwd(), "node_modules/@bottobot/td-mcp")
|
|
42
|
+
];
|
|
43
|
+
for (const candidate of candidates) {
|
|
44
|
+
if (existsSync(resolve(candidate, "wiki/data/processed"))) return candidate;
|
|
45
|
+
}
|
|
46
|
+
return void 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/cli/installBridge.ts
|
|
50
|
+
function runInstallBridge(args) {
|
|
51
|
+
const dirFlag = args.indexOf("--dir");
|
|
52
|
+
const explicitDir = dirFlag !== -1 ? args[dirFlag + 1] : void 0;
|
|
53
|
+
const targetRoot = explicitDir ?? join(homedir(), "tdmcp-bridge");
|
|
54
|
+
const dest = join(targetRoot, "modules");
|
|
55
|
+
const src = bridgeModulesDir();
|
|
56
|
+
if (!existsSync2(src)) {
|
|
57
|
+
console.error(
|
|
58
|
+
`[tdmcp] Could not find the bridge modules to copy (looked in ${src}).
|
|
59
|
+
If you're running from source, build first with \`npm run build\`.`
|
|
60
|
+
);
|
|
61
|
+
process.exitCode = 1;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
cpSync(src, dest, { recursive: true });
|
|
65
|
+
const oneLiner = "from mcp import install; install.run()";
|
|
66
|
+
const noPrefs = `import sys; sys.path.insert(0, ${JSON.stringify(dest)})
|
|
67
|
+
${oneLiner.replace("install.run()", `install.run(modules_dir=${JSON.stringify(dest)})`)}`;
|
|
68
|
+
console.log(
|
|
69
|
+
[
|
|
70
|
+
"",
|
|
71
|
+
" tdmcp bridge installed.",
|
|
72
|
+
` Modules copied to: ${dest}`,
|
|
73
|
+
"",
|
|
74
|
+
" Now switch the bridge on inside TouchDesigner:",
|
|
75
|
+
"",
|
|
76
|
+
" 1. Open Preferences (Edit > Preferences, or the TouchDesigner menu on macOS).",
|
|
77
|
+
' In "Python 64-bit Module Path", add this folder:',
|
|
78
|
+
"",
|
|
79
|
+
` ${dest}`,
|
|
80
|
+
"",
|
|
81
|
+
" 2. Open the Textport (Dialogs > Textport and DATs) and run:",
|
|
82
|
+
"",
|
|
83
|
+
` ${oneLiner}`,
|
|
84
|
+
"",
|
|
85
|
+
" You should see: [tdmcp] bridge running on port 9980 (/project1/tdmcp_bridge)",
|
|
86
|
+
"",
|
|
87
|
+
" \u26A0 Security: the bridge runs arbitrary Python inside TouchDesigner and the",
|
|
88
|
+
" Web Server DAT listens on all network interfaces with no auth. Only run it",
|
|
89
|
+
" on a trusted network, or firewall port 9980 to localhost.",
|
|
90
|
+
"",
|
|
91
|
+
" Prefer not to touch Preferences? Paste this in the Textport instead:",
|
|
92
|
+
"",
|
|
93
|
+
...noPrefs.split("\n").map((line) => ` ${line}`),
|
|
94
|
+
""
|
|
95
|
+
].join("\n")
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// src/server/tdmcpServer.ts
|
|
100
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
101
|
+
|
|
102
|
+
// src/prompts/debugNetwork.ts
|
|
103
|
+
import { z } from "zod";
|
|
104
|
+
|
|
105
|
+
// src/prompts/types.ts
|
|
106
|
+
function userPrompt(text) {
|
|
107
|
+
return { messages: [{ role: "user", content: { type: "text", text } }] };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// src/prompts/debugNetwork.ts
|
|
111
|
+
var registerDebugNetwork = (server) => {
|
|
112
|
+
server.registerPrompt(
|
|
113
|
+
"debug_network",
|
|
114
|
+
{
|
|
115
|
+
title: "Debug network",
|
|
116
|
+
description: "Systematically debug a TD network: check errors, verify connections, inspect cook times, and suggest fixes.",
|
|
117
|
+
argsSchema: { root_path: z.string().describe("Root path to debug from, e.g. /project1.") }
|
|
118
|
+
},
|
|
119
|
+
({ root_path }) => userPrompt(
|
|
120
|
+
[
|
|
121
|
+
`Debug the TouchDesigner network under ${root_path}. Work step by step:`,
|
|
122
|
+
`1. Call get_td_node_errors with path "${root_path}" and recursive=true. List every error/warning.`,
|
|
123
|
+
`2. Call get_td_nodes for "${root_path}" to map the children, and get_td_node_parameters on any node that errored.`,
|
|
124
|
+
"3. For each error, explain the likely cause (missing input, bad parameter, unsupported operator, resolution mismatch) and the concrete fix.",
|
|
125
|
+
"4. Apply fixes with update_td_node_parameters or connect_nodes, then re-run get_td_node_errors to confirm.",
|
|
126
|
+
"5. Finish with get_preview on the output TOP to confirm it renders."
|
|
127
|
+
].join("\n")
|
|
128
|
+
)
|
|
129
|
+
);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// src/prompts/explainNetwork.ts
|
|
133
|
+
import { z as z2 } from "zod";
|
|
134
|
+
var registerExplainNetwork = (server) => {
|
|
135
|
+
server.registerPrompt(
|
|
136
|
+
"explain_network",
|
|
137
|
+
{
|
|
138
|
+
title: "Explain network",
|
|
139
|
+
description: "Generate a human-readable explanation of what a TD network does: data flow, key parameters, and artistic intent.",
|
|
140
|
+
argsSchema: { root_path: z2.string().describe("Root path to explain, e.g. /project1.") }
|
|
141
|
+
},
|
|
142
|
+
({ root_path }) => userPrompt(
|
|
143
|
+
[
|
|
144
|
+
`Explain the TouchDesigner network under ${root_path} for someone learning TouchDesigner.`,
|
|
145
|
+
`1. Call get_td_nodes for "${root_path}" and follow the connections to understand the data flow.`,
|
|
146
|
+
"2. Describe the signal path from input to output in plain language (what each stage does and why).",
|
|
147
|
+
"3. Call out the key parameters an artist would tweak and what visual effect they have.",
|
|
148
|
+
"4. Summarize the artistic intent \u2014 what the piece looks like and the technique it uses.",
|
|
149
|
+
"Keep it concise and friendly; avoid dumping raw parameter lists."
|
|
150
|
+
].join("\n")
|
|
151
|
+
)
|
|
152
|
+
);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// src/prompts/optimizePerformance.ts
|
|
156
|
+
import { z as z3 } from "zod";
|
|
157
|
+
var registerOptimizePerformance = (server) => {
|
|
158
|
+
server.registerPrompt(
|
|
159
|
+
"optimize_performance",
|
|
160
|
+
{
|
|
161
|
+
title: "Optimize performance",
|
|
162
|
+
description: "Analyze a TD network for performance: identify bottlenecks, suggest resolution changes, and recommend cooking optimizations.",
|
|
163
|
+
argsSchema: { root_path: z3.string().describe("Root path to optimize, e.g. /project1.") }
|
|
164
|
+
},
|
|
165
|
+
({ root_path }) => userPrompt(
|
|
166
|
+
[
|
|
167
|
+
`Optimize the TouchDesigner network under ${root_path} for real-time performance.`,
|
|
168
|
+
`1. Inspect cook times: read tdmcp://operators for any heavy operators and use get_td_node_parameters under "${root_path}".`,
|
|
169
|
+
"2. Identify the most expensive nodes and explain why (resolution too high, cooking every frame, large blurs/feedback, CPU SOPs).",
|
|
170
|
+
"3. Recommend concrete fixes: lower TOP resolution, use Null/Select for caching, reduce blur sizes, move CPU work to GPU, limit cook rate, bypass unused branches.",
|
|
171
|
+
"4. Apply the safe changes with update_td_node_parameters and report the expected FPS impact. Do not delete nodes unless asked."
|
|
172
|
+
].join("\n")
|
|
173
|
+
)
|
|
174
|
+
);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// src/prompts/remixVisual.ts
|
|
178
|
+
import { z as z4 } from "zod";
|
|
179
|
+
var registerRemixVisual = (server) => {
|
|
180
|
+
server.registerPrompt(
|
|
181
|
+
"remix_visual",
|
|
182
|
+
{
|
|
183
|
+
title: "Remix visual",
|
|
184
|
+
description: "Take an existing visual system and create variations \u2014 change colors, swap techniques, add effects, alter timing.",
|
|
185
|
+
argsSchema: {
|
|
186
|
+
source_path: z4.string().describe("Path of the visual system to remix."),
|
|
187
|
+
remix_direction: z4.string().describe("What to change, e.g. 'make it darker', 'add glitch', 'slow it down'.")
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
({ source_path, remix_direction }) => userPrompt(
|
|
191
|
+
[
|
|
192
|
+
`Remix the visual system at ${source_path}. Direction: "${remix_direction}".`,
|
|
193
|
+
`1. Inspect it first: get_td_nodes and get_td_node_parameters under "${source_path}" to understand the current setup.`,
|
|
194
|
+
"2. Plan changes that match the direction (palette via level/hsv_adjust, motion via transform/feedback gain, texture via added effects).",
|
|
195
|
+
"3. Prefer non-destructive edits: update parameters, or use apply_post_processing to add effects after the output. Do not delete the original unless asked.",
|
|
196
|
+
"4. After editing, run get_td_node_errors and get_preview to show the remixed result, and describe what changed."
|
|
197
|
+
].join("\n")
|
|
198
|
+
)
|
|
199
|
+
);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
// src/prompts/visualArtistMode.ts
|
|
203
|
+
import { z as z5 } from "zod";
|
|
204
|
+
var registerVisualArtistMode = (server, ctx) => {
|
|
205
|
+
server.registerPrompt(
|
|
206
|
+
"visual_artist_mode",
|
|
207
|
+
{
|
|
208
|
+
title: "Visual artist mode",
|
|
209
|
+
description: "Think in terms of visual composition, color, motion and aesthetics rather than code while building in TouchDesigner.",
|
|
210
|
+
argsSchema: {
|
|
211
|
+
style: z5.string().optional().describe("Artistic style: abstract, geometric, organic, glitch, minimal, maximal.")
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
({ style }) => {
|
|
215
|
+
const recipes = ctx.recipes.list().map((r) => r.id).join(", ");
|
|
216
|
+
return userPrompt(
|
|
217
|
+
[
|
|
218
|
+
"You are in Visual Artist Mode for TouchDesigner via the tdmcp server.",
|
|
219
|
+
"Think like a VJ / installation artist \u2014 composition, color, motion, rhythm, negative space \u2014 not code.",
|
|
220
|
+
"",
|
|
221
|
+
"Working method:",
|
|
222
|
+
"1. Prefer high-level tools: create_visual_system (from a description) or create_feedback_network / create_generative_art / create_audio_reactive / create_particle_system.",
|
|
223
|
+
"2. Before creating nodes, consult the knowledge resources: tdmcp://operators/{category|name} and tdmcp://recipes/{name}. Never invent operator types.",
|
|
224
|
+
"3. Build inside a new container; keep the project tidy.",
|
|
225
|
+
"4. After building, run get_td_node_errors, then get_preview so we can SEE the result, and iterate on the aesthetics.",
|
|
226
|
+
`Available recipes: ${recipes || "(none yet)"}.`,
|
|
227
|
+
style ? `
|
|
228
|
+
Lean into a "${style}" aesthetic in your choices.` : ""
|
|
229
|
+
].join("\n")
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
);
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// src/prompts/index.ts
|
|
236
|
+
function registerAllPrompts(server, ctx) {
|
|
237
|
+
registerVisualArtistMode(server, ctx);
|
|
238
|
+
registerDebugNetwork(server, ctx);
|
|
239
|
+
registerOptimizePerformance(server, ctx);
|
|
240
|
+
registerExplainNetwork(server, ctx);
|
|
241
|
+
registerRemixVisual(server, ctx);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// src/resources/glslPatternResource.ts
|
|
245
|
+
import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
246
|
+
|
|
247
|
+
// src/resources/shared.ts
|
|
248
|
+
function firstVar(value) {
|
|
249
|
+
if (Array.isArray(value)) return value[0] ?? "";
|
|
250
|
+
return value ?? "";
|
|
251
|
+
}
|
|
252
|
+
function jsonContents(uri, data) {
|
|
253
|
+
return {
|
|
254
|
+
contents: [
|
|
255
|
+
{ uri: uri.href, mimeType: "application/json", text: JSON.stringify(data, null, 2) }
|
|
256
|
+
]
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// src/resources/glslPatternResource.ts
|
|
261
|
+
var registerGlslPatternResource = (server, ctx) => {
|
|
262
|
+
const template = new ResourceTemplate("tdmcp://glsl/{pattern_name}", {
|
|
263
|
+
list: async () => ({
|
|
264
|
+
resources: ctx.knowledge.listGlslPatterns().map((pattern) => ({
|
|
265
|
+
uri: `tdmcp://glsl/${pattern.id}`,
|
|
266
|
+
name: pattern.name,
|
|
267
|
+
description: `${pattern.difficulty} \u2014 ${pattern.description}`,
|
|
268
|
+
mimeType: "application/json"
|
|
269
|
+
}))
|
|
270
|
+
})
|
|
271
|
+
});
|
|
272
|
+
server.registerResource(
|
|
273
|
+
"td-glsl",
|
|
274
|
+
template,
|
|
275
|
+
{
|
|
276
|
+
title: "GLSL shader patterns",
|
|
277
|
+
description: "Named GLSL shader techniques with ready-to-use fragment shader snippets.",
|
|
278
|
+
mimeType: "application/json"
|
|
279
|
+
},
|
|
280
|
+
async (uri, variables) => {
|
|
281
|
+
const name = firstVar(variables.pattern_name);
|
|
282
|
+
const pattern = ctx.knowledge.getGlslPattern(name);
|
|
283
|
+
if (!pattern) return jsonContents(uri, { error: `GLSL pattern "${name}" not found.` });
|
|
284
|
+
return jsonContents(uri, pattern);
|
|
285
|
+
}
|
|
286
|
+
);
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
// src/resources/operatorResource.ts
|
|
290
|
+
import { ResourceTemplate as ResourceTemplate2 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
291
|
+
|
|
292
|
+
// src/knowledge/normalize.ts
|
|
293
|
+
function slugify(value) {
|
|
294
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
295
|
+
}
|
|
296
|
+
function compactKey(value) {
|
|
297
|
+
return value.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
298
|
+
}
|
|
299
|
+
function toOperatorSummary(slug, doc) {
|
|
300
|
+
return {
|
|
301
|
+
slug,
|
|
302
|
+
name: doc.name,
|
|
303
|
+
displayName: doc.displayName ?? doc.name,
|
|
304
|
+
category: doc.category ?? "Unknown",
|
|
305
|
+
subcategory: doc.subcategory ?? "",
|
|
306
|
+
summary: doc.summary ?? doc.description ?? "",
|
|
307
|
+
keywords: doc.keywords ?? []
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
function toPythonSummary(cls) {
|
|
311
|
+
return {
|
|
312
|
+
className: cls.className,
|
|
313
|
+
displayName: cls.displayName ?? cls.className,
|
|
314
|
+
category: cls.category ?? "Unknown",
|
|
315
|
+
methodCount: cls.methods?.length ?? 0,
|
|
316
|
+
memberCount: cls.members?.length ?? 0
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
function toTutorialSummary(tut) {
|
|
320
|
+
return {
|
|
321
|
+
id: tut.id,
|
|
322
|
+
name: tut.name,
|
|
323
|
+
category: tut.category ?? "Unknown",
|
|
324
|
+
summary: tut.summary ?? tut.description ?? ""
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
function normalizePatterns(raw) {
|
|
328
|
+
let list = [];
|
|
329
|
+
if (Array.isArray(raw)) {
|
|
330
|
+
list = raw;
|
|
331
|
+
} else if (raw && typeof raw === "object") {
|
|
332
|
+
const obj = raw;
|
|
333
|
+
if (Array.isArray(obj.patterns)) {
|
|
334
|
+
list = obj.patterns;
|
|
335
|
+
} else {
|
|
336
|
+
list = Object.values(obj).filter((v) => !!v && typeof v === "object");
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return list.filter((p) => typeof p.name === "string").map((p) => ({
|
|
340
|
+
id: slugify(p.name),
|
|
341
|
+
name: p.name,
|
|
342
|
+
description: p.description,
|
|
343
|
+
category: p.category,
|
|
344
|
+
workflow: Array.isArray(p.workflow) ? p.workflow : void 0,
|
|
345
|
+
use_case: p.use_case
|
|
346
|
+
}));
|
|
347
|
+
}
|
|
348
|
+
function normalizeGlsl(raw) {
|
|
349
|
+
const file = raw ?? {};
|
|
350
|
+
const techniques = Array.isArray(file.techniques) ? file.techniques : [];
|
|
351
|
+
return techniques.filter((t) => !!t && typeof t === "object").map((t) => {
|
|
352
|
+
const name = typeof t.name === "string" ? t.name : "Untitled";
|
|
353
|
+
const id = typeof t.id === "string" && t.id.length > 0 ? t.id : slugify(name);
|
|
354
|
+
return {
|
|
355
|
+
id,
|
|
356
|
+
name,
|
|
357
|
+
subcategory: typeof t.subcategory === "string" ? t.subcategory : void 0,
|
|
358
|
+
description: typeof t.description === "string" ? t.description : void 0,
|
|
359
|
+
difficulty: typeof t.difficulty === "string" ? t.difficulty : void 0,
|
|
360
|
+
operators: Array.isArray(t.operators) ? t.operators : void 0,
|
|
361
|
+
tags: Array.isArray(t.tags) ? t.tags : void 0,
|
|
362
|
+
notes: typeof t.notes === "string" ? t.notes : void 0,
|
|
363
|
+
code: t.code ?? void 0,
|
|
364
|
+
setup: typeof t.setup === "string" ? t.setup : void 0
|
|
365
|
+
};
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// src/resources/operatorResource.ts
|
|
370
|
+
var registerOperatorResource = (server, ctx) => {
|
|
371
|
+
const template = new ResourceTemplate2("tdmcp://operators/{name}", {
|
|
372
|
+
list: async () => ({
|
|
373
|
+
resources: ctx.knowledge.listOperatorCategories().map((category) => ({
|
|
374
|
+
uri: `tdmcp://operators/${category}`,
|
|
375
|
+
name: `Operators: ${category}`,
|
|
376
|
+
description: `All ${category} operators`,
|
|
377
|
+
mimeType: "application/json"
|
|
378
|
+
}))
|
|
379
|
+
}),
|
|
380
|
+
complete: {
|
|
381
|
+
name: async (value) => {
|
|
382
|
+
const categories = ctx.knowledge.listOperatorCategories();
|
|
383
|
+
const operators = ctx.knowledge.searchOperators(value, 20).map((o) => o.slug);
|
|
384
|
+
return [...categories, ...operators].slice(0, 50);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
server.registerResource(
|
|
389
|
+
"td-operators",
|
|
390
|
+
template,
|
|
391
|
+
{
|
|
392
|
+
title: "TouchDesigner operators",
|
|
393
|
+
description: "Operator catalog. Read a category (TOP, CHOP, SOP, DAT, COMP, MAT, POP) to list its operators, or an operator name/slug for full documentation.",
|
|
394
|
+
mimeType: "application/json"
|
|
395
|
+
},
|
|
396
|
+
async (uri, variables) => {
|
|
397
|
+
const name = firstVar(variables.name);
|
|
398
|
+
const isCategory = ctx.knowledge.listOperatorCategories().some((category) => compactKey(category) === compactKey(name));
|
|
399
|
+
if (isCategory) {
|
|
400
|
+
const operators = ctx.knowledge.listOperators(name);
|
|
401
|
+
return jsonContents(uri, { category: name, count: operators.length, operators });
|
|
402
|
+
}
|
|
403
|
+
const doc = ctx.knowledge.getOperator(name);
|
|
404
|
+
if (!doc) {
|
|
405
|
+
const suggestions = ctx.knowledge.searchOperators(name, 5).map((o) => o.name);
|
|
406
|
+
return jsonContents(uri, { error: `Operator "${name}" not found.`, suggestions });
|
|
407
|
+
}
|
|
408
|
+
return jsonContents(uri, doc);
|
|
409
|
+
}
|
|
410
|
+
);
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
// src/resources/patternResource.ts
|
|
414
|
+
import { ResourceTemplate as ResourceTemplate3 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
415
|
+
var registerPatternResource = (server, ctx) => {
|
|
416
|
+
const template = new ResourceTemplate3("tdmcp://patterns/{pattern_name}", {
|
|
417
|
+
list: async () => ({
|
|
418
|
+
resources: ctx.knowledge.listPatterns().map((pattern) => ({
|
|
419
|
+
uri: `tdmcp://patterns/${pattern.id}`,
|
|
420
|
+
name: pattern.name,
|
|
421
|
+
description: pattern.description,
|
|
422
|
+
mimeType: "application/json"
|
|
423
|
+
}))
|
|
424
|
+
})
|
|
425
|
+
});
|
|
426
|
+
server.registerResource(
|
|
427
|
+
"td-patterns",
|
|
428
|
+
template,
|
|
429
|
+
{
|
|
430
|
+
title: "TouchDesigner workflow patterns",
|
|
431
|
+
description: "Named operator-chain workflow patterns (recommended wiring for common tasks).",
|
|
432
|
+
mimeType: "application/json"
|
|
433
|
+
},
|
|
434
|
+
async (uri, variables) => {
|
|
435
|
+
const name = firstVar(variables.pattern_name);
|
|
436
|
+
const pattern = ctx.knowledge.getPattern(name);
|
|
437
|
+
if (!pattern) return jsonContents(uri, { error: `Pattern "${name}" not found.` });
|
|
438
|
+
return jsonContents(uri, pattern);
|
|
439
|
+
}
|
|
440
|
+
);
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
// src/resources/pythonApiResource.ts
|
|
444
|
+
import { ResourceTemplate as ResourceTemplate4 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
445
|
+
var registerPythonApiResource = (server, ctx) => {
|
|
446
|
+
const template = new ResourceTemplate4("tdmcp://python-api/{class_name}", {
|
|
447
|
+
list: async () => ({
|
|
448
|
+
resources: ctx.knowledge.listPythonClasses().map((cls) => ({
|
|
449
|
+
uri: `tdmcp://python-api/${cls.className}`,
|
|
450
|
+
name: `Python: ${cls.className}`,
|
|
451
|
+
description: `${cls.methodCount} methods, ${cls.memberCount} members`,
|
|
452
|
+
mimeType: "application/json"
|
|
453
|
+
}))
|
|
454
|
+
}),
|
|
455
|
+
complete: {
|
|
456
|
+
class_name: async (value) => ctx.knowledge.listPythonClasses().map((c) => c.className).filter((n) => n.toLowerCase().includes(value.toLowerCase())).slice(0, 50)
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
server.registerResource(
|
|
460
|
+
"td-python-api",
|
|
461
|
+
template,
|
|
462
|
+
{
|
|
463
|
+
title: "TouchDesigner Python API",
|
|
464
|
+
description: "Python class reference (members + methods) for the TouchDesigner API.",
|
|
465
|
+
mimeType: "application/json"
|
|
466
|
+
},
|
|
467
|
+
async (uri, variables) => {
|
|
468
|
+
const className = firstVar(variables.class_name);
|
|
469
|
+
const cls = ctx.knowledge.getPythonClass(className);
|
|
470
|
+
if (!cls) return jsonContents(uri, { error: `Python class "${className}" not found.` });
|
|
471
|
+
return jsonContents(uri, cls);
|
|
472
|
+
}
|
|
473
|
+
);
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
// src/resources/recipeResource.ts
|
|
477
|
+
import { ResourceTemplate as ResourceTemplate5 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
478
|
+
var registerRecipeResource = (server, ctx) => {
|
|
479
|
+
const template = new ResourceTemplate5("tdmcp://recipes/{recipe_name}", {
|
|
480
|
+
list: async () => ({
|
|
481
|
+
resources: ctx.recipes.list().map((recipe) => ({
|
|
482
|
+
uri: `tdmcp://recipes/${recipe.id}`,
|
|
483
|
+
name: recipe.name,
|
|
484
|
+
description: `${recipe.difficulty} \u2014 ${recipe.description}`,
|
|
485
|
+
mimeType: "application/json"
|
|
486
|
+
}))
|
|
487
|
+
})
|
|
488
|
+
});
|
|
489
|
+
server.registerResource(
|
|
490
|
+
"td-recipes",
|
|
491
|
+
template,
|
|
492
|
+
{
|
|
493
|
+
title: "Composite network recipes",
|
|
494
|
+
description: "Pre-validated composite network templates (nodes + connections + parameters).",
|
|
495
|
+
mimeType: "application/json"
|
|
496
|
+
},
|
|
497
|
+
async (uri, variables) => {
|
|
498
|
+
const name = firstVar(variables.recipe_name);
|
|
499
|
+
const recipe = ctx.recipes.get(name);
|
|
500
|
+
if (!recipe) {
|
|
501
|
+
return jsonContents(uri, {
|
|
502
|
+
error: `Recipe "${name}" not found.`,
|
|
503
|
+
available: ctx.recipes.list().map((r) => r.id)
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
return jsonContents(uri, recipe);
|
|
507
|
+
}
|
|
508
|
+
);
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
// src/resources/tutorialResource.ts
|
|
512
|
+
import { ResourceTemplate as ResourceTemplate6 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
513
|
+
var registerTutorialResource = (server, ctx) => {
|
|
514
|
+
const template = new ResourceTemplate6("tdmcp://tutorials/{tutorial_name}", {
|
|
515
|
+
list: async () => ({
|
|
516
|
+
resources: ctx.knowledge.listTutorials().map((tutorial) => ({
|
|
517
|
+
uri: `tdmcp://tutorials/${tutorial.id}`,
|
|
518
|
+
name: tutorial.name,
|
|
519
|
+
description: tutorial.summary,
|
|
520
|
+
mimeType: "application/json"
|
|
521
|
+
}))
|
|
522
|
+
})
|
|
523
|
+
});
|
|
524
|
+
server.registerResource(
|
|
525
|
+
"td-tutorials",
|
|
526
|
+
template,
|
|
527
|
+
{
|
|
528
|
+
title: "TouchDesigner tutorials",
|
|
529
|
+
description: "Long-form tutorial content covering TD fundamentals and workflows.",
|
|
530
|
+
mimeType: "application/json"
|
|
531
|
+
},
|
|
532
|
+
async (uri, variables) => {
|
|
533
|
+
const name = firstVar(variables.tutorial_name);
|
|
534
|
+
const tutorial = ctx.knowledge.getTutorial(name);
|
|
535
|
+
if (!tutorial) return jsonContents(uri, { error: `Tutorial "${name}" not found.` });
|
|
536
|
+
return jsonContents(uri, tutorial);
|
|
537
|
+
}
|
|
538
|
+
);
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
// src/resources/index.ts
|
|
542
|
+
function registerAllResources(server, ctx) {
|
|
543
|
+
registerOperatorResource(server, ctx);
|
|
544
|
+
registerPythonApiResource(server, ctx);
|
|
545
|
+
registerPatternResource(server, ctx);
|
|
546
|
+
registerGlslPatternResource(server, ctx);
|
|
547
|
+
registerRecipeResource(server, ctx);
|
|
548
|
+
registerTutorialResource(server, ctx);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// src/tools/layer1/applyPostProcessing.ts
|
|
552
|
+
import { z as z6 } from "zod";
|
|
553
|
+
|
|
554
|
+
// src/feedback/errorChecker.ts
|
|
555
|
+
async function checkErrors(client, path, recursive = true) {
|
|
556
|
+
const result = recursive ? await client.getNetworkErrors(path) : await client.getNodeErrors(path);
|
|
557
|
+
return { path, hasErrors: result.errors.length > 0, errors: result.errors };
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// src/feedback/previewCapture.ts
|
|
561
|
+
async function capturePreview(client, path, width = 640, height = 360) {
|
|
562
|
+
const preview = await client.getPreview(path, width, height);
|
|
563
|
+
return {
|
|
564
|
+
path: preview.path,
|
|
565
|
+
width: preview.width,
|
|
566
|
+
height: preview.height,
|
|
567
|
+
base64: preview.base64,
|
|
568
|
+
mimeType: `image/${preview.format || "png"}`
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// src/td-client/types.ts
|
|
573
|
+
var TdError = class extends Error {
|
|
574
|
+
code;
|
|
575
|
+
constructor(message, code, options) {
|
|
576
|
+
super(message, options);
|
|
577
|
+
this.name = "TdError";
|
|
578
|
+
this.code = code;
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
var TdConnectionError = class extends TdError {
|
|
582
|
+
constructor(message, options) {
|
|
583
|
+
super(message, "TD_CONNECTION", options);
|
|
584
|
+
this.name = "TdConnectionError";
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
var TdTimeoutError = class extends TdError {
|
|
588
|
+
constructor(message, options) {
|
|
589
|
+
super(message, "TD_TIMEOUT", options);
|
|
590
|
+
this.name = "TdTimeoutError";
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
var TdApiError = class extends TdError {
|
|
594
|
+
status;
|
|
595
|
+
apiCode;
|
|
596
|
+
constructor(message, options) {
|
|
597
|
+
super(message, "TD_API", options);
|
|
598
|
+
this.name = "TdApiError";
|
|
599
|
+
this.status = options?.status;
|
|
600
|
+
this.apiCode = options?.apiCode;
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
function friendlyTdError(err) {
|
|
604
|
+
if (err instanceof TdError) return err.message;
|
|
605
|
+
if (err instanceof Error) return err.message;
|
|
606
|
+
return String(err);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// src/tools/layer2/connectHelper.ts
|
|
610
|
+
async function connectNodesViaBridge(client, sourcePath, targetPath, sourceOutput = 0, targetInput = 0) {
|
|
611
|
+
try {
|
|
612
|
+
const result = await client.batch([
|
|
613
|
+
{
|
|
614
|
+
action: "connect",
|
|
615
|
+
source_path: sourcePath,
|
|
616
|
+
target_path: targetPath,
|
|
617
|
+
source_output: sourceOutput,
|
|
618
|
+
target_input: targetInput
|
|
619
|
+
}
|
|
620
|
+
]);
|
|
621
|
+
if (result.results[0]?.ok) return { method: "batch" };
|
|
622
|
+
} catch (err) {
|
|
623
|
+
if (!(err instanceof TdApiError)) throw err;
|
|
624
|
+
}
|
|
625
|
+
const src = JSON.stringify(sourcePath);
|
|
626
|
+
const dst = JSON.stringify(targetPath);
|
|
627
|
+
const python = `op(${dst}).inputConnectors[${targetInput}].connect(op(${src}).outputConnectors[${sourceOutput}])`;
|
|
628
|
+
await client.executePythonScript(python, false);
|
|
629
|
+
return { method: "python" };
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// src/tools/result.ts
|
|
633
|
+
function textResult(text) {
|
|
634
|
+
return { content: [{ type: "text", text }] };
|
|
635
|
+
}
|
|
636
|
+
function errorResult(message) {
|
|
637
|
+
return { isError: true, content: [{ type: "text", text: message }] };
|
|
638
|
+
}
|
|
639
|
+
function jsonResult(summary, data) {
|
|
640
|
+
const text = `${summary}
|
|
641
|
+
|
|
642
|
+
\`\`\`json
|
|
643
|
+
${JSON.stringify(data, null, 2)}
|
|
644
|
+
\`\`\``;
|
|
645
|
+
return { content: [{ type: "text", text }] };
|
|
646
|
+
}
|
|
647
|
+
function structuredResult(summary, data) {
|
|
648
|
+
return {
|
|
649
|
+
content: [{ type: "text", text: summary }],
|
|
650
|
+
structuredContent: data
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
function imageResult(base64, mimeType = "image/png", caption) {
|
|
654
|
+
const content = [];
|
|
655
|
+
if (caption) content.push({ type: "text", text: caption });
|
|
656
|
+
content.push({ type: "image", data: base64, mimeType });
|
|
657
|
+
return { content };
|
|
658
|
+
}
|
|
659
|
+
async function guardTd(fn, onOk) {
|
|
660
|
+
try {
|
|
661
|
+
return onOk(await fn());
|
|
662
|
+
} catch (err) {
|
|
663
|
+
return errorResult(friendlyTdError(err));
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// src/tools/layer1/orchestration.ts
|
|
668
|
+
var q = (value) => JSON.stringify(value);
|
|
669
|
+
var UNIFORM_KINDS = {
|
|
670
|
+
float: { seq: "vec", fields: ["valuex"] },
|
|
671
|
+
vec: { seq: "vec", fields: ["valuex", "valuey", "valuez", "valuew"] },
|
|
672
|
+
color: { seq: "color", fields: ["rgbr", "rgbg", "rgbb", "alpha"] }
|
|
673
|
+
};
|
|
674
|
+
function groupUniforms(uniforms) {
|
|
675
|
+
const groups = /* @__PURE__ */ new Map();
|
|
676
|
+
for (const uniform of uniforms) {
|
|
677
|
+
const { seq } = UNIFORM_KINDS[uniform.kind];
|
|
678
|
+
const key = `${uniform.node} ${seq}`;
|
|
679
|
+
let group = groups.get(key);
|
|
680
|
+
if (!group) {
|
|
681
|
+
group = { node: uniform.node, seq, items: [] };
|
|
682
|
+
groups.set(key, group);
|
|
683
|
+
}
|
|
684
|
+
group.items.push(uniform);
|
|
685
|
+
}
|
|
686
|
+
return [...groups.values()];
|
|
687
|
+
}
|
|
688
|
+
function converterSourceParam(type) {
|
|
689
|
+
const match = /^(chop|dat|top|sop)to/i.exec(type);
|
|
690
|
+
return match?.[1]?.toLowerCase();
|
|
691
|
+
}
|
|
692
|
+
async function runBuild(fn) {
|
|
693
|
+
try {
|
|
694
|
+
return await fn();
|
|
695
|
+
} catch (err) {
|
|
696
|
+
return errorResult(friendlyTdError(err));
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
var NetworkBuilder = class {
|
|
700
|
+
constructor(ctx, containerPath) {
|
|
701
|
+
this.ctx = ctx;
|
|
702
|
+
this.containerPath = containerPath;
|
|
703
|
+
}
|
|
704
|
+
ctx;
|
|
705
|
+
containerPath;
|
|
706
|
+
created = [];
|
|
707
|
+
warnings = [];
|
|
708
|
+
nameToPath = /* @__PURE__ */ new Map();
|
|
709
|
+
pathToType = /* @__PURE__ */ new Map();
|
|
710
|
+
async add(type, name, parameters, parentPath) {
|
|
711
|
+
const ref = await this.ctx.client.createNode({
|
|
712
|
+
parent_path: parentPath ?? this.containerPath,
|
|
713
|
+
type,
|
|
714
|
+
name,
|
|
715
|
+
parameters
|
|
716
|
+
});
|
|
717
|
+
if (name) this.nameToPath.set(name, ref.path);
|
|
718
|
+
if (ref.name) this.nameToPath.set(ref.name, ref.path);
|
|
719
|
+
this.pathToType.set(ref.path, ref.type || type);
|
|
720
|
+
this.created.push({ name: ref.name || name || "", path: ref.path, type: ref.type || type });
|
|
721
|
+
if (/geometrycomp/i.test(type)) {
|
|
722
|
+
await this.python(`_g = op(${q(ref.path)})
|
|
723
|
+
for _c in list(_g.children):
|
|
724
|
+
_c.destroy()`);
|
|
725
|
+
}
|
|
726
|
+
return ref.path;
|
|
727
|
+
}
|
|
728
|
+
pathOf(name) {
|
|
729
|
+
return this.nameToPath.get(name);
|
|
730
|
+
}
|
|
731
|
+
async connect(fromPath, toPath, fromOutput = 0, toInput = 0) {
|
|
732
|
+
const targetType = this.pathToType.get(toPath);
|
|
733
|
+
const param = targetType ? converterSourceParam(targetType) : void 0;
|
|
734
|
+
if (param) {
|
|
735
|
+
await this.setParams(toPath, { [param]: fromPath });
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
try {
|
|
739
|
+
await connectNodesViaBridge(this.ctx.client, fromPath, toPath, fromOutput, toInput);
|
|
740
|
+
} catch (err) {
|
|
741
|
+
this.warnings.push(`Failed to connect ${fromPath} \u2192 ${toPath}: ${friendlyTdError(err)}`);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
async setParams(path, parameters) {
|
|
745
|
+
try {
|
|
746
|
+
await this.ctx.client.updateNodeParameters(path, parameters);
|
|
747
|
+
} catch (err) {
|
|
748
|
+
this.warnings.push(`Failed to set parameters on ${path}: ${friendlyTdError(err)}`);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
async python(code) {
|
|
752
|
+
try {
|
|
753
|
+
await this.ctx.client.executePythonScript(code, false);
|
|
754
|
+
} catch (err) {
|
|
755
|
+
this.warnings.push(`Python step failed: ${friendlyTdError(err)}`);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
};
|
|
759
|
+
async function createSystemContainer(ctx, parentPath, name) {
|
|
760
|
+
const container = await ctx.client.createNode({
|
|
761
|
+
parent_path: parentPath,
|
|
762
|
+
type: "baseCOMP",
|
|
763
|
+
name
|
|
764
|
+
});
|
|
765
|
+
return new NetworkBuilder(ctx, container.path);
|
|
766
|
+
}
|
|
767
|
+
async function buildFromRecipe(ctx, recipe, parentPath) {
|
|
768
|
+
const builder = await createSystemContainer(ctx, parentPath, recipe.id);
|
|
769
|
+
for (const node of recipe.nodes) {
|
|
770
|
+
let parentPath2;
|
|
771
|
+
if (node.parent) {
|
|
772
|
+
parentPath2 = builder.pathOf(node.parent);
|
|
773
|
+
if (!parentPath2) {
|
|
774
|
+
builder.warnings.push(
|
|
775
|
+
`Node "${node.name}" references unknown parent "${node.parent}" (it must appear earlier in nodes).`
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
await builder.add(node.type, node.name, node.parameters, parentPath2);
|
|
780
|
+
}
|
|
781
|
+
if (recipe.glsl_code) {
|
|
782
|
+
for (const [nodeName, code] of Object.entries(recipe.glsl_code)) {
|
|
783
|
+
const target = builder.pathOf(nodeName);
|
|
784
|
+
if (!target) {
|
|
785
|
+
builder.warnings.push(`glsl_code references unknown node "${nodeName}".`);
|
|
786
|
+
continue;
|
|
787
|
+
}
|
|
788
|
+
const fragPath = await builder.add("textDAT", `${nodeName}_frag`);
|
|
789
|
+
await builder.python(
|
|
790
|
+
`op(${q(fragPath)}).text = ${q(code)}
|
|
791
|
+
op(${q(target)}).par.pixeldat = op(${q(fragPath)}).name`
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
for (const group of groupUniforms(recipe.glsl_uniforms)) {
|
|
796
|
+
const target = builder.pathOf(group.node);
|
|
797
|
+
if (!target) {
|
|
798
|
+
builder.warnings.push(`glsl_uniforms references unknown node "${group.node}".`);
|
|
799
|
+
continue;
|
|
800
|
+
}
|
|
801
|
+
await builder.python(
|
|
802
|
+
`_seq = op(${q(target)}).seq.${group.seq}
|
|
803
|
+
_seq.numBlocks = max(_seq.numBlocks, ${group.items.length})`
|
|
804
|
+
);
|
|
805
|
+
for (const [i, uniform] of group.items.entries()) {
|
|
806
|
+
const { fields } = UNIFORM_KINDS[uniform.kind];
|
|
807
|
+
const params = { [`${group.seq}${i}name`]: uniform.name };
|
|
808
|
+
const values = Array.isArray(uniform.value) ? uniform.value : uniform.value === void 0 ? [] : [uniform.value];
|
|
809
|
+
for (const [j, field] of fields.entries()) {
|
|
810
|
+
const value = values[j];
|
|
811
|
+
if (value !== void 0) params[`${group.seq}${i}${field}`] = value;
|
|
812
|
+
}
|
|
813
|
+
await builder.setParams(target, params);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
if (recipe.python_code) {
|
|
817
|
+
for (const [nodeName, code] of Object.entries(recipe.python_code)) {
|
|
818
|
+
const target = builder.pathOf(nodeName);
|
|
819
|
+
if (!target) {
|
|
820
|
+
builder.warnings.push(`python_code references unknown node "${nodeName}".`);
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
823
|
+
await builder.python(`op(${q(target)}).text = ${q(code)}`);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
for (const param of recipe.parameters) {
|
|
827
|
+
const target = builder.pathOf(param.node);
|
|
828
|
+
if (!target) {
|
|
829
|
+
builder.warnings.push(`Parameter "${param.name}" references unknown node "${param.node}".`);
|
|
830
|
+
continue;
|
|
831
|
+
}
|
|
832
|
+
if (param.value === void 0) continue;
|
|
833
|
+
let value = param.value;
|
|
834
|
+
if (typeof value === "string") {
|
|
835
|
+
const referenced = builder.pathOf(value);
|
|
836
|
+
if (referenced) value = referenced;
|
|
837
|
+
}
|
|
838
|
+
await builder.setParams(target, { [param.param]: value });
|
|
839
|
+
}
|
|
840
|
+
for (const connection of recipe.connections) {
|
|
841
|
+
const from = builder.pathOf(connection.from);
|
|
842
|
+
const to = builder.pathOf(connection.to);
|
|
843
|
+
if (!from || !to) {
|
|
844
|
+
builder.warnings.push(
|
|
845
|
+
`Connection ${connection.from} \u2192 ${connection.to} references an unknown node.`
|
|
846
|
+
);
|
|
847
|
+
continue;
|
|
848
|
+
}
|
|
849
|
+
await builder.connect(from, to, connection.from_output, connection.to_input);
|
|
850
|
+
}
|
|
851
|
+
for (const node of recipe.nodes) {
|
|
852
|
+
if (!node.render) continue;
|
|
853
|
+
const target = builder.pathOf(node.name);
|
|
854
|
+
if (!target) continue;
|
|
855
|
+
await builder.python(
|
|
856
|
+
`_n = op(${q(target)})
|
|
857
|
+
_p = _n.parent()
|
|
858
|
+
for _c in _p.children:
|
|
859
|
+
_c.render = False
|
|
860
|
+
_c.display = False
|
|
861
|
+
_n.render = True
|
|
862
|
+
_n.display = True`
|
|
863
|
+
);
|
|
864
|
+
}
|
|
865
|
+
const outNode = recipe.nodes.find((n) => /^out/i.test(n.name)) ?? recipe.nodes[recipe.nodes.length - 1];
|
|
866
|
+
const outputPath = outNode ? builder.pathOf(outNode.name) : void 0;
|
|
867
|
+
return { builder, outputPath };
|
|
868
|
+
}
|
|
869
|
+
async function finalize(ctx, options) {
|
|
870
|
+
const { builder } = options;
|
|
871
|
+
const warnings = [...builder.warnings];
|
|
872
|
+
let errors = [];
|
|
873
|
+
try {
|
|
874
|
+
const report = await checkErrors(ctx.client, builder.containerPath);
|
|
875
|
+
errors = report.errors;
|
|
876
|
+
if (report.hasErrors)
|
|
877
|
+
warnings.push(`${report.errors.length} node error(s) detected after build.`);
|
|
878
|
+
} catch (err) {
|
|
879
|
+
warnings.push(`Error check unavailable: ${friendlyTdError(err)}`);
|
|
880
|
+
}
|
|
881
|
+
let previewBase64;
|
|
882
|
+
let previewMime;
|
|
883
|
+
if (options.capturePreviewImage !== false && options.outputPath) {
|
|
884
|
+
try {
|
|
885
|
+
const preview = await capturePreview(ctx.client, options.outputPath);
|
|
886
|
+
previewBase64 = preview.base64;
|
|
887
|
+
previewMime = preview.mimeType;
|
|
888
|
+
} catch (err) {
|
|
889
|
+
warnings.push(`Preview unavailable: ${friendlyTdError(err)}`);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
const data = {
|
|
893
|
+
container: builder.containerPath,
|
|
894
|
+
created: builder.created.map((c) => c.path),
|
|
895
|
+
output: options.outputPath,
|
|
896
|
+
recipe: options.recipeId,
|
|
897
|
+
errors,
|
|
898
|
+
warnings,
|
|
899
|
+
...options.extra
|
|
900
|
+
};
|
|
901
|
+
const content = [
|
|
902
|
+
{
|
|
903
|
+
type: "text",
|
|
904
|
+
text: `${options.summary}
|
|
905
|
+
|
|
906
|
+
\`\`\`json
|
|
907
|
+
${JSON.stringify(data, null, 2)}
|
|
908
|
+
\`\`\``
|
|
909
|
+
}
|
|
910
|
+
];
|
|
911
|
+
if (previewBase64) {
|
|
912
|
+
content.push({ type: "image", data: previewBase64, mimeType: previewMime ?? "image/png" });
|
|
913
|
+
}
|
|
914
|
+
return { content };
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// src/tools/layer1/applyPostProcessing.ts
|
|
918
|
+
var q2 = (value) => JSON.stringify(value);
|
|
919
|
+
var DIRECT_EFFECTS = {
|
|
920
|
+
bloom: { type: "bloomTOP" },
|
|
921
|
+
blur: { type: "blurTOP", parameters: { size: 4 } },
|
|
922
|
+
edge_detect: { type: "edgeTOP" },
|
|
923
|
+
sharpen: { type: "sharpenTOP" },
|
|
924
|
+
threshold: { type: "thresholdTOP", parameters: { threshold: 0.5 } },
|
|
925
|
+
invert: { type: "levelTOP", parameters: { invert: 1 } },
|
|
926
|
+
color_grade: { type: "levelTOP", parameters: { gamma1: 1.1, blacklevel: 0.02 } }
|
|
927
|
+
};
|
|
928
|
+
var GLSL_EFFECTS = {
|
|
929
|
+
rgb_split: `out vec4 fragColor;
|
|
930
|
+
void main(){ vec2 uv=vUV.st; vec2 o=vec2(0.004,0.0);
|
|
931
|
+
float r=texture(sTD2DInputs[0],uv+o).r; float g=texture(sTD2DInputs[0],uv).g; float b=texture(sTD2DInputs[0],uv-o).b;
|
|
932
|
+
fragColor=TDOutputSwizzle(vec4(r,g,b,1.0)); }
|
|
933
|
+
`,
|
|
934
|
+
chromatic_aberration: `out vec4 fragColor;
|
|
935
|
+
void main(){ vec2 uv=vUV.st; vec2 o=(uv-0.5)*0.02;
|
|
936
|
+
float r=texture(sTD2DInputs[0],uv+o).r; float g=texture(sTD2DInputs[0],uv).g; float b=texture(sTD2DInputs[0],uv-o).b;
|
|
937
|
+
fragColor=TDOutputSwizzle(vec4(r,g,b,1.0)); }
|
|
938
|
+
`,
|
|
939
|
+
scanlines: `out vec4 fragColor;
|
|
940
|
+
void main(){ vec2 uv=vUV.st; vec4 c=texture(sTD2DInputs[0],uv);
|
|
941
|
+
float s=0.85+0.15*step(0.5,fract(uv.y*uTD2DInfos[0].res.w*0.5));
|
|
942
|
+
fragColor=TDOutputSwizzle(vec4(c.rgb*s,c.a)); }
|
|
943
|
+
`,
|
|
944
|
+
vignette: `out vec4 fragColor;
|
|
945
|
+
void main(){ vec2 uv=vUV.st; vec4 c=texture(sTD2DInputs[0],uv);
|
|
946
|
+
float v=smoothstep(0.8,0.35,distance(uv,vec2(0.5)));
|
|
947
|
+
fragColor=TDOutputSwizzle(vec4(c.rgb*v,c.a)); }
|
|
948
|
+
`,
|
|
949
|
+
posterize: `out vec4 fragColor;
|
|
950
|
+
void main(){ vec2 uv=vUV.st; vec4 c=texture(sTD2DInputs[0],uv);
|
|
951
|
+
vec3 p=floor(c.rgb*6.0)/6.0; fragColor=TDOutputSwizzle(vec4(p,c.a)); }
|
|
952
|
+
`,
|
|
953
|
+
film_grain: `out vec4 fragColor;
|
|
954
|
+
float hash(vec2 p){return fract(sin(dot(p,vec2(12.9898,78.233)))*43758.5453);}
|
|
955
|
+
void main(){ vec2 uv=vUV.st; vec4 c=texture(sTD2DInputs[0],uv);
|
|
956
|
+
float g=hash(uv*uTD2DInfos[0].res.zw)*0.12-0.06; fragColor=TDOutputSwizzle(vec4(c.rgb+g,c.a)); }
|
|
957
|
+
`,
|
|
958
|
+
glitch: `out vec4 fragColor;
|
|
959
|
+
float hash(float x){return fract(sin(x*127.1)*43758.5453);}
|
|
960
|
+
void main(){ vec2 uv=vUV.st; float band=floor(uv.y*24.0);
|
|
961
|
+
float off=(hash(band)-0.5)*0.06*step(0.7,hash(band*1.7));
|
|
962
|
+
fragColor=TDOutputSwizzle(texture(sTD2DInputs[0],uv+vec2(off,0.0))); }
|
|
963
|
+
`
|
|
964
|
+
};
|
|
965
|
+
var EFFECTS = [
|
|
966
|
+
"bloom",
|
|
967
|
+
"chromatic_aberration",
|
|
968
|
+
"film_grain",
|
|
969
|
+
"vignette",
|
|
970
|
+
"color_grade",
|
|
971
|
+
"sharpen",
|
|
972
|
+
"blur",
|
|
973
|
+
"edge_detect",
|
|
974
|
+
"invert",
|
|
975
|
+
"threshold",
|
|
976
|
+
"posterize",
|
|
977
|
+
"glitch",
|
|
978
|
+
"rgb_split",
|
|
979
|
+
"scanlines"
|
|
980
|
+
];
|
|
981
|
+
var applyPostProcessingSchema = z6.object({
|
|
982
|
+
source_path: z6.string().describe("Path of the TOP to post-process."),
|
|
983
|
+
effects: z6.array(z6.enum(EFFECTS)).min(1).describe("Effects to apply in order."),
|
|
984
|
+
parent_path: z6.string().default("/project1")
|
|
985
|
+
});
|
|
986
|
+
async function addGlslEffect(builder, name, fragment) {
|
|
987
|
+
const glsl = await builder.add("glslTOP", name);
|
|
988
|
+
const frag = await builder.add("textDAT", `${name}_frag`);
|
|
989
|
+
await builder.python(
|
|
990
|
+
`op(${q2(frag)}).text = ${q2(fragment)}
|
|
991
|
+
op(${q2(glsl)}).par.pixeldat = op(${q2(frag)}).name`
|
|
992
|
+
);
|
|
993
|
+
return glsl;
|
|
994
|
+
}
|
|
995
|
+
async function applyPostProcessingImpl(ctx, args) {
|
|
996
|
+
return runBuild(async () => {
|
|
997
|
+
const builder = await createSystemContainer(ctx, args.parent_path, "post_fx");
|
|
998
|
+
const source = await builder.add("selectTOP", "source");
|
|
999
|
+
await builder.setParams(source, { top: args.source_path });
|
|
1000
|
+
let previous = source;
|
|
1001
|
+
let applied = 0;
|
|
1002
|
+
for (let i = 0; i < args.effects.length; i++) {
|
|
1003
|
+
const effect = args.effects[i];
|
|
1004
|
+
if (!effect) continue;
|
|
1005
|
+
let nodePath;
|
|
1006
|
+
const direct = DIRECT_EFFECTS[effect];
|
|
1007
|
+
if (direct) {
|
|
1008
|
+
nodePath = await builder.add(direct.type, `${effect}${i}`, direct.parameters);
|
|
1009
|
+
} else if (GLSL_EFFECTS[effect]) {
|
|
1010
|
+
nodePath = await addGlslEffect(builder, `${effect}${i}`, GLSL_EFFECTS[effect]);
|
|
1011
|
+
}
|
|
1012
|
+
if (!nodePath) {
|
|
1013
|
+
builder.warnings.push(`Effect "${effect}" is not supported and was skipped.`);
|
|
1014
|
+
continue;
|
|
1015
|
+
}
|
|
1016
|
+
await builder.connect(previous, nodePath);
|
|
1017
|
+
previous = nodePath;
|
|
1018
|
+
applied++;
|
|
1019
|
+
}
|
|
1020
|
+
const out = await builder.add("nullTOP", "out1");
|
|
1021
|
+
await builder.connect(previous, out);
|
|
1022
|
+
return finalize(ctx, {
|
|
1023
|
+
summary: `Applied ${applied}/${args.effects.length} post-processing effect(s) to ${args.source_path}.`,
|
|
1024
|
+
builder,
|
|
1025
|
+
outputPath: out,
|
|
1026
|
+
extra: { source_path: args.source_path, effects: args.effects }
|
|
1027
|
+
});
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
var registerApplyPostProcessing = (server, ctx) => {
|
|
1031
|
+
server.registerTool(
|
|
1032
|
+
"apply_post_processing",
|
|
1033
|
+
{
|
|
1034
|
+
title: "Apply post-processing",
|
|
1035
|
+
description: "Chain post-processing effects (bloom, glitch, rgb_split, vignette, etc.) onto an existing TOP. Returns the processed output.",
|
|
1036
|
+
inputSchema: applyPostProcessingSchema.shape,
|
|
1037
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true }
|
|
1038
|
+
},
|
|
1039
|
+
(args) => applyPostProcessingImpl(ctx, args)
|
|
1040
|
+
);
|
|
1041
|
+
};
|
|
1042
|
+
|
|
1043
|
+
// src/tools/layer1/createAudioReactive.ts
|
|
1044
|
+
import { z as z7 } from "zod";
|
|
1045
|
+
var q3 = (value) => JSON.stringify(value);
|
|
1046
|
+
var AUDIO_SPECTRUM_SHADER = `out vec4 fragColor;
|
|
1047
|
+
void main(){
|
|
1048
|
+
vec2 uv = vUV.st;
|
|
1049
|
+
float amp = texture(sTD2DInputs[0], vec2(uv.x, 0.5)).r;
|
|
1050
|
+
float bar = step(uv.y, clamp(amp, 0.0, 1.0));
|
|
1051
|
+
vec3 col = mix(vec3(0.02, 0.0, 0.08), vec3(0.1, 0.8, 1.0), uv.x) * bar;
|
|
1052
|
+
fragColor = TDOutputSwizzle(vec4(col, 1.0));
|
|
1053
|
+
}
|
|
1054
|
+
`;
|
|
1055
|
+
var createAudioReactiveSchema = z7.object({
|
|
1056
|
+
audio_source: z7.enum(["microphone", "file", "device_in", "existing_chop"]).default("microphone"),
|
|
1057
|
+
audio_file_path: z7.string().optional().describe("Audio file path (audio_source='file')."),
|
|
1058
|
+
existing_chop_path: z7.string().optional().describe("Existing CHOP path (audio_source='existing_chop')."),
|
|
1059
|
+
visual_style: z7.enum(["geometric", "particle", "feedback", "glsl", "instancing"]),
|
|
1060
|
+
frequency_bands: z7.number().int().positive().default(8).describe(
|
|
1061
|
+
"Spectrum resolution: sets the Audio Spectrum CHOP output length (TouchDesigner clamps it to 128\u20134096 bins). Higher = finer spectrum."
|
|
1062
|
+
),
|
|
1063
|
+
beat_detection: z7.boolean().default(true),
|
|
1064
|
+
parent_path: z7.string().default("/project1")
|
|
1065
|
+
});
|
|
1066
|
+
async function buildAudioSource(builder, args) {
|
|
1067
|
+
if (args.audio_source === "existing_chop" && args.existing_chop_path) {
|
|
1068
|
+
return args.existing_chop_path;
|
|
1069
|
+
}
|
|
1070
|
+
if (args.audio_source === "file") {
|
|
1071
|
+
return builder.add("audiofileinCHOP", "audioin", {
|
|
1072
|
+
...args.audio_file_path ? { file: args.audio_file_path } : {},
|
|
1073
|
+
play: 1
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
return builder.add("audiodeviceinCHOP", "audioin");
|
|
1077
|
+
}
|
|
1078
|
+
async function createAudioReactiveImpl(ctx, args) {
|
|
1079
|
+
return runBuild(async () => {
|
|
1080
|
+
const builder = await createSystemContainer(ctx, args.parent_path, "audio_reactive");
|
|
1081
|
+
const audioSource = await buildAudioSource(builder, args);
|
|
1082
|
+
const outLength = Math.min(Math.max(args.frequency_bands, 128), 4096);
|
|
1083
|
+
const spectrum = await builder.add("audiospectrumCHOP", "spectrum", {
|
|
1084
|
+
outputmenu: "setmanually",
|
|
1085
|
+
outlength: outLength
|
|
1086
|
+
});
|
|
1087
|
+
await builder.connect(audioSource, spectrum);
|
|
1088
|
+
const analyze = await builder.add("analyzeCHOP", "level", { function: 6 });
|
|
1089
|
+
await builder.connect(audioSource, analyze);
|
|
1090
|
+
if (args.beat_detection) {
|
|
1091
|
+
const beat = await builder.add("beatCHOP", "beat");
|
|
1092
|
+
await builder.connect(audioSource, beat);
|
|
1093
|
+
}
|
|
1094
|
+
const audioTex = await builder.add("choptoTOP", "audio_tex");
|
|
1095
|
+
await builder.connect(spectrum, audioTex);
|
|
1096
|
+
let visual;
|
|
1097
|
+
if (args.visual_style === "glsl") {
|
|
1098
|
+
visual = await builder.add("glslTOP", "visual", {
|
|
1099
|
+
outputresolution: "custom",
|
|
1100
|
+
resolutionw: 1280,
|
|
1101
|
+
resolutionh: 720,
|
|
1102
|
+
format: "rgba8fixed"
|
|
1103
|
+
});
|
|
1104
|
+
const frag = await builder.add("textDAT", "visual_frag");
|
|
1105
|
+
await builder.python(
|
|
1106
|
+
`op(${q3(frag)}).text = ${q3(AUDIO_SPECTRUM_SHADER)}
|
|
1107
|
+
op(${q3(visual)}).par.pixeldat = op(${q3(frag)}).name`
|
|
1108
|
+
);
|
|
1109
|
+
await builder.connect(audioTex, visual);
|
|
1110
|
+
} else {
|
|
1111
|
+
visual = await builder.add("circleTOP", "visual", { radius: 0.3 });
|
|
1112
|
+
await builder.python(
|
|
1113
|
+
`c = op(${q3(visual)})
|
|
1114
|
+
for p in ('radiusx', 'radiusy'):
|
|
1115
|
+
try:
|
|
1116
|
+
par = getattr(c.par, p)
|
|
1117
|
+
par.expr = "0.2 + op('level')['chan1'] * 0.6"
|
|
1118
|
+
except Exception: pass`
|
|
1119
|
+
);
|
|
1120
|
+
builder.warnings.push(
|
|
1121
|
+
`Visual style "${args.visual_style}" is approximated: a circle driven by the audio level. Refine the mapping for production.`
|
|
1122
|
+
);
|
|
1123
|
+
}
|
|
1124
|
+
const out = await builder.add("nullTOP", "out1");
|
|
1125
|
+
await builder.connect(visual, out);
|
|
1126
|
+
return finalize(ctx, {
|
|
1127
|
+
summary: `Created an audio-reactive system (source: ${args.audio_source}, style: ${args.visual_style}, ${args.frequency_bands} bands).`,
|
|
1128
|
+
builder,
|
|
1129
|
+
outputPath: out,
|
|
1130
|
+
extra: {
|
|
1131
|
+
audio_source: args.audio_source,
|
|
1132
|
+
visual_style: args.visual_style,
|
|
1133
|
+
beat_detection: args.beat_detection
|
|
1134
|
+
}
|
|
1135
|
+
});
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
var registerCreateAudioReactive = (server, ctx) => {
|
|
1139
|
+
server.registerTool(
|
|
1140
|
+
"create_audio_reactive",
|
|
1141
|
+
{
|
|
1142
|
+
title: "Create audio-reactive visual",
|
|
1143
|
+
description: "Build an audio analysis chain (spectrum + level + optional beat) and a visual driven by it. The 'glsl' style renders a spectrum visualization; other styles are approximated.",
|
|
1144
|
+
inputSchema: createAudioReactiveSchema.shape,
|
|
1145
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true }
|
|
1146
|
+
},
|
|
1147
|
+
(args) => createAudioReactiveImpl(ctx, args)
|
|
1148
|
+
);
|
|
1149
|
+
};
|
|
1150
|
+
|
|
1151
|
+
// src/tools/layer1/createDataVisualization.ts
|
|
1152
|
+
import { z as z8 } from "zod";
|
|
1153
|
+
var q4 = (value) => JSON.stringify(value);
|
|
1154
|
+
var BARS_SHADER = `out vec4 fragColor;
|
|
1155
|
+
void main(){
|
|
1156
|
+
vec2 uv = vUV.st;
|
|
1157
|
+
float v = texture(sTD2DInputs[0], vec2(uv.x, 0.5)).r;
|
|
1158
|
+
float bar = step(uv.y, clamp(v, 0.0, 1.0));
|
|
1159
|
+
vec3 col = mix(vec3(0.03, 0.05, 0.1), vec3(0.2, 0.9, 0.6), uv.x) * bar;
|
|
1160
|
+
fragColor = TDOutputSwizzle(vec4(col, 1.0));
|
|
1161
|
+
}
|
|
1162
|
+
`;
|
|
1163
|
+
var SOURCE_TYPE = {
|
|
1164
|
+
table: "tableDAT",
|
|
1165
|
+
file: "fileinDAT",
|
|
1166
|
+
chop: "constantCHOP"
|
|
1167
|
+
};
|
|
1168
|
+
var createDataVisualizationSchema = z8.object({
|
|
1169
|
+
data_source: z8.enum(["table", "file", "chop"]).default("table"),
|
|
1170
|
+
chart_style: z8.enum(["bars", "graph", "points"]).default("bars"),
|
|
1171
|
+
parent_path: z8.string().default("/project1")
|
|
1172
|
+
});
|
|
1173
|
+
async function createDataVisualizationImpl(ctx, args) {
|
|
1174
|
+
return runBuild(async () => {
|
|
1175
|
+
const builder = await createSystemContainer(ctx, args.parent_path, "data_viz");
|
|
1176
|
+
const source = await builder.add(SOURCE_TYPE[args.data_source], "data");
|
|
1177
|
+
if (args.data_source === "table") {
|
|
1178
|
+
const rows = Array.from(
|
|
1179
|
+
{ length: 16 },
|
|
1180
|
+
(_, i) => (0.5 + 0.45 * Math.sin(i * 0.6)).toFixed(3)
|
|
1181
|
+
);
|
|
1182
|
+
await builder.python(
|
|
1183
|
+
`d = op(${q4(source)})
|
|
1184
|
+
d.clear()
|
|
1185
|
+
for v in ${JSON.stringify(rows)}:
|
|
1186
|
+
d.appendRow([v])`
|
|
1187
|
+
);
|
|
1188
|
+
}
|
|
1189
|
+
let chop = source;
|
|
1190
|
+
if (args.data_source !== "chop") {
|
|
1191
|
+
chop = await builder.add("dattoCHOP", "datto", {
|
|
1192
|
+
firstrow: "values",
|
|
1193
|
+
firstcolumn: "values",
|
|
1194
|
+
output: "chanpercol"
|
|
1195
|
+
});
|
|
1196
|
+
await builder.connect(source, chop);
|
|
1197
|
+
}
|
|
1198
|
+
const tex = await builder.add("choptoTOP", "data_tex");
|
|
1199
|
+
await builder.connect(chop, tex);
|
|
1200
|
+
let visual = tex;
|
|
1201
|
+
if (args.chart_style === "bars") {
|
|
1202
|
+
const glsl = await builder.add("glslTOP", "chart", {
|
|
1203
|
+
outputresolution: "custom",
|
|
1204
|
+
resolutionw: 1280,
|
|
1205
|
+
resolutionh: 720,
|
|
1206
|
+
format: "rgba8fixed"
|
|
1207
|
+
});
|
|
1208
|
+
const frag = await builder.add("textDAT", "chart_frag");
|
|
1209
|
+
await builder.python(
|
|
1210
|
+
`op(${q4(frag)}).text = ${q4(BARS_SHADER)}
|
|
1211
|
+
op(${q4(glsl)}).par.pixeldat = op(${q4(frag)}).name`
|
|
1212
|
+
);
|
|
1213
|
+
await builder.connect(tex, glsl);
|
|
1214
|
+
visual = glsl;
|
|
1215
|
+
} else {
|
|
1216
|
+
builder.warnings.push(
|
|
1217
|
+
`Chart style "${args.chart_style}" renders the data as a texture strip; richer plotting needs customization.`
|
|
1218
|
+
);
|
|
1219
|
+
}
|
|
1220
|
+
const out = await builder.add("nullTOP", "out1");
|
|
1221
|
+
await builder.connect(visual, out);
|
|
1222
|
+
builder.warnings.push(
|
|
1223
|
+
"Wire your real data into the 'data' node \u2014 a placeholder source was created."
|
|
1224
|
+
);
|
|
1225
|
+
return finalize(ctx, {
|
|
1226
|
+
summary: `Created a data visualization (source: ${args.data_source}, style: ${args.chart_style}).`,
|
|
1227
|
+
builder,
|
|
1228
|
+
outputPath: out,
|
|
1229
|
+
extra: { data_source: args.data_source, chart_style: args.chart_style }
|
|
1230
|
+
});
|
|
1231
|
+
});
|
|
1232
|
+
}
|
|
1233
|
+
var registerCreateDataVisualization = (server, ctx) => {
|
|
1234
|
+
server.registerTool(
|
|
1235
|
+
"create_data_visualization",
|
|
1236
|
+
{
|
|
1237
|
+
title: "Create data visualization",
|
|
1238
|
+
description: "Build a data-driven visualization: a data source feeds a CHOP that drives a chart TOP. Wire your real data into the created 'data' node.",
|
|
1239
|
+
inputSchema: createDataVisualizationSchema.shape,
|
|
1240
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true }
|
|
1241
|
+
},
|
|
1242
|
+
(args) => createDataVisualizationImpl(ctx, args)
|
|
1243
|
+
);
|
|
1244
|
+
};
|
|
1245
|
+
|
|
1246
|
+
// src/tools/layer1/createFeedbackNetwork.ts
|
|
1247
|
+
import { z as z9 } from "zod";
|
|
1248
|
+
var q5 = (value) => JSON.stringify(value);
|
|
1249
|
+
function colorizeShader(colors) {
|
|
1250
|
+
const toVec3 = (hex) => {
|
|
1251
|
+
const m = hex ? /^#?([0-9a-fA-F]{6})$/.exec(hex.trim()) : null;
|
|
1252
|
+
if (!m?.[1]) return "vec3(1.0)";
|
|
1253
|
+
const n = Number.parseInt(m[1], 16);
|
|
1254
|
+
const r = (n >> 16 & 255) / 255;
|
|
1255
|
+
const g = (n >> 8 & 255) / 255;
|
|
1256
|
+
const b = (n & 255) / 255;
|
|
1257
|
+
return `vec3(${r.toFixed(4)}, ${g.toFixed(4)}, ${b.toFixed(4)})`;
|
|
1258
|
+
};
|
|
1259
|
+
const lo = colors.length >= 2 ? toVec3(colors[0]) : "vec3(0.0)";
|
|
1260
|
+
const hi = toVec3(colors[colors.length - 1]);
|
|
1261
|
+
return `out vec4 fragColor;
|
|
1262
|
+
void main(){
|
|
1263
|
+
float l = texture(sTD2DInputs[0], vUV.st).r;
|
|
1264
|
+
fragColor = TDOutputSwizzle(vec4(mix(${lo}, ${hi}, l), 1.0));
|
|
1265
|
+
}
|
|
1266
|
+
`;
|
|
1267
|
+
}
|
|
1268
|
+
var SEED_TYPES = {
|
|
1269
|
+
noise: "noiseTOP",
|
|
1270
|
+
shape: "circleTOP",
|
|
1271
|
+
image: "moviefileinTOP",
|
|
1272
|
+
video: "moviefileinTOP",
|
|
1273
|
+
webcam: "videodeviceinTOP",
|
|
1274
|
+
glsl: "glslTOP"
|
|
1275
|
+
};
|
|
1276
|
+
var TRANSFORM_TYPES = {
|
|
1277
|
+
blur: "blurTOP",
|
|
1278
|
+
displace: "displaceTOP",
|
|
1279
|
+
edge: "edgeTOP",
|
|
1280
|
+
level: "levelTOP",
|
|
1281
|
+
hsv_adjust: "hsvadjustTOP",
|
|
1282
|
+
transform: "transformTOP",
|
|
1283
|
+
mirror: "mirrorTOP",
|
|
1284
|
+
tile: "tileTOP",
|
|
1285
|
+
luma_blur: "lumablurTOP"
|
|
1286
|
+
};
|
|
1287
|
+
var createFeedbackNetworkSchema = z9.object({
|
|
1288
|
+
seed_type: z9.enum(["noise", "shape", "image", "video", "webcam", "glsl"]).default("noise"),
|
|
1289
|
+
transformations: z9.array(
|
|
1290
|
+
z9.enum([
|
|
1291
|
+
"blur",
|
|
1292
|
+
"displace",
|
|
1293
|
+
"edge",
|
|
1294
|
+
"level",
|
|
1295
|
+
"hsv_adjust",
|
|
1296
|
+
"transform",
|
|
1297
|
+
"mirror",
|
|
1298
|
+
"tile",
|
|
1299
|
+
"luma_blur"
|
|
1300
|
+
])
|
|
1301
|
+
).default(["blur", "displace", "level"]),
|
|
1302
|
+
feedback_gain: z9.number().min(0).max(1).default(0.95),
|
|
1303
|
+
colors: z9.array(z9.string()).max(2).optional().describe("Up to two hex colors to colorize the otherwise-grayscale output."),
|
|
1304
|
+
parent_path: z9.string().default("/project1")
|
|
1305
|
+
});
|
|
1306
|
+
async function createFeedbackNetworkImpl(ctx, args) {
|
|
1307
|
+
return runBuild(async () => {
|
|
1308
|
+
const builder = await createSystemContainer(ctx, args.parent_path, "feedback_system");
|
|
1309
|
+
const seed = await builder.add(SEED_TYPES[args.seed_type], "seed", {
|
|
1310
|
+
...args.seed_type === "noise" ? { monochrome: 1, period: 4 } : {}
|
|
1311
|
+
});
|
|
1312
|
+
const feedback = await builder.add("feedbackTOP", "feedback1");
|
|
1313
|
+
const comp = await builder.add("compositeTOP", "comp1");
|
|
1314
|
+
await builder.setParams(comp, { operand: "maximum" });
|
|
1315
|
+
await builder.connect(seed, comp, 0, 0);
|
|
1316
|
+
await builder.connect(feedback, comp, 0, 1);
|
|
1317
|
+
await builder.connect(seed, feedback);
|
|
1318
|
+
let last = comp;
|
|
1319
|
+
for (const transformation of args.transformations) {
|
|
1320
|
+
const node = await builder.add(TRANSFORM_TYPES[transformation], transformation);
|
|
1321
|
+
await builder.connect(last, node);
|
|
1322
|
+
if (transformation === "displace" || transformation === "luma_blur") {
|
|
1323
|
+
await builder.connect(seed, node, 0, 1);
|
|
1324
|
+
}
|
|
1325
|
+
last = node;
|
|
1326
|
+
}
|
|
1327
|
+
const gain = await builder.add("levelTOP", "gain");
|
|
1328
|
+
await builder.setParams(gain, { gain: args.feedback_gain });
|
|
1329
|
+
await builder.connect(last, gain);
|
|
1330
|
+
let output = gain;
|
|
1331
|
+
if (args.colors && args.colors.length > 0) {
|
|
1332
|
+
const colorize = await builder.add("glslTOP", "colorize");
|
|
1333
|
+
const frag = await builder.add("textDAT", "colorize_frag");
|
|
1334
|
+
await builder.python(
|
|
1335
|
+
`op(${q5(frag)}).text = ${q5(colorizeShader(args.colors))}
|
|
1336
|
+
op(${q5(colorize)}).par.pixeldat = op(${q5(frag)}).name`
|
|
1337
|
+
);
|
|
1338
|
+
await builder.connect(gain, colorize);
|
|
1339
|
+
output = colorize;
|
|
1340
|
+
}
|
|
1341
|
+
const out = await builder.add("nullTOP", "out1");
|
|
1342
|
+
await builder.connect(output, out);
|
|
1343
|
+
await builder.python(`op(${q5(feedback)}).par.top = op(${q5(gain)}).name`);
|
|
1344
|
+
return finalize(ctx, {
|
|
1345
|
+
summary: `Created a feedback network (seed: ${args.seed_type}, gain: ${args.feedback_gain}, ${args.transformations.length} transform(s)).`,
|
|
1346
|
+
builder,
|
|
1347
|
+
outputPath: out,
|
|
1348
|
+
extra: { seed_type: args.seed_type, transformations: args.transformations }
|
|
1349
|
+
});
|
|
1350
|
+
});
|
|
1351
|
+
}
|
|
1352
|
+
var registerCreateFeedbackNetwork = (server, ctx) => {
|
|
1353
|
+
server.registerTool(
|
|
1354
|
+
"create_feedback_network",
|
|
1355
|
+
{
|
|
1356
|
+
title: "Create feedback network",
|
|
1357
|
+
description: "Build a feedback-based visual system: a seed feeds a loop that is transformed (blur/displace/etc.) and fed back each frame. Great for evolving, hypnotic visuals.",
|
|
1358
|
+
inputSchema: createFeedbackNetworkSchema.shape,
|
|
1359
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true }
|
|
1360
|
+
},
|
|
1361
|
+
(args) => createFeedbackNetworkImpl(ctx, args)
|
|
1362
|
+
);
|
|
1363
|
+
};
|
|
1364
|
+
|
|
1365
|
+
// src/tools/layer1/createGenerativeArt.ts
|
|
1366
|
+
import { z as z10 } from "zod";
|
|
1367
|
+
var q6 = (value) => JSON.stringify(value);
|
|
1368
|
+
var DEFAULT_PLASMA = `out vec4 fragColor;
|
|
1369
|
+
uniform float uTime;
|
|
1370
|
+
void main(){
|
|
1371
|
+
vec2 uv = vUV.st;
|
|
1372
|
+
float v = sin(uv.x * 10.0 + uTime) + sin(uv.y * 10.0 + uTime * 0.7);
|
|
1373
|
+
v += sin((uv.x + uv.y) * 8.0 + uTime * 1.3);
|
|
1374
|
+
vec3 col = 0.5 + 0.5 * cos(vec3(0.0, 2.0, 4.0) + v);
|
|
1375
|
+
fragColor = TDOutputSwizzle(vec4(col, 1.0));
|
|
1376
|
+
}
|
|
1377
|
+
`;
|
|
1378
|
+
var RECIPE_FOR = /* @__PURE__ */ new Map([
|
|
1379
|
+
["reaction_diffusion", "reaction_diffusion"],
|
|
1380
|
+
["noise_landscape", "noise_landscape"]
|
|
1381
|
+
]);
|
|
1382
|
+
var createGenerativeArtSchema = z10.object({
|
|
1383
|
+
technique: z10.enum([
|
|
1384
|
+
"noise_landscape",
|
|
1385
|
+
"reaction_diffusion",
|
|
1386
|
+
"strange_attractor",
|
|
1387
|
+
"l_system",
|
|
1388
|
+
"cellular_automata",
|
|
1389
|
+
"flow_field",
|
|
1390
|
+
"voronoi",
|
|
1391
|
+
"fractal",
|
|
1392
|
+
"custom_glsl"
|
|
1393
|
+
]),
|
|
1394
|
+
color_palette: z10.string().optional().describe("Free-text palette hint (best-effort)."),
|
|
1395
|
+
evolution_speed: z10.number().positive().default(1),
|
|
1396
|
+
custom_glsl_code: z10.string().optional().describe("Fragment shader (only for technique 'custom_glsl')."),
|
|
1397
|
+
parent_path: z10.string().default("/project1")
|
|
1398
|
+
});
|
|
1399
|
+
async function buildGlslGenerative(ctx, parentPath, name, fragment) {
|
|
1400
|
+
const builder = await createSystemContainer(ctx, parentPath, name);
|
|
1401
|
+
const glsl = await builder.add("glslTOP", "glsl1");
|
|
1402
|
+
const frag = await builder.add("textDAT", "glsl1_frag");
|
|
1403
|
+
await builder.python(
|
|
1404
|
+
`op(${q6(frag)}).text = ${q6(fragment)}
|
|
1405
|
+
op(${q6(glsl)}).par.pixeldat = op(${q6(frag)}).name`
|
|
1406
|
+
);
|
|
1407
|
+
const out = await builder.add("nullTOP", "out1");
|
|
1408
|
+
await builder.connect(glsl, out);
|
|
1409
|
+
return { builder, outputPath: out };
|
|
1410
|
+
}
|
|
1411
|
+
async function createGenerativeArtImpl(ctx, args) {
|
|
1412
|
+
return runBuild(async () => {
|
|
1413
|
+
const recipeId = RECIPE_FOR.get(args.technique);
|
|
1414
|
+
if (recipeId) {
|
|
1415
|
+
const recipe = ctx.recipes.get(recipeId);
|
|
1416
|
+
if (recipe) {
|
|
1417
|
+
const { builder: builder2, outputPath } = await buildFromRecipe(ctx, recipe, args.parent_path);
|
|
1418
|
+
return finalize(ctx, {
|
|
1419
|
+
summary: `Created "${recipe.name}" generative system.`,
|
|
1420
|
+
builder: builder2,
|
|
1421
|
+
outputPath,
|
|
1422
|
+
recipeId,
|
|
1423
|
+
extra: { technique: args.technique, color_palette: args.color_palette }
|
|
1424
|
+
});
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
if (args.technique === "custom_glsl") {
|
|
1428
|
+
const fragment = args.custom_glsl_code ?? DEFAULT_PLASMA;
|
|
1429
|
+
const { builder: builder2, outputPath } = await buildGlslGenerative(
|
|
1430
|
+
ctx,
|
|
1431
|
+
args.parent_path,
|
|
1432
|
+
"generative_custom_glsl",
|
|
1433
|
+
fragment
|
|
1434
|
+
);
|
|
1435
|
+
if (!args.custom_glsl_code) {
|
|
1436
|
+
builder2.warnings.push("No custom_glsl_code provided; used a default plasma shader.");
|
|
1437
|
+
}
|
|
1438
|
+
return finalize(ctx, {
|
|
1439
|
+
summary: "Created a custom GLSL generative system.",
|
|
1440
|
+
builder: builder2,
|
|
1441
|
+
outputPath,
|
|
1442
|
+
extra: { technique: args.technique }
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1445
|
+
const pattern = ctx.knowledge.getGlslPattern(args.technique);
|
|
1446
|
+
if (pattern?.code?.snippet) {
|
|
1447
|
+
const { builder: builder2, outputPath } = await buildGlslGenerative(
|
|
1448
|
+
ctx,
|
|
1449
|
+
args.parent_path,
|
|
1450
|
+
`generative_${args.technique}`,
|
|
1451
|
+
pattern.code.snippet
|
|
1452
|
+
);
|
|
1453
|
+
return finalize(ctx, {
|
|
1454
|
+
summary: `Created a "${args.technique}" system from the GLSL knowledge pattern "${pattern.name}".`,
|
|
1455
|
+
builder: builder2,
|
|
1456
|
+
outputPath,
|
|
1457
|
+
extra: { technique: args.technique, glsl_pattern: pattern.id }
|
|
1458
|
+
});
|
|
1459
|
+
}
|
|
1460
|
+
const builder = await createSystemContainer(
|
|
1461
|
+
ctx,
|
|
1462
|
+
args.parent_path,
|
|
1463
|
+
`generative_${args.technique}`
|
|
1464
|
+
);
|
|
1465
|
+
const noise = await builder.add("noiseTOP", "noise1", { monochrome: 0, period: 6 });
|
|
1466
|
+
const level = await builder.add("levelTOP", "level1");
|
|
1467
|
+
const out = await builder.add("nullTOP", "out1");
|
|
1468
|
+
await builder.connect(noise, level);
|
|
1469
|
+
await builder.connect(level, out);
|
|
1470
|
+
await builder.python(
|
|
1471
|
+
`p = op(${q6(noise)}).par.tz
|
|
1472
|
+
p.expr = ${q6(`absTime.seconds * ${args.evolution_speed}`)}`
|
|
1473
|
+
);
|
|
1474
|
+
builder.warnings.push(
|
|
1475
|
+
`Technique "${args.technique}" is approximated with an animated-noise generator in this version.`
|
|
1476
|
+
);
|
|
1477
|
+
return finalize(ctx, {
|
|
1478
|
+
summary: `Created an approximate "${args.technique}" generative system.`,
|
|
1479
|
+
builder,
|
|
1480
|
+
outputPath: out,
|
|
1481
|
+
extra: { technique: args.technique, evolution_speed: args.evolution_speed }
|
|
1482
|
+
});
|
|
1483
|
+
});
|
|
1484
|
+
}
|
|
1485
|
+
var registerCreateGenerativeArt = (server, ctx) => {
|
|
1486
|
+
server.registerTool(
|
|
1487
|
+
"create_generative_art",
|
|
1488
|
+
{
|
|
1489
|
+
title: "Create generative art",
|
|
1490
|
+
description: "Create an evolving generative visual. Known techniques (reaction_diffusion, noise_landscape) use validated recipes; custom_glsl uses your shader; others fall back to a knowledge GLSL pattern or animated noise.",
|
|
1491
|
+
inputSchema: createGenerativeArtSchema.shape,
|
|
1492
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true }
|
|
1493
|
+
},
|
|
1494
|
+
(args) => createGenerativeArtImpl(ctx, args)
|
|
1495
|
+
);
|
|
1496
|
+
};
|
|
1497
|
+
|
|
1498
|
+
// src/tools/layer1/createParticleSystem.ts
|
|
1499
|
+
import { z as z11 } from "zod";
|
|
1500
|
+
var q7 = (value) => JSON.stringify(value);
|
|
1501
|
+
var EMITTER_SOP = {
|
|
1502
|
+
point: "addSOP",
|
|
1503
|
+
line: "lineSOP",
|
|
1504
|
+
circle: "circleSOP",
|
|
1505
|
+
sphere: "sphereSOP",
|
|
1506
|
+
mesh: "boxSOP",
|
|
1507
|
+
image: "gridSOP"
|
|
1508
|
+
};
|
|
1509
|
+
var createParticleSystemSchema = z11.object({
|
|
1510
|
+
emitter_shape: z11.enum(["point", "line", "circle", "sphere", "mesh", "image"]).default("point"),
|
|
1511
|
+
particle_count: z11.number().int().positive().default(1e4),
|
|
1512
|
+
forces: z11.array(z11.enum(["gravity", "noise", "attract", "repel", "vortex", "turbulence", "drag"])).default(["noise", "gravity"]),
|
|
1513
|
+
render_style: z11.enum(["points", "sprites", "lines", "trails", "instanced_geo"]).default("sprites"),
|
|
1514
|
+
lifetime: z11.number().positive().default(3),
|
|
1515
|
+
parent_path: z11.string().default("/project1")
|
|
1516
|
+
});
|
|
1517
|
+
async function createParticleSystemImpl(ctx, args) {
|
|
1518
|
+
return runBuild(async () => {
|
|
1519
|
+
const builder = await createSystemContainer(ctx, args.parent_path, "particle_system");
|
|
1520
|
+
const geo = await builder.add("geometryCOMP", "geo");
|
|
1521
|
+
const emitter = await ctx.client.createNode({
|
|
1522
|
+
parent_path: geo,
|
|
1523
|
+
type: EMITTER_SOP[args.emitter_shape],
|
|
1524
|
+
name: "emitter"
|
|
1525
|
+
});
|
|
1526
|
+
const particle = await ctx.client.createNode({
|
|
1527
|
+
parent_path: geo,
|
|
1528
|
+
type: "particleSOP",
|
|
1529
|
+
name: "particle",
|
|
1530
|
+
parameters: { life: args.lifetime }
|
|
1531
|
+
});
|
|
1532
|
+
builder.created.push(
|
|
1533
|
+
{ name: "emitter", path: emitter.path, type: emitter.type },
|
|
1534
|
+
{ name: "particle", path: particle.path, type: particle.type }
|
|
1535
|
+
);
|
|
1536
|
+
await builder.connect(emitter.path, particle.path);
|
|
1537
|
+
const mat = await builder.add(
|
|
1538
|
+
args.render_style === "sprites" ? "pointspriteMAT" : "constantMAT",
|
|
1539
|
+
"mat"
|
|
1540
|
+
);
|
|
1541
|
+
const cam = await builder.add("cameraCOMP", "cam", { tz: 5 });
|
|
1542
|
+
const light = await builder.add("lightCOMP", "light", { tx: 3, ty: 4, tz: 4 });
|
|
1543
|
+
const render = await builder.add("renderTOP", "render");
|
|
1544
|
+
const out = await builder.add("nullTOP", "out1");
|
|
1545
|
+
await builder.connect(render, out);
|
|
1546
|
+
await builder.python(
|
|
1547
|
+
[
|
|
1548
|
+
`g = op(${q7(geo)})`,
|
|
1549
|
+
"p = op(g.path + '/particle')",
|
|
1550
|
+
"p.render = True",
|
|
1551
|
+
"p.display = True",
|
|
1552
|
+
`g.par.material = ${q7(mat)}`,
|
|
1553
|
+
`r = op(${q7(render)})`,
|
|
1554
|
+
`r.par.geometry = ${q7(geo)}`,
|
|
1555
|
+
`r.par.camera = ${q7(cam)}`,
|
|
1556
|
+
`r.par.lights = ${q7(light)}`
|
|
1557
|
+
].join("\n")
|
|
1558
|
+
);
|
|
1559
|
+
builder.warnings.push(
|
|
1560
|
+
`Particle forces (${args.forces.join(", ")}), exact count (${args.particle_count}) and the "${args.render_style}" render style are scaffolded; tune the particleSOP and material for production.`
|
|
1561
|
+
);
|
|
1562
|
+
return finalize(ctx, {
|
|
1563
|
+
summary: `Created a particle system (emitter: ${args.emitter_shape}, ~${args.particle_count} particles, render: ${args.render_style}).`,
|
|
1564
|
+
builder,
|
|
1565
|
+
outputPath: out,
|
|
1566
|
+
extra: {
|
|
1567
|
+
emitter_shape: args.emitter_shape,
|
|
1568
|
+
forces: args.forces,
|
|
1569
|
+
render_style: args.render_style
|
|
1570
|
+
}
|
|
1571
|
+
});
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
1574
|
+
var registerCreateParticleSystem = (server, ctx) => {
|
|
1575
|
+
server.registerTool(
|
|
1576
|
+
"create_particle_system",
|
|
1577
|
+
{
|
|
1578
|
+
title: "Create particle system",
|
|
1579
|
+
description: "Build a particle system: an emitter feeds a particle SOP inside a Geometry COMP, rendered with a camera + light. Forces and render style are scaffolded for further tuning.",
|
|
1580
|
+
inputSchema: createParticleSystemSchema.shape,
|
|
1581
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true }
|
|
1582
|
+
},
|
|
1583
|
+
(args) => createParticleSystemImpl(ctx, args)
|
|
1584
|
+
);
|
|
1585
|
+
};
|
|
1586
|
+
|
|
1587
|
+
// src/tools/layer1/createVisualSystem.ts
|
|
1588
|
+
import { z as z12 } from "zod";
|
|
1589
|
+
|
|
1590
|
+
// src/tools/layer1/intent.ts
|
|
1591
|
+
var GENERIC_TERMS = /* @__PURE__ */ new Set([
|
|
1592
|
+
"generative",
|
|
1593
|
+
"visual",
|
|
1594
|
+
"visuals",
|
|
1595
|
+
"glow",
|
|
1596
|
+
"glowing",
|
|
1597
|
+
"abstract",
|
|
1598
|
+
"field",
|
|
1599
|
+
"system",
|
|
1600
|
+
"animated",
|
|
1601
|
+
"evolving",
|
|
1602
|
+
"dynamic",
|
|
1603
|
+
"background",
|
|
1604
|
+
"colorful",
|
|
1605
|
+
"colourful",
|
|
1606
|
+
"bright",
|
|
1607
|
+
"beautiful"
|
|
1608
|
+
]);
|
|
1609
|
+
function significantTerms(description) {
|
|
1610
|
+
return description.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 3 && !GENERIC_TERMS.has(t));
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
// src/tools/layer1/createVisualSystem.ts
|
|
1614
|
+
var createVisualSystemSchema = z12.object({
|
|
1615
|
+
description: z12.string().min(1).describe("Natural-language description of the visual system."),
|
|
1616
|
+
parent_path: z12.string().default("/project1"),
|
|
1617
|
+
resolution: z12.enum(["720p", "1080p", "4K", "custom"]).default("1080p"),
|
|
1618
|
+
target_fps: z12.number().positive().default(60)
|
|
1619
|
+
});
|
|
1620
|
+
function classify(description) {
|
|
1621
|
+
const d = description.toLowerCase();
|
|
1622
|
+
const has = (...words) => words.some((w) => d.includes(w));
|
|
1623
|
+
if (has("audio", "sound", "music", "beat", "frequenc", "spectrum", "reactive")) {
|
|
1624
|
+
return { kind: "audio", label: "audio-reactive" };
|
|
1625
|
+
}
|
|
1626
|
+
if (has("particle", "swarm", "galaxy", "sparkle", "emitter")) {
|
|
1627
|
+
return { kind: "particle", label: "particle" };
|
|
1628
|
+
}
|
|
1629
|
+
if (has("reaction", "diffusion", "gray-scott", "gray scott")) {
|
|
1630
|
+
return { kind: "reaction_diffusion", label: "reaction-diffusion" };
|
|
1631
|
+
}
|
|
1632
|
+
if (has("landscape", "terrain", "mountain", "heightfield")) {
|
|
1633
|
+
return { kind: "noise_landscape", label: "noise landscape" };
|
|
1634
|
+
}
|
|
1635
|
+
if (has("feedback", "tunnel", "echo", "trail", "infinite", "kaleido")) {
|
|
1636
|
+
return { kind: "feedback", label: "feedback" };
|
|
1637
|
+
}
|
|
1638
|
+
return { kind: "default", label: "generative" };
|
|
1639
|
+
}
|
|
1640
|
+
function pickAudioStyle(description) {
|
|
1641
|
+
const d = description.toLowerCase();
|
|
1642
|
+
if (d.includes("particle")) return "particle";
|
|
1643
|
+
if (d.includes("feedback") || d.includes("tunnel")) return "feedback";
|
|
1644
|
+
if (d.includes("geometr") || d.includes("shape")) return "geometric";
|
|
1645
|
+
if (d.includes("instanc")) return "instancing";
|
|
1646
|
+
return "glsl";
|
|
1647
|
+
}
|
|
1648
|
+
function pickEmitter(description) {
|
|
1649
|
+
const d = description.toLowerCase();
|
|
1650
|
+
if (d.includes("galaxy") || d.includes("sphere") || d.includes("orbit")) return "sphere";
|
|
1651
|
+
if (d.includes("ring") || d.includes("circle")) return "circle";
|
|
1652
|
+
if (d.includes("line")) return "line";
|
|
1653
|
+
return "point";
|
|
1654
|
+
}
|
|
1655
|
+
var COLOR_WORDS = {
|
|
1656
|
+
red: "#e03030",
|
|
1657
|
+
crimson: "#c01040",
|
|
1658
|
+
orange: "#ff7a18",
|
|
1659
|
+
amber: "#ffb000",
|
|
1660
|
+
yellow: "#f5e050",
|
|
1661
|
+
gold: "#e8c020",
|
|
1662
|
+
green: "#30c050",
|
|
1663
|
+
lime: "#9be025",
|
|
1664
|
+
teal: "#10b0a0",
|
|
1665
|
+
cyan: "#20d0e0",
|
|
1666
|
+
blue: "#1840d0",
|
|
1667
|
+
navy: "#0a1a66",
|
|
1668
|
+
indigo: "#3010a0",
|
|
1669
|
+
purple: "#7a20c0",
|
|
1670
|
+
violet: "#8a40e0",
|
|
1671
|
+
magenta: "#d020a0",
|
|
1672
|
+
pink: "#ff60b0",
|
|
1673
|
+
white: "#f0f0f0",
|
|
1674
|
+
black: "#101015"
|
|
1675
|
+
};
|
|
1676
|
+
function parseColors(description) {
|
|
1677
|
+
const d = description.toLowerCase();
|
|
1678
|
+
const found = [];
|
|
1679
|
+
for (const [word, hex] of Object.entries(COLOR_WORDS)) {
|
|
1680
|
+
if (new RegExp(`\\b${word}`).test(d) && !found.includes(hex)) found.push(hex);
|
|
1681
|
+
if (found.length >= 2) break;
|
|
1682
|
+
}
|
|
1683
|
+
return found;
|
|
1684
|
+
}
|
|
1685
|
+
function withNote(result, note) {
|
|
1686
|
+
return { ...result, content: [{ type: "text", text: note }, ...result.content] };
|
|
1687
|
+
}
|
|
1688
|
+
async function createVisualSystemImpl(ctx, args) {
|
|
1689
|
+
const { kind, label } = classify(args.description);
|
|
1690
|
+
const note = `Interpreted "${args.description}" as a ${label} system (target ${args.resolution} @ ${args.target_fps}fps).`;
|
|
1691
|
+
ctx.logger.info("create_visual_system classified", { kind, description: args.description });
|
|
1692
|
+
switch (kind) {
|
|
1693
|
+
case "audio":
|
|
1694
|
+
return withNote(
|
|
1695
|
+
await createAudioReactiveImpl(ctx, {
|
|
1696
|
+
audio_source: "microphone",
|
|
1697
|
+
visual_style: pickAudioStyle(args.description),
|
|
1698
|
+
frequency_bands: 8,
|
|
1699
|
+
beat_detection: true,
|
|
1700
|
+
parent_path: args.parent_path
|
|
1701
|
+
}),
|
|
1702
|
+
note
|
|
1703
|
+
);
|
|
1704
|
+
case "particle":
|
|
1705
|
+
return withNote(
|
|
1706
|
+
await createParticleSystemImpl(ctx, {
|
|
1707
|
+
emitter_shape: pickEmitter(args.description),
|
|
1708
|
+
particle_count: 1e4,
|
|
1709
|
+
forces: ["noise", "gravity"],
|
|
1710
|
+
render_style: "sprites",
|
|
1711
|
+
lifetime: 3,
|
|
1712
|
+
parent_path: args.parent_path
|
|
1713
|
+
}),
|
|
1714
|
+
note
|
|
1715
|
+
);
|
|
1716
|
+
case "feedback":
|
|
1717
|
+
return withNote(
|
|
1718
|
+
await createFeedbackNetworkImpl(ctx, {
|
|
1719
|
+
seed_type: "noise",
|
|
1720
|
+
transformations: ["blur", "displace", "level"],
|
|
1721
|
+
feedback_gain: 0.95,
|
|
1722
|
+
colors: parseColors(args.description),
|
|
1723
|
+
parent_path: args.parent_path
|
|
1724
|
+
}),
|
|
1725
|
+
note
|
|
1726
|
+
);
|
|
1727
|
+
case "reaction_diffusion":
|
|
1728
|
+
case "noise_landscape":
|
|
1729
|
+
return withNote(
|
|
1730
|
+
await createGenerativeArtImpl(ctx, {
|
|
1731
|
+
technique: kind,
|
|
1732
|
+
evolution_speed: 1,
|
|
1733
|
+
parent_path: args.parent_path
|
|
1734
|
+
}),
|
|
1735
|
+
note
|
|
1736
|
+
);
|
|
1737
|
+
default: {
|
|
1738
|
+
const recipe = ctx.recipes.findByTags(significantTerms(args.description));
|
|
1739
|
+
if (recipe) {
|
|
1740
|
+
return runBuild(async () => {
|
|
1741
|
+
const { builder, outputPath } = await buildFromRecipe(ctx, recipe, args.parent_path);
|
|
1742
|
+
return withNote(
|
|
1743
|
+
await finalize(ctx, {
|
|
1744
|
+
summary: `Built "${recipe.name}" from a matching recipe.`,
|
|
1745
|
+
builder,
|
|
1746
|
+
outputPath,
|
|
1747
|
+
recipeId: recipe.id
|
|
1748
|
+
}),
|
|
1749
|
+
note
|
|
1750
|
+
);
|
|
1751
|
+
});
|
|
1752
|
+
}
|
|
1753
|
+
return withNote(
|
|
1754
|
+
await createGenerativeArtImpl(ctx, {
|
|
1755
|
+
technique: "custom_glsl",
|
|
1756
|
+
evolution_speed: 1,
|
|
1757
|
+
parent_path: args.parent_path
|
|
1758
|
+
}),
|
|
1759
|
+
note
|
|
1760
|
+
);
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
var registerCreateVisualSystem = (server, ctx) => {
|
|
1765
|
+
server.registerTool(
|
|
1766
|
+
"create_visual_system",
|
|
1767
|
+
{
|
|
1768
|
+
title: "Create visual system",
|
|
1769
|
+
description: "Create a complete visual system from a natural-language description. Classifies intent (audio-reactive, particle, feedback, reaction-diffusion, landscape, generative) and builds it in a self-contained COMP, then verifies and previews it.",
|
|
1770
|
+
inputSchema: createVisualSystemSchema.shape,
|
|
1771
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true }
|
|
1772
|
+
},
|
|
1773
|
+
(args) => createVisualSystemImpl(ctx, args)
|
|
1774
|
+
);
|
|
1775
|
+
};
|
|
1776
|
+
|
|
1777
|
+
// src/tools/layer1/describeProject.ts
|
|
1778
|
+
import { z as z13 } from "zod";
|
|
1779
|
+
var describeProjectSchema = z13.object({
|
|
1780
|
+
description: z13.string().min(1).describe("Natural-language description of the visual you want.")
|
|
1781
|
+
});
|
|
1782
|
+
function describeProjectImpl(ctx, args) {
|
|
1783
|
+
const d = args.description.toLowerCase();
|
|
1784
|
+
const has = (...words) => words.some((w) => d.includes(w));
|
|
1785
|
+
let tool;
|
|
1786
|
+
let summary;
|
|
1787
|
+
let recipeId;
|
|
1788
|
+
if (has("audio", "sound", "music", "beat", "frequenc", "spectrum", "reactive")) {
|
|
1789
|
+
tool = "create_audio_reactive";
|
|
1790
|
+
summary = "audio-reactive visual";
|
|
1791
|
+
} else if (has("particle", "swarm", "galaxy", "sparkle", "emitter")) {
|
|
1792
|
+
tool = "create_particle_system";
|
|
1793
|
+
summary = "particle system";
|
|
1794
|
+
} else if (has("reaction", "diffusion", "gray-scott", "gray scott")) {
|
|
1795
|
+
tool = "create_generative_art";
|
|
1796
|
+
summary = "reaction-diffusion simulation";
|
|
1797
|
+
recipeId = "reaction_diffusion";
|
|
1798
|
+
} else if (has("landscape", "terrain", "mountain", "heightfield")) {
|
|
1799
|
+
tool = "create_generative_art";
|
|
1800
|
+
summary = "3D noise landscape";
|
|
1801
|
+
recipeId = "noise_landscape";
|
|
1802
|
+
} else if (has("feedback", "tunnel", "echo", "trail", "kaleido")) {
|
|
1803
|
+
tool = "create_feedback_network";
|
|
1804
|
+
summary = "feedback network";
|
|
1805
|
+
} else {
|
|
1806
|
+
tool = "create_generative_art";
|
|
1807
|
+
summary = "generative visual";
|
|
1808
|
+
recipeId = ctx.recipes.findByTags(significantTerms(d))?.id;
|
|
1809
|
+
}
|
|
1810
|
+
const lines = [
|
|
1811
|
+
`Plan for: "${args.description}"`,
|
|
1812
|
+
"",
|
|
1813
|
+
`Interpreted as: ${summary}.`,
|
|
1814
|
+
`Recommended tool: ${tool}${recipeId ? ` (recipe: ${recipeId})` : ""}.`
|
|
1815
|
+
];
|
|
1816
|
+
if (recipeId) {
|
|
1817
|
+
const recipe = ctx.recipes.get(recipeId);
|
|
1818
|
+
if (recipe) {
|
|
1819
|
+
lines.push("", `Recipe "${recipe.name}" would create:`);
|
|
1820
|
+
for (const node of recipe.nodes) {
|
|
1821
|
+
lines.push(` - ${node.name} (${node.type})${node.comment ? ` \u2014 ${node.comment}` : ""}`);
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
lines.push("", "Next: call the recommended tool, then check get_td_node_errors and get_preview.");
|
|
1826
|
+
return textResult(lines.join("\n"));
|
|
1827
|
+
}
|
|
1828
|
+
var registerDescribeProject = (server, ctx) => {
|
|
1829
|
+
server.registerTool(
|
|
1830
|
+
"plan_visual",
|
|
1831
|
+
{
|
|
1832
|
+
title: "Plan a visual from a description",
|
|
1833
|
+
description: "Turn a natural-language description of a visual you WANT into a build plan (which tool/recipe and nodes) \u2014 a dry run that creates nothing. Note: this does NOT inspect the current TouchDesigner project; to read existing nodes use get_td_nodes / get_td_topology / find_td_nodes.",
|
|
1834
|
+
inputSchema: describeProjectSchema.shape,
|
|
1835
|
+
annotations: { readOnlyHint: true, openWorldHint: false }
|
|
1836
|
+
},
|
|
1837
|
+
(args) => describeProjectImpl(ctx, args)
|
|
1838
|
+
);
|
|
1839
|
+
};
|
|
1840
|
+
|
|
1841
|
+
// src/tools/layer1/getPreview.ts
|
|
1842
|
+
import { z as z14 } from "zod";
|
|
1843
|
+
var getPreviewSchema = z14.object({
|
|
1844
|
+
node_path: z14.string().describe("Path of the TOP node to capture."),
|
|
1845
|
+
width: z14.number().int().positive().default(640),
|
|
1846
|
+
height: z14.number().int().positive().default(360)
|
|
1847
|
+
});
|
|
1848
|
+
async function getPreviewImpl(ctx, args) {
|
|
1849
|
+
return guardTd(
|
|
1850
|
+
() => capturePreview(ctx.client, args.node_path, args.width, args.height),
|
|
1851
|
+
(preview) => imageResult(
|
|
1852
|
+
preview.base64,
|
|
1853
|
+
preview.mimeType,
|
|
1854
|
+
`Preview of ${args.node_path} (${preview.width}\xD7${preview.height}).`
|
|
1855
|
+
)
|
|
1856
|
+
);
|
|
1857
|
+
}
|
|
1858
|
+
var registerGetPreview = (server, ctx) => {
|
|
1859
|
+
server.registerTool(
|
|
1860
|
+
"get_preview",
|
|
1861
|
+
{
|
|
1862
|
+
title: "Preview a TOP",
|
|
1863
|
+
description: "Capture a TOP node as an inline image so you can see what was created.",
|
|
1864
|
+
inputSchema: getPreviewSchema.shape,
|
|
1865
|
+
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
1866
|
+
},
|
|
1867
|
+
(args) => getPreviewImpl(ctx, args)
|
|
1868
|
+
);
|
|
1869
|
+
};
|
|
1870
|
+
|
|
1871
|
+
// src/tools/layer1/setupOutput.ts
|
|
1872
|
+
import { z as z15 } from "zod";
|
|
1873
|
+
var q8 = (value) => JSON.stringify(value);
|
|
1874
|
+
var OUTPUT_MAP = {
|
|
1875
|
+
window: "windowCOMP",
|
|
1876
|
+
ndi: "ndioutTOP",
|
|
1877
|
+
syphon_spout: "syphonspoutoutTOP",
|
|
1878
|
+
record: "moviefileoutTOP",
|
|
1879
|
+
touch_out: "touchoutTOP"
|
|
1880
|
+
};
|
|
1881
|
+
var RESOLUTIONS = {
|
|
1882
|
+
"720p": [1280, 720],
|
|
1883
|
+
"1080p": [1920, 1080],
|
|
1884
|
+
"4K": [3840, 2160]
|
|
1885
|
+
};
|
|
1886
|
+
var setupOutputSchema = z15.object({
|
|
1887
|
+
source_path: z15.string().describe("Path of the final TOP to output."),
|
|
1888
|
+
output_type: z15.enum(["window", "ndi", "syphon_spout", "record", "touch_out"]).default("window"),
|
|
1889
|
+
resolution: z15.enum(["720p", "1080p", "4K"]).default("1080p"),
|
|
1890
|
+
record_format: z15.enum(["mp4", "mov", "image_sequence"]).optional(),
|
|
1891
|
+
parent_path: z15.string().default("/project1")
|
|
1892
|
+
});
|
|
1893
|
+
async function setupOutputImpl(ctx, args) {
|
|
1894
|
+
return runBuild(async () => {
|
|
1895
|
+
const warnings = [];
|
|
1896
|
+
const node = await ctx.client.createNode({
|
|
1897
|
+
parent_path: args.parent_path,
|
|
1898
|
+
type: OUTPUT_MAP[args.output_type],
|
|
1899
|
+
name: `${args.output_type}_out`
|
|
1900
|
+
});
|
|
1901
|
+
if (args.output_type === "window") {
|
|
1902
|
+
const [width, height] = RESOLUTIONS[args.resolution];
|
|
1903
|
+
try {
|
|
1904
|
+
await ctx.client.executePythonScript(
|
|
1905
|
+
`w = op(${q8(node.path)})
|
|
1906
|
+
w.par.winop = ${q8(args.source_path)}
|
|
1907
|
+
w.par.winw = ${width}
|
|
1908
|
+
w.par.winh = ${height}`,
|
|
1909
|
+
false
|
|
1910
|
+
);
|
|
1911
|
+
} catch (err) {
|
|
1912
|
+
warnings.push(`Could not configure window: ${friendlyTdError(err)}`);
|
|
1913
|
+
}
|
|
1914
|
+
} else {
|
|
1915
|
+
try {
|
|
1916
|
+
await connectNodesViaBridge(ctx.client, args.source_path, node.path);
|
|
1917
|
+
} catch (err) {
|
|
1918
|
+
warnings.push(`Could not connect source to output: ${friendlyTdError(err)}`);
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
if (args.output_type === "record" && args.record_format) {
|
|
1922
|
+
try {
|
|
1923
|
+
await ctx.client.updateNodeParameters(node.path, { type: args.record_format });
|
|
1924
|
+
} catch (err) {
|
|
1925
|
+
warnings.push(`Could not set record format: ${friendlyTdError(err)}`);
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
return jsonResult(`Configured ${args.output_type} output for ${args.source_path}.`, {
|
|
1929
|
+
output: node.path,
|
|
1930
|
+
output_type: args.output_type,
|
|
1931
|
+
source_path: args.source_path,
|
|
1932
|
+
resolution: args.resolution,
|
|
1933
|
+
warnings
|
|
1934
|
+
});
|
|
1935
|
+
});
|
|
1936
|
+
}
|
|
1937
|
+
var registerSetupOutput = (server, ctx) => {
|
|
1938
|
+
server.registerTool(
|
|
1939
|
+
"setup_output",
|
|
1940
|
+
{
|
|
1941
|
+
title: "Set up output",
|
|
1942
|
+
description: "Route a TOP to an output: a display window, NDI stream, Syphon/Spout, a recording, or Touch Out.",
|
|
1943
|
+
inputSchema: setupOutputSchema.shape,
|
|
1944
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true }
|
|
1945
|
+
},
|
|
1946
|
+
(args) => setupOutputImpl(ctx, args)
|
|
1947
|
+
);
|
|
1948
|
+
};
|
|
1949
|
+
|
|
1950
|
+
// src/tools/layer1/index.ts
|
|
1951
|
+
var layer1Registrars = [
|
|
1952
|
+
registerCreateFeedbackNetwork,
|
|
1953
|
+
registerCreateGenerativeArt,
|
|
1954
|
+
registerCreateAudioReactive,
|
|
1955
|
+
registerCreateParticleSystem,
|
|
1956
|
+
registerCreateDataVisualization,
|
|
1957
|
+
registerApplyPostProcessing,
|
|
1958
|
+
registerSetupOutput,
|
|
1959
|
+
registerGetPreview,
|
|
1960
|
+
registerDescribeProject,
|
|
1961
|
+
registerCreateVisualSystem
|
|
1962
|
+
];
|
|
1963
|
+
|
|
1964
|
+
// src/tools/layer2/connectNodes.ts
|
|
1965
|
+
import { z as z16 } from "zod";
|
|
1966
|
+
var connectNodesSchema = z16.object({
|
|
1967
|
+
source_path: z16.string().describe("Path of the source node (output side)."),
|
|
1968
|
+
target_path: z16.string().describe("Path of the target node (input side)."),
|
|
1969
|
+
source_output: z16.number().int().nonnegative().default(0),
|
|
1970
|
+
target_input: z16.number().int().nonnegative().default(0)
|
|
1971
|
+
});
|
|
1972
|
+
async function connectNodesImpl(ctx, args) {
|
|
1973
|
+
return guardTd(
|
|
1974
|
+
() => connectNodesViaBridge(
|
|
1975
|
+
ctx.client,
|
|
1976
|
+
args.source_path,
|
|
1977
|
+
args.target_path,
|
|
1978
|
+
args.source_output,
|
|
1979
|
+
args.target_input
|
|
1980
|
+
),
|
|
1981
|
+
(result) => jsonResult(`Connected ${args.source_path} \u2192 ${args.target_path} (via ${result.method}).`, {
|
|
1982
|
+
source: args.source_path,
|
|
1983
|
+
target: args.target_path,
|
|
1984
|
+
source_output: args.source_output,
|
|
1985
|
+
target_input: args.target_input,
|
|
1986
|
+
method: result.method
|
|
1987
|
+
})
|
|
1988
|
+
);
|
|
1989
|
+
}
|
|
1990
|
+
var registerConnectNodes = (server, ctx) => {
|
|
1991
|
+
server.registerTool(
|
|
1992
|
+
"connect_nodes",
|
|
1993
|
+
{
|
|
1994
|
+
title: "Connect two nodes",
|
|
1995
|
+
description: "Wire one node's output into another node's input. Uses the batch endpoint when available, with a Python fallback.",
|
|
1996
|
+
inputSchema: connectNodesSchema.shape,
|
|
1997
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true }
|
|
1998
|
+
},
|
|
1999
|
+
(args) => connectNodesImpl(ctx, args)
|
|
2000
|
+
);
|
|
2001
|
+
};
|
|
2002
|
+
|
|
2003
|
+
// src/tools/layer2/createContainer.ts
|
|
2004
|
+
import { z as z17 } from "zod";
|
|
2005
|
+
var COMP_MAP = {
|
|
2006
|
+
container: "containerCOMP",
|
|
2007
|
+
base: "baseCOMP"
|
|
2008
|
+
};
|
|
2009
|
+
var createContainerSchema = z17.object({
|
|
2010
|
+
parent_path: z17.string().default("/project1").describe("Parent COMP to create the container in."),
|
|
2011
|
+
name: z17.string().optional(),
|
|
2012
|
+
comp_type: z17.enum(["container", "base"]).default("container").describe("'container' (2D panel COMP) or 'base' (generic COMP).")
|
|
2013
|
+
});
|
|
2014
|
+
async function createContainerImpl(ctx, args) {
|
|
2015
|
+
return guardTd(
|
|
2016
|
+
() => ctx.client.createNode({
|
|
2017
|
+
parent_path: args.parent_path,
|
|
2018
|
+
type: COMP_MAP[args.comp_type],
|
|
2019
|
+
name: args.name
|
|
2020
|
+
}),
|
|
2021
|
+
(node) => jsonResult(`Created ${args.comp_type} COMP at ${node.path}.`, { node })
|
|
2022
|
+
);
|
|
2023
|
+
}
|
|
2024
|
+
var registerCreateContainer = (server, ctx) => {
|
|
2025
|
+
server.registerTool(
|
|
2026
|
+
"create_container",
|
|
2027
|
+
{
|
|
2028
|
+
title: "Create container COMP",
|
|
2029
|
+
description: "Create a self-contained COMP to hold a visual system.",
|
|
2030
|
+
inputSchema: createContainerSchema.shape,
|
|
2031
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true }
|
|
2032
|
+
},
|
|
2033
|
+
(args) => createContainerImpl(ctx, args)
|
|
2034
|
+
);
|
|
2035
|
+
};
|
|
2036
|
+
|
|
2037
|
+
// src/tools/layer2/createGlslShader.ts
|
|
2038
|
+
import { z as z18 } from "zod";
|
|
2039
|
+
var UniformSchema = z18.object({
|
|
2040
|
+
name: z18.string(),
|
|
2041
|
+
type: z18.enum(["float", "vec2", "vec3", "vec4", "int", "sampler2D"]),
|
|
2042
|
+
default_value: z18.string().optional()
|
|
2043
|
+
});
|
|
2044
|
+
var createGlslShaderSchema = z18.object({
|
|
2045
|
+
parent_path: z18.string().describe("Parent COMP to create the GLSL TOP inside."),
|
|
2046
|
+
name: z18.string().optional().describe("Name for the GLSL TOP (default 'glsl1')."),
|
|
2047
|
+
fragment_shader: z18.string().min(1).describe("GLSL fragment (pixel) shader source."),
|
|
2048
|
+
vertex_shader: z18.string().optional().describe("Optional GLSL vertex shader source."),
|
|
2049
|
+
uniforms: z18.array(UniformSchema).optional().describe("Optional uniform declarations to best-effort bind on the GLSL TOP."),
|
|
2050
|
+
resolution: z18.enum(["720p", "1080p", "4K", "input"]).default("input")
|
|
2051
|
+
});
|
|
2052
|
+
var RESOLUTIONS2 = {
|
|
2053
|
+
"720p": [1280, 720],
|
|
2054
|
+
"1080p": [1920, 1080],
|
|
2055
|
+
"4K": [3840, 2160]
|
|
2056
|
+
};
|
|
2057
|
+
var q9 = (value) => JSON.stringify(value);
|
|
2058
|
+
async function createGlslShaderImpl(ctx, args) {
|
|
2059
|
+
const desiredName = args.name ?? "glsl1";
|
|
2060
|
+
return guardTd(
|
|
2061
|
+
async () => {
|
|
2062
|
+
const warnings = [];
|
|
2063
|
+
const glsl = await ctx.client.createNode({
|
|
2064
|
+
parent_path: args.parent_path,
|
|
2065
|
+
type: "glslTOP",
|
|
2066
|
+
name: desiredName
|
|
2067
|
+
});
|
|
2068
|
+
const glslName = glsl.name || desiredName;
|
|
2069
|
+
const fragName = `${glslName}_frag`;
|
|
2070
|
+
const frag = await ctx.client.createNode({
|
|
2071
|
+
parent_path: args.parent_path,
|
|
2072
|
+
type: "textDAT",
|
|
2073
|
+
name: fragName
|
|
2074
|
+
});
|
|
2075
|
+
const wiring = [
|
|
2076
|
+
`op(${q9(frag.path)}).text = ${q9(args.fragment_shader)}`,
|
|
2077
|
+
`op(${q9(glsl.path)}).par.pixeldat = ${q9(frag.name || fragName)}`
|
|
2078
|
+
];
|
|
2079
|
+
let vertexPath;
|
|
2080
|
+
if (args.vertex_shader) {
|
|
2081
|
+
const vertName = `${glslName}_vert`;
|
|
2082
|
+
const vert = await ctx.client.createNode({
|
|
2083
|
+
parent_path: args.parent_path,
|
|
2084
|
+
type: "textDAT",
|
|
2085
|
+
name: vertName
|
|
2086
|
+
});
|
|
2087
|
+
vertexPath = vert.path;
|
|
2088
|
+
wiring.push(`op(${q9(vert.path)}).text = ${q9(args.vertex_shader)}`);
|
|
2089
|
+
wiring.push(`op(${q9(glsl.path)}).par.vertexdat = ${q9(vert.name || vertName)}`);
|
|
2090
|
+
}
|
|
2091
|
+
await ctx.client.executePythonScript(wiring.join("\n"), false);
|
|
2092
|
+
if (args.uniforms && args.uniforms.length > 0) {
|
|
2093
|
+
const names = JSON.stringify(args.uniforms.map((u) => u.name));
|
|
2094
|
+
const values = JSON.stringify(args.uniforms.map((u) => u.default_value ?? ""));
|
|
2095
|
+
const bind = [
|
|
2096
|
+
`g = op(${q9(glsl.path)})`,
|
|
2097
|
+
`for i, nm in enumerate(${names}):`,
|
|
2098
|
+
" try: setattr(g.par, 'uniname' + str(i), nm)",
|
|
2099
|
+
" except Exception: pass",
|
|
2100
|
+
`for i, v in enumerate(${values}):`,
|
|
2101
|
+
" try:",
|
|
2102
|
+
" if v != '': setattr(g.par, 'value' + str(i) + 'x', v)",
|
|
2103
|
+
" except Exception: pass"
|
|
2104
|
+
].join("\n");
|
|
2105
|
+
try {
|
|
2106
|
+
await ctx.client.executePythonScript(bind, false);
|
|
2107
|
+
} catch {
|
|
2108
|
+
warnings.push(
|
|
2109
|
+
"Could not auto-bind uniforms; declare them in the shader and set them on the GLSL TOP manually."
|
|
2110
|
+
);
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
if (args.resolution !== "input") {
|
|
2114
|
+
const [width, height] = RESOLUTIONS2[args.resolution];
|
|
2115
|
+
await ctx.client.updateNodeParameters(glsl.path, {
|
|
2116
|
+
outputresolution: "custom",
|
|
2117
|
+
resolutionw: width,
|
|
2118
|
+
resolutionh: height
|
|
2119
|
+
});
|
|
2120
|
+
}
|
|
2121
|
+
return { glsl, fragmentDat: frag.path, vertexDat: vertexPath, warnings };
|
|
2122
|
+
},
|
|
2123
|
+
(result) => jsonResult(`Created GLSL TOP at ${result.glsl.path}.`, result)
|
|
2124
|
+
);
|
|
2125
|
+
}
|
|
2126
|
+
var registerCreateGlslShader = (server, ctx) => {
|
|
2127
|
+
server.registerTool(
|
|
2128
|
+
"create_glsl_shader",
|
|
2129
|
+
{
|
|
2130
|
+
title: "Create GLSL shader",
|
|
2131
|
+
description: "Create a GLSL TOP with a fragment shader (and optional vertex shader) supplied via Text DATs, with optional uniform binding and output resolution.",
|
|
2132
|
+
inputSchema: createGlslShaderSchema.shape,
|
|
2133
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true }
|
|
2134
|
+
},
|
|
2135
|
+
(args) => createGlslShaderImpl(ctx, args)
|
|
2136
|
+
);
|
|
2137
|
+
};
|
|
2138
|
+
|
|
2139
|
+
// src/tools/layer2/createNodeChain.ts
|
|
2140
|
+
import { z as z19 } from "zod";
|
|
2141
|
+
var ChainNodeSchema = z19.object({
|
|
2142
|
+
type: z19.string().describe("Operator type, e.g. 'noiseTOP'."),
|
|
2143
|
+
name: z19.string().optional(),
|
|
2144
|
+
parameters: z19.record(z19.string(), z19.unknown()).optional()
|
|
2145
|
+
});
|
|
2146
|
+
var createNodeChainSchema = z19.object({
|
|
2147
|
+
parent_path: z19.string().describe("Parent COMP to create the chain inside."),
|
|
2148
|
+
nodes: z19.array(ChainNodeSchema).min(1).describe("Ordered list of nodes to create."),
|
|
2149
|
+
connect_sequentially: z19.boolean().default(true).describe("Wire output[0] \u2192 input[0] for each consecutive pair.")
|
|
2150
|
+
});
|
|
2151
|
+
async function createNodeChainImpl(ctx, args) {
|
|
2152
|
+
const created = [];
|
|
2153
|
+
const warnings = [];
|
|
2154
|
+
for (const node of args.nodes) {
|
|
2155
|
+
if (!ctx.knowledge.operatorExists(node.type)) {
|
|
2156
|
+
warnings.push(`Operator type "${node.type}" was not found in the knowledge base.`);
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
for (const node of args.nodes) {
|
|
2160
|
+
try {
|
|
2161
|
+
const ref = await ctx.client.createNode({
|
|
2162
|
+
parent_path: args.parent_path,
|
|
2163
|
+
type: node.type,
|
|
2164
|
+
name: node.name,
|
|
2165
|
+
parameters: node.parameters
|
|
2166
|
+
});
|
|
2167
|
+
created.push({
|
|
2168
|
+
path: ref.path,
|
|
2169
|
+
type: ref.type || node.type,
|
|
2170
|
+
name: ref.name || node.name || ""
|
|
2171
|
+
});
|
|
2172
|
+
} catch (err) {
|
|
2173
|
+
return errorResult(
|
|
2174
|
+
`Stopped after creating ${created.length}/${args.nodes.length} node(s). Failed to create "${node.type}": ${friendlyTdError(err)}. Created so far: ${created.map((c) => c.path).join(", ") || "(none)"}. No nodes were deleted.`
|
|
2175
|
+
);
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
const connections = [];
|
|
2179
|
+
if (args.connect_sequentially && created.length > 1) {
|
|
2180
|
+
for (let i = 0; i < created.length - 1; i++) {
|
|
2181
|
+
const from = created[i];
|
|
2182
|
+
const to = created[i + 1];
|
|
2183
|
+
if (!from || !to) continue;
|
|
2184
|
+
try {
|
|
2185
|
+
const result = await connectNodesViaBridge(ctx.client, from.path, to.path);
|
|
2186
|
+
connections.push({ from: from.path, to: to.path, method: result.method });
|
|
2187
|
+
} catch (err) {
|
|
2188
|
+
warnings.push(`Failed to connect ${from.path} \u2192 ${to.path}: ${friendlyTdError(err)}`);
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
return jsonResult(
|
|
2193
|
+
`Created ${created.length} node(s) and ${connections.length} connection(s) under ${args.parent_path}.`,
|
|
2194
|
+
{ created, connections, warnings }
|
|
2195
|
+
);
|
|
2196
|
+
}
|
|
2197
|
+
var registerCreateNodeChain = (server, ctx) => {
|
|
2198
|
+
server.registerTool(
|
|
2199
|
+
"create_node_chain",
|
|
2200
|
+
{
|
|
2201
|
+
title: "Create node chain",
|
|
2202
|
+
description: "Create multiple nodes and (optionally) connect them in sequence. Returns all created paths; on failure it stops and reports partial progress without deleting anything.",
|
|
2203
|
+
inputSchema: createNodeChainSchema.shape,
|
|
2204
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true }
|
|
2205
|
+
},
|
|
2206
|
+
(args) => createNodeChainImpl(ctx, args)
|
|
2207
|
+
);
|
|
2208
|
+
};
|
|
2209
|
+
|
|
2210
|
+
// src/tools/layer2/createPythonScript.ts
|
|
2211
|
+
import { z as z20 } from "zod";
|
|
2212
|
+
var TYPE_MAP = {
|
|
2213
|
+
text: "textDAT",
|
|
2214
|
+
execute: "executeDAT",
|
|
2215
|
+
script: "scriptDAT"
|
|
2216
|
+
};
|
|
2217
|
+
var createPythonScriptSchema = z20.object({
|
|
2218
|
+
parent_path: z20.string().describe("Parent COMP to create the DAT inside."),
|
|
2219
|
+
name: z20.string().optional(),
|
|
2220
|
+
code: z20.string().min(1).describe("Python source to place in the DAT."),
|
|
2221
|
+
dat_type: z20.enum(["text", "execute", "script"]).default("text").describe("Kind of DAT: 'text' (plain), 'execute' (event hooks), or 'script' (table builder).")
|
|
2222
|
+
});
|
|
2223
|
+
async function createPythonScriptImpl(ctx, args) {
|
|
2224
|
+
return guardTd(
|
|
2225
|
+
async () => {
|
|
2226
|
+
const dat = await ctx.client.createNode({
|
|
2227
|
+
parent_path: args.parent_path,
|
|
2228
|
+
type: TYPE_MAP[args.dat_type],
|
|
2229
|
+
name: args.name
|
|
2230
|
+
});
|
|
2231
|
+
await ctx.client.executePythonScript(
|
|
2232
|
+
`op(${JSON.stringify(dat.path)}).text = ${JSON.stringify(args.code)}`,
|
|
2233
|
+
false
|
|
2234
|
+
);
|
|
2235
|
+
return dat;
|
|
2236
|
+
},
|
|
2237
|
+
(dat) => jsonResult(`Created ${args.dat_type} DAT at ${dat.path}.`, { node: dat })
|
|
2238
|
+
);
|
|
2239
|
+
}
|
|
2240
|
+
var registerCreatePythonScript = (server, ctx) => {
|
|
2241
|
+
server.registerTool(
|
|
2242
|
+
"create_python_script",
|
|
2243
|
+
{
|
|
2244
|
+
title: "Create Python DAT",
|
|
2245
|
+
description: "Create a DAT (text/execute/script) preloaded with Python code.",
|
|
2246
|
+
inputSchema: createPythonScriptSchema.shape,
|
|
2247
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true }
|
|
2248
|
+
},
|
|
2249
|
+
(args) => createPythonScriptImpl(ctx, args)
|
|
2250
|
+
);
|
|
2251
|
+
};
|
|
2252
|
+
|
|
2253
|
+
// src/tools/layer2/duplicateNetwork.ts
|
|
2254
|
+
import { z as z21 } from "zod";
|
|
2255
|
+
var q10 = (value) => JSON.stringify(value);
|
|
2256
|
+
var duplicateNetworkSchema = z21.object({
|
|
2257
|
+
source_path: z21.string().describe("Path of the node/COMP to duplicate."),
|
|
2258
|
+
name: z21.string().optional().describe("Name for the copy (auto-generated if omitted)."),
|
|
2259
|
+
parent_path: z21.string().optional().describe("Where to place the copy (defaults to the source's parent).")
|
|
2260
|
+
});
|
|
2261
|
+
async function duplicateNetworkImpl(ctx, args) {
|
|
2262
|
+
const script = [
|
|
2263
|
+
`src = op(${q10(args.source_path)})`,
|
|
2264
|
+
`if src is None: raise Exception('source not found: ' + ${q10(args.source_path)})`,
|
|
2265
|
+
args.parent_path ? `parent = op(${q10(args.parent_path)})` : "parent = src.parent()",
|
|
2266
|
+
"if parent is None: raise Exception('parent not found')",
|
|
2267
|
+
args.name ? `new = parent.copy(src, name=${q10(args.name)})` : "new = parent.copy(src)",
|
|
2268
|
+
"result = new.path"
|
|
2269
|
+
].join("\n");
|
|
2270
|
+
return guardTd(
|
|
2271
|
+
() => ctx.client.executePythonScript(script, true),
|
|
2272
|
+
(res) => jsonResult(`Duplicated ${args.source_path} \u2192 ${res.result ?? "(see stdout)"}.`, {
|
|
2273
|
+
source: args.source_path,
|
|
2274
|
+
copy: res.result
|
|
2275
|
+
})
|
|
2276
|
+
);
|
|
2277
|
+
}
|
|
2278
|
+
var registerDuplicateNetwork = (server, ctx) => {
|
|
2279
|
+
server.registerTool(
|
|
2280
|
+
"duplicate_network",
|
|
2281
|
+
{
|
|
2282
|
+
title: "Duplicate a network",
|
|
2283
|
+
description: "Copy a node or whole COMP (and its contents) to a new node, optionally into another parent.",
|
|
2284
|
+
inputSchema: duplicateNetworkSchema.shape,
|
|
2285
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true }
|
|
2286
|
+
},
|
|
2287
|
+
(args) => duplicateNetworkImpl(ctx, args)
|
|
2288
|
+
);
|
|
2289
|
+
};
|
|
2290
|
+
|
|
2291
|
+
// src/tools/layer2/setParametersBatch.ts
|
|
2292
|
+
import { z as z22 } from "zod";
|
|
2293
|
+
var UpdateSchema = z22.object({
|
|
2294
|
+
path: z22.string(),
|
|
2295
|
+
parameters: z22.record(z22.string(), z22.unknown())
|
|
2296
|
+
});
|
|
2297
|
+
var setParametersBatchSchema = z22.object({
|
|
2298
|
+
updates: z22.array(UpdateSchema).min(1).describe(
|
|
2299
|
+
"List of { path, parameters } updates sent in one batch request (per-update results; not transactional \u2014 a failed update does not roll back the others)."
|
|
2300
|
+
)
|
|
2301
|
+
});
|
|
2302
|
+
async function setParametersBatchImpl(ctx, args) {
|
|
2303
|
+
return guardTd(
|
|
2304
|
+
() => ctx.client.batch(
|
|
2305
|
+
args.updates.map((update) => ({
|
|
2306
|
+
action: "update",
|
|
2307
|
+
path: update.path,
|
|
2308
|
+
parameters: update.parameters
|
|
2309
|
+
}))
|
|
2310
|
+
),
|
|
2311
|
+
(result) => jsonResult(`Applied ${args.updates.length} parameter update(s) in one batch.`, result)
|
|
2312
|
+
);
|
|
2313
|
+
}
|
|
2314
|
+
var registerSetParametersBatch = (server, ctx) => {
|
|
2315
|
+
server.registerTool(
|
|
2316
|
+
"set_parameters_batch",
|
|
2317
|
+
{
|
|
2318
|
+
title: "Set parameters (batch)",
|
|
2319
|
+
description: "Update parameters on multiple nodes in a single batch request. Each update reports its own success; a failure does not roll back the others.",
|
|
2320
|
+
inputSchema: setParametersBatchSchema.shape,
|
|
2321
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true }
|
|
2322
|
+
},
|
|
2323
|
+
(args) => setParametersBatchImpl(ctx, args)
|
|
2324
|
+
);
|
|
2325
|
+
};
|
|
2326
|
+
|
|
2327
|
+
// src/tools/layer2/index.ts
|
|
2328
|
+
var layer2Registrars = [
|
|
2329
|
+
registerCreateNodeChain,
|
|
2330
|
+
registerConnectNodes,
|
|
2331
|
+
registerCreateGlslShader,
|
|
2332
|
+
registerCreatePythonScript,
|
|
2333
|
+
registerSetParametersBatch,
|
|
2334
|
+
registerCreateContainer,
|
|
2335
|
+
registerDuplicateNetwork
|
|
2336
|
+
];
|
|
2337
|
+
|
|
2338
|
+
// src/tools/layer3/compareTdNodes.ts
|
|
2339
|
+
import { z as z23 } from "zod";
|
|
2340
|
+
var compareTdNodesSchema = z23.object({
|
|
2341
|
+
path_a: z23.string().describe("First node path."),
|
|
2342
|
+
path_b: z23.string().describe("Second node path."),
|
|
2343
|
+
only_diff: z23.boolean().default(true).describe("Return only the parameters that differ (true) or also list the identical ones.")
|
|
2344
|
+
});
|
|
2345
|
+
var compareTdNodesOutputSchema = z23.object({
|
|
2346
|
+
a: z23.string(),
|
|
2347
|
+
b: z23.string(),
|
|
2348
|
+
type_a: z23.string(),
|
|
2349
|
+
type_b: z23.string(),
|
|
2350
|
+
type_match: z23.boolean(),
|
|
2351
|
+
differing_count: z23.number(),
|
|
2352
|
+
same_count: z23.number(),
|
|
2353
|
+
differing: z23.array(z23.object({ param: z23.string(), a: z23.unknown(), b: z23.unknown() })),
|
|
2354
|
+
identical: z23.array(z23.string()).optional()
|
|
2355
|
+
});
|
|
2356
|
+
async function compareTdNodesImpl(ctx, args) {
|
|
2357
|
+
return guardTd(
|
|
2358
|
+
async () => {
|
|
2359
|
+
const [a, b] = await Promise.all([
|
|
2360
|
+
ctx.client.getNode(args.path_a),
|
|
2361
|
+
ctx.client.getNode(args.path_b)
|
|
2362
|
+
]);
|
|
2363
|
+
return { a, b };
|
|
2364
|
+
},
|
|
2365
|
+
({ a, b }) => {
|
|
2366
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(a.parameters), ...Object.keys(b.parameters)]);
|
|
2367
|
+
const differing = [];
|
|
2368
|
+
const identical = [];
|
|
2369
|
+
for (const key of [...keys].sort()) {
|
|
2370
|
+
const va = a.parameters[key];
|
|
2371
|
+
const vb = b.parameters[key];
|
|
2372
|
+
if (JSON.stringify(va) === JSON.stringify(vb)) identical.push(key);
|
|
2373
|
+
else differing.push({ param: key, a: va, b: vb });
|
|
2374
|
+
}
|
|
2375
|
+
const data = {
|
|
2376
|
+
a: a.path,
|
|
2377
|
+
b: b.path,
|
|
2378
|
+
type_a: a.type,
|
|
2379
|
+
type_b: b.type,
|
|
2380
|
+
type_match: a.type === b.type,
|
|
2381
|
+
differing_count: differing.length,
|
|
2382
|
+
same_count: identical.length,
|
|
2383
|
+
differing
|
|
2384
|
+
};
|
|
2385
|
+
if (!args.only_diff) data.identical = identical;
|
|
2386
|
+
return structuredResult(
|
|
2387
|
+
`${differing.length} differing parameter(s) between ${a.path} and ${b.path}${a.type === b.type ? "" : ` (types differ: ${a.type} vs ${b.type})`}.`,
|
|
2388
|
+
data
|
|
2389
|
+
);
|
|
2390
|
+
}
|
|
2391
|
+
);
|
|
2392
|
+
}
|
|
2393
|
+
var registerCompareTdNodes = (server, ctx) => {
|
|
2394
|
+
server.registerTool(
|
|
2395
|
+
"compare_td_nodes",
|
|
2396
|
+
{
|
|
2397
|
+
title: "Compare two nodes",
|
|
2398
|
+
description: "Diff the parameters of two nodes, returning only the values that differ (by default). Useful for aligning settings across similar operators.",
|
|
2399
|
+
inputSchema: compareTdNodesSchema.shape,
|
|
2400
|
+
outputSchema: compareTdNodesOutputSchema.shape,
|
|
2401
|
+
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
2402
|
+
},
|
|
2403
|
+
(args) => compareTdNodesImpl(ctx, args)
|
|
2404
|
+
);
|
|
2405
|
+
};
|
|
2406
|
+
|
|
2407
|
+
// src/tools/layer3/createTdNode.ts
|
|
2408
|
+
import { z as z24 } from "zod";
|
|
2409
|
+
var createTdNodeSchema = z24.object({
|
|
2410
|
+
parent_path: z24.string().default("/project1").describe("Parent COMP path to create the node inside."),
|
|
2411
|
+
type: z24.string().describe("Operator type string, e.g. 'noiseTOP', 'feedbackTOP', 'nullTOP', 'constantCHOP'."),
|
|
2412
|
+
name: z24.string().optional().describe("Optional node name (auto-generated if omitted)."),
|
|
2413
|
+
parameters: z24.record(z24.string(), z24.unknown()).optional().describe("Optional initial parameter overrides as key\u2192value pairs.")
|
|
2414
|
+
});
|
|
2415
|
+
async function createTdNodeImpl(ctx, args) {
|
|
2416
|
+
const warnings = [];
|
|
2417
|
+
if (!ctx.knowledge.operatorExists(args.type)) {
|
|
2418
|
+
const suggestions = ctx.knowledge.searchOperators(args.type, 3).map((s) => s.name);
|
|
2419
|
+
warnings.push(
|
|
2420
|
+
`Operator type "${args.type}" was not found in the knowledge base.${suggestions.length ? ` Did you mean: ${suggestions.join(", ")}?` : ""}`
|
|
2421
|
+
);
|
|
2422
|
+
}
|
|
2423
|
+
return guardTd(
|
|
2424
|
+
() => ctx.client.createNode({
|
|
2425
|
+
parent_path: args.parent_path,
|
|
2426
|
+
type: args.type,
|
|
2427
|
+
name: args.name,
|
|
2428
|
+
parameters: args.parameters
|
|
2429
|
+
}),
|
|
2430
|
+
(node) => jsonResult(`Created ${node.type || args.type} at ${node.path}.`, { node, warnings })
|
|
2431
|
+
);
|
|
2432
|
+
}
|
|
2433
|
+
var registerCreateTdNode = (server, ctx) => {
|
|
2434
|
+
server.registerTool(
|
|
2435
|
+
"create_td_node",
|
|
2436
|
+
{
|
|
2437
|
+
title: "Create TouchDesigner node",
|
|
2438
|
+
description: "Create a single operator (node) inside a parent COMP. Validates the operator type against the knowledge base and warns (without blocking) on unknown types.",
|
|
2439
|
+
inputSchema: createTdNodeSchema.shape,
|
|
2440
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true }
|
|
2441
|
+
},
|
|
2442
|
+
(args) => createTdNodeImpl(ctx, args)
|
|
2443
|
+
);
|
|
2444
|
+
};
|
|
2445
|
+
|
|
2446
|
+
// src/tools/layer3/deleteTdNode.ts
|
|
2447
|
+
import { z as z25 } from "zod";
|
|
2448
|
+
var deleteTdNodeSchema = z25.object({
|
|
2449
|
+
path: z25.string().describe("Full path of the node to delete, e.g. '/project1/noise1'.")
|
|
2450
|
+
});
|
|
2451
|
+
async function deleteTdNodeImpl(ctx, args) {
|
|
2452
|
+
return guardTd(
|
|
2453
|
+
() => ctx.client.deleteNode(args.path),
|
|
2454
|
+
(result) => jsonResult(`Deleted ${result.deleted}.`, result)
|
|
2455
|
+
);
|
|
2456
|
+
}
|
|
2457
|
+
var registerDeleteTdNode = (server, ctx) => {
|
|
2458
|
+
server.registerTool(
|
|
2459
|
+
"delete_td_node",
|
|
2460
|
+
{
|
|
2461
|
+
title: "Delete TouchDesigner node",
|
|
2462
|
+
description: "Delete a single node by path. Destructive \u2014 only call this when the user explicitly asks to remove a node.",
|
|
2463
|
+
inputSchema: deleteTdNodeSchema.shape,
|
|
2464
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true }
|
|
2465
|
+
},
|
|
2466
|
+
(args) => deleteTdNodeImpl(ctx, args)
|
|
2467
|
+
);
|
|
2468
|
+
};
|
|
2469
|
+
|
|
2470
|
+
// src/tools/layer3/execNodeMethod.ts
|
|
2471
|
+
import { z as z26 } from "zod";
|
|
2472
|
+
var execNodeMethodSchema = z26.object({
|
|
2473
|
+
path: z26.string().describe("Full path of the node to call the method on."),
|
|
2474
|
+
method: z26.string().describe("Method name to call, e.g. 'cook', 'par', 'destroy', 'copy'."),
|
|
2475
|
+
args: z26.array(z26.unknown()).default([]).describe("Positional arguments."),
|
|
2476
|
+
kwargs: z26.record(z26.string(), z26.unknown()).default({}).describe("Keyword arguments.")
|
|
2477
|
+
});
|
|
2478
|
+
async function execNodeMethodImpl(ctx, args) {
|
|
2479
|
+
return guardTd(
|
|
2480
|
+
() => ctx.client.execNodeMethod(args.path, args.method, args.args, args.kwargs),
|
|
2481
|
+
(result) => jsonResult(`Called ${args.path}.${args.method}().`, result)
|
|
2482
|
+
);
|
|
2483
|
+
}
|
|
2484
|
+
var registerExecNodeMethod = (server, ctx) => {
|
|
2485
|
+
if (ctx.allowRawPython === false) return;
|
|
2486
|
+
server.registerTool(
|
|
2487
|
+
"exec_node_method",
|
|
2488
|
+
{
|
|
2489
|
+
title: "Call node method",
|
|
2490
|
+
description: "Escape hatch \u2014 invoke an arbitrary Python method on a node (operator). Prefer structured tools where one exists; use this for operations they don't cover (e.g. .cook(), .copy(), .destroy()).",
|
|
2491
|
+
inputSchema: execNodeMethodSchema.shape,
|
|
2492
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true }
|
|
2493
|
+
},
|
|
2494
|
+
(args) => execNodeMethodImpl(ctx, args)
|
|
2495
|
+
);
|
|
2496
|
+
};
|
|
2497
|
+
|
|
2498
|
+
// src/tools/layer3/executePythonScript.ts
|
|
2499
|
+
import { z as z27 } from "zod";
|
|
2500
|
+
var executePythonScriptSchema = z27.object({
|
|
2501
|
+
script: z27.string().min(1).describe(
|
|
2502
|
+
"Python source to execute inside TouchDesigner (runs in the TD process, not locally)."
|
|
2503
|
+
),
|
|
2504
|
+
return_output: z27.boolean().default(true).describe("Capture stdout / the value of the last expression and return it.")
|
|
2505
|
+
});
|
|
2506
|
+
async function executePythonScriptImpl(ctx, args) {
|
|
2507
|
+
return guardTd(
|
|
2508
|
+
() => ctx.client.executePythonScript(args.script, args.return_output),
|
|
2509
|
+
(result) => jsonResult("Python executed in TouchDesigner.", result)
|
|
2510
|
+
);
|
|
2511
|
+
}
|
|
2512
|
+
var registerExecutePythonScript = (server, ctx) => {
|
|
2513
|
+
if (ctx.allowRawPython === false) return;
|
|
2514
|
+
server.registerTool(
|
|
2515
|
+
"execute_python_script",
|
|
2516
|
+
{
|
|
2517
|
+
title: "Execute Python in TouchDesigner",
|
|
2518
|
+
description: "Escape hatch \u2014 run an arbitrary Python script inside the TouchDesigner process. Prefer the structured tools (find_td_nodes, get_td_node_parameters, update_td_node_parameters, summarize_td_errors, snapshot_td_graph, \u2026); reach for this only when no structured tool can express the operation. Code runs in TD only, never on the local machine.",
|
|
2519
|
+
inputSchema: executePythonScriptSchema.shape,
|
|
2520
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true }
|
|
2521
|
+
},
|
|
2522
|
+
(args) => executePythonScriptImpl(ctx, args)
|
|
2523
|
+
);
|
|
2524
|
+
};
|
|
2525
|
+
|
|
2526
|
+
// src/tools/layer3/findTdNodes.ts
|
|
2527
|
+
import { z as z29 } from "zod";
|
|
2528
|
+
|
|
2529
|
+
// src/td-client/validators.ts
|
|
2530
|
+
import { z as z28 } from "zod";
|
|
2531
|
+
var ApiEnvelopeSchema = z28.object({
|
|
2532
|
+
ok: z28.boolean(),
|
|
2533
|
+
data: z28.unknown().optional(),
|
|
2534
|
+
error: z28.object({ code: z28.string().optional(), message: z28.string() }).optional()
|
|
2535
|
+
});
|
|
2536
|
+
var NodeRefSchema = z28.object({
|
|
2537
|
+
path: z28.string(),
|
|
2538
|
+
type: z28.string().default(""),
|
|
2539
|
+
name: z28.string().default("")
|
|
2540
|
+
});
|
|
2541
|
+
var NodeDetailSchema = NodeRefSchema.extend({
|
|
2542
|
+
parameters: z28.record(z28.string(), z28.unknown()).default({}),
|
|
2543
|
+
inputs: z28.array(z28.string()).optional(),
|
|
2544
|
+
outputs: z28.array(z28.string()).optional(),
|
|
2545
|
+
family: z28.string().optional(),
|
|
2546
|
+
errors: z28.array(z28.string()).optional()
|
|
2547
|
+
});
|
|
2548
|
+
var NodeListSchema = z28.object({ nodes: z28.array(NodeRefSchema).default([]) });
|
|
2549
|
+
var InfoSchema = z28.object({
|
|
2550
|
+
td_version: z28.string().optional(),
|
|
2551
|
+
python_version: z28.string().optional(),
|
|
2552
|
+
build: z28.string().optional(),
|
|
2553
|
+
bridge_version: z28.string().optional(),
|
|
2554
|
+
project: z28.string().optional()
|
|
2555
|
+
});
|
|
2556
|
+
var NodeErrorSchema = z28.object({
|
|
2557
|
+
path: z28.string(),
|
|
2558
|
+
message: z28.string(),
|
|
2559
|
+
type: z28.string().optional()
|
|
2560
|
+
});
|
|
2561
|
+
var NodeErrorsSchema = z28.object({ errors: z28.array(NodeErrorSchema).default([]) });
|
|
2562
|
+
var PreviewSchema = z28.object({
|
|
2563
|
+
path: z28.string(),
|
|
2564
|
+
width: z28.number().int().positive(),
|
|
2565
|
+
height: z28.number().int().positive(),
|
|
2566
|
+
format: z28.string().default("png"),
|
|
2567
|
+
base64: z28.string()
|
|
2568
|
+
});
|
|
2569
|
+
var ExecResultSchema = z28.object({
|
|
2570
|
+
result: z28.unknown().optional(),
|
|
2571
|
+
stdout: z28.string().optional(),
|
|
2572
|
+
printed: z28.array(z28.string()).optional()
|
|
2573
|
+
});
|
|
2574
|
+
var MethodResultSchema = z28.object({ result: z28.unknown() });
|
|
2575
|
+
var DeleteResultSchema = z28.object({ deleted: z28.string() });
|
|
2576
|
+
var ConnectionSchema = z28.object({
|
|
2577
|
+
source_path: z28.string(),
|
|
2578
|
+
source_output: z28.number().int().default(0),
|
|
2579
|
+
target_path: z28.string(),
|
|
2580
|
+
target_input: z28.number().int().default(0)
|
|
2581
|
+
});
|
|
2582
|
+
var TopologySchema = z28.object({
|
|
2583
|
+
nodes: z28.array(NodeRefSchema).default([]),
|
|
2584
|
+
connections: z28.array(ConnectionSchema).default([])
|
|
2585
|
+
});
|
|
2586
|
+
var PerformanceSchema = z28.object({
|
|
2587
|
+
nodes: z28.array(
|
|
2588
|
+
z28.object({
|
|
2589
|
+
path: z28.string(),
|
|
2590
|
+
cook_time_ms: z28.number().default(0),
|
|
2591
|
+
cook_count: z28.number().optional()
|
|
2592
|
+
})
|
|
2593
|
+
).default([]),
|
|
2594
|
+
total_cook_time_ms: z28.number().optional(),
|
|
2595
|
+
gpu_memory_mb: z28.number().optional()
|
|
2596
|
+
});
|
|
2597
|
+
var BatchOpResultSchema = z28.object({
|
|
2598
|
+
action: z28.string(),
|
|
2599
|
+
ok: z28.boolean(),
|
|
2600
|
+
path: z28.string().optional(),
|
|
2601
|
+
data: z28.unknown().optional(),
|
|
2602
|
+
error: z28.string().optional()
|
|
2603
|
+
});
|
|
2604
|
+
var BatchResultSchema = z28.object({ results: z28.array(BatchOpResultSchema).default([]) });
|
|
2605
|
+
var CreateNodeInputSchema = z28.object({
|
|
2606
|
+
parent_path: z28.string(),
|
|
2607
|
+
type: z28.string(),
|
|
2608
|
+
name: z28.string().optional(),
|
|
2609
|
+
parameters: z28.record(z28.string(), z28.unknown()).optional()
|
|
2610
|
+
});
|
|
2611
|
+
var BatchOperationSchema = z28.discriminatedUnion("action", [
|
|
2612
|
+
z28.object({
|
|
2613
|
+
action: z28.literal("create"),
|
|
2614
|
+
parent_path: z28.string(),
|
|
2615
|
+
type: z28.string(),
|
|
2616
|
+
name: z28.string().optional(),
|
|
2617
|
+
parameters: z28.record(z28.string(), z28.unknown()).optional()
|
|
2618
|
+
}),
|
|
2619
|
+
z28.object({
|
|
2620
|
+
action: z28.literal("update"),
|
|
2621
|
+
path: z28.string(),
|
|
2622
|
+
parameters: z28.record(z28.string(), z28.unknown())
|
|
2623
|
+
}),
|
|
2624
|
+
z28.object({ action: z28.literal("delete"), path: z28.string() }),
|
|
2625
|
+
z28.object({
|
|
2626
|
+
action: z28.literal("connect"),
|
|
2627
|
+
source_path: z28.string(),
|
|
2628
|
+
target_path: z28.string(),
|
|
2629
|
+
source_output: z28.number().int().default(0),
|
|
2630
|
+
target_input: z28.number().int().default(0)
|
|
2631
|
+
})
|
|
2632
|
+
]);
|
|
2633
|
+
|
|
2634
|
+
// src/tools/layer3/nodeMatch.ts
|
|
2635
|
+
function globToRegExp(pattern) {
|
|
2636
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
2637
|
+
return new RegExp(escaped, "i");
|
|
2638
|
+
}
|
|
2639
|
+
function parentOf(path) {
|
|
2640
|
+
const idx = path.lastIndexOf("/");
|
|
2641
|
+
return idx <= 0 ? "/" : path.slice(0, idx);
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2644
|
+
// src/tools/layer3/findTdNodes.ts
|
|
2645
|
+
var findTdNodesSchema = z29.object({
|
|
2646
|
+
parent_path: z29.string().default("/project1").describe("Where to search from."),
|
|
2647
|
+
pattern: z29.string().optional().describe("Case-insensitive name/path filter with '*' wildcards (e.g. 'text*', '*noise*')."),
|
|
2648
|
+
type: z29.string().optional().describe("Case-insensitive operator-type substring (e.g. 'TOP', 'noise')."),
|
|
2649
|
+
recursive: z29.boolean().default(true).describe("Search the whole sub-network (true) or only direct children (false)."),
|
|
2650
|
+
path_only: z29.boolean().default(false).describe("Return only matching paths."),
|
|
2651
|
+
limit: z29.number().int().positive().default(50).describe("Max matches to return.")
|
|
2652
|
+
});
|
|
2653
|
+
var findTdNodesOutputSchema = z29.object({
|
|
2654
|
+
parent_path: z29.string(),
|
|
2655
|
+
recursive: z29.boolean(),
|
|
2656
|
+
count: z29.number(),
|
|
2657
|
+
truncated: z29.boolean(),
|
|
2658
|
+
paths: z29.array(z29.string()).optional(),
|
|
2659
|
+
matches: z29.array(NodeRefSchema).optional()
|
|
2660
|
+
});
|
|
2661
|
+
async function findTdNodesImpl(ctx, args) {
|
|
2662
|
+
const fetch2 = args.recursive ? async () => (await ctx.client.getNetworkTopology(args.parent_path, true)).nodes : async () => (await ctx.client.getNodes(args.parent_path)).nodes;
|
|
2663
|
+
return guardTd(fetch2, (allNodes) => {
|
|
2664
|
+
let nodes = allNodes;
|
|
2665
|
+
if (args.pattern) {
|
|
2666
|
+
const re = globToRegExp(args.pattern);
|
|
2667
|
+
nodes = nodes.filter((n) => re.test(n.name) || re.test(n.path));
|
|
2668
|
+
}
|
|
2669
|
+
if (args.type) {
|
|
2670
|
+
const t = args.type.toLowerCase();
|
|
2671
|
+
nodes = nodes.filter((n) => n.type.toLowerCase().includes(t));
|
|
2672
|
+
}
|
|
2673
|
+
const count = nodes.length;
|
|
2674
|
+
const truncated = count > args.limit;
|
|
2675
|
+
nodes = nodes.slice(0, args.limit);
|
|
2676
|
+
const summary = `${count} match(es) under ${args.parent_path}${truncated ? ` (showing ${args.limit})` : ""}.`;
|
|
2677
|
+
const base = { parent_path: args.parent_path, recursive: args.recursive, count, truncated };
|
|
2678
|
+
return args.path_only ? structuredResult(summary, { ...base, paths: nodes.map((n) => n.path) }) : structuredResult(summary, { ...base, matches: nodes });
|
|
2679
|
+
});
|
|
2680
|
+
}
|
|
2681
|
+
var registerFindTdNodes = (server, ctx) => {
|
|
2682
|
+
server.registerTool(
|
|
2683
|
+
"find_td_nodes",
|
|
2684
|
+
{
|
|
2685
|
+
title: "Find TouchDesigner nodes",
|
|
2686
|
+
description: "Search a network for nodes by name pattern and/or operator type. Recursive by default; pass path_only:true for a compact path list. Prefer this over get_td_nodes when you are looking for specific nodes.",
|
|
2687
|
+
inputSchema: findTdNodesSchema.shape,
|
|
2688
|
+
outputSchema: findTdNodesOutputSchema.shape,
|
|
2689
|
+
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
2690
|
+
},
|
|
2691
|
+
(args) => findTdNodesImpl(ctx, args)
|
|
2692
|
+
);
|
|
2693
|
+
};
|
|
2694
|
+
|
|
2695
|
+
// src/tools/layer3/getModuleHelp.ts
|
|
2696
|
+
import { z as z30 } from "zod";
|
|
2697
|
+
var getModuleHelpSchema = z30.object({
|
|
2698
|
+
name: z30.string().describe("Class or module name to get help for, e.g. 'OP', 'App', 'Project'.")
|
|
2699
|
+
});
|
|
2700
|
+
function getModuleHelpImpl(ctx, args) {
|
|
2701
|
+
const cls = ctx.knowledge.getPythonClass(args.name);
|
|
2702
|
+
if (!cls) {
|
|
2703
|
+
const suggestions = ctx.knowledge.listPythonClasses().filter((c) => c.className.toLowerCase().includes(args.name.toLowerCase())).slice(0, 5).map((c) => c.className);
|
|
2704
|
+
return jsonResult(`No help found for "${args.name}".`, { found: false, suggestions });
|
|
2705
|
+
}
|
|
2706
|
+
const lines = [`# ${cls.displayName || cls.className}`, ""];
|
|
2707
|
+
if (cls.description) lines.push(cls.description, "");
|
|
2708
|
+
if (cls.members && cls.members.length > 0) {
|
|
2709
|
+
lines.push("## Members");
|
|
2710
|
+
for (const m of cls.members) {
|
|
2711
|
+
const ro = m.readOnly ? " (read-only)" : "";
|
|
2712
|
+
lines.push(`- ${m.name}${m.returnType ? `: ${m.returnType}` : ""}${ro}`);
|
|
2713
|
+
}
|
|
2714
|
+
lines.push("");
|
|
2715
|
+
}
|
|
2716
|
+
if (cls.methods && cls.methods.length > 0) {
|
|
2717
|
+
lines.push("## Methods");
|
|
2718
|
+
for (const method of cls.methods) {
|
|
2719
|
+
const sig = method.signature || method.name || "";
|
|
2720
|
+
lines.push(`- ${sig}${method.returns ? ` -> ${method.returns}` : ""}`);
|
|
2721
|
+
}
|
|
2722
|
+
}
|
|
2723
|
+
return textResult(lines.join("\n"));
|
|
2724
|
+
}
|
|
2725
|
+
var registerGetModuleHelp = (server, ctx) => {
|
|
2726
|
+
server.registerTool(
|
|
2727
|
+
"get_module_help",
|
|
2728
|
+
{
|
|
2729
|
+
title: "Get module/class help",
|
|
2730
|
+
description: "Human-readable help (description, members, method signatures) for a TouchDesigner Python class or module, from the knowledge base.",
|
|
2731
|
+
inputSchema: getModuleHelpSchema.shape,
|
|
2732
|
+
annotations: { readOnlyHint: true, openWorldHint: false }
|
|
2733
|
+
},
|
|
2734
|
+
(args) => getModuleHelpImpl(ctx, args)
|
|
2735
|
+
);
|
|
2736
|
+
};
|
|
2737
|
+
|
|
2738
|
+
// src/tools/layer3/getTdClassDetails.ts
|
|
2739
|
+
import { z as z31 } from "zod";
|
|
2740
|
+
var getTdClassDetailsSchema = z31.object({
|
|
2741
|
+
class_name: z31.string().describe("Python class name, e.g. 'OP', 'TOP', 'App', 'CHOP'.")
|
|
2742
|
+
});
|
|
2743
|
+
function getTdClassDetailsImpl(ctx, args) {
|
|
2744
|
+
const cls = ctx.knowledge.getPythonClass(args.class_name);
|
|
2745
|
+
if (!cls) {
|
|
2746
|
+
const suggestions = ctx.knowledge.listPythonClasses().filter((c) => c.className.toLowerCase().includes(args.class_name.toLowerCase())).slice(0, 5).map((c) => c.className);
|
|
2747
|
+
return structuredResult(`Python class "${args.class_name}" not found.`, {
|
|
2748
|
+
found: false,
|
|
2749
|
+
suggestions
|
|
2750
|
+
});
|
|
2751
|
+
}
|
|
2752
|
+
return structuredResult(
|
|
2753
|
+
`${cls.className} \u2014 ${cls.methods?.length ?? 0} methods, ${cls.members?.length ?? 0} members.`,
|
|
2754
|
+
cls
|
|
2755
|
+
);
|
|
2756
|
+
}
|
|
2757
|
+
var registerGetTdClassDetails = (server, ctx) => {
|
|
2758
|
+
server.registerTool(
|
|
2759
|
+
"get_td_class_details",
|
|
2760
|
+
{
|
|
2761
|
+
title: "Get TD Python class details",
|
|
2762
|
+
description: "Full documentation for one TouchDesigner Python class (members + methods) from the knowledge base.",
|
|
2763
|
+
inputSchema: getTdClassDetailsSchema.shape,
|
|
2764
|
+
annotations: { readOnlyHint: true, openWorldHint: false }
|
|
2765
|
+
},
|
|
2766
|
+
(args) => getTdClassDetailsImpl(ctx, args)
|
|
2767
|
+
);
|
|
2768
|
+
};
|
|
2769
|
+
|
|
2770
|
+
// src/tools/layer3/getTdClasses.ts
|
|
2771
|
+
import { z as z32 } from "zod";
|
|
2772
|
+
var getTdClassesSchema = z32.object({
|
|
2773
|
+
filter: z32.string().optional().describe("Optional case-insensitive substring to filter class names by.")
|
|
2774
|
+
});
|
|
2775
|
+
function getTdClassesImpl(ctx, args) {
|
|
2776
|
+
let classes = ctx.knowledge.listPythonClasses();
|
|
2777
|
+
if (args.filter) {
|
|
2778
|
+
const needle = args.filter.toLowerCase();
|
|
2779
|
+
classes = classes.filter(
|
|
2780
|
+
(c) => c.className.toLowerCase().includes(needle) || c.displayName.toLowerCase().includes(needle)
|
|
2781
|
+
);
|
|
2782
|
+
}
|
|
2783
|
+
return structuredResult(`Found ${classes.length} TouchDesigner Python API class(es).`, {
|
|
2784
|
+
classes
|
|
2785
|
+
});
|
|
2786
|
+
}
|
|
2787
|
+
var registerGetTdClasses = (server, ctx) => {
|
|
2788
|
+
server.registerTool(
|
|
2789
|
+
"get_td_classes",
|
|
2790
|
+
{
|
|
2791
|
+
title: "List TD Python classes",
|
|
2792
|
+
description: "List TouchDesigner Python API classes from the embedded knowledge base (works offline). Optionally filter by name.",
|
|
2793
|
+
inputSchema: getTdClassesSchema.shape,
|
|
2794
|
+
annotations: { readOnlyHint: true, openWorldHint: false }
|
|
2795
|
+
},
|
|
2796
|
+
(args) => getTdClassesImpl(ctx, args)
|
|
2797
|
+
);
|
|
2798
|
+
};
|
|
2799
|
+
|
|
2800
|
+
// src/tools/layer3/getTdInfo.ts
|
|
2801
|
+
var DESCRIPTION = "Health check + TouchDesigner server info. Returns TD/Python version and bridge status when connected, plus the embedded knowledge-base stats. Use this first to confirm the bridge is reachable.";
|
|
2802
|
+
async function getTdInfoImpl(ctx) {
|
|
2803
|
+
const knowledge = ctx.knowledge.stats();
|
|
2804
|
+
try {
|
|
2805
|
+
const info = await ctx.client.getInfo();
|
|
2806
|
+
return jsonResult("TouchDesigner is connected.", {
|
|
2807
|
+
connected: true,
|
|
2808
|
+
endpoint: ctx.client.endpoint,
|
|
2809
|
+
touchdesigner: info,
|
|
2810
|
+
knowledge
|
|
2811
|
+
});
|
|
2812
|
+
} catch (err) {
|
|
2813
|
+
return jsonResult("TouchDesigner is not reachable (the server is still running).", {
|
|
2814
|
+
connected: false,
|
|
2815
|
+
endpoint: ctx.client.endpoint,
|
|
2816
|
+
reason: friendlyTdError(err),
|
|
2817
|
+
knowledge
|
|
2818
|
+
});
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
var registerGetTdInfo = (server, ctx) => {
|
|
2822
|
+
server.registerTool(
|
|
2823
|
+
"get_td_info",
|
|
2824
|
+
{
|
|
2825
|
+
title: "Get TouchDesigner info",
|
|
2826
|
+
description: DESCRIPTION,
|
|
2827
|
+
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
2828
|
+
},
|
|
2829
|
+
() => getTdInfoImpl(ctx)
|
|
2830
|
+
);
|
|
2831
|
+
};
|
|
2832
|
+
|
|
2833
|
+
// src/tools/layer3/getTdNodeErrors.ts
|
|
2834
|
+
import { z as z33 } from "zod";
|
|
2835
|
+
var getTdNodeErrorsSchema = z33.object({
|
|
2836
|
+
path: z33.string().describe("Full path of the node (or network root) to check for errors."),
|
|
2837
|
+
recursive: z33.boolean().default(false).describe("If true, check the whole network under `path`; otherwise just that node."),
|
|
2838
|
+
summary: z33.boolean().default(false).describe("Return only counts grouped by error type instead of the full error list.")
|
|
2839
|
+
});
|
|
2840
|
+
var getTdNodeErrorsOutputSchema = z33.object({
|
|
2841
|
+
path: z33.string(),
|
|
2842
|
+
total: z33.number(),
|
|
2843
|
+
errors: z33.array(NodeErrorSchema).optional(),
|
|
2844
|
+
by_type: z33.record(z33.string(), z33.number()).optional()
|
|
2845
|
+
});
|
|
2846
|
+
async function getTdNodeErrorsImpl(ctx, args) {
|
|
2847
|
+
return guardTd(
|
|
2848
|
+
() => args.recursive ? ctx.client.getNetworkErrors(args.path) : ctx.client.getNodeErrors(args.path),
|
|
2849
|
+
(result) => {
|
|
2850
|
+
const errors = result.errors;
|
|
2851
|
+
const total = errors.length;
|
|
2852
|
+
const none = total === 0;
|
|
2853
|
+
if (args.summary) {
|
|
2854
|
+
const byType = {};
|
|
2855
|
+
for (const e of errors) {
|
|
2856
|
+
const key = e.type || "error";
|
|
2857
|
+
byType[key] = (byType[key] ?? 0) + 1;
|
|
2858
|
+
}
|
|
2859
|
+
return structuredResult(
|
|
2860
|
+
none ? `No errors found at ${args.path}.` : `${total} error(s) at ${args.path}.`,
|
|
2861
|
+
{ path: args.path, total, by_type: byType }
|
|
2862
|
+
);
|
|
2863
|
+
}
|
|
2864
|
+
return structuredResult(
|
|
2865
|
+
none ? `No errors found at ${args.path}.` : `Found ${total} error(s) at ${args.path}.`,
|
|
2866
|
+
{ path: args.path, total, errors }
|
|
2867
|
+
);
|
|
2868
|
+
}
|
|
2869
|
+
);
|
|
2870
|
+
}
|
|
2871
|
+
var registerGetTdNodeErrors = (server, ctx) => {
|
|
2872
|
+
server.registerTool(
|
|
2873
|
+
"get_td_node_errors",
|
|
2874
|
+
{
|
|
2875
|
+
title: "Get node errors",
|
|
2876
|
+
description: "Check a node (or its whole sub-network) for cook/compile errors and warnings. Pass `summary:true` for grouped counts instead of the full list.",
|
|
2877
|
+
inputSchema: getTdNodeErrorsSchema.shape,
|
|
2878
|
+
outputSchema: getTdNodeErrorsOutputSchema.shape,
|
|
2879
|
+
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
2880
|
+
},
|
|
2881
|
+
(args) => getTdNodeErrorsImpl(ctx, args)
|
|
2882
|
+
);
|
|
2883
|
+
};
|
|
2884
|
+
|
|
2885
|
+
// src/tools/layer3/getTdNodeParameters.ts
|
|
2886
|
+
import { z as z34 } from "zod";
|
|
2887
|
+
var getTdNodeParametersSchema = z34.object({
|
|
2888
|
+
path: z34.string().describe("Full path of the node to inspect."),
|
|
2889
|
+
keys: z34.array(z34.string()).optional().describe("Only return these parameter names (case-sensitive). Omit to return all parameters."),
|
|
2890
|
+
omit_io: z34.boolean().default(false).describe("Drop the inputs/outputs lists from the result to save context.")
|
|
2891
|
+
});
|
|
2892
|
+
async function getTdNodeParametersImpl(ctx, args) {
|
|
2893
|
+
return guardTd(
|
|
2894
|
+
() => ctx.client.getNode(args.path),
|
|
2895
|
+
(node) => {
|
|
2896
|
+
let parameters = node.parameters;
|
|
2897
|
+
if (args.keys && args.keys.length > 0) {
|
|
2898
|
+
const wanted = new Set(args.keys);
|
|
2899
|
+
parameters = Object.fromEntries(
|
|
2900
|
+
Object.entries(node.parameters).filter(([k]) => wanted.has(k))
|
|
2901
|
+
);
|
|
2902
|
+
}
|
|
2903
|
+
const data = {
|
|
2904
|
+
path: node.path,
|
|
2905
|
+
type: node.type,
|
|
2906
|
+
name: node.name,
|
|
2907
|
+
parameters
|
|
2908
|
+
};
|
|
2909
|
+
if (!args.omit_io) {
|
|
2910
|
+
data.inputs = node.inputs;
|
|
2911
|
+
data.outputs = node.outputs;
|
|
2912
|
+
}
|
|
2913
|
+
const count = Object.keys(parameters).length;
|
|
2914
|
+
return structuredResult(`${count} parameter(s) for ${node.path} (${node.type}).`, data);
|
|
2915
|
+
}
|
|
2916
|
+
);
|
|
2917
|
+
}
|
|
2918
|
+
var registerGetTdNodeParameters = (server, ctx) => {
|
|
2919
|
+
server.registerTool(
|
|
2920
|
+
"get_td_node_parameters",
|
|
2921
|
+
{
|
|
2922
|
+
title: "Get node parameters",
|
|
2923
|
+
description: "Read the current parameters (and I/O) of a node. Pass `keys` to project specific parameters or `omit_io:true` to drop the inputs/outputs lists.",
|
|
2924
|
+
inputSchema: getTdNodeParametersSchema.shape,
|
|
2925
|
+
outputSchema: NodeDetailSchema.shape,
|
|
2926
|
+
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
2927
|
+
},
|
|
2928
|
+
(args) => getTdNodeParametersImpl(ctx, args)
|
|
2929
|
+
);
|
|
2930
|
+
};
|
|
2931
|
+
|
|
2932
|
+
// src/tools/layer3/getTdNodes.ts
|
|
2933
|
+
import { z as z35 } from "zod";
|
|
2934
|
+
var getTdNodesSchema = z35.object({
|
|
2935
|
+
parent_path: z35.string().default("/project1").describe("Parent COMP whose direct children should be listed."),
|
|
2936
|
+
pattern: z35.string().optional().describe(
|
|
2937
|
+
"Case-insensitive filter on node name/path. Supports '*' wildcards (e.g. 'text*', '*noise*')."
|
|
2938
|
+
),
|
|
2939
|
+
path_only: z35.boolean().default(false).describe("Return only the list of node paths, dropping type/name."),
|
|
2940
|
+
limit: z35.number().int().positive().optional().describe("Cap the number of nodes returned."),
|
|
2941
|
+
detail_level: z35.enum(["summary", "full"]).default("summary").describe(
|
|
2942
|
+
"'summary' (default) returns a count, a type breakdown and the first few paths; 'full' returns every node. Use 'full' (or path_only) when you need the complete list."
|
|
2943
|
+
)
|
|
2944
|
+
});
|
|
2945
|
+
var SAMPLE_SIZE = 10;
|
|
2946
|
+
var getTdNodesOutputSchema = z35.object({
|
|
2947
|
+
parent_path: z35.string(),
|
|
2948
|
+
count: z35.number(),
|
|
2949
|
+
detail_level: z35.enum(["summary", "full"]),
|
|
2950
|
+
truncated: z35.boolean(),
|
|
2951
|
+
by_type: z35.record(z35.string(), z35.number()).optional(),
|
|
2952
|
+
sample: z35.array(z35.string()).optional(),
|
|
2953
|
+
paths: z35.array(z35.string()).optional(),
|
|
2954
|
+
nodes: z35.array(NodeRefSchema).optional(),
|
|
2955
|
+
hint: z35.string().optional()
|
|
2956
|
+
});
|
|
2957
|
+
async function getTdNodesImpl(ctx, args) {
|
|
2958
|
+
return guardTd(
|
|
2959
|
+
() => ctx.client.getNodes(args.parent_path),
|
|
2960
|
+
(list) => {
|
|
2961
|
+
let nodes = list.nodes;
|
|
2962
|
+
if (args.pattern) {
|
|
2963
|
+
const re = globToRegExp(args.pattern);
|
|
2964
|
+
nodes = nodes.filter((n) => re.test(n.name) || re.test(n.path));
|
|
2965
|
+
}
|
|
2966
|
+
const matched = nodes.length;
|
|
2967
|
+
const truncated = args.limit !== void 0 && matched > args.limit;
|
|
2968
|
+
if (args.limit !== void 0) nodes = nodes.slice(0, args.limit);
|
|
2969
|
+
const where = args.pattern ? ` matching "${args.pattern}"` : "";
|
|
2970
|
+
const base = {
|
|
2971
|
+
parent_path: args.parent_path,
|
|
2972
|
+
count: matched,
|
|
2973
|
+
detail_level: args.detail_level,
|
|
2974
|
+
truncated
|
|
2975
|
+
};
|
|
2976
|
+
if (args.path_only) {
|
|
2977
|
+
return structuredResult(`${matched} node(s) under ${args.parent_path}${where}.`, {
|
|
2978
|
+
...base,
|
|
2979
|
+
paths: nodes.map((n) => n.path)
|
|
2980
|
+
});
|
|
2981
|
+
}
|
|
2982
|
+
if (args.detail_level === "summary") {
|
|
2983
|
+
const byType = {};
|
|
2984
|
+
for (const n of nodes) {
|
|
2985
|
+
const key = n.type || "unknown";
|
|
2986
|
+
byType[key] = (byType[key] ?? 0) + 1;
|
|
2987
|
+
}
|
|
2988
|
+
const sample = nodes.slice(0, SAMPLE_SIZE).map((n) => n.path);
|
|
2989
|
+
return structuredResult(
|
|
2990
|
+
`${matched} node(s) under ${args.parent_path}${where}. Summary only \u2014 pass detail_level:"full" or path_only:true for the full list.`,
|
|
2991
|
+
{
|
|
2992
|
+
...base,
|
|
2993
|
+
by_type: byType,
|
|
2994
|
+
sample,
|
|
2995
|
+
...matched > sample.length ? {
|
|
2996
|
+
hint: `Showing first ${sample.length} of ${matched}. Use detail_level:"full" for all.`
|
|
2997
|
+
} : {}
|
|
2998
|
+
}
|
|
2999
|
+
);
|
|
3000
|
+
}
|
|
3001
|
+
return structuredResult(`${matched} node(s) under ${args.parent_path}${where}.`, {
|
|
3002
|
+
...base,
|
|
3003
|
+
nodes
|
|
3004
|
+
});
|
|
3005
|
+
}
|
|
3006
|
+
);
|
|
3007
|
+
}
|
|
3008
|
+
var registerGetTdNodes = (server, ctx) => {
|
|
3009
|
+
server.registerTool(
|
|
3010
|
+
"get_td_nodes",
|
|
3011
|
+
{
|
|
3012
|
+
title: "List TouchDesigner nodes",
|
|
3013
|
+
description: 'List the direct child nodes of a COMP. Defaults to a compact summary (count + type breakdown + sample paths); pass detail_level:"full" or path_only:true for the complete list, and `pattern` to filter by name.',
|
|
3014
|
+
inputSchema: getTdNodesSchema.shape,
|
|
3015
|
+
outputSchema: getTdNodesOutputSchema.shape,
|
|
3016
|
+
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
3017
|
+
},
|
|
3018
|
+
(args) => getTdNodesImpl(ctx, args)
|
|
3019
|
+
);
|
|
3020
|
+
};
|
|
3021
|
+
|
|
3022
|
+
// src/tools/layer3/getTdPerformance.ts
|
|
3023
|
+
import { z as z36 } from "zod";
|
|
3024
|
+
|
|
3025
|
+
// src/feedback/performanceMonitor.ts
|
|
3026
|
+
async function checkPerformance(client, path, targetFps = 60) {
|
|
3027
|
+
const perf = await client.getNetworkPerformance(path);
|
|
3028
|
+
const frameBudgetMs = 1e3 / targetFps;
|
|
3029
|
+
const warnings = [];
|
|
3030
|
+
for (const node of perf.nodes) {
|
|
3031
|
+
if (node.cook_time_ms > frameBudgetMs) {
|
|
3032
|
+
warnings.push(
|
|
3033
|
+
`${node.path} cooks in ${node.cook_time_ms.toFixed(2)}ms, exceeding the ${frameBudgetMs.toFixed(2)}ms budget at ${targetFps}fps.`
|
|
3034
|
+
);
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
const totalCookMs = perf.total_cook_time_ms ?? perf.nodes.reduce((sum, node) => sum + node.cook_time_ms, 0);
|
|
3038
|
+
if (totalCookMs > frameBudgetMs) {
|
|
3039
|
+
warnings.push(
|
|
3040
|
+
`Total cook time ${totalCookMs.toFixed(2)}ms exceeds the ${frameBudgetMs.toFixed(2)}ms budget at ${targetFps}fps.`
|
|
3041
|
+
);
|
|
3042
|
+
}
|
|
3043
|
+
return { path, targetFps, frameBudgetMs, totalCookMs, nodes: perf.nodes, warnings };
|
|
3044
|
+
}
|
|
3045
|
+
|
|
3046
|
+
// src/tools/layer3/getTdPerformance.ts
|
|
3047
|
+
var getTdPerformanceSchema = z36.object({
|
|
3048
|
+
root_path: z36.string().default("/project1").describe("Network root to measure cook times under."),
|
|
3049
|
+
target_fps: z36.number().positive().default(60).describe("Frame-rate target used to flag slow nodes.")
|
|
3050
|
+
});
|
|
3051
|
+
var getTdPerformanceOutputSchema = z36.object({
|
|
3052
|
+
path: z36.string(),
|
|
3053
|
+
targetFps: z36.number(),
|
|
3054
|
+
frameBudgetMs: z36.number(),
|
|
3055
|
+
totalCookMs: z36.number(),
|
|
3056
|
+
nodes: z36.array(
|
|
3057
|
+
z36.object({
|
|
3058
|
+
path: z36.string(),
|
|
3059
|
+
cook_time_ms: z36.number(),
|
|
3060
|
+
cook_count: z36.number().optional()
|
|
3061
|
+
})
|
|
3062
|
+
),
|
|
3063
|
+
warnings: z36.array(z36.string())
|
|
3064
|
+
});
|
|
3065
|
+
async function getTdPerformanceImpl(ctx, args) {
|
|
3066
|
+
return guardTd(
|
|
3067
|
+
() => checkPerformance(ctx.client, args.root_path, args.target_fps),
|
|
3068
|
+
(report) => structuredResult(
|
|
3069
|
+
report.warnings.length === 0 ? `Within budget: ${report.totalCookMs.toFixed(2)}ms total under ${args.root_path} (${args.target_fps}fps).` : `${report.warnings.length} performance warning(s) under ${args.root_path}.`,
|
|
3070
|
+
report
|
|
3071
|
+
)
|
|
3072
|
+
);
|
|
3073
|
+
}
|
|
3074
|
+
var registerGetTdPerformance = (server, ctx) => {
|
|
3075
|
+
server.registerTool(
|
|
3076
|
+
"get_td_performance",
|
|
3077
|
+
{
|
|
3078
|
+
title: "Get network performance",
|
|
3079
|
+
description: "Report cook times under a network and warn about nodes that exceed the frame budget.",
|
|
3080
|
+
inputSchema: getTdPerformanceSchema.shape,
|
|
3081
|
+
outputSchema: getTdPerformanceOutputSchema.shape,
|
|
3082
|
+
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
3083
|
+
},
|
|
3084
|
+
(args) => getTdPerformanceImpl(ctx, args)
|
|
3085
|
+
);
|
|
3086
|
+
};
|
|
3087
|
+
|
|
3088
|
+
// src/tools/layer3/getTdTopology.ts
|
|
3089
|
+
import { z as z37 } from "zod";
|
|
3090
|
+
|
|
3091
|
+
// src/feedback/networkVerifier.ts
|
|
3092
|
+
async function verifyNetwork(client, path) {
|
|
3093
|
+
const topology = await client.getNetworkTopology(path);
|
|
3094
|
+
const issues = [];
|
|
3095
|
+
if (topology.nodes.length === 0) {
|
|
3096
|
+
issues.push(`No nodes were found under ${path}.`);
|
|
3097
|
+
} else if (topology.nodes.length > 1 && topology.connections.length === 0) {
|
|
3098
|
+
issues.push("Multiple nodes exist but none are connected.");
|
|
3099
|
+
}
|
|
3100
|
+
return {
|
|
3101
|
+
path,
|
|
3102
|
+
nodeCount: topology.nodes.length,
|
|
3103
|
+
connectionCount: topology.connections.length,
|
|
3104
|
+
issues,
|
|
3105
|
+
topology
|
|
3106
|
+
};
|
|
3107
|
+
}
|
|
3108
|
+
|
|
3109
|
+
// src/tools/layer3/getTdTopology.ts
|
|
3110
|
+
var getTdTopologySchema = z37.object({
|
|
3111
|
+
root_path: z37.string().default("/project1").describe("Network root to map.")
|
|
3112
|
+
});
|
|
3113
|
+
var getTdTopologyOutputSchema = z37.object({
|
|
3114
|
+
path: z37.string(),
|
|
3115
|
+
nodeCount: z37.number(),
|
|
3116
|
+
connectionCount: z37.number(),
|
|
3117
|
+
issues: z37.array(z37.string()),
|
|
3118
|
+
topology: TopologySchema
|
|
3119
|
+
});
|
|
3120
|
+
async function getTdTopologyImpl(ctx, args) {
|
|
3121
|
+
return guardTd(
|
|
3122
|
+
() => verifyNetwork(ctx.client, args.root_path),
|
|
3123
|
+
(report) => structuredResult(
|
|
3124
|
+
`${report.nodeCount} node(s), ${report.connectionCount} connection(s) under ${args.root_path}.`,
|
|
3125
|
+
report
|
|
3126
|
+
)
|
|
3127
|
+
);
|
|
3128
|
+
}
|
|
3129
|
+
var registerGetTdTopology = (server, ctx) => {
|
|
3130
|
+
server.registerTool(
|
|
3131
|
+
"get_td_topology",
|
|
3132
|
+
{
|
|
3133
|
+
title: "Get network topology",
|
|
3134
|
+
description: "Return the nodes and connections under a network root, flagging obvious structural issues.",
|
|
3135
|
+
inputSchema: getTdTopologySchema.shape,
|
|
3136
|
+
outputSchema: getTdTopologyOutputSchema.shape,
|
|
3137
|
+
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
3138
|
+
},
|
|
3139
|
+
(args) => getTdTopologyImpl(ctx, args)
|
|
3140
|
+
);
|
|
3141
|
+
};
|
|
3142
|
+
|
|
3143
|
+
// src/tools/layer3/snapshotTdGraph.ts
|
|
3144
|
+
import { z as z38 } from "zod";
|
|
3145
|
+
var MAX_PARAM_NODES = 60;
|
|
3146
|
+
var snapshotTdGraphSchema = z38.object({
|
|
3147
|
+
path: z38.string().default("/project1").describe("Network root to snapshot."),
|
|
3148
|
+
include_params: z38.boolean().default(false).describe("Also fetch each node's parameters (one request per node; capped for large graphs).")
|
|
3149
|
+
});
|
|
3150
|
+
var snapshotTdGraphOutputSchema = z38.object({
|
|
3151
|
+
path: z38.string(),
|
|
3152
|
+
nodeCount: z38.number(),
|
|
3153
|
+
connectionCount: z38.number(),
|
|
3154
|
+
issues: z38.array(z38.string()),
|
|
3155
|
+
params_truncated: z38.boolean(),
|
|
3156
|
+
nodes: z38.array(
|
|
3157
|
+
z38.object({
|
|
3158
|
+
path: z38.string(),
|
|
3159
|
+
type: z38.string(),
|
|
3160
|
+
name: z38.string(),
|
|
3161
|
+
parameters: z38.record(z38.string(), z38.unknown()).optional()
|
|
3162
|
+
})
|
|
3163
|
+
),
|
|
3164
|
+
connections: z38.array(ConnectionSchema)
|
|
3165
|
+
});
|
|
3166
|
+
async function snapshotTdGraphImpl(ctx, args) {
|
|
3167
|
+
return guardTd(
|
|
3168
|
+
async () => {
|
|
3169
|
+
const report = await verifyNetwork(ctx.client, args.path);
|
|
3170
|
+
const refs = report.topology.nodes;
|
|
3171
|
+
let paramsTruncated = false;
|
|
3172
|
+
const params = /* @__PURE__ */ new Map();
|
|
3173
|
+
if (args.include_params) {
|
|
3174
|
+
const targets = refs.slice(0, MAX_PARAM_NODES);
|
|
3175
|
+
paramsTruncated = refs.length > MAX_PARAM_NODES;
|
|
3176
|
+
const details = await Promise.allSettled(targets.map((n) => ctx.client.getNode(n.path)));
|
|
3177
|
+
for (const detail of details) {
|
|
3178
|
+
if (detail.status === "fulfilled") params.set(detail.value.path, detail.value.parameters);
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
const nodes = refs.map((n) => ({
|
|
3182
|
+
path: n.path,
|
|
3183
|
+
type: n.type,
|
|
3184
|
+
name: n.name,
|
|
3185
|
+
...args.include_params ? { parameters: params.get(n.path) ?? {} } : {}
|
|
3186
|
+
}));
|
|
3187
|
+
return { report, nodes, paramsTruncated };
|
|
3188
|
+
},
|
|
3189
|
+
({ report, nodes, paramsTruncated }) => structuredResult(
|
|
3190
|
+
`Snapshot of ${args.path}: ${report.nodeCount} node(s), ${report.connectionCount} connection(s)${report.issues.length ? `, ${report.issues.length} issue(s)` : ""}.`,
|
|
3191
|
+
{
|
|
3192
|
+
path: report.path,
|
|
3193
|
+
nodeCount: report.nodeCount,
|
|
3194
|
+
connectionCount: report.connectionCount,
|
|
3195
|
+
issues: report.issues,
|
|
3196
|
+
params_truncated: paramsTruncated,
|
|
3197
|
+
nodes,
|
|
3198
|
+
connections: report.topology.connections
|
|
3199
|
+
}
|
|
3200
|
+
)
|
|
3201
|
+
);
|
|
3202
|
+
}
|
|
3203
|
+
var registerSnapshotTdGraph = (server, ctx) => {
|
|
3204
|
+
server.registerTool(
|
|
3205
|
+
"snapshot_td_graph",
|
|
3206
|
+
{
|
|
3207
|
+
title: "Snapshot network graph",
|
|
3208
|
+
description: "Capture a compact, serializable snapshot of a network \u2014 nodes, connections, structural issues, and optionally each node's parameters \u2014 for review, diffing, or documentation.",
|
|
3209
|
+
inputSchema: snapshotTdGraphSchema.shape,
|
|
3210
|
+
outputSchema: snapshotTdGraphOutputSchema.shape,
|
|
3211
|
+
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
3212
|
+
},
|
|
3213
|
+
(args) => snapshotTdGraphImpl(ctx, args)
|
|
3214
|
+
);
|
|
3215
|
+
};
|
|
3216
|
+
|
|
3217
|
+
// src/tools/layer3/summarizeTdErrors.ts
|
|
3218
|
+
import { z as z39 } from "zod";
|
|
3219
|
+
var summarizeTdErrorsSchema = z39.object({
|
|
3220
|
+
path: z39.string().default("/project1").describe("Network root to collect errors under."),
|
|
3221
|
+
group_by: z39.enum(["message", "type", "parent"]).default("message").describe(
|
|
3222
|
+
"How to cluster errors: by exact message, by error type, or by parent container (to find a common upstream cause)."
|
|
3223
|
+
)
|
|
3224
|
+
});
|
|
3225
|
+
var summarizeTdErrorsOutputSchema = z39.object({
|
|
3226
|
+
path: z39.string(),
|
|
3227
|
+
total: z39.number(),
|
|
3228
|
+
group_by: z39.enum(["message", "type", "parent"]),
|
|
3229
|
+
groups: z39.array(
|
|
3230
|
+
z39.object({
|
|
3231
|
+
key: z39.string(),
|
|
3232
|
+
count: z39.number(),
|
|
3233
|
+
sample: z39.object({ path: z39.string(), message: z39.string() })
|
|
3234
|
+
})
|
|
3235
|
+
),
|
|
3236
|
+
suggestions: z39.array(z39.string())
|
|
3237
|
+
});
|
|
3238
|
+
async function summarizeTdErrorsImpl(ctx, args) {
|
|
3239
|
+
return guardTd(
|
|
3240
|
+
() => ctx.client.getNetworkErrors(args.path),
|
|
3241
|
+
(result) => {
|
|
3242
|
+
const errors = result.errors;
|
|
3243
|
+
const total = errors.length;
|
|
3244
|
+
if (total === 0) {
|
|
3245
|
+
return structuredResult(`No errors found under ${args.path}.`, {
|
|
3246
|
+
path: args.path,
|
|
3247
|
+
total: 0,
|
|
3248
|
+
group_by: args.group_by,
|
|
3249
|
+
groups: [],
|
|
3250
|
+
suggestions: []
|
|
3251
|
+
});
|
|
3252
|
+
}
|
|
3253
|
+
const keyOf = (e) => args.group_by === "message" ? e.message : args.group_by === "type" ? e.type || "error" : parentOf(e.path);
|
|
3254
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
3255
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
3256
|
+
for (const e of errors) {
|
|
3257
|
+
const key = keyOf(e);
|
|
3258
|
+
const g = grouped.get(key);
|
|
3259
|
+
if (g) g.count += 1;
|
|
3260
|
+
else grouped.set(key, { count: 1, sample: { path: e.path, message: e.message } });
|
|
3261
|
+
byPath.set(e.path, (byPath.get(e.path) ?? 0) + 1);
|
|
3262
|
+
}
|
|
3263
|
+
const groups = [...grouped.entries()].map(([key, g]) => ({ key, count: g.count, sample: g.sample })).sort((a, b) => b.count - a.count);
|
|
3264
|
+
const worstNodes = [...byPath.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3).map(([p, c]) => `${p} (${c} error${c === 1 ? "" : "s"})`);
|
|
3265
|
+
const suggestions = [];
|
|
3266
|
+
if (groups[0] && groups[0].count > 1) {
|
|
3267
|
+
suggestions.push(
|
|
3268
|
+
`${groups[0].count} errors share ${args.group_by} "${groups[0].key}" \u2014 fixing the common cause clears them at once.`
|
|
3269
|
+
);
|
|
3270
|
+
}
|
|
3271
|
+
if (worstNodes.length > 0) {
|
|
3272
|
+
suggestions.push(`Check first: ${worstNodes.join(", ")}.`);
|
|
3273
|
+
}
|
|
3274
|
+
return structuredResult(
|
|
3275
|
+
`${total} error(s) under ${args.path} in ${groups.length} ${args.group_by} group(s).`,
|
|
3276
|
+
{ path: args.path, total, group_by: args.group_by, groups, suggestions }
|
|
3277
|
+
);
|
|
3278
|
+
}
|
|
3279
|
+
);
|
|
3280
|
+
}
|
|
3281
|
+
var registerSummarizeTdErrors = (server, ctx) => {
|
|
3282
|
+
server.registerTool(
|
|
3283
|
+
"summarize_td_errors",
|
|
3284
|
+
{
|
|
3285
|
+
title: "Summarize network errors",
|
|
3286
|
+
description: "Collect errors across a network and cluster them by message, type, or parent container, with the worst-offending nodes and a suggested order to investigate. Use this instead of reading every node's errors one by one.",
|
|
3287
|
+
inputSchema: summarizeTdErrorsSchema.shape,
|
|
3288
|
+
outputSchema: summarizeTdErrorsOutputSchema.shape,
|
|
3289
|
+
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
3290
|
+
},
|
|
3291
|
+
(args) => summarizeTdErrorsImpl(ctx, args)
|
|
3292
|
+
);
|
|
3293
|
+
};
|
|
3294
|
+
|
|
3295
|
+
// src/tools/layer3/updateTdNodeParameters.ts
|
|
3296
|
+
import { z as z40 } from "zod";
|
|
3297
|
+
var updateTdNodeParametersSchema = z40.object({
|
|
3298
|
+
path: z40.string().describe("Full path of the node whose parameters to update."),
|
|
3299
|
+
parameters: z40.record(z40.string(), z40.unknown()).describe("Parameter overrides as key\u2192value pairs, e.g. { period: 4, amplitude: 0.5 }.")
|
|
3300
|
+
});
|
|
3301
|
+
async function updateTdNodeParametersImpl(ctx, args) {
|
|
3302
|
+
return guardTd(
|
|
3303
|
+
() => ctx.client.updateNodeParameters(args.path, args.parameters),
|
|
3304
|
+
(node) => jsonResult(`Updated ${Object.keys(args.parameters).length} parameter(s) on ${node.path}.`, {
|
|
3305
|
+
node
|
|
3306
|
+
})
|
|
3307
|
+
);
|
|
3308
|
+
}
|
|
3309
|
+
var registerUpdateTdNodeParameters = (server, ctx) => {
|
|
3310
|
+
server.registerTool(
|
|
3311
|
+
"update_td_node_parameters",
|
|
3312
|
+
{
|
|
3313
|
+
title: "Update node parameters",
|
|
3314
|
+
description: "Set one or more parameters on an existing node.",
|
|
3315
|
+
inputSchema: updateTdNodeParametersSchema.shape,
|
|
3316
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true }
|
|
3317
|
+
},
|
|
3318
|
+
(args) => updateTdNodeParametersImpl(ctx, args)
|
|
3319
|
+
);
|
|
3320
|
+
};
|
|
3321
|
+
|
|
3322
|
+
// src/tools/layer3/index.ts
|
|
3323
|
+
var layer3Registrars = [
|
|
3324
|
+
registerGetTdInfo,
|
|
3325
|
+
registerCreateTdNode,
|
|
3326
|
+
registerDeleteTdNode,
|
|
3327
|
+
registerUpdateTdNodeParameters,
|
|
3328
|
+
registerGetTdNodes,
|
|
3329
|
+
registerGetTdNodeParameters,
|
|
3330
|
+
registerGetTdNodeErrors,
|
|
3331
|
+
registerExecutePythonScript,
|
|
3332
|
+
registerExecNodeMethod,
|
|
3333
|
+
registerGetTdClasses,
|
|
3334
|
+
registerGetTdClassDetails,
|
|
3335
|
+
registerGetModuleHelp,
|
|
3336
|
+
registerGetTdPerformance,
|
|
3337
|
+
registerGetTdTopology,
|
|
3338
|
+
registerFindTdNodes,
|
|
3339
|
+
registerSummarizeTdErrors,
|
|
3340
|
+
registerCompareTdNodes,
|
|
3341
|
+
registerSnapshotTdGraph
|
|
3342
|
+
];
|
|
3343
|
+
|
|
3344
|
+
// src/tools/index.ts
|
|
3345
|
+
function registerAllTools(server, ctx) {
|
|
3346
|
+
const registrars = [...layer3Registrars, ...layer2Registrars, ...layer1Registrars];
|
|
3347
|
+
for (const register of registrars) register(server, ctx);
|
|
3348
|
+
}
|
|
3349
|
+
|
|
3350
|
+
// src/utils/version.ts
|
|
3351
|
+
import { readFileSync } from "fs";
|
|
3352
|
+
import { dirname as dirname2, resolve as resolve2 } from "path";
|
|
3353
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3354
|
+
var cached;
|
|
3355
|
+
function getVersion() {
|
|
3356
|
+
if (cached) return cached;
|
|
3357
|
+
let dir = dirname2(fileURLToPath2(import.meta.url));
|
|
3358
|
+
for (let i = 0; i < 6; i++) {
|
|
3359
|
+
try {
|
|
3360
|
+
const raw = readFileSync(resolve2(dir, "package.json"), "utf8");
|
|
3361
|
+
const pkg = JSON.parse(raw);
|
|
3362
|
+
if (pkg.name === "tdmcp" && pkg.version) {
|
|
3363
|
+
cached = pkg.version;
|
|
3364
|
+
return cached;
|
|
3365
|
+
}
|
|
3366
|
+
} catch {
|
|
3367
|
+
}
|
|
3368
|
+
const parent = dirname2(dir);
|
|
3369
|
+
if (parent === dir) break;
|
|
3370
|
+
dir = parent;
|
|
3371
|
+
}
|
|
3372
|
+
cached = "0.0.0";
|
|
3373
|
+
return cached;
|
|
3374
|
+
}
|
|
3375
|
+
|
|
3376
|
+
// src/knowledge/index.ts
|
|
3377
|
+
import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync2 } from "fs";
|
|
3378
|
+
import { join as join2 } from "path";
|
|
3379
|
+
|
|
3380
|
+
// src/utils/logger.ts
|
|
3381
|
+
var LEVELS = {
|
|
3382
|
+
debug: 10,
|
|
3383
|
+
info: 20,
|
|
3384
|
+
warn: 30,
|
|
3385
|
+
error: 40,
|
|
3386
|
+
silent: 99
|
|
3387
|
+
};
|
|
3388
|
+
function createLogger(level = "info") {
|
|
3389
|
+
const threshold = LEVELS[level];
|
|
3390
|
+
const emit = (lvl, message, meta) => {
|
|
3391
|
+
if (LEVELS[lvl] < threshold) return;
|
|
3392
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
3393
|
+
const suffix = meta && Object.keys(meta).length > 0 ? ` ${JSON.stringify(meta)}` : "";
|
|
3394
|
+
process.stderr.write(`[tdmcp] ${stamp} ${lvl.toUpperCase()} ${message}${suffix}
|
|
3395
|
+
`);
|
|
3396
|
+
};
|
|
3397
|
+
return {
|
|
3398
|
+
debug: (message, meta) => emit("debug", message, meta),
|
|
3399
|
+
info: (message, meta) => emit("info", message, meta),
|
|
3400
|
+
warn: (message, meta) => emit("warn", message, meta),
|
|
3401
|
+
error: (message, meta) => emit("error", message, meta)
|
|
3402
|
+
};
|
|
3403
|
+
}
|
|
3404
|
+
var silentLogger = {
|
|
3405
|
+
debug: () => {
|
|
3406
|
+
},
|
|
3407
|
+
info: () => {
|
|
3408
|
+
},
|
|
3409
|
+
warn: () => {
|
|
3410
|
+
},
|
|
3411
|
+
error: () => {
|
|
3412
|
+
}
|
|
3413
|
+
};
|
|
3414
|
+
|
|
3415
|
+
// src/knowledge/index.ts
|
|
3416
|
+
var EMPTY_SOURCE = {
|
|
3417
|
+
kind: "empty",
|
|
3418
|
+
operatorsDir: "",
|
|
3419
|
+
pythonDir: "",
|
|
3420
|
+
tutorialsDir: "",
|
|
3421
|
+
patternsFile: "",
|
|
3422
|
+
glslFile: ""
|
|
3423
|
+
};
|
|
3424
|
+
var KnowledgeBase = class _KnowledgeBase {
|
|
3425
|
+
logger;
|
|
3426
|
+
source;
|
|
3427
|
+
opIndexCache;
|
|
3428
|
+
opLookupCache;
|
|
3429
|
+
opDocCache = /* @__PURE__ */ new Map();
|
|
3430
|
+
pyIndexCache;
|
|
3431
|
+
pyLookupCache;
|
|
3432
|
+
pyDocCache = /* @__PURE__ */ new Map();
|
|
3433
|
+
tutIndexCache;
|
|
3434
|
+
tutLookupCache;
|
|
3435
|
+
tutDocCache = /* @__PURE__ */ new Map();
|
|
3436
|
+
patternsCache;
|
|
3437
|
+
glslCache;
|
|
3438
|
+
constructor(options = {}) {
|
|
3439
|
+
this.logger = options.logger ?? silentLogger;
|
|
3440
|
+
this.source = _KnowledgeBase.resolveSource(options.dataDir ?? knowledgeDataDir());
|
|
3441
|
+
this.logger.debug("knowledge source resolved", { kind: this.source.kind });
|
|
3442
|
+
}
|
|
3443
|
+
static resolveSource(localDir) {
|
|
3444
|
+
if (existsSync3(join2(localDir, "operators"))) {
|
|
3445
|
+
return {
|
|
3446
|
+
kind: "local",
|
|
3447
|
+
operatorsDir: join2(localDir, "operators"),
|
|
3448
|
+
operatorsIndex: join2(localDir, "operators", "index.json"),
|
|
3449
|
+
pythonDir: join2(localDir, "python-api"),
|
|
3450
|
+
tutorialsDir: join2(localDir, "tutorials"),
|
|
3451
|
+
patternsFile: join2(localDir, "patterns.json"),
|
|
3452
|
+
glslFile: join2(localDir, "glsl.json")
|
|
3453
|
+
};
|
|
3454
|
+
}
|
|
3455
|
+
const bb = bottobotPackageDir();
|
|
3456
|
+
if (bb) {
|
|
3457
|
+
return {
|
|
3458
|
+
kind: "bottobot",
|
|
3459
|
+
operatorsDir: join2(bb, "wiki/data/processed"),
|
|
3460
|
+
pythonDir: join2(bb, "wiki/data/python-api"),
|
|
3461
|
+
tutorialsDir: join2(bb, "wiki/data/tutorials"),
|
|
3462
|
+
patternsFile: join2(bb, "data/patterns.json"),
|
|
3463
|
+
glslFile: join2(bb, "wiki/data/experimental/glsl.json")
|
|
3464
|
+
};
|
|
3465
|
+
}
|
|
3466
|
+
return EMPTY_SOURCE;
|
|
3467
|
+
}
|
|
3468
|
+
readJson(path) {
|
|
3469
|
+
try {
|
|
3470
|
+
return JSON.parse(readFileSync2(path, "utf8"));
|
|
3471
|
+
} catch (err) {
|
|
3472
|
+
this.logger.debug("knowledge readJson failed", { path, error: String(err) });
|
|
3473
|
+
return void 0;
|
|
3474
|
+
}
|
|
3475
|
+
}
|
|
3476
|
+
listJsonFiles(dir) {
|
|
3477
|
+
if (!dir || !existsSync3(dir)) return [];
|
|
3478
|
+
try {
|
|
3479
|
+
return readdirSync(dir).filter((f) => f.endsWith(".json") && f !== "index.json");
|
|
3480
|
+
} catch {
|
|
3481
|
+
return [];
|
|
3482
|
+
}
|
|
3483
|
+
}
|
|
3484
|
+
// ---- Operators -----------------------------------------------------------
|
|
3485
|
+
operatorIndex() {
|
|
3486
|
+
if (this.opIndexCache) return this.opIndexCache;
|
|
3487
|
+
if (this.source.kind === "empty") {
|
|
3488
|
+
this.opIndexCache = [];
|
|
3489
|
+
return this.opIndexCache;
|
|
3490
|
+
}
|
|
3491
|
+
if (this.source.operatorsIndex && existsSync3(this.source.operatorsIndex)) {
|
|
3492
|
+
const data = this.readJson(this.source.operatorsIndex);
|
|
3493
|
+
if (Array.isArray(data)) {
|
|
3494
|
+
this.opIndexCache = data;
|
|
3495
|
+
return this.opIndexCache;
|
|
3496
|
+
}
|
|
3497
|
+
}
|
|
3498
|
+
const summaries = [];
|
|
3499
|
+
for (const file of this.listJsonFiles(this.source.operatorsDir)) {
|
|
3500
|
+
const doc = this.readJson(join2(this.source.operatorsDir, file));
|
|
3501
|
+
if (doc?.name) summaries.push(toOperatorSummary(file.replace(/\.json$/, ""), doc));
|
|
3502
|
+
}
|
|
3503
|
+
this.opIndexCache = summaries;
|
|
3504
|
+
return this.opIndexCache;
|
|
3505
|
+
}
|
|
3506
|
+
operatorLookup() {
|
|
3507
|
+
if (this.opLookupCache) return this.opLookupCache;
|
|
3508
|
+
const map = /* @__PURE__ */ new Map();
|
|
3509
|
+
for (const summary of this.operatorIndex()) {
|
|
3510
|
+
map.set(compactKey(summary.slug), summary.slug);
|
|
3511
|
+
map.set(compactKey(summary.name), summary.slug);
|
|
3512
|
+
map.set(compactKey(summary.displayName), summary.slug);
|
|
3513
|
+
}
|
|
3514
|
+
this.opLookupCache = map;
|
|
3515
|
+
return map;
|
|
3516
|
+
}
|
|
3517
|
+
listOperatorCategories() {
|
|
3518
|
+
const set = /* @__PURE__ */ new Set();
|
|
3519
|
+
for (const summary of this.operatorIndex()) set.add(summary.category);
|
|
3520
|
+
return [...set].sort();
|
|
3521
|
+
}
|
|
3522
|
+
listOperators(category) {
|
|
3523
|
+
const all = this.operatorIndex();
|
|
3524
|
+
if (!category) return all;
|
|
3525
|
+
const wanted = compactKey(category);
|
|
3526
|
+
return all.filter((s) => compactKey(s.category) === wanted);
|
|
3527
|
+
}
|
|
3528
|
+
getOperator(nameOrSlug) {
|
|
3529
|
+
if (this.source.kind === "empty") return void 0;
|
|
3530
|
+
const slug = this.operatorLookup().get(compactKey(nameOrSlug)) ?? slugify(nameOrSlug);
|
|
3531
|
+
const cached2 = this.opDocCache.get(slug);
|
|
3532
|
+
if (cached2 !== void 0) return cached2 ?? void 0;
|
|
3533
|
+
const file = join2(this.source.operatorsDir, `${slug}.json`);
|
|
3534
|
+
const doc = existsSync3(file) ? this.readJson(file) : void 0;
|
|
3535
|
+
this.opDocCache.set(slug, doc ?? null);
|
|
3536
|
+
return doc;
|
|
3537
|
+
}
|
|
3538
|
+
/** Soft existence check (by operator type, display name, or slug). */
|
|
3539
|
+
operatorExists(typeOrName) {
|
|
3540
|
+
return this.operatorLookup().has(compactKey(typeOrName));
|
|
3541
|
+
}
|
|
3542
|
+
searchOperators(query, limit = 25) {
|
|
3543
|
+
const q11 = query.trim().toLowerCase();
|
|
3544
|
+
if (!q11) return [];
|
|
3545
|
+
const terms = q11.split(/\s+/);
|
|
3546
|
+
const scored = [];
|
|
3547
|
+
for (const summary of this.operatorIndex()) {
|
|
3548
|
+
const haystack = `${summary.name} ${summary.displayName} ${summary.summary} ${summary.keywords.join(" ")}`.toLowerCase();
|
|
3549
|
+
let score = 0;
|
|
3550
|
+
for (const term of terms) {
|
|
3551
|
+
if (haystack.includes(term)) score += 1;
|
|
3552
|
+
if (summary.name.toLowerCase().includes(term)) score += 1;
|
|
3553
|
+
}
|
|
3554
|
+
if (score > 0) scored.push({ summary, score });
|
|
3555
|
+
}
|
|
3556
|
+
scored.sort((a, b) => b.score - a.score);
|
|
3557
|
+
return scored.slice(0, limit).map((s) => s.summary);
|
|
3558
|
+
}
|
|
3559
|
+
// ---- Python API ----------------------------------------------------------
|
|
3560
|
+
pythonIndex() {
|
|
3561
|
+
if (this.pyIndexCache) return this.pyIndexCache;
|
|
3562
|
+
if (this.source.kind === "empty") {
|
|
3563
|
+
this.pyIndexCache = [];
|
|
3564
|
+
return this.pyIndexCache;
|
|
3565
|
+
}
|
|
3566
|
+
const indexPath = join2(this.source.pythonDir, "index.json");
|
|
3567
|
+
if (existsSync3(indexPath)) {
|
|
3568
|
+
const data = this.readJson(indexPath);
|
|
3569
|
+
if (Array.isArray(data)) {
|
|
3570
|
+
this.pyIndexCache = data;
|
|
3571
|
+
return this.pyIndexCache;
|
|
3572
|
+
}
|
|
3573
|
+
}
|
|
3574
|
+
const summaries = [];
|
|
3575
|
+
for (const file of this.listJsonFiles(this.source.pythonDir)) {
|
|
3576
|
+
const cls = this.readJson(join2(this.source.pythonDir, file));
|
|
3577
|
+
if (cls?.className) summaries.push(toPythonSummary(cls));
|
|
3578
|
+
}
|
|
3579
|
+
this.pyIndexCache = summaries;
|
|
3580
|
+
return this.pyIndexCache;
|
|
3581
|
+
}
|
|
3582
|
+
pythonLookup() {
|
|
3583
|
+
if (this.pyLookupCache) return this.pyLookupCache;
|
|
3584
|
+
const map = /* @__PURE__ */ new Map();
|
|
3585
|
+
for (const summary of this.pythonIndex()) {
|
|
3586
|
+
map.set(compactKey(summary.className), summary.className);
|
|
3587
|
+
map.set(compactKey(summary.displayName), summary.className);
|
|
3588
|
+
}
|
|
3589
|
+
this.pyLookupCache = map;
|
|
3590
|
+
return map;
|
|
3591
|
+
}
|
|
3592
|
+
listPythonClasses() {
|
|
3593
|
+
return this.pythonIndex();
|
|
3594
|
+
}
|
|
3595
|
+
getPythonClass(name) {
|
|
3596
|
+
if (this.source.kind === "empty") return void 0;
|
|
3597
|
+
const className = this.pythonLookup().get(compactKey(name)) ?? name;
|
|
3598
|
+
const cached2 = this.pyDocCache.get(className);
|
|
3599
|
+
if (cached2 !== void 0) return cached2 ?? void 0;
|
|
3600
|
+
const file = join2(this.source.pythonDir, `${className}.json`);
|
|
3601
|
+
const cls = existsSync3(file) ? this.readJson(file) : void 0;
|
|
3602
|
+
this.pyDocCache.set(className, cls ?? null);
|
|
3603
|
+
return cls;
|
|
3604
|
+
}
|
|
3605
|
+
// ---- Patterns ------------------------------------------------------------
|
|
3606
|
+
patterns() {
|
|
3607
|
+
if (this.patternsCache) return this.patternsCache;
|
|
3608
|
+
if (this.source.kind === "empty") {
|
|
3609
|
+
this.patternsCache = [];
|
|
3610
|
+
return this.patternsCache;
|
|
3611
|
+
}
|
|
3612
|
+
const data = this.readJson(this.source.patternsFile);
|
|
3613
|
+
if (Array.isArray(data) && data.length > 0 && typeof data[0].id === "string") {
|
|
3614
|
+
this.patternsCache = data;
|
|
3615
|
+
} else {
|
|
3616
|
+
this.patternsCache = normalizePatterns(data);
|
|
3617
|
+
}
|
|
3618
|
+
return this.patternsCache;
|
|
3619
|
+
}
|
|
3620
|
+
listPatterns() {
|
|
3621
|
+
return this.patterns().map((p) => ({
|
|
3622
|
+
id: p.id,
|
|
3623
|
+
name: p.name,
|
|
3624
|
+
category: p.category ?? "Unknown",
|
|
3625
|
+
description: p.description ?? ""
|
|
3626
|
+
}));
|
|
3627
|
+
}
|
|
3628
|
+
getPattern(name) {
|
|
3629
|
+
const key = compactKey(name);
|
|
3630
|
+
return this.patterns().find((p) => compactKey(p.id) === key || compactKey(p.name) === key);
|
|
3631
|
+
}
|
|
3632
|
+
// ---- GLSL ----------------------------------------------------------------
|
|
3633
|
+
glsl() {
|
|
3634
|
+
if (this.glslCache) return this.glslCache;
|
|
3635
|
+
if (this.source.kind === "empty") {
|
|
3636
|
+
this.glslCache = [];
|
|
3637
|
+
return this.glslCache;
|
|
3638
|
+
}
|
|
3639
|
+
const data = this.readJson(this.source.glslFile);
|
|
3640
|
+
this.glslCache = Array.isArray(data) ? data : normalizeGlsl(data);
|
|
3641
|
+
return this.glslCache;
|
|
3642
|
+
}
|
|
3643
|
+
listGlslPatterns() {
|
|
3644
|
+
return this.glsl().map((g) => ({
|
|
3645
|
+
id: g.id,
|
|
3646
|
+
name: g.name,
|
|
3647
|
+
description: g.description ?? "",
|
|
3648
|
+
difficulty: g.difficulty ?? "unknown"
|
|
3649
|
+
}));
|
|
3650
|
+
}
|
|
3651
|
+
getGlslPattern(name) {
|
|
3652
|
+
const key = compactKey(name);
|
|
3653
|
+
return this.glsl().find((g) => compactKey(g.id) === key || compactKey(g.name) === key);
|
|
3654
|
+
}
|
|
3655
|
+
// ---- Tutorials -----------------------------------------------------------
|
|
3656
|
+
tutorialIndex() {
|
|
3657
|
+
if (this.tutIndexCache) return this.tutIndexCache;
|
|
3658
|
+
if (this.source.kind === "empty") {
|
|
3659
|
+
this.tutIndexCache = [];
|
|
3660
|
+
return this.tutIndexCache;
|
|
3661
|
+
}
|
|
3662
|
+
const indexPath = join2(this.source.tutorialsDir, "index.json");
|
|
3663
|
+
if (existsSync3(indexPath)) {
|
|
3664
|
+
const data = this.readJson(indexPath);
|
|
3665
|
+
if (Array.isArray(data)) {
|
|
3666
|
+
this.tutIndexCache = data;
|
|
3667
|
+
return this.tutIndexCache;
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3670
|
+
const summaries = [];
|
|
3671
|
+
for (const file of this.listJsonFiles(this.source.tutorialsDir)) {
|
|
3672
|
+
const tut = this.readJson(join2(this.source.tutorialsDir, file));
|
|
3673
|
+
if (tut?.id || tut?.name) {
|
|
3674
|
+
const normalized = {
|
|
3675
|
+
...tut,
|
|
3676
|
+
id: tut.id ?? file.replace(/\.json$/, ""),
|
|
3677
|
+
name: tut.name ?? file
|
|
3678
|
+
};
|
|
3679
|
+
summaries.push(toTutorialSummary(normalized));
|
|
3680
|
+
}
|
|
3681
|
+
}
|
|
3682
|
+
this.tutIndexCache = summaries;
|
|
3683
|
+
return this.tutIndexCache;
|
|
3684
|
+
}
|
|
3685
|
+
tutorialLookup() {
|
|
3686
|
+
if (this.tutLookupCache) return this.tutLookupCache;
|
|
3687
|
+
const map = /* @__PURE__ */ new Map();
|
|
3688
|
+
for (const file of this.listJsonFiles(this.source.tutorialsDir)) {
|
|
3689
|
+
const slug = file.replace(/\.json$/, "");
|
|
3690
|
+
map.set(compactKey(slug), slug);
|
|
3691
|
+
}
|
|
3692
|
+
for (const summary of this.tutorialIndex()) {
|
|
3693
|
+
map.set(compactKey(summary.id), summary.id);
|
|
3694
|
+
map.set(compactKey(summary.name), summary.id);
|
|
3695
|
+
}
|
|
3696
|
+
this.tutLookupCache = map;
|
|
3697
|
+
return map;
|
|
3698
|
+
}
|
|
3699
|
+
listTutorials() {
|
|
3700
|
+
return this.tutorialIndex();
|
|
3701
|
+
}
|
|
3702
|
+
getTutorial(name) {
|
|
3703
|
+
if (this.source.kind === "empty") return void 0;
|
|
3704
|
+
const slug = this.tutorialLookup().get(compactKey(name)) ?? slugify(name);
|
|
3705
|
+
const cached2 = this.tutDocCache.get(slug);
|
|
3706
|
+
if (cached2 !== void 0) return cached2 ?? void 0;
|
|
3707
|
+
const file = join2(this.source.tutorialsDir, `${slug}.json`);
|
|
3708
|
+
const tut = existsSync3(file) ? this.readJson(file) : void 0;
|
|
3709
|
+
this.tutDocCache.set(slug, tut ?? null);
|
|
3710
|
+
return tut;
|
|
3711
|
+
}
|
|
3712
|
+
// ---- Meta ----------------------------------------------------------------
|
|
3713
|
+
get sourceKind() {
|
|
3714
|
+
return this.source.kind;
|
|
3715
|
+
}
|
|
3716
|
+
stats() {
|
|
3717
|
+
return {
|
|
3718
|
+
source: this.source.kind,
|
|
3719
|
+
operators: this.operatorIndex().length,
|
|
3720
|
+
pythonClasses: this.pythonIndex().length,
|
|
3721
|
+
patterns: this.patterns().length,
|
|
3722
|
+
glsl: this.glsl().length,
|
|
3723
|
+
tutorials: this.tutorialIndex().length
|
|
3724
|
+
};
|
|
3725
|
+
}
|
|
3726
|
+
};
|
|
3727
|
+
|
|
3728
|
+
// src/recipes/loader.ts
|
|
3729
|
+
import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
3730
|
+
import { join as join3 } from "path";
|
|
3731
|
+
|
|
3732
|
+
// src/recipes/schema.ts
|
|
3733
|
+
import { z as z41 } from "zod";
|
|
3734
|
+
var RecipeNodeSchema = z41.object({
|
|
3735
|
+
name: z41.string().describe("Unique node name within the recipe (used for wiring)."),
|
|
3736
|
+
type: z41.string().describe("Operator type, e.g. 'noiseTOP'."),
|
|
3737
|
+
parameters: z41.record(z41.string(), z41.unknown()).default({}),
|
|
3738
|
+
parent: z41.string().optional().describe(
|
|
3739
|
+
"Name of another recipe node (a COMP, e.g. a geometryCOMP) to nest this node inside of. The parent must appear earlier in `nodes`. Used to place SOPs inside a Geometry COMP."
|
|
3740
|
+
),
|
|
3741
|
+
render: z41.boolean().optional().describe(
|
|
3742
|
+
"For a SOP nested in a geometryCOMP: make this the rendered geometry. Sets the render/display flags on it and clears its siblings, so the COMP renders this instead of its default torus."
|
|
3743
|
+
),
|
|
3744
|
+
comment: z41.string().optional()
|
|
3745
|
+
});
|
|
3746
|
+
var RecipeConnectionSchema = z41.object({
|
|
3747
|
+
from: z41.string().describe("Source node name."),
|
|
3748
|
+
to: z41.string().describe("Target node name."),
|
|
3749
|
+
from_output: z41.number().int().nonnegative().default(0),
|
|
3750
|
+
to_input: z41.number().int().nonnegative().default(0)
|
|
3751
|
+
});
|
|
3752
|
+
var RecipeParameterSchema = z41.object({
|
|
3753
|
+
name: z41.string().describe("Friendly name of the exposed control."),
|
|
3754
|
+
node: z41.string().describe("Recipe node name the parameter belongs to."),
|
|
3755
|
+
param: z41.string().describe("TD parameter name on that node."),
|
|
3756
|
+
value: z41.unknown().optional(),
|
|
3757
|
+
label: z41.string().optional(),
|
|
3758
|
+
min: z41.number().optional(),
|
|
3759
|
+
max: z41.number().optional(),
|
|
3760
|
+
description: z41.string().optional()
|
|
3761
|
+
});
|
|
3762
|
+
var RecipeGlslUniformSchema = z41.object({
|
|
3763
|
+
node: z41.string().describe("Recipe node name of the GLSL TOP that declares the uniform."),
|
|
3764
|
+
name: z41.string().describe("Uniform name as referenced in the shader, e.g. 'uFeed'."),
|
|
3765
|
+
kind: z41.enum(["float", "vec", "color"]).default("float").describe(
|
|
3766
|
+
"Uniform kind: float (uniform float), vec (uniform vec2/3/4), color (rgba). float/vec use the Vectors page; color uses the Colors page."
|
|
3767
|
+
),
|
|
3768
|
+
value: z41.union([z41.number(), z41.array(z41.number())]).optional().describe("Initial value: a number for float, or an array of components for vec/color."),
|
|
3769
|
+
label: z41.string().optional(),
|
|
3770
|
+
min: z41.number().optional(),
|
|
3771
|
+
max: z41.number().optional(),
|
|
3772
|
+
description: z41.string().optional()
|
|
3773
|
+
});
|
|
3774
|
+
var RecipeSchema = z41.object({
|
|
3775
|
+
id: z41.string(),
|
|
3776
|
+
name: z41.string(),
|
|
3777
|
+
description: z41.string().default(""),
|
|
3778
|
+
tags: z41.array(z41.string()).default([]),
|
|
3779
|
+
difficulty: z41.enum(["beginner", "intermediate", "advanced"]).default("intermediate"),
|
|
3780
|
+
td_version_min: z41.string().default("2023"),
|
|
3781
|
+
nodes: z41.array(RecipeNodeSchema).min(1),
|
|
3782
|
+
connections: z41.array(RecipeConnectionSchema).default([]),
|
|
3783
|
+
parameters: z41.array(RecipeParameterSchema).default([]),
|
|
3784
|
+
glsl_uniforms: z41.array(RecipeGlslUniformSchema).default([]),
|
|
3785
|
+
glsl_code: z41.record(z41.string(), z41.string()).optional(),
|
|
3786
|
+
python_code: z41.record(z41.string(), z41.string()).optional(),
|
|
3787
|
+
preview_description: z41.string().default("")
|
|
3788
|
+
});
|
|
3789
|
+
|
|
3790
|
+
// src/recipes/loader.ts
|
|
3791
|
+
var RecipeLibrary = class {
|
|
3792
|
+
dir;
|
|
3793
|
+
logger;
|
|
3794
|
+
cache;
|
|
3795
|
+
constructor(options = {}) {
|
|
3796
|
+
this.dir = options.dir ?? recipesDir();
|
|
3797
|
+
this.logger = options.logger ?? silentLogger;
|
|
3798
|
+
}
|
|
3799
|
+
load() {
|
|
3800
|
+
if (this.cache) return this.cache;
|
|
3801
|
+
const recipes = [];
|
|
3802
|
+
if (existsSync4(this.dir)) {
|
|
3803
|
+
for (const file of readdirSync2(this.dir)) {
|
|
3804
|
+
if (!file.endsWith(".json")) continue;
|
|
3805
|
+
try {
|
|
3806
|
+
const raw = JSON.parse(readFileSync3(join3(this.dir, file), "utf8"));
|
|
3807
|
+
const parsed = RecipeSchema.safeParse(raw);
|
|
3808
|
+
if (parsed.success) {
|
|
3809
|
+
recipes.push(parsed.data);
|
|
3810
|
+
} else {
|
|
3811
|
+
this.logger.warn(`Invalid recipe ${file}`, { issues: parsed.error.issues.length });
|
|
3812
|
+
}
|
|
3813
|
+
} catch (err) {
|
|
3814
|
+
this.logger.warn(`Failed to read recipe ${file}`, { error: String(err) });
|
|
3815
|
+
}
|
|
3816
|
+
}
|
|
3817
|
+
}
|
|
3818
|
+
this.cache = recipes;
|
|
3819
|
+
return recipes;
|
|
3820
|
+
}
|
|
3821
|
+
all() {
|
|
3822
|
+
return this.load();
|
|
3823
|
+
}
|
|
3824
|
+
list() {
|
|
3825
|
+
return this.load().map((r) => ({
|
|
3826
|
+
id: r.id,
|
|
3827
|
+
name: r.name,
|
|
3828
|
+
description: r.description,
|
|
3829
|
+
tags: r.tags,
|
|
3830
|
+
difficulty: r.difficulty
|
|
3831
|
+
}));
|
|
3832
|
+
}
|
|
3833
|
+
get(id) {
|
|
3834
|
+
const key = compactKey(id);
|
|
3835
|
+
return this.load().find((r) => compactKey(r.id) === key || compactKey(r.name) === key);
|
|
3836
|
+
}
|
|
3837
|
+
/** Finds the best recipe whose tags/name/description match any of the given terms. */
|
|
3838
|
+
findByTags(terms) {
|
|
3839
|
+
const wanted = terms.map((t) => t.toLowerCase()).filter(Boolean);
|
|
3840
|
+
if (wanted.length === 0) return void 0;
|
|
3841
|
+
let best;
|
|
3842
|
+
for (const recipe of this.load()) {
|
|
3843
|
+
const haystack = `${recipe.name} ${recipe.description} ${recipe.tags.join(" ")}`.toLowerCase();
|
|
3844
|
+
let score = 0;
|
|
3845
|
+
for (const term of wanted) {
|
|
3846
|
+
if (recipe.tags.some((tag) => tag.toLowerCase() === term)) score += 2;
|
|
3847
|
+
else if (haystack.includes(term)) score += 1;
|
|
3848
|
+
}
|
|
3849
|
+
if (score > 0 && (!best || score > best.score)) best = { recipe, score };
|
|
3850
|
+
}
|
|
3851
|
+
return best?.recipe;
|
|
3852
|
+
}
|
|
3853
|
+
};
|
|
3854
|
+
|
|
3855
|
+
// src/td-client/touchDesignerClient.ts
|
|
3856
|
+
function extractErrorMessage(json) {
|
|
3857
|
+
if (json && typeof json === "object") {
|
|
3858
|
+
const obj = json;
|
|
3859
|
+
const error = obj.error;
|
|
3860
|
+
if (error && typeof error === "object" && typeof error.message === "string") {
|
|
3861
|
+
return error.message;
|
|
3862
|
+
}
|
|
3863
|
+
if (typeof obj.message === "string") return obj.message;
|
|
3864
|
+
}
|
|
3865
|
+
return void 0;
|
|
3866
|
+
}
|
|
3867
|
+
function segment(path) {
|
|
3868
|
+
return encodeURIComponent(path);
|
|
3869
|
+
}
|
|
3870
|
+
var TouchDesignerClient = class {
|
|
3871
|
+
baseUrl;
|
|
3872
|
+
timeoutMs;
|
|
3873
|
+
logger;
|
|
3874
|
+
token;
|
|
3875
|
+
fetchImpl;
|
|
3876
|
+
constructor(options) {
|
|
3877
|
+
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
3878
|
+
this.timeoutMs = options.timeoutMs ?? 1e4;
|
|
3879
|
+
this.logger = options.logger ?? silentLogger;
|
|
3880
|
+
this.token = options.token;
|
|
3881
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
3882
|
+
}
|
|
3883
|
+
get endpoint() {
|
|
3884
|
+
return this.baseUrl;
|
|
3885
|
+
}
|
|
3886
|
+
async request(method, path, schema, body, query) {
|
|
3887
|
+
const url = new URL(`${this.baseUrl}${path}`);
|
|
3888
|
+
if (query) {
|
|
3889
|
+
for (const [key, value] of Object.entries(query)) {
|
|
3890
|
+
if (value !== void 0) url.searchParams.set(key, String(value));
|
|
3891
|
+
}
|
|
3892
|
+
}
|
|
3893
|
+
const controller = new AbortController();
|
|
3894
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
3895
|
+
let response;
|
|
3896
|
+
try {
|
|
3897
|
+
this.logger.debug(`TD ${method} ${path}`);
|
|
3898
|
+
const headers = {};
|
|
3899
|
+
if (body !== void 0) headers["content-type"] = "application/json";
|
|
3900
|
+
if (this.token) headers.authorization = `Bearer ${this.token}`;
|
|
3901
|
+
response = await this.fetchImpl(url, {
|
|
3902
|
+
method,
|
|
3903
|
+
signal: controller.signal,
|
|
3904
|
+
headers: Object.keys(headers).length > 0 ? headers : void 0,
|
|
3905
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
3906
|
+
});
|
|
3907
|
+
} catch (err) {
|
|
3908
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
3909
|
+
throw new TdTimeoutError(
|
|
3910
|
+
`TouchDesigner request timed out after ${this.timeoutMs}ms (${method} ${path}).`,
|
|
3911
|
+
{ cause: err }
|
|
3912
|
+
);
|
|
3913
|
+
}
|
|
3914
|
+
throw new TdConnectionError(
|
|
3915
|
+
`Cannot reach TouchDesigner at ${this.baseUrl}. Make sure TD is running with the tdmcp bridge (WebServer DAT) installed and listening on that port.`,
|
|
3916
|
+
{ cause: err }
|
|
3917
|
+
);
|
|
3918
|
+
} finally {
|
|
3919
|
+
clearTimeout(timer);
|
|
3920
|
+
}
|
|
3921
|
+
const text = await response.text();
|
|
3922
|
+
let json;
|
|
3923
|
+
if (text) {
|
|
3924
|
+
try {
|
|
3925
|
+
json = JSON.parse(text);
|
|
3926
|
+
} catch {
|
|
3927
|
+
json = void 0;
|
|
3928
|
+
}
|
|
3929
|
+
}
|
|
3930
|
+
if (!response.ok) {
|
|
3931
|
+
const message = extractErrorMessage(json) ?? `TouchDesigner bridge returned HTTP ${response.status} for ${method} ${path}.`;
|
|
3932
|
+
throw new TdApiError(message, { status: response.status });
|
|
3933
|
+
}
|
|
3934
|
+
const envelope = ApiEnvelopeSchema.safeParse(json);
|
|
3935
|
+
if (!envelope.success) {
|
|
3936
|
+
throw new TdApiError(`Malformed response from TouchDesigner bridge for ${method} ${path}.`, {
|
|
3937
|
+
status: response.status
|
|
3938
|
+
});
|
|
3939
|
+
}
|
|
3940
|
+
if (!envelope.data.ok) {
|
|
3941
|
+
throw new TdApiError(
|
|
3942
|
+
envelope.data.error?.message ?? `TouchDesigner reported an error for ${method} ${path}.`,
|
|
3943
|
+
{ status: response.status, apiCode: envelope.data.error?.code }
|
|
3944
|
+
);
|
|
3945
|
+
}
|
|
3946
|
+
const parsed = schema.safeParse(envelope.data.data);
|
|
3947
|
+
if (!parsed.success) {
|
|
3948
|
+
throw new TdApiError(
|
|
3949
|
+
`Unexpected data shape from TouchDesigner bridge for ${method} ${path}: ${parsed.error.message}`,
|
|
3950
|
+
{ status: response.status }
|
|
3951
|
+
);
|
|
3952
|
+
}
|
|
3953
|
+
return parsed.data;
|
|
3954
|
+
}
|
|
3955
|
+
getInfo() {
|
|
3956
|
+
return this.request("GET", "/api/info", InfoSchema);
|
|
3957
|
+
}
|
|
3958
|
+
createNode(input) {
|
|
3959
|
+
return this.request("POST", "/api/nodes", NodeRefSchema, CreateNodeInputSchema.parse(input));
|
|
3960
|
+
}
|
|
3961
|
+
deleteNode(path) {
|
|
3962
|
+
return this.request("DELETE", `/api/nodes/${segment(path)}`, DeleteResultSchema);
|
|
3963
|
+
}
|
|
3964
|
+
getNodes(parentPath) {
|
|
3965
|
+
return this.request("GET", "/api/nodes", NodeListSchema, void 0, { parent: parentPath });
|
|
3966
|
+
}
|
|
3967
|
+
getNode(path) {
|
|
3968
|
+
return this.request("GET", `/api/nodes/${segment(path)}`, NodeDetailSchema);
|
|
3969
|
+
}
|
|
3970
|
+
updateNodeParameters(path, parameters) {
|
|
3971
|
+
return this.request("PATCH", `/api/nodes/${segment(path)}`, NodeDetailSchema, { parameters });
|
|
3972
|
+
}
|
|
3973
|
+
executePythonScript(script, returnOutput = true) {
|
|
3974
|
+
return this.request("POST", "/api/exec", ExecResultSchema, {
|
|
3975
|
+
script,
|
|
3976
|
+
return_output: returnOutput
|
|
3977
|
+
});
|
|
3978
|
+
}
|
|
3979
|
+
execNodeMethod(path, method, args = [], kwargs = {}) {
|
|
3980
|
+
return this.request("POST", `/api/nodes/${segment(path)}/method`, MethodResultSchema, {
|
|
3981
|
+
method,
|
|
3982
|
+
args,
|
|
3983
|
+
kwargs
|
|
3984
|
+
});
|
|
3985
|
+
}
|
|
3986
|
+
getNodeErrors(path) {
|
|
3987
|
+
return this.request("GET", `/api/nodes/${segment(path)}/errors`, NodeErrorsSchema);
|
|
3988
|
+
}
|
|
3989
|
+
getPreview(path, width = 640, height = 360) {
|
|
3990
|
+
return this.request("GET", `/api/preview/${segment(path)}`, PreviewSchema, void 0, {
|
|
3991
|
+
width,
|
|
3992
|
+
height
|
|
3993
|
+
});
|
|
3994
|
+
}
|
|
3995
|
+
batch(operations) {
|
|
3996
|
+
return this.request("POST", "/api/batch", BatchResultSchema, { operations });
|
|
3997
|
+
}
|
|
3998
|
+
getNetworkErrors(path) {
|
|
3999
|
+
return this.request("GET", `/api/network/${segment(path)}/errors`, NodeErrorsSchema);
|
|
4000
|
+
}
|
|
4001
|
+
getNetworkTopology(path, recursive = false) {
|
|
4002
|
+
return this.request(
|
|
4003
|
+
"GET",
|
|
4004
|
+
`/api/network/${segment(path)}/topology`,
|
|
4005
|
+
TopologySchema,
|
|
4006
|
+
void 0,
|
|
4007
|
+
recursive ? { recursive: true } : void 0
|
|
4008
|
+
);
|
|
4009
|
+
}
|
|
4010
|
+
getNetworkPerformance(path) {
|
|
4011
|
+
return this.request("GET", `/api/network/${segment(path)}/performance`, PerformanceSchema);
|
|
4012
|
+
}
|
|
4013
|
+
};
|
|
4014
|
+
|
|
4015
|
+
// src/utils/config.ts
|
|
4016
|
+
import { z as z42 } from "zod";
|
|
4017
|
+
var ConfigSchema = z42.object({
|
|
4018
|
+
/** TouchDesigner bridge host. */
|
|
4019
|
+
tdHost: z42.string().min(1).default("127.0.0.1"),
|
|
4020
|
+
/** TouchDesigner bridge port (WebServer DAT). */
|
|
4021
|
+
tdPort: z42.coerce.number().int().positive().max(65535).default(9980),
|
|
4022
|
+
/** MCP transport: `stdio` (default, for local clients) or `http` (Streamable HTTP, loopback-only). */
|
|
4023
|
+
transport: z42.enum(["stdio", "http"]).default("stdio"),
|
|
4024
|
+
/** Log verbosity (written to stderr). */
|
|
4025
|
+
logLevel: z42.enum(["debug", "info", "warn", "error", "silent"]).default("info"),
|
|
4026
|
+
/** Per-request timeout against the TD bridge, in milliseconds. */
|
|
4027
|
+
requestTimeoutMs: z42.coerce.number().int().positive().default(1e4),
|
|
4028
|
+
/** HTTP transport port (only used when transport=http). */
|
|
4029
|
+
httpPort: z42.coerce.number().int().positive().max(65535).default(3939),
|
|
4030
|
+
/** Subscribe to TD WebSocket events and forward them as MCP logging notifications. */
|
|
4031
|
+
events: z42.enum(["on", "off"]).default("on"),
|
|
4032
|
+
/**
|
|
4033
|
+
* Raw Python escape-hatch tools (`execute_python_script`, `exec_node_method`).
|
|
4034
|
+
* Set to "off" to lock them out for restricted setups; on by default.
|
|
4035
|
+
*/
|
|
4036
|
+
rawPython: z42.enum(["on", "off"]).default("on"),
|
|
4037
|
+
/**
|
|
4038
|
+
* Optional shared bearer token for the TD bridge. When set, the server sends it
|
|
4039
|
+
* as `Authorization: Bearer <token>` and the bridge requires a match. Leave unset
|
|
4040
|
+
* (default) for the zero-config local flow. Set the SAME value in TouchDesigner's
|
|
4041
|
+
* environment (`TDMCP_BRIDGE_TOKEN`) to turn enforcement on.
|
|
4042
|
+
*/
|
|
4043
|
+
bridgeToken: z42.string().min(1).optional()
|
|
4044
|
+
});
|
|
4045
|
+
function loadConfig(env = process.env) {
|
|
4046
|
+
return ConfigSchema.parse({
|
|
4047
|
+
tdHost: env.TDMCP_TD_HOST,
|
|
4048
|
+
tdPort: env.TDMCP_TD_PORT,
|
|
4049
|
+
transport: env.TDMCP_TRANSPORT,
|
|
4050
|
+
logLevel: env.TDMCP_LOG_LEVEL,
|
|
4051
|
+
requestTimeoutMs: env.TDMCP_REQUEST_TIMEOUT_MS,
|
|
4052
|
+
httpPort: env.TDMCP_HTTP_PORT,
|
|
4053
|
+
events: env.TDMCP_EVENTS,
|
|
4054
|
+
rawPython: env.TDMCP_RAW_PYTHON,
|
|
4055
|
+
bridgeToken: env.TDMCP_BRIDGE_TOKEN || void 0
|
|
4056
|
+
});
|
|
4057
|
+
}
|
|
4058
|
+
function tdBaseUrl(config) {
|
|
4059
|
+
return `http://${config.tdHost}:${config.tdPort}`;
|
|
4060
|
+
}
|
|
4061
|
+
|
|
4062
|
+
// src/server/connectionManager.ts
|
|
4063
|
+
var ConnectionManager = class {
|
|
4064
|
+
client;
|
|
4065
|
+
constructor(config, logger) {
|
|
4066
|
+
this.client = new TouchDesignerClient({
|
|
4067
|
+
baseUrl: tdBaseUrl(config),
|
|
4068
|
+
timeoutMs: config.requestTimeoutMs,
|
|
4069
|
+
token: config.bridgeToken,
|
|
4070
|
+
logger
|
|
4071
|
+
});
|
|
4072
|
+
}
|
|
4073
|
+
async isReachable() {
|
|
4074
|
+
try {
|
|
4075
|
+
await this.client.getInfo();
|
|
4076
|
+
return true;
|
|
4077
|
+
} catch {
|
|
4078
|
+
return false;
|
|
4079
|
+
}
|
|
4080
|
+
}
|
|
4081
|
+
};
|
|
4082
|
+
|
|
4083
|
+
// src/server/context.ts
|
|
4084
|
+
function buildToolContext(config, overrides = {}) {
|
|
4085
|
+
const logger = overrides.logger ?? createLogger(config.logLevel);
|
|
4086
|
+
const connection = overrides.connection ?? new ConnectionManager(config, logger);
|
|
4087
|
+
const knowledge = overrides.knowledge ?? new KnowledgeBase({ logger });
|
|
4088
|
+
const recipes = overrides.recipes ?? new RecipeLibrary({ logger });
|
|
4089
|
+
return {
|
|
4090
|
+
client: connection.client,
|
|
4091
|
+
knowledge,
|
|
4092
|
+
recipes,
|
|
4093
|
+
logger,
|
|
4094
|
+
allowRawPython: config.rawPython !== "off"
|
|
4095
|
+
};
|
|
4096
|
+
}
|
|
4097
|
+
|
|
4098
|
+
// src/server/tdmcpServer.ts
|
|
4099
|
+
var INSTRUCTIONS = `tdmcp lets you build visual systems in TouchDesigner.
|
|
4100
|
+
|
|
4101
|
+
Workflow:
|
|
4102
|
+
1. Call get_td_info first to confirm the bridge is reachable.
|
|
4103
|
+
2. Consult the knowledge base resources (tdmcp://operators/..., tdmcp://recipes/...) before creating nodes \u2014 never invent operator types.
|
|
4104
|
+
3. Build with the highest-level tool that fits, dropping to Layer 2/3 for fine control.
|
|
4105
|
+
4. After building, check get_td_node_errors and capture get_preview so the artist can see the result.
|
|
4106
|
+
5. Prefer structured inspection/edit tools (find_td_nodes, get_td_node_parameters, summarize_td_errors, compare_td_nodes, snapshot_td_graph, update_td_node_parameters) and process their structuredContent with code. Treat execute_python_script and exec_node_method as a last resort, only when no structured tool fits.
|
|
4107
|
+
|
|
4108
|
+
The server stays usable even when TouchDesigner is offline; tools return a friendly error in that case.`;
|
|
4109
|
+
function createTdmcpServer(config, overrides = {}) {
|
|
4110
|
+
const ctx = buildToolContext(config, overrides);
|
|
4111
|
+
const { knowledge, recipes, logger } = ctx;
|
|
4112
|
+
const server = new McpServer(
|
|
4113
|
+
{ name: "tdmcp", version: getVersion() },
|
|
4114
|
+
{ instructions: INSTRUCTIONS }
|
|
4115
|
+
);
|
|
4116
|
+
registerAllTools(server, ctx);
|
|
4117
|
+
registerAllResources(server, { knowledge, recipes, logger });
|
|
4118
|
+
registerAllPrompts(server, { knowledge, recipes, logger });
|
|
4119
|
+
logger.info("tdmcp server initialized", {
|
|
4120
|
+
version: getVersion(),
|
|
4121
|
+
knowledge: knowledge.stats(),
|
|
4122
|
+
recipes: recipes.list().length
|
|
4123
|
+
});
|
|
4124
|
+
return server;
|
|
4125
|
+
}
|
|
4126
|
+
|
|
4127
|
+
// src/server/transportFactory.ts
|
|
4128
|
+
import { randomUUID } from "crypto";
|
|
4129
|
+
import { createServer } from "http";
|
|
4130
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4131
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
4132
|
+
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
4133
|
+
|
|
4134
|
+
// src/td-client/eventStream.ts
|
|
4135
|
+
var HIGH_FREQUENCY = /* @__PURE__ */ new Set(["timeline.frame", "node.cook"]);
|
|
4136
|
+
function parseEventMessage(raw, includeHighFrequency = false) {
|
|
4137
|
+
if (typeof raw !== "string") return void 0;
|
|
4138
|
+
let parsed;
|
|
4139
|
+
try {
|
|
4140
|
+
parsed = JSON.parse(raw);
|
|
4141
|
+
} catch {
|
|
4142
|
+
return void 0;
|
|
4143
|
+
}
|
|
4144
|
+
if (!parsed || typeof parsed !== "object") return void 0;
|
|
4145
|
+
const event = parsed.event;
|
|
4146
|
+
if (typeof event !== "string") return void 0;
|
|
4147
|
+
if (!includeHighFrequency && HIGH_FREQUENCY.has(event)) return void 0;
|
|
4148
|
+
return { event, data: parsed.data };
|
|
4149
|
+
}
|
|
4150
|
+
var TdEventStream = class {
|
|
4151
|
+
socket;
|
|
4152
|
+
closed = false;
|
|
4153
|
+
backoffMs = 1e3;
|
|
4154
|
+
url;
|
|
4155
|
+
onEvent;
|
|
4156
|
+
logger;
|
|
4157
|
+
includeHighFrequency;
|
|
4158
|
+
constructor(options) {
|
|
4159
|
+
this.url = options.url;
|
|
4160
|
+
this.onEvent = options.onEvent;
|
|
4161
|
+
this.logger = options.logger ?? silentLogger;
|
|
4162
|
+
this.includeHighFrequency = options.includeHighFrequency ?? false;
|
|
4163
|
+
}
|
|
4164
|
+
start() {
|
|
4165
|
+
this.connect();
|
|
4166
|
+
}
|
|
4167
|
+
connect() {
|
|
4168
|
+
if (this.closed) return;
|
|
4169
|
+
let socket;
|
|
4170
|
+
try {
|
|
4171
|
+
socket = new WebSocket(this.url);
|
|
4172
|
+
} catch {
|
|
4173
|
+
this.scheduleReconnect();
|
|
4174
|
+
return;
|
|
4175
|
+
}
|
|
4176
|
+
this.socket = socket;
|
|
4177
|
+
socket.addEventListener("open", () => {
|
|
4178
|
+
this.backoffMs = 1e3;
|
|
4179
|
+
this.logger.debug("event stream connected", { url: this.url });
|
|
4180
|
+
});
|
|
4181
|
+
socket.addEventListener("message", (ev) => {
|
|
4182
|
+
const event = parseEventMessage(ev.data, this.includeHighFrequency);
|
|
4183
|
+
if (!event) return;
|
|
4184
|
+
try {
|
|
4185
|
+
this.onEvent(event);
|
|
4186
|
+
} catch (err) {
|
|
4187
|
+
this.logger.debug("event handler failed", { error: String(err) });
|
|
4188
|
+
}
|
|
4189
|
+
});
|
|
4190
|
+
socket.addEventListener("close", () => this.scheduleReconnect());
|
|
4191
|
+
socket.addEventListener("error", () => {
|
|
4192
|
+
});
|
|
4193
|
+
}
|
|
4194
|
+
scheduleReconnect() {
|
|
4195
|
+
if (this.closed) return;
|
|
4196
|
+
const delay = this.backoffMs;
|
|
4197
|
+
this.backoffMs = Math.min(this.backoffMs * 2, 3e4);
|
|
4198
|
+
const timer = setTimeout(() => this.connect(), delay);
|
|
4199
|
+
timer.unref?.();
|
|
4200
|
+
}
|
|
4201
|
+
close() {
|
|
4202
|
+
this.closed = true;
|
|
4203
|
+
try {
|
|
4204
|
+
this.socket?.close();
|
|
4205
|
+
} catch {
|
|
4206
|
+
}
|
|
4207
|
+
}
|
|
4208
|
+
};
|
|
4209
|
+
|
|
4210
|
+
// src/server/transportFactory.ts
|
|
4211
|
+
var MCP_PATH = "/mcp";
|
|
4212
|
+
function forwardEvent(server, event) {
|
|
4213
|
+
const level = event.event === "node.error" ? "error" : "info";
|
|
4214
|
+
void server.sendLoggingMessage({ level, logger: "touchdesigner", data: event }).catch(() => {
|
|
4215
|
+
});
|
|
4216
|
+
}
|
|
4217
|
+
function createEventStream(config, logger, onEvent) {
|
|
4218
|
+
if (config.events !== "on") return void 0;
|
|
4219
|
+
const url = `${tdBaseUrl(config).replace(/^http/, "ws")}/`;
|
|
4220
|
+
const stream = new TdEventStream({ url, logger, onEvent });
|
|
4221
|
+
stream.start();
|
|
4222
|
+
return stream;
|
|
4223
|
+
}
|
|
4224
|
+
function readBody(req) {
|
|
4225
|
+
return new Promise((resolve3, reject) => {
|
|
4226
|
+
const chunks = [];
|
|
4227
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
4228
|
+
req.on("end", () => {
|
|
4229
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
4230
|
+
if (!raw) return resolve3(void 0);
|
|
4231
|
+
try {
|
|
4232
|
+
resolve3(JSON.parse(raw));
|
|
4233
|
+
} catch (err) {
|
|
4234
|
+
reject(err);
|
|
4235
|
+
}
|
|
4236
|
+
});
|
|
4237
|
+
req.on("error", reject);
|
|
4238
|
+
});
|
|
4239
|
+
}
|
|
4240
|
+
function sendJson(res, status, payload) {
|
|
4241
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
4242
|
+
res.end(JSON.stringify(payload));
|
|
4243
|
+
}
|
|
4244
|
+
function startStdio(createMcpServer, config, logger) {
|
|
4245
|
+
const server = createMcpServer();
|
|
4246
|
+
const transport = new StdioServerTransport();
|
|
4247
|
+
void server.connect(transport);
|
|
4248
|
+
logger.info("tdmcp connected over stdio");
|
|
4249
|
+
const events = createEventStream(config, logger, (event) => forwardEvent(server, event));
|
|
4250
|
+
return {
|
|
4251
|
+
close: async () => {
|
|
4252
|
+
events?.close();
|
|
4253
|
+
await server.close();
|
|
4254
|
+
}
|
|
4255
|
+
};
|
|
4256
|
+
}
|
|
4257
|
+
function startHttp(createMcpServer, config, logger) {
|
|
4258
|
+
const transports = /* @__PURE__ */ new Map();
|
|
4259
|
+
const servers = /* @__PURE__ */ new Map();
|
|
4260
|
+
const events = createEventStream(config, logger, (event) => {
|
|
4261
|
+
for (const sessionServer of servers.values()) forwardEvent(sessionServer, event);
|
|
4262
|
+
});
|
|
4263
|
+
const httpServer = createServer((req, res) => {
|
|
4264
|
+
void handle(req, res).catch((err) => {
|
|
4265
|
+
logger.error("HTTP request failed", { error: String(err) });
|
|
4266
|
+
if (!res.headersSent) {
|
|
4267
|
+
sendJson(res, 500, {
|
|
4268
|
+
jsonrpc: "2.0",
|
|
4269
|
+
error: { code: -32603, message: "Internal server error" },
|
|
4270
|
+
id: null
|
|
4271
|
+
});
|
|
4272
|
+
}
|
|
4273
|
+
});
|
|
4274
|
+
});
|
|
4275
|
+
async function handle(req, res) {
|
|
4276
|
+
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
4277
|
+
if (url.pathname !== MCP_PATH) {
|
|
4278
|
+
sendJson(res, 404, { error: "Not found. MCP endpoint is at /mcp." });
|
|
4279
|
+
return;
|
|
4280
|
+
}
|
|
4281
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
4282
|
+
const existing = typeof sessionId === "string" ? transports.get(sessionId) : void 0;
|
|
4283
|
+
if (req.method === "POST") {
|
|
4284
|
+
const body = await readBody(req);
|
|
4285
|
+
if (!existing) {
|
|
4286
|
+
if (!isInitializeRequest(body)) {
|
|
4287
|
+
sendJson(res, 400, {
|
|
4288
|
+
jsonrpc: "2.0",
|
|
4289
|
+
error: { code: -32e3, message: "No valid session; send an initialize request first." },
|
|
4290
|
+
id: null
|
|
4291
|
+
});
|
|
4292
|
+
return;
|
|
4293
|
+
}
|
|
4294
|
+
const sessionServer = createMcpServer();
|
|
4295
|
+
const created = new StreamableHTTPServerTransport({
|
|
4296
|
+
sessionIdGenerator: () => randomUUID(),
|
|
4297
|
+
onsessioninitialized: (id) => {
|
|
4298
|
+
transports.set(id, created);
|
|
4299
|
+
servers.set(id, sessionServer);
|
|
4300
|
+
},
|
|
4301
|
+
// Reject Host headers other than loopback to block DNS-rebinding attacks.
|
|
4302
|
+
enableDnsRebindingProtection: true,
|
|
4303
|
+
allowedHosts: [`127.0.0.1:${config.httpPort}`, `localhost:${config.httpPort}`]
|
|
4304
|
+
});
|
|
4305
|
+
created.onclose = () => {
|
|
4306
|
+
if (created.sessionId) {
|
|
4307
|
+
transports.delete(created.sessionId);
|
|
4308
|
+
servers.delete(created.sessionId);
|
|
4309
|
+
}
|
|
4310
|
+
};
|
|
4311
|
+
await sessionServer.connect(created);
|
|
4312
|
+
await created.handleRequest(req, res, body);
|
|
4313
|
+
return;
|
|
4314
|
+
}
|
|
4315
|
+
await existing.handleRequest(req, res, body);
|
|
4316
|
+
return;
|
|
4317
|
+
}
|
|
4318
|
+
if (req.method === "GET" || req.method === "DELETE") {
|
|
4319
|
+
if (!existing) {
|
|
4320
|
+
sendJson(res, 400, { error: "Unknown or missing mcp-session-id." });
|
|
4321
|
+
return;
|
|
4322
|
+
}
|
|
4323
|
+
await existing.handleRequest(req, res);
|
|
4324
|
+
return;
|
|
4325
|
+
}
|
|
4326
|
+
sendJson(res, 405, { error: "Method not allowed." });
|
|
4327
|
+
}
|
|
4328
|
+
httpServer.listen(config.httpPort, "127.0.0.1", () => {
|
|
4329
|
+
logger.info("tdmcp listening over Streamable HTTP", { port: config.httpPort, path: MCP_PATH });
|
|
4330
|
+
});
|
|
4331
|
+
return {
|
|
4332
|
+
close: async () => {
|
|
4333
|
+
events?.close();
|
|
4334
|
+
for (const transport of transports.values()) await transport.close();
|
|
4335
|
+
transports.clear();
|
|
4336
|
+
servers.clear();
|
|
4337
|
+
await new Promise((resolve3) => httpServer.close(() => resolve3()));
|
|
4338
|
+
}
|
|
4339
|
+
};
|
|
4340
|
+
}
|
|
4341
|
+
async function startTransport(createMcpServer, config, logger) {
|
|
4342
|
+
if (config.transport === "http") {
|
|
4343
|
+
return startHttp(createMcpServer, config, logger);
|
|
4344
|
+
}
|
|
4345
|
+
return startStdio(createMcpServer, config, logger);
|
|
4346
|
+
}
|
|
4347
|
+
|
|
4348
|
+
// src/index.ts
|
|
4349
|
+
async function main() {
|
|
4350
|
+
const argv = process.argv.slice(2);
|
|
4351
|
+
if (argv[0] === "install-bridge") {
|
|
4352
|
+
runInstallBridge(argv.slice(1));
|
|
4353
|
+
return;
|
|
4354
|
+
}
|
|
4355
|
+
const config = loadConfig();
|
|
4356
|
+
const logger = createLogger(config.logLevel);
|
|
4357
|
+
try {
|
|
4358
|
+
const handle = await startTransport(
|
|
4359
|
+
() => createTdmcpServer(config, { logger }),
|
|
4360
|
+
config,
|
|
4361
|
+
logger
|
|
4362
|
+
);
|
|
4363
|
+
const shutdown = () => {
|
|
4364
|
+
logger.info("tdmcp shutting down");
|
|
4365
|
+
void handle.close().finally(() => process.exit(0));
|
|
4366
|
+
};
|
|
4367
|
+
process.on("SIGINT", shutdown);
|
|
4368
|
+
process.on("SIGTERM", shutdown);
|
|
4369
|
+
} catch (err) {
|
|
4370
|
+
logger.error("Failed to start tdmcp server", { error: String(err) });
|
|
4371
|
+
process.exitCode = 1;
|
|
4372
|
+
}
|
|
4373
|
+
}
|
|
4374
|
+
void main();
|
|
4375
|
+
//# sourceMappingURL=index.js.map
|