@hybridlabor-api/aos 4.4.1 → 4.4.2-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/firecrawl/SKILL.md +149 -0
- package/.agents/skills/firecrawl/rules/install.md +82 -0
- package/.agents/skills/firecrawl/rules/security.md +26 -0
- package/.agents/skills/firecrawl-agent/SKILL.md +58 -0
- package/.agents/skills/firecrawl-build/SKILL.md +39 -0
- package/.agents/skills/firecrawl-build-interact/SKILL.md +68 -0
- package/.agents/skills/firecrawl-build-onboarding/SKILL.md +103 -0
- package/.agents/skills/firecrawl-build-onboarding/references/auth-flow.md +39 -0
- package/.agents/skills/firecrawl-build-onboarding/references/project-setup.md +20 -0
- package/.agents/skills/firecrawl-build-onboarding/references/sdk-installation.md +17 -0
- package/.agents/skills/firecrawl-build-scrape/SKILL.md +69 -0
- package/.agents/skills/firecrawl-build-search/SKILL.md +69 -0
- package/.agents/skills/firecrawl-crawl/SKILL.md +59 -0
- package/.agents/skills/firecrawl-download/SKILL.md +70 -0
- package/.agents/skills/firecrawl-interact/SKILL.md +84 -0
- package/.agents/skills/firecrawl-map/SKILL.md +51 -0
- package/.agents/skills/firecrawl-scrape/SKILL.md +69 -0
- package/.agents/skills/firecrawl-search/SKILL.md +60 -0
- package/CLAUDE.md +573 -0
- package/README.de.md +0 -8
- package/README.md +0 -8
- package/README.pt.md +0 -8
- package/assets/brand/ao-ant-mark.svg +91 -0
- package/assets/brand/bdb-core-mark.svg +34 -0
- package/assets/brand/heimdall-guardian-mark.svg +16 -0
- package/assets/brand/memb-crystal-mark.svg +18 -0
- package/bin/aos-config.mjs +89 -0
- package/bin/aos-dashboard.mjs +608 -0
- package/bin/aos-uninstall.mjs +227 -0
- package/docs/sessions/audit-2026-09-14-silent-failures.md +114 -0
- package/docs/skills_table.md +0 -3
- package/installer.js +471 -237
- package/mcps/RhinoMCP/cc-plugin/.claude/settings.json +10 -0
- package/mcps/after-effects-mcp/build/index.js +840 -0
- package/mcps/after-effects-mcp/build/scripts/applyEffect.jsx +153 -0
- package/mcps/after-effects-mcp/build/scripts/applyEffectTemplate.jsx +218 -0
- package/mcps/after-effects-mcp/build/scripts/createComposition.jsx +71 -0
- package/mcps/after-effects-mcp/build/scripts/createShapeLayer.jsx +147 -0
- package/mcps/after-effects-mcp/build/scripts/createSolidLayer.jsx +114 -0
- package/mcps/after-effects-mcp/build/scripts/createTextLayer.jsx +115 -0
- package/mcps/after-effects-mcp/build/scripts/getLayerInfo.jsx +192 -0
- package/mcps/after-effects-mcp/build/scripts/getProjectInfo.jsx +90 -0
- package/mcps/after-effects-mcp/build/scripts/listCompositions.jsx +50 -0
- package/mcps/after-effects-mcp/build/scripts/mcp-bridge-auto.jsx +1773 -0
- package/mcps/after-effects-mcp/build/scripts/setLayerProperties.jsx +160 -0
- package/mcps/computer-use-mcp/dist/client.d.ts +150 -0
- package/mcps/computer-use-mcp/dist/client.js +136 -0
- package/mcps/computer-use-mcp/dist/entrypoint.d.ts +16 -0
- package/mcps/computer-use-mcp/dist/entrypoint.js +26 -0
- package/mcps/computer-use-mcp/dist/native.d.ts +212 -0
- package/mcps/computer-use-mcp/dist/native.js +50 -0
- package/mcps/computer-use-mcp/dist/server.d.ts +32 -0
- package/mcps/computer-use-mcp/dist/server.js +342 -0
- package/mcps/computer-use-mcp/dist/session.d.ts +101 -0
- package/mcps/computer-use-mcp/dist/session.js +2372 -0
- package/package.json +13 -4
- package/skills/global_config/aos-project-init/SKILL.md +97 -130
- package/skills/global_config/aos-project-init/assets/openwikiignore.template +62 -0
- package/skills/global_config/aos-project-init/scripts/aos-project-doctor.mjs +38 -1
- package/skills/global_config/aos-setup/SKILL.md +98 -8
- package/skills/global_config/aos-setup/scripts/aos-doctor.mjs +21 -2
- package/skills/global_config/ask-tim/SKILL.md +2 -7
- package/skills/global_config/openwiki-skill/scripts/openwiki_daemon.py +65 -12
- package/bin/setup-saas.mjs +0 -792
- package/mcps/blender-mcp-server/.dockerignore +0 -19
- package/mcps/blender-mcp-server/.github/workflows/ci.yml +0 -67
- package/mcps/blender-mcp-server/.github/workflows/publish-pypi.yml +0 -54
- package/mcps/blender-mcp-server/CHANGELOG.md +0 -64
- package/mcps/blender-mcp-server/CONTRIBUTING.md +0 -107
- package/mcps/blender-mcp-server/Dockerfile +0 -19
- package/mcps/blender-mcp-server/LICENSE +0 -21
- package/mcps/blender-mcp-server/README.md +0 -409
- package/mcps/blender-mcp-server/addon/__init__.py +0 -1440
- package/mcps/blender-mcp-server/addon/models.py +0 -223
- package/mcps/blender-mcp-server/pyproject.toml +0 -120
- package/mcps/blender-mcp-server/scripts/blender_bridge_request.py +0 -71
- package/mcps/blender-mcp-server/scripts/blender_create_test_cube.py +0 -49
- package/mcps/blender-mcp-server/scripts/blender_scene_info.py +0 -38
- package/mcps/blender-mcp-server/scripts/build_addon_zip.sh +0 -29
- package/mcps/blender-mcp-server/scripts/demos/README.md +0 -186
- package/mcps/blender-mcp-server/scripts/demos/dam_break_scene.py +0 -276
- package/mcps/blender-mcp-server/scripts/demos/open_dam_break_gui.py +0 -33
- package/mcps/blender-mcp-server/scripts/demos/pipe_movement_study.py +0 -518
- package/mcps/blender-mcp-server/scripts/demos/pipe_pressure_failure_scene.py +0 -806
- package/mcps/blender-mcp-server/scripts/demos/procedural_dam_break_scene.py +0 -600
- package/mcps/blender-mcp-server/scripts/demos/run_dam_break.py +0 -510
- package/mcps/blender-mcp-server/scripts/launch_blender_gui.py +0 -41
- package/mcps/blender-mcp-server/scripts/library/README.md +0 -64
- package/mcps/blender-mcp-server/scripts/library/apply_transforms.py +0 -46
- package/mcps/blender-mcp-server/scripts/library/camera.py +0 -51
- package/mcps/blender-mcp-server/scripts/library/collections.py +0 -65
- package/mcps/blender-mcp-server/scripts/library/create_mesh.py +0 -41
- package/mcps/blender-mcp-server/scripts/library/effector.py +0 -48
- package/mcps/blender-mcp-server/scripts/library/fluid_domain.py +0 -80
- package/mcps/blender-mcp-server/scripts/library/fluid_inflow.py +0 -98
- package/mcps/blender-mcp-server/scripts/library/frame_range.py +0 -35
- package/mcps/blender-mcp-server/scripts/library/keyframes.py +0 -59
- package/mcps/blender-mcp-server/scripts/library/rigid_body.py +0 -52
- package/mcps/blender-mcp-server/scripts/library/save_blend.py +0 -40
- package/mcps/blender-mcp-server/scripts/server_start.sh +0 -26
- package/mcps/blender-mcp-server/src/blender_mcp_server/__init__.py +0 -3
- package/mcps/blender-mcp-server/src/blender_mcp_server/headless.py +0 -330
- package/mcps/blender-mcp-server/src/blender_mcp_server/server.py +0 -606
- package/mcps/davinci-mcp-professional/.bandit +0 -35
- package/mcps/davinci-mcp-professional/.claude/commands/security-review.md +0 -191
- package/mcps/davinci-mcp-professional/.claude/settings.json +0 -1
- package/mcps/davinci-mcp-professional/.editorconfig +0 -27
- package/mcps/davinci-mcp-professional/.gitattributes +0 -207
- package/mcps/davinci-mcp-professional/.github/CODEOWNERS +0 -2
- package/mcps/davinci-mcp-professional/.github/ISSUE_TEMPLATE/bug_report.md +0 -33
- package/mcps/davinci-mcp-professional/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
- package/mcps/davinci-mcp-professional/.github/PULL_REQUEST_TEMPLATE.md +0 -20
- package/mcps/davinci-mcp-professional/.github/SECURITY.md +0 -97
- package/mcps/davinci-mcp-professional/.github/dependabot.yml +0 -27
- package/mcps/davinci-mcp-professional/.github/workflows/ci.yml +0 -39
- package/mcps/davinci-mcp-professional/ATTRIBUTION.md +0 -4
- package/mcps/davinci-mcp-professional/AUTHORS.md +0 -9
- package/mcps/davinci-mcp-professional/BUGS.md +0 -55
- package/mcps/davinci-mcp-professional/CLAUDE.md +0 -229
- package/mcps/davinci-mcp-professional/CONTRIBUTING.md +0 -51
- package/mcps/davinci-mcp-professional/COPYING.md +0 -228
- package/mcps/davinci-mcp-professional/Doxyfile +0 -379
- package/mcps/davinci-mcp-professional/README.md +0 -143
- package/mcps/davinci-mcp-professional/SECURITY.md +0 -40
- package/mcps/davinci-mcp-professional/USING.md +0 -301
- package/mcps/davinci-mcp-professional/VERSION.md +0 -13
- package/mcps/davinci-mcp-professional/build.py +0 -169
- package/mcps/davinci-mcp-professional/claude_desktop_config_template.json +0 -11
- package/mcps/davinci-mcp-professional/hooks/ci-check.sh +0 -29
- package/mcps/davinci-mcp-professional/hooks/pre-push +0 -46
- package/mcps/davinci-mcp-professional/main.py +0 -40
- package/mcps/davinci-mcp-professional/mcp_server.py +0 -30
- package/mcps/davinci-mcp-professional/pyproject.toml +0 -115
- package/mcps/davinci-mcp-professional/security_audit.py +0 -379
- package/mcps/davinci-mcp-professional/src/davinci_mcp/__init__.py +0 -19
- package/mcps/davinci-mcp-professional/src/davinci_mcp/cli.py +0 -138
- package/mcps/davinci-mcp-professional/src/davinci_mcp/resolve_client.py +0 -342
- package/mcps/davinci-mcp-professional/src/davinci_mcp/resources/__init__.py +0 -60
- package/mcps/davinci-mcp-professional/src/davinci_mcp/server.py +0 -185
- package/mcps/davinci-mcp-professional/src/davinci_mcp/tools/__init__.py +0 -146
- package/mcps/davinci-mcp-professional/src/davinci_mcp/types.py +0 -125
- package/mcps/davinci-mcp-professional/src/davinci_mcp/utils/__init__.py +0 -19
- package/mcps/davinci-mcp-professional/src/davinci_mcp/utils/platform.py +0 -239
- package/mcps/davinci-mcp-professional/test.py +0 -186
- package/mcps/davinci-mcp-professional/uv.lock +0 -2073
- package/mcps/davinci-resolve-mcp/.claude/skills/media-analysis.md +0 -44
- package/mcps/davinci-resolve-mcp/.clinerules +0 -17
- package/mcps/davinci-resolve-mcp/.cursorrules +0 -17
- package/mcps/davinci-resolve-mcp/.github/copilot-instructions.md +0 -17
- package/mcps/davinci-resolve-mcp/.github/workflows/npm-publish.yml +0 -109
- package/mcps/davinci-resolve-mcp/.windsurfrules +0 -17
- package/mcps/davinci-resolve-mcp/AGENTS.md +0 -114
- package/mcps/davinci-resolve-mcp/CHANGELOG.md +0 -2648
- package/mcps/davinci-resolve-mcp/CLAUDE.md +0 -15
- package/mcps/davinci-resolve-mcp/LICENSE +0 -21
- package/mcps/davinci-resolve-mcp/README.md +0 -173
- package/mcps/davinci-resolve-mcp/SECURITY.md +0 -53
- package/mcps/davinci-resolve-mcp/bin/davinci-resolve-mcp.mjs +0 -450
- package/mcps/davinci-resolve-mcp/examples/README.md +0 -53
- package/mcps/davinci-resolve-mcp/examples/markers/README.md +0 -81
- package/mcps/davinci-resolve-mcp/examples/media/README.md +0 -94
- package/mcps/davinci-resolve-mcp/examples/timeline/README.md +0 -98
- package/mcps/davinci-resolve-mcp/install.py +0 -1687
- package/mcps/davinci-resolve-mcp/package.json +0 -52
- package/mcps/davinci-resolve-mcp/release-notes/v2.22.0.md +0 -51
- package/mcps/davinci-resolve-mcp/release-notes/v2.23.0.md +0 -51
- package/mcps/davinci-resolve-mcp/release-notes/v2.23.1.md +0 -35
- package/mcps/davinci-resolve-mcp/release-notes/v2.24.1.md +0 -32
- package/mcps/davinci-resolve-mcp/scripts/audit_api_parity.py +0 -275
- package/mcps/davinci-resolve-mcp/scripts/audit_readwrite_symmetry.py +0 -80
- package/mcps/davinci-resolve-mcp/scripts/doctor.py +0 -227
- package/mcps/davinci-resolve-mcp/scripts/gen_api_limitations.py +0 -144
- package/mcps/davinci-resolve-mcp/scripts/live_media_analysis_polish_probe.py +0 -65
- package/mcps/davinci-resolve-mcp/scripts/measure_bridge_cost.py +0 -65
- package/mcps/davinci-resolve-mcp/scripts/regen_panel_screenshots.py +0 -76
- package/mcps/davinci-resolve-mcp/src/__init__.py +0 -3
- package/mcps/davinci-resolve-mcp/src/analysis_dashboard.py +0 -15505
- package/mcps/davinci-resolve-mcp/src/batch_cli.py +0 -540
- package/mcps/davinci-resolve-mcp/src/control_panel.py +0 -13
- package/mcps/davinci-resolve-mcp/src/granular/__init__.py +0 -17
- package/mcps/davinci-resolve-mcp/src/granular/common.py +0 -741
- package/mcps/davinci-resolve-mcp/src/granular/folder.py +0 -420
- package/mcps/davinci-resolve-mcp/src/granular/gallery.py +0 -306
- package/mcps/davinci-resolve-mcp/src/granular/graph.py +0 -309
- package/mcps/davinci-resolve-mcp/src/granular/media_pool.py +0 -679
- package/mcps/davinci-resolve-mcp/src/granular/media_pool_item.py +0 -1020
- package/mcps/davinci-resolve-mcp/src/granular/media_storage.py +0 -179
- package/mcps/davinci-resolve-mcp/src/granular/project.py +0 -1654
- package/mcps/davinci-resolve-mcp/src/granular/resolve_control.py +0 -538
- package/mcps/davinci-resolve-mcp/src/granular/timeline.py +0 -1075
- package/mcps/davinci-resolve-mcp/src/granular/timeline_item.py +0 -2251
- package/mcps/davinci-resolve-mcp/src/resolve_mcp_server.py +0 -43
- package/mcps/davinci-resolve-mcp/src/server.py +0 -24116
- package/mcps/davinci-resolve-mcp/src/utils/__init__.py +0 -3
- package/mcps/davinci-resolve-mcp/src/utils/actor_identity.py +0 -46
- package/mcps/davinci-resolve-mcp/src/utils/analysis_caps.py +0 -678
- package/mcps/davinci-resolve-mcp/src/utils/analysis_memory.py +0 -755
- package/mcps/davinci-resolve-mcp/src/utils/analysis_runs.py +0 -307
- package/mcps/davinci-resolve-mcp/src/utils/analysis_store.py +0 -945
- package/mcps/davinci-resolve-mcp/src/utils/api_truth.py +0 -525
- package/mcps/davinci-resolve-mcp/src/utils/app_control.py +0 -319
- package/mcps/davinci-resolve-mcp/src/utils/audio_fairlight_live_probe.py +0 -263
- package/mcps/davinci-resolve-mcp/src/utils/brain_edits.py +0 -387
- package/mcps/davinci-resolve-mcp/src/utils/bridge_metrics.py +0 -76
- package/mcps/davinci-resolve-mcp/src/utils/cdl.py +0 -20
- package/mcps/davinci-resolve-mcp/src/utils/clip_query.py +0 -85
- package/mcps/davinci-resolve-mcp/src/utils/cloud_operations.py +0 -192
- package/mcps/davinci-resolve-mcp/src/utils/color_grade_live_probe.py +0 -444
- package/mcps/davinci-resolve-mcp/src/utils/contracts.py +0 -102
- package/mcps/davinci-resolve-mcp/src/utils/cut_ir.py +0 -115
- package/mcps/davinci-resolve-mcp/src/utils/dctl_templates.py +0 -368
- package/mcps/davinci-resolve-mcp/src/utils/deep_vision.py +0 -643
- package/mcps/davinci-resolve-mcp/src/utils/destructive_hook.py +0 -606
- package/mcps/davinci-resolve-mcp/src/utils/edit_engine.py +0 -737
- package/mcps/davinci-resolve-mcp/src/utils/embeddings.py +0 -922
- package/mcps/davinci-resolve-mcp/src/utils/entities.py +0 -579
- package/mcps/davinci-resolve-mcp/src/utils/extension_authoring_live_probe.py +0 -292
- package/mcps/davinci-resolve-mcp/src/utils/failure_tracker.py +0 -119
- package/mcps/davinci-resolve-mcp/src/utils/fuse_templates.py +0 -1968
- package/mcps/davinci-resolve-mcp/src/utils/fusion_composition_live_probe.py +0 -284
- package/mcps/davinci-resolve-mcp/src/utils/fusion_group_settings.py +0 -334
- package/mcps/davinci-resolve-mcp/src/utils/layout_presets.py +0 -333
- package/mcps/davinci-resolve-mcp/src/utils/mcp_stdio.py +0 -32
- package/mcps/davinci-resolve-mcp/src/utils/mcp_transport.py +0 -128
- package/mcps/davinci-resolve-mcp/src/utils/media_analysis.py +0 -7690
- package/mcps/davinci-resolve-mcp/src/utils/media_analysis_jobs.py +0 -805
- package/mcps/davinci-resolve-mcp/src/utils/media_pool_changes.py +0 -121
- package/mcps/davinci-resolve-mcp/src/utils/media_pool_ingest_live_probe.py +0 -592
- package/mcps/davinci-resolve-mcp/src/utils/multicam.py +0 -393
- package/mcps/davinci-resolve-mcp/src/utils/object_inspection.py +0 -301
- package/mcps/davinci-resolve-mcp/src/utils/page_lock.py +0 -75
- package/mcps/davinci-resolve-mcp/src/utils/platform.py +0 -157
- package/mcps/davinci-resolve-mcp/src/utils/proc.py +0 -26
- package/mcps/davinci-resolve-mcp/src/utils/project_cleanup.py +0 -73
- package/mcps/davinci-resolve-mcp/src/utils/project_lifecycle_live_probe.py +0 -376
- package/mcps/davinci-resolve-mcp/src/utils/project_lint.py +0 -131
- package/mcps/davinci-resolve-mcp/src/utils/project_properties.py +0 -601
- package/mcps/davinci-resolve-mcp/src/utils/project_spec.py +0 -459
- package/mcps/davinci-resolve-mcp/src/utils/readback.py +0 -91
- package/mcps/davinci-resolve-mcp/src/utils/render_deliver_live_probe.py +0 -384
- package/mcps/davinci-resolve-mcp/src/utils/resolve_ai_governance.py +0 -214
- package/mcps/davinci-resolve-mcp/src/utils/resolve_ai_ledger.py +0 -244
- package/mcps/davinci-resolve-mcp/src/utils/resolve_busy.py +0 -171
- package/mcps/davinci-resolve-mcp/src/utils/resolve_connection.py +0 -77
- package/mcps/davinci-resolve-mcp/src/utils/review_annotation_live_probe.py +0 -352
- package/mcps/davinci-resolve-mcp/src/utils/script_templates.py +0 -1193
- package/mcps/davinci-resolve-mcp/src/utils/shot_relationships.py +0 -544
- package/mcps/davinci-resolve-mcp/src/utils/structural_diff.py +0 -175
- package/mcps/davinci-resolve-mcp/src/utils/sync_detection.py +0 -889
- package/mcps/davinci-resolve-mcp/src/utils/timeline_brain_db.py +0 -826
- package/mcps/davinci-resolve-mcp/src/utils/timeline_conform_live_probe.py +0 -280
- package/mcps/davinci-resolve-mcp/src/utils/timeline_kernel_live_probe.py +0 -1092
- package/mcps/davinci-resolve-mcp/src/utils/timeline_kernel_probe.py +0 -185
- package/mcps/davinci-resolve-mcp/src/utils/timeline_title_text.py +0 -87
- package/mcps/davinci-resolve-mcp/src/utils/timeline_versioning.py +0 -730
- package/mcps/davinci-resolve-mcp/src/utils/update_check.py +0 -685
- package/mcps/vectorworks-mcp/.dockerignore +0 -8
- package/mcps/vectorworks-mcp/Dockerfile +0 -28
- package/mcps/vectorworks-mcp/README.md +0 -156
- package/mcps/vectorworks-mcp/app/__init__.py +0 -2
- package/mcps/vectorworks-mcp/app/api.py +0 -52
- package/mcps/vectorworks-mcp/app/chunking.py +0 -56
- package/mcps/vectorworks-mcp/app/config.py +0 -29
- package/mcps/vectorworks-mcp/app/embeddings.py +0 -28
- package/mcps/vectorworks-mcp/app/indexer.py +0 -106
- package/mcps/vectorworks-mcp/app/main.py +0 -33
- package/mcps/vectorworks-mcp/app/mcp_server.py +0 -71
- package/mcps/vectorworks-mcp/app/search.py +0 -54
- package/mcps/vectorworks-mcp/app/utils.py +0 -14
- package/mcps/vectorworks-mcp/data/.gitkeep +0 -1
- package/mcps/vectorworks-mcp/data/vw-app-help/VW2022_PluginScript.htm +0 -95
- package/mcps/vectorworks-mcp/data/vw-app-help/VW2022_ScriptEditor.htm +0 -266
- package/mcps/vectorworks-mcp/data/vw-app-help/VW2022_Scripting.htm +0 -106
- package/mcps/vectorworks-mcp/data/vw-app-help/VW2023_CreatingScriptedPlugins.htm +0 -327
- package/mcps/vectorworks-mcp/data/vw-app-help/VW2024_RunningScripts.htm +0 -116
- package/mcps/vectorworks-mcp/data/vw-devwiki/VS_Function_Reference_category.html +0 -2129
- package/mcps/vectorworks-mcp/data/vw-jp-ref/ObjectsSymbols.html +0 -2035
- package/mcps/vectorworks-mcp/data/vw-jp-ref/ScriptFunctionReference.html +0 -29
- package/mcps/vectorworks-mcp/design.md +0 -57
- package/mcps/vectorworks-mcp/docker-compose.yml +0 -18
- package/mcps/vectorworks-mcp/index/.gitkeep +0 -1
- package/mcps/vectorworks-mcp/index/meta.jsonl +0 -3956
- package/mcps/vectorworks-mcp/index/vw.faiss +0 -0
- package/mcps/vectorworks-mcp/requirements.txt +0 -10
- package/mcps/vectorworks-mcp/scripts/fetch_docs_minimal.sh +0 -84
- package/mcps/vectorworks-mcp/scripts/fetch_github_vectorworks.sh +0 -85
- package/mcps/vectorworks-mcp/templates/index.html +0 -40
- package/scripts/ecosystem-health-audit.js +0 -257
- package/skills/bdb-dev-os-skill/SKILL.md +0 -49
- package/skills/bdb-dev-os-skill/package.json +0 -11
- package/skills/bdbsaastraining/SKILL.md +0 -316
- package/skills/bdbsaastraining/references/exam-pool.md +0 -313
- package/skills/bdbsaastraining/references/interaction-modes.md +0 -94
- package/skills/bdbsaastraining/references/track-a-ai-agent.md +0 -111
- package/skills/bdbsaastraining/references/track-b-wordpress.md +0 -111
- package/skills/bdbsaastraining/references/track-c-mailserver.md +0 -107
- package/skills/bdbsaastraining/references/track-d-debian.md +0 -109
- package/skills/bdbsaastraining/references/track-e-custom.md +0 -83
- package/skills/bdbsaastraining/scripts/build_profile.py +0 -529
- package/skills/bdbsaastraining/scripts/generate_certificate.py +0 -161
- package/skills/bdbsaastraining/scripts/preflight_check.sh +0 -150
- package/skills/bdbsaastraining/templates/certificate_template.html +0 -508
- package/skills/global_config/bdb-ecosystem-health/SKILL.md +0 -68
- package/skills/global_config/bdbsaas-ops/SKILL.md +0 -16
- package/skills/global_config/bdbsaashost/SKILL.md +0 -125
|
@@ -1,284 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Live Fusion Composition boundary probe."""
|
|
3
|
-
|
|
4
|
-
from __future__ import annotations
|
|
5
|
-
|
|
6
|
-
import json
|
|
7
|
-
import platform
|
|
8
|
-
import shutil
|
|
9
|
-
import subprocess
|
|
10
|
-
import sys
|
|
11
|
-
import tempfile
|
|
12
|
-
import time
|
|
13
|
-
from pathlib import Path
|
|
14
|
-
from typing import Any, Dict, Optional
|
|
15
|
-
|
|
16
|
-
from src.utils.timeline_kernel_probe import ProbeRecorder, render_markdown_report, utc_timestamp
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def _require_success(label: str, result: Dict[str, Any]) -> Dict[str, Any]:
|
|
20
|
-
if not isinstance(result, dict):
|
|
21
|
-
raise AssertionError(f"{label}: expected dict, got {result!r}")
|
|
22
|
-
if result.get("error"):
|
|
23
|
-
raise AssertionError(f"{label}: {result['error']}")
|
|
24
|
-
if "success" in result and result["success"] is not True:
|
|
25
|
-
raise AssertionError(f"{label}: expected success=True, got {result!r}")
|
|
26
|
-
return result
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def _record_tool_result(
|
|
30
|
-
recorder: ProbeRecorder,
|
|
31
|
-
category: str,
|
|
32
|
-
name: str,
|
|
33
|
-
result: Dict[str, Any],
|
|
34
|
-
*,
|
|
35
|
-
expected_status: Optional[str] = None,
|
|
36
|
-
) -> None:
|
|
37
|
-
if not isinstance(result, dict):
|
|
38
|
-
recorder.record(category, name, "error", details={"reason": "non-dict result", "result": repr(result)})
|
|
39
|
-
return
|
|
40
|
-
if result.get("error"):
|
|
41
|
-
recorder.record(
|
|
42
|
-
category,
|
|
43
|
-
name,
|
|
44
|
-
expected_status or "error",
|
|
45
|
-
details={"reason": result.get("error"), "expected_status": expected_status},
|
|
46
|
-
evidence=result,
|
|
47
|
-
)
|
|
48
|
-
return
|
|
49
|
-
if "success" in result and result["success"] is not True:
|
|
50
|
-
recorder.record(
|
|
51
|
-
category,
|
|
52
|
-
name,
|
|
53
|
-
expected_status or "partially_supported",
|
|
54
|
-
details={"reason": "success returned false", "expected_status": expected_status},
|
|
55
|
-
evidence=result,
|
|
56
|
-
)
|
|
57
|
-
return
|
|
58
|
-
recorder.record(category, name, expected_status or "supported", evidence=result)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def _run_ffmpeg(args: list[str]) -> None:
|
|
62
|
-
subprocess.run(["ffmpeg", "-hide_banner", "-loglevel", "error", *args], check=True)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def _make_synthetic_video(work_dir: Path) -> Path:
|
|
66
|
-
video = work_dir / "fusion_composition_probe.mov"
|
|
67
|
-
_run_ffmpeg(
|
|
68
|
-
[
|
|
69
|
-
"-f",
|
|
70
|
-
"lavfi",
|
|
71
|
-
"-i",
|
|
72
|
-
"testsrc=size=640x360:rate=24:duration=4",
|
|
73
|
-
"-pix_fmt",
|
|
74
|
-
"yuv420p",
|
|
75
|
-
"-c:v",
|
|
76
|
-
"libx264",
|
|
77
|
-
"-y",
|
|
78
|
-
str(video),
|
|
79
|
-
]
|
|
80
|
-
)
|
|
81
|
-
return video
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def _first_imported_clip(imported_items):
|
|
85
|
-
for item in imported_items or []:
|
|
86
|
-
try:
|
|
87
|
-
if item.GetUniqueId():
|
|
88
|
-
return item
|
|
89
|
-
except Exception:
|
|
90
|
-
pass
|
|
91
|
-
return None
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def run_probe(server, output_dir: Path, keep_open: bool = False) -> Dict[str, Any]:
|
|
95
|
-
output_dir.mkdir(parents=True, exist_ok=True)
|
|
96
|
-
work_dir = Path(tempfile.mkdtemp(prefix="mcp_fusion_composition_probe_"))
|
|
97
|
-
project_name = f"_mcp_fusion_composition_probe_{int(time.time())}"
|
|
98
|
-
timeline_name = "Fusion Composition Probe Timeline"
|
|
99
|
-
recorder = ProbeRecorder()
|
|
100
|
-
created_project = False
|
|
101
|
-
delete_result: Optional[Dict[str, Any]] = None
|
|
102
|
-
|
|
103
|
-
metadata: Dict[str, Any] = {
|
|
104
|
-
"title": "Fusion Composition Kernel Capability Probe",
|
|
105
|
-
"timestamp_utc": utc_timestamp(),
|
|
106
|
-
"python": sys.version,
|
|
107
|
-
"platform": platform.platform(),
|
|
108
|
-
"output_dir": str(output_dir),
|
|
109
|
-
"project_name": project_name,
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
try:
|
|
113
|
-
version = _require_success("resolve_control.get_version", server.resolve_control("get_version"))
|
|
114
|
-
metadata.update(
|
|
115
|
-
{
|
|
116
|
-
"product": version.get("product"),
|
|
117
|
-
"version": version.get("version"),
|
|
118
|
-
"version_string": version.get("version_string"),
|
|
119
|
-
}
|
|
120
|
-
)
|
|
121
|
-
print(f"Connected to {metadata['product']} {metadata['version_string']}")
|
|
122
|
-
|
|
123
|
-
_require_success("project_manager.create", server.project_manager("create", {"name": project_name}))
|
|
124
|
-
created_project = True
|
|
125
|
-
print(f"Created disposable project: {project_name}")
|
|
126
|
-
server.resolve_control("open_page", {"page": "edit"})
|
|
127
|
-
|
|
128
|
-
video = _make_synthetic_video(work_dir)
|
|
129
|
-
metadata["synthetic_media"] = {"video": str(video)}
|
|
130
|
-
print(f"Generated synthetic media under: {work_dir}")
|
|
131
|
-
|
|
132
|
-
resolve = server.get_resolve()
|
|
133
|
-
project = resolve.GetProjectManager().GetCurrentProject()
|
|
134
|
-
media_pool = project.GetMediaPool()
|
|
135
|
-
imported = media_pool.ImportMedia([str(video)]) or []
|
|
136
|
-
clip = _first_imported_clip(imported)
|
|
137
|
-
if not clip:
|
|
138
|
-
raise AssertionError("Failed to import synthetic Fusion media")
|
|
139
|
-
timeline = media_pool.CreateTimelineFromClips(timeline_name, [clip])
|
|
140
|
-
if not timeline:
|
|
141
|
-
raise AssertionError("Failed to create Fusion timeline")
|
|
142
|
-
project.SetCurrentTimeline(timeline)
|
|
143
|
-
timeline.SetCurrentTimecode("01:00:00:01")
|
|
144
|
-
print(f"Created timeline: {timeline_name}")
|
|
145
|
-
|
|
146
|
-
item_scope = {"track_type": "video", "track_index": 1, "item_index": 0}
|
|
147
|
-
comp_scope = {"timeline_item": item_scope}
|
|
148
|
-
|
|
149
|
-
_record_tool_result(recorder, "timeline_item_fusion", "add_comp", server.timeline_item_fusion("add_comp", item_scope))
|
|
150
|
-
_record_tool_result(recorder, "timeline_item_fusion", "get_comp_count", server.timeline_item_fusion("get_comp_count", item_scope))
|
|
151
|
-
_record_tool_result(recorder, "timeline_item_fusion", "get_comp_names", server.timeline_item_fusion("get_comp_names", item_scope))
|
|
152
|
-
|
|
153
|
-
_record_tool_result(recorder, "capabilities", "fusion_graph_capabilities", server.fusion_comp("fusion_graph_capabilities", comp_scope))
|
|
154
|
-
_record_tool_result(
|
|
155
|
-
recorder,
|
|
156
|
-
"inspection",
|
|
157
|
-
"probe_fusion_comp_initial",
|
|
158
|
-
server.fusion_comp("probe_fusion_comp", {**comp_scope, "include_io": False}),
|
|
159
|
-
)
|
|
160
|
-
|
|
161
|
-
for tool_type, name in (
|
|
162
|
-
("Background", "MCP_Background"),
|
|
163
|
-
("TextPlus", "MCP_Text"),
|
|
164
|
-
("Merge", "MCP_Merge"),
|
|
165
|
-
("Transform", "MCP_Transform"),
|
|
166
|
-
("Blur", "MCP_Blur"),
|
|
167
|
-
):
|
|
168
|
-
_record_tool_result(
|
|
169
|
-
recorder,
|
|
170
|
-
"tools",
|
|
171
|
-
f"safe_add_{tool_type}",
|
|
172
|
-
server.fusion_comp("safe_add_tool", {**comp_scope, "tool_type": tool_type, "name": name, "include_io": True}),
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
_record_tool_result(
|
|
176
|
-
recorder,
|
|
177
|
-
"inputs",
|
|
178
|
-
"safe_set_text",
|
|
179
|
-
server.fusion_comp(
|
|
180
|
-
"safe_set_inputs",
|
|
181
|
-
{**comp_scope, "tool_name": "MCP_Text", "inputs": {"StyledText": "MCP Fusion Probe"}, "readback": True},
|
|
182
|
-
),
|
|
183
|
-
)
|
|
184
|
-
_record_tool_result(
|
|
185
|
-
recorder,
|
|
186
|
-
"inputs",
|
|
187
|
-
"safe_set_background_color",
|
|
188
|
-
server.fusion_comp(
|
|
189
|
-
"safe_set_inputs",
|
|
190
|
-
{
|
|
191
|
-
**comp_scope,
|
|
192
|
-
"tool_name": "MCP_Background",
|
|
193
|
-
"inputs": {
|
|
194
|
-
"TopLeftRed": 0.1,
|
|
195
|
-
"TopLeftGreen": 0.2,
|
|
196
|
-
"TopLeftBlue": 0.7,
|
|
197
|
-
"TopLeftAlpha": 1.0,
|
|
198
|
-
},
|
|
199
|
-
"readback": True,
|
|
200
|
-
},
|
|
201
|
-
),
|
|
202
|
-
)
|
|
203
|
-
_record_tool_result(
|
|
204
|
-
recorder,
|
|
205
|
-
"inspection",
|
|
206
|
-
"probe_text_tool",
|
|
207
|
-
server.fusion_comp("probe_fusion_tool", {**comp_scope, "tool_name": "MCP_Text", "include_io": True}),
|
|
208
|
-
)
|
|
209
|
-
_record_tool_result(
|
|
210
|
-
recorder,
|
|
211
|
-
"wiring",
|
|
212
|
-
"safe_connect_text_to_mediaout",
|
|
213
|
-
server.fusion_comp(
|
|
214
|
-
"safe_connect_tools",
|
|
215
|
-
{**comp_scope, "target_tool": "MediaOut1", "input_name": "Input", "source_tool": "MCP_Text"},
|
|
216
|
-
),
|
|
217
|
-
)
|
|
218
|
-
_record_tool_result(
|
|
219
|
-
recorder,
|
|
220
|
-
"bulk",
|
|
221
|
-
"bulk_set_inputs",
|
|
222
|
-
server.fusion_comp(
|
|
223
|
-
"bulk_set_inputs",
|
|
224
|
-
{
|
|
225
|
-
"ops": [
|
|
226
|
-
{**comp_scope, "tool_name": "MCP_Text", "input_name": "Size", "value": 0.08},
|
|
227
|
-
{**comp_scope, "tool_name": "MCP_Background", "input_name": "GlobalOut", "value": 96},
|
|
228
|
-
]
|
|
229
|
-
},
|
|
230
|
-
),
|
|
231
|
-
)
|
|
232
|
-
_record_tool_result(
|
|
233
|
-
recorder,
|
|
234
|
-
"composition",
|
|
235
|
-
"set_frame_range",
|
|
236
|
-
server.fusion_comp("set_frame_range", {**comp_scope, "start": 0, "end": 96}),
|
|
237
|
-
)
|
|
238
|
-
export_path = str(work_dir / "fusion_probe.setting")
|
|
239
|
-
_record_tool_result(
|
|
240
|
-
recorder,
|
|
241
|
-
"timeline_item_fusion",
|
|
242
|
-
"export_comp",
|
|
243
|
-
server.timeline_item_fusion("export_comp", {**item_scope, "path": export_path, "index": 1}),
|
|
244
|
-
)
|
|
245
|
-
_record_tool_result(
|
|
246
|
-
recorder,
|
|
247
|
-
"report",
|
|
248
|
-
"fusion_boundary_report",
|
|
249
|
-
server.fusion_comp("fusion_boundary_report", {**comp_scope, "include_io": False}),
|
|
250
|
-
)
|
|
251
|
-
|
|
252
|
-
if keep_open:
|
|
253
|
-
server.project_manager("save")
|
|
254
|
-
print(f"LEFT PROJECT OPEN FOR INSPECTION: {project_name}")
|
|
255
|
-
created_project = False
|
|
256
|
-
|
|
257
|
-
finally:
|
|
258
|
-
if created_project:
|
|
259
|
-
server.project_manager("save")
|
|
260
|
-
server.project_manager("close")
|
|
261
|
-
delete_result = server.project_manager("delete", {"name": project_name})
|
|
262
|
-
print(f"Deleted disposable project: {delete_result}")
|
|
263
|
-
|
|
264
|
-
report = recorder.to_report(
|
|
265
|
-
metadata,
|
|
266
|
-
{
|
|
267
|
-
"json": str(output_dir / "fusion-composition-probe.json"),
|
|
268
|
-
"markdown": str(output_dir / "fusion-composition-probe.md"),
|
|
269
|
-
},
|
|
270
|
-
)
|
|
271
|
-
json_path = output_dir / "fusion-composition-probe.json"
|
|
272
|
-
markdown_path = output_dir / "fusion-composition-probe.md"
|
|
273
|
-
json_path.write_text(json.dumps(report, indent=2, sort_keys=True), encoding="utf-8")
|
|
274
|
-
markdown_path.write_text(render_markdown_report(report), encoding="utf-8")
|
|
275
|
-
print(f"Wrote JSON report: {json_path}")
|
|
276
|
-
print(f"Wrote Markdown report: {markdown_path}")
|
|
277
|
-
print(f"Counts: {json.dumps(report['counts'], sort_keys=True)}")
|
|
278
|
-
if not keep_open:
|
|
279
|
-
shutil.rmtree(work_dir, ignore_errors=True)
|
|
280
|
-
print(f"Removed synthetic media directory: {work_dir}")
|
|
281
|
-
|
|
282
|
-
if delete_result and delete_result.get("success") is not True:
|
|
283
|
-
raise AssertionError(f"Cleanup failed for {project_name}: {delete_result!r}")
|
|
284
|
-
return report
|
|
@@ -1,334 +0,0 @@
|
|
|
1
|
-
"""Parse and patch Fusion GroupOperator .setting files.
|
|
2
|
-
|
|
3
|
-
Fusion's .setting format is a Lua-like nested structure. Real-world group
|
|
4
|
-
exports routinely contain InstanceInput blocks with nested UserControls /
|
|
5
|
-
ControlGroup tables (>=2 levels of braces), so we cannot parse them with a
|
|
6
|
-
flat regex. All structural scans here use balanced-brace matching.
|
|
7
|
-
|
|
8
|
-
Public surface:
|
|
9
|
-
parse_setting_file(path, group_name=None) -> dict
|
|
10
|
-
Inspect a .setting file and return the published-input summary for a
|
|
11
|
-
named (or first) GroupOperator.
|
|
12
|
-
|
|
13
|
-
splice_inputs_block(source_path, template_path, dest_path,
|
|
14
|
-
group_name=None) -> dict
|
|
15
|
-
Replace the `Inputs = ordered() { ... }` block of source_path with
|
|
16
|
-
the matching block from template_path and write to dest_path.
|
|
17
|
-
Returns a before/after diff summary.
|
|
18
|
-
|
|
19
|
-
default_backup_path(path) -> str
|
|
20
|
-
Generate a timestamped backup filename next to a target path.
|
|
21
|
-
|
|
22
|
-
FUSION_GROUP_GUARDRAILS, FUSION_COMMIT_CHECKLIST
|
|
23
|
-
Advisory text. Other modules may surface these on demand; this module
|
|
24
|
-
does not jam them into every return.
|
|
25
|
-
"""
|
|
26
|
-
|
|
27
|
-
from __future__ import annotations
|
|
28
|
-
|
|
29
|
-
import os
|
|
30
|
-
import re
|
|
31
|
-
from dataclasses import dataclass, field
|
|
32
|
-
from datetime import datetime, timezone
|
|
33
|
-
from typing import Any, Dict, List, Optional, Tuple
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
FUSION_COMMIT_CHECKLIST: Tuple[str, ...] = (
|
|
37
|
-
"Open the Fusion page for the modified timeline item (or comp scope).",
|
|
38
|
-
"Select the group/macro node and nudge one published control so Resolve refreshes bindings.",
|
|
39
|
-
"Verify Edit-page controls respond (Text, Text Size, wrap, padding, corners).",
|
|
40
|
-
"Save the project (Ctrl+S / Cmd+S).",
|
|
41
|
-
"If InstanceInput order changed, confirm Edit Controls order in UI; LoadSettings may require manual UI 'Load Settings' on the group.",
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
FUSION_GROUP_GUARDRAILS: Tuple[str, ...] = (
|
|
45
|
-
"Do not call project_manager.load or switch projects mid Fusion edit — comp scope and undo stacks are lost.",
|
|
46
|
-
"Batch graph mutations: prefer bulk_set_inputs / bulk_set_expressions over many single-action calls.",
|
|
47
|
-
"InstanceInput remapping via LoadSettings may not refresh Edit-page control order until the group is selected and settings reloaded in Fusion UI.",
|
|
48
|
-
"Never delete a group via automation; group_settings_load only patches published inputs and inner settings.",
|
|
49
|
-
"After mutating a timeline-item Fusion comp, follow the commit checklist before expecting Edit-page updates.",
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
@dataclass
|
|
54
|
-
class InstanceInputSummary:
|
|
55
|
-
slot: str
|
|
56
|
-
source_op: Optional[str] = None
|
|
57
|
-
source: Optional[str] = None
|
|
58
|
-
name: Optional[str] = None
|
|
59
|
-
max_scale: Optional[float] = None
|
|
60
|
-
default: Optional[str] = None
|
|
61
|
-
control_group: Optional[int] = None
|
|
62
|
-
raw_fields: Dict[str, Any] = field(default_factory=dict)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
# Used only to read shallow fields out of an InstanceInput body that we have
|
|
66
|
-
# already isolated with balanced-brace scanning. We never use it to FIND the
|
|
67
|
-
# bounds of a block.
|
|
68
|
-
_FIELD_RE = re.compile(r'(\w+)\s*=\s*(?:"([^"]*)"|([\d.eE+-]+))')
|
|
69
|
-
|
|
70
|
-
_INPUT_HEAD_RE = re.compile(r"(Input\d+)\s*=\s*InstanceInput\s*\{")
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def _find_balanced_brace(text: str, open_index: int) -> int:
|
|
74
|
-
"""Return the index of the `}` that closes the `{` at open_index."""
|
|
75
|
-
depth = 0
|
|
76
|
-
for idx in range(open_index, len(text)):
|
|
77
|
-
ch = text[idx]
|
|
78
|
-
if ch == "{":
|
|
79
|
-
depth += 1
|
|
80
|
-
elif ch == "}":
|
|
81
|
-
depth -= 1
|
|
82
|
-
if depth == 0:
|
|
83
|
-
return idx
|
|
84
|
-
raise ValueError("Unbalanced braces")
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
def _iter_instance_input_blocks(inputs_inner: str) -> List[Tuple[str, str]]:
|
|
88
|
-
"""Yield (slot, body) pairs by walking InstanceInput heads + balanced braces."""
|
|
89
|
-
blocks: List[Tuple[str, str]] = []
|
|
90
|
-
pos = 0
|
|
91
|
-
while True:
|
|
92
|
-
match = _INPUT_HEAD_RE.search(inputs_inner, pos)
|
|
93
|
-
if not match:
|
|
94
|
-
break
|
|
95
|
-
slot = match.group(1)
|
|
96
|
-
open_brace = match.end() - 1
|
|
97
|
-
try:
|
|
98
|
-
close_brace = _find_balanced_brace(inputs_inner, open_brace)
|
|
99
|
-
except ValueError:
|
|
100
|
-
break
|
|
101
|
-
body = inputs_inner[open_brace + 1 : close_brace]
|
|
102
|
-
blocks.append((slot, body))
|
|
103
|
-
pos = close_brace + 1
|
|
104
|
-
return blocks
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def _shallow_fields(body: str) -> Dict[str, Any]:
|
|
108
|
-
"""Extract top-level `key = value` fields, skipping nested braces."""
|
|
109
|
-
fields: Dict[str, Any] = {}
|
|
110
|
-
i = 0
|
|
111
|
-
n = len(body)
|
|
112
|
-
while i < n:
|
|
113
|
-
ch = body[i]
|
|
114
|
-
if ch == "{":
|
|
115
|
-
try:
|
|
116
|
-
close = _find_balanced_brace(body, i)
|
|
117
|
-
except ValueError:
|
|
118
|
-
break
|
|
119
|
-
i = close + 1
|
|
120
|
-
continue
|
|
121
|
-
match = _FIELD_RE.match(body, i)
|
|
122
|
-
if match:
|
|
123
|
-
key, str_val, num_val = match.group(1), match.group(2), match.group(3)
|
|
124
|
-
if str_val is not None:
|
|
125
|
-
fields[key] = str_val
|
|
126
|
-
elif num_val is not None:
|
|
127
|
-
try:
|
|
128
|
-
fields[key] = (
|
|
129
|
-
float(num_val) if "." in num_val or "e" in num_val.lower() else int(num_val)
|
|
130
|
-
)
|
|
131
|
-
except ValueError:
|
|
132
|
-
fields[key] = num_val
|
|
133
|
-
i = match.end()
|
|
134
|
-
else:
|
|
135
|
-
i += 1
|
|
136
|
-
return fields
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
def parse_instance_input_block(inputs_inner: str) -> List[InstanceInputSummary]:
|
|
140
|
-
summaries: List[InstanceInputSummary] = []
|
|
141
|
-
for slot, body in _iter_instance_input_blocks(inputs_inner):
|
|
142
|
-
fields = _shallow_fields(body)
|
|
143
|
-
max_scale = fields.get("MaxScale")
|
|
144
|
-
if isinstance(max_scale, (int, float)):
|
|
145
|
-
max_scale_f: Optional[float] = float(max_scale)
|
|
146
|
-
else:
|
|
147
|
-
max_scale_f = None
|
|
148
|
-
cg = fields.get("ControlGroup")
|
|
149
|
-
summaries.append(
|
|
150
|
-
InstanceInputSummary(
|
|
151
|
-
slot=slot,
|
|
152
|
-
source_op=fields.get("SourceOp"),
|
|
153
|
-
source=fields.get("Source"),
|
|
154
|
-
name=fields.get("Name"),
|
|
155
|
-
max_scale=max_scale_f,
|
|
156
|
-
default=str(fields["Default"]) if "Default" in fields else None,
|
|
157
|
-
control_group=int(cg) if isinstance(cg, int) else None,
|
|
158
|
-
raw_fields=fields,
|
|
159
|
-
)
|
|
160
|
-
)
|
|
161
|
-
summaries.sort(key=lambda row: _slot_key(row.slot))
|
|
162
|
-
return summaries
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
def _slot_key(slot: str) -> int:
|
|
166
|
-
try:
|
|
167
|
-
return int(slot.replace("Input", ""))
|
|
168
|
-
except ValueError:
|
|
169
|
-
return 9999
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
def _group_inputs_span(
|
|
173
|
-
content: str, group_name: Optional[str] = None
|
|
174
|
-
) -> Tuple[int, int, str]:
|
|
175
|
-
"""Return absolute (start, end_exclusive, inner) for the Inputs ordered block."""
|
|
176
|
-
if group_name:
|
|
177
|
-
pattern = re.compile(
|
|
178
|
-
rf"{re.escape(group_name)}\s*=\s*GroupOperator\s*\{{",
|
|
179
|
-
re.MULTILINE,
|
|
180
|
-
)
|
|
181
|
-
match = pattern.search(content)
|
|
182
|
-
if not match:
|
|
183
|
-
raise ValueError(f"GroupOperator {group_name!r} not found in .setting content")
|
|
184
|
-
group_open = match.end() - 1
|
|
185
|
-
else:
|
|
186
|
-
match = re.search(r"=\s*GroupOperator\s*\{", content)
|
|
187
|
-
if not match:
|
|
188
|
-
raise ValueError("No GroupOperator found in .setting content")
|
|
189
|
-
group_open = match.end() - 1
|
|
190
|
-
|
|
191
|
-
group_close = _find_balanced_brace(content, group_open)
|
|
192
|
-
group_body = content[group_open + 1 : group_close]
|
|
193
|
-
inputs_marker = re.search(r"Inputs\s*=\s*ordered\s*\(\s*\)\s*\{", group_body)
|
|
194
|
-
if not inputs_marker:
|
|
195
|
-
raise ValueError("GroupOperator has no Inputs = ordered() block")
|
|
196
|
-
abs_start = group_open + 1 + inputs_marker.start()
|
|
197
|
-
open_brace = group_open + 1 + inputs_marker.end() - 1
|
|
198
|
-
close_brace = _find_balanced_brace(content, open_brace)
|
|
199
|
-
replace_end = close_brace + 1
|
|
200
|
-
if replace_end < len(content) and content[replace_end] == ",":
|
|
201
|
-
replace_end += 1
|
|
202
|
-
return abs_start, replace_end, content[open_brace + 1 : close_brace]
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
def parse_setting_file(path: str, group_name: Optional[str] = None) -> Dict[str, Any]:
|
|
206
|
-
with open(path, encoding="utf-8", errors="replace") as handle:
|
|
207
|
-
content = handle.read()
|
|
208
|
-
_, _, inner = _group_inputs_span(content, group_name=group_name)
|
|
209
|
-
inputs = parse_instance_input_block(inner)
|
|
210
|
-
return {
|
|
211
|
-
"path": os.path.abspath(path),
|
|
212
|
-
"published_inputs": [
|
|
213
|
-
{
|
|
214
|
-
"slot": row.slot,
|
|
215
|
-
"name": row.name,
|
|
216
|
-
"source_op": row.source_op,
|
|
217
|
-
"source": row.source,
|
|
218
|
-
"max_scale": row.max_scale,
|
|
219
|
-
"default": row.default,
|
|
220
|
-
"control_group": row.control_group,
|
|
221
|
-
}
|
|
222
|
-
for row in inputs
|
|
223
|
-
],
|
|
224
|
-
"input_count": len(inputs),
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
def _read_template_inputs_block(
|
|
229
|
-
template_path: str, group_name: Optional[str] = None
|
|
230
|
-
) -> str:
|
|
231
|
-
"""Pull the full `Inputs = ordered() { ... },` block from a .setting file."""
|
|
232
|
-
with open(template_path, encoding="utf-8", errors="replace") as handle:
|
|
233
|
-
content = handle.read()
|
|
234
|
-
start, end, _ = _group_inputs_span(content, group_name=group_name)
|
|
235
|
-
return content[start:end]
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
def _summary_dict(row: Optional[InstanceInputSummary]) -> Optional[Dict[str, Any]]:
|
|
239
|
-
if row is None:
|
|
240
|
-
return None
|
|
241
|
-
return {
|
|
242
|
-
"source_op": row.source_op,
|
|
243
|
-
"source": row.source,
|
|
244
|
-
"name": row.name,
|
|
245
|
-
"max_scale": row.max_scale,
|
|
246
|
-
"control_group": row.control_group,
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
def _diff_inputs(
|
|
251
|
-
before: List[InstanceInputSummary], after: List[InstanceInputSummary]
|
|
252
|
-
) -> List[Dict[str, Any]]:
|
|
253
|
-
before_by_slot = {row.slot: row for row in before}
|
|
254
|
-
after_by_slot = {row.slot: row for row in after}
|
|
255
|
-
all_slots = sorted(set(before_by_slot) | set(after_by_slot), key=_slot_key)
|
|
256
|
-
diff: List[Dict[str, Any]] = []
|
|
257
|
-
for slot in all_slots:
|
|
258
|
-
b = before_by_slot.get(slot)
|
|
259
|
-
a = after_by_slot.get(slot)
|
|
260
|
-
if b is None:
|
|
261
|
-
diff.append({"slot": slot, "change": "added", "after": _summary_dict(a)})
|
|
262
|
-
elif a is None:
|
|
263
|
-
diff.append({"slot": slot, "change": "removed", "before": _summary_dict(b)})
|
|
264
|
-
elif _summary_dict(b) != _summary_dict(a):
|
|
265
|
-
diff.append(
|
|
266
|
-
{
|
|
267
|
-
"slot": slot,
|
|
268
|
-
"change": "modified",
|
|
269
|
-
"before": _summary_dict(b),
|
|
270
|
-
"after": _summary_dict(a),
|
|
271
|
-
}
|
|
272
|
-
)
|
|
273
|
-
return diff
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
def splice_inputs_block(
|
|
277
|
-
source_path: str,
|
|
278
|
-
template_path: str,
|
|
279
|
-
dest_path: str,
|
|
280
|
-
*,
|
|
281
|
-
source_group_name: Optional[str] = None,
|
|
282
|
-
template_group_name: Optional[str] = None,
|
|
283
|
-
) -> Dict[str, Any]:
|
|
284
|
-
"""Replace source's `Inputs = ordered() { ... }` with template's, write dest.
|
|
285
|
-
|
|
286
|
-
source_path: .setting whose published inputs you want to replace.
|
|
287
|
-
template_path: .setting whose Inputs block defines the desired layout.
|
|
288
|
-
dest_path: where to write the spliced result.
|
|
289
|
-
|
|
290
|
-
Returns a summary with input counts before/after and a per-slot diff.
|
|
291
|
-
"""
|
|
292
|
-
with open(source_path, encoding="utf-8", errors="replace") as handle:
|
|
293
|
-
source_content = handle.read()
|
|
294
|
-
src_start, src_end, src_inner = _group_inputs_span(
|
|
295
|
-
source_content, group_name=source_group_name
|
|
296
|
-
)
|
|
297
|
-
before_inputs = parse_instance_input_block(src_inner)
|
|
298
|
-
template_block = _read_template_inputs_block(
|
|
299
|
-
template_path, group_name=template_group_name
|
|
300
|
-
)
|
|
301
|
-
|
|
302
|
-
new_content = source_content[:src_start] + template_block + source_content[src_end:]
|
|
303
|
-
_, _, new_inner = _group_inputs_span(new_content, group_name=source_group_name)
|
|
304
|
-
after_inputs = parse_instance_input_block(new_inner)
|
|
305
|
-
|
|
306
|
-
os.makedirs(os.path.dirname(os.path.abspath(dest_path)) or ".", exist_ok=True)
|
|
307
|
-
# Atomic write (temp + os.replace): a crash mid-write must not truncate the
|
|
308
|
-
# Fusion .setting file and leave the GroupOperator config uneditable (PS5).
|
|
309
|
-
tmp_path = f"{dest_path}.tmp-{os.getpid()}"
|
|
310
|
-
try:
|
|
311
|
-
with open(tmp_path, "w", encoding="utf-8", newline="\n") as handle:
|
|
312
|
-
handle.write(new_content)
|
|
313
|
-
os.replace(tmp_path, dest_path)
|
|
314
|
-
finally:
|
|
315
|
-
if os.path.isfile(tmp_path):
|
|
316
|
-
try:
|
|
317
|
-
os.remove(tmp_path)
|
|
318
|
-
except OSError:
|
|
319
|
-
pass
|
|
320
|
-
|
|
321
|
-
return {
|
|
322
|
-
"source_path": os.path.abspath(source_path),
|
|
323
|
-
"template_path": os.path.abspath(template_path),
|
|
324
|
-
"dest_path": os.path.abspath(dest_path),
|
|
325
|
-
"before_input_count": len(before_inputs),
|
|
326
|
-
"after_input_count": len(after_inputs),
|
|
327
|
-
"diff": _diff_inputs(before_inputs, after_inputs),
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
def default_backup_path(base_path: str) -> str:
|
|
332
|
-
stamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
|
|
333
|
-
root, ext = os.path.splitext(base_path)
|
|
334
|
-
return f"{root}.backup_{stamp}{ext or '.setting'}"
|