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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (306) hide show
  1. package/.agents/skills/firecrawl/SKILL.md +149 -0
  2. package/.agents/skills/firecrawl/rules/install.md +82 -0
  3. package/.agents/skills/firecrawl/rules/security.md +26 -0
  4. package/.agents/skills/firecrawl-agent/SKILL.md +58 -0
  5. package/.agents/skills/firecrawl-build/SKILL.md +39 -0
  6. package/.agents/skills/firecrawl-build-interact/SKILL.md +68 -0
  7. package/.agents/skills/firecrawl-build-onboarding/SKILL.md +103 -0
  8. package/.agents/skills/firecrawl-build-onboarding/references/auth-flow.md +39 -0
  9. package/.agents/skills/firecrawl-build-onboarding/references/project-setup.md +20 -0
  10. package/.agents/skills/firecrawl-build-onboarding/references/sdk-installation.md +17 -0
  11. package/.agents/skills/firecrawl-build-scrape/SKILL.md +69 -0
  12. package/.agents/skills/firecrawl-build-search/SKILL.md +69 -0
  13. package/.agents/skills/firecrawl-crawl/SKILL.md +59 -0
  14. package/.agents/skills/firecrawl-download/SKILL.md +70 -0
  15. package/.agents/skills/firecrawl-interact/SKILL.md +84 -0
  16. package/.agents/skills/firecrawl-map/SKILL.md +51 -0
  17. package/.agents/skills/firecrawl-scrape/SKILL.md +69 -0
  18. package/.agents/skills/firecrawl-search/SKILL.md +60 -0
  19. package/.claude/hooks/memb-inject.mjs +6 -0
  20. package/CLAUDE.md +573 -0
  21. package/README.de.md +0 -8
  22. package/README.md +0 -8
  23. package/README.pt.md +0 -8
  24. package/assets/brand/ao-ant-mark.svg +91 -0
  25. package/assets/brand/bdb-core-mark.svg +34 -0
  26. package/assets/brand/heimdall-guardian-mark.svg +16 -0
  27. package/assets/brand/memb-crystal-mark.svg +18 -0
  28. package/bin/aos-config.mjs +89 -0
  29. package/bin/aos-dashboard.mjs +608 -0
  30. package/bin/aos-uninstall.mjs +227 -0
  31. package/docs/sessions/audit-2026-09-14-silent-failures.md +114 -0
  32. package/docs/skills_table.md +0 -3
  33. package/installer.js +428 -237
  34. package/mcps/RhinoMCP/cc-plugin/.claude/settings.json +10 -0
  35. package/mcps/after-effects-mcp/build/index.js +840 -0
  36. package/mcps/after-effects-mcp/build/scripts/applyEffect.jsx +153 -0
  37. package/mcps/after-effects-mcp/build/scripts/applyEffectTemplate.jsx +218 -0
  38. package/mcps/after-effects-mcp/build/scripts/createComposition.jsx +71 -0
  39. package/mcps/after-effects-mcp/build/scripts/createShapeLayer.jsx +147 -0
  40. package/mcps/after-effects-mcp/build/scripts/createSolidLayer.jsx +114 -0
  41. package/mcps/after-effects-mcp/build/scripts/createTextLayer.jsx +115 -0
  42. package/mcps/after-effects-mcp/build/scripts/getLayerInfo.jsx +192 -0
  43. package/mcps/after-effects-mcp/build/scripts/getProjectInfo.jsx +90 -0
  44. package/mcps/after-effects-mcp/build/scripts/listCompositions.jsx +50 -0
  45. package/mcps/after-effects-mcp/build/scripts/mcp-bridge-auto.jsx +1773 -0
  46. package/mcps/after-effects-mcp/build/scripts/setLayerProperties.jsx +160 -0
  47. package/mcps/computer-use-mcp/dist/client.d.ts +150 -0
  48. package/mcps/computer-use-mcp/dist/client.js +136 -0
  49. package/mcps/computer-use-mcp/dist/entrypoint.d.ts +16 -0
  50. package/mcps/computer-use-mcp/dist/entrypoint.js +26 -0
  51. package/mcps/computer-use-mcp/dist/native.d.ts +212 -0
  52. package/mcps/computer-use-mcp/dist/native.js +50 -0
  53. package/mcps/computer-use-mcp/dist/server.d.ts +32 -0
  54. package/mcps/computer-use-mcp/dist/server.js +342 -0
  55. package/mcps/computer-use-mcp/dist/session.d.ts +101 -0
  56. package/mcps/computer-use-mcp/dist/session.js +2372 -0
  57. package/package.json +13 -4
  58. package/skills/global_config/aos-project-init/SKILL.md +97 -130
  59. package/skills/global_config/aos-project-init/assets/openwikiignore.template +62 -0
  60. package/skills/global_config/aos-project-init/scripts/aos-project-doctor.mjs +38 -1
  61. package/skills/global_config/aos-setup/SKILL.md +110 -11
  62. package/skills/global_config/aos-setup/scripts/aos-doctor.mjs +48 -4
  63. package/skills/global_config/ask-tim/SKILL.md +2 -7
  64. package/skills/global_config/openwiki-skill/scripts/openwiki_daemon.py +65 -12
  65. package/bin/setup-saas.mjs +0 -792
  66. package/mcps/blender-mcp-server/.dockerignore +0 -19
  67. package/mcps/blender-mcp-server/.github/workflows/ci.yml +0 -67
  68. package/mcps/blender-mcp-server/.github/workflows/publish-pypi.yml +0 -54
  69. package/mcps/blender-mcp-server/CHANGELOG.md +0 -64
  70. package/mcps/blender-mcp-server/CONTRIBUTING.md +0 -107
  71. package/mcps/blender-mcp-server/Dockerfile +0 -19
  72. package/mcps/blender-mcp-server/LICENSE +0 -21
  73. package/mcps/blender-mcp-server/README.md +0 -409
  74. package/mcps/blender-mcp-server/addon/__init__.py +0 -1440
  75. package/mcps/blender-mcp-server/addon/models.py +0 -223
  76. package/mcps/blender-mcp-server/pyproject.toml +0 -120
  77. package/mcps/blender-mcp-server/scripts/blender_bridge_request.py +0 -71
  78. package/mcps/blender-mcp-server/scripts/blender_create_test_cube.py +0 -49
  79. package/mcps/blender-mcp-server/scripts/blender_scene_info.py +0 -38
  80. package/mcps/blender-mcp-server/scripts/build_addon_zip.sh +0 -29
  81. package/mcps/blender-mcp-server/scripts/demos/README.md +0 -186
  82. package/mcps/blender-mcp-server/scripts/demos/dam_break_scene.py +0 -276
  83. package/mcps/blender-mcp-server/scripts/demos/open_dam_break_gui.py +0 -33
  84. package/mcps/blender-mcp-server/scripts/demos/pipe_movement_study.py +0 -518
  85. package/mcps/blender-mcp-server/scripts/demos/pipe_pressure_failure_scene.py +0 -806
  86. package/mcps/blender-mcp-server/scripts/demos/procedural_dam_break_scene.py +0 -600
  87. package/mcps/blender-mcp-server/scripts/demos/run_dam_break.py +0 -510
  88. package/mcps/blender-mcp-server/scripts/launch_blender_gui.py +0 -41
  89. package/mcps/blender-mcp-server/scripts/library/README.md +0 -64
  90. package/mcps/blender-mcp-server/scripts/library/apply_transforms.py +0 -46
  91. package/mcps/blender-mcp-server/scripts/library/camera.py +0 -51
  92. package/mcps/blender-mcp-server/scripts/library/collections.py +0 -65
  93. package/mcps/blender-mcp-server/scripts/library/create_mesh.py +0 -41
  94. package/mcps/blender-mcp-server/scripts/library/effector.py +0 -48
  95. package/mcps/blender-mcp-server/scripts/library/fluid_domain.py +0 -80
  96. package/mcps/blender-mcp-server/scripts/library/fluid_inflow.py +0 -98
  97. package/mcps/blender-mcp-server/scripts/library/frame_range.py +0 -35
  98. package/mcps/blender-mcp-server/scripts/library/keyframes.py +0 -59
  99. package/mcps/blender-mcp-server/scripts/library/rigid_body.py +0 -52
  100. package/mcps/blender-mcp-server/scripts/library/save_blend.py +0 -40
  101. package/mcps/blender-mcp-server/scripts/server_start.sh +0 -26
  102. package/mcps/blender-mcp-server/src/blender_mcp_server/__init__.py +0 -3
  103. package/mcps/blender-mcp-server/src/blender_mcp_server/headless.py +0 -330
  104. package/mcps/blender-mcp-server/src/blender_mcp_server/server.py +0 -606
  105. package/mcps/davinci-mcp-professional/.bandit +0 -35
  106. package/mcps/davinci-mcp-professional/.claude/commands/security-review.md +0 -191
  107. package/mcps/davinci-mcp-professional/.claude/settings.json +0 -1
  108. package/mcps/davinci-mcp-professional/.editorconfig +0 -27
  109. package/mcps/davinci-mcp-professional/.gitattributes +0 -207
  110. package/mcps/davinci-mcp-professional/.github/CODEOWNERS +0 -2
  111. package/mcps/davinci-mcp-professional/.github/ISSUE_TEMPLATE/bug_report.md +0 -33
  112. package/mcps/davinci-mcp-professional/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
  113. package/mcps/davinci-mcp-professional/.github/PULL_REQUEST_TEMPLATE.md +0 -20
  114. package/mcps/davinci-mcp-professional/.github/SECURITY.md +0 -97
  115. package/mcps/davinci-mcp-professional/.github/dependabot.yml +0 -27
  116. package/mcps/davinci-mcp-professional/.github/workflows/ci.yml +0 -39
  117. package/mcps/davinci-mcp-professional/ATTRIBUTION.md +0 -4
  118. package/mcps/davinci-mcp-professional/AUTHORS.md +0 -9
  119. package/mcps/davinci-mcp-professional/BUGS.md +0 -55
  120. package/mcps/davinci-mcp-professional/CLAUDE.md +0 -229
  121. package/mcps/davinci-mcp-professional/CONTRIBUTING.md +0 -51
  122. package/mcps/davinci-mcp-professional/COPYING.md +0 -228
  123. package/mcps/davinci-mcp-professional/Doxyfile +0 -379
  124. package/mcps/davinci-mcp-professional/README.md +0 -143
  125. package/mcps/davinci-mcp-professional/SECURITY.md +0 -40
  126. package/mcps/davinci-mcp-professional/USING.md +0 -301
  127. package/mcps/davinci-mcp-professional/VERSION.md +0 -13
  128. package/mcps/davinci-mcp-professional/build.py +0 -169
  129. package/mcps/davinci-mcp-professional/claude_desktop_config_template.json +0 -11
  130. package/mcps/davinci-mcp-professional/hooks/ci-check.sh +0 -29
  131. package/mcps/davinci-mcp-professional/hooks/pre-push +0 -46
  132. package/mcps/davinci-mcp-professional/main.py +0 -40
  133. package/mcps/davinci-mcp-professional/mcp_server.py +0 -30
  134. package/mcps/davinci-mcp-professional/pyproject.toml +0 -115
  135. package/mcps/davinci-mcp-professional/security_audit.py +0 -379
  136. package/mcps/davinci-mcp-professional/src/davinci_mcp/__init__.py +0 -19
  137. package/mcps/davinci-mcp-professional/src/davinci_mcp/cli.py +0 -138
  138. package/mcps/davinci-mcp-professional/src/davinci_mcp/resolve_client.py +0 -342
  139. package/mcps/davinci-mcp-professional/src/davinci_mcp/resources/__init__.py +0 -60
  140. package/mcps/davinci-mcp-professional/src/davinci_mcp/server.py +0 -185
  141. package/mcps/davinci-mcp-professional/src/davinci_mcp/tools/__init__.py +0 -146
  142. package/mcps/davinci-mcp-professional/src/davinci_mcp/types.py +0 -125
  143. package/mcps/davinci-mcp-professional/src/davinci_mcp/utils/__init__.py +0 -19
  144. package/mcps/davinci-mcp-professional/src/davinci_mcp/utils/platform.py +0 -239
  145. package/mcps/davinci-mcp-professional/test.py +0 -186
  146. package/mcps/davinci-mcp-professional/uv.lock +0 -2073
  147. package/mcps/davinci-resolve-mcp/.claude/skills/media-analysis.md +0 -44
  148. package/mcps/davinci-resolve-mcp/.clinerules +0 -17
  149. package/mcps/davinci-resolve-mcp/.cursorrules +0 -17
  150. package/mcps/davinci-resolve-mcp/.github/copilot-instructions.md +0 -17
  151. package/mcps/davinci-resolve-mcp/.github/workflows/npm-publish.yml +0 -109
  152. package/mcps/davinci-resolve-mcp/.windsurfrules +0 -17
  153. package/mcps/davinci-resolve-mcp/AGENTS.md +0 -114
  154. package/mcps/davinci-resolve-mcp/CHANGELOG.md +0 -2648
  155. package/mcps/davinci-resolve-mcp/CLAUDE.md +0 -15
  156. package/mcps/davinci-resolve-mcp/LICENSE +0 -21
  157. package/mcps/davinci-resolve-mcp/README.md +0 -173
  158. package/mcps/davinci-resolve-mcp/SECURITY.md +0 -53
  159. package/mcps/davinci-resolve-mcp/bin/davinci-resolve-mcp.mjs +0 -450
  160. package/mcps/davinci-resolve-mcp/examples/README.md +0 -53
  161. package/mcps/davinci-resolve-mcp/examples/markers/README.md +0 -81
  162. package/mcps/davinci-resolve-mcp/examples/media/README.md +0 -94
  163. package/mcps/davinci-resolve-mcp/examples/timeline/README.md +0 -98
  164. package/mcps/davinci-resolve-mcp/install.py +0 -1687
  165. package/mcps/davinci-resolve-mcp/package.json +0 -52
  166. package/mcps/davinci-resolve-mcp/release-notes/v2.22.0.md +0 -51
  167. package/mcps/davinci-resolve-mcp/release-notes/v2.23.0.md +0 -51
  168. package/mcps/davinci-resolve-mcp/release-notes/v2.23.1.md +0 -35
  169. package/mcps/davinci-resolve-mcp/release-notes/v2.24.1.md +0 -32
  170. package/mcps/davinci-resolve-mcp/scripts/audit_api_parity.py +0 -275
  171. package/mcps/davinci-resolve-mcp/scripts/audit_readwrite_symmetry.py +0 -80
  172. package/mcps/davinci-resolve-mcp/scripts/doctor.py +0 -227
  173. package/mcps/davinci-resolve-mcp/scripts/gen_api_limitations.py +0 -144
  174. package/mcps/davinci-resolve-mcp/scripts/live_media_analysis_polish_probe.py +0 -65
  175. package/mcps/davinci-resolve-mcp/scripts/measure_bridge_cost.py +0 -65
  176. package/mcps/davinci-resolve-mcp/scripts/regen_panel_screenshots.py +0 -76
  177. package/mcps/davinci-resolve-mcp/src/__init__.py +0 -3
  178. package/mcps/davinci-resolve-mcp/src/analysis_dashboard.py +0 -15505
  179. package/mcps/davinci-resolve-mcp/src/batch_cli.py +0 -540
  180. package/mcps/davinci-resolve-mcp/src/control_panel.py +0 -13
  181. package/mcps/davinci-resolve-mcp/src/granular/__init__.py +0 -17
  182. package/mcps/davinci-resolve-mcp/src/granular/common.py +0 -741
  183. package/mcps/davinci-resolve-mcp/src/granular/folder.py +0 -420
  184. package/mcps/davinci-resolve-mcp/src/granular/gallery.py +0 -306
  185. package/mcps/davinci-resolve-mcp/src/granular/graph.py +0 -309
  186. package/mcps/davinci-resolve-mcp/src/granular/media_pool.py +0 -679
  187. package/mcps/davinci-resolve-mcp/src/granular/media_pool_item.py +0 -1020
  188. package/mcps/davinci-resolve-mcp/src/granular/media_storage.py +0 -179
  189. package/mcps/davinci-resolve-mcp/src/granular/project.py +0 -1654
  190. package/mcps/davinci-resolve-mcp/src/granular/resolve_control.py +0 -538
  191. package/mcps/davinci-resolve-mcp/src/granular/timeline.py +0 -1075
  192. package/mcps/davinci-resolve-mcp/src/granular/timeline_item.py +0 -2251
  193. package/mcps/davinci-resolve-mcp/src/resolve_mcp_server.py +0 -43
  194. package/mcps/davinci-resolve-mcp/src/server.py +0 -24116
  195. package/mcps/davinci-resolve-mcp/src/utils/__init__.py +0 -3
  196. package/mcps/davinci-resolve-mcp/src/utils/actor_identity.py +0 -46
  197. package/mcps/davinci-resolve-mcp/src/utils/analysis_caps.py +0 -678
  198. package/mcps/davinci-resolve-mcp/src/utils/analysis_memory.py +0 -755
  199. package/mcps/davinci-resolve-mcp/src/utils/analysis_runs.py +0 -307
  200. package/mcps/davinci-resolve-mcp/src/utils/analysis_store.py +0 -945
  201. package/mcps/davinci-resolve-mcp/src/utils/api_truth.py +0 -525
  202. package/mcps/davinci-resolve-mcp/src/utils/app_control.py +0 -319
  203. package/mcps/davinci-resolve-mcp/src/utils/audio_fairlight_live_probe.py +0 -263
  204. package/mcps/davinci-resolve-mcp/src/utils/brain_edits.py +0 -387
  205. package/mcps/davinci-resolve-mcp/src/utils/bridge_metrics.py +0 -76
  206. package/mcps/davinci-resolve-mcp/src/utils/cdl.py +0 -20
  207. package/mcps/davinci-resolve-mcp/src/utils/clip_query.py +0 -85
  208. package/mcps/davinci-resolve-mcp/src/utils/cloud_operations.py +0 -192
  209. package/mcps/davinci-resolve-mcp/src/utils/color_grade_live_probe.py +0 -444
  210. package/mcps/davinci-resolve-mcp/src/utils/contracts.py +0 -102
  211. package/mcps/davinci-resolve-mcp/src/utils/cut_ir.py +0 -115
  212. package/mcps/davinci-resolve-mcp/src/utils/dctl_templates.py +0 -368
  213. package/mcps/davinci-resolve-mcp/src/utils/deep_vision.py +0 -643
  214. package/mcps/davinci-resolve-mcp/src/utils/destructive_hook.py +0 -606
  215. package/mcps/davinci-resolve-mcp/src/utils/edit_engine.py +0 -737
  216. package/mcps/davinci-resolve-mcp/src/utils/embeddings.py +0 -922
  217. package/mcps/davinci-resolve-mcp/src/utils/entities.py +0 -579
  218. package/mcps/davinci-resolve-mcp/src/utils/extension_authoring_live_probe.py +0 -292
  219. package/mcps/davinci-resolve-mcp/src/utils/failure_tracker.py +0 -119
  220. package/mcps/davinci-resolve-mcp/src/utils/fuse_templates.py +0 -1968
  221. package/mcps/davinci-resolve-mcp/src/utils/fusion_composition_live_probe.py +0 -284
  222. package/mcps/davinci-resolve-mcp/src/utils/fusion_group_settings.py +0 -334
  223. package/mcps/davinci-resolve-mcp/src/utils/layout_presets.py +0 -333
  224. package/mcps/davinci-resolve-mcp/src/utils/mcp_stdio.py +0 -32
  225. package/mcps/davinci-resolve-mcp/src/utils/mcp_transport.py +0 -128
  226. package/mcps/davinci-resolve-mcp/src/utils/media_analysis.py +0 -7690
  227. package/mcps/davinci-resolve-mcp/src/utils/media_analysis_jobs.py +0 -805
  228. package/mcps/davinci-resolve-mcp/src/utils/media_pool_changes.py +0 -121
  229. package/mcps/davinci-resolve-mcp/src/utils/media_pool_ingest_live_probe.py +0 -592
  230. package/mcps/davinci-resolve-mcp/src/utils/multicam.py +0 -393
  231. package/mcps/davinci-resolve-mcp/src/utils/object_inspection.py +0 -301
  232. package/mcps/davinci-resolve-mcp/src/utils/page_lock.py +0 -75
  233. package/mcps/davinci-resolve-mcp/src/utils/platform.py +0 -157
  234. package/mcps/davinci-resolve-mcp/src/utils/proc.py +0 -26
  235. package/mcps/davinci-resolve-mcp/src/utils/project_cleanup.py +0 -73
  236. package/mcps/davinci-resolve-mcp/src/utils/project_lifecycle_live_probe.py +0 -376
  237. package/mcps/davinci-resolve-mcp/src/utils/project_lint.py +0 -131
  238. package/mcps/davinci-resolve-mcp/src/utils/project_properties.py +0 -601
  239. package/mcps/davinci-resolve-mcp/src/utils/project_spec.py +0 -459
  240. package/mcps/davinci-resolve-mcp/src/utils/readback.py +0 -91
  241. package/mcps/davinci-resolve-mcp/src/utils/render_deliver_live_probe.py +0 -384
  242. package/mcps/davinci-resolve-mcp/src/utils/resolve_ai_governance.py +0 -214
  243. package/mcps/davinci-resolve-mcp/src/utils/resolve_ai_ledger.py +0 -244
  244. package/mcps/davinci-resolve-mcp/src/utils/resolve_busy.py +0 -171
  245. package/mcps/davinci-resolve-mcp/src/utils/resolve_connection.py +0 -77
  246. package/mcps/davinci-resolve-mcp/src/utils/review_annotation_live_probe.py +0 -352
  247. package/mcps/davinci-resolve-mcp/src/utils/script_templates.py +0 -1193
  248. package/mcps/davinci-resolve-mcp/src/utils/shot_relationships.py +0 -544
  249. package/mcps/davinci-resolve-mcp/src/utils/structural_diff.py +0 -175
  250. package/mcps/davinci-resolve-mcp/src/utils/sync_detection.py +0 -889
  251. package/mcps/davinci-resolve-mcp/src/utils/timeline_brain_db.py +0 -826
  252. package/mcps/davinci-resolve-mcp/src/utils/timeline_conform_live_probe.py +0 -280
  253. package/mcps/davinci-resolve-mcp/src/utils/timeline_kernel_live_probe.py +0 -1092
  254. package/mcps/davinci-resolve-mcp/src/utils/timeline_kernel_probe.py +0 -185
  255. package/mcps/davinci-resolve-mcp/src/utils/timeline_title_text.py +0 -87
  256. package/mcps/davinci-resolve-mcp/src/utils/timeline_versioning.py +0 -730
  257. package/mcps/davinci-resolve-mcp/src/utils/update_check.py +0 -685
  258. package/mcps/vectorworks-mcp/.dockerignore +0 -8
  259. package/mcps/vectorworks-mcp/Dockerfile +0 -28
  260. package/mcps/vectorworks-mcp/README.md +0 -156
  261. package/mcps/vectorworks-mcp/app/__init__.py +0 -2
  262. package/mcps/vectorworks-mcp/app/api.py +0 -52
  263. package/mcps/vectorworks-mcp/app/chunking.py +0 -56
  264. package/mcps/vectorworks-mcp/app/config.py +0 -29
  265. package/mcps/vectorworks-mcp/app/embeddings.py +0 -28
  266. package/mcps/vectorworks-mcp/app/indexer.py +0 -106
  267. package/mcps/vectorworks-mcp/app/main.py +0 -33
  268. package/mcps/vectorworks-mcp/app/mcp_server.py +0 -71
  269. package/mcps/vectorworks-mcp/app/search.py +0 -54
  270. package/mcps/vectorworks-mcp/app/utils.py +0 -14
  271. package/mcps/vectorworks-mcp/data/.gitkeep +0 -1
  272. package/mcps/vectorworks-mcp/data/vw-app-help/VW2022_PluginScript.htm +0 -95
  273. package/mcps/vectorworks-mcp/data/vw-app-help/VW2022_ScriptEditor.htm +0 -266
  274. package/mcps/vectorworks-mcp/data/vw-app-help/VW2022_Scripting.htm +0 -106
  275. package/mcps/vectorworks-mcp/data/vw-app-help/VW2023_CreatingScriptedPlugins.htm +0 -327
  276. package/mcps/vectorworks-mcp/data/vw-app-help/VW2024_RunningScripts.htm +0 -116
  277. package/mcps/vectorworks-mcp/data/vw-devwiki/VS_Function_Reference_category.html +0 -2129
  278. package/mcps/vectorworks-mcp/data/vw-jp-ref/ObjectsSymbols.html +0 -2035
  279. package/mcps/vectorworks-mcp/data/vw-jp-ref/ScriptFunctionReference.html +0 -29
  280. package/mcps/vectorworks-mcp/design.md +0 -57
  281. package/mcps/vectorworks-mcp/docker-compose.yml +0 -18
  282. package/mcps/vectorworks-mcp/index/.gitkeep +0 -1
  283. package/mcps/vectorworks-mcp/index/meta.jsonl +0 -3956
  284. package/mcps/vectorworks-mcp/index/vw.faiss +0 -0
  285. package/mcps/vectorworks-mcp/requirements.txt +0 -10
  286. package/mcps/vectorworks-mcp/scripts/fetch_docs_minimal.sh +0 -84
  287. package/mcps/vectorworks-mcp/scripts/fetch_github_vectorworks.sh +0 -85
  288. package/mcps/vectorworks-mcp/templates/index.html +0 -40
  289. package/scripts/ecosystem-health-audit.js +0 -257
  290. package/skills/bdb-dev-os-skill/SKILL.md +0 -49
  291. package/skills/bdb-dev-os-skill/package.json +0 -11
  292. package/skills/bdbsaastraining/SKILL.md +0 -316
  293. package/skills/bdbsaastraining/references/exam-pool.md +0 -313
  294. package/skills/bdbsaastraining/references/interaction-modes.md +0 -94
  295. package/skills/bdbsaastraining/references/track-a-ai-agent.md +0 -111
  296. package/skills/bdbsaastraining/references/track-b-wordpress.md +0 -111
  297. package/skills/bdbsaastraining/references/track-c-mailserver.md +0 -107
  298. package/skills/bdbsaastraining/references/track-d-debian.md +0 -109
  299. package/skills/bdbsaastraining/references/track-e-custom.md +0 -83
  300. package/skills/bdbsaastraining/scripts/build_profile.py +0 -529
  301. package/skills/bdbsaastraining/scripts/generate_certificate.py +0 -161
  302. package/skills/bdbsaastraining/scripts/preflight_check.sh +0 -150
  303. package/skills/bdbsaastraining/templates/certificate_template.html +0 -508
  304. package/skills/global_config/bdb-ecosystem-health/SKILL.md +0 -68
  305. package/skills/global_config/bdbsaas-ops/SKILL.md +0 -16
  306. package/skills/global_config/bdbsaashost/SKILL.md +0 -125
@@ -0,0 +1,608 @@
1
+ #!/usr/bin/env node
2
+ // AOS Dashboard — one page showing every BDB service, live, with start/stop
3
+ // and log access. Zero dependencies: node:http plus a TCP probe.
4
+ //
5
+ // aos-dashboard [--port 7900] [--no-open]
6
+ //
7
+ // Binds to 127.0.0.1 only. Control actions run launchctl against a fixed
8
+ // service table — the request never reaches a shell, and an id that is not in
9
+ // the table is refused, so nothing a browser sends can widen what this can do.
10
+
11
+ import { createServer } from 'node:http';
12
+ import { connect, createServer as createTcpServer } from 'node:net';
13
+ import { execFile, spawn } from 'node:child_process';
14
+ import { readFile, stat } from 'node:fs/promises';
15
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
16
+ import { fileURLToPath } from 'node:url';
17
+ import { promisify } from 'node:util';
18
+ import os from 'node:os';
19
+ import path from 'node:path';
20
+
21
+ const execFileP = promisify(execFile);
22
+ const HOME = os.homedir();
23
+ const h = (...p) => path.join(HOME, ...p);
24
+ const IS_MAC = process.platform === 'darwin';
25
+ const PKG_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
26
+ const BRAND_DIR = path.join(PKG_ROOT, 'assets', 'brand');
27
+
28
+ const argOf = (name, fallback) => {
29
+ const i = process.argv.indexOf(name);
30
+ return i !== -1 && process.argv[i + 1] ? process.argv[i + 1] : fallback;
31
+ };
32
+ const PORT = Number(argOf('--port', 7900));
33
+ const OPEN = !process.argv.includes('--no-open');
34
+
35
+ // The single source of truth. Everything the dashboard can see or do is here;
36
+ // there is no path by which a request adds to it.
37
+ const SERVICES = [
38
+ {
39
+ id: 'memb', name: 'memB', role: 'Persistent agent memory',
40
+ mark: 'memb-crystal-mark.svg',
41
+ port: 8088, url: 'http://127.0.0.1:8088', agent: 'com.bdb.memb.webui',
42
+ module: h('.agents', 'memB'), pkg: '@hybridlabor-api/memb',
43
+ logs: [h('.memb', 'webui.stdout.log'), h('.memb', 'webui.stderr.log')],
44
+ },
45
+ {
46
+ id: 'synapse', name: 'Synapse 3D', role: 'Spatial codebase map',
47
+ mark: 'bdb-core-mark.svg', muted: true,
48
+ port: 7781, url: 'http://127.0.0.1:7781', agent: 'com.bdb.synapse',
49
+ module: h('.agents', 'bdb-synapse'), pkg: '@hybridlabor-api/bdb-synapse',
50
+ logs: [h('.synapse', 'daemon.stdout.log'), h('.synapse', 'daemon.stderr.log')],
51
+ },
52
+ {
53
+ id: 'openwiki', name: 'OpenWiki', role: 'Refreshes project wikis every 2h',
54
+ mark: 'bdb-core-mark.svg', muted: true,
55
+ port: null, url: null, agent: 'com.bdb.openwiki.daemon',
56
+ module: null, pkg: null,
57
+ logs: [h('.openwiki', 'daemon.log'), h('.openwiki', 'daemon_stderr.log')],
58
+ // The daemon is machine-global, but the graph viewer is per repository —
59
+ // so this card offers a project list rather than one "open" button.
60
+ projectsFile: h('.openwiki', 'projects.json'),
61
+ },
62
+ {
63
+ id: 'ao', name: 'AO Orchestrator', role: 'Agent workspace daemon',
64
+ mark: 'ao-ant-mark.svg',
65
+ port: 3101, url: 'http://127.0.0.1:3101', agent: 'com.bdb.ao.daemon',
66
+ // Deliberately no module/pkg. Reading ~/.agents/bdb-os-agent-workspace
67
+ // reported the version of the ARCHIVED npm package (1.0.2, last published
68
+ // 2026-08-18) while the binary actually running was built from the current
69
+ // fork — a version string that looked authoritative and was wrong. AO now
70
+ // reports the version of the binary that is really running, and nothing
71
+ // else. The published package exists, but the binary actually in place is
72
+ // frequently a local build from a fork branch and diverges from it.
73
+ module: null, pkg: null,
74
+ version: async () => {
75
+ const bin = h('.local', 'bin', 'ao');
76
+ if (!existsSync(bin)) return null;
77
+ try { return (await execFileP(bin, ['--version'])).stdout.trim().replace(/^ao version\s*/i, '') || null; }
78
+ catch { return null; }
79
+ },
80
+ logs: [h('.ao', 'daemon.log')],
81
+ // An `ao` binary rebuilt and copied into place without being re-signed is
82
+ // SIGKILLed by AMFI on launch (exit 137). The daemon then reads as simply
83
+ // "down" with nothing in the log explaining why, so name the cause here.
84
+ health: async () => {
85
+ const bin = h('.local', 'bin', 'ao');
86
+ if (!existsSync(bin)) return { level: 'warn', text: 'kein ao-Binary unter ~/.local/bin/ao' };
87
+ if (!IS_MAC) return null;
88
+ try {
89
+ await execFileP('codesign', ['-v', bin]);
90
+ return null;
91
+ } catch {
92
+ return { level: 'error', text: 'Binary ist nicht signiert — AMFI beendet es beim Start (Exit 137). Beheben: codesign -s - -f ~/.local/bin/ao' };
93
+ }
94
+ },
95
+ },
96
+ {
97
+ id: 'remoteos', name: 'RemoteOS', role: 'Multi-cloud execution gateway',
98
+ mark: 'bdb-core-mark.svg', muted: true,
99
+ port: 9080, url: 'http://127.0.0.1:9080', agent: 'com.hybridlabor.bdb-remote',
100
+ module: h('.agents', 'bdb-os-remote'), pkg: '@hybridlabor-api/bdb-os-remote',
101
+ logs: [],
102
+ },
103
+ ];
104
+
105
+ const byId = new Map(SERVICES.map((s) => [s.id, s]));
106
+
107
+ // ------------------------------------------------------------------ probing
108
+ function portOpen(port, timeout = 800) {
109
+ return new Promise((resolve) => {
110
+ if (!port) return resolve(null);
111
+ const sock = connect({ host: '127.0.0.1', port });
112
+ const done = (v) => { sock.destroy(); resolve(v); };
113
+ sock.setTimeout(timeout);
114
+ sock.once('connect', () => done(true));
115
+ sock.once('timeout', () => done(false));
116
+ sock.once('error', () => done(false));
117
+ });
118
+ }
119
+
120
+ const freePort = () => new Promise((resolve, reject) => {
121
+ const srv = createTcpServer();
122
+ srv.once('error', reject);
123
+ srv.listen(0, '127.0.0.1', () => {
124
+ const { port } = srv.address();
125
+ srv.close(() => resolve(port));
126
+ });
127
+ });
128
+
129
+ async function agentLoaded(label) {
130
+ if (!IS_MAC) return null;
131
+ try {
132
+ const { stdout } = await execFileP('launchctl', ['list']);
133
+ return stdout.split('\n').some((l) => l.trim().endsWith(label));
134
+ } catch { return null; }
135
+ }
136
+
137
+ const localVersion = (dir) => {
138
+ if (!dir) return null;
139
+ try { return JSON.parse(readFileSync(path.join(dir, 'package.json'), 'utf8')).version || null; }
140
+ catch { return null; }
141
+ };
142
+
143
+ // ------------------------------------------------- version + update checking
144
+ // npm view costs seconds, so it never runs inside a status request. The page
145
+ // renders from local state immediately and asks for this separately; results
146
+ // are cached, and a failed lookup is reported as "not checkable" rather than
147
+ // silently passing for "current" — the mistake that let stale modules sit
148
+ // unnoticed for releases.
149
+ const UPDATE_TTL_MS = 30 * 60 * 1000;
150
+ let updateCache = { at: 0, data: null, checking: false };
151
+
152
+ async function npmVersion(pkg) {
153
+ try {
154
+ const { stdout } = await execFileP('npm', ['view', pkg, 'version'], { timeout: 12000 });
155
+ return stdout.trim() || null;
156
+ } catch { return null; }
157
+ }
158
+
159
+ const AOS_VERSION = localVersion(PKG_ROOT);
160
+
161
+ async function collectUpdates() {
162
+ const targets = [
163
+ { id: 'aos', pkg: '@hybridlabor-api/aos', local: AOS_VERSION },
164
+ ...SERVICES.filter((s) => s.pkg).map((s) => ({ id: s.id, pkg: s.pkg, local: localVersion(s.module) })),
165
+ ];
166
+ const out = {};
167
+ await Promise.all(targets.map(async (t) => {
168
+ if (!t.local) { out[t.id] = { state: 'unknown' }; return; }
169
+ const remote = await npmVersion(t.pkg);
170
+ out[t.id] = remote === null
171
+ ? { state: 'uncheckable', local: t.local }
172
+ : remote === t.local
173
+ ? { state: 'current', local: t.local }
174
+ : { state: 'outdated', local: t.local, remote };
175
+ }));
176
+ return out;
177
+ }
178
+
179
+ async function updates({ force = false } = {}) {
180
+ const fresh = Date.now() - updateCache.at < UPDATE_TTL_MS;
181
+ if (updateCache.data && fresh && !force) return updateCache.data;
182
+ if (updateCache.checking) return updateCache.data || {};
183
+ updateCache.checking = true;
184
+ try {
185
+ updateCache = { at: Date.now(), data: await collectUpdates(), checking: false };
186
+ } catch {
187
+ updateCache.checking = false;
188
+ }
189
+ return updateCache.data || {};
190
+ }
191
+
192
+ // ------------------------------------------------------------------ status
193
+ async function snapshot() {
194
+ return Promise.all(SERVICES.map(async (s) => {
195
+ const [listening, loaded] = await Promise.all([portOpen(s.port), agentLoaded(s.agent)]);
196
+ // A service may know its own version better than a package.json does.
197
+ let version = localVersion(s.module);
198
+ if (typeof s.version === 'function') { try { version = await s.version(); } catch { version = null; } }
199
+ // A port-less service (OpenWiki) can only be judged by its LaunchAgent.
200
+ const up = s.port ? listening : loaded;
201
+ const logs = [];
202
+ for (const f of s.logs) {
203
+ if (!existsSync(f)) continue;
204
+ let size = 0;
205
+ try { size = (await stat(f)).size; } catch { /* raced with a rotate */ }
206
+ logs.push({ name: path.basename(f), size });
207
+ }
208
+ let health = null;
209
+ if (s.health) { try { health = await s.health(); } catch { /* a health probe must never sink the page */ } }
210
+ return {
211
+ id: s.id, name: s.name, role: s.role, mark: s.mark, muted: !!s.muted,
212
+ port: s.port, url: s.url, up, loaded, version,
213
+ logs, health, installed: !s.module || existsSync(s.module),
214
+ projects: s.projectsFile ? openWikiProjects() : null,
215
+ viewers: s.id === 'openwiki' ? [...viewers.entries()].map(([p, v]) => ({ project: p, port: v.port })) : null,
216
+ };
217
+ }));
218
+ }
219
+
220
+ // ------------------------------------------------------------- openwiki view
221
+ const viewers = new Map(); // project path -> { port, child }
222
+
223
+ // A wiki exists wherever a repo has a .openwiki directory. projects.json is
224
+ // only what the refresh daemon has been told about — showing that list alone
225
+ // hid the fact that 31 wikis existed and 3 were being refreshed.
226
+ let wikiCache = { at: 0, list: [] };
227
+ const WIKI_TTL_MS = 60 * 1000;
228
+
229
+ function discoverWikis(root = path.join(HOME, 'dev'), depth = 4) {
230
+ const out = [];
231
+ const walk = (dir, left) => {
232
+ if (left < 0) return;
233
+ let entries = [];
234
+ try { entries = readdirSync(dir, { withFileTypes: true }); } catch { return; }
235
+ if (entries.some((e) => e.isDirectory() && e.name === '.openwiki')) { out.push(dir); return; }
236
+ for (const e of entries) {
237
+ if (!e.isDirectory() || e.name.startsWith('.')) continue;
238
+ if (['node_modules', 'venv', '__pycache__'].includes(e.name)) continue;
239
+ walk(path.join(dir, e.name), left - 1);
240
+ }
241
+ };
242
+ walk(root, depth);
243
+ return out.sort();
244
+ }
245
+
246
+ function openWikiProjects() {
247
+ if (Date.now() - wikiCache.at < WIKI_TTL_MS) return wikiCache.list;
248
+ const svc = byId.get('openwiki');
249
+ let tracked = [];
250
+ try { tracked = JSON.parse(readFileSync(svc.projectsFile, 'utf8')).projects || []; } catch { /* not written yet */ }
251
+ const trackedSet = new Set(tracked);
252
+
253
+ const list = [...new Set([...tracked, ...discoverWikis()])]
254
+ .filter((p) => existsSync(path.join(p, '.openwiki')))
255
+ .map((p) => {
256
+ // Freshness comes from the wiki's own pages, which is what the daemon
257
+ // rewrites — a repo that has not been visited shows its real age.
258
+ let newest = 0;
259
+ try {
260
+ for (const f of readdirSync(path.join(p, '.openwiki'))) {
261
+ if (!f.endsWith('.md')) continue;
262
+ const m = statSync(path.join(p, '.openwiki', f)).mtimeMs;
263
+ if (m > newest) newest = m;
264
+ }
265
+ } catch { /* unreadable is simply unknown */ }
266
+ return { path: p, name: path.basename(p), tracked: trackedSet.has(p), days: newest ? Math.floor((Date.now() - newest) / 86400000) : null };
267
+ })
268
+ .sort((a, b) => (a.days ?? 1e9) - (b.days ?? 1e9));
269
+
270
+ wikiCache = { at: Date.now(), list };
271
+ return list;
272
+ }
273
+
274
+ async function startViewer(projectPath) {
275
+ // Only a path this machine already tracks may be launched; nothing from the
276
+ // request is used as a path, and the port is chosen here, not by the caller.
277
+ const known = openWikiProjects().some((p) => p.path === projectPath);
278
+ if (!known) throw new Error('unbekanntes Projekt');
279
+ const running = viewers.get(projectPath);
280
+ if (running && await portOpen(running.port)) return { port: running.port };
281
+
282
+ const port = await freePort();
283
+ const child = spawn('openwiki', ['visualize', projectPath, '--port', String(port), '--no-open'], {
284
+ stdio: 'ignore', detached: false,
285
+ });
286
+ child.on('exit', () => { if (viewers.get(projectPath)?.child === child) viewers.delete(projectPath); });
287
+ viewers.set(projectPath, { port, child });
288
+
289
+ for (let i = 0; i < 40; i++) { // up to ~10s for it to bind
290
+ if (await portOpen(port, 250)) return { port };
291
+ await new Promise((r) => setTimeout(r, 250));
292
+ }
293
+ throw new Error('Visualizer ist nicht gestartet — läuft `openwiki` auf dem PATH?');
294
+ }
295
+
296
+ const stopViewers = () => { for (const { child } of viewers.values()) { try { child.kill(); } catch { /* already gone */ } } };
297
+
298
+ // ------------------------------------------------------------------ control
299
+ async function control(id, action) {
300
+ const svc = byId.get(id);
301
+ if (!svc) throw new Error('unknown service');
302
+ if (!['start', 'stop', 'restart'].includes(action)) throw new Error('unknown action');
303
+ if (!IS_MAC) throw new Error('start/stop is wired for launchd (macOS) only');
304
+
305
+ const plist = h('Library', 'LaunchAgents', `${svc.agent}.plist`);
306
+ if (!existsSync(plist)) throw new Error(`no LaunchAgent installed for ${svc.name}`);
307
+
308
+ const run = (args) => execFileP('launchctl', args).catch((e) => { throw new Error(e.stderr?.trim() || e.message); });
309
+ if (action === 'stop' || action === 'restart') await run(['unload', plist]).catch(() => {});
310
+ if (action === 'start' || action === 'restart') await run(['load', '-w', plist]);
311
+ return { ok: true };
312
+ }
313
+
314
+ async function readLog(id, name) {
315
+ const svc = byId.get(id);
316
+ if (!svc) throw new Error('unknown service');
317
+ // Match against the service's own declared logs; a path from the request is
318
+ // never joined or resolved.
319
+ const file = svc.logs.find((f) => path.basename(f) === name);
320
+ if (!file) throw new Error('unknown log');
321
+ const text = await readFile(file, 'utf8').catch(() => '');
322
+ return text.split('\n').slice(-400).join('\n') || '(leer)';
323
+ }
324
+
325
+ // ------------------------------------------------------------------ page
326
+ const PAGE = `<!doctype html>
327
+ <html lang="de"><head><meta charset="utf-8">
328
+ <meta name="viewport" content="width=device-width,initial-scale=1">
329
+ <title>AOS Dashboard</title>
330
+ <style>
331
+ :root{
332
+ --bg:#0a0a0a; --card:#141414; --line:#262626; --line-soft:#1d1d1d;
333
+ --fg:#fff; --dim:#8a8a8a; --dimmer:#5f5f5f;
334
+ --accent:#9b30c4; --plum:#51116F; --pill:#180524;
335
+ --up:#4ade80; --down:#f87171;
336
+ }
337
+ *{box-sizing:border-box}
338
+ body{margin:0;background:var(--bg);color:var(--fg);
339
+ font:14px/1.5 ui-sans-serif,-apple-system,"SF Pro Display",Inter,system-ui,sans-serif}
340
+
341
+ header{display:flex;align-items:center;gap:16px;padding:22px 28px;
342
+ border-bottom:1px solid var(--line);flex-wrap:wrap}
343
+ header img{width:34px;height:34px;flex:0 0 auto}
344
+ .brand{display:flex;flex-direction:column;line-height:1.2}
345
+ .brand b{font-size:16px;font-weight:700;letter-spacing:.01em}
346
+ .brand span{font-size:11px;color:var(--dim);letter-spacing:.08em;text-transform:uppercase}
347
+ .grow{flex:1}
348
+ .ver{font:600 12px/1 ui-monospace,"SF Mono",monospace;color:var(--dim);
349
+ border:1px solid var(--line);border-radius:999px;padding:6px 11px}
350
+ .badge{font:700 11px/1 ui-monospace,"SF Mono",monospace;letter-spacing:.04em;
351
+ background:var(--pill);border:1px solid var(--accent);color:#fff;
352
+ border-radius:999px;padding:6px 12px;text-decoration:none;display:inline-block}
353
+ .badge.muted{border-color:var(--line);color:var(--dim);background:transparent}
354
+
355
+ main{padding:22px 28px;display:grid;gap:10px;max-width:960px}
356
+
357
+ .card{background:var(--card);border:1px solid var(--line);border-radius:12px}
358
+ .head{display:grid;grid-template-columns:38px minmax(0,1fr) auto auto;
359
+ align-items:center;gap:14px;padding:15px 18px}
360
+ .mark{width:38px;height:38px;display:grid;place-items:center;
361
+ background:#0e0e0e;border:1px solid var(--line-soft);border-radius:9px;overflow:hidden}
362
+ .mark img{width:26px;height:26px;display:block}
363
+ .mark img.muted{opacity:.42;filter:grayscale(1)}
364
+ .ident{min-width:0}
365
+ .ident b{display:block;font-size:14.5px;font-weight:650}
366
+ .ident span{display:block;font-size:12px;color:var(--dim);
367
+ overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
368
+ .state{display:flex;align-items:center;gap:7px;font-size:12px;color:var(--dim);
369
+ font-variant-numeric:tabular-nums;white-space:nowrap}
370
+ .dot{width:8px;height:8px;border-radius:50%}
371
+ .up{background:var(--up)}.down{background:var(--down)}.unknown{background:var(--dimmer)}
372
+ .acts{display:flex;gap:6px;justify-self:end}
373
+
374
+ a.btn,button{font:inherit;font-size:12.5px;background:transparent;color:var(--fg);
375
+ border:1px solid var(--line);border-radius:7px;padding:5px 11px;cursor:pointer;
376
+ text-decoration:none;white-space:nowrap;transition:border-color .14s,background .14s}
377
+ a.btn:hover,button:hover{border-color:var(--accent);background:rgba(155,48,196,.13)}
378
+ button:disabled{opacity:.35;cursor:not-allowed}
379
+ button.primary{border-color:var(--accent)}
380
+
381
+ .sub{border-top:1px solid var(--line-soft);padding:11px 18px;
382
+ display:flex;gap:6px;align-items:center;flex-wrap:wrap}
383
+ .sub .lbl{font-size:11px;color:var(--dimmer);letter-spacing:.06em;
384
+ text-transform:uppercase;margin-right:4px}
385
+ .health{margin:0 18px 14px;padding:9px 11px;border-radius:8px;font-size:12px;line-height:1.45}
386
+ .health.error{background:rgba(248,113,113,.09);border:1px solid rgba(248,113,113,.34);color:#fca5a5}
387
+ .health.warn{background:var(--pill);border:1px solid rgba(155,48,196,.45);color:#d8b4fe}
388
+ pre{background:#000;border:1px solid var(--line);border-radius:8px;padding:12px;
389
+ margin:0 18px 16px;overflow:auto;max-height:320px;font-size:12px;
390
+ white-space:pre-wrap;word-break:break-word}
391
+ footer{padding:16px 28px 30px;color:var(--dimmer);font-size:12px}
392
+
393
+ @media (max-width:640px){
394
+ .head{grid-template-columns:38px minmax(0,1fr);row-gap:10px}
395
+ .state,.acts{grid-column:1/-1;justify-self:start}
396
+ }
397
+ </style></head><body>
398
+ <header>
399
+ <img src="/brand/bdb-core-mark.svg" alt="">
400
+ <div class="brand"><b>Agent OS</b><span>Dashboard</span></div>
401
+ <div class="grow"></div>
402
+ <span class="ver" id="ver">—</span>
403
+ <span id="update"></span>
404
+ </header>
405
+ <main id="list"></main>
406
+ <footer>Aktualisiert alle 5 s · nur über 127.0.0.1 erreichbar · Beenden mit Strg-C im Terminal</footer>
407
+ <script>
408
+ const list = document.getElementById('list');
409
+ const openLog = new Map(); // service id -> log name
410
+
411
+ async function api(p, opts) {
412
+ const r = await fetch(p, opts);
413
+ if (!r.ok) throw new Error((await r.json().catch(() => ({}))).error || r.statusText);
414
+ return r.json();
415
+ }
416
+ const kb = (n) => n < 1024 ? n + ' B' : (n / 1024).toFixed(0) + ' KB';
417
+ const esc = (s) => String(s).replace(/[&<>"]/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]));
418
+
419
+ let upd = {};
420
+
421
+ function card(s) {
422
+ const state = s.up === null ? 'unknown' : s.up ? 'up' : 'down';
423
+ const label = s.up === null ? 'unbekannt' : s.up ? 'läuft' : 'gestoppt';
424
+ const u = upd[s.id];
425
+ const verBits = [s.version && 'v' + s.version, s.port && ':' + s.port].filter(Boolean).join(' · ');
426
+
427
+ const el = document.createElement('div');
428
+ el.className = 'card';
429
+
430
+ const logRow = s.logs.length ? \`<div class="sub"><span class="lbl">Logs</span>\${
431
+ s.logs.map(l => \`<button data-log="\${esc(l.name)}" data-id="\${s.id}">\${esc(l.name)} · \${kb(l.size)}</button>\`).join('')
432
+ }</div>\` : '';
433
+
434
+ const wikiRow = s.projects ? \`<div class="sub"><span class="lbl">Wikis</span>\${
435
+ s.projects.length
436
+ ? s.projects.map(p => {
437
+ const v = (s.viewers || []).find(x => x.project === p.path);
438
+ return v
439
+ ? \`<a class="btn primary" href="http://127.0.0.1:\${v.port}" target="_blank" rel="noopener">\${esc(p.name)} · :\${v.port}</a>\`
440
+ : \`<button data-wiki="\${esc(p.path)}">\${esc(p.name)}</button>\`;
441
+ }).join('')
442
+ : '<span style="color:var(--dimmer);font-size:12px">keine Projekte in projects.json</span>'
443
+ }</div>\` : '';
444
+
445
+ el.innerHTML = \`
446
+ <div class="head">
447
+ <span class="mark"><img src="/brand/\${s.mark}" class="\${s.muted ? 'muted' : ''}" alt=""></span>
448
+ <span class="ident"><b>\${esc(s.name)}</b><span>\${esc(s.role)}\${s.installed ? '' : ' — Modul nicht installiert'}</span></span>
449
+ <span class="state"><span class="dot \${state}"></span>\${label}\${verBits ? ' · ' + verBits : ''}\${
450
+ u && u.state === 'outdated' ? ' · <span style="color:var(--accent)">v' + esc(u.remote) + ' verfügbar</span>' :
451
+ u && u.state === 'uncheckable' ? ' · <span title="npm nicht erreichbar">nicht prüfbar</span>' : ''
452
+ }</span>
453
+ <span class="acts">
454
+ \${s.url && s.up ? \`<a class="btn" href="\${s.url}" target="_blank" rel="noopener">Öffnen</a>\` : ''}
455
+ <button data-act="\${s.up ? 'stop' : 'start'}" data-id="\${s.id}">\${s.up ? 'Stop' : 'Start'}</button>
456
+ \${s.up ? \`<button data-act="restart" data-id="\${s.id}">Neustart</button>\` : ''}
457
+ </span>
458
+ </div>
459
+ \${s.health ? \`<div class="health \${s.health.level}">\${esc(s.health.text)}</div>\` : ''}
460
+ \${wikiRow}\${logRow}\`;
461
+
462
+ if (openLog.has(s.id)) {
463
+ const pre = document.createElement('pre');
464
+ pre.id = 'log-' + s.id; pre.textContent = 'lädt …';
465
+ el.appendChild(pre);
466
+ }
467
+ return el;
468
+ }
469
+
470
+ async function refresh() {
471
+ try {
472
+ const data = await api('/api/status');
473
+ list.replaceChildren(...data.services.map(card));
474
+ for (const id of openLog.keys()) loadLog(id);
475
+ } catch { document.getElementById('ver').textContent = 'Server weg'; }
476
+ }
477
+
478
+ async function loadMeta(force) {
479
+ try {
480
+ const m = await api('/api/meta' + (force ? '?force=1' : ''));
481
+ document.getElementById('ver').textContent = 'AOS v' + (m.version || '?');
482
+ const a = m.updates && m.updates.aos;
483
+ const box = document.getElementById('update');
484
+ if (a && a.state === 'outdated') {
485
+ box.innerHTML = '<span class="badge">Update auf v' + esc(a.remote) + '</span>';
486
+ } else if (a && a.state === 'uncheckable') {
487
+ box.innerHTML = '<span class="badge muted">npm nicht erreichbar</span>';
488
+ } else if (a && a.state === 'current') {
489
+ box.innerHTML = '<span class="badge muted">aktuell</span>';
490
+ }
491
+ upd = m.updates || {};
492
+ refresh();
493
+ } catch { /* the page stays useful without version info */ }
494
+ }
495
+
496
+ list.addEventListener('click', async (ev) => {
497
+ const b = ev.target.closest('button');
498
+ if (!b) return;
499
+
500
+ if (b.dataset.wiki) {
501
+ b.disabled = true; b.textContent = 'startet …';
502
+ try { await api('/api/openwiki/visualize', { method: 'POST',
503
+ headers: { 'content-type': 'application/json' }, body: JSON.stringify({ project: b.dataset.wiki }) }); }
504
+ catch (e) { alert(e.message); }
505
+ return refresh();
506
+ }
507
+
508
+ const id = b.dataset.id;
509
+ if (b.dataset.log) {
510
+ if (openLog.get(id) === b.dataset.log) openLog.delete(id);
511
+ else openLog.set(id, b.dataset.log);
512
+ return refresh();
513
+ }
514
+
515
+ b.disabled = true; b.textContent = '…';
516
+ try { await api('/api/control', { method: 'POST', headers: { 'content-type': 'application/json' },
517
+ body: JSON.stringify({ id, action: b.dataset.act }) }); }
518
+ catch (e) { alert(e.message); }
519
+ setTimeout(refresh, 1200);
520
+ });
521
+
522
+ async function loadLog(id) {
523
+ const pre = document.getElementById('log-' + id);
524
+ if (!pre) return;
525
+ try {
526
+ const r = await api('/api/log?id=' + encodeURIComponent(id) + '&name=' + encodeURIComponent(openLog.get(id)));
527
+ pre.textContent = r.text;
528
+ } catch (e) { pre.textContent = 'Log nicht lesbar: ' + e.message; }
529
+ }
530
+
531
+ refresh();
532
+ loadMeta(false);
533
+ setInterval(refresh, 5000);
534
+ setInterval(() => loadMeta(false), 30 * 60 * 1000);
535
+ </script></body></html>`;
536
+
537
+ // ------------------------------------------------------------------ server
538
+ const json = (res, code, body) => {
539
+ res.writeHead(code, { 'content-type': 'application/json' });
540
+ res.end(JSON.stringify(body));
541
+ };
542
+
543
+ const readBody = async (req) => {
544
+ let raw = '';
545
+ for await (const chunk of req) {
546
+ raw += chunk;
547
+ if (raw.length > 8192) { req.destroy(); throw new Error('body too large'); }
548
+ }
549
+ return JSON.parse(raw || '{}');
550
+ };
551
+
552
+ const server = createServer(async (req, res) => {
553
+ const url = new URL(req.url, 'http://127.0.0.1');
554
+ try {
555
+ if (url.pathname === '/') {
556
+ res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
557
+ return res.end(PAGE);
558
+ }
559
+ if (url.pathname.startsWith('/brand/')) {
560
+ // Served by basename against the shipped directory — a request cannot
561
+ // reach outside it.
562
+ const file = path.join(BRAND_DIR, path.basename(url.pathname));
563
+ if (!existsSync(file)) return json(res, 404, { error: 'not found' });
564
+ res.writeHead(200, { 'content-type': 'image/svg+xml', 'cache-control': 'max-age=3600' });
565
+ return res.end(await readFile(file));
566
+ }
567
+ if (url.pathname === '/api/status') {
568
+ return json(res, 200, { services: await snapshot() });
569
+ }
570
+ if (url.pathname === '/api/meta') {
571
+ return json(res, 200, { version: AOS_VERSION, updates: await updates({ force: url.searchParams.has('force') }) });
572
+ }
573
+ if (url.pathname === '/api/log') {
574
+ return json(res, 200, { text: await readLog(url.searchParams.get('id'), url.searchParams.get('name')) });
575
+ }
576
+ if (url.pathname === '/api/openwiki/visualize' && req.method === 'POST') {
577
+ const { project } = await readBody(req);
578
+ return json(res, 200, await startViewer(project));
579
+ }
580
+ if (url.pathname === '/api/control' && req.method === 'POST') {
581
+ const { id, action } = await readBody(req);
582
+ return json(res, 200, await control(id, action));
583
+ }
584
+ json(res, 404, { error: 'not found' });
585
+ } catch (e) {
586
+ json(res, 400, { error: e.message });
587
+ }
588
+ });
589
+
590
+ server.on('error', (e) => {
591
+ if (e.code === 'EADDRINUSE') {
592
+ console.error(`Port ${PORT} ist belegt. Anderen Port wählen: aos-dashboard --port 7901`);
593
+ process.exit(1);
594
+ }
595
+ throw e;
596
+ });
597
+
598
+ for (const sig of ['SIGINT', 'SIGTERM']) {
599
+ process.on(sig, () => { stopViewers(); process.exit(0); });
600
+ }
601
+
602
+ server.listen(PORT, '127.0.0.1', () => {
603
+ const url = `http://127.0.0.1:${PORT}`;
604
+ console.log(`AOS Dashboard v${AOS_VERSION || '?'}: ${url} (Strg-C beendet)`);
605
+ if (!OPEN) return;
606
+ const opener = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
607
+ execFile(opener, [url], () => { /* no browser is not an error */ });
608
+ });