@hybridlabor-api/aos 4.4.1 → 4.4.2-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (305) hide show
  1. package/.agents/skills/firecrawl/SKILL.md +149 -0
  2. package/.agents/skills/firecrawl/rules/install.md +82 -0
  3. package/.agents/skills/firecrawl/rules/security.md +26 -0
  4. package/.agents/skills/firecrawl-agent/SKILL.md +58 -0
  5. package/.agents/skills/firecrawl-build/SKILL.md +39 -0
  6. package/.agents/skills/firecrawl-build-interact/SKILL.md +68 -0
  7. package/.agents/skills/firecrawl-build-onboarding/SKILL.md +103 -0
  8. package/.agents/skills/firecrawl-build-onboarding/references/auth-flow.md +39 -0
  9. package/.agents/skills/firecrawl-build-onboarding/references/project-setup.md +20 -0
  10. package/.agents/skills/firecrawl-build-onboarding/references/sdk-installation.md +17 -0
  11. package/.agents/skills/firecrawl-build-scrape/SKILL.md +69 -0
  12. package/.agents/skills/firecrawl-build-search/SKILL.md +69 -0
  13. package/.agents/skills/firecrawl-crawl/SKILL.md +59 -0
  14. package/.agents/skills/firecrawl-download/SKILL.md +70 -0
  15. package/.agents/skills/firecrawl-interact/SKILL.md +84 -0
  16. package/.agents/skills/firecrawl-map/SKILL.md +51 -0
  17. package/.agents/skills/firecrawl-scrape/SKILL.md +69 -0
  18. package/.agents/skills/firecrawl-search/SKILL.md +60 -0
  19. package/CLAUDE.md +573 -0
  20. package/README.de.md +0 -8
  21. package/README.md +0 -8
  22. package/README.pt.md +0 -8
  23. package/assets/brand/ao-ant-mark.svg +91 -0
  24. package/assets/brand/bdb-core-mark.svg +34 -0
  25. package/assets/brand/heimdall-guardian-mark.svg +16 -0
  26. package/assets/brand/memb-crystal-mark.svg +18 -0
  27. package/bin/aos-config.mjs +89 -0
  28. package/bin/aos-dashboard.mjs +608 -0
  29. package/bin/aos-uninstall.mjs +227 -0
  30. package/docs/sessions/audit-2026-09-14-silent-failures.md +114 -0
  31. package/docs/skills_table.md +0 -3
  32. package/installer.js +387 -237
  33. package/mcps/RhinoMCP/cc-plugin/.claude/settings.json +10 -0
  34. package/mcps/after-effects-mcp/build/index.js +840 -0
  35. package/mcps/after-effects-mcp/build/scripts/applyEffect.jsx +153 -0
  36. package/mcps/after-effects-mcp/build/scripts/applyEffectTemplate.jsx +218 -0
  37. package/mcps/after-effects-mcp/build/scripts/createComposition.jsx +71 -0
  38. package/mcps/after-effects-mcp/build/scripts/createShapeLayer.jsx +147 -0
  39. package/mcps/after-effects-mcp/build/scripts/createSolidLayer.jsx +114 -0
  40. package/mcps/after-effects-mcp/build/scripts/createTextLayer.jsx +115 -0
  41. package/mcps/after-effects-mcp/build/scripts/getLayerInfo.jsx +192 -0
  42. package/mcps/after-effects-mcp/build/scripts/getProjectInfo.jsx +90 -0
  43. package/mcps/after-effects-mcp/build/scripts/listCompositions.jsx +50 -0
  44. package/mcps/after-effects-mcp/build/scripts/mcp-bridge-auto.jsx +1773 -0
  45. package/mcps/after-effects-mcp/build/scripts/setLayerProperties.jsx +160 -0
  46. package/mcps/computer-use-mcp/dist/client.d.ts +150 -0
  47. package/mcps/computer-use-mcp/dist/client.js +136 -0
  48. package/mcps/computer-use-mcp/dist/entrypoint.d.ts +16 -0
  49. package/mcps/computer-use-mcp/dist/entrypoint.js +26 -0
  50. package/mcps/computer-use-mcp/dist/native.d.ts +212 -0
  51. package/mcps/computer-use-mcp/dist/native.js +50 -0
  52. package/mcps/computer-use-mcp/dist/server.d.ts +32 -0
  53. package/mcps/computer-use-mcp/dist/server.js +342 -0
  54. package/mcps/computer-use-mcp/dist/session.d.ts +101 -0
  55. package/mcps/computer-use-mcp/dist/session.js +2372 -0
  56. package/package.json +13 -4
  57. package/skills/global_config/aos-project-init/SKILL.md +97 -130
  58. package/skills/global_config/aos-project-init/assets/openwikiignore.template +62 -0
  59. package/skills/global_config/aos-project-init/scripts/aos-project-doctor.mjs +38 -1
  60. package/skills/global_config/aos-setup/SKILL.md +98 -8
  61. package/skills/global_config/aos-setup/scripts/aos-doctor.mjs +21 -2
  62. package/skills/global_config/ask-tim/SKILL.md +2 -7
  63. package/skills/global_config/openwiki-skill/scripts/openwiki_daemon.py +65 -12
  64. package/bin/setup-saas.mjs +0 -792
  65. package/mcps/blender-mcp-server/.dockerignore +0 -19
  66. package/mcps/blender-mcp-server/.github/workflows/ci.yml +0 -67
  67. package/mcps/blender-mcp-server/.github/workflows/publish-pypi.yml +0 -54
  68. package/mcps/blender-mcp-server/CHANGELOG.md +0 -64
  69. package/mcps/blender-mcp-server/CONTRIBUTING.md +0 -107
  70. package/mcps/blender-mcp-server/Dockerfile +0 -19
  71. package/mcps/blender-mcp-server/LICENSE +0 -21
  72. package/mcps/blender-mcp-server/README.md +0 -409
  73. package/mcps/blender-mcp-server/addon/__init__.py +0 -1440
  74. package/mcps/blender-mcp-server/addon/models.py +0 -223
  75. package/mcps/blender-mcp-server/pyproject.toml +0 -120
  76. package/mcps/blender-mcp-server/scripts/blender_bridge_request.py +0 -71
  77. package/mcps/blender-mcp-server/scripts/blender_create_test_cube.py +0 -49
  78. package/mcps/blender-mcp-server/scripts/blender_scene_info.py +0 -38
  79. package/mcps/blender-mcp-server/scripts/build_addon_zip.sh +0 -29
  80. package/mcps/blender-mcp-server/scripts/demos/README.md +0 -186
  81. package/mcps/blender-mcp-server/scripts/demos/dam_break_scene.py +0 -276
  82. package/mcps/blender-mcp-server/scripts/demos/open_dam_break_gui.py +0 -33
  83. package/mcps/blender-mcp-server/scripts/demos/pipe_movement_study.py +0 -518
  84. package/mcps/blender-mcp-server/scripts/demos/pipe_pressure_failure_scene.py +0 -806
  85. package/mcps/blender-mcp-server/scripts/demos/procedural_dam_break_scene.py +0 -600
  86. package/mcps/blender-mcp-server/scripts/demos/run_dam_break.py +0 -510
  87. package/mcps/blender-mcp-server/scripts/launch_blender_gui.py +0 -41
  88. package/mcps/blender-mcp-server/scripts/library/README.md +0 -64
  89. package/mcps/blender-mcp-server/scripts/library/apply_transforms.py +0 -46
  90. package/mcps/blender-mcp-server/scripts/library/camera.py +0 -51
  91. package/mcps/blender-mcp-server/scripts/library/collections.py +0 -65
  92. package/mcps/blender-mcp-server/scripts/library/create_mesh.py +0 -41
  93. package/mcps/blender-mcp-server/scripts/library/effector.py +0 -48
  94. package/mcps/blender-mcp-server/scripts/library/fluid_domain.py +0 -80
  95. package/mcps/blender-mcp-server/scripts/library/fluid_inflow.py +0 -98
  96. package/mcps/blender-mcp-server/scripts/library/frame_range.py +0 -35
  97. package/mcps/blender-mcp-server/scripts/library/keyframes.py +0 -59
  98. package/mcps/blender-mcp-server/scripts/library/rigid_body.py +0 -52
  99. package/mcps/blender-mcp-server/scripts/library/save_blend.py +0 -40
  100. package/mcps/blender-mcp-server/scripts/server_start.sh +0 -26
  101. package/mcps/blender-mcp-server/src/blender_mcp_server/__init__.py +0 -3
  102. package/mcps/blender-mcp-server/src/blender_mcp_server/headless.py +0 -330
  103. package/mcps/blender-mcp-server/src/blender_mcp_server/server.py +0 -606
  104. package/mcps/davinci-mcp-professional/.bandit +0 -35
  105. package/mcps/davinci-mcp-professional/.claude/commands/security-review.md +0 -191
  106. package/mcps/davinci-mcp-professional/.claude/settings.json +0 -1
  107. package/mcps/davinci-mcp-professional/.editorconfig +0 -27
  108. package/mcps/davinci-mcp-professional/.gitattributes +0 -207
  109. package/mcps/davinci-mcp-professional/.github/CODEOWNERS +0 -2
  110. package/mcps/davinci-mcp-professional/.github/ISSUE_TEMPLATE/bug_report.md +0 -33
  111. package/mcps/davinci-mcp-professional/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
  112. package/mcps/davinci-mcp-professional/.github/PULL_REQUEST_TEMPLATE.md +0 -20
  113. package/mcps/davinci-mcp-professional/.github/SECURITY.md +0 -97
  114. package/mcps/davinci-mcp-professional/.github/dependabot.yml +0 -27
  115. package/mcps/davinci-mcp-professional/.github/workflows/ci.yml +0 -39
  116. package/mcps/davinci-mcp-professional/ATTRIBUTION.md +0 -4
  117. package/mcps/davinci-mcp-professional/AUTHORS.md +0 -9
  118. package/mcps/davinci-mcp-professional/BUGS.md +0 -55
  119. package/mcps/davinci-mcp-professional/CLAUDE.md +0 -229
  120. package/mcps/davinci-mcp-professional/CONTRIBUTING.md +0 -51
  121. package/mcps/davinci-mcp-professional/COPYING.md +0 -228
  122. package/mcps/davinci-mcp-professional/Doxyfile +0 -379
  123. package/mcps/davinci-mcp-professional/README.md +0 -143
  124. package/mcps/davinci-mcp-professional/SECURITY.md +0 -40
  125. package/mcps/davinci-mcp-professional/USING.md +0 -301
  126. package/mcps/davinci-mcp-professional/VERSION.md +0 -13
  127. package/mcps/davinci-mcp-professional/build.py +0 -169
  128. package/mcps/davinci-mcp-professional/claude_desktop_config_template.json +0 -11
  129. package/mcps/davinci-mcp-professional/hooks/ci-check.sh +0 -29
  130. package/mcps/davinci-mcp-professional/hooks/pre-push +0 -46
  131. package/mcps/davinci-mcp-professional/main.py +0 -40
  132. package/mcps/davinci-mcp-professional/mcp_server.py +0 -30
  133. package/mcps/davinci-mcp-professional/pyproject.toml +0 -115
  134. package/mcps/davinci-mcp-professional/security_audit.py +0 -379
  135. package/mcps/davinci-mcp-professional/src/davinci_mcp/__init__.py +0 -19
  136. package/mcps/davinci-mcp-professional/src/davinci_mcp/cli.py +0 -138
  137. package/mcps/davinci-mcp-professional/src/davinci_mcp/resolve_client.py +0 -342
  138. package/mcps/davinci-mcp-professional/src/davinci_mcp/resources/__init__.py +0 -60
  139. package/mcps/davinci-mcp-professional/src/davinci_mcp/server.py +0 -185
  140. package/mcps/davinci-mcp-professional/src/davinci_mcp/tools/__init__.py +0 -146
  141. package/mcps/davinci-mcp-professional/src/davinci_mcp/types.py +0 -125
  142. package/mcps/davinci-mcp-professional/src/davinci_mcp/utils/__init__.py +0 -19
  143. package/mcps/davinci-mcp-professional/src/davinci_mcp/utils/platform.py +0 -239
  144. package/mcps/davinci-mcp-professional/test.py +0 -186
  145. package/mcps/davinci-mcp-professional/uv.lock +0 -2073
  146. package/mcps/davinci-resolve-mcp/.claude/skills/media-analysis.md +0 -44
  147. package/mcps/davinci-resolve-mcp/.clinerules +0 -17
  148. package/mcps/davinci-resolve-mcp/.cursorrules +0 -17
  149. package/mcps/davinci-resolve-mcp/.github/copilot-instructions.md +0 -17
  150. package/mcps/davinci-resolve-mcp/.github/workflows/npm-publish.yml +0 -109
  151. package/mcps/davinci-resolve-mcp/.windsurfrules +0 -17
  152. package/mcps/davinci-resolve-mcp/AGENTS.md +0 -114
  153. package/mcps/davinci-resolve-mcp/CHANGELOG.md +0 -2648
  154. package/mcps/davinci-resolve-mcp/CLAUDE.md +0 -15
  155. package/mcps/davinci-resolve-mcp/LICENSE +0 -21
  156. package/mcps/davinci-resolve-mcp/README.md +0 -173
  157. package/mcps/davinci-resolve-mcp/SECURITY.md +0 -53
  158. package/mcps/davinci-resolve-mcp/bin/davinci-resolve-mcp.mjs +0 -450
  159. package/mcps/davinci-resolve-mcp/examples/README.md +0 -53
  160. package/mcps/davinci-resolve-mcp/examples/markers/README.md +0 -81
  161. package/mcps/davinci-resolve-mcp/examples/media/README.md +0 -94
  162. package/mcps/davinci-resolve-mcp/examples/timeline/README.md +0 -98
  163. package/mcps/davinci-resolve-mcp/install.py +0 -1687
  164. package/mcps/davinci-resolve-mcp/package.json +0 -52
  165. package/mcps/davinci-resolve-mcp/release-notes/v2.22.0.md +0 -51
  166. package/mcps/davinci-resolve-mcp/release-notes/v2.23.0.md +0 -51
  167. package/mcps/davinci-resolve-mcp/release-notes/v2.23.1.md +0 -35
  168. package/mcps/davinci-resolve-mcp/release-notes/v2.24.1.md +0 -32
  169. package/mcps/davinci-resolve-mcp/scripts/audit_api_parity.py +0 -275
  170. package/mcps/davinci-resolve-mcp/scripts/audit_readwrite_symmetry.py +0 -80
  171. package/mcps/davinci-resolve-mcp/scripts/doctor.py +0 -227
  172. package/mcps/davinci-resolve-mcp/scripts/gen_api_limitations.py +0 -144
  173. package/mcps/davinci-resolve-mcp/scripts/live_media_analysis_polish_probe.py +0 -65
  174. package/mcps/davinci-resolve-mcp/scripts/measure_bridge_cost.py +0 -65
  175. package/mcps/davinci-resolve-mcp/scripts/regen_panel_screenshots.py +0 -76
  176. package/mcps/davinci-resolve-mcp/src/__init__.py +0 -3
  177. package/mcps/davinci-resolve-mcp/src/analysis_dashboard.py +0 -15505
  178. package/mcps/davinci-resolve-mcp/src/batch_cli.py +0 -540
  179. package/mcps/davinci-resolve-mcp/src/control_panel.py +0 -13
  180. package/mcps/davinci-resolve-mcp/src/granular/__init__.py +0 -17
  181. package/mcps/davinci-resolve-mcp/src/granular/common.py +0 -741
  182. package/mcps/davinci-resolve-mcp/src/granular/folder.py +0 -420
  183. package/mcps/davinci-resolve-mcp/src/granular/gallery.py +0 -306
  184. package/mcps/davinci-resolve-mcp/src/granular/graph.py +0 -309
  185. package/mcps/davinci-resolve-mcp/src/granular/media_pool.py +0 -679
  186. package/mcps/davinci-resolve-mcp/src/granular/media_pool_item.py +0 -1020
  187. package/mcps/davinci-resolve-mcp/src/granular/media_storage.py +0 -179
  188. package/mcps/davinci-resolve-mcp/src/granular/project.py +0 -1654
  189. package/mcps/davinci-resolve-mcp/src/granular/resolve_control.py +0 -538
  190. package/mcps/davinci-resolve-mcp/src/granular/timeline.py +0 -1075
  191. package/mcps/davinci-resolve-mcp/src/granular/timeline_item.py +0 -2251
  192. package/mcps/davinci-resolve-mcp/src/resolve_mcp_server.py +0 -43
  193. package/mcps/davinci-resolve-mcp/src/server.py +0 -24116
  194. package/mcps/davinci-resolve-mcp/src/utils/__init__.py +0 -3
  195. package/mcps/davinci-resolve-mcp/src/utils/actor_identity.py +0 -46
  196. package/mcps/davinci-resolve-mcp/src/utils/analysis_caps.py +0 -678
  197. package/mcps/davinci-resolve-mcp/src/utils/analysis_memory.py +0 -755
  198. package/mcps/davinci-resolve-mcp/src/utils/analysis_runs.py +0 -307
  199. package/mcps/davinci-resolve-mcp/src/utils/analysis_store.py +0 -945
  200. package/mcps/davinci-resolve-mcp/src/utils/api_truth.py +0 -525
  201. package/mcps/davinci-resolve-mcp/src/utils/app_control.py +0 -319
  202. package/mcps/davinci-resolve-mcp/src/utils/audio_fairlight_live_probe.py +0 -263
  203. package/mcps/davinci-resolve-mcp/src/utils/brain_edits.py +0 -387
  204. package/mcps/davinci-resolve-mcp/src/utils/bridge_metrics.py +0 -76
  205. package/mcps/davinci-resolve-mcp/src/utils/cdl.py +0 -20
  206. package/mcps/davinci-resolve-mcp/src/utils/clip_query.py +0 -85
  207. package/mcps/davinci-resolve-mcp/src/utils/cloud_operations.py +0 -192
  208. package/mcps/davinci-resolve-mcp/src/utils/color_grade_live_probe.py +0 -444
  209. package/mcps/davinci-resolve-mcp/src/utils/contracts.py +0 -102
  210. package/mcps/davinci-resolve-mcp/src/utils/cut_ir.py +0 -115
  211. package/mcps/davinci-resolve-mcp/src/utils/dctl_templates.py +0 -368
  212. package/mcps/davinci-resolve-mcp/src/utils/deep_vision.py +0 -643
  213. package/mcps/davinci-resolve-mcp/src/utils/destructive_hook.py +0 -606
  214. package/mcps/davinci-resolve-mcp/src/utils/edit_engine.py +0 -737
  215. package/mcps/davinci-resolve-mcp/src/utils/embeddings.py +0 -922
  216. package/mcps/davinci-resolve-mcp/src/utils/entities.py +0 -579
  217. package/mcps/davinci-resolve-mcp/src/utils/extension_authoring_live_probe.py +0 -292
  218. package/mcps/davinci-resolve-mcp/src/utils/failure_tracker.py +0 -119
  219. package/mcps/davinci-resolve-mcp/src/utils/fuse_templates.py +0 -1968
  220. package/mcps/davinci-resolve-mcp/src/utils/fusion_composition_live_probe.py +0 -284
  221. package/mcps/davinci-resolve-mcp/src/utils/fusion_group_settings.py +0 -334
  222. package/mcps/davinci-resolve-mcp/src/utils/layout_presets.py +0 -333
  223. package/mcps/davinci-resolve-mcp/src/utils/mcp_stdio.py +0 -32
  224. package/mcps/davinci-resolve-mcp/src/utils/mcp_transport.py +0 -128
  225. package/mcps/davinci-resolve-mcp/src/utils/media_analysis.py +0 -7690
  226. package/mcps/davinci-resolve-mcp/src/utils/media_analysis_jobs.py +0 -805
  227. package/mcps/davinci-resolve-mcp/src/utils/media_pool_changes.py +0 -121
  228. package/mcps/davinci-resolve-mcp/src/utils/media_pool_ingest_live_probe.py +0 -592
  229. package/mcps/davinci-resolve-mcp/src/utils/multicam.py +0 -393
  230. package/mcps/davinci-resolve-mcp/src/utils/object_inspection.py +0 -301
  231. package/mcps/davinci-resolve-mcp/src/utils/page_lock.py +0 -75
  232. package/mcps/davinci-resolve-mcp/src/utils/platform.py +0 -157
  233. package/mcps/davinci-resolve-mcp/src/utils/proc.py +0 -26
  234. package/mcps/davinci-resolve-mcp/src/utils/project_cleanup.py +0 -73
  235. package/mcps/davinci-resolve-mcp/src/utils/project_lifecycle_live_probe.py +0 -376
  236. package/mcps/davinci-resolve-mcp/src/utils/project_lint.py +0 -131
  237. package/mcps/davinci-resolve-mcp/src/utils/project_properties.py +0 -601
  238. package/mcps/davinci-resolve-mcp/src/utils/project_spec.py +0 -459
  239. package/mcps/davinci-resolve-mcp/src/utils/readback.py +0 -91
  240. package/mcps/davinci-resolve-mcp/src/utils/render_deliver_live_probe.py +0 -384
  241. package/mcps/davinci-resolve-mcp/src/utils/resolve_ai_governance.py +0 -214
  242. package/mcps/davinci-resolve-mcp/src/utils/resolve_ai_ledger.py +0 -244
  243. package/mcps/davinci-resolve-mcp/src/utils/resolve_busy.py +0 -171
  244. package/mcps/davinci-resolve-mcp/src/utils/resolve_connection.py +0 -77
  245. package/mcps/davinci-resolve-mcp/src/utils/review_annotation_live_probe.py +0 -352
  246. package/mcps/davinci-resolve-mcp/src/utils/script_templates.py +0 -1193
  247. package/mcps/davinci-resolve-mcp/src/utils/shot_relationships.py +0 -544
  248. package/mcps/davinci-resolve-mcp/src/utils/structural_diff.py +0 -175
  249. package/mcps/davinci-resolve-mcp/src/utils/sync_detection.py +0 -889
  250. package/mcps/davinci-resolve-mcp/src/utils/timeline_brain_db.py +0 -826
  251. package/mcps/davinci-resolve-mcp/src/utils/timeline_conform_live_probe.py +0 -280
  252. package/mcps/davinci-resolve-mcp/src/utils/timeline_kernel_live_probe.py +0 -1092
  253. package/mcps/davinci-resolve-mcp/src/utils/timeline_kernel_probe.py +0 -185
  254. package/mcps/davinci-resolve-mcp/src/utils/timeline_title_text.py +0 -87
  255. package/mcps/davinci-resolve-mcp/src/utils/timeline_versioning.py +0 -730
  256. package/mcps/davinci-resolve-mcp/src/utils/update_check.py +0 -685
  257. package/mcps/vectorworks-mcp/.dockerignore +0 -8
  258. package/mcps/vectorworks-mcp/Dockerfile +0 -28
  259. package/mcps/vectorworks-mcp/README.md +0 -156
  260. package/mcps/vectorworks-mcp/app/__init__.py +0 -2
  261. package/mcps/vectorworks-mcp/app/api.py +0 -52
  262. package/mcps/vectorworks-mcp/app/chunking.py +0 -56
  263. package/mcps/vectorworks-mcp/app/config.py +0 -29
  264. package/mcps/vectorworks-mcp/app/embeddings.py +0 -28
  265. package/mcps/vectorworks-mcp/app/indexer.py +0 -106
  266. package/mcps/vectorworks-mcp/app/main.py +0 -33
  267. package/mcps/vectorworks-mcp/app/mcp_server.py +0 -71
  268. package/mcps/vectorworks-mcp/app/search.py +0 -54
  269. package/mcps/vectorworks-mcp/app/utils.py +0 -14
  270. package/mcps/vectorworks-mcp/data/.gitkeep +0 -1
  271. package/mcps/vectorworks-mcp/data/vw-app-help/VW2022_PluginScript.htm +0 -95
  272. package/mcps/vectorworks-mcp/data/vw-app-help/VW2022_ScriptEditor.htm +0 -266
  273. package/mcps/vectorworks-mcp/data/vw-app-help/VW2022_Scripting.htm +0 -106
  274. package/mcps/vectorworks-mcp/data/vw-app-help/VW2023_CreatingScriptedPlugins.htm +0 -327
  275. package/mcps/vectorworks-mcp/data/vw-app-help/VW2024_RunningScripts.htm +0 -116
  276. package/mcps/vectorworks-mcp/data/vw-devwiki/VS_Function_Reference_category.html +0 -2129
  277. package/mcps/vectorworks-mcp/data/vw-jp-ref/ObjectsSymbols.html +0 -2035
  278. package/mcps/vectorworks-mcp/data/vw-jp-ref/ScriptFunctionReference.html +0 -29
  279. package/mcps/vectorworks-mcp/design.md +0 -57
  280. package/mcps/vectorworks-mcp/docker-compose.yml +0 -18
  281. package/mcps/vectorworks-mcp/index/.gitkeep +0 -1
  282. package/mcps/vectorworks-mcp/index/meta.jsonl +0 -3956
  283. package/mcps/vectorworks-mcp/index/vw.faiss +0 -0
  284. package/mcps/vectorworks-mcp/requirements.txt +0 -10
  285. package/mcps/vectorworks-mcp/scripts/fetch_docs_minimal.sh +0 -84
  286. package/mcps/vectorworks-mcp/scripts/fetch_github_vectorworks.sh +0 -85
  287. package/mcps/vectorworks-mcp/templates/index.html +0 -40
  288. package/scripts/ecosystem-health-audit.js +0 -257
  289. package/skills/bdb-dev-os-skill/SKILL.md +0 -49
  290. package/skills/bdb-dev-os-skill/package.json +0 -11
  291. package/skills/bdbsaastraining/SKILL.md +0 -316
  292. package/skills/bdbsaastraining/references/exam-pool.md +0 -313
  293. package/skills/bdbsaastraining/references/interaction-modes.md +0 -94
  294. package/skills/bdbsaastraining/references/track-a-ai-agent.md +0 -111
  295. package/skills/bdbsaastraining/references/track-b-wordpress.md +0 -111
  296. package/skills/bdbsaastraining/references/track-c-mailserver.md +0 -107
  297. package/skills/bdbsaastraining/references/track-d-debian.md +0 -109
  298. package/skills/bdbsaastraining/references/track-e-custom.md +0 -83
  299. package/skills/bdbsaastraining/scripts/build_profile.py +0 -529
  300. package/skills/bdbsaastraining/scripts/generate_certificate.py +0 -161
  301. package/skills/bdbsaastraining/scripts/preflight_check.sh +0 -150
  302. package/skills/bdbsaastraining/templates/certificate_template.html +0 -508
  303. package/skills/global_config/bdb-ecosystem-health/SKILL.md +0 -68
  304. package/skills/global_config/bdbsaas-ops/SKILL.md +0 -16
  305. package/skills/global_config/bdbsaashost/SKILL.md +0 -125
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);
@@ -524,7 +530,7 @@ function resolveFileConflict(sourcePath, targetPath, manifest, knownSourceHashes
524
530
  // not in refusing to write. A genuine local edit is recoverable from .bak
525
531
  // rather than silently outvoting the shipped version forever.
526
532
  if (!manifestEntry && !knownSourceHashes.has(diskHash)) {
527
- const bakPath = `${targetPath}.bak`;
533
+ const bakPath = `${targetPath}.${timestamp}.bak`;
528
534
  try { fs.copyFileSync(targetPath, bakPath); } catch (e) {
529
535
  log.warn(`[manifest] Could not create backup ${bakPath}: ${e.message}`);
530
536
  }
@@ -549,7 +555,7 @@ function resolveFileConflict(sourcePath, targetPath, manifest, knownSourceHashes
549
555
 
550
556
  // Case: ours + user-edited (manifest recorded our hash, but disk now differs).
551
557
  if (manifestEntry && diskHash !== manifestEntry.sha256) {
552
- const bakPath = `${targetPath}.bak`;
558
+ const bakPath = `${targetPath}.${timestamp}.bak`;
553
559
  try { fs.copyFileSync(targetPath, bakPath); } catch (e) {
554
560
  log.warn(`[manifest] Could not create backup ${bakPath}: ${e.message}`);
555
561
  }
@@ -637,66 +643,123 @@ function resolveMcpsArg(availableMcps) {
637
643
  return matched;
638
644
  }
639
645
 
640
- function detectPlatforms() {
641
- const detections = [];
642
-
643
- if (fs.existsSync(geminiDir)) {
644
- detections.push({ name: "Google Antigravity", path: geminiDir, key: "antigravity" });
645
- }
646
-
647
- const codexDir = path.join(homeDir, '.codex');
648
- if (fs.existsSync(codexDir) || fs.existsSync(path.join(codexDir, 'config.toml'))) {
649
- detections.push({ name: "ChatGPT Codex CLI", path: codexDir, key: "codex" });
650
- }
651
-
652
- const claudeCodeConfig = path.join(homeDir, '.claude.json');
653
- const claudeCodeDir = path.join(homeDir, '.claude');
654
- if (fs.existsSync(claudeCodeConfig) || fs.existsSync(claudeCodeDir)) {
655
- detections.push({ name: "Claude Code CLI", path: claudeCodeDir, key: "claudecode" });
656
- }
657
-
658
- const claudePath = process.platform === 'win32'
659
- ? path.join(process.env.APPDATA || homeDir, 'Claude')
660
- : path.join(homeDir, 'Library', 'Application Support', 'Claude');
661
- if (fs.existsSync(claudePath)) {
662
- detections.push({ name: "Claude Desktop", path: claudePath, key: "claudedesktop" });
663
- }
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
+ };
664
659
 
665
- const cursorPath = process.platform === 'win32'
666
- ? path.join(process.env.APPDATA || homeDir, 'Cursor')
667
- : path.join(homeDir, 'Library', 'Application Support', 'Cursor');
668
- if (fs.existsSync(cursorPath)) {
669
- 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);
670
665
  }
666
+ }
671
667
 
672
- const windsurfPath = process.platform === 'win32'
673
- ? path.join(process.env.APPDATA || homeDir, 'Windsurf')
674
- : path.join(homeDir, 'Library', 'Application Support', 'Windsurf');
675
- if (fs.existsSync(windsurfPath)) {
676
- detections.push({ name: "Windsurf IDE", path: windsurfPath, key: "windsurf" });
677
- }
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
+ }
678
675
 
679
- const vscodePath = process.platform === 'win32'
680
- ? path.join(process.env.APPDATA || homeDir, 'Code')
681
- : path.join(homeDir, 'Library', 'Application Support', 'Code');
682
- const rooPath = path.join(homeDir, '.roo');
683
- const clinePath = path.join(homeDir, '.cline');
684
- if (fs.existsSync(vscodePath) || fs.existsSync(rooPath) || fs.existsSync(clinePath)) {
685
- detections.push({ name: "Roo Code / Cline / VS Code", path: vscodePath, key: "vscode" });
686
- }
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
+ };
687
680
 
688
- const aiderConf = path.join(homeDir, '.aider.conf.yml');
689
- if (fs.existsSync(aiderConf) || fs.existsSync(path.join(homeDir, '.aider'))) {
690
- detections.push({ name: "Aider CLI", path: homeDir, key: "aider" });
691
- }
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
+ ];
692
754
 
693
- const opencodeDir = process.platform === 'win32'
694
- ? path.join(process.env.APPDATA || homeDir, 'opencode')
695
- : path.join(homeDir, '.config', 'opencode');
696
- if (fs.existsSync(opencodeDir) || fs.existsSync(path.join(homeDir, '.opencode'))) {
697
- 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
+ }
698
762
  }
699
-
700
763
  return detections;
701
764
  }
702
765
 
@@ -751,10 +814,9 @@ function detectInstallState() {
751
814
  { id: 'synapse', dir: path.join(basePath, 'bdb-synapse') },
752
815
  { id: 'memb', dir: path.join(basePath, 'memB') },
753
816
  { id: 'remote', dir: path.join(basePath, 'bdb-os-remote') },
754
- // bdb-os-agent-workspace: temporarily disabled -- see the comment in
755
- // promptOptionalModules() below for why. Not re-detected as a fresh
756
- // install; existing installs are still skipped explicitly in the
757
- // 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.
758
820
  { id: 'creator', dir: path.join(basePath, 'bdb-dev-creator-extension') },
759
821
  { id: 'installer', dir: path.join(basePath, 'bdb-dev-tool-installer') }
760
822
  ];
@@ -952,9 +1014,9 @@ function copyDirRecursiveSync(source, target, excludeList = [], manifest = null,
952
1014
  // root-level leaf skill dumps into the same flat target, so each subsequent
953
1015
  // one silently overwrites the previous one's SKILL.md -- found by tracing
954
1016
  // why skills/bdbrainstorm/ and skills/global_config/bdbrainstorm/ both
955
- // existed with different content; the root-level one (and 5 siblings:
956
- // bdbsaastraining, github-repo, memb-ingest, bdb-dev-os-skill,
957
- // 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.
958
1020
  function syncSkillEntry(fullPath, dirName, targetSkillDir, excludeSkills) {
959
1021
  const isLeafSkill = fs.existsSync(path.join(fullPath, 'SKILL.md'));
960
1022
  if (isLeafSkill) {
@@ -985,15 +1047,20 @@ function syncSkillsToGlobalHarnesses(excludeSkills = []) {
985
1047
  const skillsBase = path.join(srcDir, 'skills');
986
1048
  if (!fs.existsSync(skillsBase)) return;
987
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));
988
1055
  const extraSkillDestinations = [
989
- path.join(homeDir, '.claude', 'skills'),
990
- path.join(homeDir, '.agents', 'skills'),
991
- path.join(homeDir, '.codex', 'skills'),
992
- path.join(homeDir, '.cursor', 'skills'),
993
- path.join(homeDir, '.roo', 'skills')
994
- ];
995
-
996
- 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) {
997
1064
  try {
998
1065
  fs.mkdirSync(dest, { recursive: true });
999
1066
  const rawDirs = fs.readdirSync(skillsBase);
@@ -1414,23 +1481,34 @@ function checkModuleUpdate(pkgName, targetDir) {
1414
1481
  try {
1415
1482
  const localVer = JSON.parse(fs.readFileSync(modulePkgPath, 'utf8')).version;
1416
1483
  let remoteVer = null;
1484
+ let checkFailed = false;
1417
1485
  try {
1418
1486
  remoteVer = execSync(`npm view ${pkgName} version`, { stdio: ['ignore', 'pipe', 'ignore'], encoding: 'utf8', timeout: 4000 }).trim();
1419
- } 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
+ }
1420
1494
 
1421
1495
  if (remoteVer && localVer !== remoteVer) {
1422
- return { installed: true, updateAvailable: true, localVer, remoteVer };
1496
+ return { installed: true, updateAvailable: true, localVer, remoteVer, checkFailed };
1423
1497
  }
1424
- return { installed: true, updateAvailable: false, localVer, remoteVer };
1498
+ return { installed: true, updateAvailable: false, localVer, remoteVer, checkFailed };
1425
1499
  } catch (e) {
1426
- return { installed: true, updateAvailable: false, localVer: null, remoteVer: null };
1500
+ return { installed: true, updateAvailable: false, localVer: null, remoteVer: null, checkFailed: true };
1427
1501
  }
1428
1502
  }
1429
1503
 
1430
1504
  function downloadOrUpdateModule(pkgName, targetDir, displayName) {
1431
1505
  const status = checkModuleUpdate(pkgName, targetDir);
1432
1506
  if (status.installed && !status.updateAvailable) {
1433
- 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
+ }
1434
1512
  return true;
1435
1513
  }
1436
1514
 
@@ -1458,14 +1536,42 @@ function downloadOrUpdateModule(pkgName, targetDir, displayName) {
1458
1536
  const ok = runNpmWithRetry(`npm pack ${pkgName}@latest`, { stdio: 'ignore', cwd: targetDir }, `${displayName} download`);
1459
1537
  cleanNpmCacheOnWindows();
1460
1538
  const tarball = ok ? fs.readdirSync(targetDir).find(f => f.endsWith('.tgz')) : null;
1461
- if (tarball) {
1462
- execSync(`tar -xzf "${tarball}" --strip-components=1`, { stdio: 'ignore', cwd: targetDir });
1463
- fs.unlinkSync(path.join(targetDir, tarball));
1464
- s.stop(`${displayName} ready (v${status.remoteVer || 'latest'})`);
1465
- return true;
1466
- } else {
1467
- 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
+ }
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;
1468
1570
  }
1571
+ fs.rmSync(retiredDir, { recursive: true, force: true });
1572
+
1573
+ s.stop(`${displayName} ready (v${status.remoteVer || 'latest'})`);
1574
+ return true;
1469
1575
  } catch (e) {
1470
1576
  s.stop(`Failed downloading ${displayName}: ${e.message}`);
1471
1577
  return false;
@@ -1485,7 +1591,14 @@ async function installMemB(interactive) {
1485
1591
  }));
1486
1592
  }
1487
1593
  const membDir = path.join(moduleBasePath(), 'memB');
1488
- 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
+ }
1489
1602
  if (DRY_RUN) {
1490
1603
  log.step('[dry-run] would bootstrap memB venv + pip requirements');
1491
1604
  return;
@@ -1597,7 +1710,10 @@ async function installMemB(interactive) {
1597
1710
 
1598
1711
  async function installSynapse() {
1599
1712
  const synapseDir = path.join(moduleBasePath(), 'bdb-synapse');
1600
- 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
+ }
1601
1717
  if (DRY_RUN) {
1602
1718
  log.step('[dry-run] would link synapse binary into ~/.local/bin/synapse + setup background daemon');
1603
1719
  return;
@@ -1711,7 +1827,10 @@ async function installSynapse() {
1711
1827
 
1712
1828
  async function installCreatorExtension() {
1713
1829
  const creatorDir = path.join(moduleBasePath(), 'bdb-dev-creator-extension');
1714
- 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
+ }
1715
1834
  if (DRY_RUN) {
1716
1835
  log.step('[dry-run] would run BDB Creator Extension setup');
1717
1836
  return;
@@ -1729,138 +1848,101 @@ async function installCreatorExtension() {
1729
1848
 
1730
1849
  async function installOSRemoteGateway() {
1731
1850
  const remoteDir = path.join(moduleBasePath(), 'bdb-os-remote');
1732
- 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
+ }
1733
1855
  }
1734
1856
 
1735
1857
  async function installDevToolInstaller() {
1736
1858
  const toolInstallerDir = path.join(moduleBasePath(), 'bdb-dev-tool-installer');
1737
- 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
+ }
1738
1863
  }
1739
1864
 
1740
- async function installOSAgentWorkspace() {
1741
- const osAgentDir = path.join(moduleBasePath(), 'bdb-os-agent-workspace');
1742
- const localBinDir = path.join(homeDir, '.local', 'bin');
1743
- const binTarget = path.join(localBinDir, 'ao');
1744
-
1745
- fs.mkdirSync(localBinDir, { recursive: true });
1746
- fs.mkdirSync(path.join(homeDir, '.ao', 'data'), { recursive: true });
1747
- 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
+ }
1748
1874
 
1749
- 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
+ }
1750
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
+ }
1751
1887
  if (DRY_RUN) {
1752
- 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`');
1753
1889
  return;
1754
1890
  }
1755
1891
 
1892
+ const localBinDir = path.join(homeDir, '.local', 'bin');
1893
+ const binTarget = path.join(localBinDir, 'ao');
1756
1894
  const daemonBin = path.join(osAgentDir, 'backend', 'ao-daemon');
1757
- if (fs.existsSync(daemonBin)) {
1758
- try {
1759
- fs.copyFileSync(daemonBin, binTarget);
1760
- fs.chmodSync(binTarget, '755');
1761
- } catch (e) { logDebug(e, 'operation'); }
1895
+
1896
+ if (!fs.existsSync(daemonBin)) {
1897
+ log.warn(`AO binary missing at ${daemonBin} — package layout changed?`);
1898
+ return;
1762
1899
  }
1763
1900
 
1764
- if (process.platform === 'darwin') {
1765
- const plistPath = path.join(homeDir, 'Library', 'LaunchAgents', 'com.bdb.agent-workspace.plist');
1766
- const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
1767
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1768
- <plist version="1.0">
1769
- <dict>
1770
- <key>Label</key>
1771
- <string>com.bdb.agent-workspace</string>
1772
- <key>ProgramArguments</key>
1773
- <array>
1774
- <string>${binTarget}</string>
1775
- </array>
1776
- <key>EnvironmentVariables</key>
1777
- <dict>
1778
- <key>AO_PORT</key>
1779
- <string>3101</string>
1780
- <key>AO_DATA_DIR</key>
1781
- <string>${path.join(homeDir, '.ao', 'data')}</string>
1782
- <key>PATH</key>
1783
- <string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/homebrew/bin:${localBinDir}</string>
1784
- </dict>
1785
- <key>RunAtLoad</key>
1786
- <true/>
1787
- <key>KeepAlive</key>
1788
- <true/>
1789
- <key>StandardOutPath</key>
1790
- <string>${path.join(homeDir, '.ao', 'logs', 'daemon.stdout.log')}</string>
1791
- <key>StandardErrorPath</key>
1792
- <string>${path.join(homeDir, '.ao', 'logs', 'daemon.stderr.log')}</string>
1793
- </dict>
1794
- </plist>`;
1795
- try {
1796
- fs.writeFileSync(plistPath, plistContent);
1797
- execSync(`launchctl unload "${plistPath}" 2>/dev/null || true`, { stdio: 'ignore' });
1798
- execSync(`launchctl load -w "${plistPath}" 2>/dev/null || true`, { stdio: 'ignore' });
1799
- const isListening = await verifyDaemonListening(3101, 'Agent Workspace');
1800
- if (isListening) {
1801
- log.success('Agent Workspace LaunchAgent active (Port 3101)');
1802
- } else {
1803
- 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.');
1804
- }
1805
- } 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.');
1806
1911
 
1807
- let appDir = '/Applications/BDB Agent Workspace.app';
1808
- try {
1809
- fs.accessSync('/Applications', fs.constants.W_OK);
1810
- } catch (e) {
1811
- logDebug(e, 'write-access probe on /Applications');
1812
- appDir = path.join(homeDir, 'Applications', 'BDB Agent Workspace.app');
1813
- log.step('/Applications is not writable - falling back to ~/Applications');
1814
- }
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.');
1815
1916
  try {
1816
- fs.mkdirSync(path.join(appDir, 'Contents', 'MacOS'), { recursive: true });
1817
- fs.mkdirSync(path.join(appDir, 'Contents', 'Resources'), { recursive: true });
1818
- const iconSrc = path.join(osAgentDir, 'frontend', 'assets', 'icon.icns');
1819
- if (fs.existsSync(iconSrc)) {
1820
- fs.copyFileSync(iconSrc, path.join(appDir, 'Contents', 'Resources', 'AppIcon.icns'));
1821
- }
1822
- const launcherScript = `#!/usr/bin/env bash
1823
- export PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/homebrew/bin:${localBinDir}:$PATH"
1824
- if ! curl -s http://127.0.0.1:3101/healthz >/dev/null 2>&1; then
1825
- launchctl start com.bdb.agent-workspace 2>/dev/null || "${binTarget}" &
1826
- sleep 0.5
1827
- fi
1828
- if [ -d "/Applications/Google Chrome.app" ]; then
1829
- open -na "/Applications/Google Chrome.app" --args --app="http://127.0.0.1:3101" --user-data-dir="$HOME/.ao/chrome-app-profile"
1830
- elif [ -d "/Applications/Brave Browser.app" ]; then
1831
- open -na "/Applications/Brave Browser.app" --args --app="http://127.0.0.1:3101" --user-data-dir="$HOME/.ao/brave-app-profile"
1832
- else
1833
- open "http://127.0.0.1:3101"
1834
- fi`;
1835
- const launcherPath = path.join(appDir, 'Contents', 'MacOS', 'app_launcher');
1836
- fs.writeFileSync(launcherPath, launcherScript);
1837
- fs.chmodSync(launcherPath, '755');
1838
- log.success(`Desktop App available under ${appDir}`);
1839
- } catch (e) { logDebug(e, 'operation'); }
1840
- } else if (process.platform === 'win32') {
1841
- const startupDir = path.join(process.env.APPDATA || path.join(homeDir, 'AppData', 'Roaming'), 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup');
1842
- fs.mkdirSync(startupDir, { recursive: true });
1843
- const vbsPath = path.join(startupDir, 'com.bdb.agent-workspace.vbs');
1844
- const daemonBin = path.join(osAgentDir, 'backend', 'ao-daemon.exe');
1845
- const runBin = fs.existsSync(daemonBin) ? daemonBin : path.join(osAgentDir, 'backend', 'ao.exe');
1846
- if (fs.existsSync(runBin)) {
1847
- const vbsContent = `Set WshShell = CreateObject("WScript.Shell")\r\nWshShell.CurrentDirectory = "${osAgentDir}"\r\nWshShell.Run """${runBin}""", 0, False\r\n`;
1848
- try {
1849
- fs.writeFileSync(vbsPath, vbsContent, 'utf-8');
1850
- spawn('wscript.exe', [vbsPath], { detached: true, stdio: 'ignore' }).unref();
1851
- const isListening = await verifyDaemonListening(3101, 'Agent Workspace');
1852
- if (isListening) {
1853
- log.success('Agent Workspace Windows Background Service registered & started (Port 3101)');
1854
- } else {
1855
- 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.');
1856
- }
1857
- } catch (e) { logDebug(e, 'windows workspace daemon setup'); }
1858
- }
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`.');
1859
1944
  }
1860
-
1861
- log.step('BDB Agent Workspace WebUI: http://localhost:3101');
1862
1945
  }
1863
-
1864
1946
  async function promptMemBIngestion(mcpCodeTarget) {
1865
1947
  if (isAutoYes || DRY_RUN) return;
1866
1948
 
@@ -1903,30 +1985,16 @@ async function promptMemBIngestion(mcpCodeTarget) {
1903
1985
  }
1904
1986
  }
1905
1987
 
1906
- async function promptEcosystemHealthScheduler() {
1907
- if (isAutoYes) return;
1908
- const doSchedule = pick(await askConfirm({
1909
- message: 'Enable automated Health Audit Cron Job (2x daily at 01:00 and 12:00)?',
1910
- initialValue: true
1911
- }));
1912
- if (doSchedule) {
1913
- log.success(`Health Audit Scheduler configured for 01:00 & 12:00. Script: ${path.join(srcDir, 'scripts', 'ecosystem-health-audit.js')}`);
1914
- }
1915
- }
1916
-
1917
1988
  function verifyEcosystemInstallation() {
1918
1989
  const modules = [
1919
1990
  { name: '1. bdb-synapse', pkg: '@hybridlabor-api/bdb-synapse', paths: [path.join(moduleBasePath(), 'bdb-synapse')] },
1920
1991
  { name: '2. memB', pkg: '@hybridlabor-api/memb', paths: [path.join(moduleBasePath(), 'memB'), path.join(geminiDir, 'config', 'mcps', 'memb-mcp')] },
1921
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')] },
1922
- // bdb-os-agent-workspace: temporarily disabled -- its main branch (both local
1923
- // and on GitHub) is frozen while the real work sits on unmerged WIP branches,
1924
- // so its published npm version doesn't reliably reflect what "latest" means
1925
- // right now. Re-add here once that's resolved.
1926
- { name: '4. bdb-dev-creator-extension', pkg: '@hybridlabor-api/bdb-dev-creator-extension', paths: [path.join(moduleBasePath(), 'bdb-dev-creator-extension')] },
1927
- { name: '5. bdb-os-remote', pkg: '@hybridlabor-api/bdb-os-remote', paths: [path.join(moduleBasePath(), 'bdb-os-remote')] },
1928
- { name: '6. bdb-dev-tool-installer', pkg: '@hybridlabor-api/bdb-dev-tool-installer', paths: [path.join(moduleBasePath(), 'bdb-dev-tool-installer')] },
1929
- { 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] }
1930
1998
  ];
1931
1999
 
1932
2000
  for (const mod of modules) {
@@ -1969,7 +2037,21 @@ function verifyEcosystemInstallation() {
1969
2037
  console.log(` • ${colors.bold}${mod.name.padEnd(35)}${colors.reset} ➔ ${colors.green}✅ Installed${colors.reset}`);
1970
2038
  }
1971
2039
  } else if (mod.name.includes('token-saver') && fs.existsSync(path.join(srcDir, 'vendor', 'token-saver'))) {
1972
- 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}`);
1973
2055
  } else {
1974
2056
  console.log(` • ${colors.bold}${mod.name.padEnd(35)}${colors.reset} ➔ ${colors.dim}⚪ Optional / Not downloaded${colors.reset}`);
1975
2057
  }
@@ -2135,7 +2217,13 @@ async function installMcpsForTarget(paths, ctx) {
2135
2217
  if (selectedMcps.includes(CORE_MCP)) {
2136
2218
  const membMcpTarget = path.join(mcpCodeTarget, CORE_MCP);
2137
2219
  installStep(`download/update ${CORE_MCP} from npm`, () => {
2138
- 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
+ }
2139
2227
  }, 'memb-mcp installation skipped; MCP config may reference a missing path.');
2140
2228
  }
2141
2229
  log.step(`Installed selected MCP servers to ${mcpCodeTarget}`);
@@ -2148,7 +2236,11 @@ async function installMcpsForTarget(paths, ctx) {
2148
2236
  const ok = runNpmWithRetry('npm install --no-audit --no-fund', { cwd: targetFolder }, `npm install for ${mcpFolder}`);
2149
2237
  if (ok && (fs.existsSync(path.join(targetFolder, 'tsconfig.json')) || fs.existsSync(path.join(targetFolder, 'tsconfig.build.json')))) {
2150
2238
  log.step(`Compiling TypeScript for ${mcpFolder}...`);
2151
- 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.`);
2152
2244
  }
2153
2245
  }
2154
2246
  }
@@ -2180,7 +2272,11 @@ async function installMcpsForTarget(paths, ctx) {
2180
2272
  ? path.join(membMcpFolder, '.venv', 'Scripts', 'python.exe')
2181
2273
  : path.join(membMcpFolder, '.venv', 'bin', 'python');
2182
2274
 
2183
- 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)) {
2184
2280
  try {
2185
2281
  execSync(`uv venv --seed .venv`, { cwd: membMcpFolder, stdio: 'ignore' });
2186
2282
  } catch (e1) {
@@ -2198,12 +2294,12 @@ async function installMcpsForTarget(paths, ctx) {
2198
2294
  }
2199
2295
 
2200
2296
  const pythonMcps = [
2201
- { folder: 'golem-rhino-mcp', args: ['-m', 'mcp_server', '--help'] },
2202
- { 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 },
2203
2299
  { folder: 'davinci-resolve-mcp-free', args: ['-r', 'requirements.txt', 'src/resolve_mcp_bridge.py', '--help'], uvArgs: true },
2204
- { folder: 'blender-mcp', args: ['-m', 'blender_mcp.server', '--help'] },
2300
+ { folder: 'blender-mcp', args: ['-m', 'blender_mcp.server', '--help'], uvArgs: true },
2205
2301
  { folder: 'vectorworks-mcp', args: ['-r', 'requirements.txt', 'app/mcp_server.py', '--help'], uvArgs: true },
2206
- { folder: 'windows-computer-use-mcp', args: ['run_server.py', '--help'] }
2302
+ { folder: 'windows-computer-use-mcp', args: ['run_server.py', '--help'], uvArgs: true }
2207
2303
  ];
2208
2304
  for (const mcp of pythonMcps.filter(m => selectedMcps.includes(m.folder))) {
2209
2305
  const targetFolder = path.join(mcpCodeTarget, mcp.folder);
@@ -2213,7 +2309,18 @@ async function installMcpsForTarget(paths, ctx) {
2213
2309
  log.step(`[dry-run] would pre-warm Python deps for ${mcp.folder}`);
2214
2310
  continue;
2215
2311
  }
2216
- 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
+ }
2217
2324
  }
2218
2325
  }
2219
2326
 
@@ -2236,6 +2343,28 @@ async function installMcpsForTarget(paths, ctx) {
2236
2343
  const parsedMcpConfig = JSON.parse(mcpConfigStr);
2237
2344
  const finalMcpServers = {};
2238
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
+
2239
2368
  for (const [key, val] of Object.entries(parsedMcpConfig.mcpServers)) {
2240
2369
  let keep = !skippedMcpConfigKeys.includes(key);
2241
2370
  for (const available of availableFolders) {
@@ -2960,10 +3089,13 @@ async function promptOptionalModules(installedModules) {
2960
3089
  { id: 'synapse', name: 'BDB Synapse (3D Codebase Visualizer)', fn: installSynapse },
2961
3090
  { id: 'memb', name: 'memB Vector Engine (Local Semantic Memory)', fn: () => installMemB(true) },
2962
3091
  { id: 'remote', name: 'BDB OS Remote Gateway (Zero-Trust Tailscale Multiplexer)', fn: installOSRemoteGateway },
2963
- // bdb-os-agent-workspace: temporarily disabled -- its main branch (both local
2964
- // and on GitHub) is frozen while the real work sits on unmerged WIP branches,
2965
- // so its published npm version doesn't reliably reflect what "latest" means
2966
- // 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
+ : []),
2967
3099
  { id: 'creator', name: 'BDB Creator Extension (Generative 3D, Video & ComfyUI)', fn: installCreatorExtension },
2968
3100
  { id: 'installer', name: 'BDB Dev Tool Installer (Interactive Hub & CLI Launcher)', fn: installDevToolInstaller }
2969
3101
  ];
@@ -3334,9 +3466,16 @@ async function runQuickUpdate(installState) {
3334
3466
  syncSkillsToGlobalHarnesses(excludeSkills);
3335
3467
  s.stop('Skills refreshed');
3336
3468
 
3337
- installStep('refresh gate + memory hooks', () => {
3338
- installGlobalHooks();
3339
- }, 'Hooks keep whatever version this machine already had.');
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.');
3340
3479
 
3341
3480
  // OpenWiki setup only ever ran on a brand-new install (main()'s fresh-install
3342
3481
  // branch below) -- an existing install running Quick Update never got offered
@@ -3357,7 +3496,7 @@ async function runQuickUpdate(installState) {
3357
3496
  if (subId === 'synapse') await installSynapse();
3358
3497
  else if (subId === 'memb') await installMemB(false);
3359
3498
  else if (subId === 'remote') await installOSRemoteGateway();
3360
- // 'ao' (bdb-os-agent-workspace) intentionally skipped here even for
3499
+ // 'ao' intentionally skipped here even for
3361
3500
  // existing installs -- see promptOptionalModules() for why.
3362
3501
  else if (subId === 'creator') await installCreatorExtension();
3363
3502
  else if (subId === 'installer') await installDevToolInstaller();
@@ -3398,7 +3537,10 @@ async function main() {
3398
3537
  console.log(buildTelemetryCard({ installState, detections, daemonStatus }));
3399
3538
 
3400
3539
  if (DRY_RUN) {
3401
- 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).');
3402
3544
  }
3403
3545
  if (isAutoYes) {
3404
3546
  log.warn('Non-interactive/auto mode (-y): all defaults are accepted automatically.');
@@ -3490,6 +3632,8 @@ async function main() {
3490
3632
  let wantsUniversal = true;
3491
3633
  let specificPlatforms = ['1'];
3492
3634
 
3635
+ markPlatformsExplicit(PLATFORMS_ARG || []);
3636
+
3493
3637
  if (PLATFORMS_ARG) {
3494
3638
  // Explicit targets win over both the menu and the auto-yes default.
3495
3639
  // '0' means universal; anything else means exactly those targets, so
@@ -3529,6 +3673,9 @@ async function main() {
3529
3673
  });
3530
3674
  if (sel === BACK) return 'back';
3531
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);
3532
3679
  };
3533
3680
 
3534
3681
  const stepMode = async () => {
@@ -3742,7 +3889,6 @@ async function main() {
3742
3889
  const justInstalled = await promptOptionalModules(installedModulesForPrompt) || [];
3743
3890
 
3744
3891
  await promptMemBIngestion(path.join(primaryTarget.targetMcpDir, 'mcps'));
3745
- await promptEcosystemHealthScheduler();
3746
3892
 
3747
3893
  const alreadyHandled = justInstalled.map(id => MODULE_ID_TO_DAEMON_NAME[id]).filter(Boolean);
3748
3894
  await reloadDaemons(alreadyHandled);
@@ -3767,6 +3913,10 @@ if (require.main === module) {
3767
3913
 
3768
3914
  // Exported for tests -- requiring installer.js must not launch the TUI.
3769
3915
  module.exports = {
3916
+ installOSAgentWorkspace,
3917
+ downloadOrUpdateModule,
3918
+ detectPlatforms,
3919
+ markPlatformsExplicit,
3770
3920
  mergeBdbSettingsHooks,
3771
3921
  installGlobalHooks,
3772
3922
  installProjectHarness,