@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,387 +0,0 @@
|
|
|
1
|
-
"""Brain-edit logging + metric capture (C6).
|
|
2
|
-
|
|
3
|
-
A *brain edit* is any timeline mutation that should be measured. Two callers:
|
|
4
|
-
|
|
5
|
-
1. **Automatic** — the destructive-op hook in `src/server.py` logs a row for
|
|
6
|
-
every destructive action call, even when the caller didn't declare a metric.
|
|
7
|
-
Gives history without requiring deliberate intent.
|
|
8
|
-
2. **Declared** — a caller that knows it's making an editorial improvement
|
|
9
|
-
(a take swap, a gap close, a pacing tighten) passes `metric`, `direction`,
|
|
10
|
-
and `rationale` in the action params, plus computes `before_value` and
|
|
11
|
-
`after_value` from the metric vocabulary helpers below. These rows form the
|
|
12
|
-
measurement substrate for tuning the brain.
|
|
13
|
-
|
|
14
|
-
The cross-project rollup (`brain_edits_registry.json`) is updated by
|
|
15
|
-
`update_brain_edits_registry()` after each `log_brain_edit()`. Mirrors the
|
|
16
|
-
existing `analysis_registry.json` pattern.
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
|
-
from __future__ import annotations
|
|
20
|
-
|
|
21
|
-
import json
|
|
22
|
-
import logging
|
|
23
|
-
import os
|
|
24
|
-
import time
|
|
25
|
-
from typing import Any, Callable, Dict, List, Optional
|
|
26
|
-
|
|
27
|
-
from src.utils import actor_identity, timeline_brain_db
|
|
28
|
-
|
|
29
|
-
logger = logging.getLogger("resolve-mcp.brain-edits")
|
|
30
|
-
|
|
31
|
-
# ── Metric vocabulary ────────────────────────────────────────────────────────
|
|
32
|
-
#
|
|
33
|
-
# Starter set. Add new metrics here; the brain (and the dashboard) consult this
|
|
34
|
-
# registry to know how to render and aggregate them. `direction` is the
|
|
35
|
-
# default-better direction; an `direction` override at log-time wins.
|
|
36
|
-
|
|
37
|
-
METRIC_DURATION_SECONDS = "duration_seconds"
|
|
38
|
-
METRIC_AVG_PERFORMANCE = "avg_performance_score"
|
|
39
|
-
METRIC_CLIP_COUNT = "clip_count"
|
|
40
|
-
METRIC_GAP_COUNT = "gap_count"
|
|
41
|
-
METRIC_TOTAL_GAP_SECONDS = "total_gap_seconds"
|
|
42
|
-
METRIC_REDUNDANCY_SCORE = "redundancy_score"
|
|
43
|
-
METRIC_COLOR_INTENT = "color_intent"
|
|
44
|
-
|
|
45
|
-
METRIC_REGISTRY: Dict[str, Dict[str, Any]] = {
|
|
46
|
-
METRIC_DURATION_SECONDS: {
|
|
47
|
-
"label": "Duration (seconds)",
|
|
48
|
-
"unit": "s",
|
|
49
|
-
"default_direction": "target_value",
|
|
50
|
-
},
|
|
51
|
-
METRIC_AVG_PERFORMANCE: {
|
|
52
|
-
"label": "Avg performance score",
|
|
53
|
-
"unit": "score 0-1",
|
|
54
|
-
"default_direction": "increase",
|
|
55
|
-
},
|
|
56
|
-
METRIC_CLIP_COUNT: {
|
|
57
|
-
"label": "Clip count",
|
|
58
|
-
"unit": "clips",
|
|
59
|
-
"default_direction": "target_value",
|
|
60
|
-
},
|
|
61
|
-
METRIC_GAP_COUNT: {
|
|
62
|
-
"label": "Gap count",
|
|
63
|
-
"unit": "gaps",
|
|
64
|
-
"default_direction": "decrease",
|
|
65
|
-
},
|
|
66
|
-
METRIC_TOTAL_GAP_SECONDS: {
|
|
67
|
-
"label": "Total gap duration (seconds)",
|
|
68
|
-
"unit": "s",
|
|
69
|
-
"default_direction": "decrease",
|
|
70
|
-
},
|
|
71
|
-
METRIC_REDUNDANCY_SCORE: {
|
|
72
|
-
"label": "Redundancy score",
|
|
73
|
-
"unit": "score 0-1",
|
|
74
|
-
"default_direction": "decrease",
|
|
75
|
-
},
|
|
76
|
-
METRIC_COLOR_INTENT: {
|
|
77
|
-
"label": "Color intent",
|
|
78
|
-
"unit": "qualitative",
|
|
79
|
-
"default_direction": "target_value",
|
|
80
|
-
},
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
DIRECTIONS = {"increase", "decrease", "target_value"}
|
|
84
|
-
|
|
85
|
-
# Cross-project registry filename (one level above project root)
|
|
86
|
-
REGISTRY_FILENAME = "brain_edits_registry.json"
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def _now_iso() -> str:
|
|
90
|
-
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
# ── Metric capture helpers ────────────────────────────────────────────────────
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def capture_timeline_duration_seconds(timeline: Any) -> Optional[float]:
|
|
97
|
-
"""Compute duration in seconds from a Resolve Timeline handle."""
|
|
98
|
-
try:
|
|
99
|
-
start = int(timeline.GetStartFrame())
|
|
100
|
-
end = int(timeline.GetEndFrame())
|
|
101
|
-
fps_setting = timeline.GetSetting("timelineFrameRate")
|
|
102
|
-
fps = float(fps_setting) if fps_setting else 24.0
|
|
103
|
-
if fps <= 0:
|
|
104
|
-
return None
|
|
105
|
-
return max(0.0, (end - start) / fps)
|
|
106
|
-
except Exception as exc: # pragma: no cover — Resolve API surface
|
|
107
|
-
logger.debug("capture_timeline_duration_seconds failed: %s", exc)
|
|
108
|
-
return None
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def capture_timeline_clip_count(timeline: Any) -> Optional[int]:
|
|
112
|
-
"""Sum of video + audio + subtitle clips across all tracks."""
|
|
113
|
-
try:
|
|
114
|
-
total = 0
|
|
115
|
-
for tt in ("video", "audio", "subtitle"):
|
|
116
|
-
count = timeline.GetTrackCount(tt)
|
|
117
|
-
if not count:
|
|
118
|
-
continue
|
|
119
|
-
for ti in range(1, int(count) + 1):
|
|
120
|
-
items = timeline.GetItemListInTrack(tt, ti) or []
|
|
121
|
-
total += len(items)
|
|
122
|
-
return total
|
|
123
|
-
except Exception as exc: # pragma: no cover
|
|
124
|
-
logger.debug("capture_timeline_clip_count failed: %s", exc)
|
|
125
|
-
return None
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
def capture_timeline_gap_stats(timeline: Any, *, track_types=("video",)) -> Dict[str, Any]:
|
|
129
|
-
"""Compute gap_count and total_gap_seconds on a timeline.
|
|
130
|
-
|
|
131
|
-
Single-track-per-type gap detection: walks each track in order, accumulates
|
|
132
|
-
the empty frames between consecutive items. Multi-track overlap is ignored
|
|
133
|
-
here (gaps are per-track).
|
|
134
|
-
"""
|
|
135
|
-
out = {"gap_count": 0, "total_gap_frames": 0, "total_gap_seconds": None}
|
|
136
|
-
try:
|
|
137
|
-
fps_setting = timeline.GetSetting("timelineFrameRate")
|
|
138
|
-
fps = float(fps_setting) if fps_setting else 24.0
|
|
139
|
-
for tt in track_types:
|
|
140
|
-
count = timeline.GetTrackCount(tt)
|
|
141
|
-
if not count:
|
|
142
|
-
continue
|
|
143
|
-
for ti in range(1, int(count) + 1):
|
|
144
|
-
items = timeline.GetItemListInTrack(tt, ti) or []
|
|
145
|
-
items_sorted = sorted(items, key=lambda it: int(it.GetStart()))
|
|
146
|
-
for prev, curr in zip(items_sorted, items_sorted[1:]):
|
|
147
|
-
gap_frames = int(curr.GetStart()) - int(prev.GetEnd())
|
|
148
|
-
if gap_frames > 0:
|
|
149
|
-
out["gap_count"] += 1
|
|
150
|
-
out["total_gap_frames"] += gap_frames
|
|
151
|
-
if fps > 0:
|
|
152
|
-
out["total_gap_seconds"] = round(out["total_gap_frames"] / fps, 3)
|
|
153
|
-
except Exception as exc: # pragma: no cover
|
|
154
|
-
logger.debug("capture_timeline_gap_stats failed: %s", exc)
|
|
155
|
-
return out
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
METRIC_CAPTURERS: Dict[str, Callable[[Any], Any]] = {
|
|
159
|
-
METRIC_DURATION_SECONDS: capture_timeline_duration_seconds,
|
|
160
|
-
METRIC_CLIP_COUNT: capture_timeline_clip_count,
|
|
161
|
-
METRIC_GAP_COUNT: lambda tl: capture_timeline_gap_stats(tl)["gap_count"],
|
|
162
|
-
METRIC_TOTAL_GAP_SECONDS: lambda tl: capture_timeline_gap_stats(tl)["total_gap_seconds"],
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
def capture_metric(metric: str, timeline: Any) -> Optional[float]:
|
|
167
|
-
"""Capture a registered metric from a live timeline. None if not capturable."""
|
|
168
|
-
fn = METRIC_CAPTURERS.get(metric)
|
|
169
|
-
if fn is None:
|
|
170
|
-
return None
|
|
171
|
-
try:
|
|
172
|
-
value = fn(timeline)
|
|
173
|
-
except Exception as exc: # pragma: no cover
|
|
174
|
-
logger.debug("capture_metric(%s) failed: %s", metric, exc)
|
|
175
|
-
return None
|
|
176
|
-
if value is None:
|
|
177
|
-
return None
|
|
178
|
-
try:
|
|
179
|
-
return float(value)
|
|
180
|
-
except (TypeError, ValueError):
|
|
181
|
-
return None
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
# ── Logging ──────────────────────────────────────────────────────────────────
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
def log_brain_edit(
|
|
188
|
-
*,
|
|
189
|
-
project_root: str,
|
|
190
|
-
analysis_run_id: str,
|
|
191
|
-
edit_type: str,
|
|
192
|
-
tool_name: Optional[str] = None,
|
|
193
|
-
action_name: Optional[str] = None,
|
|
194
|
-
timeline_before: Optional[str] = None,
|
|
195
|
-
timeline_after: Optional[str] = None,
|
|
196
|
-
target_metric: Optional[str] = None,
|
|
197
|
-
metric_direction: Optional[str] = None,
|
|
198
|
-
before_value: Optional[float] = None,
|
|
199
|
-
after_value: Optional[float] = None,
|
|
200
|
-
rationale: Optional[str] = None,
|
|
201
|
-
params: Optional[Dict[str, Any]] = None,
|
|
202
|
-
result_summary: Optional[Dict[str, Any]] = None,
|
|
203
|
-
project_name: Optional[str] = None,
|
|
204
|
-
initiator: Optional[str] = None,
|
|
205
|
-
) -> Dict[str, Any]:
|
|
206
|
-
"""Persist a brain-edit row + update the cross-project registry."""
|
|
207
|
-
if target_metric is not None and target_metric not in METRIC_REGISTRY:
|
|
208
|
-
logger.warning("Unknown metric '%s' — logging anyway", target_metric)
|
|
209
|
-
if metric_direction is not None and metric_direction not in DIRECTIONS:
|
|
210
|
-
return {"success": False, "error": f"Invalid direction: {metric_direction}"}
|
|
211
|
-
|
|
212
|
-
params_json = json.dumps(params, default=str) if params is not None else None
|
|
213
|
-
result_json = json.dumps(result_summary, default=str) if result_summary is not None else None
|
|
214
|
-
created_at = _now_iso()
|
|
215
|
-
|
|
216
|
-
with timeline_brain_db.transaction(project_root) as txn:
|
|
217
|
-
cursor = txn.execute(
|
|
218
|
-
"""
|
|
219
|
-
INSERT INTO brain_edits(
|
|
220
|
-
analysis_run_id, timeline_before, timeline_after, edit_type,
|
|
221
|
-
tool_name, action_name, target_metric, metric_direction,
|
|
222
|
-
before_value, after_value, rationale, params_json,
|
|
223
|
-
result_summary_json, created_at, initiator, actor
|
|
224
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
225
|
-
""",
|
|
226
|
-
(
|
|
227
|
-
analysis_run_id, timeline_before, timeline_after, edit_type,
|
|
228
|
-
tool_name, action_name, target_metric, metric_direction,
|
|
229
|
-
before_value, after_value, rationale, params_json,
|
|
230
|
-
result_json, created_at, initiator,
|
|
231
|
-
actor_identity.actor_string(),
|
|
232
|
-
),
|
|
233
|
-
)
|
|
234
|
-
row_id = cursor.lastrowid
|
|
235
|
-
|
|
236
|
-
try:
|
|
237
|
-
update_brain_edits_registry(
|
|
238
|
-
project_root=project_root,
|
|
239
|
-
project_name=project_name,
|
|
240
|
-
summary={
|
|
241
|
-
"row_id": row_id,
|
|
242
|
-
"analysis_run_id": analysis_run_id,
|
|
243
|
-
"timeline_before": timeline_before,
|
|
244
|
-
"timeline_after": timeline_after,
|
|
245
|
-
"edit_type": edit_type,
|
|
246
|
-
"tool_name": tool_name,
|
|
247
|
-
"action_name": action_name,
|
|
248
|
-
"target_metric": target_metric,
|
|
249
|
-
"before_value": before_value,
|
|
250
|
-
"after_value": after_value,
|
|
251
|
-
"delta": _compute_delta(before_value, after_value),
|
|
252
|
-
"created_at": created_at,
|
|
253
|
-
},
|
|
254
|
-
)
|
|
255
|
-
except Exception as exc:
|
|
256
|
-
logger.warning("update_brain_edits_registry failed (non-fatal): %s", exc)
|
|
257
|
-
|
|
258
|
-
return {"success": True, "row_id": row_id, "created_at": created_at}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
def _compute_delta(before: Optional[float], after: Optional[float]) -> Optional[float]:
|
|
262
|
-
if before is None or after is None:
|
|
263
|
-
return None
|
|
264
|
-
try:
|
|
265
|
-
return round(float(after) - float(before), 6)
|
|
266
|
-
except (TypeError, ValueError):
|
|
267
|
-
return None
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
# ── Query ─────────────────────────────────────────────────────────────────────
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
def get_brain_edit_history(
|
|
274
|
-
*,
|
|
275
|
-
project_root: str,
|
|
276
|
-
timeline_name: Optional[str] = None,
|
|
277
|
-
analysis_run_id: Optional[str] = None,
|
|
278
|
-
limit: int = 50,
|
|
279
|
-
) -> List[Dict[str, Any]]:
|
|
280
|
-
"""Return brain_edit rows, optionally filtered by timeline or run, newest first."""
|
|
281
|
-
# SQLite treats a negative LIMIT as "no limit"; clamp so a negative/huge
|
|
282
|
-
# limit can't silently fetch the whole table (EX8).
|
|
283
|
-
try:
|
|
284
|
-
limit = max(1, min(1000, int(limit)))
|
|
285
|
-
except (TypeError, ValueError):
|
|
286
|
-
limit = 50
|
|
287
|
-
conn = timeline_brain_db.connect(project_root)
|
|
288
|
-
clauses: List[str] = []
|
|
289
|
-
args: List[Any] = []
|
|
290
|
-
if timeline_name:
|
|
291
|
-
clauses.append("(timeline_before = ? OR timeline_after = ?)")
|
|
292
|
-
args.extend([timeline_name, timeline_name])
|
|
293
|
-
if analysis_run_id:
|
|
294
|
-
clauses.append("analysis_run_id = ?")
|
|
295
|
-
args.append(analysis_run_id)
|
|
296
|
-
where = (" WHERE " + " AND ".join(clauses)) if clauses else ""
|
|
297
|
-
rows = conn.execute(
|
|
298
|
-
f"""
|
|
299
|
-
SELECT id, analysis_run_id, timeline_before, timeline_after, edit_type,
|
|
300
|
-
tool_name, action_name, target_metric, metric_direction,
|
|
301
|
-
before_value, after_value, rationale, params_json,
|
|
302
|
-
result_summary_json, created_at, rolled_back_at
|
|
303
|
-
FROM brain_edits
|
|
304
|
-
{where}
|
|
305
|
-
ORDER BY id DESC
|
|
306
|
-
LIMIT ?
|
|
307
|
-
""",
|
|
308
|
-
(*args, int(limit)),
|
|
309
|
-
).fetchall()
|
|
310
|
-
|
|
311
|
-
out: List[Dict[str, Any]] = []
|
|
312
|
-
for r in rows:
|
|
313
|
-
d = dict(r)
|
|
314
|
-
for key in ("params_json", "result_summary_json"):
|
|
315
|
-
raw = d.pop(key)
|
|
316
|
-
d[key[:-5]] = json.loads(raw) if raw else None
|
|
317
|
-
d["delta"] = _compute_delta(d.get("before_value"), d.get("after_value"))
|
|
318
|
-
out.append(d)
|
|
319
|
-
return out
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
# ── Cross-project rollup ─────────────────────────────────────────────────────
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
def _registry_path_for(project_root: str) -> str:
|
|
326
|
-
base_root = os.path.dirname(os.path.normpath(project_root))
|
|
327
|
-
return os.path.join(base_root, REGISTRY_FILENAME)
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
def update_brain_edits_registry(
|
|
331
|
-
*,
|
|
332
|
-
project_root: str,
|
|
333
|
-
project_name: Optional[str],
|
|
334
|
-
summary: Dict[str, Any],
|
|
335
|
-
max_rows: int = 5000,
|
|
336
|
-
) -> Dict[str, Any]:
|
|
337
|
-
"""Append `summary` to the cross-project registry; trim oldest beyond max_rows.
|
|
338
|
-
|
|
339
|
-
The registry sits one level above each project_root (the analysis "base
|
|
340
|
-
root"), so it aggregates every project that shares that base. Mirrors how
|
|
341
|
-
`analysis_registry.json` is positioned.
|
|
342
|
-
"""
|
|
343
|
-
if not project_root:
|
|
344
|
-
return {"success": False, "error": "project_root required"}
|
|
345
|
-
path = _registry_path_for(project_root)
|
|
346
|
-
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
347
|
-
|
|
348
|
-
payload: Dict[str, Any]
|
|
349
|
-
if os.path.isfile(path):
|
|
350
|
-
try:
|
|
351
|
-
with open(path, "r") as fh:
|
|
352
|
-
payload = json.load(fh)
|
|
353
|
-
except (OSError, json.JSONDecodeError):
|
|
354
|
-
payload = {"entries": []}
|
|
355
|
-
else:
|
|
356
|
-
payload = {"entries": []}
|
|
357
|
-
|
|
358
|
-
entries = payload.setdefault("entries", [])
|
|
359
|
-
entry = {
|
|
360
|
-
"project_root": project_root,
|
|
361
|
-
"project_name": project_name or os.path.basename(project_root.rstrip("/")),
|
|
362
|
-
**summary,
|
|
363
|
-
}
|
|
364
|
-
entries.append(entry)
|
|
365
|
-
if len(entries) > max_rows:
|
|
366
|
-
del entries[: len(entries) - max_rows]
|
|
367
|
-
payload["entries"] = entries
|
|
368
|
-
payload["updated_at"] = _now_iso()
|
|
369
|
-
|
|
370
|
-
tmp_path = path + ".tmp"
|
|
371
|
-
with open(tmp_path, "w") as fh:
|
|
372
|
-
json.dump(payload, fh, indent=2, default=str)
|
|
373
|
-
os.replace(tmp_path, path)
|
|
374
|
-
return {"success": True, "registry_path": path, "entry_count": len(entries)}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
def read_brain_edits_registry(project_root: str) -> Dict[str, Any]:
|
|
378
|
-
path = _registry_path_for(project_root)
|
|
379
|
-
if not os.path.isfile(path):
|
|
380
|
-
return {"entries": [], "registry_path": path}
|
|
381
|
-
try:
|
|
382
|
-
with open(path, "r") as fh:
|
|
383
|
-
payload = json.load(fh)
|
|
384
|
-
except (OSError, json.JSONDecodeError):
|
|
385
|
-
return {"entries": [], "registry_path": path}
|
|
386
|
-
payload["registry_path"] = path
|
|
387
|
-
return payload
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
"""Instrumentation for counting DaVinci Resolve bridge round-trips.
|
|
2
|
-
|
|
3
|
-
Every attribute access and method call on a Resolve object is a COM/socket
|
|
4
|
-
round-trip — the dominant cost of most operations. Before optimizing (or adding
|
|
5
|
-
a cache), you have to *measure* where the round-trips concentrate. This module
|
|
6
|
-
provides an opt-in counting proxy that wraps a Resolve handle and tallies
|
|
7
|
-
attribute accesses and method calls, so a hot path's real bridge cost is
|
|
8
|
-
visible instead of guessed.
|
|
9
|
-
|
|
10
|
-
This is the measurement half of the bridge-perf work. A property cache is only
|
|
11
|
-
worth building once measurement shows a clear, repeated-read hot spot — don't
|
|
12
|
-
cache blind.
|
|
13
|
-
|
|
14
|
-
Example:
|
|
15
|
-
|
|
16
|
-
counts = {}
|
|
17
|
-
rp = CountingProxy(resolve, counts)
|
|
18
|
-
rp.GetProjectManager().GetCurrentProject().GetName()
|
|
19
|
-
print(counts) # {'attr_access': N, 'calls': M}
|
|
20
|
-
"""
|
|
21
|
-
from typing import Any, Dict, Optional
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class CountingProxy:
|
|
25
|
-
"""Wrap a Resolve object, counting attribute accesses and method calls.
|
|
26
|
-
|
|
27
|
-
Method return values that are themselves objects are wrapped too, so a whole
|
|
28
|
-
call chain through the bridge is counted. Primitives are returned as-is.
|
|
29
|
-
"""
|
|
30
|
-
|
|
31
|
-
__slots__ = ("_target", "_counter")
|
|
32
|
-
|
|
33
|
-
def __init__(self, target: Any, counter: Dict[str, int]):
|
|
34
|
-
object.__setattr__(self, "_target", target)
|
|
35
|
-
object.__setattr__(self, "_counter", counter)
|
|
36
|
-
counter.setdefault("attr_access", 0)
|
|
37
|
-
counter.setdefault("calls", 0)
|
|
38
|
-
|
|
39
|
-
def __getattr__(self, name: str) -> Any:
|
|
40
|
-
counter = object.__getattribute__(self, "_counter")
|
|
41
|
-
target = object.__getattribute__(self, "_target")
|
|
42
|
-
counter["attr_access"] += 1
|
|
43
|
-
attr = getattr(target, name)
|
|
44
|
-
if callable(attr):
|
|
45
|
-
def wrapped(*args: Any, **kwargs: Any) -> Any:
|
|
46
|
-
counter["calls"] += 1
|
|
47
|
-
result = attr(*args, **kwargs)
|
|
48
|
-
return _maybe_wrap(result, counter)
|
|
49
|
-
return wrapped
|
|
50
|
-
return _maybe_wrap(attr, counter)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
_PRIMITIVE = (str, int, float, bool, bytes, type(None))
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def _maybe_wrap(value: Any, counter: Dict[str, int]) -> Any:
|
|
57
|
-
if isinstance(value, _PRIMITIVE):
|
|
58
|
-
return value
|
|
59
|
-
if isinstance(value, (list, tuple)):
|
|
60
|
-
# Wrap object elements so traversals (e.g. clip lists) are counted.
|
|
61
|
-
return type(value)(_maybe_wrap(v, counter) for v in value)
|
|
62
|
-
if isinstance(value, dict):
|
|
63
|
-
return {k: _maybe_wrap(v, counter) for k, v in value.items()}
|
|
64
|
-
# Likely a Resolve API object — wrap so its further use is counted.
|
|
65
|
-
return CountingProxy(value, counter)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def measure(fn, target: Any) -> Dict[str, int]:
|
|
69
|
-
"""Run ``fn(proxy)`` against a counting proxy of ``target``; return the counts.
|
|
70
|
-
|
|
71
|
-
``fn`` receives the proxy and should exercise the operation under study.
|
|
72
|
-
"""
|
|
73
|
-
counter: Dict[str, int] = {}
|
|
74
|
-
proxy = CountingProxy(target, counter)
|
|
75
|
-
fn(proxy)
|
|
76
|
-
return counter
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
"""CDL normalization helpers shared by compound and granular servers."""
|
|
2
|
-
|
|
3
|
-
from typing import Any, Dict
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def normalize_cdl_payload(cdl: Any) -> Any:
|
|
7
|
-
"""Resolve's SetCDL expects strings like ``"1.0 1.0 1.0"`` instead of arrays."""
|
|
8
|
-
if not isinstance(cdl, dict):
|
|
9
|
-
return cdl
|
|
10
|
-
out: Dict[str, Any] = {}
|
|
11
|
-
for key, value in cdl.items():
|
|
12
|
-
if isinstance(value, (list, tuple)):
|
|
13
|
-
out[key] = " ".join(str(item) for item in value)
|
|
14
|
-
elif isinstance(value, bool):
|
|
15
|
-
out[key] = str(value)
|
|
16
|
-
elif isinstance(value, (int, float)):
|
|
17
|
-
out[key] = str(value)
|
|
18
|
-
else:
|
|
19
|
-
out[key] = value
|
|
20
|
-
return out
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
"""Safe declarative clip-query DSL.
|
|
2
|
-
|
|
3
|
-
The agent-facing way to ask "which clips match X?" without enumerating a whole
|
|
4
|
-
timeline call-by-call. Named filters only — no arbitrary lambdas/code cross the
|
|
5
|
-
tool boundary (the brain gets a closed vocabulary, not `eval`).
|
|
6
|
-
|
|
7
|
-
Pure: `filter_clips` takes a list of plain clip dicts + a filter dict and returns
|
|
8
|
-
the matching subset. The live MCP adapter gathers clip dicts from the timeline
|
|
9
|
-
and calls this.
|
|
10
|
-
|
|
11
|
-
Each clip dict is expected to carry (best-effort; missing keys are tolerated):
|
|
12
|
-
name, track_type, track_index, duration (frames), in_frame, out_frame,
|
|
13
|
-
clip_id / media_pool_item_id, clip_hash, marker_color, analyzed (bool),
|
|
14
|
-
has_transcription (bool), shot_type (str).
|
|
15
|
-
"""
|
|
16
|
-
from __future__ import annotations
|
|
17
|
-
|
|
18
|
-
from typing import Any, Dict, List, Tuple
|
|
19
|
-
|
|
20
|
-
# Supported filter keys and a one-line description (also used to validate input
|
|
21
|
-
# and to document the surface in the tool docstring).
|
|
22
|
-
SUPPORTED_FILTERS: Dict[str, str] = {
|
|
23
|
-
"track_type": "exact match: 'video' | 'audio' | 'subtitle'",
|
|
24
|
-
"track_index": "exact 1-based track index",
|
|
25
|
-
"name_contains": "case-insensitive substring of the clip name",
|
|
26
|
-
"duration_lt": "duration (frames) strictly less than",
|
|
27
|
-
"duration_gt": "duration (frames) strictly greater than",
|
|
28
|
-
"marker_color": "exact clip marker/flag color",
|
|
29
|
-
"shot_type": "exact analyzed shot_type",
|
|
30
|
-
"analyzed": "bool — clip has an analysis record",
|
|
31
|
-
"has_transcription": "bool — clip has transcription",
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def validate_filters(filters: Dict[str, Any]) -> Tuple[bool, List[str]]:
|
|
36
|
-
"""Return (ok, unknown_keys). Unknown filter keys are rejected, not ignored,
|
|
37
|
-
so a typo never silently widens the match set."""
|
|
38
|
-
unknown = [k for k in filters if k not in SUPPORTED_FILTERS]
|
|
39
|
-
return (not unknown, unknown)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def _as_bool(value: Any) -> bool:
|
|
43
|
-
if isinstance(value, bool):
|
|
44
|
-
return value
|
|
45
|
-
if isinstance(value, str):
|
|
46
|
-
return value.strip().lower() in {"1", "true", "yes", "y"}
|
|
47
|
-
return bool(value)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def _matches(clip: Dict[str, Any], filters: Dict[str, Any]) -> bool:
|
|
51
|
-
if "track_type" in filters and clip.get("track_type") != filters["track_type"]:
|
|
52
|
-
return False
|
|
53
|
-
if "track_index" in filters and clip.get("track_index") != int(filters["track_index"]):
|
|
54
|
-
return False
|
|
55
|
-
if "name_contains" in filters:
|
|
56
|
-
needle = str(filters["name_contains"]).lower()
|
|
57
|
-
if needle not in str(clip.get("name") or "").lower():
|
|
58
|
-
return False
|
|
59
|
-
dur = clip.get("duration")
|
|
60
|
-
if "duration_lt" in filters:
|
|
61
|
-
if dur is None or not (dur < float(filters["duration_lt"])):
|
|
62
|
-
return False
|
|
63
|
-
if "duration_gt" in filters:
|
|
64
|
-
if dur is None or not (dur > float(filters["duration_gt"])):
|
|
65
|
-
return False
|
|
66
|
-
if "marker_color" in filters and clip.get("marker_color") != filters["marker_color"]:
|
|
67
|
-
return False
|
|
68
|
-
if "shot_type" in filters and clip.get("shot_type") != filters["shot_type"]:
|
|
69
|
-
return False
|
|
70
|
-
if "analyzed" in filters and bool(clip.get("analyzed")) != _as_bool(filters["analyzed"]):
|
|
71
|
-
return False
|
|
72
|
-
if "has_transcription" in filters and bool(clip.get("has_transcription")) != _as_bool(
|
|
73
|
-
filters["has_transcription"]
|
|
74
|
-
):
|
|
75
|
-
return False
|
|
76
|
-
return True
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def filter_clips(clips: List[Dict[str, Any]], filters: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
80
|
-
"""Return the subset of `clips` matching every supplied filter (AND semantics).
|
|
81
|
-
|
|
82
|
-
Empty/None filter values are skipped so callers can pass a sparse dict.
|
|
83
|
-
"""
|
|
84
|
-
active = {k: v for k, v in (filters or {}).items() if v is not None and v != ""}
|
|
85
|
-
return [c for c in clips if _matches(c, active)]
|