@hybridlabor-api/aos 4.4.1 → 4.4.2-beta.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.
Files changed (305) hide show
  1. package/.agents/skills/firecrawl/SKILL.md +149 -0
  2. package/.agents/skills/firecrawl/rules/install.md +82 -0
  3. package/.agents/skills/firecrawl/rules/security.md +26 -0
  4. package/.agents/skills/firecrawl-agent/SKILL.md +58 -0
  5. package/.agents/skills/firecrawl-build/SKILL.md +39 -0
  6. package/.agents/skills/firecrawl-build-interact/SKILL.md +68 -0
  7. package/.agents/skills/firecrawl-build-onboarding/SKILL.md +103 -0
  8. package/.agents/skills/firecrawl-build-onboarding/references/auth-flow.md +39 -0
  9. package/.agents/skills/firecrawl-build-onboarding/references/project-setup.md +20 -0
  10. package/.agents/skills/firecrawl-build-onboarding/references/sdk-installation.md +17 -0
  11. package/.agents/skills/firecrawl-build-scrape/SKILL.md +69 -0
  12. package/.agents/skills/firecrawl-build-search/SKILL.md +69 -0
  13. package/.agents/skills/firecrawl-crawl/SKILL.md +59 -0
  14. package/.agents/skills/firecrawl-download/SKILL.md +70 -0
  15. package/.agents/skills/firecrawl-interact/SKILL.md +84 -0
  16. package/.agents/skills/firecrawl-map/SKILL.md +51 -0
  17. package/.agents/skills/firecrawl-scrape/SKILL.md +69 -0
  18. package/.agents/skills/firecrawl-search/SKILL.md +60 -0
  19. package/CLAUDE.md +573 -0
  20. package/README.de.md +0 -8
  21. package/README.md +0 -8
  22. package/README.pt.md +0 -8
  23. package/assets/brand/ao-ant-mark.svg +91 -0
  24. package/assets/brand/bdb-core-mark.svg +34 -0
  25. package/assets/brand/heimdall-guardian-mark.svg +16 -0
  26. package/assets/brand/memb-crystal-mark.svg +18 -0
  27. package/bin/aos-config.mjs +89 -0
  28. package/bin/aos-dashboard.mjs +608 -0
  29. package/bin/aos-uninstall.mjs +227 -0
  30. package/docs/sessions/audit-2026-09-14-silent-failures.md +114 -0
  31. package/docs/skills_table.md +0 -3
  32. package/installer.js +387 -237
  33. package/mcps/RhinoMCP/cc-plugin/.claude/settings.json +10 -0
  34. package/mcps/after-effects-mcp/build/index.js +840 -0
  35. package/mcps/after-effects-mcp/build/scripts/applyEffect.jsx +153 -0
  36. package/mcps/after-effects-mcp/build/scripts/applyEffectTemplate.jsx +218 -0
  37. package/mcps/after-effects-mcp/build/scripts/createComposition.jsx +71 -0
  38. package/mcps/after-effects-mcp/build/scripts/createShapeLayer.jsx +147 -0
  39. package/mcps/after-effects-mcp/build/scripts/createSolidLayer.jsx +114 -0
  40. package/mcps/after-effects-mcp/build/scripts/createTextLayer.jsx +115 -0
  41. package/mcps/after-effects-mcp/build/scripts/getLayerInfo.jsx +192 -0
  42. package/mcps/after-effects-mcp/build/scripts/getProjectInfo.jsx +90 -0
  43. package/mcps/after-effects-mcp/build/scripts/listCompositions.jsx +50 -0
  44. package/mcps/after-effects-mcp/build/scripts/mcp-bridge-auto.jsx +1773 -0
  45. package/mcps/after-effects-mcp/build/scripts/setLayerProperties.jsx +160 -0
  46. package/mcps/computer-use-mcp/dist/client.d.ts +150 -0
  47. package/mcps/computer-use-mcp/dist/client.js +136 -0
  48. package/mcps/computer-use-mcp/dist/entrypoint.d.ts +16 -0
  49. package/mcps/computer-use-mcp/dist/entrypoint.js +26 -0
  50. package/mcps/computer-use-mcp/dist/native.d.ts +212 -0
  51. package/mcps/computer-use-mcp/dist/native.js +50 -0
  52. package/mcps/computer-use-mcp/dist/server.d.ts +32 -0
  53. package/mcps/computer-use-mcp/dist/server.js +342 -0
  54. package/mcps/computer-use-mcp/dist/session.d.ts +101 -0
  55. package/mcps/computer-use-mcp/dist/session.js +2372 -0
  56. package/package.json +13 -4
  57. package/skills/global_config/aos-project-init/SKILL.md +97 -130
  58. package/skills/global_config/aos-project-init/assets/openwikiignore.template +62 -0
  59. package/skills/global_config/aos-project-init/scripts/aos-project-doctor.mjs +38 -1
  60. package/skills/global_config/aos-setup/SKILL.md +98 -8
  61. package/skills/global_config/aos-setup/scripts/aos-doctor.mjs +21 -2
  62. package/skills/global_config/ask-tim/SKILL.md +2 -7
  63. package/skills/global_config/openwiki-skill/scripts/openwiki_daemon.py +65 -12
  64. package/bin/setup-saas.mjs +0 -792
  65. package/mcps/blender-mcp-server/.dockerignore +0 -19
  66. package/mcps/blender-mcp-server/.github/workflows/ci.yml +0 -67
  67. package/mcps/blender-mcp-server/.github/workflows/publish-pypi.yml +0 -54
  68. package/mcps/blender-mcp-server/CHANGELOG.md +0 -64
  69. package/mcps/blender-mcp-server/CONTRIBUTING.md +0 -107
  70. package/mcps/blender-mcp-server/Dockerfile +0 -19
  71. package/mcps/blender-mcp-server/LICENSE +0 -21
  72. package/mcps/blender-mcp-server/README.md +0 -409
  73. package/mcps/blender-mcp-server/addon/__init__.py +0 -1440
  74. package/mcps/blender-mcp-server/addon/models.py +0 -223
  75. package/mcps/blender-mcp-server/pyproject.toml +0 -120
  76. package/mcps/blender-mcp-server/scripts/blender_bridge_request.py +0 -71
  77. package/mcps/blender-mcp-server/scripts/blender_create_test_cube.py +0 -49
  78. package/mcps/blender-mcp-server/scripts/blender_scene_info.py +0 -38
  79. package/mcps/blender-mcp-server/scripts/build_addon_zip.sh +0 -29
  80. package/mcps/blender-mcp-server/scripts/demos/README.md +0 -186
  81. package/mcps/blender-mcp-server/scripts/demos/dam_break_scene.py +0 -276
  82. package/mcps/blender-mcp-server/scripts/demos/open_dam_break_gui.py +0 -33
  83. package/mcps/blender-mcp-server/scripts/demos/pipe_movement_study.py +0 -518
  84. package/mcps/blender-mcp-server/scripts/demos/pipe_pressure_failure_scene.py +0 -806
  85. package/mcps/blender-mcp-server/scripts/demos/procedural_dam_break_scene.py +0 -600
  86. package/mcps/blender-mcp-server/scripts/demos/run_dam_break.py +0 -510
  87. package/mcps/blender-mcp-server/scripts/launch_blender_gui.py +0 -41
  88. package/mcps/blender-mcp-server/scripts/library/README.md +0 -64
  89. package/mcps/blender-mcp-server/scripts/library/apply_transforms.py +0 -46
  90. package/mcps/blender-mcp-server/scripts/library/camera.py +0 -51
  91. package/mcps/blender-mcp-server/scripts/library/collections.py +0 -65
  92. package/mcps/blender-mcp-server/scripts/library/create_mesh.py +0 -41
  93. package/mcps/blender-mcp-server/scripts/library/effector.py +0 -48
  94. package/mcps/blender-mcp-server/scripts/library/fluid_domain.py +0 -80
  95. package/mcps/blender-mcp-server/scripts/library/fluid_inflow.py +0 -98
  96. package/mcps/blender-mcp-server/scripts/library/frame_range.py +0 -35
  97. package/mcps/blender-mcp-server/scripts/library/keyframes.py +0 -59
  98. package/mcps/blender-mcp-server/scripts/library/rigid_body.py +0 -52
  99. package/mcps/blender-mcp-server/scripts/library/save_blend.py +0 -40
  100. package/mcps/blender-mcp-server/scripts/server_start.sh +0 -26
  101. package/mcps/blender-mcp-server/src/blender_mcp_server/__init__.py +0 -3
  102. package/mcps/blender-mcp-server/src/blender_mcp_server/headless.py +0 -330
  103. package/mcps/blender-mcp-server/src/blender_mcp_server/server.py +0 -606
  104. package/mcps/davinci-mcp-professional/.bandit +0 -35
  105. package/mcps/davinci-mcp-professional/.claude/commands/security-review.md +0 -191
  106. package/mcps/davinci-mcp-professional/.claude/settings.json +0 -1
  107. package/mcps/davinci-mcp-professional/.editorconfig +0 -27
  108. package/mcps/davinci-mcp-professional/.gitattributes +0 -207
  109. package/mcps/davinci-mcp-professional/.github/CODEOWNERS +0 -2
  110. package/mcps/davinci-mcp-professional/.github/ISSUE_TEMPLATE/bug_report.md +0 -33
  111. package/mcps/davinci-mcp-professional/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
  112. package/mcps/davinci-mcp-professional/.github/PULL_REQUEST_TEMPLATE.md +0 -20
  113. package/mcps/davinci-mcp-professional/.github/SECURITY.md +0 -97
  114. package/mcps/davinci-mcp-professional/.github/dependabot.yml +0 -27
  115. package/mcps/davinci-mcp-professional/.github/workflows/ci.yml +0 -39
  116. package/mcps/davinci-mcp-professional/ATTRIBUTION.md +0 -4
  117. package/mcps/davinci-mcp-professional/AUTHORS.md +0 -9
  118. package/mcps/davinci-mcp-professional/BUGS.md +0 -55
  119. package/mcps/davinci-mcp-professional/CLAUDE.md +0 -229
  120. package/mcps/davinci-mcp-professional/CONTRIBUTING.md +0 -51
  121. package/mcps/davinci-mcp-professional/COPYING.md +0 -228
  122. package/mcps/davinci-mcp-professional/Doxyfile +0 -379
  123. package/mcps/davinci-mcp-professional/README.md +0 -143
  124. package/mcps/davinci-mcp-professional/SECURITY.md +0 -40
  125. package/mcps/davinci-mcp-professional/USING.md +0 -301
  126. package/mcps/davinci-mcp-professional/VERSION.md +0 -13
  127. package/mcps/davinci-mcp-professional/build.py +0 -169
  128. package/mcps/davinci-mcp-professional/claude_desktop_config_template.json +0 -11
  129. package/mcps/davinci-mcp-professional/hooks/ci-check.sh +0 -29
  130. package/mcps/davinci-mcp-professional/hooks/pre-push +0 -46
  131. package/mcps/davinci-mcp-professional/main.py +0 -40
  132. package/mcps/davinci-mcp-professional/mcp_server.py +0 -30
  133. package/mcps/davinci-mcp-professional/pyproject.toml +0 -115
  134. package/mcps/davinci-mcp-professional/security_audit.py +0 -379
  135. package/mcps/davinci-mcp-professional/src/davinci_mcp/__init__.py +0 -19
  136. package/mcps/davinci-mcp-professional/src/davinci_mcp/cli.py +0 -138
  137. package/mcps/davinci-mcp-professional/src/davinci_mcp/resolve_client.py +0 -342
  138. package/mcps/davinci-mcp-professional/src/davinci_mcp/resources/__init__.py +0 -60
  139. package/mcps/davinci-mcp-professional/src/davinci_mcp/server.py +0 -185
  140. package/mcps/davinci-mcp-professional/src/davinci_mcp/tools/__init__.py +0 -146
  141. package/mcps/davinci-mcp-professional/src/davinci_mcp/types.py +0 -125
  142. package/mcps/davinci-mcp-professional/src/davinci_mcp/utils/__init__.py +0 -19
  143. package/mcps/davinci-mcp-professional/src/davinci_mcp/utils/platform.py +0 -239
  144. package/mcps/davinci-mcp-professional/test.py +0 -186
  145. package/mcps/davinci-mcp-professional/uv.lock +0 -2073
  146. package/mcps/davinci-resolve-mcp/.claude/skills/media-analysis.md +0 -44
  147. package/mcps/davinci-resolve-mcp/.clinerules +0 -17
  148. package/mcps/davinci-resolve-mcp/.cursorrules +0 -17
  149. package/mcps/davinci-resolve-mcp/.github/copilot-instructions.md +0 -17
  150. package/mcps/davinci-resolve-mcp/.github/workflows/npm-publish.yml +0 -109
  151. package/mcps/davinci-resolve-mcp/.windsurfrules +0 -17
  152. package/mcps/davinci-resolve-mcp/AGENTS.md +0 -114
  153. package/mcps/davinci-resolve-mcp/CHANGELOG.md +0 -2648
  154. package/mcps/davinci-resolve-mcp/CLAUDE.md +0 -15
  155. package/mcps/davinci-resolve-mcp/LICENSE +0 -21
  156. package/mcps/davinci-resolve-mcp/README.md +0 -173
  157. package/mcps/davinci-resolve-mcp/SECURITY.md +0 -53
  158. package/mcps/davinci-resolve-mcp/bin/davinci-resolve-mcp.mjs +0 -450
  159. package/mcps/davinci-resolve-mcp/examples/README.md +0 -53
  160. package/mcps/davinci-resolve-mcp/examples/markers/README.md +0 -81
  161. package/mcps/davinci-resolve-mcp/examples/media/README.md +0 -94
  162. package/mcps/davinci-resolve-mcp/examples/timeline/README.md +0 -98
  163. package/mcps/davinci-resolve-mcp/install.py +0 -1687
  164. package/mcps/davinci-resolve-mcp/package.json +0 -52
  165. package/mcps/davinci-resolve-mcp/release-notes/v2.22.0.md +0 -51
  166. package/mcps/davinci-resolve-mcp/release-notes/v2.23.0.md +0 -51
  167. package/mcps/davinci-resolve-mcp/release-notes/v2.23.1.md +0 -35
  168. package/mcps/davinci-resolve-mcp/release-notes/v2.24.1.md +0 -32
  169. package/mcps/davinci-resolve-mcp/scripts/audit_api_parity.py +0 -275
  170. package/mcps/davinci-resolve-mcp/scripts/audit_readwrite_symmetry.py +0 -80
  171. package/mcps/davinci-resolve-mcp/scripts/doctor.py +0 -227
  172. package/mcps/davinci-resolve-mcp/scripts/gen_api_limitations.py +0 -144
  173. package/mcps/davinci-resolve-mcp/scripts/live_media_analysis_polish_probe.py +0 -65
  174. package/mcps/davinci-resolve-mcp/scripts/measure_bridge_cost.py +0 -65
  175. package/mcps/davinci-resolve-mcp/scripts/regen_panel_screenshots.py +0 -76
  176. package/mcps/davinci-resolve-mcp/src/__init__.py +0 -3
  177. package/mcps/davinci-resolve-mcp/src/analysis_dashboard.py +0 -15505
  178. package/mcps/davinci-resolve-mcp/src/batch_cli.py +0 -540
  179. package/mcps/davinci-resolve-mcp/src/control_panel.py +0 -13
  180. package/mcps/davinci-resolve-mcp/src/granular/__init__.py +0 -17
  181. package/mcps/davinci-resolve-mcp/src/granular/common.py +0 -741
  182. package/mcps/davinci-resolve-mcp/src/granular/folder.py +0 -420
  183. package/mcps/davinci-resolve-mcp/src/granular/gallery.py +0 -306
  184. package/mcps/davinci-resolve-mcp/src/granular/graph.py +0 -309
  185. package/mcps/davinci-resolve-mcp/src/granular/media_pool.py +0 -679
  186. package/mcps/davinci-resolve-mcp/src/granular/media_pool_item.py +0 -1020
  187. package/mcps/davinci-resolve-mcp/src/granular/media_storage.py +0 -179
  188. package/mcps/davinci-resolve-mcp/src/granular/project.py +0 -1654
  189. package/mcps/davinci-resolve-mcp/src/granular/resolve_control.py +0 -538
  190. package/mcps/davinci-resolve-mcp/src/granular/timeline.py +0 -1075
  191. package/mcps/davinci-resolve-mcp/src/granular/timeline_item.py +0 -2251
  192. package/mcps/davinci-resolve-mcp/src/resolve_mcp_server.py +0 -43
  193. package/mcps/davinci-resolve-mcp/src/server.py +0 -24116
  194. package/mcps/davinci-resolve-mcp/src/utils/__init__.py +0 -3
  195. package/mcps/davinci-resolve-mcp/src/utils/actor_identity.py +0 -46
  196. package/mcps/davinci-resolve-mcp/src/utils/analysis_caps.py +0 -678
  197. package/mcps/davinci-resolve-mcp/src/utils/analysis_memory.py +0 -755
  198. package/mcps/davinci-resolve-mcp/src/utils/analysis_runs.py +0 -307
  199. package/mcps/davinci-resolve-mcp/src/utils/analysis_store.py +0 -945
  200. package/mcps/davinci-resolve-mcp/src/utils/api_truth.py +0 -525
  201. package/mcps/davinci-resolve-mcp/src/utils/app_control.py +0 -319
  202. package/mcps/davinci-resolve-mcp/src/utils/audio_fairlight_live_probe.py +0 -263
  203. package/mcps/davinci-resolve-mcp/src/utils/brain_edits.py +0 -387
  204. package/mcps/davinci-resolve-mcp/src/utils/bridge_metrics.py +0 -76
  205. package/mcps/davinci-resolve-mcp/src/utils/cdl.py +0 -20
  206. package/mcps/davinci-resolve-mcp/src/utils/clip_query.py +0 -85
  207. package/mcps/davinci-resolve-mcp/src/utils/cloud_operations.py +0 -192
  208. package/mcps/davinci-resolve-mcp/src/utils/color_grade_live_probe.py +0 -444
  209. package/mcps/davinci-resolve-mcp/src/utils/contracts.py +0 -102
  210. package/mcps/davinci-resolve-mcp/src/utils/cut_ir.py +0 -115
  211. package/mcps/davinci-resolve-mcp/src/utils/dctl_templates.py +0 -368
  212. package/mcps/davinci-resolve-mcp/src/utils/deep_vision.py +0 -643
  213. package/mcps/davinci-resolve-mcp/src/utils/destructive_hook.py +0 -606
  214. package/mcps/davinci-resolve-mcp/src/utils/edit_engine.py +0 -737
  215. package/mcps/davinci-resolve-mcp/src/utils/embeddings.py +0 -922
  216. package/mcps/davinci-resolve-mcp/src/utils/entities.py +0 -579
  217. package/mcps/davinci-resolve-mcp/src/utils/extension_authoring_live_probe.py +0 -292
  218. package/mcps/davinci-resolve-mcp/src/utils/failure_tracker.py +0 -119
  219. package/mcps/davinci-resolve-mcp/src/utils/fuse_templates.py +0 -1968
  220. package/mcps/davinci-resolve-mcp/src/utils/fusion_composition_live_probe.py +0 -284
  221. package/mcps/davinci-resolve-mcp/src/utils/fusion_group_settings.py +0 -334
  222. package/mcps/davinci-resolve-mcp/src/utils/layout_presets.py +0 -333
  223. package/mcps/davinci-resolve-mcp/src/utils/mcp_stdio.py +0 -32
  224. package/mcps/davinci-resolve-mcp/src/utils/mcp_transport.py +0 -128
  225. package/mcps/davinci-resolve-mcp/src/utils/media_analysis.py +0 -7690
  226. package/mcps/davinci-resolve-mcp/src/utils/media_analysis_jobs.py +0 -805
  227. package/mcps/davinci-resolve-mcp/src/utils/media_pool_changes.py +0 -121
  228. package/mcps/davinci-resolve-mcp/src/utils/media_pool_ingest_live_probe.py +0 -592
  229. package/mcps/davinci-resolve-mcp/src/utils/multicam.py +0 -393
  230. package/mcps/davinci-resolve-mcp/src/utils/object_inspection.py +0 -301
  231. package/mcps/davinci-resolve-mcp/src/utils/page_lock.py +0 -75
  232. package/mcps/davinci-resolve-mcp/src/utils/platform.py +0 -157
  233. package/mcps/davinci-resolve-mcp/src/utils/proc.py +0 -26
  234. package/mcps/davinci-resolve-mcp/src/utils/project_cleanup.py +0 -73
  235. package/mcps/davinci-resolve-mcp/src/utils/project_lifecycle_live_probe.py +0 -376
  236. package/mcps/davinci-resolve-mcp/src/utils/project_lint.py +0 -131
  237. package/mcps/davinci-resolve-mcp/src/utils/project_properties.py +0 -601
  238. package/mcps/davinci-resolve-mcp/src/utils/project_spec.py +0 -459
  239. package/mcps/davinci-resolve-mcp/src/utils/readback.py +0 -91
  240. package/mcps/davinci-resolve-mcp/src/utils/render_deliver_live_probe.py +0 -384
  241. package/mcps/davinci-resolve-mcp/src/utils/resolve_ai_governance.py +0 -214
  242. package/mcps/davinci-resolve-mcp/src/utils/resolve_ai_ledger.py +0 -244
  243. package/mcps/davinci-resolve-mcp/src/utils/resolve_busy.py +0 -171
  244. package/mcps/davinci-resolve-mcp/src/utils/resolve_connection.py +0 -77
  245. package/mcps/davinci-resolve-mcp/src/utils/review_annotation_live_probe.py +0 -352
  246. package/mcps/davinci-resolve-mcp/src/utils/script_templates.py +0 -1193
  247. package/mcps/davinci-resolve-mcp/src/utils/shot_relationships.py +0 -544
  248. package/mcps/davinci-resolve-mcp/src/utils/structural_diff.py +0 -175
  249. package/mcps/davinci-resolve-mcp/src/utils/sync_detection.py +0 -889
  250. package/mcps/davinci-resolve-mcp/src/utils/timeline_brain_db.py +0 -826
  251. package/mcps/davinci-resolve-mcp/src/utils/timeline_conform_live_probe.py +0 -280
  252. package/mcps/davinci-resolve-mcp/src/utils/timeline_kernel_live_probe.py +0 -1092
  253. package/mcps/davinci-resolve-mcp/src/utils/timeline_kernel_probe.py +0 -185
  254. package/mcps/davinci-resolve-mcp/src/utils/timeline_title_text.py +0 -87
  255. package/mcps/davinci-resolve-mcp/src/utils/timeline_versioning.py +0 -730
  256. package/mcps/davinci-resolve-mcp/src/utils/update_check.py +0 -685
  257. package/mcps/vectorworks-mcp/.dockerignore +0 -8
  258. package/mcps/vectorworks-mcp/Dockerfile +0 -28
  259. package/mcps/vectorworks-mcp/README.md +0 -156
  260. package/mcps/vectorworks-mcp/app/__init__.py +0 -2
  261. package/mcps/vectorworks-mcp/app/api.py +0 -52
  262. package/mcps/vectorworks-mcp/app/chunking.py +0 -56
  263. package/mcps/vectorworks-mcp/app/config.py +0 -29
  264. package/mcps/vectorworks-mcp/app/embeddings.py +0 -28
  265. package/mcps/vectorworks-mcp/app/indexer.py +0 -106
  266. package/mcps/vectorworks-mcp/app/main.py +0 -33
  267. package/mcps/vectorworks-mcp/app/mcp_server.py +0 -71
  268. package/mcps/vectorworks-mcp/app/search.py +0 -54
  269. package/mcps/vectorworks-mcp/app/utils.py +0 -14
  270. package/mcps/vectorworks-mcp/data/.gitkeep +0 -1
  271. package/mcps/vectorworks-mcp/data/vw-app-help/VW2022_PluginScript.htm +0 -95
  272. package/mcps/vectorworks-mcp/data/vw-app-help/VW2022_ScriptEditor.htm +0 -266
  273. package/mcps/vectorworks-mcp/data/vw-app-help/VW2022_Scripting.htm +0 -106
  274. package/mcps/vectorworks-mcp/data/vw-app-help/VW2023_CreatingScriptedPlugins.htm +0 -327
  275. package/mcps/vectorworks-mcp/data/vw-app-help/VW2024_RunningScripts.htm +0 -116
  276. package/mcps/vectorworks-mcp/data/vw-devwiki/VS_Function_Reference_category.html +0 -2129
  277. package/mcps/vectorworks-mcp/data/vw-jp-ref/ObjectsSymbols.html +0 -2035
  278. package/mcps/vectorworks-mcp/data/vw-jp-ref/ScriptFunctionReference.html +0 -29
  279. package/mcps/vectorworks-mcp/design.md +0 -57
  280. package/mcps/vectorworks-mcp/docker-compose.yml +0 -18
  281. package/mcps/vectorworks-mcp/index/.gitkeep +0 -1
  282. package/mcps/vectorworks-mcp/index/meta.jsonl +0 -3956
  283. package/mcps/vectorworks-mcp/index/vw.faiss +0 -0
  284. package/mcps/vectorworks-mcp/requirements.txt +0 -10
  285. package/mcps/vectorworks-mcp/scripts/fetch_docs_minimal.sh +0 -84
  286. package/mcps/vectorworks-mcp/scripts/fetch_github_vectorworks.sh +0 -85
  287. package/mcps/vectorworks-mcp/templates/index.html +0 -40
  288. package/scripts/ecosystem-health-audit.js +0 -257
  289. package/skills/bdb-dev-os-skill/SKILL.md +0 -49
  290. package/skills/bdb-dev-os-skill/package.json +0 -11
  291. package/skills/bdbsaastraining/SKILL.md +0 -316
  292. package/skills/bdbsaastraining/references/exam-pool.md +0 -313
  293. package/skills/bdbsaastraining/references/interaction-modes.md +0 -94
  294. package/skills/bdbsaastraining/references/track-a-ai-agent.md +0 -111
  295. package/skills/bdbsaastraining/references/track-b-wordpress.md +0 -111
  296. package/skills/bdbsaastraining/references/track-c-mailserver.md +0 -107
  297. package/skills/bdbsaastraining/references/track-d-debian.md +0 -109
  298. package/skills/bdbsaastraining/references/track-e-custom.md +0 -83
  299. package/skills/bdbsaastraining/scripts/build_profile.py +0 -529
  300. package/skills/bdbsaastraining/scripts/generate_certificate.py +0 -161
  301. package/skills/bdbsaastraining/scripts/preflight_check.sh +0 -150
  302. package/skills/bdbsaastraining/templates/certificate_template.html +0 -508
  303. package/skills/global_config/bdb-ecosystem-health/SKILL.md +0 -68
  304. package/skills/global_config/bdbsaas-ops/SKILL.md +0 -16
  305. 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)]