@hybridlabor-api/aos 4.4.0 → 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 (306) 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/hooks/memb-inject.mjs +6 -0
  20. package/CLAUDE.md +573 -0
  21. package/README.de.md +0 -8
  22. package/README.md +0 -8
  23. package/README.pt.md +0 -8
  24. package/assets/brand/ao-ant-mark.svg +91 -0
  25. package/assets/brand/bdb-core-mark.svg +34 -0
  26. package/assets/brand/heimdall-guardian-mark.svg +16 -0
  27. package/assets/brand/memb-crystal-mark.svg +18 -0
  28. package/bin/aos-config.mjs +89 -0
  29. package/bin/aos-dashboard.mjs +608 -0
  30. package/bin/aos-uninstall.mjs +227 -0
  31. package/docs/sessions/audit-2026-09-14-silent-failures.md +114 -0
  32. package/docs/skills_table.md +0 -3
  33. package/installer.js +428 -237
  34. package/mcps/RhinoMCP/cc-plugin/.claude/settings.json +10 -0
  35. package/mcps/after-effects-mcp/build/index.js +840 -0
  36. package/mcps/after-effects-mcp/build/scripts/applyEffect.jsx +153 -0
  37. package/mcps/after-effects-mcp/build/scripts/applyEffectTemplate.jsx +218 -0
  38. package/mcps/after-effects-mcp/build/scripts/createComposition.jsx +71 -0
  39. package/mcps/after-effects-mcp/build/scripts/createShapeLayer.jsx +147 -0
  40. package/mcps/after-effects-mcp/build/scripts/createSolidLayer.jsx +114 -0
  41. package/mcps/after-effects-mcp/build/scripts/createTextLayer.jsx +115 -0
  42. package/mcps/after-effects-mcp/build/scripts/getLayerInfo.jsx +192 -0
  43. package/mcps/after-effects-mcp/build/scripts/getProjectInfo.jsx +90 -0
  44. package/mcps/after-effects-mcp/build/scripts/listCompositions.jsx +50 -0
  45. package/mcps/after-effects-mcp/build/scripts/mcp-bridge-auto.jsx +1773 -0
  46. package/mcps/after-effects-mcp/build/scripts/setLayerProperties.jsx +160 -0
  47. package/mcps/computer-use-mcp/dist/client.d.ts +150 -0
  48. package/mcps/computer-use-mcp/dist/client.js +136 -0
  49. package/mcps/computer-use-mcp/dist/entrypoint.d.ts +16 -0
  50. package/mcps/computer-use-mcp/dist/entrypoint.js +26 -0
  51. package/mcps/computer-use-mcp/dist/native.d.ts +212 -0
  52. package/mcps/computer-use-mcp/dist/native.js +50 -0
  53. package/mcps/computer-use-mcp/dist/server.d.ts +32 -0
  54. package/mcps/computer-use-mcp/dist/server.js +342 -0
  55. package/mcps/computer-use-mcp/dist/session.d.ts +101 -0
  56. package/mcps/computer-use-mcp/dist/session.js +2372 -0
  57. package/package.json +13 -4
  58. package/skills/global_config/aos-project-init/SKILL.md +97 -130
  59. package/skills/global_config/aos-project-init/assets/openwikiignore.template +62 -0
  60. package/skills/global_config/aos-project-init/scripts/aos-project-doctor.mjs +38 -1
  61. package/skills/global_config/aos-setup/SKILL.md +110 -11
  62. package/skills/global_config/aos-setup/scripts/aos-doctor.mjs +48 -4
  63. package/skills/global_config/ask-tim/SKILL.md +2 -7
  64. package/skills/global_config/openwiki-skill/scripts/openwiki_daemon.py +65 -12
  65. package/bin/setup-saas.mjs +0 -792
  66. package/mcps/blender-mcp-server/.dockerignore +0 -19
  67. package/mcps/blender-mcp-server/.github/workflows/ci.yml +0 -67
  68. package/mcps/blender-mcp-server/.github/workflows/publish-pypi.yml +0 -54
  69. package/mcps/blender-mcp-server/CHANGELOG.md +0 -64
  70. package/mcps/blender-mcp-server/CONTRIBUTING.md +0 -107
  71. package/mcps/blender-mcp-server/Dockerfile +0 -19
  72. package/mcps/blender-mcp-server/LICENSE +0 -21
  73. package/mcps/blender-mcp-server/README.md +0 -409
  74. package/mcps/blender-mcp-server/addon/__init__.py +0 -1440
  75. package/mcps/blender-mcp-server/addon/models.py +0 -223
  76. package/mcps/blender-mcp-server/pyproject.toml +0 -120
  77. package/mcps/blender-mcp-server/scripts/blender_bridge_request.py +0 -71
  78. package/mcps/blender-mcp-server/scripts/blender_create_test_cube.py +0 -49
  79. package/mcps/blender-mcp-server/scripts/blender_scene_info.py +0 -38
  80. package/mcps/blender-mcp-server/scripts/build_addon_zip.sh +0 -29
  81. package/mcps/blender-mcp-server/scripts/demos/README.md +0 -186
  82. package/mcps/blender-mcp-server/scripts/demos/dam_break_scene.py +0 -276
  83. package/mcps/blender-mcp-server/scripts/demos/open_dam_break_gui.py +0 -33
  84. package/mcps/blender-mcp-server/scripts/demos/pipe_movement_study.py +0 -518
  85. package/mcps/blender-mcp-server/scripts/demos/pipe_pressure_failure_scene.py +0 -806
  86. package/mcps/blender-mcp-server/scripts/demos/procedural_dam_break_scene.py +0 -600
  87. package/mcps/blender-mcp-server/scripts/demos/run_dam_break.py +0 -510
  88. package/mcps/blender-mcp-server/scripts/launch_blender_gui.py +0 -41
  89. package/mcps/blender-mcp-server/scripts/library/README.md +0 -64
  90. package/mcps/blender-mcp-server/scripts/library/apply_transforms.py +0 -46
  91. package/mcps/blender-mcp-server/scripts/library/camera.py +0 -51
  92. package/mcps/blender-mcp-server/scripts/library/collections.py +0 -65
  93. package/mcps/blender-mcp-server/scripts/library/create_mesh.py +0 -41
  94. package/mcps/blender-mcp-server/scripts/library/effector.py +0 -48
  95. package/mcps/blender-mcp-server/scripts/library/fluid_domain.py +0 -80
  96. package/mcps/blender-mcp-server/scripts/library/fluid_inflow.py +0 -98
  97. package/mcps/blender-mcp-server/scripts/library/frame_range.py +0 -35
  98. package/mcps/blender-mcp-server/scripts/library/keyframes.py +0 -59
  99. package/mcps/blender-mcp-server/scripts/library/rigid_body.py +0 -52
  100. package/mcps/blender-mcp-server/scripts/library/save_blend.py +0 -40
  101. package/mcps/blender-mcp-server/scripts/server_start.sh +0 -26
  102. package/mcps/blender-mcp-server/src/blender_mcp_server/__init__.py +0 -3
  103. package/mcps/blender-mcp-server/src/blender_mcp_server/headless.py +0 -330
  104. package/mcps/blender-mcp-server/src/blender_mcp_server/server.py +0 -606
  105. package/mcps/davinci-mcp-professional/.bandit +0 -35
  106. package/mcps/davinci-mcp-professional/.claude/commands/security-review.md +0 -191
  107. package/mcps/davinci-mcp-professional/.claude/settings.json +0 -1
  108. package/mcps/davinci-mcp-professional/.editorconfig +0 -27
  109. package/mcps/davinci-mcp-professional/.gitattributes +0 -207
  110. package/mcps/davinci-mcp-professional/.github/CODEOWNERS +0 -2
  111. package/mcps/davinci-mcp-professional/.github/ISSUE_TEMPLATE/bug_report.md +0 -33
  112. package/mcps/davinci-mcp-professional/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
  113. package/mcps/davinci-mcp-professional/.github/PULL_REQUEST_TEMPLATE.md +0 -20
  114. package/mcps/davinci-mcp-professional/.github/SECURITY.md +0 -97
  115. package/mcps/davinci-mcp-professional/.github/dependabot.yml +0 -27
  116. package/mcps/davinci-mcp-professional/.github/workflows/ci.yml +0 -39
  117. package/mcps/davinci-mcp-professional/ATTRIBUTION.md +0 -4
  118. package/mcps/davinci-mcp-professional/AUTHORS.md +0 -9
  119. package/mcps/davinci-mcp-professional/BUGS.md +0 -55
  120. package/mcps/davinci-mcp-professional/CLAUDE.md +0 -229
  121. package/mcps/davinci-mcp-professional/CONTRIBUTING.md +0 -51
  122. package/mcps/davinci-mcp-professional/COPYING.md +0 -228
  123. package/mcps/davinci-mcp-professional/Doxyfile +0 -379
  124. package/mcps/davinci-mcp-professional/README.md +0 -143
  125. package/mcps/davinci-mcp-professional/SECURITY.md +0 -40
  126. package/mcps/davinci-mcp-professional/USING.md +0 -301
  127. package/mcps/davinci-mcp-professional/VERSION.md +0 -13
  128. package/mcps/davinci-mcp-professional/build.py +0 -169
  129. package/mcps/davinci-mcp-professional/claude_desktop_config_template.json +0 -11
  130. package/mcps/davinci-mcp-professional/hooks/ci-check.sh +0 -29
  131. package/mcps/davinci-mcp-professional/hooks/pre-push +0 -46
  132. package/mcps/davinci-mcp-professional/main.py +0 -40
  133. package/mcps/davinci-mcp-professional/mcp_server.py +0 -30
  134. package/mcps/davinci-mcp-professional/pyproject.toml +0 -115
  135. package/mcps/davinci-mcp-professional/security_audit.py +0 -379
  136. package/mcps/davinci-mcp-professional/src/davinci_mcp/__init__.py +0 -19
  137. package/mcps/davinci-mcp-professional/src/davinci_mcp/cli.py +0 -138
  138. package/mcps/davinci-mcp-professional/src/davinci_mcp/resolve_client.py +0 -342
  139. package/mcps/davinci-mcp-professional/src/davinci_mcp/resources/__init__.py +0 -60
  140. package/mcps/davinci-mcp-professional/src/davinci_mcp/server.py +0 -185
  141. package/mcps/davinci-mcp-professional/src/davinci_mcp/tools/__init__.py +0 -146
  142. package/mcps/davinci-mcp-professional/src/davinci_mcp/types.py +0 -125
  143. package/mcps/davinci-mcp-professional/src/davinci_mcp/utils/__init__.py +0 -19
  144. package/mcps/davinci-mcp-professional/src/davinci_mcp/utils/platform.py +0 -239
  145. package/mcps/davinci-mcp-professional/test.py +0 -186
  146. package/mcps/davinci-mcp-professional/uv.lock +0 -2073
  147. package/mcps/davinci-resolve-mcp/.claude/skills/media-analysis.md +0 -44
  148. package/mcps/davinci-resolve-mcp/.clinerules +0 -17
  149. package/mcps/davinci-resolve-mcp/.cursorrules +0 -17
  150. package/mcps/davinci-resolve-mcp/.github/copilot-instructions.md +0 -17
  151. package/mcps/davinci-resolve-mcp/.github/workflows/npm-publish.yml +0 -109
  152. package/mcps/davinci-resolve-mcp/.windsurfrules +0 -17
  153. package/mcps/davinci-resolve-mcp/AGENTS.md +0 -114
  154. package/mcps/davinci-resolve-mcp/CHANGELOG.md +0 -2648
  155. package/mcps/davinci-resolve-mcp/CLAUDE.md +0 -15
  156. package/mcps/davinci-resolve-mcp/LICENSE +0 -21
  157. package/mcps/davinci-resolve-mcp/README.md +0 -173
  158. package/mcps/davinci-resolve-mcp/SECURITY.md +0 -53
  159. package/mcps/davinci-resolve-mcp/bin/davinci-resolve-mcp.mjs +0 -450
  160. package/mcps/davinci-resolve-mcp/examples/README.md +0 -53
  161. package/mcps/davinci-resolve-mcp/examples/markers/README.md +0 -81
  162. package/mcps/davinci-resolve-mcp/examples/media/README.md +0 -94
  163. package/mcps/davinci-resolve-mcp/examples/timeline/README.md +0 -98
  164. package/mcps/davinci-resolve-mcp/install.py +0 -1687
  165. package/mcps/davinci-resolve-mcp/package.json +0 -52
  166. package/mcps/davinci-resolve-mcp/release-notes/v2.22.0.md +0 -51
  167. package/mcps/davinci-resolve-mcp/release-notes/v2.23.0.md +0 -51
  168. package/mcps/davinci-resolve-mcp/release-notes/v2.23.1.md +0 -35
  169. package/mcps/davinci-resolve-mcp/release-notes/v2.24.1.md +0 -32
  170. package/mcps/davinci-resolve-mcp/scripts/audit_api_parity.py +0 -275
  171. package/mcps/davinci-resolve-mcp/scripts/audit_readwrite_symmetry.py +0 -80
  172. package/mcps/davinci-resolve-mcp/scripts/doctor.py +0 -227
  173. package/mcps/davinci-resolve-mcp/scripts/gen_api_limitations.py +0 -144
  174. package/mcps/davinci-resolve-mcp/scripts/live_media_analysis_polish_probe.py +0 -65
  175. package/mcps/davinci-resolve-mcp/scripts/measure_bridge_cost.py +0 -65
  176. package/mcps/davinci-resolve-mcp/scripts/regen_panel_screenshots.py +0 -76
  177. package/mcps/davinci-resolve-mcp/src/__init__.py +0 -3
  178. package/mcps/davinci-resolve-mcp/src/analysis_dashboard.py +0 -15505
  179. package/mcps/davinci-resolve-mcp/src/batch_cli.py +0 -540
  180. package/mcps/davinci-resolve-mcp/src/control_panel.py +0 -13
  181. package/mcps/davinci-resolve-mcp/src/granular/__init__.py +0 -17
  182. package/mcps/davinci-resolve-mcp/src/granular/common.py +0 -741
  183. package/mcps/davinci-resolve-mcp/src/granular/folder.py +0 -420
  184. package/mcps/davinci-resolve-mcp/src/granular/gallery.py +0 -306
  185. package/mcps/davinci-resolve-mcp/src/granular/graph.py +0 -309
  186. package/mcps/davinci-resolve-mcp/src/granular/media_pool.py +0 -679
  187. package/mcps/davinci-resolve-mcp/src/granular/media_pool_item.py +0 -1020
  188. package/mcps/davinci-resolve-mcp/src/granular/media_storage.py +0 -179
  189. package/mcps/davinci-resolve-mcp/src/granular/project.py +0 -1654
  190. package/mcps/davinci-resolve-mcp/src/granular/resolve_control.py +0 -538
  191. package/mcps/davinci-resolve-mcp/src/granular/timeline.py +0 -1075
  192. package/mcps/davinci-resolve-mcp/src/granular/timeline_item.py +0 -2251
  193. package/mcps/davinci-resolve-mcp/src/resolve_mcp_server.py +0 -43
  194. package/mcps/davinci-resolve-mcp/src/server.py +0 -24116
  195. package/mcps/davinci-resolve-mcp/src/utils/__init__.py +0 -3
  196. package/mcps/davinci-resolve-mcp/src/utils/actor_identity.py +0 -46
  197. package/mcps/davinci-resolve-mcp/src/utils/analysis_caps.py +0 -678
  198. package/mcps/davinci-resolve-mcp/src/utils/analysis_memory.py +0 -755
  199. package/mcps/davinci-resolve-mcp/src/utils/analysis_runs.py +0 -307
  200. package/mcps/davinci-resolve-mcp/src/utils/analysis_store.py +0 -945
  201. package/mcps/davinci-resolve-mcp/src/utils/api_truth.py +0 -525
  202. package/mcps/davinci-resolve-mcp/src/utils/app_control.py +0 -319
  203. package/mcps/davinci-resolve-mcp/src/utils/audio_fairlight_live_probe.py +0 -263
  204. package/mcps/davinci-resolve-mcp/src/utils/brain_edits.py +0 -387
  205. package/mcps/davinci-resolve-mcp/src/utils/bridge_metrics.py +0 -76
  206. package/mcps/davinci-resolve-mcp/src/utils/cdl.py +0 -20
  207. package/mcps/davinci-resolve-mcp/src/utils/clip_query.py +0 -85
  208. package/mcps/davinci-resolve-mcp/src/utils/cloud_operations.py +0 -192
  209. package/mcps/davinci-resolve-mcp/src/utils/color_grade_live_probe.py +0 -444
  210. package/mcps/davinci-resolve-mcp/src/utils/contracts.py +0 -102
  211. package/mcps/davinci-resolve-mcp/src/utils/cut_ir.py +0 -115
  212. package/mcps/davinci-resolve-mcp/src/utils/dctl_templates.py +0 -368
  213. package/mcps/davinci-resolve-mcp/src/utils/deep_vision.py +0 -643
  214. package/mcps/davinci-resolve-mcp/src/utils/destructive_hook.py +0 -606
  215. package/mcps/davinci-resolve-mcp/src/utils/edit_engine.py +0 -737
  216. package/mcps/davinci-resolve-mcp/src/utils/embeddings.py +0 -922
  217. package/mcps/davinci-resolve-mcp/src/utils/entities.py +0 -579
  218. package/mcps/davinci-resolve-mcp/src/utils/extension_authoring_live_probe.py +0 -292
  219. package/mcps/davinci-resolve-mcp/src/utils/failure_tracker.py +0 -119
  220. package/mcps/davinci-resolve-mcp/src/utils/fuse_templates.py +0 -1968
  221. package/mcps/davinci-resolve-mcp/src/utils/fusion_composition_live_probe.py +0 -284
  222. package/mcps/davinci-resolve-mcp/src/utils/fusion_group_settings.py +0 -334
  223. package/mcps/davinci-resolve-mcp/src/utils/layout_presets.py +0 -333
  224. package/mcps/davinci-resolve-mcp/src/utils/mcp_stdio.py +0 -32
  225. package/mcps/davinci-resolve-mcp/src/utils/mcp_transport.py +0 -128
  226. package/mcps/davinci-resolve-mcp/src/utils/media_analysis.py +0 -7690
  227. package/mcps/davinci-resolve-mcp/src/utils/media_analysis_jobs.py +0 -805
  228. package/mcps/davinci-resolve-mcp/src/utils/media_pool_changes.py +0 -121
  229. package/mcps/davinci-resolve-mcp/src/utils/media_pool_ingest_live_probe.py +0 -592
  230. package/mcps/davinci-resolve-mcp/src/utils/multicam.py +0 -393
  231. package/mcps/davinci-resolve-mcp/src/utils/object_inspection.py +0 -301
  232. package/mcps/davinci-resolve-mcp/src/utils/page_lock.py +0 -75
  233. package/mcps/davinci-resolve-mcp/src/utils/platform.py +0 -157
  234. package/mcps/davinci-resolve-mcp/src/utils/proc.py +0 -26
  235. package/mcps/davinci-resolve-mcp/src/utils/project_cleanup.py +0 -73
  236. package/mcps/davinci-resolve-mcp/src/utils/project_lifecycle_live_probe.py +0 -376
  237. package/mcps/davinci-resolve-mcp/src/utils/project_lint.py +0 -131
  238. package/mcps/davinci-resolve-mcp/src/utils/project_properties.py +0 -601
  239. package/mcps/davinci-resolve-mcp/src/utils/project_spec.py +0 -459
  240. package/mcps/davinci-resolve-mcp/src/utils/readback.py +0 -91
  241. package/mcps/davinci-resolve-mcp/src/utils/render_deliver_live_probe.py +0 -384
  242. package/mcps/davinci-resolve-mcp/src/utils/resolve_ai_governance.py +0 -214
  243. package/mcps/davinci-resolve-mcp/src/utils/resolve_ai_ledger.py +0 -244
  244. package/mcps/davinci-resolve-mcp/src/utils/resolve_busy.py +0 -171
  245. package/mcps/davinci-resolve-mcp/src/utils/resolve_connection.py +0 -77
  246. package/mcps/davinci-resolve-mcp/src/utils/review_annotation_live_probe.py +0 -352
  247. package/mcps/davinci-resolve-mcp/src/utils/script_templates.py +0 -1193
  248. package/mcps/davinci-resolve-mcp/src/utils/shot_relationships.py +0 -544
  249. package/mcps/davinci-resolve-mcp/src/utils/structural_diff.py +0 -175
  250. package/mcps/davinci-resolve-mcp/src/utils/sync_detection.py +0 -889
  251. package/mcps/davinci-resolve-mcp/src/utils/timeline_brain_db.py +0 -826
  252. package/mcps/davinci-resolve-mcp/src/utils/timeline_conform_live_probe.py +0 -280
  253. package/mcps/davinci-resolve-mcp/src/utils/timeline_kernel_live_probe.py +0 -1092
  254. package/mcps/davinci-resolve-mcp/src/utils/timeline_kernel_probe.py +0 -185
  255. package/mcps/davinci-resolve-mcp/src/utils/timeline_title_text.py +0 -87
  256. package/mcps/davinci-resolve-mcp/src/utils/timeline_versioning.py +0 -730
  257. package/mcps/davinci-resolve-mcp/src/utils/update_check.py +0 -685
  258. package/mcps/vectorworks-mcp/.dockerignore +0 -8
  259. package/mcps/vectorworks-mcp/Dockerfile +0 -28
  260. package/mcps/vectorworks-mcp/README.md +0 -156
  261. package/mcps/vectorworks-mcp/app/__init__.py +0 -2
  262. package/mcps/vectorworks-mcp/app/api.py +0 -52
  263. package/mcps/vectorworks-mcp/app/chunking.py +0 -56
  264. package/mcps/vectorworks-mcp/app/config.py +0 -29
  265. package/mcps/vectorworks-mcp/app/embeddings.py +0 -28
  266. package/mcps/vectorworks-mcp/app/indexer.py +0 -106
  267. package/mcps/vectorworks-mcp/app/main.py +0 -33
  268. package/mcps/vectorworks-mcp/app/mcp_server.py +0 -71
  269. package/mcps/vectorworks-mcp/app/search.py +0 -54
  270. package/mcps/vectorworks-mcp/app/utils.py +0 -14
  271. package/mcps/vectorworks-mcp/data/.gitkeep +0 -1
  272. package/mcps/vectorworks-mcp/data/vw-app-help/VW2022_PluginScript.htm +0 -95
  273. package/mcps/vectorworks-mcp/data/vw-app-help/VW2022_ScriptEditor.htm +0 -266
  274. package/mcps/vectorworks-mcp/data/vw-app-help/VW2022_Scripting.htm +0 -106
  275. package/mcps/vectorworks-mcp/data/vw-app-help/VW2023_CreatingScriptedPlugins.htm +0 -327
  276. package/mcps/vectorworks-mcp/data/vw-app-help/VW2024_RunningScripts.htm +0 -116
  277. package/mcps/vectorworks-mcp/data/vw-devwiki/VS_Function_Reference_category.html +0 -2129
  278. package/mcps/vectorworks-mcp/data/vw-jp-ref/ObjectsSymbols.html +0 -2035
  279. package/mcps/vectorworks-mcp/data/vw-jp-ref/ScriptFunctionReference.html +0 -29
  280. package/mcps/vectorworks-mcp/design.md +0 -57
  281. package/mcps/vectorworks-mcp/docker-compose.yml +0 -18
  282. package/mcps/vectorworks-mcp/index/.gitkeep +0 -1
  283. package/mcps/vectorworks-mcp/index/meta.jsonl +0 -3956
  284. package/mcps/vectorworks-mcp/index/vw.faiss +0 -0
  285. package/mcps/vectorworks-mcp/requirements.txt +0 -10
  286. package/mcps/vectorworks-mcp/scripts/fetch_docs_minimal.sh +0 -84
  287. package/mcps/vectorworks-mcp/scripts/fetch_github_vectorworks.sh +0 -85
  288. package/mcps/vectorworks-mcp/templates/index.html +0 -40
  289. package/scripts/ecosystem-health-audit.js +0 -257
  290. package/skills/bdb-dev-os-skill/SKILL.md +0 -49
  291. package/skills/bdb-dev-os-skill/package.json +0 -11
  292. package/skills/bdbsaastraining/SKILL.md +0 -316
  293. package/skills/bdbsaastraining/references/exam-pool.md +0 -313
  294. package/skills/bdbsaastraining/references/interaction-modes.md +0 -94
  295. package/skills/bdbsaastraining/references/track-a-ai-agent.md +0 -111
  296. package/skills/bdbsaastraining/references/track-b-wordpress.md +0 -111
  297. package/skills/bdbsaastraining/references/track-c-mailserver.md +0 -107
  298. package/skills/bdbsaastraining/references/track-d-debian.md +0 -109
  299. package/skills/bdbsaastraining/references/track-e-custom.md +0 -83
  300. package/skills/bdbsaastraining/scripts/build_profile.py +0 -529
  301. package/skills/bdbsaastraining/scripts/generate_certificate.py +0 -161
  302. package/skills/bdbsaastraining/scripts/preflight_check.sh +0 -150
  303. package/skills/bdbsaastraining/templates/certificate_template.html +0 -508
  304. package/skills/global_config/bdb-ecosystem-health/SKILL.md +0 -68
  305. package/skills/global_config/bdbsaas-ops/SKILL.md +0 -16
  306. package/skills/global_config/bdbsaashost/SKILL.md +0 -125
@@ -1,643 +0,0 @@
1
- """Deep shot-level vision tier (Phase B of the analysis program).
2
-
3
- Fills the per-shot Visual / Content / Production / Editorial / Cuttability
4
- field groups (v2 shot schema spec §3.2–3.8) via the same deferred-payload
5
- host-vision pattern as standard analysis: the server prepares frames + a
6
- schema, the host chat reads the frames and commits JSON back.
7
-
8
- Two entry points share the schema:
9
-
10
- - ``depth="deep"`` on analyze extends the single-pass payload (handled in
11
- media_analysis.build_host_chat_paths_payload via deep_shot_schema()).
12
- - ``deepen`` is the post-hoc per-clip/per-shot pass for already-analyzed
13
- clips. It is estimate-first: the first call returns the frame/token cost
14
- and a confirm_token; the second call (with the token) returns the deferred
15
- payload. Both paths pass the caps pre-call refusal — confirmation never
16
- bypasses budgets.
17
-
18
- Deep results commit via ``commit_shot_vision``: subjective rows are written
19
- with source ``vision_deep_v1`` (human rows still win), the canonical report
20
- blob is updated, and analysis.json re-exports in lockstep.
21
- """
22
-
23
- from __future__ import annotations
24
-
25
- import json
26
- import os
27
- import time
28
- from typing import Any, Dict, List, Optional, Tuple
29
-
30
- from src.utils import analysis_store, timeline_brain_db
31
-
32
- DEEP_SHOT_SCHEMA_REFERENCE = "davinci_resolve_mcp.shot_deep_analysis.v1"
33
- DEEP_SOURCE = "vision_deep_v1"
34
-
35
- # Threshold above which a shot gets one extra extracted frame for the deep pass.
36
- LONG_SHOT_SECONDS = 15.0
37
-
38
- # Per-shot field groups (v2-shot-schema-spec §3.2–3.8). Values are the schema
39
- # template shown to the host chat; enums are listed inline.
40
- DEEP_SHOT_FIELD_GROUPS: Dict[str, Any] = {
41
- "visual": {
42
- "shot_size": "wide|medium_wide|medium|medium_close|close|extreme_close|insert|establishing|other",
43
- "framing": "single|two_shot|group|crowd|empty|insert|establishing|abstract",
44
- "camera_height": "eye_level|high_angle|low_angle|birds_eye|dutch|unknown",
45
- "camera_motion": "locked|pan|tilt|dolly|handheld|crane|drone|zoom|composite|other",
46
- "motion_direction": "left|right|up|down|in|out|clockwise|counter_clockwise|none",
47
- "depth_of_field": "deep|shallow|rack_focus|unknown",
48
- "lens_character": "wide|normal|tele|fisheye|unknown",
49
- "lighting": "natural|high_key|low_key|practical|backlit|silhouette|mixed|unknown",
50
- "color_mood": "warm|cool|neutral|desaturated|saturated|monochrome|unnatural|unknown",
51
- "composition_notes": "<short free text>",
52
- },
53
- "content": {
54
- "primary_subject": {
55
- "type": "person|object|landscape|interior|vehicle|animal|text_graphic|abstract",
56
- "description": "<one phrase>",
57
- "performance": "null unless type==person: {eye_line: to_camera|off_left|off_right|down|up|closed|unknown, energy: low|medium|high, emotional_register: <short free text>}",
58
- },
59
- "secondary_subjects": ["<[{type, description}]>"],
60
- "action": "<one sentence — what is happening>",
61
- "location": "<one sentence — where this is>",
62
- "visible_text": ["<readable on-screen text>"],
63
- "objects_of_note": ["<notable props/elements>"],
64
- "audio_character": "silence|sync_dialogue|vo_dialogue|music|ambient|sfx|mixed|unknown",
65
- },
66
- "production": {
67
- "composite_shot": "<bool — split-screen / PiP / multi-angle composite>",
68
- "composite_panels": "null unless composite_shot: [{region, primary_subject, action}]",
69
- "vfx_present": "none|minor|major|unknown",
70
- },
71
- "editorial": {
72
- "editorial_role": "establishing|coverage|reaction|insert|transition|b_roll|montage_element|titles_or_graphics|bumper|other",
73
- "select_potential": "low|medium|high",
74
- "best_moment": "null if the shot is a sustained flat beat, else {time_seconds, why}",
75
- "best_moment_present": "<bool>",
76
- "pacing": "still|moderate|kinetic|variable",
77
- "stillness_type": "held_tension|quiet|contemplative|transitional|dead_air|unknown — null unless pacing is still/variable",
78
- "pacing_note": "<free text or null — only when pacing is still/variable>",
79
- },
80
- "cuttability": {
81
- "cut_in": {"quality": "poor|ok|clean", "notes": "<short>"},
82
- "cut_out": {"quality": "poor|ok|clean", "notes": "<short>"},
83
- "match_action_in": "<bool — could receive a match cut>",
84
- "match_action_out": "<bool — could send a match cut>",
85
- "cut_compatibility_hints": "<free text>",
86
- },
87
- "description": "<1-3 sentences, editorially useful, colleague-style note>",
88
- "confidence": {
89
- "visual": "low|medium|high",
90
- "content": "low|medium|high",
91
- "audio": "low|medium|high",
92
- "editorial": "low|medium|high",
93
- "cuttability": "low|medium|high",
94
- },
95
- }
96
-
97
- DEEP_SHOT_PROMPT = (
98
- "Deep per-shot editorial analysis. For EACH shot listed in shot_table, look "
99
- "only at the frames whose indices appear in that shot's frame_indices and "
100
- "fill every field group in deep_shot_schema (visual, content, production, "
101
- "editorial, cuttability, description, confidence). Use the enum values "
102
- "verbatim; use 'unknown' or null when the frames do not support a claim — "
103
- "hedge identity/intent/value when frame evidence is thin. Return strict "
104
- "JSON only: {\"shots\": [{\"shot_index\": <int>, ...field groups...}]}."
105
- )
106
-
107
-
108
- def _ma():
109
- from src.utils import media_analysis
110
-
111
- return media_analysis
112
-
113
-
114
- def deep_shot_schema() -> Dict[str, Any]:
115
- """The per-shot deep field groups (shared by deep-depth analyze + deepen)."""
116
- return json.loads(json.dumps(DEEP_SHOT_FIELD_GROUPS))
117
-
118
-
119
- def _now() -> str:
120
- return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
121
-
122
-
123
- def _clip_context(project_root: str, clip_ref: Any) -> Tuple[Optional[Dict[str, Any]], Optional[str]]:
124
- """Resolve a clip to (context, error). Auto-ingests the JSON report when
125
- the DB has no rows yet (pre-v9 analysis roots)."""
126
- ma = _ma()
127
- conn = timeline_brain_db.connect(project_root)
128
- clip_uuid = analysis_store.resolve_clip_uuid(conn, clip_ref)
129
- if not clip_uuid:
130
- # Pre-v9 report? Find the analysis.json by walking clips/ and ingest it.
131
- clips_root = os.path.join(project_root, "clips")
132
- candidate = str(clip_ref or "")
133
- if os.path.isdir(clips_root):
134
- for entry in sorted(os.listdir(clips_root)):
135
- report_path = os.path.join(clips_root, entry, "analysis.json")
136
- if not os.path.isfile(report_path):
137
- continue
138
- try:
139
- with open(report_path, "r", encoding="utf-8") as handle:
140
- report = json.load(handle)
141
- except (OSError, json.JSONDecodeError):
142
- continue
143
- clip_block = report.get("clip") or {}
144
- if candidate not in (
145
- entry,
146
- str(clip_block.get("clip_id") or ""),
147
- str(clip_block.get("media_id") or ""),
148
- ma.normalize_path(clip_block.get("file_path") or ""),
149
- ):
150
- continue
151
- ingest = analysis_store.ingest_report(
152
- project_root, report, clip_dir=os.path.join(clips_root, entry)
153
- )
154
- if ingest.get("success"):
155
- clip_uuid = ingest["clip_uuid"]
156
- break
157
- if not clip_uuid:
158
- return None, f"No analyzed clip found for {clip_ref!r} (run db_ingest if this is an older analysis root)"
159
- clip_row = conn.execute("SELECT * FROM clips WHERE clip_uuid = ?", (clip_uuid,)).fetchone()
160
- shots = conn.execute(
161
- "SELECT * FROM shots WHERE clip_uuid = ? ORDER BY shot_index", (clip_uuid,)
162
- ).fetchall()
163
- frames = conn.execute(
164
- "SELECT * FROM frames WHERE clip_uuid = ? ORDER BY frame_index", (clip_uuid,)
165
- ).fetchall()
166
- if not shots:
167
- return None, "Clip has no detected shots — deep shot analysis needs a standard analysis with cut detection first"
168
- return {
169
- "clip_uuid": clip_uuid,
170
- "clip": dict(clip_row) if clip_row else {},
171
- "shots": [dict(s) for s in shots],
172
- "frames": [dict(f) for f in frames],
173
- }, None
174
-
175
-
176
- def _select_shots(shots: List[Dict[str, Any]], shot_indices: Optional[List[int]]) -> Tuple[List[Dict[str, Any]], List[int]]:
177
- if not shot_indices:
178
- return shots, []
179
- wanted = {int(i) for i in shot_indices}
180
- selected = [s for s in shots if int(s["shot_index"]) in wanted]
181
- missing = sorted(wanted - {int(s["shot_index"]) for s in selected})
182
- return selected, missing
183
-
184
-
185
- def _frames_for_shot(shot: Dict[str, Any], frames: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
186
- rows = [f for f in frames if f.get("shot_uuid") == shot["shot_uuid"]]
187
- if rows:
188
- return rows
189
- start, end = shot.get("time_seconds_start"), shot.get("time_seconds_end")
190
- if start is None or end is None:
191
- return []
192
- return [
193
- f for f in frames
194
- if f.get("time_seconds") is not None and start <= float(f["time_seconds"]) < end
195
- ]
196
-
197
-
198
- def _extract_frame(source: str, time_seconds: float, out_path: str) -> Optional[str]:
199
- """Extract one frame with ffmpeg (read-only on source). None on failure."""
200
- ma = _ma()
201
- os.makedirs(os.path.dirname(out_path), exist_ok=True)
202
- code, _out, _err = ma._run_command([
203
- "ffmpeg", "-y", "-ss", f"{max(0.0, time_seconds):.3f}", "-i", source,
204
- "-frames:v", "1", "-q:v", "3", out_path,
205
- ])
206
- if code != 0 or not os.path.isfile(out_path):
207
- return None
208
- try:
209
- from src.utils import analysis_caps
210
-
211
- caps = ma._resolve_active_caps()
212
- analysis_caps.downscale_frame_if_needed(out_path, caps.max_frame_dim_pixels)
213
- except Exception:
214
- pass
215
- return out_path
216
-
217
-
218
- def _confirm_token_for(clip_uuid: str, shot_uuids: List[str]) -> str:
219
- return _ma().short_hash(f"deepen:{clip_uuid}:{','.join(sorted(shot_uuids))}", 16)
220
-
221
-
222
- def _vision_token_for(clip_uuid: str, shot_uuids: List[str]) -> str:
223
- return _ma().short_hash(f"deep_vision:{clip_uuid}:{','.join(sorted(shot_uuids))}", 16)
224
-
225
-
226
- def deepen_clip(
227
- project_root: str,
228
- *,
229
- clip_ref: Any,
230
- shot_indices: Optional[List[int]] = None,
231
- confirm_token: Optional[str] = None,
232
- job_id: Optional[str] = None,
233
- ) -> Dict[str, Any]:
234
- """Estimate-first deep pass over a clip (or selected shots).
235
-
236
- First call (no confirm_token) returns the cost estimate + confirm_token.
237
- Second call with the token returns the deferred host-vision payload.
238
- """
239
- ma = _ma()
240
- context, err = _clip_context(project_root, clip_ref)
241
- if err:
242
- return {"success": False, "error": err}
243
- clip_uuid = context["clip_uuid"]
244
- clip = context["clip"]
245
- shots, missing = _select_shots(context["shots"], shot_indices)
246
- if missing:
247
- return {"success": False, "error": f"shot_index not found: {missing}", "available": [s['shot_index'] for s in context['shots']]}
248
- if not shots:
249
- return {"success": False, "error": "No shots selected"}
250
-
251
- shot_uuids = [s["shot_uuid"] for s in shots]
252
- expected_confirm = _confirm_token_for(clip_uuid, shot_uuids)
253
-
254
- # Frame plan: existing on-disk frames per shot; shots with none get
255
- # extraction placeholders; long shots get one extra mid-late frame.
256
- source = clip.get("file_path")
257
- frame_plan: List[Dict[str, Any]] = []
258
- total_frames = 0
259
- for shot in shots:
260
- rows = _frames_for_shot(shot, context["frames"])
261
- on_disk = [r for r in rows if r.get("frame_path") and os.path.isfile(str(r["frame_path"]))]
262
- start = float(shot.get("time_seconds_start") or 0.0)
263
- end = float(shot.get("time_seconds_end") or start)
264
- duration = max(0.0, end - start)
265
- to_extract: List[float] = []
266
- if not on_disk:
267
- to_extract.append(start + duration / 2.0)
268
- if duration > LONG_SHOT_SECONDS:
269
- to_extract.append(start + duration * 2.0 / 3.0)
270
- count = len(on_disk) + len(to_extract)
271
- total_frames += count
272
- frame_plan.append({
273
- "shot_index": shot["shot_index"],
274
- "shot_uuid": shot["shot_uuid"],
275
- "existing_frames": len(on_disk),
276
- "frames_to_extract": len(to_extract),
277
- "extract_times": to_extract,
278
- "rows": on_disk,
279
- })
280
-
281
- estimated_tokens = total_frames * ma.AVG_VISION_TOKENS_PER_FRAME
282
- refusal = ma._check_caps_pre_call(
283
- project_root=project_root,
284
- estimated_vision_tokens=estimated_tokens,
285
- clip_id=clip.get("resolve_clip_id") or clip_uuid,
286
- job_id=job_id,
287
- )
288
- if refusal is not None:
289
- return refusal
290
-
291
- estimate = {
292
- "clip_uuid": clip_uuid,
293
- "clip_name": clip.get("clip_name"),
294
- "shot_count": len(shots),
295
- "frame_count": total_frames,
296
- "estimated_vision_tokens": estimated_tokens,
297
- "tokens_per_frame_assumption": ma.AVG_VISION_TOKENS_PER_FRAME,
298
- }
299
-
300
- if confirm_token != expected_confirm:
301
- return {
302
- "success": True,
303
- "status": "confirmation_required",
304
- "estimate": estimate,
305
- "confirm_token": expected_confirm,
306
- "note": (
307
- "Deep shot analysis is opt-in and costs vision tokens. Re-call "
308
- "media_analysis(action='deepen') with this confirm_token to "
309
- "proceed. Confirmation does not bypass caps."
310
- ),
311
- }
312
-
313
- # Confirmed: materialize missing frames, then build the deferred payload.
314
- clip_dir_name = clip.get("clip_dir")
315
- clip_dir = os.path.join(project_root, "clips", clip_dir_name) if clip_dir_name else None
316
- shot_table: List[Dict[str, Any]] = []
317
- frame_metadata: List[Dict[str, Any]] = []
318
- frame_paths: List[str] = []
319
- synthetic_index = 100000 # extraction-only frames get indices above sampled ones
320
- for plan in frame_plan:
321
- indices: List[int] = []
322
- for row in plan["rows"]:
323
- path = ma.normalize_path(str(row["frame_path"]))
324
- if path not in frame_paths:
325
- frame_paths.append(path)
326
- frame_metadata.append({
327
- "frame_index": int(row["frame_index"]),
328
- "frame_path": path,
329
- "time_seconds": row.get("time_seconds"),
330
- "selection_reason": row.get("selection_reason"),
331
- "shot_index": plan["shot_index"],
332
- })
333
- indices.append(int(row["frame_index"]))
334
- for t in plan["extract_times"]:
335
- if not source or not os.path.isfile(source):
336
- continue
337
- if not clip_dir:
338
- continue
339
- out_path = os.path.join(clip_dir, "frames", f"deep_shot{int(plan['shot_index']):03d}_{int(t * 1000):08d}.jpg")
340
- extracted = out_path if os.path.isfile(out_path) else _extract_frame(source, t, out_path)
341
- if not extracted:
342
- continue
343
- synthetic_index += 1
344
- path = ma.normalize_path(extracted)
345
- frame_paths.append(path)
346
- frame_metadata.append({
347
- "frame_index": synthetic_index,
348
- "frame_path": path,
349
- "time_seconds": t,
350
- "selection_reason": "deep_pass_extraction",
351
- "shot_index": plan["shot_index"],
352
- })
353
- indices.append(synthetic_index)
354
- shot = next(s for s in shots if s["shot_index"] == plan["shot_index"])
355
- shot_table.append({
356
- "shot_index": int(shot["shot_index"]),
357
- "shot_uuid": shot["shot_uuid"],
358
- "time_seconds_start": shot.get("time_seconds_start"),
359
- "time_seconds_end": shot.get("time_seconds_end"),
360
- "current_description": shot.get("description"),
361
- "frame_indices": indices,
362
- })
363
-
364
- vision_token = _vision_token_for(clip_uuid, shot_uuids)
365
- commit_params: Dict[str, Any] = {
366
- "vision_token": vision_token,
367
- "shots": "<host chat: [{shot_index, ...deep_shot_schema groups...}]>",
368
- "analysis_root": project_root,
369
- }
370
- if clip.get("resolve_clip_id"):
371
- commit_params["clip_id"] = clip["resolve_clip_id"]
372
- elif clip_dir_name:
373
- commit_params["clip_dir"] = clip_dir_name
374
-
375
- return {
376
- "success": True,
377
- "status": "pending_host_analysis",
378
- "provider": "host_chat_paths",
379
- "mode": "deep_shots",
380
- "vision_token": vision_token,
381
- "estimate": estimate,
382
- "clip": {
383
- "clip_uuid": clip_uuid,
384
- "clip_id": clip.get("resolve_clip_id"),
385
- "clip_name": clip.get("clip_name"),
386
- "file_path": source,
387
- },
388
- "frame_count": len(frame_paths),
389
- "frame_paths": frame_paths,
390
- "frame_metadata": frame_metadata,
391
- "shot_table": shot_table,
392
- "deep_shot_schema": deep_shot_schema(),
393
- "schema_reference": DEEP_SHOT_SCHEMA_REFERENCE,
394
- "prompt": DEEP_SHOT_PROMPT,
395
- "commit_action": {
396
- "tool": "media_analysis",
397
- "action": "commit_shot_vision",
398
- "params": commit_params,
399
- },
400
- "instructions": (
401
- "Read every file under frame_paths as a local image. For each entry in "
402
- "shot_table, produce one object in `shots` with its shot_index and the "
403
- "deep_shot_schema field groups, grounded ONLY in the frames listed in "
404
- "that shot's frame_indices. Then call the tool in commit_action with "
405
- "`shots` set to that array. Skipping the commit leaves the deep pass "
406
- "incomplete — surface that rather than silently stopping."
407
- ),
408
- }
409
-
410
-
411
- _DEEP_GROUP_KEYS = ("visual", "content", "production", "editorial", "cuttability")
412
-
413
-
414
- def commit_shot_vision(
415
- project_root: str,
416
- *,
417
- shots: Any,
418
- vision_token: Optional[str] = None,
419
- clip_ref: Any = None,
420
- author: str = "host_chat",
421
- ) -> Dict[str, Any]:
422
- """Commit a deep per-shot payload: subjective rows (source vision_deep_v1),
423
- canonical blob update, and lockstep analysis.json re-export."""
424
- if isinstance(shots, str):
425
- try:
426
- shots = json.loads(shots)
427
- except json.JSONDecodeError as exc:
428
- return {"success": False, "error": f"shots was a string but not valid JSON: {exc}"}
429
- if isinstance(shots, dict) and isinstance(shots.get("shots"), list):
430
- shots = shots["shots"]
431
- if not isinstance(shots, list) or not shots:
432
- return {"success": False, "error": "commit_shot_vision requires `shots`: a non-empty array of per-shot objects"}
433
-
434
- context, err = _clip_context(project_root, clip_ref)
435
- if err:
436
- return {"success": False, "error": err}
437
- clip_uuid = context["clip_uuid"]
438
- db_shots = {int(s["shot_index"]): s for s in context["shots"]}
439
- by_uuid = {s["shot_uuid"]: s for s in context["shots"]}
440
-
441
- # Token check: recompute over the shots being committed.
442
- target_uuids = []
443
- normalized_entries: List[Tuple[Dict[str, Any], Dict[str, Any]]] = [] # (db_shot, payload_entry)
444
- for entry in shots:
445
- if not isinstance(entry, dict):
446
- continue
447
- db_shot = None
448
- if entry.get("shot_uuid") and entry["shot_uuid"] in by_uuid:
449
- db_shot = by_uuid[entry["shot_uuid"]]
450
- else:
451
- try:
452
- db_shot = db_shots.get(int(entry.get("shot_index")))
453
- except (TypeError, ValueError):
454
- db_shot = None
455
- if db_shot is None:
456
- return {"success": False, "error": f"shot not found for entry: {entry.get('shot_uuid') or entry.get('shot_index')!r}"}
457
- target_uuids.append(db_shot["shot_uuid"])
458
- normalized_entries.append((db_shot, entry))
459
- if not normalized_entries:
460
- return {"success": False, "error": "No valid shot entries in payload"}
461
- expected = _vision_token_for(clip_uuid, target_uuids)
462
- if vision_token and str(vision_token) != expected:
463
- return {
464
- "success": False,
465
- "error": "vision_token mismatch; the clip was re-analyzed or the shot selection changed since the deepen payload was issued.",
466
- "expected_vision_token": expected,
467
- "received_vision_token": vision_token,
468
- }
469
-
470
- ma = _ma()
471
- conn = timeline_brain_db.connect(project_root)
472
- report_row = conn.execute(
473
- "SELECT report_json FROM analysis_reports WHERE clip_uuid = ?", (clip_uuid,)
474
- ).fetchone()
475
- if not report_row:
476
- return {"success": False, "error": "No canonical report for clip — run db_ingest first"}
477
- report = json.loads(report_row["report_json"])
478
- visual = report.get("visual") if isinstance(report.get("visual"), dict) else {}
479
- shot_entries = visual.get("shot_descriptions") if isinstance(visual.get("shot_descriptions"), list) else []
480
- entries_by_index: Dict[int, Dict[str, Any]] = {}
481
- for se in shot_entries:
482
- if isinstance(se, dict) and se.get("shot_index") is not None:
483
- try:
484
- entries_by_index[int(se["shot_index"])] = se
485
- except (TypeError, ValueError):
486
- continue
487
-
488
- updated = 0
489
- for db_shot, entry in normalized_entries:
490
- target = entries_by_index.get(int(db_shot["shot_index"]))
491
- if target is None:
492
- target = {
493
- "shot_index": int(db_shot["shot_index"]),
494
- "time_seconds_start": db_shot.get("time_seconds_start"),
495
- "time_seconds_end": db_shot.get("time_seconds_end"),
496
- "description": db_shot.get("description") or "",
497
- "qc_flags": [],
498
- }
499
- shot_entries.append(target)
500
- entries_by_index[int(db_shot["shot_index"])] = target
501
- for group in _DEEP_GROUP_KEYS:
502
- if isinstance(entry.get(group), dict):
503
- target[group] = entry[group]
504
- if isinstance(entry.get("confidence"), dict):
505
- target["confidence"] = entry["confidence"]
506
- description = entry.get("description")
507
- if isinstance(description, str) and description.strip():
508
- target["description"] = description.strip()
509
- updated += 1
510
-
511
- visual["shot_descriptions"] = shot_entries
512
- report["visual"] = visual
513
- report["deep_shots_committed_at"] = _now()
514
-
515
- # Rows first (canonical), deep source label; human rows preserved inside.
516
- ingest = analysis_store.ingest_report(
517
- project_root,
518
- report,
519
- clip_dir=os.path.join(project_root, "clips", context["clip"].get("clip_dir") or ""),
520
- author=author,
521
- source=DEEP_SOURCE,
522
- )
523
- if not ingest.get("success"):
524
- return {"success": False, "error": f"DB ingest failed: {ingest.get('error')}"}
525
-
526
- # Lockstep JSON export.
527
- export_path = None
528
- clip_dir_name = context["clip"].get("clip_dir")
529
- if clip_dir_name:
530
- candidate = os.path.join(project_root, "clips", clip_dir_name, "analysis.json")
531
- if os.path.isfile(candidate):
532
- export_path = analysis_store.export_report_file(project_root, clip_uuid, candidate)
533
-
534
- # Caps usage: frames that fed this pass (frames table rows for the target
535
- # shots), falling back to one frame per shot when none are recorded.
536
- placeholders = ",".join("?" for _ in target_uuids)
537
- frames_seen = int(conn.execute(
538
- f"SELECT COUNT(*) FROM frames WHERE clip_uuid = ? AND shot_uuid IN ({placeholders})",
539
- (clip_uuid, *target_uuids),
540
- ).fetchone()[0]) or len(normalized_entries)
541
- ma._record_caps_usage(
542
- project_root=project_root,
543
- clip_id=context["clip"].get("resolve_clip_id") or clip_uuid,
544
- vision_tokens=frames_seen * ma.AVG_VISION_TOKENS_PER_FRAME,
545
- frames_uploaded=frames_seen,
546
- )
547
-
548
- return {
549
- "success": True,
550
- "clip_uuid": clip_uuid,
551
- "shots_updated": updated,
552
- "subjective_fields_written": ingest.get("subjective_fields_written"),
553
- "subjective_fields_preserved_human": ingest.get("subjective_fields_preserved_human"),
554
- "source": DEEP_SOURCE,
555
- "analysis_json": export_path,
556
- }
557
-
558
-
559
- def vision_pending_sweep(
560
- project_root: str,
561
- *,
562
- expire: bool = False,
563
- max_age_days: Optional[float] = None,
564
- reoffer: bool = False,
565
- ) -> Dict[str, Any]:
566
- """List clips stuck in pending_host_analysis; optionally expire or re-offer.
567
-
568
- Re-offer returns each clip's stored deferred payload (report["visual"])
569
- after verifying its frame files still exist. Expire stamps the report
570
- ``expired_host_analysis`` so pendings never linger silently.
571
- """
572
- ma = _ma()
573
- root = ma.normalize_path(project_root)
574
- clips_root = os.path.join(root, "clips")
575
- now = time.time()
576
- pending: List[Dict[str, Any]] = []
577
- expired: List[str] = []
578
- reoffers: List[Dict[str, Any]] = []
579
- if os.path.isdir(clips_root):
580
- for entry in sorted(os.listdir(clips_root)):
581
- clip_dir = os.path.join(clips_root, entry)
582
- report_path = os.path.join(clip_dir, "analysis.json")
583
- if not os.path.isfile(report_path):
584
- continue
585
- report = analysis_store.load_db_report(root, clip_dir=entry)
586
- if report is None:
587
- try:
588
- with open(report_path, "r", encoding="utf-8") as handle:
589
- report = json.load(handle)
590
- except (OSError, json.JSONDecodeError):
591
- continue
592
- if report.get("vision_status") != "pending_host_analysis":
593
- continue
594
- analyzed_at = report.get("analyzed_at")
595
- age_days = None
596
- ts = ma._timestamp_from_analyzed_at(analyzed_at)
597
- if ts:
598
- age_days = round((now - ts) / 86400.0, 2)
599
- visual = report.get("visual") if isinstance(report.get("visual"), dict) else {}
600
- frame_paths = visual.get("frame_paths") if isinstance(visual.get("frame_paths"), list) else []
601
- frames_present = sum(1 for p in frame_paths if p and os.path.isfile(str(p)))
602
- row = {
603
- "clip_dir": entry,
604
- "clip_name": (report.get("clip") or {}).get("clip_name"),
605
- "clip_id": (report.get("clip") or {}).get("clip_id"),
606
- "analyzed_at": analyzed_at,
607
- "age_days": age_days,
608
- "vision_token": report.get("vision_token") or visual.get("vision_token"),
609
- "frame_count": len(frame_paths),
610
- "frames_still_on_disk": frames_present,
611
- "reofferable": frames_present > 0,
612
- }
613
- pending.append(row)
614
- over_age = max_age_days is not None and (age_days or 0) >= float(max_age_days)
615
- if expire and (max_age_days is None or over_age):
616
- report["vision_status"] = "expired_host_analysis"
617
- report["vision_expired_at"] = _now()
618
- ingest = analysis_store.ingest_report(root, report, clip_dir=clip_dir)
619
- if ingest.get("success"):
620
- analysis_store.export_report_file(root, ingest["clip_uuid"], report_path)
621
- else:
622
- ma._write_json(report_path, report)
623
- expired.append(entry)
624
- row["expired"] = True
625
- elif reoffer and frames_present:
626
- reoffers.append({
627
- "clip_dir": entry,
628
- "payload": visual,
629
- })
630
- result: Dict[str, Any] = {
631
- "success": True,
632
- "project_root": root,
633
- "pending_count": len(pending),
634
- "pending": pending,
635
- "expired": expired,
636
- }
637
- if reoffer:
638
- result["reoffers"] = reoffers
639
- result["note"] = (
640
- "Each reoffer.payload is the original deferred host-vision payload; "
641
- "read its frame_paths and call its commit_action to finish the run."
642
- )
643
- return result