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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +471 -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
  }
@@ -602,6 +608,88 @@ function initSessionManifest(existingManifest, sourceDirs) {
602
608
  _sessionHashes = buildKnownSourceHashes(sourceDirs);
603
609
  }
604
610
 
611
+ // Skills that left the package in 4.4.2 (Plan 02: migrated to the private
612
+ // aos-internal repo, per ~/dev/_plans/aos-2026-09-14/), by their flattened
613
+ // directory name at a sync destination.
614
+ //
615
+ // This is deliberately a fixed list, not a diff against what the current
616
+ // payload ships. A generic "anything on disk that is not in skills/ right
617
+ // now" pass was tried first and swept up two unrelated things: skill folders
618
+ // that MCP servers bundle inside their own package tree (e.g.
619
+ // mcps/tdmcp/.agents/skills/...), which happen to share the literal path
620
+ // segment "skills" but belong to a different install entirely; and skills
621
+ // under skills/workspace_agents/, which syncSkillsToGlobalHarnesses already
622
+ // deliberately excludes from the six global destinations, so a stray global
623
+ // copy of one says nothing about whether it is safe to delete. Both are real
624
+ // cleanup opportunities, but they are a separate investigation, not a side
625
+ // effect of this migration's beta test.
626
+ const SKILLS_REMOVED_IN_4_4_2 = new Set([
627
+ 'bdbsaastraining', 'bdb-dev-os-skill', 'bdbsaashost', 'bdb-ecosystem-health', 'bdbsaas-ops',
628
+ ]);
629
+
630
+ // The exact roots syncSkillsToGlobalHarnesses and the Gemini/Antigravity
631
+ // config-skills path write into. Pruning only touches a manifest entry whose
632
+ // path starts under one of these -- never one that merely contains a
633
+ // "skills" segment somewhere deeper, which is what let the MCP-bundle and
634
+ // workspace_agents cases above leak in during testing.
635
+ function globalSkillDestRoots() {
636
+ return [
637
+ path.join(homeDir, '.agents', 'skills'),
638
+ path.join(homeDir, '.claude', 'skills'),
639
+ path.join(homeDir, '.codex', 'skills'),
640
+ path.join(homeDir, '.cursor', 'skills'),
641
+ path.join(homeDir, '.roo', 'skills'),
642
+ path.join(geminiDir, 'config', 'skills'),
643
+ ];
644
+ }
645
+
646
+ // syncSkillsToGlobalHarnesses (and its sibling copy loops) only ever add or
647
+ // update -- nothing in that path ever deletes a skill that the current
648
+ // payload no longer ships. That is fine for new skills and updated ones, but
649
+ // it means a skill removed from a release stays on every user's disk
650
+ // forever, through every future update, because nothing after the removal
651
+ // ever looks at what is on disk that no longer has a source. This walks the
652
+ // install manifest -- which already has one entry per file this installer
653
+ // has ever placed -- for the specific skills 4.4.2 removed, under the six
654
+ // known global skill roots, and deletes them. A user-modified file is backed
655
+ // up first, the same way resolveFileConflict backs up a user edit it is
656
+ // about to overwrite, rather than silently deleting local changes.
657
+ function pruneRemovedSkills(manifest) {
658
+ if (!manifest) return;
659
+ const roots = globalSkillDestRoots();
660
+
661
+ const staleDirs = new Set();
662
+ let removedFiles = 0;
663
+ for (const targetPath of Object.keys(manifest)) {
664
+ const root = roots.find((r) => targetPath.startsWith(r + path.sep));
665
+ if (!root) continue;
666
+ const skillDirName = targetPath.slice(root.length + 1).split(path.sep)[0];
667
+ if (!SKILLS_REMOVED_IN_4_4_2.has(skillDirName)) continue;
668
+
669
+ if (fs.existsSync(targetPath)) {
670
+ const diskHash = computeFileHash(targetPath);
671
+ const manifestEntry = manifest[targetPath];
672
+ if (diskHash !== null && manifestEntry && diskHash !== manifestEntry.sha256) {
673
+ const bakPath = `${targetPath}.${timestamp}.bak`;
674
+ try { fs.copyFileSync(targetPath, bakPath); } catch (e) { logDebug(e, 'prune-removed-skill backup'); }
675
+ log.warn(`[manifest] Retired skill left a user-edited file, backed up to ${bakPath} instead of deleting it.`);
676
+ } else {
677
+ try { fs.unlinkSync(targetPath); removedFiles++; } catch (e) { logDebug(e, 'prune-removed-skill unlink'); }
678
+ }
679
+ staleDirs.add(path.join(root, skillDirName));
680
+ }
681
+ delete manifest[targetPath];
682
+ }
683
+
684
+ for (const dir of staleDirs) {
685
+ try { fs.rmSync(dir, { recursive: true, force: true }); } catch (e) { logDebug(e, 'prune-removed-skill rmdir'); }
686
+ }
687
+
688
+ if (staleDirs.size > 0) {
689
+ log.step(`Removed ${removedFiles} file(s) from ${staleDirs.size} retired skill install(s) no longer shipped.`);
690
+ }
691
+ }
692
+
605
693
  function flushSessionManifest() {
606
694
  if (_sessionManifest) saveInstallManifest(_sessionManifest);
607
695
  }
@@ -637,66 +725,123 @@ function resolveMcpsArg(availableMcps) {
637
725
  return matched;
638
726
  }
639
727
 
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
- }
728
+ // Platforms the user explicitly asked for, by detection key. A harness the
729
+ // user names is installed as far as we are concerned, whether or not we can
730
+ // find its binary -- they know their machine better than a PATH lookup does.
731
+ const explicitPlatformKeys = new Set();
732
+ const PLATFORM_KEYS_BY_OPTION = {
733
+ '1': 'antigravity',
734
+ '2': ['claudecode', 'claudedesktop'],
735
+ '3': 'cursor',
736
+ '5': 'codex',
737
+ '6': 'windsurf',
738
+ '7': 'vscode',
739
+ '8': 'aider',
740
+ };
664
741
 
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" });
742
+ function markPlatformsExplicit(optionValues) {
743
+ for (const v of optionValues || []) {
744
+ const keys = PLATFORM_KEYS_BY_OPTION[v];
745
+ if (!keys) continue;
746
+ for (const k of [].concat(keys)) explicitPlatformKeys.add(k);
670
747
  }
748
+ }
671
749
 
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
- }
750
+ // Where an application itself lives, per OS. Presence of the app is proof;
751
+ // presence of a dot-directory is not.
752
+ function appBundle(name) {
753
+ if (process.platform === 'darwin') return [`/Applications/${name}.app`, path.join(homeDir, 'Applications', `${name}.app`)];
754
+ if (process.platform === 'win32') return [path.join(process.env.LOCALAPPDATA || homeDir, 'Programs', name)];
755
+ return [`/usr/share/${name.toLowerCase()}`, `/opt/${name.toLowerCase()}`];
756
+ }
678
757
 
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
- }
758
+ const anyExists = (paths) => paths.some((p) => fs.existsSync(p));
759
+ const globExists = (dir, re) => {
760
+ try { return fs.readdirSync(dir).some((e) => re.test(e)); } catch { return false; }
761
+ };
687
762
 
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
- }
763
+ // Detect installed agent harnesses.
764
+ //
765
+ // This used to test for a config *directory* and nothing else, which was
766
+ // circular: syncSkillsToGlobalHarnesses() creates ~/.claude, ~/.codex,
767
+ // ~/.cursor and ~/.roo unconditionally, so after one install the detector
768
+ // found evidence the installer had planted itself. A machine with neither
769
+ // Cursor nor VS Code nor Aider still reported all three, and skills were
770
+ // written into directories nothing would ever read.
771
+ //
772
+ // Evidence now means the application itself and nothing else: a binary on
773
+ // PATH, an installed app bundle, or an editor extension. Config files are
774
+ // deliberately NOT evidence -- the installer writes ~/.claude.json,
775
+ // ~/.cursor/mcp.json, ~/.roo/mcp_settings.json and the rest of them, so
776
+ // trusting those reintroduces the same circularity one level down. (Written
777
+ // after exactly that: an early cut of this fix still "found" Cursor and
778
+ // Windsurf on a machine with neither, via files AOS had planted.)
779
+ // The user's explicit choice counts too -- see markPlatformsExplicit().
780
+ function detectPlatforms() {
781
+ const candidates = [
782
+ {
783
+ key: 'antigravity', name: 'Google Antigravity', path: geminiDir,
784
+ evidence: () => hasExecutable('agy') || anyExists(appBundle('Antigravity')),
785
+ },
786
+ {
787
+ key: 'codex', name: 'ChatGPT Codex CLI', path: path.join(homeDir, '.codex'),
788
+ evidence: () => hasExecutable('codex'),
789
+ },
790
+ {
791
+ key: 'claudecode', name: 'Claude Code CLI', path: path.join(homeDir, '.claude'),
792
+ evidence: () => hasExecutable('claude'),
793
+ },
794
+ {
795
+ key: 'claudedesktop', name: 'Claude Desktop',
796
+ path: process.platform === 'win32'
797
+ ? path.join(process.env.APPDATA || homeDir, 'Claude')
798
+ : path.join(homeDir, 'Library', 'Application Support', 'Claude'),
799
+ evidence: () => anyExists(appBundle('Claude')),
800
+ },
801
+ {
802
+ key: 'cursor', name: 'Cursor IDE',
803
+ path: process.platform === 'win32'
804
+ ? path.join(process.env.APPDATA || homeDir, 'Cursor')
805
+ : path.join(homeDir, 'Library', 'Application Support', 'Cursor'),
806
+ evidence: () => anyExists(appBundle('Cursor')) || hasExecutable('cursor'),
807
+ },
808
+ {
809
+ key: 'windsurf', name: 'Windsurf IDE',
810
+ path: process.platform === 'win32'
811
+ ? path.join(process.env.APPDATA || homeDir, 'Windsurf')
812
+ : path.join(homeDir, 'Library', 'Application Support', 'Windsurf'),
813
+ evidence: () => anyExists(appBundle('Windsurf')) || hasExecutable('windsurf'),
814
+ },
815
+ {
816
+ key: 'vscode', name: 'Roo Code / Cline / VS Code',
817
+ path: process.platform === 'win32'
818
+ ? path.join(process.env.APPDATA || homeDir, 'Code')
819
+ : path.join(homeDir, 'Library', 'Application Support', 'Code'),
820
+ evidence: () => anyExists(appBundle('Visual Studio Code'))
821
+ || hasExecutable('code')
822
+ || globExists(path.join(homeDir, '.vscode', 'extensions'), /roo|cline/i),
823
+ },
824
+ {
825
+ key: 'aider', name: 'Aider CLI', path: homeDir,
826
+ evidence: () => hasExecutable('aider'),
827
+ },
828
+ {
829
+ key: 'opencode', name: 'OpenCode CLI',
830
+ path: process.platform === 'win32'
831
+ ? path.join(process.env.APPDATA || homeDir, 'opencode')
832
+ : path.join(homeDir, '.config', 'opencode'),
833
+ evidence: () => hasExecutable('opencode'),
834
+ },
835
+ ];
692
836
 
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" });
837
+ const detections = [];
838
+ for (const c of candidates) {
839
+ let present = false;
840
+ try { present = c.evidence(); } catch (e) { logDebug(e, `detect ${c.key}`); }
841
+ if (present || explicitPlatformKeys.has(c.key)) {
842
+ detections.push({ name: c.name, path: c.path, key: c.key, chosen: !present });
843
+ }
698
844
  }
699
-
700
845
  return detections;
701
846
  }
702
847
 
@@ -751,10 +896,9 @@ function detectInstallState() {
751
896
  { id: 'synapse', dir: path.join(basePath, 'bdb-synapse') },
752
897
  { id: 'memb', dir: path.join(basePath, 'memB') },
753
898
  { 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.
899
+ { id: 'ao', dir: path.join(basePath, 'bdb-agent-orchestrator') },
900
+ // The archived predecessor is deliberately NOT detected: a copy of it
901
+ // should be removed, not carried forward into another install.
758
902
  { id: 'creator', dir: path.join(basePath, 'bdb-dev-creator-extension') },
759
903
  { id: 'installer', dir: path.join(basePath, 'bdb-dev-tool-installer') }
760
904
  ];
@@ -952,9 +1096,9 @@ function copyDirRecursiveSync(source, target, excludeList = [], manifest = null,
952
1096
  // root-level leaf skill dumps into the same flat target, so each subsequent
953
1097
  // one silently overwrites the previous one's SKILL.md -- found by tracing
954
1098
  // 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.
1099
+ // existed with different content; the root-level one (and 3 siblings:
1100
+ // github-repo, memb-ingest, synapse-integration-skill) never survived a
1101
+ // global sync intact.
958
1102
  function syncSkillEntry(fullPath, dirName, targetSkillDir, excludeSkills) {
959
1103
  const isLeafSkill = fs.existsSync(path.join(fullPath, 'SKILL.md'));
960
1104
  if (isLeafSkill) {
@@ -985,15 +1129,20 @@ function syncSkillsToGlobalHarnesses(excludeSkills = []) {
985
1129
  const skillsBase = path.join(srcDir, 'skills');
986
1130
  if (!fs.existsSync(skillsBase)) return;
987
1131
 
1132
+ // Mirror only into harnesses that are actually present. This list used to
1133
+ // be unconditional, which both wrote skills nobody would read and planted
1134
+ // the very directories detectPlatforms() then read back as proof the
1135
+ // harness existed. ~/.agents is ours and always written.
1136
+ const detectedKeys = new Set(detectPlatforms().map((d) => d.key));
988
1137
  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) {
1138
+ { dir: path.join(homeDir, '.agents', 'skills'), key: null },
1139
+ { dir: path.join(homeDir, '.claude', 'skills'), key: 'claudecode' },
1140
+ { dir: path.join(homeDir, '.codex', 'skills'), key: 'codex' },
1141
+ { dir: path.join(homeDir, '.cursor', 'skills'), key: 'cursor' },
1142
+ { dir: path.join(homeDir, '.roo', 'skills'), key: 'vscode' },
1143
+ ].filter((d) => d.key === null || detectedKeys.has(d.key) || fs.existsSync(d.dir));
1144
+
1145
+ for (const { dir: dest } of extraSkillDestinations) {
997
1146
  try {
998
1147
  fs.mkdirSync(dest, { recursive: true });
999
1148
  const rawDirs = fs.readdirSync(skillsBase);
@@ -1414,23 +1563,34 @@ function checkModuleUpdate(pkgName, targetDir) {
1414
1563
  try {
1415
1564
  const localVer = JSON.parse(fs.readFileSync(modulePkgPath, 'utf8')).version;
1416
1565
  let remoteVer = null;
1566
+ let checkFailed = false;
1417
1567
  try {
1418
1568
  remoteVer = execSync(`npm view ${pkgName} version`, { stdio: ['ignore', 'pipe', 'ignore'], encoding: 'utf8', timeout: 4000 }).trim();
1419
- } catch (e) { logDebug(e, 'operation'); }
1569
+ } catch (e) {
1570
+ // Offline, a proxy, or a misconfigured registry. Swallowing this
1571
+ // made every module report "up to date" forever on such a machine,
1572
+ // indistinguishable from a real answer -- so say which one it is.
1573
+ checkFailed = true;
1574
+ logDebug(e, `npm view ${pkgName}`);
1575
+ }
1420
1576
 
1421
1577
  if (remoteVer && localVer !== remoteVer) {
1422
- return { installed: true, updateAvailable: true, localVer, remoteVer };
1578
+ return { installed: true, updateAvailable: true, localVer, remoteVer, checkFailed };
1423
1579
  }
1424
- return { installed: true, updateAvailable: false, localVer, remoteVer };
1580
+ return { installed: true, updateAvailable: false, localVer, remoteVer, checkFailed };
1425
1581
  } catch (e) {
1426
- return { installed: true, updateAvailable: false, localVer: null, remoteVer: null };
1582
+ return { installed: true, updateAvailable: false, localVer: null, remoteVer: null, checkFailed: true };
1427
1583
  }
1428
1584
  }
1429
1585
 
1430
1586
  function downloadOrUpdateModule(pkgName, targetDir, displayName) {
1431
1587
  const status = checkModuleUpdate(pkgName, targetDir);
1432
1588
  if (status.installed && !status.updateAvailable) {
1433
- log.step(`${displayName} is up to date (v${status.localVer})`);
1589
+ if (status.checkFailed) {
1590
+ log.warn(`${displayName}: could not reach the npm registry — keeping the installed v${status.localVer}, which may be stale.`);
1591
+ } else {
1592
+ log.step(`${displayName} is up to date (v${status.localVer})`);
1593
+ }
1434
1594
  return true;
1435
1595
  }
1436
1596
 
@@ -1458,14 +1618,42 @@ function downloadOrUpdateModule(pkgName, targetDir, displayName) {
1458
1618
  const ok = runNpmWithRetry(`npm pack ${pkgName}@latest`, { stdio: 'ignore', cwd: targetDir }, `${displayName} download`);
1459
1619
  cleanNpmCacheOnWindows();
1460
1620
  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.");
1621
+ if (!tarball) throw new Error("NPM pack returned no archive.");
1622
+
1623
+ // Unpacking straight over targetDir made an update a MERGE: a file the
1624
+ // new version dropped stayed on disk forever, while package.json said
1625
+ // the module was current. Unpack beside it and swap, so the tree that
1626
+ // ends up installed is exactly the published one. The previous tree is
1627
+ // kept until the swap succeeds, and restored if it does not.
1628
+ const stagingDir = `${targetDir}.incoming-${timestamp}`;
1629
+ const retiredDir = `${targetDir}.previous-${timestamp}`;
1630
+ fs.rmSync(stagingDir, { recursive: true, force: true });
1631
+ fs.mkdirSync(stagingDir, { recursive: true });
1632
+ execSync(`tar -xzf "${path.join(targetDir, tarball)}" --strip-components=1 -C "${stagingDir}"`, { stdio: 'ignore' });
1633
+ fs.unlinkSync(path.join(targetDir, tarball));
1634
+
1635
+ // Anything the module generated in place -- venvs, caches, local state
1636
+ // -- is not in the tarball and must survive the swap.
1637
+ const PRESERVE = ['.venv', 'venv', 'node_modules', '.env', 'data', 'logs'];
1638
+ for (const keep of PRESERVE) {
1639
+ const from = path.join(targetDir, keep);
1640
+ if (fs.existsSync(from) && !fs.existsSync(path.join(stagingDir, keep))) {
1641
+ fs.renameSync(from, path.join(stagingDir, keep));
1642
+ }
1468
1643
  }
1644
+
1645
+ try {
1646
+ fs.renameSync(targetDir, retiredDir);
1647
+ fs.renameSync(stagingDir, targetDir);
1648
+ } catch (swapError) {
1649
+ if (fs.existsSync(retiredDir) && !fs.existsSync(targetDir)) fs.renameSync(retiredDir, targetDir);
1650
+ fs.rmSync(stagingDir, { recursive: true, force: true });
1651
+ throw swapError;
1652
+ }
1653
+ fs.rmSync(retiredDir, { recursive: true, force: true });
1654
+
1655
+ s.stop(`${displayName} ready (v${status.remoteVer || 'latest'})`);
1656
+ return true;
1469
1657
  } catch (e) {
1470
1658
  s.stop(`Failed downloading ${displayName}: ${e.message}`);
1471
1659
  return false;
@@ -1485,7 +1673,14 @@ async function installMemB(interactive) {
1485
1673
  }));
1486
1674
  }
1487
1675
  const membDir = path.join(moduleBasePath(), 'memB');
1488
- downloadOrUpdateModule('@hybridlabor-api/memb', membDir, 'memB Vector Engine');
1676
+ // A failed download used to fall through into venv setup and daemon
1677
+ // registration, each of which then failed on its own terms or, worse,
1678
+ // "succeeded" against a stale tree -- and the run still ended in a success
1679
+ // banner. Stop at the module that could not be fetched.
1680
+ if (!downloadOrUpdateModule('@hybridlabor-api/memb', membDir, 'memB Vector Engine')) {
1681
+ log.warn('Skipping memB setup: the module could not be downloaded.');
1682
+ return;
1683
+ }
1489
1684
  if (DRY_RUN) {
1490
1685
  log.step('[dry-run] would bootstrap memB venv + pip requirements');
1491
1686
  return;
@@ -1597,7 +1792,10 @@ async function installMemB(interactive) {
1597
1792
 
1598
1793
  async function installSynapse() {
1599
1794
  const synapseDir = path.join(moduleBasePath(), 'bdb-synapse');
1600
- downloadOrUpdateModule('@hybridlabor-api/bdb-synapse', synapseDir, 'BDB Synapse');
1795
+ if (!downloadOrUpdateModule('@hybridlabor-api/bdb-synapse', synapseDir, 'BDB Synapse')) {
1796
+ log.warn('Skipping Synapse setup: the module could not be downloaded.');
1797
+ return;
1798
+ }
1601
1799
  if (DRY_RUN) {
1602
1800
  log.step('[dry-run] would link synapse binary into ~/.local/bin/synapse + setup background daemon');
1603
1801
  return;
@@ -1711,7 +1909,10 @@ async function installSynapse() {
1711
1909
 
1712
1910
  async function installCreatorExtension() {
1713
1911
  const creatorDir = path.join(moduleBasePath(), 'bdb-dev-creator-extension');
1714
- downloadOrUpdateModule('@hybridlabor-api/bdb-dev-creator-extension', creatorDir, 'BDB Creator Extension');
1912
+ if (!downloadOrUpdateModule('@hybridlabor-api/bdb-dev-creator-extension', creatorDir, 'BDB Creator Extension')) {
1913
+ log.warn('Skipping Creator Extension setup: the module could not be downloaded.');
1914
+ return;
1915
+ }
1715
1916
  if (DRY_RUN) {
1716
1917
  log.step('[dry-run] would run BDB Creator Extension setup');
1717
1918
  return;
@@ -1729,138 +1930,101 @@ async function installCreatorExtension() {
1729
1930
 
1730
1931
  async function installOSRemoteGateway() {
1731
1932
  const remoteDir = path.join(moduleBasePath(), 'bdb-os-remote');
1732
- downloadOrUpdateModule('@hybridlabor-api/bdb-os-remote', remoteDir, 'BDB OS Remote Gateway');
1933
+ if (!downloadOrUpdateModule('@hybridlabor-api/bdb-os-remote', remoteDir, 'BDB OS Remote Gateway')) {
1934
+ log.warn('Skipping OS Remote Gateway setup: the module could not be downloaded.');
1935
+ return;
1936
+ }
1733
1937
  }
1734
1938
 
1735
1939
  async function installDevToolInstaller() {
1736
1940
  const toolInstallerDir = path.join(moduleBasePath(), 'bdb-dev-tool-installer');
1737
- downloadOrUpdateModule('@hybridlabor-api/bdb-dev-tool-installer', toolInstallerDir, 'BDB Dev Tool Installer');
1941
+ if (!downloadOrUpdateModule('@hybridlabor-api/bdb-dev-tool-installer', toolInstallerDir, 'BDB Dev Tool Installer')) {
1942
+ log.warn('Skipping Dev Tool Installer setup: the module could not be downloaded.');
1943
+ return;
1944
+ }
1738
1945
  }
1739
1946
 
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 });
1947
+ // AO ships one prebuilt binary: backend/ao-daemon, Mach-O arm64. The package
1948
+ // carries Windows and Linux sources but nothing built for them, and its CLI
1949
+ // shim picks the first candidate path that EXISTS without checking whether it
1950
+ // can run — so on any other platform it hands spawn a Mach-O file and fails
1951
+ // with an exec-format error instead of the "go build" hint it means to give.
1952
+ // Installing it there would be installing something known not to work.
1953
+ function aoSupportedHere() {
1954
+ return process.platform === 'darwin' && process.arch === 'arm64';
1955
+ }
1748
1956
 
1749
- downloadOrUpdateModule('@hybridlabor-api/bdb-os-agent-workspace', osAgentDir, 'BDB OS Agent Workspace');
1957
+ async function installOSAgentWorkspace() {
1958
+ if (!aoSupportedHere()) {
1959
+ log.warn(`AO ships only a macOS arm64 binary — skipping on ${process.platform}/${process.arch}.`);
1960
+ log.warn('Build it from source instead: github.com/hybridlabor-api/bdb-agent-orchestrator');
1961
+ return;
1962
+ }
1750
1963
 
1964
+ const osAgentDir = path.join(moduleBasePath(), 'bdb-agent-orchestrator');
1965
+ if (!downloadOrUpdateModule('@hybridlabor-api/bdb-agent-orchestrator', osAgentDir, 'BDB Agent Orchestrator')) {
1966
+ log.warn('Skipping AO setup: the module could not be downloaded.');
1967
+ return;
1968
+ }
1751
1969
  if (DRY_RUN) {
1752
- log.step('[dry-run] would link ao binary + register LaunchAgent + Desktop App');
1970
+ log.step('[dry-run] would link ~/.local/bin/ao and run `ao service install`');
1753
1971
  return;
1754
1972
  }
1755
1973
 
1974
+ const localBinDir = path.join(homeDir, '.local', 'bin');
1975
+ const binTarget = path.join(localBinDir, 'ao');
1756
1976
  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'); }
1977
+
1978
+ if (!fs.existsSync(daemonBin)) {
1979
+ log.warn(`AO binary missing at ${daemonBin} — package layout changed?`);
1980
+ return;
1762
1981
  }
1763
1982
 
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'); }
1983
+ installStep('place the ao binary', () => {
1984
+ fs.mkdirSync(localBinDir, { recursive: true });
1985
+ // copyFileSync preserves bytes, so the package's ad-hoc signature
1986
+ // survives. That matters: an unsigned binary at this path is SIGKILLed
1987
+ // by AMFI on launch (exit 137) and reads as a daemon that simply will
1988
+ // not start, with nothing in the log to say why.
1989
+ fs.copyFileSync(daemonBin, binTarget);
1990
+ fs.chmodSync(binTarget, 0o755);
1991
+ log.step(`Installed ao to ${binTarget}`);
1992
+ }, 'AO cannot be started without its binary.');
1806
1993
 
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
- }
1994
+ try {
1995
+ execSync(`codesign -v "${binTarget}"`, { stdio: 'ignore' });
1996
+ } catch {
1997
+ log.warn('The ao binary is not code-signed — AMFI will kill it at launch.');
1815
1998
  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
- }
1999
+ execSync(`codesign -s - -f "${binTarget}"`, { stdio: 'ignore' });
2000
+ log.step('Signed it ad-hoc.');
2001
+ } catch (e) { log.warn(`Could not sign it: ${e.message}`); }
2002
+ }
2003
+
2004
+ // AO installs its own service, and does it for macOS, Windows and Linux.
2005
+ // AOS used to hand-write a LaunchAgent instead, labelled
2006
+ // com.bdb.agent-workspace, whose ProgramArguments were just the binary with
2007
+ // NO subcommand -- so launchd started `ao`, which prints help and exits,
2008
+ // and with KeepAlive kept restarting it. The daemon never ran, on any
2009
+ // machine that installed AO through AOS. Delegating to the tool removes
2010
+ // both the wrong label and the wrong arguments, and keeps working when the
2011
+ // service definition changes upstream.
2012
+ const legacyPlist = path.join(homeDir, 'Library', 'LaunchAgents', 'com.bdb.agent-workspace.plist');
2013
+ if (fs.existsSync(legacyPlist)) {
2014
+ try { execSync(`launchctl unload "${legacyPlist}" 2>/dev/null`, { stdio: 'ignore' }); } catch (e) { logDebug(e, 'unload legacy ao agent'); }
2015
+ try { fs.unlinkSync(legacyPlist); log.step('Removed the old com.bdb.agent-workspace service.'); } catch (e) { logDebug(e, 'remove legacy plist'); }
2016
+ }
2017
+
2018
+ installStep('register the AO service', () => {
2019
+ execSync(`"${binTarget}" service install`, { stdio: 'ignore' });
2020
+ }, 'Start it by hand with: ao service install');
2021
+
2022
+ if (await verifyDaemonListening(3101, 'AO Orchestrator', 8000)) {
2023
+ log.success('AO Orchestrator running on http://localhost:3101');
2024
+ } else {
2025
+ log.warn('AO did not answer on :3101 — check `ao service status` and `ao service logs`.');
1859
2026
  }
1860
-
1861
- log.step('BDB Agent Workspace WebUI: http://localhost:3101');
1862
2027
  }
1863
-
1864
2028
  async function promptMemBIngestion(mcpCodeTarget) {
1865
2029
  if (isAutoYes || DRY_RUN) return;
1866
2030
 
@@ -1903,30 +2067,16 @@ async function promptMemBIngestion(mcpCodeTarget) {
1903
2067
  }
1904
2068
  }
1905
2069
 
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
2070
  function verifyEcosystemInstallation() {
1918
2071
  const modules = [
1919
2072
  { name: '1. bdb-synapse', pkg: '@hybridlabor-api/bdb-synapse', paths: [path.join(moduleBasePath(), 'bdb-synapse')] },
1920
2073
  { name: '2. memB', pkg: '@hybridlabor-api/memb', paths: [path.join(moduleBasePath(), 'memB'), path.join(geminiDir, 'config', 'mcps', 'memb-mcp')] },
1921
2074
  { 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] }
2075
+ { name: '4. AO Agent Orchestrator', pkg: '@hybridlabor-api/bdb-agent-orchestrator', paths: [path.join(moduleBasePath(), 'bdb-agent-orchestrator')] },
2076
+ { name: '5. bdb-dev-creator-extension', pkg: '@hybridlabor-api/bdb-dev-creator-extension', paths: [path.join(moduleBasePath(), 'bdb-dev-creator-extension')] },
2077
+ { name: '6. bdb-os-remote', pkg: '@hybridlabor-api/bdb-os-remote', paths: [path.join(moduleBasePath(), 'bdb-os-remote')] },
2078
+ { name: '7. bdb-dev-tool-installer', pkg: '@hybridlabor-api/bdb-dev-tool-installer', paths: [path.join(moduleBasePath(), 'bdb-dev-tool-installer')] },
2079
+ { name: '8. aos (bdb agent os)', pkg: '@hybridlabor-api/aos', paths: [srcDir] }
1930
2080
  ];
1931
2081
 
1932
2082
  for (const mod of modules) {
@@ -1969,7 +2119,21 @@ function verifyEcosystemInstallation() {
1969
2119
  console.log(` • ${colors.bold}${mod.name.padEnd(35)}${colors.reset} ➔ ${colors.green}✅ Installed${colors.reset}`);
1970
2120
  }
1971
2121
  } 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}`);
2122
+ // Was a hardcoded "v2.6.3", which stopped being true the moment the
2123
+ // vendored copy moved. Read it, and say so when it cannot be read.
2124
+ let vendored = null;
2125
+ for (const f of ['package.json', 'pyproject.toml', 'VERSION']) {
2126
+ const vp = path.join(srcDir, 'vendor', 'token-saver', f);
2127
+ if (!fs.existsSync(vp)) continue;
2128
+ try {
2129
+ const raw = fs.readFileSync(vp, 'utf8');
2130
+ vendored = f === 'package.json'
2131
+ ? JSON.parse(raw).version
2132
+ : (raw.match(/^\s*version\s*=\s*["']?([\w.\-+]+)/m) || [])[1] || raw.trim().split('\n')[0];
2133
+ } catch (e) { logDebug(e, `token-saver version from ${f}`); }
2134
+ if (vendored) break;
2135
+ }
2136
+ console.log(` • ${colors.bold}${mod.name.padEnd(35)}${colors.reset} ➔ ${colors.green}✅ ${vendored ? `v${vendored} ` : ''}(Integrated)${colors.reset}`);
1973
2137
  } else {
1974
2138
  console.log(` • ${colors.bold}${mod.name.padEnd(35)}${colors.reset} ➔ ${colors.dim}⚪ Optional / Not downloaded${colors.reset}`);
1975
2139
  }
@@ -2135,7 +2299,13 @@ async function installMcpsForTarget(paths, ctx) {
2135
2299
  if (selectedMcps.includes(CORE_MCP)) {
2136
2300
  const membMcpTarget = path.join(mcpCodeTarget, CORE_MCP);
2137
2301
  installStep(`download/update ${CORE_MCP} from npm`, () => {
2138
- downloadOrUpdateModule('@hybridlabor-api/memb', membMcpTarget, 'memB MCP');
2302
+ // installStep only catches a throw, and downloadOrUpdateModule
2303
+ // reports failure by returning false -- so without this the hint
2304
+ // below never printed and the MCP config was written pointing at a
2305
+ // path that had not been populated.
2306
+ if (!downloadOrUpdateModule('@hybridlabor-api/memb', membMcpTarget, 'memB MCP')) {
2307
+ throw new Error('npm pack did not produce the memb-mcp payload');
2308
+ }
2139
2309
  }, 'memb-mcp installation skipped; MCP config may reference a missing path.');
2140
2310
  }
2141
2311
  log.step(`Installed selected MCP servers to ${mcpCodeTarget}`);
@@ -2148,7 +2318,11 @@ async function installMcpsForTarget(paths, ctx) {
2148
2318
  const ok = runNpmWithRetry('npm install --no-audit --no-fund', { cwd: targetFolder }, `npm install for ${mcpFolder}`);
2149
2319
  if (ok && (fs.existsSync(path.join(targetFolder, 'tsconfig.json')) || fs.existsSync(path.join(targetFolder, 'tsconfig.build.json')))) {
2150
2320
  log.step(`Compiling TypeScript for ${mcpFolder}...`);
2151
- runNpmWithRetry('npm run build', { cwd: targetFolder }, `npm run build for ${mcpFolder}`);
2321
+ const built = runNpmWithRetry('npm run build', { cwd: targetFolder }, `npm run build for ${mcpFolder}`);
2322
+ // runNpmWithRetry already warned with the failure detail; this
2323
+ // just makes the consequence visible now instead of only later,
2324
+ // when the config-entry check below finds the missing dist file.
2325
+ if (!built) log.warn(`${mcpFolder}: build failed -- its MCP config entry will be dropped if the expected output is missing.`);
2152
2326
  }
2153
2327
  }
2154
2328
  }
@@ -2180,7 +2354,11 @@ async function installMcpsForTarget(paths, ctx) {
2180
2354
  ? path.join(membMcpFolder, '.venv', 'Scripts', 'python.exe')
2181
2355
  : path.join(membMcpFolder, '.venv', 'bin', 'python');
2182
2356
 
2183
- if (!fs.existsSync(venvPython)) {
2357
+ // The fs.* writes are covered by the global dry-run patch, but
2358
+ // child processes are not -- these two would really create a venv.
2359
+ if (DRY_RUN) {
2360
+ log.step(`[dry-run] would create the memb-mcp venv in ${membMcpFolder}`);
2361
+ } else if (!fs.existsSync(venvPython)) {
2184
2362
  try {
2185
2363
  execSync(`uv venv --seed .venv`, { cwd: membMcpFolder, stdio: 'ignore' });
2186
2364
  } catch (e1) {
@@ -2198,12 +2376,12 @@ async function installMcpsForTarget(paths, ctx) {
2198
2376
  }
2199
2377
 
2200
2378
  const pythonMcps = [
2201
- { folder: 'golem-rhino-mcp', args: ['-m', 'mcp_server', '--help'] },
2202
- { folder: 'davinci-mcp-professional', args: ['main.py', '--help'] },
2379
+ { folder: 'golem-rhino-mcp', args: ['-m', 'mcp_server', '--help'], uvArgs: true },
2380
+ { folder: 'davinci-mcp-professional', args: ['main.py', '--help'], uvArgs: true },
2203
2381
  { 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'] },
2382
+ { folder: 'blender-mcp', args: ['-m', 'blender_mcp.server', '--help'], uvArgs: true },
2205
2383
  { 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'] }
2384
+ { folder: 'windows-computer-use-mcp', args: ['run_server.py', '--help'], uvArgs: true }
2207
2385
  ];
2208
2386
  for (const mcp of pythonMcps.filter(m => selectedMcps.includes(m.folder))) {
2209
2387
  const targetFolder = path.join(mcpCodeTarget, mcp.folder);
@@ -2213,7 +2391,18 @@ async function installMcpsForTarget(paths, ctx) {
2213
2391
  log.step(`[dry-run] would pre-warm Python deps for ${mcp.folder}`);
2214
2392
  continue;
2215
2393
  }
2216
- spawnSync('uv', prewarmArgs, { cwd: targetFolder, stdio: 'ignore' });
2394
+ const result = spawnSync('uv', prewarmArgs, { cwd: targetFolder, stdio: 'ignore' });
2395
+ if (result.error) {
2396
+ // uv itself is missing or unrunnable. That is one machine-level
2397
+ // fact, not six per-MCP failures — say it once and stop trying.
2398
+ log.warn(`uv could not be run (${result.error.message}) — skipping the Python prewarm for the remaining MCPs.`);
2399
+ break;
2400
+ }
2401
+ if (result.status === 0) {
2402
+ log.step(`Pre-warmed Python deps for ${mcp.folder}`);
2403
+ } else {
2404
+ log.warn(`Prewarm failed for ${mcp.folder}: uv exited ${result.status}. The MCP is configured but its dependencies may not resolve.`);
2405
+ }
2217
2406
  }
2218
2407
  }
2219
2408
 
@@ -2236,6 +2425,28 @@ async function installMcpsForTarget(paths, ctx) {
2236
2425
  const parsedMcpConfig = JSON.parse(mcpConfigStr);
2237
2426
  const finalMcpServers = {};
2238
2427
  const availableFolders = fs.readdirSync(mcpSrcDir);
2428
+
2429
+ // A node MCP's build can fail (see above) or simply never run for a
2430
+ // dist-based server that was skipped -- either way an entry pointing
2431
+ // at a file that was never produced fails at MCP launch, far from the
2432
+ // real cause. Derive the check from each entry's own args (node's
2433
+ // first arg is always its entry script) instead of a hardcoded list of
2434
+ // MCP names, so a fifth dist-based entry added later is covered too.
2435
+ if (!DRY_RUN) {
2436
+ for (const [key, val] of Object.entries(parsedMcpConfig.mcpServers)) {
2437
+ if (val.command !== 'node' || !Array.isArray(val.args) || !val.args[0]) continue;
2438
+ const entryArg = val.args[0];
2439
+ if (!entryArg.startsWith('__MCPS_DIR__/')) continue;
2440
+ const relPath = entryArg.slice('__MCPS_DIR__/'.length);
2441
+ const mcpFolder = relPath.split('/')[0];
2442
+ if (!selectedMcps.includes(mcpFolder)) continue; // not installed at all, already handled below
2443
+ if (!fs.existsSync(path.join(mcpCodeTarget, relPath))) {
2444
+ log.warn(`${mcpFolder}: build produced no ${relPath.slice(mcpFolder.length + 1)} -- not registering ${key}`);
2445
+ skippedMcpConfigKeys.push(key);
2446
+ }
2447
+ }
2448
+ }
2449
+
2239
2450
  for (const [key, val] of Object.entries(parsedMcpConfig.mcpServers)) {
2240
2451
  let keep = !skippedMcpConfigKeys.includes(key);
2241
2452
  for (const available of availableFolders) {
@@ -2960,10 +3171,13 @@ async function promptOptionalModules(installedModules) {
2960
3171
  { id: 'synapse', name: 'BDB Synapse (3D Codebase Visualizer)', fn: installSynapse },
2961
3172
  { id: 'memb', name: 'memB Vector Engine (Local Semantic Memory)', fn: () => installMemB(true) },
2962
3173
  { 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.
3174
+ // Offered only where it can actually run -- see aoSupportedHere(). The
3175
+ // predecessor, @hybridlabor-api/bdb-os-agent-workspace, must never come
3176
+ // back: its repository is archived and its last release predates the
3177
+ // archiving, so it hands out the build with the CDC loop defect.
3178
+ ...(aoSupportedHere()
3179
+ ? [{ id: 'ao', name: 'AO Agent Orchestrator (Session telemetry & WebUI)', fn: installOSAgentWorkspace }]
3180
+ : []),
2967
3181
  { id: 'creator', name: 'BDB Creator Extension (Generative 3D, Video & ComfyUI)', fn: installCreatorExtension },
2968
3182
  { id: 'installer', name: 'BDB Dev Tool Installer (Interactive Hub & CLI Launcher)', fn: installDevToolInstaller }
2969
3183
  ];
@@ -3332,11 +3546,19 @@ async function runQuickUpdate(installState) {
3332
3546
  }
3333
3547
  }
3334
3548
  syncSkillsToGlobalHarnesses(excludeSkills);
3549
+ pruneRemovedSkills(_sessionManifest);
3335
3550
  s.stop('Skills refreshed');
3336
3551
 
3337
- installStep('refresh gate + memory hooks', () => {
3338
- installGlobalHooks();
3339
- }, 'Hooks keep whatever version this machine already had.');
3552
+ // Everything injectHarnessRules() delivers -- GEMINI.md, the dispatcher
3553
+ // workflows the skills point at, the compiled subagent definitions, the
3554
+ // harness rule files and the gate + memory hooks -- used to be
3555
+ // fresh-install-only. v4.4.1 split just the hooks out of it for Quick
3556
+ // Update; the rest stayed behind, so a machine that only ever quick-updates
3557
+ // kept running first-install agent definitions and dispatcher scripts
3558
+ // forever. Same defect as the hook, one layer up. Run the whole thing.
3559
+ installStep('refresh harness rules, workflows, agents and hooks', () => {
3560
+ injectHarnessRules();
3561
+ }, 'Harness files keep whatever version this machine already had.');
3340
3562
 
3341
3563
  // OpenWiki setup only ever ran on a brand-new install (main()'s fresh-install
3342
3564
  // branch below) -- an existing install running Quick Update never got offered
@@ -3357,7 +3579,7 @@ async function runQuickUpdate(installState) {
3357
3579
  if (subId === 'synapse') await installSynapse();
3358
3580
  else if (subId === 'memb') await installMemB(false);
3359
3581
  else if (subId === 'remote') await installOSRemoteGateway();
3360
- // 'ao' (bdb-os-agent-workspace) intentionally skipped here even for
3582
+ // 'ao' intentionally skipped here even for
3361
3583
  // existing installs -- see promptOptionalModules() for why.
3362
3584
  else if (subId === 'creator') await installCreatorExtension();
3363
3585
  else if (subId === 'installer') await installDevToolInstaller();
@@ -3398,7 +3620,10 @@ async function main() {
3398
3620
  console.log(buildTelemetryCard({ installState, detections, daemonStatus }));
3399
3621
 
3400
3622
  if (DRY_RUN) {
3401
- log.warn('DRY-RUN MODE active - no files will be modified, no commands executed.');
3623
+ // Precise on purpose: version comparison still queries the registry, and
3624
+ // npm writes its own cache and logs under ~/.npm doing so. Nothing AOS
3625
+ // owns is touched -- but claiming "no commands executed" was not true.
3626
+ log.warn('DRY-RUN MODE active - nothing AOS owns is written. Read-only registry lookups still run (npm caches under ~/.npm).');
3402
3627
  }
3403
3628
  if (isAutoYes) {
3404
3629
  log.warn('Non-interactive/auto mode (-y): all defaults are accepted automatically.');
@@ -3490,6 +3715,8 @@ async function main() {
3490
3715
  let wantsUniversal = true;
3491
3716
  let specificPlatforms = ['1'];
3492
3717
 
3718
+ markPlatformsExplicit(PLATFORMS_ARG || []);
3719
+
3493
3720
  if (PLATFORMS_ARG) {
3494
3721
  // Explicit targets win over both the menu and the auto-yes default.
3495
3722
  // '0' means universal; anything else means exactly those targets, so
@@ -3529,6 +3756,9 @@ async function main() {
3529
3756
  });
3530
3757
  if (sel === BACK) return 'back';
3531
3758
  ctx.selectedPlatforms = sel;
3759
+ // A harness the user names counts as present even when no binary
3760
+ // or app bundle turns up for it.
3761
+ markPlatformsExplicit(sel);
3532
3762
  };
3533
3763
 
3534
3764
  const stepMode = async () => {
@@ -3721,6 +3951,7 @@ async function main() {
3721
3951
  }
3722
3952
 
3723
3953
  syncSkillsToGlobalHarnesses(excludeSkills);
3954
+ pruneRemovedSkills(_sessionManifest);
3724
3955
  s.stop('Skills installed.');
3725
3956
 
3726
3957
  injectHarnessRules();
@@ -3742,7 +3973,6 @@ async function main() {
3742
3973
  const justInstalled = await promptOptionalModules(installedModulesForPrompt) || [];
3743
3974
 
3744
3975
  await promptMemBIngestion(path.join(primaryTarget.targetMcpDir, 'mcps'));
3745
- await promptEcosystemHealthScheduler();
3746
3976
 
3747
3977
  const alreadyHandled = justInstalled.map(id => MODULE_ID_TO_DAEMON_NAME[id]).filter(Boolean);
3748
3978
  await reloadDaemons(alreadyHandled);
@@ -3767,6 +3997,10 @@ if (require.main === module) {
3767
3997
 
3768
3998
  // Exported for tests -- requiring installer.js must not launch the TUI.
3769
3999
  module.exports = {
4000
+ installOSAgentWorkspace,
4001
+ downloadOrUpdateModule,
4002
+ detectPlatforms,
4003
+ markPlatformsExplicit,
3770
4004
  mergeBdbSettingsHooks,
3771
4005
  installGlobalHooks,
3772
4006
  installProjectHarness,