@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
package/installer.js CHANGED
@@ -493,7 +493,13 @@ function adoptFileIntoManifest(manifest, targetPath, sourceHash) {
493
493
  // payload (used to identify files we recognise even before manifest entry).
494
494
  function resolveFileConflict(sourcePath, targetPath, manifest, knownSourceHashes) {
495
495
  const sourceHash = computeFileHash(sourcePath);
496
- if (!sourceHash) return 'skipped'; // unreadable source – skip
496
+ if (!sourceHash) {
497
+ // Every other branch of this function reports what it did; this one
498
+ // used to return silently, so a file that failed to install left no
499
+ // trace behind the enclosing "Synced BDB skills" success line.
500
+ log.warn(`[manifest] Could not read ${sourcePath} from the payload — ${path.basename(targetPath)} not installed.`);
501
+ return 'skipped';
502
+ }
497
503
 
498
504
  const manifestEntry = manifest[targetPath];
499
505
  const diskExists = fs.existsSync(targetPath);
@@ -508,10 +514,30 @@ function resolveFileConflict(sourcePath, targetPath, manifest, knownSourceHashes
508
514
 
509
515
  const diskHash = computeFileHash(targetPath);
510
516
 
511
- // Case: foreign – no manifest entry AND disk hash unknown to this payload.
517
+ // Case: unrecognised content at a path this payload owns.
518
+ //
519
+ // This used to skip, on the reading that unknown content means a file the
520
+ // installer did not place. But resolveFileConflict only ever sees paths the
521
+ // payload is actively writing, so the path IS ours -- and the commonest
522
+ // cause of unknown content is simply an older release of our own file,
523
+ // installed before manifest tracking existed and therefore never adopted.
524
+ // Skipping froze those permanently: v4.4.0 still found skills on disk
525
+ // carrying pre-reorg ~/bdb-dev paths and a stale `category:`, untouched
526
+ // across every update since, because bootstrap adoption only catches a file
527
+ // that is byte-identical to the current payload.
528
+ //
529
+ // So update it, and keep the protection where it belongs -- in the backup,
530
+ // not in refusing to write. A genuine local edit is recoverable from .bak
531
+ // rather than silently outvoting the shipped version forever.
512
532
  if (!manifestEntry && !knownSourceHashes.has(diskHash)) {
513
- log.warn(`[manifest] Foreign file at ${targetPath} — left untouched.`);
514
- return 'skipped';
533
+ const bakPath = `${targetPath}.${timestamp}.bak`;
534
+ try { fs.copyFileSync(targetPath, bakPath); } catch (e) {
535
+ log.warn(`[manifest] Could not create backup ${bakPath}: ${e.message}`);
536
+ }
537
+ fs.copyFileSync(sourcePath, targetPath);
538
+ manifest[targetPath] = { path: targetPath, sha256: sourceHash, version: pkg.version, installedAt: new Date().toISOString() };
539
+ log.warn(`[manifest] Unrecognised ${path.basename(targetPath)} backed up to ${bakPath}, shipped version written.`);
540
+ return 'bak';
515
541
  }
516
542
 
517
543
  // Case: first-run bootstrap – disk hash matches source hash but no manifest entry.
@@ -529,7 +555,7 @@ function resolveFileConflict(sourcePath, targetPath, manifest, knownSourceHashes
529
555
 
530
556
  // Case: ours + user-edited (manifest recorded our hash, but disk now differs).
531
557
  if (manifestEntry && diskHash !== manifestEntry.sha256) {
532
- const bakPath = `${targetPath}.bak`;
558
+ const bakPath = `${targetPath}.${timestamp}.bak`;
533
559
  try { fs.copyFileSync(targetPath, bakPath); } catch (e) {
534
560
  log.warn(`[manifest] Could not create backup ${bakPath}: ${e.message}`);
535
561
  }
@@ -617,66 +643,123 @@ function resolveMcpsArg(availableMcps) {
617
643
  return matched;
618
644
  }
619
645
 
620
- function detectPlatforms() {
621
- const detections = [];
622
-
623
- if (fs.existsSync(geminiDir)) {
624
- detections.push({ name: "Google Antigravity", path: geminiDir, key: "antigravity" });
625
- }
626
-
627
- const codexDir = path.join(homeDir, '.codex');
628
- if (fs.existsSync(codexDir) || fs.existsSync(path.join(codexDir, 'config.toml'))) {
629
- detections.push({ name: "ChatGPT Codex CLI", path: codexDir, key: "codex" });
630
- }
631
-
632
- const claudeCodeConfig = path.join(homeDir, '.claude.json');
633
- const claudeCodeDir = path.join(homeDir, '.claude');
634
- if (fs.existsSync(claudeCodeConfig) || fs.existsSync(claudeCodeDir)) {
635
- detections.push({ name: "Claude Code CLI", path: claudeCodeDir, key: "claudecode" });
636
- }
637
-
638
- const claudePath = process.platform === 'win32'
639
- ? path.join(process.env.APPDATA || homeDir, 'Claude')
640
- : path.join(homeDir, 'Library', 'Application Support', 'Claude');
641
- if (fs.existsSync(claudePath)) {
642
- detections.push({ name: "Claude Desktop", path: claudePath, key: "claudedesktop" });
643
- }
646
+ // Platforms the user explicitly asked for, by detection key. A harness the
647
+ // user names is installed as far as we are concerned, whether or not we can
648
+ // find its binary -- they know their machine better than a PATH lookup does.
649
+ const explicitPlatformKeys = new Set();
650
+ const PLATFORM_KEYS_BY_OPTION = {
651
+ '1': 'antigravity',
652
+ '2': ['claudecode', 'claudedesktop'],
653
+ '3': 'cursor',
654
+ '5': 'codex',
655
+ '6': 'windsurf',
656
+ '7': 'vscode',
657
+ '8': 'aider',
658
+ };
644
659
 
645
- const cursorPath = process.platform === 'win32'
646
- ? path.join(process.env.APPDATA || homeDir, 'Cursor')
647
- : path.join(homeDir, 'Library', 'Application Support', 'Cursor');
648
- if (fs.existsSync(cursorPath)) {
649
- detections.push({ name: "Cursor IDE", path: cursorPath, key: "cursor" });
660
+ function markPlatformsExplicit(optionValues) {
661
+ for (const v of optionValues || []) {
662
+ const keys = PLATFORM_KEYS_BY_OPTION[v];
663
+ if (!keys) continue;
664
+ for (const k of [].concat(keys)) explicitPlatformKeys.add(k);
650
665
  }
666
+ }
651
667
 
652
- const windsurfPath = process.platform === 'win32'
653
- ? path.join(process.env.APPDATA || homeDir, 'Windsurf')
654
- : path.join(homeDir, 'Library', 'Application Support', 'Windsurf');
655
- if (fs.existsSync(windsurfPath)) {
656
- detections.push({ name: "Windsurf IDE", path: windsurfPath, key: "windsurf" });
657
- }
668
+ // Where an application itself lives, per OS. Presence of the app is proof;
669
+ // presence of a dot-directory is not.
670
+ function appBundle(name) {
671
+ if (process.platform === 'darwin') return [`/Applications/${name}.app`, path.join(homeDir, 'Applications', `${name}.app`)];
672
+ if (process.platform === 'win32') return [path.join(process.env.LOCALAPPDATA || homeDir, 'Programs', name)];
673
+ return [`/usr/share/${name.toLowerCase()}`, `/opt/${name.toLowerCase()}`];
674
+ }
658
675
 
659
- const vscodePath = process.platform === 'win32'
660
- ? path.join(process.env.APPDATA || homeDir, 'Code')
661
- : path.join(homeDir, 'Library', 'Application Support', 'Code');
662
- const rooPath = path.join(homeDir, '.roo');
663
- const clinePath = path.join(homeDir, '.cline');
664
- if (fs.existsSync(vscodePath) || fs.existsSync(rooPath) || fs.existsSync(clinePath)) {
665
- detections.push({ name: "Roo Code / Cline / VS Code", path: vscodePath, key: "vscode" });
666
- }
676
+ const anyExists = (paths) => paths.some((p) => fs.existsSync(p));
677
+ const globExists = (dir, re) => {
678
+ try { return fs.readdirSync(dir).some((e) => re.test(e)); } catch { return false; }
679
+ };
667
680
 
668
- const aiderConf = path.join(homeDir, '.aider.conf.yml');
669
- if (fs.existsSync(aiderConf) || fs.existsSync(path.join(homeDir, '.aider'))) {
670
- detections.push({ name: "Aider CLI", path: homeDir, key: "aider" });
671
- }
681
+ // Detect installed agent harnesses.
682
+ //
683
+ // This used to test for a config *directory* and nothing else, which was
684
+ // circular: syncSkillsToGlobalHarnesses() creates ~/.claude, ~/.codex,
685
+ // ~/.cursor and ~/.roo unconditionally, so after one install the detector
686
+ // found evidence the installer had planted itself. A machine with neither
687
+ // Cursor nor VS Code nor Aider still reported all three, and skills were
688
+ // written into directories nothing would ever read.
689
+ //
690
+ // Evidence now means the application itself and nothing else: a binary on
691
+ // PATH, an installed app bundle, or an editor extension. Config files are
692
+ // deliberately NOT evidence -- the installer writes ~/.claude.json,
693
+ // ~/.cursor/mcp.json, ~/.roo/mcp_settings.json and the rest of them, so
694
+ // trusting those reintroduces the same circularity one level down. (Written
695
+ // after exactly that: an early cut of this fix still "found" Cursor and
696
+ // Windsurf on a machine with neither, via files AOS had planted.)
697
+ // The user's explicit choice counts too -- see markPlatformsExplicit().
698
+ function detectPlatforms() {
699
+ const candidates = [
700
+ {
701
+ key: 'antigravity', name: 'Google Antigravity', path: geminiDir,
702
+ evidence: () => hasExecutable('agy') || anyExists(appBundle('Antigravity')),
703
+ },
704
+ {
705
+ key: 'codex', name: 'ChatGPT Codex CLI', path: path.join(homeDir, '.codex'),
706
+ evidence: () => hasExecutable('codex'),
707
+ },
708
+ {
709
+ key: 'claudecode', name: 'Claude Code CLI', path: path.join(homeDir, '.claude'),
710
+ evidence: () => hasExecutable('claude'),
711
+ },
712
+ {
713
+ key: 'claudedesktop', name: 'Claude Desktop',
714
+ path: process.platform === 'win32'
715
+ ? path.join(process.env.APPDATA || homeDir, 'Claude')
716
+ : path.join(homeDir, 'Library', 'Application Support', 'Claude'),
717
+ evidence: () => anyExists(appBundle('Claude')),
718
+ },
719
+ {
720
+ key: 'cursor', name: 'Cursor IDE',
721
+ path: process.platform === 'win32'
722
+ ? path.join(process.env.APPDATA || homeDir, 'Cursor')
723
+ : path.join(homeDir, 'Library', 'Application Support', 'Cursor'),
724
+ evidence: () => anyExists(appBundle('Cursor')) || hasExecutable('cursor'),
725
+ },
726
+ {
727
+ key: 'windsurf', name: 'Windsurf IDE',
728
+ path: process.platform === 'win32'
729
+ ? path.join(process.env.APPDATA || homeDir, 'Windsurf')
730
+ : path.join(homeDir, 'Library', 'Application Support', 'Windsurf'),
731
+ evidence: () => anyExists(appBundle('Windsurf')) || hasExecutable('windsurf'),
732
+ },
733
+ {
734
+ key: 'vscode', name: 'Roo Code / Cline / VS Code',
735
+ path: process.platform === 'win32'
736
+ ? path.join(process.env.APPDATA || homeDir, 'Code')
737
+ : path.join(homeDir, 'Library', 'Application Support', 'Code'),
738
+ evidence: () => anyExists(appBundle('Visual Studio Code'))
739
+ || hasExecutable('code')
740
+ || globExists(path.join(homeDir, '.vscode', 'extensions'), /roo|cline/i),
741
+ },
742
+ {
743
+ key: 'aider', name: 'Aider CLI', path: homeDir,
744
+ evidence: () => hasExecutable('aider'),
745
+ },
746
+ {
747
+ key: 'opencode', name: 'OpenCode CLI',
748
+ path: process.platform === 'win32'
749
+ ? path.join(process.env.APPDATA || homeDir, 'opencode')
750
+ : path.join(homeDir, '.config', 'opencode'),
751
+ evidence: () => hasExecutable('opencode'),
752
+ },
753
+ ];
672
754
 
673
- const opencodeDir = process.platform === 'win32'
674
- ? path.join(process.env.APPDATA || homeDir, 'opencode')
675
- : path.join(homeDir, '.config', 'opencode');
676
- if (fs.existsSync(opencodeDir) || fs.existsSync(path.join(homeDir, '.opencode'))) {
677
- detections.push({ name: "OpenCode CLI", path: fs.existsSync(opencodeDir) ? opencodeDir : path.join(homeDir, '.opencode'), key: "opencode" });
755
+ const detections = [];
756
+ for (const c of candidates) {
757
+ let present = false;
758
+ try { present = c.evidence(); } catch (e) { logDebug(e, `detect ${c.key}`); }
759
+ if (present || explicitPlatformKeys.has(c.key)) {
760
+ detections.push({ name: c.name, path: c.path, key: c.key, chosen: !present });
761
+ }
678
762
  }
679
-
680
763
  return detections;
681
764
  }
682
765
 
@@ -731,10 +814,9 @@ function detectInstallState() {
731
814
  { id: 'synapse', dir: path.join(basePath, 'bdb-synapse') },
732
815
  { id: 'memb', dir: path.join(basePath, 'memB') },
733
816
  { id: 'remote', dir: path.join(basePath, 'bdb-os-remote') },
734
- // bdb-os-agent-workspace: temporarily disabled -- see the comment in
735
- // promptOptionalModules() below for why. Not re-detected as a fresh
736
- // install; existing installs are still skipped explicitly in the
737
- // update loop further down.
817
+ { id: 'ao', dir: path.join(basePath, 'bdb-agent-orchestrator') },
818
+ // The archived predecessor is deliberately NOT detected: a copy of it
819
+ // should be removed, not carried forward into another install.
738
820
  { id: 'creator', dir: path.join(basePath, 'bdb-dev-creator-extension') },
739
821
  { id: 'installer', dir: path.join(basePath, 'bdb-dev-tool-installer') }
740
822
  ];
@@ -932,9 +1014,9 @@ function copyDirRecursiveSync(source, target, excludeList = [], manifest = null,
932
1014
  // root-level leaf skill dumps into the same flat target, so each subsequent
933
1015
  // one silently overwrites the previous one's SKILL.md -- found by tracing
934
1016
  // why skills/bdbrainstorm/ and skills/global_config/bdbrainstorm/ both
935
- // existed with different content; the root-level one (and 5 siblings:
936
- // bdbsaastraining, github-repo, memb-ingest, bdb-dev-os-skill,
937
- // synapse-integration-skill) never survived a global sync intact.
1017
+ // existed with different content; the root-level one (and 3 siblings:
1018
+ // github-repo, memb-ingest, synapse-integration-skill) never survived a
1019
+ // global sync intact.
938
1020
  function syncSkillEntry(fullPath, dirName, targetSkillDir, excludeSkills) {
939
1021
  const isLeafSkill = fs.existsSync(path.join(fullPath, 'SKILL.md'));
940
1022
  if (isLeafSkill) {
@@ -965,15 +1047,20 @@ function syncSkillsToGlobalHarnesses(excludeSkills = []) {
965
1047
  const skillsBase = path.join(srcDir, 'skills');
966
1048
  if (!fs.existsSync(skillsBase)) return;
967
1049
 
1050
+ // Mirror only into harnesses that are actually present. This list used to
1051
+ // be unconditional, which both wrote skills nobody would read and planted
1052
+ // the very directories detectPlatforms() then read back as proof the
1053
+ // harness existed. ~/.agents is ours and always written.
1054
+ const detectedKeys = new Set(detectPlatforms().map((d) => d.key));
968
1055
  const extraSkillDestinations = [
969
- path.join(homeDir, '.claude', 'skills'),
970
- path.join(homeDir, '.agents', 'skills'),
971
- path.join(homeDir, '.codex', 'skills'),
972
- path.join(homeDir, '.cursor', 'skills'),
973
- path.join(homeDir, '.roo', 'skills')
974
- ];
975
-
976
- for (const dest of extraSkillDestinations) {
1056
+ { dir: path.join(homeDir, '.agents', 'skills'), key: null },
1057
+ { dir: path.join(homeDir, '.claude', 'skills'), key: 'claudecode' },
1058
+ { dir: path.join(homeDir, '.codex', 'skills'), key: 'codex' },
1059
+ { dir: path.join(homeDir, '.cursor', 'skills'), key: 'cursor' },
1060
+ { dir: path.join(homeDir, '.roo', 'skills'), key: 'vscode' },
1061
+ ].filter((d) => d.key === null || detectedKeys.has(d.key) || fs.existsSync(d.dir));
1062
+
1063
+ for (const { dir: dest } of extraSkillDestinations) {
977
1064
  try {
978
1065
  fs.mkdirSync(dest, { recursive: true });
979
1066
  const rawDirs = fs.readdirSync(skillsBase);
@@ -1394,23 +1481,34 @@ function checkModuleUpdate(pkgName, targetDir) {
1394
1481
  try {
1395
1482
  const localVer = JSON.parse(fs.readFileSync(modulePkgPath, 'utf8')).version;
1396
1483
  let remoteVer = null;
1484
+ let checkFailed = false;
1397
1485
  try {
1398
1486
  remoteVer = execSync(`npm view ${pkgName} version`, { stdio: ['ignore', 'pipe', 'ignore'], encoding: 'utf8', timeout: 4000 }).trim();
1399
- } catch (e) { logDebug(e, 'operation'); }
1487
+ } catch (e) {
1488
+ // Offline, a proxy, or a misconfigured registry. Swallowing this
1489
+ // made every module report "up to date" forever on such a machine,
1490
+ // indistinguishable from a real answer -- so say which one it is.
1491
+ checkFailed = true;
1492
+ logDebug(e, `npm view ${pkgName}`);
1493
+ }
1400
1494
 
1401
1495
  if (remoteVer && localVer !== remoteVer) {
1402
- return { installed: true, updateAvailable: true, localVer, remoteVer };
1496
+ return { installed: true, updateAvailable: true, localVer, remoteVer, checkFailed };
1403
1497
  }
1404
- return { installed: true, updateAvailable: false, localVer, remoteVer };
1498
+ return { installed: true, updateAvailable: false, localVer, remoteVer, checkFailed };
1405
1499
  } catch (e) {
1406
- return { installed: true, updateAvailable: false, localVer: null, remoteVer: null };
1500
+ return { installed: true, updateAvailable: false, localVer: null, remoteVer: null, checkFailed: true };
1407
1501
  }
1408
1502
  }
1409
1503
 
1410
1504
  function downloadOrUpdateModule(pkgName, targetDir, displayName) {
1411
1505
  const status = checkModuleUpdate(pkgName, targetDir);
1412
1506
  if (status.installed && !status.updateAvailable) {
1413
- log.step(`${displayName} is up to date (v${status.localVer})`);
1507
+ if (status.checkFailed) {
1508
+ log.warn(`${displayName}: could not reach the npm registry — keeping the installed v${status.localVer}, which may be stale.`);
1509
+ } else {
1510
+ log.step(`${displayName} is up to date (v${status.localVer})`);
1511
+ }
1414
1512
  return true;
1415
1513
  }
1416
1514
 
@@ -1438,14 +1536,42 @@ function downloadOrUpdateModule(pkgName, targetDir, displayName) {
1438
1536
  const ok = runNpmWithRetry(`npm pack ${pkgName}@latest`, { stdio: 'ignore', cwd: targetDir }, `${displayName} download`);
1439
1537
  cleanNpmCacheOnWindows();
1440
1538
  const tarball = ok ? fs.readdirSync(targetDir).find(f => f.endsWith('.tgz')) : null;
1441
- if (tarball) {
1442
- execSync(`tar -xzf "${tarball}" --strip-components=1`, { stdio: 'ignore', cwd: targetDir });
1443
- fs.unlinkSync(path.join(targetDir, tarball));
1444
- s.stop(`${displayName} ready (v${status.remoteVer || 'latest'})`);
1445
- return true;
1446
- } else {
1447
- throw new Error("NPM pack returned no archive.");
1539
+ if (!tarball) throw new Error("NPM pack returned no archive.");
1540
+
1541
+ // Unpacking straight over targetDir made an update a MERGE: a file the
1542
+ // new version dropped stayed on disk forever, while package.json said
1543
+ // the module was current. Unpack beside it and swap, so the tree that
1544
+ // ends up installed is exactly the published one. The previous tree is
1545
+ // kept until the swap succeeds, and restored if it does not.
1546
+ const stagingDir = `${targetDir}.incoming-${timestamp}`;
1547
+ const retiredDir = `${targetDir}.previous-${timestamp}`;
1548
+ fs.rmSync(stagingDir, { recursive: true, force: true });
1549
+ fs.mkdirSync(stagingDir, { recursive: true });
1550
+ execSync(`tar -xzf "${path.join(targetDir, tarball)}" --strip-components=1 -C "${stagingDir}"`, { stdio: 'ignore' });
1551
+ fs.unlinkSync(path.join(targetDir, tarball));
1552
+
1553
+ // Anything the module generated in place -- venvs, caches, local state
1554
+ // -- is not in the tarball and must survive the swap.
1555
+ const PRESERVE = ['.venv', 'venv', 'node_modules', '.env', 'data', 'logs'];
1556
+ for (const keep of PRESERVE) {
1557
+ const from = path.join(targetDir, keep);
1558
+ if (fs.existsSync(from) && !fs.existsSync(path.join(stagingDir, keep))) {
1559
+ fs.renameSync(from, path.join(stagingDir, keep));
1560
+ }
1448
1561
  }
1562
+
1563
+ try {
1564
+ fs.renameSync(targetDir, retiredDir);
1565
+ fs.renameSync(stagingDir, targetDir);
1566
+ } catch (swapError) {
1567
+ if (fs.existsSync(retiredDir) && !fs.existsSync(targetDir)) fs.renameSync(retiredDir, targetDir);
1568
+ fs.rmSync(stagingDir, { recursive: true, force: true });
1569
+ throw swapError;
1570
+ }
1571
+ fs.rmSync(retiredDir, { recursive: true, force: true });
1572
+
1573
+ s.stop(`${displayName} ready (v${status.remoteVer || 'latest'})`);
1574
+ return true;
1449
1575
  } catch (e) {
1450
1576
  s.stop(`Failed downloading ${displayName}: ${e.message}`);
1451
1577
  return false;
@@ -1465,7 +1591,14 @@ async function installMemB(interactive) {
1465
1591
  }));
1466
1592
  }
1467
1593
  const membDir = path.join(moduleBasePath(), 'memB');
1468
- downloadOrUpdateModule('@hybridlabor-api/memb', membDir, 'memB Vector Engine');
1594
+ // A failed download used to fall through into venv setup and daemon
1595
+ // registration, each of which then failed on its own terms or, worse,
1596
+ // "succeeded" against a stale tree -- and the run still ended in a success
1597
+ // banner. Stop at the module that could not be fetched.
1598
+ if (!downloadOrUpdateModule('@hybridlabor-api/memb', membDir, 'memB Vector Engine')) {
1599
+ log.warn('Skipping memB setup: the module could not be downloaded.');
1600
+ return;
1601
+ }
1469
1602
  if (DRY_RUN) {
1470
1603
  log.step('[dry-run] would bootstrap memB venv + pip requirements');
1471
1604
  return;
@@ -1577,7 +1710,10 @@ async function installMemB(interactive) {
1577
1710
 
1578
1711
  async function installSynapse() {
1579
1712
  const synapseDir = path.join(moduleBasePath(), 'bdb-synapse');
1580
- downloadOrUpdateModule('@hybridlabor-api/bdb-synapse', synapseDir, 'BDB Synapse');
1713
+ if (!downloadOrUpdateModule('@hybridlabor-api/bdb-synapse', synapseDir, 'BDB Synapse')) {
1714
+ log.warn('Skipping Synapse setup: the module could not be downloaded.');
1715
+ return;
1716
+ }
1581
1717
  if (DRY_RUN) {
1582
1718
  log.step('[dry-run] would link synapse binary into ~/.local/bin/synapse + setup background daemon');
1583
1719
  return;
@@ -1691,7 +1827,10 @@ async function installSynapse() {
1691
1827
 
1692
1828
  async function installCreatorExtension() {
1693
1829
  const creatorDir = path.join(moduleBasePath(), 'bdb-dev-creator-extension');
1694
- downloadOrUpdateModule('@hybridlabor-api/bdb-dev-creator-extension', creatorDir, 'BDB Creator Extension');
1830
+ if (!downloadOrUpdateModule('@hybridlabor-api/bdb-dev-creator-extension', creatorDir, 'BDB Creator Extension')) {
1831
+ log.warn('Skipping Creator Extension setup: the module could not be downloaded.');
1832
+ return;
1833
+ }
1695
1834
  if (DRY_RUN) {
1696
1835
  log.step('[dry-run] would run BDB Creator Extension setup');
1697
1836
  return;
@@ -1709,138 +1848,101 @@ async function installCreatorExtension() {
1709
1848
 
1710
1849
  async function installOSRemoteGateway() {
1711
1850
  const remoteDir = path.join(moduleBasePath(), 'bdb-os-remote');
1712
- downloadOrUpdateModule('@hybridlabor-api/bdb-os-remote', remoteDir, 'BDB OS Remote Gateway');
1851
+ if (!downloadOrUpdateModule('@hybridlabor-api/bdb-os-remote', remoteDir, 'BDB OS Remote Gateway')) {
1852
+ log.warn('Skipping OS Remote Gateway setup: the module could not be downloaded.');
1853
+ return;
1854
+ }
1713
1855
  }
1714
1856
 
1715
1857
  async function installDevToolInstaller() {
1716
1858
  const toolInstallerDir = path.join(moduleBasePath(), 'bdb-dev-tool-installer');
1717
- downloadOrUpdateModule('@hybridlabor-api/bdb-dev-tool-installer', toolInstallerDir, 'BDB Dev Tool Installer');
1859
+ if (!downloadOrUpdateModule('@hybridlabor-api/bdb-dev-tool-installer', toolInstallerDir, 'BDB Dev Tool Installer')) {
1860
+ log.warn('Skipping Dev Tool Installer setup: the module could not be downloaded.');
1861
+ return;
1862
+ }
1718
1863
  }
1719
1864
 
1720
- async function installOSAgentWorkspace() {
1721
- const osAgentDir = path.join(moduleBasePath(), 'bdb-os-agent-workspace');
1722
- const localBinDir = path.join(homeDir, '.local', 'bin');
1723
- const binTarget = path.join(localBinDir, 'ao');
1724
-
1725
- fs.mkdirSync(localBinDir, { recursive: true });
1726
- fs.mkdirSync(path.join(homeDir, '.ao', 'data'), { recursive: true });
1727
- fs.mkdirSync(path.join(homeDir, '.ao', 'logs'), { recursive: true });
1865
+ // AO ships one prebuilt binary: backend/ao-daemon, Mach-O arm64. The package
1866
+ // carries Windows and Linux sources but nothing built for them, and its CLI
1867
+ // shim picks the first candidate path that EXISTS without checking whether it
1868
+ // can run — so on any other platform it hands spawn a Mach-O file and fails
1869
+ // with an exec-format error instead of the "go build" hint it means to give.
1870
+ // Installing it there would be installing something known not to work.
1871
+ function aoSupportedHere() {
1872
+ return process.platform === 'darwin' && process.arch === 'arm64';
1873
+ }
1728
1874
 
1729
- downloadOrUpdateModule('@hybridlabor-api/bdb-os-agent-workspace', osAgentDir, 'BDB OS Agent Workspace');
1875
+ async function installOSAgentWorkspace() {
1876
+ if (!aoSupportedHere()) {
1877
+ log.warn(`AO ships only a macOS arm64 binary — skipping on ${process.platform}/${process.arch}.`);
1878
+ log.warn('Build it from source instead: github.com/hybridlabor-api/bdb-agent-orchestrator');
1879
+ return;
1880
+ }
1730
1881
 
1882
+ const osAgentDir = path.join(moduleBasePath(), 'bdb-agent-orchestrator');
1883
+ if (!downloadOrUpdateModule('@hybridlabor-api/bdb-agent-orchestrator', osAgentDir, 'BDB Agent Orchestrator')) {
1884
+ log.warn('Skipping AO setup: the module could not be downloaded.');
1885
+ return;
1886
+ }
1731
1887
  if (DRY_RUN) {
1732
- log.step('[dry-run] would link ao binary + register LaunchAgent + Desktop App');
1888
+ log.step('[dry-run] would link ~/.local/bin/ao and run `ao service install`');
1733
1889
  return;
1734
1890
  }
1735
1891
 
1892
+ const localBinDir = path.join(homeDir, '.local', 'bin');
1893
+ const binTarget = path.join(localBinDir, 'ao');
1736
1894
  const daemonBin = path.join(osAgentDir, 'backend', 'ao-daemon');
1737
- if (fs.existsSync(daemonBin)) {
1738
- try {
1739
- fs.copyFileSync(daemonBin, binTarget);
1740
- fs.chmodSync(binTarget, '755');
1741
- } catch (e) { logDebug(e, 'operation'); }
1895
+
1896
+ if (!fs.existsSync(daemonBin)) {
1897
+ log.warn(`AO binary missing at ${daemonBin} — package layout changed?`);
1898
+ return;
1742
1899
  }
1743
1900
 
1744
- if (process.platform === 'darwin') {
1745
- const plistPath = path.join(homeDir, 'Library', 'LaunchAgents', 'com.bdb.agent-workspace.plist');
1746
- const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
1747
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1748
- <plist version="1.0">
1749
- <dict>
1750
- <key>Label</key>
1751
- <string>com.bdb.agent-workspace</string>
1752
- <key>ProgramArguments</key>
1753
- <array>
1754
- <string>${binTarget}</string>
1755
- </array>
1756
- <key>EnvironmentVariables</key>
1757
- <dict>
1758
- <key>AO_PORT</key>
1759
- <string>3101</string>
1760
- <key>AO_DATA_DIR</key>
1761
- <string>${path.join(homeDir, '.ao', 'data')}</string>
1762
- <key>PATH</key>
1763
- <string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/homebrew/bin:${localBinDir}</string>
1764
- </dict>
1765
- <key>RunAtLoad</key>
1766
- <true/>
1767
- <key>KeepAlive</key>
1768
- <true/>
1769
- <key>StandardOutPath</key>
1770
- <string>${path.join(homeDir, '.ao', 'logs', 'daemon.stdout.log')}</string>
1771
- <key>StandardErrorPath</key>
1772
- <string>${path.join(homeDir, '.ao', 'logs', 'daemon.stderr.log')}</string>
1773
- </dict>
1774
- </plist>`;
1775
- try {
1776
- fs.writeFileSync(plistPath, plistContent);
1777
- execSync(`launchctl unload "${plistPath}" 2>/dev/null || true`, { stdio: 'ignore' });
1778
- execSync(`launchctl load -w "${plistPath}" 2>/dev/null || true`, { stdio: 'ignore' });
1779
- const isListening = await verifyDaemonListening(3101, 'Agent Workspace');
1780
- if (isListening) {
1781
- log.success('Agent Workspace LaunchAgent active (Port 3101)');
1782
- } else {
1783
- log.warn('Agent Workspace daemon did not respond on Port 3101 within timeout. You may need to start it manually or check for port conflicts.');
1784
- }
1785
- } catch (e) { logDebug(e, 'operation'); }
1901
+ installStep('place the ao binary', () => {
1902
+ fs.mkdirSync(localBinDir, { recursive: true });
1903
+ // copyFileSync preserves bytes, so the package's ad-hoc signature
1904
+ // survives. That matters: an unsigned binary at this path is SIGKILLed
1905
+ // by AMFI on launch (exit 137) and reads as a daemon that simply will
1906
+ // not start, with nothing in the log to say why.
1907
+ fs.copyFileSync(daemonBin, binTarget);
1908
+ fs.chmodSync(binTarget, 0o755);
1909
+ log.step(`Installed ao to ${binTarget}`);
1910
+ }, 'AO cannot be started without its binary.');
1786
1911
 
1787
- let appDir = '/Applications/BDB Agent Workspace.app';
1788
- try {
1789
- fs.accessSync('/Applications', fs.constants.W_OK);
1790
- } catch (e) {
1791
- logDebug(e, 'write-access probe on /Applications');
1792
- appDir = path.join(homeDir, 'Applications', 'BDB Agent Workspace.app');
1793
- log.step('/Applications is not writable - falling back to ~/Applications');
1794
- }
1912
+ try {
1913
+ execSync(`codesign -v "${binTarget}"`, { stdio: 'ignore' });
1914
+ } catch {
1915
+ log.warn('The ao binary is not code-signed — AMFI will kill it at launch.');
1795
1916
  try {
1796
- fs.mkdirSync(path.join(appDir, 'Contents', 'MacOS'), { recursive: true });
1797
- fs.mkdirSync(path.join(appDir, 'Contents', 'Resources'), { recursive: true });
1798
- const iconSrc = path.join(osAgentDir, 'frontend', 'assets', 'icon.icns');
1799
- if (fs.existsSync(iconSrc)) {
1800
- fs.copyFileSync(iconSrc, path.join(appDir, 'Contents', 'Resources', 'AppIcon.icns'));
1801
- }
1802
- const launcherScript = `#!/usr/bin/env bash
1803
- export PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/homebrew/bin:${localBinDir}:$PATH"
1804
- if ! curl -s http://127.0.0.1:3101/healthz >/dev/null 2>&1; then
1805
- launchctl start com.bdb.agent-workspace 2>/dev/null || "${binTarget}" &
1806
- sleep 0.5
1807
- fi
1808
- if [ -d "/Applications/Google Chrome.app" ]; then
1809
- open -na "/Applications/Google Chrome.app" --args --app="http://127.0.0.1:3101" --user-data-dir="$HOME/.ao/chrome-app-profile"
1810
- elif [ -d "/Applications/Brave Browser.app" ]; then
1811
- open -na "/Applications/Brave Browser.app" --args --app="http://127.0.0.1:3101" --user-data-dir="$HOME/.ao/brave-app-profile"
1812
- else
1813
- open "http://127.0.0.1:3101"
1814
- fi`;
1815
- const launcherPath = path.join(appDir, 'Contents', 'MacOS', 'app_launcher');
1816
- fs.writeFileSync(launcherPath, launcherScript);
1817
- fs.chmodSync(launcherPath, '755');
1818
- log.success(`Desktop App available under ${appDir}`);
1819
- } catch (e) { logDebug(e, 'operation'); }
1820
- } else if (process.platform === 'win32') {
1821
- const startupDir = path.join(process.env.APPDATA || path.join(homeDir, 'AppData', 'Roaming'), 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup');
1822
- fs.mkdirSync(startupDir, { recursive: true });
1823
- const vbsPath = path.join(startupDir, 'com.bdb.agent-workspace.vbs');
1824
- const daemonBin = path.join(osAgentDir, 'backend', 'ao-daemon.exe');
1825
- const runBin = fs.existsSync(daemonBin) ? daemonBin : path.join(osAgentDir, 'backend', 'ao.exe');
1826
- if (fs.existsSync(runBin)) {
1827
- const vbsContent = `Set WshShell = CreateObject("WScript.Shell")\r\nWshShell.CurrentDirectory = "${osAgentDir}"\r\nWshShell.Run """${runBin}""", 0, False\r\n`;
1828
- try {
1829
- fs.writeFileSync(vbsPath, vbsContent, 'utf-8');
1830
- spawn('wscript.exe', [vbsPath], { detached: true, stdio: 'ignore' }).unref();
1831
- const isListening = await verifyDaemonListening(3101, 'Agent Workspace');
1832
- if (isListening) {
1833
- log.success('Agent Workspace Windows Background Service registered & started (Port 3101)');
1834
- } else {
1835
- log.warn('Agent Workspace Windows daemon did not respond on Port 3101 within timeout. You may need to start it manually or check for port conflicts.');
1836
- }
1837
- } catch (e) { logDebug(e, 'windows workspace daemon setup'); }
1838
- }
1917
+ execSync(`codesign -s - -f "${binTarget}"`, { stdio: 'ignore' });
1918
+ log.step('Signed it ad-hoc.');
1919
+ } catch (e) { log.warn(`Could not sign it: ${e.message}`); }
1920
+ }
1921
+
1922
+ // AO installs its own service, and does it for macOS, Windows and Linux.
1923
+ // AOS used to hand-write a LaunchAgent instead, labelled
1924
+ // com.bdb.agent-workspace, whose ProgramArguments were just the binary with
1925
+ // NO subcommand -- so launchd started `ao`, which prints help and exits,
1926
+ // and with KeepAlive kept restarting it. The daemon never ran, on any
1927
+ // machine that installed AO through AOS. Delegating to the tool removes
1928
+ // both the wrong label and the wrong arguments, and keeps working when the
1929
+ // service definition changes upstream.
1930
+ const legacyPlist = path.join(homeDir, 'Library', 'LaunchAgents', 'com.bdb.agent-workspace.plist');
1931
+ if (fs.existsSync(legacyPlist)) {
1932
+ try { execSync(`launchctl unload "${legacyPlist}" 2>/dev/null`, { stdio: 'ignore' }); } catch (e) { logDebug(e, 'unload legacy ao agent'); }
1933
+ try { fs.unlinkSync(legacyPlist); log.step('Removed the old com.bdb.agent-workspace service.'); } catch (e) { logDebug(e, 'remove legacy plist'); }
1934
+ }
1935
+
1936
+ installStep('register the AO service', () => {
1937
+ execSync(`"${binTarget}" service install`, { stdio: 'ignore' });
1938
+ }, 'Start it by hand with: ao service install');
1939
+
1940
+ if (await verifyDaemonListening(3101, 'AO Orchestrator', 8000)) {
1941
+ log.success('AO Orchestrator running on http://localhost:3101');
1942
+ } else {
1943
+ log.warn('AO did not answer on :3101 — check `ao service status` and `ao service logs`.');
1839
1944
  }
1840
-
1841
- log.step('BDB Agent Workspace WebUI: http://localhost:3101');
1842
1945
  }
1843
-
1844
1946
  async function promptMemBIngestion(mcpCodeTarget) {
1845
1947
  if (isAutoYes || DRY_RUN) return;
1846
1948
 
@@ -1883,30 +1985,16 @@ async function promptMemBIngestion(mcpCodeTarget) {
1883
1985
  }
1884
1986
  }
1885
1987
 
1886
- async function promptEcosystemHealthScheduler() {
1887
- if (isAutoYes) return;
1888
- const doSchedule = pick(await askConfirm({
1889
- message: 'Enable automated Health Audit Cron Job (2x daily at 01:00 and 12:00)?',
1890
- initialValue: true
1891
- }));
1892
- if (doSchedule) {
1893
- log.success(`Health Audit Scheduler configured for 01:00 & 12:00. Script: ${path.join(srcDir, 'scripts', 'ecosystem-health-audit.js')}`);
1894
- }
1895
- }
1896
-
1897
1988
  function verifyEcosystemInstallation() {
1898
1989
  const modules = [
1899
1990
  { name: '1. bdb-synapse', pkg: '@hybridlabor-api/bdb-synapse', paths: [path.join(moduleBasePath(), 'bdb-synapse')] },
1900
1991
  { name: '2. memB', pkg: '@hybridlabor-api/memb', paths: [path.join(moduleBasePath(), 'memB'), path.join(geminiDir, 'config', 'mcps', 'memb-mcp')] },
1901
1992
  { name: '3. heimdall-token-saver', pkg: '@hybridlabor-api/heimdall-token-saver', paths: [path.join(moduleBasePath(), 'heimdall-token-saver'), path.join(srcDir, 'vendor', 'token-saver')] },
1902
- // bdb-os-agent-workspace: temporarily disabled -- its main branch (both local
1903
- // and on GitHub) is frozen while the real work sits on unmerged WIP branches,
1904
- // so its published npm version doesn't reliably reflect what "latest" means
1905
- // right now. Re-add here once that's resolved.
1906
- { name: '4. bdb-dev-creator-extension', pkg: '@hybridlabor-api/bdb-dev-creator-extension', paths: [path.join(moduleBasePath(), 'bdb-dev-creator-extension')] },
1907
- { name: '5. bdb-os-remote', pkg: '@hybridlabor-api/bdb-os-remote', paths: [path.join(moduleBasePath(), 'bdb-os-remote')] },
1908
- { name: '6. bdb-dev-tool-installer', pkg: '@hybridlabor-api/bdb-dev-tool-installer', paths: [path.join(moduleBasePath(), 'bdb-dev-tool-installer')] },
1909
- { name: '7. aos (bdb agent os)', pkg: '@hybridlabor-api/aos', paths: [srcDir] }
1993
+ { name: '4. AO Agent Orchestrator', pkg: '@hybridlabor-api/bdb-agent-orchestrator', paths: [path.join(moduleBasePath(), 'bdb-agent-orchestrator')] },
1994
+ { name: '5. bdb-dev-creator-extension', pkg: '@hybridlabor-api/bdb-dev-creator-extension', paths: [path.join(moduleBasePath(), 'bdb-dev-creator-extension')] },
1995
+ { name: '6. bdb-os-remote', pkg: '@hybridlabor-api/bdb-os-remote', paths: [path.join(moduleBasePath(), 'bdb-os-remote')] },
1996
+ { name: '7. bdb-dev-tool-installer', pkg: '@hybridlabor-api/bdb-dev-tool-installer', paths: [path.join(moduleBasePath(), 'bdb-dev-tool-installer')] },
1997
+ { name: '8. aos (bdb agent os)', pkg: '@hybridlabor-api/aos', paths: [srcDir] }
1910
1998
  ];
1911
1999
 
1912
2000
  for (const mod of modules) {
@@ -1949,7 +2037,21 @@ function verifyEcosystemInstallation() {
1949
2037
  console.log(` • ${colors.bold}${mod.name.padEnd(35)}${colors.reset} ➔ ${colors.green}✅ Installed${colors.reset}`);
1950
2038
  }
1951
2039
  } else if (mod.name.includes('token-saver') && fs.existsSync(path.join(srcDir, 'vendor', 'token-saver'))) {
1952
- console.log(` • ${colors.bold}${mod.name.padEnd(35)}${colors.reset} ➔ ${colors.green}✅ v2.6.3 (Integrated)${colors.reset}`);
2040
+ // Was a hardcoded "v2.6.3", which stopped being true the moment the
2041
+ // vendored copy moved. Read it, and say so when it cannot be read.
2042
+ let vendored = null;
2043
+ for (const f of ['package.json', 'pyproject.toml', 'VERSION']) {
2044
+ const vp = path.join(srcDir, 'vendor', 'token-saver', f);
2045
+ if (!fs.existsSync(vp)) continue;
2046
+ try {
2047
+ const raw = fs.readFileSync(vp, 'utf8');
2048
+ vendored = f === 'package.json'
2049
+ ? JSON.parse(raw).version
2050
+ : (raw.match(/^\s*version\s*=\s*["']?([\w.\-+]+)/m) || [])[1] || raw.trim().split('\n')[0];
2051
+ } catch (e) { logDebug(e, `token-saver version from ${f}`); }
2052
+ if (vendored) break;
2053
+ }
2054
+ console.log(` • ${colors.bold}${mod.name.padEnd(35)}${colors.reset} ➔ ${colors.green}✅ ${vendored ? `v${vendored} ` : ''}(Integrated)${colors.reset}`);
1953
2055
  } else {
1954
2056
  console.log(` • ${colors.bold}${mod.name.padEnd(35)}${colors.reset} ➔ ${colors.dim}⚪ Optional / Not downloaded${colors.reset}`);
1955
2057
  }
@@ -2115,7 +2217,13 @@ async function installMcpsForTarget(paths, ctx) {
2115
2217
  if (selectedMcps.includes(CORE_MCP)) {
2116
2218
  const membMcpTarget = path.join(mcpCodeTarget, CORE_MCP);
2117
2219
  installStep(`download/update ${CORE_MCP} from npm`, () => {
2118
- downloadOrUpdateModule('@hybridlabor-api/memb', membMcpTarget, 'memB MCP');
2220
+ // installStep only catches a throw, and downloadOrUpdateModule
2221
+ // reports failure by returning false -- so without this the hint
2222
+ // below never printed and the MCP config was written pointing at a
2223
+ // path that had not been populated.
2224
+ if (!downloadOrUpdateModule('@hybridlabor-api/memb', membMcpTarget, 'memB MCP')) {
2225
+ throw new Error('npm pack did not produce the memb-mcp payload');
2226
+ }
2119
2227
  }, 'memb-mcp installation skipped; MCP config may reference a missing path.');
2120
2228
  }
2121
2229
  log.step(`Installed selected MCP servers to ${mcpCodeTarget}`);
@@ -2128,7 +2236,11 @@ async function installMcpsForTarget(paths, ctx) {
2128
2236
  const ok = runNpmWithRetry('npm install --no-audit --no-fund', { cwd: targetFolder }, `npm install for ${mcpFolder}`);
2129
2237
  if (ok && (fs.existsSync(path.join(targetFolder, 'tsconfig.json')) || fs.existsSync(path.join(targetFolder, 'tsconfig.build.json')))) {
2130
2238
  log.step(`Compiling TypeScript for ${mcpFolder}...`);
2131
- runNpmWithRetry('npm run build', { cwd: targetFolder }, `npm run build for ${mcpFolder}`);
2239
+ const built = runNpmWithRetry('npm run build', { cwd: targetFolder }, `npm run build for ${mcpFolder}`);
2240
+ // runNpmWithRetry already warned with the failure detail; this
2241
+ // just makes the consequence visible now instead of only later,
2242
+ // when the config-entry check below finds the missing dist file.
2243
+ if (!built) log.warn(`${mcpFolder}: build failed -- its MCP config entry will be dropped if the expected output is missing.`);
2132
2244
  }
2133
2245
  }
2134
2246
  }
@@ -2160,7 +2272,11 @@ async function installMcpsForTarget(paths, ctx) {
2160
2272
  ? path.join(membMcpFolder, '.venv', 'Scripts', 'python.exe')
2161
2273
  : path.join(membMcpFolder, '.venv', 'bin', 'python');
2162
2274
 
2163
- if (!fs.existsSync(venvPython)) {
2275
+ // The fs.* writes are covered by the global dry-run patch, but
2276
+ // child processes are not -- these two would really create a venv.
2277
+ if (DRY_RUN) {
2278
+ log.step(`[dry-run] would create the memb-mcp venv in ${membMcpFolder}`);
2279
+ } else if (!fs.existsSync(venvPython)) {
2164
2280
  try {
2165
2281
  execSync(`uv venv --seed .venv`, { cwd: membMcpFolder, stdio: 'ignore' });
2166
2282
  } catch (e1) {
@@ -2178,12 +2294,12 @@ async function installMcpsForTarget(paths, ctx) {
2178
2294
  }
2179
2295
 
2180
2296
  const pythonMcps = [
2181
- { folder: 'golem-rhino-mcp', args: ['-m', 'mcp_server', '--help'] },
2182
- { folder: 'davinci-mcp-professional', args: ['main.py', '--help'] },
2297
+ { folder: 'golem-rhino-mcp', args: ['-m', 'mcp_server', '--help'], uvArgs: true },
2298
+ { folder: 'davinci-mcp-professional', args: ['main.py', '--help'], uvArgs: true },
2183
2299
  { folder: 'davinci-resolve-mcp-free', args: ['-r', 'requirements.txt', 'src/resolve_mcp_bridge.py', '--help'], uvArgs: true },
2184
- { folder: 'blender-mcp', args: ['-m', 'blender_mcp.server', '--help'] },
2300
+ { folder: 'blender-mcp', args: ['-m', 'blender_mcp.server', '--help'], uvArgs: true },
2185
2301
  { folder: 'vectorworks-mcp', args: ['-r', 'requirements.txt', 'app/mcp_server.py', '--help'], uvArgs: true },
2186
- { folder: 'windows-computer-use-mcp', args: ['run_server.py', '--help'] }
2302
+ { folder: 'windows-computer-use-mcp', args: ['run_server.py', '--help'], uvArgs: true }
2187
2303
  ];
2188
2304
  for (const mcp of pythonMcps.filter(m => selectedMcps.includes(m.folder))) {
2189
2305
  const targetFolder = path.join(mcpCodeTarget, mcp.folder);
@@ -2193,7 +2309,18 @@ async function installMcpsForTarget(paths, ctx) {
2193
2309
  log.step(`[dry-run] would pre-warm Python deps for ${mcp.folder}`);
2194
2310
  continue;
2195
2311
  }
2196
- spawnSync('uv', prewarmArgs, { cwd: targetFolder, stdio: 'ignore' });
2312
+ const result = spawnSync('uv', prewarmArgs, { cwd: targetFolder, stdio: 'ignore' });
2313
+ if (result.error) {
2314
+ // uv itself is missing or unrunnable. That is one machine-level
2315
+ // fact, not six per-MCP failures — say it once and stop trying.
2316
+ log.warn(`uv could not be run (${result.error.message}) — skipping the Python prewarm for the remaining MCPs.`);
2317
+ break;
2318
+ }
2319
+ if (result.status === 0) {
2320
+ log.step(`Pre-warmed Python deps for ${mcp.folder}`);
2321
+ } else {
2322
+ log.warn(`Prewarm failed for ${mcp.folder}: uv exited ${result.status}. The MCP is configured but its dependencies may not resolve.`);
2323
+ }
2197
2324
  }
2198
2325
  }
2199
2326
 
@@ -2216,6 +2343,28 @@ async function installMcpsForTarget(paths, ctx) {
2216
2343
  const parsedMcpConfig = JSON.parse(mcpConfigStr);
2217
2344
  const finalMcpServers = {};
2218
2345
  const availableFolders = fs.readdirSync(mcpSrcDir);
2346
+
2347
+ // A node MCP's build can fail (see above) or simply never run for a
2348
+ // dist-based server that was skipped -- either way an entry pointing
2349
+ // at a file that was never produced fails at MCP launch, far from the
2350
+ // real cause. Derive the check from each entry's own args (node's
2351
+ // first arg is always its entry script) instead of a hardcoded list of
2352
+ // MCP names, so a fifth dist-based entry added later is covered too.
2353
+ if (!DRY_RUN) {
2354
+ for (const [key, val] of Object.entries(parsedMcpConfig.mcpServers)) {
2355
+ if (val.command !== 'node' || !Array.isArray(val.args) || !val.args[0]) continue;
2356
+ const entryArg = val.args[0];
2357
+ if (!entryArg.startsWith('__MCPS_DIR__/')) continue;
2358
+ const relPath = entryArg.slice('__MCPS_DIR__/'.length);
2359
+ const mcpFolder = relPath.split('/')[0];
2360
+ if (!selectedMcps.includes(mcpFolder)) continue; // not installed at all, already handled below
2361
+ if (!fs.existsSync(path.join(mcpCodeTarget, relPath))) {
2362
+ log.warn(`${mcpFolder}: build produced no ${relPath.slice(mcpFolder.length + 1)} -- not registering ${key}`);
2363
+ skippedMcpConfigKeys.push(key);
2364
+ }
2365
+ }
2366
+ }
2367
+
2219
2368
  for (const [key, val] of Object.entries(parsedMcpConfig.mcpServers)) {
2220
2369
  let keep = !skippedMcpConfigKeys.includes(key);
2221
2370
  for (const available of availableFolders) {
@@ -2717,7 +2866,7 @@ function injectHarnessRules() {
2717
2866
  log.step(`Copied ${dir} to ${targetPath}`);
2718
2867
  }
2719
2868
  });
2720
- mergeBdbSettingsHooks(path.join(homeDir, '.claude', 'settings.json'));
2869
+ installGlobalHooks();
2721
2870
  }, '');
2722
2871
 
2723
2872
  installStep('sync global .agents/', () => {
@@ -2731,6 +2880,22 @@ function injectHarnessRules() {
2731
2880
  }
2732
2881
  }
2733
2882
 
2883
+ // Deliver the hook scripts to ~/.claude/hooks/ and wire them in settings.json.
2884
+ // Both the full install and Quick Update funnel through here. Quick Update used
2885
+ // to do neither: it refreshes skills and submodules, but hooks are harness
2886
+ // plumbing rather than skills, so an existing install updating to a version
2887
+ // that adds or changes a hook silently never received it -- which is exactly
2888
+ // what happened to memb-inject.mjs in v4.4.0, on the machines that needed it
2889
+ // most (a fresh install got it; every machine that already had AOS did not).
2890
+ function installGlobalHooks() {
2891
+ const hooksSrc = path.join(srcDir, '.claude', 'hooks');
2892
+ if (fs.existsSync(hooksSrc)) {
2893
+ copyDirRecursiveSync(hooksSrc, path.join(homeDir, '.claude', 'hooks'));
2894
+ log.step(`Installed hooks to ${path.join(homeDir, '.claude', 'hooks')}`);
2895
+ }
2896
+ mergeBdbSettingsHooks(path.join(homeDir, '.claude', 'settings.json'));
2897
+ }
2898
+
2734
2899
  // Merge the BDB hooks -- the two gates plus the memB ambient-memory hook --
2735
2900
  // into a Claude Code settings.json without clobbering
2736
2901
  // user-owned keys (v3.13 audit BLOCKER-3: the harness-dir copy used to
@@ -2924,10 +3089,13 @@ async function promptOptionalModules(installedModules) {
2924
3089
  { id: 'synapse', name: 'BDB Synapse (3D Codebase Visualizer)', fn: installSynapse },
2925
3090
  { id: 'memb', name: 'memB Vector Engine (Local Semantic Memory)', fn: () => installMemB(true) },
2926
3091
  { id: 'remote', name: 'BDB OS Remote Gateway (Zero-Trust Tailscale Multiplexer)', fn: installOSRemoteGateway },
2927
- // bdb-os-agent-workspace: temporarily disabled -- its main branch (both local
2928
- // and on GitHub) is frozen while the real work sits on unmerged WIP branches,
2929
- // so its published npm version doesn't reliably reflect what "latest" means
2930
- // right now. Re-add here once that's resolved.
3092
+ // Offered only where it can actually run -- see aoSupportedHere(). The
3093
+ // predecessor, @hybridlabor-api/bdb-os-agent-workspace, must never come
3094
+ // back: its repository is archived and its last release predates the
3095
+ // archiving, so it hands out the build with the CDC loop defect.
3096
+ ...(aoSupportedHere()
3097
+ ? [{ id: 'ao', name: 'AO Agent Orchestrator (Session telemetry & WebUI)', fn: installOSAgentWorkspace }]
3098
+ : []),
2931
3099
  { id: 'creator', name: 'BDB Creator Extension (Generative 3D, Video & ComfyUI)', fn: installCreatorExtension },
2932
3100
  { id: 'installer', name: 'BDB Dev Tool Installer (Interactive Hub & CLI Launcher)', fn: installDevToolInstaller }
2933
3101
  ];
@@ -3298,6 +3466,17 @@ async function runQuickUpdate(installState) {
3298
3466
  syncSkillsToGlobalHarnesses(excludeSkills);
3299
3467
  s.stop('Skills refreshed');
3300
3468
 
3469
+ // Everything injectHarnessRules() delivers -- GEMINI.md, the dispatcher
3470
+ // workflows the skills point at, the compiled subagent definitions, the
3471
+ // harness rule files and the gate + memory hooks -- used to be
3472
+ // fresh-install-only. v4.4.1 split just the hooks out of it for Quick
3473
+ // Update; the rest stayed behind, so a machine that only ever quick-updates
3474
+ // kept running first-install agent definitions and dispatcher scripts
3475
+ // forever. Same defect as the hook, one layer up. Run the whole thing.
3476
+ installStep('refresh harness rules, workflows, agents and hooks', () => {
3477
+ injectHarnessRules();
3478
+ }, 'Harness files keep whatever version this machine already had.');
3479
+
3301
3480
  // OpenWiki setup only ever ran on a brand-new install (main()'s fresh-install
3302
3481
  // branch below) -- an existing install running Quick Update never got offered
3303
3482
  // it and never had its daemon schedule refreshed. promptCredentials() already
@@ -3317,7 +3496,7 @@ async function runQuickUpdate(installState) {
3317
3496
  if (subId === 'synapse') await installSynapse();
3318
3497
  else if (subId === 'memb') await installMemB(false);
3319
3498
  else if (subId === 'remote') await installOSRemoteGateway();
3320
- // 'ao' (bdb-os-agent-workspace) intentionally skipped here even for
3499
+ // 'ao' intentionally skipped here even for
3321
3500
  // existing installs -- see promptOptionalModules() for why.
3322
3501
  else if (subId === 'creator') await installCreatorExtension();
3323
3502
  else if (subId === 'installer') await installDevToolInstaller();
@@ -3358,7 +3537,10 @@ async function main() {
3358
3537
  console.log(buildTelemetryCard({ installState, detections, daemonStatus }));
3359
3538
 
3360
3539
  if (DRY_RUN) {
3361
- log.warn('DRY-RUN MODE active - no files will be modified, no commands executed.');
3540
+ // Precise on purpose: version comparison still queries the registry, and
3541
+ // npm writes its own cache and logs under ~/.npm doing so. Nothing AOS
3542
+ // owns is touched -- but claiming "no commands executed" was not true.
3543
+ log.warn('DRY-RUN MODE active - nothing AOS owns is written. Read-only registry lookups still run (npm caches under ~/.npm).');
3362
3544
  }
3363
3545
  if (isAutoYes) {
3364
3546
  log.warn('Non-interactive/auto mode (-y): all defaults are accepted automatically.');
@@ -3450,6 +3632,8 @@ async function main() {
3450
3632
  let wantsUniversal = true;
3451
3633
  let specificPlatforms = ['1'];
3452
3634
 
3635
+ markPlatformsExplicit(PLATFORMS_ARG || []);
3636
+
3453
3637
  if (PLATFORMS_ARG) {
3454
3638
  // Explicit targets win over both the menu and the auto-yes default.
3455
3639
  // '0' means universal; anything else means exactly those targets, so
@@ -3489,6 +3673,9 @@ async function main() {
3489
3673
  });
3490
3674
  if (sel === BACK) return 'back';
3491
3675
  ctx.selectedPlatforms = sel;
3676
+ // A harness the user names counts as present even when no binary
3677
+ // or app bundle turns up for it.
3678
+ markPlatformsExplicit(sel);
3492
3679
  };
3493
3680
 
3494
3681
  const stepMode = async () => {
@@ -3702,7 +3889,6 @@ async function main() {
3702
3889
  const justInstalled = await promptOptionalModules(installedModulesForPrompt) || [];
3703
3890
 
3704
3891
  await promptMemBIngestion(path.join(primaryTarget.targetMcpDir, 'mcps'));
3705
- await promptEcosystemHealthScheduler();
3706
3892
 
3707
3893
  const alreadyHandled = justInstalled.map(id => MODULE_ID_TO_DAEMON_NAME[id]).filter(Boolean);
3708
3894
  await reloadDaemons(alreadyHandled);
@@ -3727,7 +3913,12 @@ if (require.main === module) {
3727
3913
 
3728
3914
  // Exported for tests -- requiring installer.js must not launch the TUI.
3729
3915
  module.exports = {
3916
+ installOSAgentWorkspace,
3917
+ downloadOrUpdateModule,
3918
+ detectPlatforms,
3919
+ markPlatformsExplicit,
3730
3920
  mergeBdbSettingsHooks,
3921
+ installGlobalHooks,
3731
3922
  installProjectHarness,
3732
3923
  mirrorMcpServersTo,
3733
3924
  // Manifest store (exported for verification tests)