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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (305) hide show
  1. package/.agents/skills/firecrawl/SKILL.md +149 -0
  2. package/.agents/skills/firecrawl/rules/install.md +82 -0
  3. package/.agents/skills/firecrawl/rules/security.md +26 -0
  4. package/.agents/skills/firecrawl-agent/SKILL.md +58 -0
  5. package/.agents/skills/firecrawl-build/SKILL.md +39 -0
  6. package/.agents/skills/firecrawl-build-interact/SKILL.md +68 -0
  7. package/.agents/skills/firecrawl-build-onboarding/SKILL.md +103 -0
  8. package/.agents/skills/firecrawl-build-onboarding/references/auth-flow.md +39 -0
  9. package/.agents/skills/firecrawl-build-onboarding/references/project-setup.md +20 -0
  10. package/.agents/skills/firecrawl-build-onboarding/references/sdk-installation.md +17 -0
  11. package/.agents/skills/firecrawl-build-scrape/SKILL.md +69 -0
  12. package/.agents/skills/firecrawl-build-search/SKILL.md +69 -0
  13. package/.agents/skills/firecrawl-crawl/SKILL.md +59 -0
  14. package/.agents/skills/firecrawl-download/SKILL.md +70 -0
  15. package/.agents/skills/firecrawl-interact/SKILL.md +84 -0
  16. package/.agents/skills/firecrawl-map/SKILL.md +51 -0
  17. package/.agents/skills/firecrawl-scrape/SKILL.md +69 -0
  18. package/.agents/skills/firecrawl-search/SKILL.md +60 -0
  19. package/CLAUDE.md +573 -0
  20. package/README.de.md +0 -8
  21. package/README.md +0 -8
  22. package/README.pt.md +0 -8
  23. package/assets/brand/ao-ant-mark.svg +91 -0
  24. package/assets/brand/bdb-core-mark.svg +34 -0
  25. package/assets/brand/heimdall-guardian-mark.svg +16 -0
  26. package/assets/brand/memb-crystal-mark.svg +18 -0
  27. package/bin/aos-config.mjs +89 -0
  28. package/bin/aos-dashboard.mjs +608 -0
  29. package/bin/aos-uninstall.mjs +227 -0
  30. package/docs/sessions/audit-2026-09-14-silent-failures.md +114 -0
  31. package/docs/skills_table.md +0 -3
  32. package/installer.js +387 -237
  33. package/mcps/RhinoMCP/cc-plugin/.claude/settings.json +10 -0
  34. package/mcps/after-effects-mcp/build/index.js +840 -0
  35. package/mcps/after-effects-mcp/build/scripts/applyEffect.jsx +153 -0
  36. package/mcps/after-effects-mcp/build/scripts/applyEffectTemplate.jsx +218 -0
  37. package/mcps/after-effects-mcp/build/scripts/createComposition.jsx +71 -0
  38. package/mcps/after-effects-mcp/build/scripts/createShapeLayer.jsx +147 -0
  39. package/mcps/after-effects-mcp/build/scripts/createSolidLayer.jsx +114 -0
  40. package/mcps/after-effects-mcp/build/scripts/createTextLayer.jsx +115 -0
  41. package/mcps/after-effects-mcp/build/scripts/getLayerInfo.jsx +192 -0
  42. package/mcps/after-effects-mcp/build/scripts/getProjectInfo.jsx +90 -0
  43. package/mcps/after-effects-mcp/build/scripts/listCompositions.jsx +50 -0
  44. package/mcps/after-effects-mcp/build/scripts/mcp-bridge-auto.jsx +1773 -0
  45. package/mcps/after-effects-mcp/build/scripts/setLayerProperties.jsx +160 -0
  46. package/mcps/computer-use-mcp/dist/client.d.ts +150 -0
  47. package/mcps/computer-use-mcp/dist/client.js +136 -0
  48. package/mcps/computer-use-mcp/dist/entrypoint.d.ts +16 -0
  49. package/mcps/computer-use-mcp/dist/entrypoint.js +26 -0
  50. package/mcps/computer-use-mcp/dist/native.d.ts +212 -0
  51. package/mcps/computer-use-mcp/dist/native.js +50 -0
  52. package/mcps/computer-use-mcp/dist/server.d.ts +32 -0
  53. package/mcps/computer-use-mcp/dist/server.js +342 -0
  54. package/mcps/computer-use-mcp/dist/session.d.ts +101 -0
  55. package/mcps/computer-use-mcp/dist/session.js +2372 -0
  56. package/package.json +13 -4
  57. package/skills/global_config/aos-project-init/SKILL.md +97 -130
  58. package/skills/global_config/aos-project-init/assets/openwikiignore.template +62 -0
  59. package/skills/global_config/aos-project-init/scripts/aos-project-doctor.mjs +38 -1
  60. package/skills/global_config/aos-setup/SKILL.md +98 -8
  61. package/skills/global_config/aos-setup/scripts/aos-doctor.mjs +21 -2
  62. package/skills/global_config/ask-tim/SKILL.md +2 -7
  63. package/skills/global_config/openwiki-skill/scripts/openwiki_daemon.py +65 -12
  64. package/bin/setup-saas.mjs +0 -792
  65. package/mcps/blender-mcp-server/.dockerignore +0 -19
  66. package/mcps/blender-mcp-server/.github/workflows/ci.yml +0 -67
  67. package/mcps/blender-mcp-server/.github/workflows/publish-pypi.yml +0 -54
  68. package/mcps/blender-mcp-server/CHANGELOG.md +0 -64
  69. package/mcps/blender-mcp-server/CONTRIBUTING.md +0 -107
  70. package/mcps/blender-mcp-server/Dockerfile +0 -19
  71. package/mcps/blender-mcp-server/LICENSE +0 -21
  72. package/mcps/blender-mcp-server/README.md +0 -409
  73. package/mcps/blender-mcp-server/addon/__init__.py +0 -1440
  74. package/mcps/blender-mcp-server/addon/models.py +0 -223
  75. package/mcps/blender-mcp-server/pyproject.toml +0 -120
  76. package/mcps/blender-mcp-server/scripts/blender_bridge_request.py +0 -71
  77. package/mcps/blender-mcp-server/scripts/blender_create_test_cube.py +0 -49
  78. package/mcps/blender-mcp-server/scripts/blender_scene_info.py +0 -38
  79. package/mcps/blender-mcp-server/scripts/build_addon_zip.sh +0 -29
  80. package/mcps/blender-mcp-server/scripts/demos/README.md +0 -186
  81. package/mcps/blender-mcp-server/scripts/demos/dam_break_scene.py +0 -276
  82. package/mcps/blender-mcp-server/scripts/demos/open_dam_break_gui.py +0 -33
  83. package/mcps/blender-mcp-server/scripts/demos/pipe_movement_study.py +0 -518
  84. package/mcps/blender-mcp-server/scripts/demos/pipe_pressure_failure_scene.py +0 -806
  85. package/mcps/blender-mcp-server/scripts/demos/procedural_dam_break_scene.py +0 -600
  86. package/mcps/blender-mcp-server/scripts/demos/run_dam_break.py +0 -510
  87. package/mcps/blender-mcp-server/scripts/launch_blender_gui.py +0 -41
  88. package/mcps/blender-mcp-server/scripts/library/README.md +0 -64
  89. package/mcps/blender-mcp-server/scripts/library/apply_transforms.py +0 -46
  90. package/mcps/blender-mcp-server/scripts/library/camera.py +0 -51
  91. package/mcps/blender-mcp-server/scripts/library/collections.py +0 -65
  92. package/mcps/blender-mcp-server/scripts/library/create_mesh.py +0 -41
  93. package/mcps/blender-mcp-server/scripts/library/effector.py +0 -48
  94. package/mcps/blender-mcp-server/scripts/library/fluid_domain.py +0 -80
  95. package/mcps/blender-mcp-server/scripts/library/fluid_inflow.py +0 -98
  96. package/mcps/blender-mcp-server/scripts/library/frame_range.py +0 -35
  97. package/mcps/blender-mcp-server/scripts/library/keyframes.py +0 -59
  98. package/mcps/blender-mcp-server/scripts/library/rigid_body.py +0 -52
  99. package/mcps/blender-mcp-server/scripts/library/save_blend.py +0 -40
  100. package/mcps/blender-mcp-server/scripts/server_start.sh +0 -26
  101. package/mcps/blender-mcp-server/src/blender_mcp_server/__init__.py +0 -3
  102. package/mcps/blender-mcp-server/src/blender_mcp_server/headless.py +0 -330
  103. package/mcps/blender-mcp-server/src/blender_mcp_server/server.py +0 -606
  104. package/mcps/davinci-mcp-professional/.bandit +0 -35
  105. package/mcps/davinci-mcp-professional/.claude/commands/security-review.md +0 -191
  106. package/mcps/davinci-mcp-professional/.claude/settings.json +0 -1
  107. package/mcps/davinci-mcp-professional/.editorconfig +0 -27
  108. package/mcps/davinci-mcp-professional/.gitattributes +0 -207
  109. package/mcps/davinci-mcp-professional/.github/CODEOWNERS +0 -2
  110. package/mcps/davinci-mcp-professional/.github/ISSUE_TEMPLATE/bug_report.md +0 -33
  111. package/mcps/davinci-mcp-professional/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
  112. package/mcps/davinci-mcp-professional/.github/PULL_REQUEST_TEMPLATE.md +0 -20
  113. package/mcps/davinci-mcp-professional/.github/SECURITY.md +0 -97
  114. package/mcps/davinci-mcp-professional/.github/dependabot.yml +0 -27
  115. package/mcps/davinci-mcp-professional/.github/workflows/ci.yml +0 -39
  116. package/mcps/davinci-mcp-professional/ATTRIBUTION.md +0 -4
  117. package/mcps/davinci-mcp-professional/AUTHORS.md +0 -9
  118. package/mcps/davinci-mcp-professional/BUGS.md +0 -55
  119. package/mcps/davinci-mcp-professional/CLAUDE.md +0 -229
  120. package/mcps/davinci-mcp-professional/CONTRIBUTING.md +0 -51
  121. package/mcps/davinci-mcp-professional/COPYING.md +0 -228
  122. package/mcps/davinci-mcp-professional/Doxyfile +0 -379
  123. package/mcps/davinci-mcp-professional/README.md +0 -143
  124. package/mcps/davinci-mcp-professional/SECURITY.md +0 -40
  125. package/mcps/davinci-mcp-professional/USING.md +0 -301
  126. package/mcps/davinci-mcp-professional/VERSION.md +0 -13
  127. package/mcps/davinci-mcp-professional/build.py +0 -169
  128. package/mcps/davinci-mcp-professional/claude_desktop_config_template.json +0 -11
  129. package/mcps/davinci-mcp-professional/hooks/ci-check.sh +0 -29
  130. package/mcps/davinci-mcp-professional/hooks/pre-push +0 -46
  131. package/mcps/davinci-mcp-professional/main.py +0 -40
  132. package/mcps/davinci-mcp-professional/mcp_server.py +0 -30
  133. package/mcps/davinci-mcp-professional/pyproject.toml +0 -115
  134. package/mcps/davinci-mcp-professional/security_audit.py +0 -379
  135. package/mcps/davinci-mcp-professional/src/davinci_mcp/__init__.py +0 -19
  136. package/mcps/davinci-mcp-professional/src/davinci_mcp/cli.py +0 -138
  137. package/mcps/davinci-mcp-professional/src/davinci_mcp/resolve_client.py +0 -342
  138. package/mcps/davinci-mcp-professional/src/davinci_mcp/resources/__init__.py +0 -60
  139. package/mcps/davinci-mcp-professional/src/davinci_mcp/server.py +0 -185
  140. package/mcps/davinci-mcp-professional/src/davinci_mcp/tools/__init__.py +0 -146
  141. package/mcps/davinci-mcp-professional/src/davinci_mcp/types.py +0 -125
  142. package/mcps/davinci-mcp-professional/src/davinci_mcp/utils/__init__.py +0 -19
  143. package/mcps/davinci-mcp-professional/src/davinci_mcp/utils/platform.py +0 -239
  144. package/mcps/davinci-mcp-professional/test.py +0 -186
  145. package/mcps/davinci-mcp-professional/uv.lock +0 -2073
  146. package/mcps/davinci-resolve-mcp/.claude/skills/media-analysis.md +0 -44
  147. package/mcps/davinci-resolve-mcp/.clinerules +0 -17
  148. package/mcps/davinci-resolve-mcp/.cursorrules +0 -17
  149. package/mcps/davinci-resolve-mcp/.github/copilot-instructions.md +0 -17
  150. package/mcps/davinci-resolve-mcp/.github/workflows/npm-publish.yml +0 -109
  151. package/mcps/davinci-resolve-mcp/.windsurfrules +0 -17
  152. package/mcps/davinci-resolve-mcp/AGENTS.md +0 -114
  153. package/mcps/davinci-resolve-mcp/CHANGELOG.md +0 -2648
  154. package/mcps/davinci-resolve-mcp/CLAUDE.md +0 -15
  155. package/mcps/davinci-resolve-mcp/LICENSE +0 -21
  156. package/mcps/davinci-resolve-mcp/README.md +0 -173
  157. package/mcps/davinci-resolve-mcp/SECURITY.md +0 -53
  158. package/mcps/davinci-resolve-mcp/bin/davinci-resolve-mcp.mjs +0 -450
  159. package/mcps/davinci-resolve-mcp/examples/README.md +0 -53
  160. package/mcps/davinci-resolve-mcp/examples/markers/README.md +0 -81
  161. package/mcps/davinci-resolve-mcp/examples/media/README.md +0 -94
  162. package/mcps/davinci-resolve-mcp/examples/timeline/README.md +0 -98
  163. package/mcps/davinci-resolve-mcp/install.py +0 -1687
  164. package/mcps/davinci-resolve-mcp/package.json +0 -52
  165. package/mcps/davinci-resolve-mcp/release-notes/v2.22.0.md +0 -51
  166. package/mcps/davinci-resolve-mcp/release-notes/v2.23.0.md +0 -51
  167. package/mcps/davinci-resolve-mcp/release-notes/v2.23.1.md +0 -35
  168. package/mcps/davinci-resolve-mcp/release-notes/v2.24.1.md +0 -32
  169. package/mcps/davinci-resolve-mcp/scripts/audit_api_parity.py +0 -275
  170. package/mcps/davinci-resolve-mcp/scripts/audit_readwrite_symmetry.py +0 -80
  171. package/mcps/davinci-resolve-mcp/scripts/doctor.py +0 -227
  172. package/mcps/davinci-resolve-mcp/scripts/gen_api_limitations.py +0 -144
  173. package/mcps/davinci-resolve-mcp/scripts/live_media_analysis_polish_probe.py +0 -65
  174. package/mcps/davinci-resolve-mcp/scripts/measure_bridge_cost.py +0 -65
  175. package/mcps/davinci-resolve-mcp/scripts/regen_panel_screenshots.py +0 -76
  176. package/mcps/davinci-resolve-mcp/src/__init__.py +0 -3
  177. package/mcps/davinci-resolve-mcp/src/analysis_dashboard.py +0 -15505
  178. package/mcps/davinci-resolve-mcp/src/batch_cli.py +0 -540
  179. package/mcps/davinci-resolve-mcp/src/control_panel.py +0 -13
  180. package/mcps/davinci-resolve-mcp/src/granular/__init__.py +0 -17
  181. package/mcps/davinci-resolve-mcp/src/granular/common.py +0 -741
  182. package/mcps/davinci-resolve-mcp/src/granular/folder.py +0 -420
  183. package/mcps/davinci-resolve-mcp/src/granular/gallery.py +0 -306
  184. package/mcps/davinci-resolve-mcp/src/granular/graph.py +0 -309
  185. package/mcps/davinci-resolve-mcp/src/granular/media_pool.py +0 -679
  186. package/mcps/davinci-resolve-mcp/src/granular/media_pool_item.py +0 -1020
  187. package/mcps/davinci-resolve-mcp/src/granular/media_storage.py +0 -179
  188. package/mcps/davinci-resolve-mcp/src/granular/project.py +0 -1654
  189. package/mcps/davinci-resolve-mcp/src/granular/resolve_control.py +0 -538
  190. package/mcps/davinci-resolve-mcp/src/granular/timeline.py +0 -1075
  191. package/mcps/davinci-resolve-mcp/src/granular/timeline_item.py +0 -2251
  192. package/mcps/davinci-resolve-mcp/src/resolve_mcp_server.py +0 -43
  193. package/mcps/davinci-resolve-mcp/src/server.py +0 -24116
  194. package/mcps/davinci-resolve-mcp/src/utils/__init__.py +0 -3
  195. package/mcps/davinci-resolve-mcp/src/utils/actor_identity.py +0 -46
  196. package/mcps/davinci-resolve-mcp/src/utils/analysis_caps.py +0 -678
  197. package/mcps/davinci-resolve-mcp/src/utils/analysis_memory.py +0 -755
  198. package/mcps/davinci-resolve-mcp/src/utils/analysis_runs.py +0 -307
  199. package/mcps/davinci-resolve-mcp/src/utils/analysis_store.py +0 -945
  200. package/mcps/davinci-resolve-mcp/src/utils/api_truth.py +0 -525
  201. package/mcps/davinci-resolve-mcp/src/utils/app_control.py +0 -319
  202. package/mcps/davinci-resolve-mcp/src/utils/audio_fairlight_live_probe.py +0 -263
  203. package/mcps/davinci-resolve-mcp/src/utils/brain_edits.py +0 -387
  204. package/mcps/davinci-resolve-mcp/src/utils/bridge_metrics.py +0 -76
  205. package/mcps/davinci-resolve-mcp/src/utils/cdl.py +0 -20
  206. package/mcps/davinci-resolve-mcp/src/utils/clip_query.py +0 -85
  207. package/mcps/davinci-resolve-mcp/src/utils/cloud_operations.py +0 -192
  208. package/mcps/davinci-resolve-mcp/src/utils/color_grade_live_probe.py +0 -444
  209. package/mcps/davinci-resolve-mcp/src/utils/contracts.py +0 -102
  210. package/mcps/davinci-resolve-mcp/src/utils/cut_ir.py +0 -115
  211. package/mcps/davinci-resolve-mcp/src/utils/dctl_templates.py +0 -368
  212. package/mcps/davinci-resolve-mcp/src/utils/deep_vision.py +0 -643
  213. package/mcps/davinci-resolve-mcp/src/utils/destructive_hook.py +0 -606
  214. package/mcps/davinci-resolve-mcp/src/utils/edit_engine.py +0 -737
  215. package/mcps/davinci-resolve-mcp/src/utils/embeddings.py +0 -922
  216. package/mcps/davinci-resolve-mcp/src/utils/entities.py +0 -579
  217. package/mcps/davinci-resolve-mcp/src/utils/extension_authoring_live_probe.py +0 -292
  218. package/mcps/davinci-resolve-mcp/src/utils/failure_tracker.py +0 -119
  219. package/mcps/davinci-resolve-mcp/src/utils/fuse_templates.py +0 -1968
  220. package/mcps/davinci-resolve-mcp/src/utils/fusion_composition_live_probe.py +0 -284
  221. package/mcps/davinci-resolve-mcp/src/utils/fusion_group_settings.py +0 -334
  222. package/mcps/davinci-resolve-mcp/src/utils/layout_presets.py +0 -333
  223. package/mcps/davinci-resolve-mcp/src/utils/mcp_stdio.py +0 -32
  224. package/mcps/davinci-resolve-mcp/src/utils/mcp_transport.py +0 -128
  225. package/mcps/davinci-resolve-mcp/src/utils/media_analysis.py +0 -7690
  226. package/mcps/davinci-resolve-mcp/src/utils/media_analysis_jobs.py +0 -805
  227. package/mcps/davinci-resolve-mcp/src/utils/media_pool_changes.py +0 -121
  228. package/mcps/davinci-resolve-mcp/src/utils/media_pool_ingest_live_probe.py +0 -592
  229. package/mcps/davinci-resolve-mcp/src/utils/multicam.py +0 -393
  230. package/mcps/davinci-resolve-mcp/src/utils/object_inspection.py +0 -301
  231. package/mcps/davinci-resolve-mcp/src/utils/page_lock.py +0 -75
  232. package/mcps/davinci-resolve-mcp/src/utils/platform.py +0 -157
  233. package/mcps/davinci-resolve-mcp/src/utils/proc.py +0 -26
  234. package/mcps/davinci-resolve-mcp/src/utils/project_cleanup.py +0 -73
  235. package/mcps/davinci-resolve-mcp/src/utils/project_lifecycle_live_probe.py +0 -376
  236. package/mcps/davinci-resolve-mcp/src/utils/project_lint.py +0 -131
  237. package/mcps/davinci-resolve-mcp/src/utils/project_properties.py +0 -601
  238. package/mcps/davinci-resolve-mcp/src/utils/project_spec.py +0 -459
  239. package/mcps/davinci-resolve-mcp/src/utils/readback.py +0 -91
  240. package/mcps/davinci-resolve-mcp/src/utils/render_deliver_live_probe.py +0 -384
  241. package/mcps/davinci-resolve-mcp/src/utils/resolve_ai_governance.py +0 -214
  242. package/mcps/davinci-resolve-mcp/src/utils/resolve_ai_ledger.py +0 -244
  243. package/mcps/davinci-resolve-mcp/src/utils/resolve_busy.py +0 -171
  244. package/mcps/davinci-resolve-mcp/src/utils/resolve_connection.py +0 -77
  245. package/mcps/davinci-resolve-mcp/src/utils/review_annotation_live_probe.py +0 -352
  246. package/mcps/davinci-resolve-mcp/src/utils/script_templates.py +0 -1193
  247. package/mcps/davinci-resolve-mcp/src/utils/shot_relationships.py +0 -544
  248. package/mcps/davinci-resolve-mcp/src/utils/structural_diff.py +0 -175
  249. package/mcps/davinci-resolve-mcp/src/utils/sync_detection.py +0 -889
  250. package/mcps/davinci-resolve-mcp/src/utils/timeline_brain_db.py +0 -826
  251. package/mcps/davinci-resolve-mcp/src/utils/timeline_conform_live_probe.py +0 -280
  252. package/mcps/davinci-resolve-mcp/src/utils/timeline_kernel_live_probe.py +0 -1092
  253. package/mcps/davinci-resolve-mcp/src/utils/timeline_kernel_probe.py +0 -185
  254. package/mcps/davinci-resolve-mcp/src/utils/timeline_title_text.py +0 -87
  255. package/mcps/davinci-resolve-mcp/src/utils/timeline_versioning.py +0 -730
  256. package/mcps/davinci-resolve-mcp/src/utils/update_check.py +0 -685
  257. package/mcps/vectorworks-mcp/.dockerignore +0 -8
  258. package/mcps/vectorworks-mcp/Dockerfile +0 -28
  259. package/mcps/vectorworks-mcp/README.md +0 -156
  260. package/mcps/vectorworks-mcp/app/__init__.py +0 -2
  261. package/mcps/vectorworks-mcp/app/api.py +0 -52
  262. package/mcps/vectorworks-mcp/app/chunking.py +0 -56
  263. package/mcps/vectorworks-mcp/app/config.py +0 -29
  264. package/mcps/vectorworks-mcp/app/embeddings.py +0 -28
  265. package/mcps/vectorworks-mcp/app/indexer.py +0 -106
  266. package/mcps/vectorworks-mcp/app/main.py +0 -33
  267. package/mcps/vectorworks-mcp/app/mcp_server.py +0 -71
  268. package/mcps/vectorworks-mcp/app/search.py +0 -54
  269. package/mcps/vectorworks-mcp/app/utils.py +0 -14
  270. package/mcps/vectorworks-mcp/data/.gitkeep +0 -1
  271. package/mcps/vectorworks-mcp/data/vw-app-help/VW2022_PluginScript.htm +0 -95
  272. package/mcps/vectorworks-mcp/data/vw-app-help/VW2022_ScriptEditor.htm +0 -266
  273. package/mcps/vectorworks-mcp/data/vw-app-help/VW2022_Scripting.htm +0 -106
  274. package/mcps/vectorworks-mcp/data/vw-app-help/VW2023_CreatingScriptedPlugins.htm +0 -327
  275. package/mcps/vectorworks-mcp/data/vw-app-help/VW2024_RunningScripts.htm +0 -116
  276. package/mcps/vectorworks-mcp/data/vw-devwiki/VS_Function_Reference_category.html +0 -2129
  277. package/mcps/vectorworks-mcp/data/vw-jp-ref/ObjectsSymbols.html +0 -2035
  278. package/mcps/vectorworks-mcp/data/vw-jp-ref/ScriptFunctionReference.html +0 -29
  279. package/mcps/vectorworks-mcp/design.md +0 -57
  280. package/mcps/vectorworks-mcp/docker-compose.yml +0 -18
  281. package/mcps/vectorworks-mcp/index/.gitkeep +0 -1
  282. package/mcps/vectorworks-mcp/index/meta.jsonl +0 -3956
  283. package/mcps/vectorworks-mcp/index/vw.faiss +0 -0
  284. package/mcps/vectorworks-mcp/requirements.txt +0 -10
  285. package/mcps/vectorworks-mcp/scripts/fetch_docs_minimal.sh +0 -84
  286. package/mcps/vectorworks-mcp/scripts/fetch_github_vectorworks.sh +0 -85
  287. package/mcps/vectorworks-mcp/templates/index.html +0 -40
  288. package/scripts/ecosystem-health-audit.js +0 -257
  289. package/skills/bdb-dev-os-skill/SKILL.md +0 -49
  290. package/skills/bdb-dev-os-skill/package.json +0 -11
  291. package/skills/bdbsaastraining/SKILL.md +0 -316
  292. package/skills/bdbsaastraining/references/exam-pool.md +0 -313
  293. package/skills/bdbsaastraining/references/interaction-modes.md +0 -94
  294. package/skills/bdbsaastraining/references/track-a-ai-agent.md +0 -111
  295. package/skills/bdbsaastraining/references/track-b-wordpress.md +0 -111
  296. package/skills/bdbsaastraining/references/track-c-mailserver.md +0 -107
  297. package/skills/bdbsaastraining/references/track-d-debian.md +0 -109
  298. package/skills/bdbsaastraining/references/track-e-custom.md +0 -83
  299. package/skills/bdbsaastraining/scripts/build_profile.py +0 -529
  300. package/skills/bdbsaastraining/scripts/generate_certificate.py +0 -161
  301. package/skills/bdbsaastraining/scripts/preflight_check.sh +0 -150
  302. package/skills/bdbsaastraining/templates/certificate_template.html +0 -508
  303. package/skills/global_config/bdb-ecosystem-health/SKILL.md +0 -68
  304. package/skills/global_config/bdbsaas-ops/SKILL.md +0 -16
  305. package/skills/global_config/bdbsaashost/SKILL.md +0 -125
@@ -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
+ });