@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.
- package/.agents/skills/firecrawl/SKILL.md +149 -0
- package/.agents/skills/firecrawl/rules/install.md +82 -0
- package/.agents/skills/firecrawl/rules/security.md +26 -0
- package/.agents/skills/firecrawl-agent/SKILL.md +58 -0
- package/.agents/skills/firecrawl-build/SKILL.md +39 -0
- package/.agents/skills/firecrawl-build-interact/SKILL.md +68 -0
- package/.agents/skills/firecrawl-build-onboarding/SKILL.md +103 -0
- package/.agents/skills/firecrawl-build-onboarding/references/auth-flow.md +39 -0
- package/.agents/skills/firecrawl-build-onboarding/references/project-setup.md +20 -0
- package/.agents/skills/firecrawl-build-onboarding/references/sdk-installation.md +17 -0
- package/.agents/skills/firecrawl-build-scrape/SKILL.md +69 -0
- package/.agents/skills/firecrawl-build-search/SKILL.md +69 -0
- package/.agents/skills/firecrawl-crawl/SKILL.md +59 -0
- package/.agents/skills/firecrawl-download/SKILL.md +70 -0
- package/.agents/skills/firecrawl-interact/SKILL.md +84 -0
- package/.agents/skills/firecrawl-map/SKILL.md +51 -0
- package/.agents/skills/firecrawl-scrape/SKILL.md +69 -0
- package/.agents/skills/firecrawl-search/SKILL.md +60 -0
- package/CLAUDE.md +573 -0
- package/README.de.md +0 -8
- package/README.md +0 -8
- package/README.pt.md +0 -8
- package/assets/brand/ao-ant-mark.svg +91 -0
- package/assets/brand/bdb-core-mark.svg +34 -0
- package/assets/brand/heimdall-guardian-mark.svg +16 -0
- package/assets/brand/memb-crystal-mark.svg +18 -0
- package/bin/aos-config.mjs +89 -0
- package/bin/aos-dashboard.mjs +608 -0
- package/bin/aos-uninstall.mjs +227 -0
- package/docs/sessions/audit-2026-09-14-silent-failures.md +114 -0
- package/docs/skills_table.md +0 -3
- package/installer.js +387 -237
- package/mcps/RhinoMCP/cc-plugin/.claude/settings.json +10 -0
- package/mcps/after-effects-mcp/build/index.js +840 -0
- package/mcps/after-effects-mcp/build/scripts/applyEffect.jsx +153 -0
- package/mcps/after-effects-mcp/build/scripts/applyEffectTemplate.jsx +218 -0
- package/mcps/after-effects-mcp/build/scripts/createComposition.jsx +71 -0
- package/mcps/after-effects-mcp/build/scripts/createShapeLayer.jsx +147 -0
- package/mcps/after-effects-mcp/build/scripts/createSolidLayer.jsx +114 -0
- package/mcps/after-effects-mcp/build/scripts/createTextLayer.jsx +115 -0
- package/mcps/after-effects-mcp/build/scripts/getLayerInfo.jsx +192 -0
- package/mcps/after-effects-mcp/build/scripts/getProjectInfo.jsx +90 -0
- package/mcps/after-effects-mcp/build/scripts/listCompositions.jsx +50 -0
- package/mcps/after-effects-mcp/build/scripts/mcp-bridge-auto.jsx +1773 -0
- package/mcps/after-effects-mcp/build/scripts/setLayerProperties.jsx +160 -0
- package/mcps/computer-use-mcp/dist/client.d.ts +150 -0
- package/mcps/computer-use-mcp/dist/client.js +136 -0
- package/mcps/computer-use-mcp/dist/entrypoint.d.ts +16 -0
- package/mcps/computer-use-mcp/dist/entrypoint.js +26 -0
- package/mcps/computer-use-mcp/dist/native.d.ts +212 -0
- package/mcps/computer-use-mcp/dist/native.js +50 -0
- package/mcps/computer-use-mcp/dist/server.d.ts +32 -0
- package/mcps/computer-use-mcp/dist/server.js +342 -0
- package/mcps/computer-use-mcp/dist/session.d.ts +101 -0
- package/mcps/computer-use-mcp/dist/session.js +2372 -0
- package/package.json +13 -4
- package/skills/global_config/aos-project-init/SKILL.md +97 -130
- package/skills/global_config/aos-project-init/assets/openwikiignore.template +62 -0
- package/skills/global_config/aos-project-init/scripts/aos-project-doctor.mjs +38 -1
- package/skills/global_config/aos-setup/SKILL.md +98 -8
- package/skills/global_config/aos-setup/scripts/aos-doctor.mjs +21 -2
- package/skills/global_config/ask-tim/SKILL.md +2 -7
- package/skills/global_config/openwiki-skill/scripts/openwiki_daemon.py +65 -12
- package/bin/setup-saas.mjs +0 -792
- package/mcps/blender-mcp-server/.dockerignore +0 -19
- package/mcps/blender-mcp-server/.github/workflows/ci.yml +0 -67
- package/mcps/blender-mcp-server/.github/workflows/publish-pypi.yml +0 -54
- package/mcps/blender-mcp-server/CHANGELOG.md +0 -64
- package/mcps/blender-mcp-server/CONTRIBUTING.md +0 -107
- package/mcps/blender-mcp-server/Dockerfile +0 -19
- package/mcps/blender-mcp-server/LICENSE +0 -21
- package/mcps/blender-mcp-server/README.md +0 -409
- package/mcps/blender-mcp-server/addon/__init__.py +0 -1440
- package/mcps/blender-mcp-server/addon/models.py +0 -223
- package/mcps/blender-mcp-server/pyproject.toml +0 -120
- package/mcps/blender-mcp-server/scripts/blender_bridge_request.py +0 -71
- package/mcps/blender-mcp-server/scripts/blender_create_test_cube.py +0 -49
- package/mcps/blender-mcp-server/scripts/blender_scene_info.py +0 -38
- package/mcps/blender-mcp-server/scripts/build_addon_zip.sh +0 -29
- package/mcps/blender-mcp-server/scripts/demos/README.md +0 -186
- package/mcps/blender-mcp-server/scripts/demos/dam_break_scene.py +0 -276
- package/mcps/blender-mcp-server/scripts/demos/open_dam_break_gui.py +0 -33
- package/mcps/blender-mcp-server/scripts/demos/pipe_movement_study.py +0 -518
- package/mcps/blender-mcp-server/scripts/demos/pipe_pressure_failure_scene.py +0 -806
- package/mcps/blender-mcp-server/scripts/demos/procedural_dam_break_scene.py +0 -600
- package/mcps/blender-mcp-server/scripts/demos/run_dam_break.py +0 -510
- package/mcps/blender-mcp-server/scripts/launch_blender_gui.py +0 -41
- package/mcps/blender-mcp-server/scripts/library/README.md +0 -64
- package/mcps/blender-mcp-server/scripts/library/apply_transforms.py +0 -46
- package/mcps/blender-mcp-server/scripts/library/camera.py +0 -51
- package/mcps/blender-mcp-server/scripts/library/collections.py +0 -65
- package/mcps/blender-mcp-server/scripts/library/create_mesh.py +0 -41
- package/mcps/blender-mcp-server/scripts/library/effector.py +0 -48
- package/mcps/blender-mcp-server/scripts/library/fluid_domain.py +0 -80
- package/mcps/blender-mcp-server/scripts/library/fluid_inflow.py +0 -98
- package/mcps/blender-mcp-server/scripts/library/frame_range.py +0 -35
- package/mcps/blender-mcp-server/scripts/library/keyframes.py +0 -59
- package/mcps/blender-mcp-server/scripts/library/rigid_body.py +0 -52
- package/mcps/blender-mcp-server/scripts/library/save_blend.py +0 -40
- package/mcps/blender-mcp-server/scripts/server_start.sh +0 -26
- package/mcps/blender-mcp-server/src/blender_mcp_server/__init__.py +0 -3
- package/mcps/blender-mcp-server/src/blender_mcp_server/headless.py +0 -330
- package/mcps/blender-mcp-server/src/blender_mcp_server/server.py +0 -606
- package/mcps/davinci-mcp-professional/.bandit +0 -35
- package/mcps/davinci-mcp-professional/.claude/commands/security-review.md +0 -191
- package/mcps/davinci-mcp-professional/.claude/settings.json +0 -1
- package/mcps/davinci-mcp-professional/.editorconfig +0 -27
- package/mcps/davinci-mcp-professional/.gitattributes +0 -207
- package/mcps/davinci-mcp-professional/.github/CODEOWNERS +0 -2
- package/mcps/davinci-mcp-professional/.github/ISSUE_TEMPLATE/bug_report.md +0 -33
- package/mcps/davinci-mcp-professional/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
- package/mcps/davinci-mcp-professional/.github/PULL_REQUEST_TEMPLATE.md +0 -20
- package/mcps/davinci-mcp-professional/.github/SECURITY.md +0 -97
- package/mcps/davinci-mcp-professional/.github/dependabot.yml +0 -27
- package/mcps/davinci-mcp-professional/.github/workflows/ci.yml +0 -39
- package/mcps/davinci-mcp-professional/ATTRIBUTION.md +0 -4
- package/mcps/davinci-mcp-professional/AUTHORS.md +0 -9
- package/mcps/davinci-mcp-professional/BUGS.md +0 -55
- package/mcps/davinci-mcp-professional/CLAUDE.md +0 -229
- package/mcps/davinci-mcp-professional/CONTRIBUTING.md +0 -51
- package/mcps/davinci-mcp-professional/COPYING.md +0 -228
- package/mcps/davinci-mcp-professional/Doxyfile +0 -379
- package/mcps/davinci-mcp-professional/README.md +0 -143
- package/mcps/davinci-mcp-professional/SECURITY.md +0 -40
- package/mcps/davinci-mcp-professional/USING.md +0 -301
- package/mcps/davinci-mcp-professional/VERSION.md +0 -13
- package/mcps/davinci-mcp-professional/build.py +0 -169
- package/mcps/davinci-mcp-professional/claude_desktop_config_template.json +0 -11
- package/mcps/davinci-mcp-professional/hooks/ci-check.sh +0 -29
- package/mcps/davinci-mcp-professional/hooks/pre-push +0 -46
- package/mcps/davinci-mcp-professional/main.py +0 -40
- package/mcps/davinci-mcp-professional/mcp_server.py +0 -30
- package/mcps/davinci-mcp-professional/pyproject.toml +0 -115
- package/mcps/davinci-mcp-professional/security_audit.py +0 -379
- package/mcps/davinci-mcp-professional/src/davinci_mcp/__init__.py +0 -19
- package/mcps/davinci-mcp-professional/src/davinci_mcp/cli.py +0 -138
- package/mcps/davinci-mcp-professional/src/davinci_mcp/resolve_client.py +0 -342
- package/mcps/davinci-mcp-professional/src/davinci_mcp/resources/__init__.py +0 -60
- package/mcps/davinci-mcp-professional/src/davinci_mcp/server.py +0 -185
- package/mcps/davinci-mcp-professional/src/davinci_mcp/tools/__init__.py +0 -146
- package/mcps/davinci-mcp-professional/src/davinci_mcp/types.py +0 -125
- package/mcps/davinci-mcp-professional/src/davinci_mcp/utils/__init__.py +0 -19
- package/mcps/davinci-mcp-professional/src/davinci_mcp/utils/platform.py +0 -239
- package/mcps/davinci-mcp-professional/test.py +0 -186
- package/mcps/davinci-mcp-professional/uv.lock +0 -2073
- package/mcps/davinci-resolve-mcp/.claude/skills/media-analysis.md +0 -44
- package/mcps/davinci-resolve-mcp/.clinerules +0 -17
- package/mcps/davinci-resolve-mcp/.cursorrules +0 -17
- package/mcps/davinci-resolve-mcp/.github/copilot-instructions.md +0 -17
- package/mcps/davinci-resolve-mcp/.github/workflows/npm-publish.yml +0 -109
- package/mcps/davinci-resolve-mcp/.windsurfrules +0 -17
- package/mcps/davinci-resolve-mcp/AGENTS.md +0 -114
- package/mcps/davinci-resolve-mcp/CHANGELOG.md +0 -2648
- package/mcps/davinci-resolve-mcp/CLAUDE.md +0 -15
- package/mcps/davinci-resolve-mcp/LICENSE +0 -21
- package/mcps/davinci-resolve-mcp/README.md +0 -173
- package/mcps/davinci-resolve-mcp/SECURITY.md +0 -53
- package/mcps/davinci-resolve-mcp/bin/davinci-resolve-mcp.mjs +0 -450
- package/mcps/davinci-resolve-mcp/examples/README.md +0 -53
- package/mcps/davinci-resolve-mcp/examples/markers/README.md +0 -81
- package/mcps/davinci-resolve-mcp/examples/media/README.md +0 -94
- package/mcps/davinci-resolve-mcp/examples/timeline/README.md +0 -98
- package/mcps/davinci-resolve-mcp/install.py +0 -1687
- package/mcps/davinci-resolve-mcp/package.json +0 -52
- package/mcps/davinci-resolve-mcp/release-notes/v2.22.0.md +0 -51
- package/mcps/davinci-resolve-mcp/release-notes/v2.23.0.md +0 -51
- package/mcps/davinci-resolve-mcp/release-notes/v2.23.1.md +0 -35
- package/mcps/davinci-resolve-mcp/release-notes/v2.24.1.md +0 -32
- package/mcps/davinci-resolve-mcp/scripts/audit_api_parity.py +0 -275
- package/mcps/davinci-resolve-mcp/scripts/audit_readwrite_symmetry.py +0 -80
- package/mcps/davinci-resolve-mcp/scripts/doctor.py +0 -227
- package/mcps/davinci-resolve-mcp/scripts/gen_api_limitations.py +0 -144
- package/mcps/davinci-resolve-mcp/scripts/live_media_analysis_polish_probe.py +0 -65
- package/mcps/davinci-resolve-mcp/scripts/measure_bridge_cost.py +0 -65
- package/mcps/davinci-resolve-mcp/scripts/regen_panel_screenshots.py +0 -76
- package/mcps/davinci-resolve-mcp/src/__init__.py +0 -3
- package/mcps/davinci-resolve-mcp/src/analysis_dashboard.py +0 -15505
- package/mcps/davinci-resolve-mcp/src/batch_cli.py +0 -540
- package/mcps/davinci-resolve-mcp/src/control_panel.py +0 -13
- package/mcps/davinci-resolve-mcp/src/granular/__init__.py +0 -17
- package/mcps/davinci-resolve-mcp/src/granular/common.py +0 -741
- package/mcps/davinci-resolve-mcp/src/granular/folder.py +0 -420
- package/mcps/davinci-resolve-mcp/src/granular/gallery.py +0 -306
- package/mcps/davinci-resolve-mcp/src/granular/graph.py +0 -309
- package/mcps/davinci-resolve-mcp/src/granular/media_pool.py +0 -679
- package/mcps/davinci-resolve-mcp/src/granular/media_pool_item.py +0 -1020
- package/mcps/davinci-resolve-mcp/src/granular/media_storage.py +0 -179
- package/mcps/davinci-resolve-mcp/src/granular/project.py +0 -1654
- package/mcps/davinci-resolve-mcp/src/granular/resolve_control.py +0 -538
- package/mcps/davinci-resolve-mcp/src/granular/timeline.py +0 -1075
- package/mcps/davinci-resolve-mcp/src/granular/timeline_item.py +0 -2251
- package/mcps/davinci-resolve-mcp/src/resolve_mcp_server.py +0 -43
- package/mcps/davinci-resolve-mcp/src/server.py +0 -24116
- package/mcps/davinci-resolve-mcp/src/utils/__init__.py +0 -3
- package/mcps/davinci-resolve-mcp/src/utils/actor_identity.py +0 -46
- package/mcps/davinci-resolve-mcp/src/utils/analysis_caps.py +0 -678
- package/mcps/davinci-resolve-mcp/src/utils/analysis_memory.py +0 -755
- package/mcps/davinci-resolve-mcp/src/utils/analysis_runs.py +0 -307
- package/mcps/davinci-resolve-mcp/src/utils/analysis_store.py +0 -945
- package/mcps/davinci-resolve-mcp/src/utils/api_truth.py +0 -525
- package/mcps/davinci-resolve-mcp/src/utils/app_control.py +0 -319
- package/mcps/davinci-resolve-mcp/src/utils/audio_fairlight_live_probe.py +0 -263
- package/mcps/davinci-resolve-mcp/src/utils/brain_edits.py +0 -387
- package/mcps/davinci-resolve-mcp/src/utils/bridge_metrics.py +0 -76
- package/mcps/davinci-resolve-mcp/src/utils/cdl.py +0 -20
- package/mcps/davinci-resolve-mcp/src/utils/clip_query.py +0 -85
- package/mcps/davinci-resolve-mcp/src/utils/cloud_operations.py +0 -192
- package/mcps/davinci-resolve-mcp/src/utils/color_grade_live_probe.py +0 -444
- package/mcps/davinci-resolve-mcp/src/utils/contracts.py +0 -102
- package/mcps/davinci-resolve-mcp/src/utils/cut_ir.py +0 -115
- package/mcps/davinci-resolve-mcp/src/utils/dctl_templates.py +0 -368
- package/mcps/davinci-resolve-mcp/src/utils/deep_vision.py +0 -643
- package/mcps/davinci-resolve-mcp/src/utils/destructive_hook.py +0 -606
- package/mcps/davinci-resolve-mcp/src/utils/edit_engine.py +0 -737
- package/mcps/davinci-resolve-mcp/src/utils/embeddings.py +0 -922
- package/mcps/davinci-resolve-mcp/src/utils/entities.py +0 -579
- package/mcps/davinci-resolve-mcp/src/utils/extension_authoring_live_probe.py +0 -292
- package/mcps/davinci-resolve-mcp/src/utils/failure_tracker.py +0 -119
- package/mcps/davinci-resolve-mcp/src/utils/fuse_templates.py +0 -1968
- package/mcps/davinci-resolve-mcp/src/utils/fusion_composition_live_probe.py +0 -284
- package/mcps/davinci-resolve-mcp/src/utils/fusion_group_settings.py +0 -334
- package/mcps/davinci-resolve-mcp/src/utils/layout_presets.py +0 -333
- package/mcps/davinci-resolve-mcp/src/utils/mcp_stdio.py +0 -32
- package/mcps/davinci-resolve-mcp/src/utils/mcp_transport.py +0 -128
- package/mcps/davinci-resolve-mcp/src/utils/media_analysis.py +0 -7690
- package/mcps/davinci-resolve-mcp/src/utils/media_analysis_jobs.py +0 -805
- package/mcps/davinci-resolve-mcp/src/utils/media_pool_changes.py +0 -121
- package/mcps/davinci-resolve-mcp/src/utils/media_pool_ingest_live_probe.py +0 -592
- package/mcps/davinci-resolve-mcp/src/utils/multicam.py +0 -393
- package/mcps/davinci-resolve-mcp/src/utils/object_inspection.py +0 -301
- package/mcps/davinci-resolve-mcp/src/utils/page_lock.py +0 -75
- package/mcps/davinci-resolve-mcp/src/utils/platform.py +0 -157
- package/mcps/davinci-resolve-mcp/src/utils/proc.py +0 -26
- package/mcps/davinci-resolve-mcp/src/utils/project_cleanup.py +0 -73
- package/mcps/davinci-resolve-mcp/src/utils/project_lifecycle_live_probe.py +0 -376
- package/mcps/davinci-resolve-mcp/src/utils/project_lint.py +0 -131
- package/mcps/davinci-resolve-mcp/src/utils/project_properties.py +0 -601
- package/mcps/davinci-resolve-mcp/src/utils/project_spec.py +0 -459
- package/mcps/davinci-resolve-mcp/src/utils/readback.py +0 -91
- package/mcps/davinci-resolve-mcp/src/utils/render_deliver_live_probe.py +0 -384
- package/mcps/davinci-resolve-mcp/src/utils/resolve_ai_governance.py +0 -214
- package/mcps/davinci-resolve-mcp/src/utils/resolve_ai_ledger.py +0 -244
- package/mcps/davinci-resolve-mcp/src/utils/resolve_busy.py +0 -171
- package/mcps/davinci-resolve-mcp/src/utils/resolve_connection.py +0 -77
- package/mcps/davinci-resolve-mcp/src/utils/review_annotation_live_probe.py +0 -352
- package/mcps/davinci-resolve-mcp/src/utils/script_templates.py +0 -1193
- package/mcps/davinci-resolve-mcp/src/utils/shot_relationships.py +0 -544
- package/mcps/davinci-resolve-mcp/src/utils/structural_diff.py +0 -175
- package/mcps/davinci-resolve-mcp/src/utils/sync_detection.py +0 -889
- package/mcps/davinci-resolve-mcp/src/utils/timeline_brain_db.py +0 -826
- package/mcps/davinci-resolve-mcp/src/utils/timeline_conform_live_probe.py +0 -280
- package/mcps/davinci-resolve-mcp/src/utils/timeline_kernel_live_probe.py +0 -1092
- package/mcps/davinci-resolve-mcp/src/utils/timeline_kernel_probe.py +0 -185
- package/mcps/davinci-resolve-mcp/src/utils/timeline_title_text.py +0 -87
- package/mcps/davinci-resolve-mcp/src/utils/timeline_versioning.py +0 -730
- package/mcps/davinci-resolve-mcp/src/utils/update_check.py +0 -685
- package/mcps/vectorworks-mcp/.dockerignore +0 -8
- package/mcps/vectorworks-mcp/Dockerfile +0 -28
- package/mcps/vectorworks-mcp/README.md +0 -156
- package/mcps/vectorworks-mcp/app/__init__.py +0 -2
- package/mcps/vectorworks-mcp/app/api.py +0 -52
- package/mcps/vectorworks-mcp/app/chunking.py +0 -56
- package/mcps/vectorworks-mcp/app/config.py +0 -29
- package/mcps/vectorworks-mcp/app/embeddings.py +0 -28
- package/mcps/vectorworks-mcp/app/indexer.py +0 -106
- package/mcps/vectorworks-mcp/app/main.py +0 -33
- package/mcps/vectorworks-mcp/app/mcp_server.py +0 -71
- package/mcps/vectorworks-mcp/app/search.py +0 -54
- package/mcps/vectorworks-mcp/app/utils.py +0 -14
- package/mcps/vectorworks-mcp/data/.gitkeep +0 -1
- package/mcps/vectorworks-mcp/data/vw-app-help/VW2022_PluginScript.htm +0 -95
- package/mcps/vectorworks-mcp/data/vw-app-help/VW2022_ScriptEditor.htm +0 -266
- package/mcps/vectorworks-mcp/data/vw-app-help/VW2022_Scripting.htm +0 -106
- package/mcps/vectorworks-mcp/data/vw-app-help/VW2023_CreatingScriptedPlugins.htm +0 -327
- package/mcps/vectorworks-mcp/data/vw-app-help/VW2024_RunningScripts.htm +0 -116
- package/mcps/vectorworks-mcp/data/vw-devwiki/VS_Function_Reference_category.html +0 -2129
- package/mcps/vectorworks-mcp/data/vw-jp-ref/ObjectsSymbols.html +0 -2035
- package/mcps/vectorworks-mcp/data/vw-jp-ref/ScriptFunctionReference.html +0 -29
- package/mcps/vectorworks-mcp/design.md +0 -57
- package/mcps/vectorworks-mcp/docker-compose.yml +0 -18
- package/mcps/vectorworks-mcp/index/.gitkeep +0 -1
- package/mcps/vectorworks-mcp/index/meta.jsonl +0 -3956
- package/mcps/vectorworks-mcp/index/vw.faiss +0 -0
- package/mcps/vectorworks-mcp/requirements.txt +0 -10
- package/mcps/vectorworks-mcp/scripts/fetch_docs_minimal.sh +0 -84
- package/mcps/vectorworks-mcp/scripts/fetch_github_vectorworks.sh +0 -85
- package/mcps/vectorworks-mcp/templates/index.html +0 -40
- package/scripts/ecosystem-health-audit.js +0 -257
- package/skills/bdb-dev-os-skill/SKILL.md +0 -49
- package/skills/bdb-dev-os-skill/package.json +0 -11
- package/skills/bdbsaastraining/SKILL.md +0 -316
- package/skills/bdbsaastraining/references/exam-pool.md +0 -313
- package/skills/bdbsaastraining/references/interaction-modes.md +0 -94
- package/skills/bdbsaastraining/references/track-a-ai-agent.md +0 -111
- package/skills/bdbsaastraining/references/track-b-wordpress.md +0 -111
- package/skills/bdbsaastraining/references/track-c-mailserver.md +0 -107
- package/skills/bdbsaastraining/references/track-d-debian.md +0 -109
- package/skills/bdbsaastraining/references/track-e-custom.md +0 -83
- package/skills/bdbsaastraining/scripts/build_profile.py +0 -529
- package/skills/bdbsaastraining/scripts/generate_certificate.py +0 -161
- package/skills/bdbsaastraining/scripts/preflight_check.sh +0 -150
- package/skills/bdbsaastraining/templates/certificate_template.html +0 -508
- package/skills/global_config/bdb-ecosystem-health/SKILL.md +0 -68
- package/skills/global_config/bdbsaas-ops/SKILL.md +0 -16
- 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 => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[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
|
+
});
|