create-workframe 0.1.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/.dockerignore +22 -0
- package/.gitignore +73 -0
- package/LICENSE +201 -0
- package/NOTICE +12 -0
- package/README.md +111 -0
- package/SECURITY.md +40 -0
- package/bin/create-workframe.js +2814 -0
- package/bin/workframe.js +329 -0
- package/docs/workspace-instructions/WORKFRAME_DISCORD.md +20 -0
- package/docs/workspace-instructions/WORKFRAME_DOCUMENTS_AND_ARTIFACTS.md +20 -0
- package/docs/workspace-instructions/WORKFRAME_KANBAN.md +20 -0
- package/docs/workspace-instructions/WORKFRAME_ONBOARDING.md +21 -0
- package/docs/workspace-instructions/WORKFRAME_ROUTING.md +29 -0
- package/docs/workspace-instructions/WORKFRAME_TELEGRAM.md +19 -0
- package/package.json +67 -0
- package/profiles/README.md +15 -0
- package/profiles/architect/AGENTS.md +29 -0
- package/profiles/architect/SOUL.md +44 -0
- package/profiles/architect/skills/devops/kanban-worker/SKILL.md +27 -0
- package/profiles/designer/AGENTS.md +26 -0
- package/profiles/designer/SOUL.md +31 -0
- package/profiles/designer/skills/devops/kanban-worker/SKILL.md +27 -0
- package/profiles/dev/AGENTS.md +28 -0
- package/profiles/dev/SOUL.md +31 -0
- package/profiles/dev/skills/devops/kanban-worker/SKILL.md +27 -0
- package/profiles/docs/AGENTS.md +27 -0
- package/profiles/docs/SOUL.md +30 -0
- package/profiles/docs/skills/devops/kanban-worker/SKILL.md +27 -0
- package/profiles/research/AGENTS.md +26 -0
- package/profiles/research/SOUL.md +31 -0
- package/profiles/research/skills/devops/kanban-worker/SKILL.md +27 -0
- package/profiles/visionary/AGENTS.md +25 -0
- package/profiles/visionary/SOUL.md +31 -0
- package/profiles/visionary/skills/devops/kanban-worker/SKILL.md +27 -0
- package/profiles/workframe-agent/AGENTS.md +37 -0
- package/profiles/workframe-agent/SETUP.md +185 -0
- package/profiles/workframe-agent/SOUL.md +61 -0
- package/profiles/workframe-agent/skills/devops/botfather/SKILL.md +85 -0
- package/profiles/workframe-agent/skills/devops/kanban-handoff-pattern/SKILL.md +58 -0
- package/profiles/workframe-agent/skills/devops/workframe-cohort/SKILL.md +54 -0
- package/prompts/WORKFRAME_PROMPT_TEMPLATES.md +16 -0
- package/rules/.hermes.md +11 -0
- package/rules/AGENTS.md +22 -0
- package/rules/workspace-README.md +5 -0
- package/scripts/apply-update-hermes.sh +17 -0
- package/scripts/apply-update-workframe.sh +77 -0
- package/scripts/bootstrap-workspace-link.sh +8 -0
- package/scripts/bundle-workframe-ui.mjs +77 -0
- package/scripts/compose-docker-host.sh +37 -0
- package/scripts/create_workframe_scaffold.py +648 -0
- package/scripts/ensure-compose-host-paths.mjs +51 -0
- package/scripts/fix-zk-encryption-key.sh +35 -0
- package/scripts/lib/install-identity.mjs +212 -0
- package/scripts/lib/workframe-registry.mjs +290 -0
- package/scripts/new-project.mjs +68 -0
- package/scripts/restart-gateway-hermes.sh +12 -0
- package/scripts/security_audit.py +156 -0
- package/scripts/select_agent_pack.py +31 -0
- package/scripts/set-compose-public-url.mjs +92 -0
- package/scripts/setup-stack-secrets.sh +50 -0
- package/scripts/sync-canonical-to-package.mjs +146 -0
- package/scripts/test-scaffold.mjs +390 -0
- package/scripts/verify-public-deploy.sh +105 -0
- package/shared/WORKFRAME_AGENT_LIBRARY.md +31 -0
- package/shared/WORKFRAME_AGENT_OPERATIONS.md +29 -0
- package/shared/WORKFRAME_AGENT_PACKS.json +64 -0
- package/shared/WORKFRAME_AGENT_PACKS.yaml +20 -0
- package/shared/WORKFRAME_CHAT_PERMISSION_MODEL.md +20 -0
- package/shared/WORKFRAME_HANDOFF_SCHEMA.md +25 -0
- package/shared/WORKFRAME_SKILL_CURATION.md +27 -0
- package/shared/agent-avatars/ada.png +0 -0
- package/shared/agent-avatars/aibert.png +0 -0
- package/shared/agent-avatars/amelia.png +0 -0
- package/shared/agent-avatars/andy.png +0 -0
- package/shared/agent-avatars/arc.png +0 -0
- package/shared/agent-avatars/bob.png +0 -0
- package/shared/agent-avatars/buzz.png +0 -0
- package/shared/agent-avatars/carl.png +0 -0
- package/shared/agent-avatars/catalog.json +171 -0
- package/shared/agent-avatars/corbu.png +0 -0
- package/shared/agent-avatars/diana.png +0 -0
- package/shared/agent-avatars/ella.png +0 -0
- package/shared/agent-avatars/elvis.png +0 -0
- package/shared/agent-avatars/f1.png +0 -0
- package/shared/agent-avatars/f2.png +0 -0
- package/shared/agent-avatars/f3.png +0 -0
- package/shared/agent-avatars/f4.png +0 -0
- package/shared/agent-avatars/f5.png +0 -0
- package/shared/agent-avatars/f6.png +0 -0
- package/shared/agent-avatars/frida.png +0 -0
- package/shared/agent-avatars/george.png +0 -0
- package/shared/agent-avatars/grace.png +0 -0
- package/shared/agent-avatars/hedy.png +0 -0
- package/shared/agent-avatars/hermes.png +0 -0
- package/shared/agent-avatars/isaac.png +0 -0
- package/shared/agent-avatars/jes.png +0 -0
- package/shared/agent-avatars/john.png +0 -0
- package/shared/agent-avatars/joni.png +0 -0
- package/shared/agent-avatars/leo.png +0 -0
- package/shared/agent-avatars/louis.png +0 -0
- package/shared/agent-avatars/ludwig.png +0 -0
- package/shared/agent-avatars/m1.png +0 -0
- package/shared/agent-avatars/m2.png +0 -0
- package/shared/agent-avatars/m3.png +0 -0
- package/shared/agent-avatars/m4.png +0 -0
- package/shared/agent-avatars/m5.png +0 -0
- package/shared/agent-avatars/m6.png +0 -0
- package/shared/agent-avatars/marie.png +0 -0
- package/shared/agent-avatars/marilyn.png +0 -0
- package/shared/agent-avatars/neil.png +0 -0
- package/shared/agent-avatars/nikola.png +0 -0
- package/shared/agent-avatars/nina.png +0 -0
- package/shared/agent-avatars/paul.png +0 -0
- package/shared/agent-avatars/ringo.png +0 -0
- package/shared/agent-avatars/rosie.png +0 -0
- package/shared/agent-avatars/ste.png +0 -0
- package/shared/agent-avatars/steve.png +0 -0
- package/shared/agent-avatars/sun.png +0 -0
- package/shared/agent-avatars/tom.png +0 -0
- package/shared/agent-avatars/warren.png +0 -0
- package/shared/agent-avatars/woz.png +0 -0
- package/shared/agent-avatars/zaha.png +0 -0
- package/workframe-api/Dockerfile +14 -0
- package/workframe-api/README.md +28 -0
- package/workframe-api/action_proxy.py +131 -0
- package/workframe-api/auth_rate_limit.py +49 -0
- package/workframe-api/catalog/avatar-catalog.json +171 -0
- package/workframe-api/catalog/logo-catalog.json +86 -0
- package/workframe-api/catalog/user-avatar-catalog.json +171 -0
- package/workframe-api/credential_vault.py +445 -0
- package/workframe-api/data/.gitkeep +0 -0
- package/workframe-api/data/avatar-catalog.json +41 -0
- package/workframe-api/data/logo-catalog.json +14 -0
- package/workframe-api/data/user-avatar-catalog.json +18 -0
- package/workframe-api/email_sender.py +220 -0
- package/workframe-api/google_auth.py +90 -0
- package/workframe-api/install_api.py +359 -0
- package/workframe-api/internal_proxy_auth.py +150 -0
- package/workframe-api/llm_proxy.py +277 -0
- package/workframe-api/oidc_jwt.py +108 -0
- package/workframe-api/package.json +13 -0
- package/workframe-api/platform_auth.py +194 -0
- package/workframe-api/profile_secret_policy.py +86 -0
- package/workframe-api/public/assets/index-DPXu_lGn.css +1 -0
- package/workframe-api/public/assets/index-DYnLrCZZ.js +9 -0
- package/workframe-api/public/assets/index-DglUqFB_.js +9 -0
- package/workframe-api/public/index.html +12 -0
- package/workframe-api/requirements.txt +2 -0
- package/workframe-api/server.py +19646 -0
- package/workframe-api/site_meta.py +271 -0
- package/workframe-api/stack_config.py +427 -0
- package/workframe-api/tests/__init__.py +0 -0
- package/workframe-api/tests/db_setup.py +13 -0
- package/workframe-api/tests/test_admin_updates_gated.py +30 -0
- package/workframe-api/tests/test_agent_dm_bootstrap.py +196 -0
- package/workframe-api/tests/test_agent_profile_sync.py +76 -0
- package/workframe-api/tests/test_auth_email.py +222 -0
- package/workframe-api/tests/test_auth_hole_fix_selfcheck.py +99 -0
- package/workframe-api/tests/test_auth_rate_limit.py +19 -0
- package/workframe-api/tests/test_avatar_resolve.py +77 -0
- package/workframe-api/tests/test_child_soul_template.py +71 -0
- package/workframe-api/tests/test_credential_canary.py +135 -0
- package/workframe-api/tests/test_credential_isolation.py +448 -0
- package/workframe-api/tests/test_credential_resolution.py +206 -0
- package/workframe-api/tests/test_device_oauth.py +108 -0
- package/workframe-api/tests/test_doctor_repair.py +103 -0
- package/workframe-api/tests/test_ensure_profile_api.py +77 -0
- package/workframe-api/tests/test_gateway_compose_security.py +136 -0
- package/workframe-api/tests/test_install_secure_host.py +39 -0
- package/workframe-api/tests/test_internal_proxy_auth.py +125 -0
- package/workframe-api/tests/test_invite_runtime_bootstrap.py +72 -0
- package/workframe-api/tests/test_kanban_delegation.py +185 -0
- package/workframe-api/tests/test_llm_proxy.py +155 -0
- package/workframe-api/tests/test_login_access_policy.py +183 -0
- package/workframe-api/tests/test_mvp_model_bootstrap.py +75 -0
- package/workframe-api/tests/test_onboarding_bootstrap.py +248 -0
- package/workframe-api/tests/test_platform_auth.py +47 -0
- package/workframe-api/tests/test_profile_config_path.py +56 -0
- package/workframe-api/tests/test_profile_config_yaml_repair.py +63 -0
- package/workframe-api/tests/test_profile_create.py +72 -0
- package/workframe-api/tests/test_profile_identity_overlay.py +61 -0
- package/workframe-api/tests/test_profile_install_health.py +45 -0
- package/workframe-api/tests/test_profile_secret_policy.py +57 -0
- package/workframe-api/tests/test_profile_workspace_cwd.py +34 -0
- package/workframe-api/tests/test_provider_bootstrap.py +75 -0
- package/workframe-api/tests/test_provider_connect.py +54 -0
- package/workframe-api/tests/test_room_crud.py +192 -0
- package/workframe-api/tests/test_room_tenancy.py +701 -0
- package/workframe-api/tests/test_runtime_identity_backfill.py +34 -0
- package/workframe-api/tests/test_site_meta.py +81 -0
- package/workframe-api/tests/test_soul_stub.py +42 -0
- package/workframe-api/tests/test_space_member_sync.py +99 -0
- package/workframe-api/tests/test_stripe_stack_config.py +37 -0
- package/workframe-api/tests/test_supervisor_lifecycle.py +52 -0
- package/workframe-api/tests/test_turn_credential_vault.py +125 -0
- package/workframe-api/tests/test_updates.py +176 -0
- package/workframe-api/tests/test_user_cohort.py +113 -0
- package/workframe-api/tests/test_vault_envelope.py +110 -0
- package/workframe-api/tests/test_workspace_members.py +183 -0
- package/workframe-api/tests/test_workspace_messaging_sync.py +125 -0
- package/workframe-api/tests/test_workspace_provider_list.py +57 -0
- package/workframe-api/time-bind-chat.py +99 -0
- package/workframe-api/turn_credentials.py +226 -0
- package/workframe-api/updates.py +417 -0
- package/workframe-api/vault_kek.py +159 -0
- package/workframe-api/zk_auth.py +633 -0
- package/workframe-supervisor/Dockerfile +11 -0
- package/workframe-supervisor/profile_secret_policy.py +76 -0
- package/workframe-supervisor/server.py +787 -0
- package/workframe-supervisor/tests/test_exec_guard.py +42 -0
- package/workframe-supervisor/tests/test_server_import.py +21 -0
- package/workframe-ui/docker/nginx.conf +85 -0
- package/workframe-ui/public/assets/1-DLJbBkOb.png +0 -0
- package/workframe-ui/public/assets/10-uwRwj5ce.png +0 -0
- package/workframe-ui/public/assets/11-5OuV9F_e.png +0 -0
- package/workframe-ui/public/assets/12-u_axjxW-.png +0 -0
- package/workframe-ui/public/assets/13-ldSvcMsH.png +0 -0
- package/workframe-ui/public/assets/14-xdcALEYD.png +0 -0
- package/workframe-ui/public/assets/15-aZ4snEFB.png +0 -0
- package/workframe-ui/public/assets/16-L_5-DttY.png +0 -0
- package/workframe-ui/public/assets/2-zOPZTppD.png +0 -0
- package/workframe-ui/public/assets/3-Dc3WoVu5.png +0 -0
- package/workframe-ui/public/assets/4-C50hk7_m.png +0 -0
- package/workframe-ui/public/assets/5-Eweetkq4.png +0 -0
- package/workframe-ui/public/assets/6-5sOXgfkw.png +0 -0
- package/workframe-ui/public/assets/7-BqRBCbiC.png +0 -0
- package/workframe-ui/public/assets/8-DEDKS94h.png +0 -0
- package/workframe-ui/public/assets/9-DNj34GW-.png +0 -0
- package/workframe-ui/public/assets/ada-DsvuOc9n.png +0 -0
- package/workframe-ui/public/assets/aibert-BCz8Lo8H.png +0 -0
- package/workframe-ui/public/assets/amelia-DUf3EBGu.png +0 -0
- package/workframe-ui/public/assets/andy-Cpymuhhx.png +0 -0
- package/workframe-ui/public/assets/arc-CBDYvkAF.js +1 -0
- package/workframe-ui/public/assets/architecture-7EHR7CIX-CtbQKTuT.js +1 -0
- package/workframe-ui/public/assets/architectureDiagram-3BPJPVTR-XnBRKeW0.js +36 -0
- package/workframe-ui/public/assets/array-BifhSqXX.js +1 -0
- package/workframe-ui/public/assets/avatars/ada.png +0 -0
- package/workframe-ui/public/assets/avatars/aibert.png +0 -0
- package/workframe-ui/public/assets/avatars/amelia.png +0 -0
- package/workframe-ui/public/assets/avatars/andy.png +0 -0
- package/workframe-ui/public/assets/avatars/bob.png +0 -0
- package/workframe-ui/public/assets/avatars/buzz.png +0 -0
- package/workframe-ui/public/assets/avatars/carl.png +0 -0
- package/workframe-ui/public/assets/avatars/catalog.json +171 -0
- package/workframe-ui/public/assets/avatars/corbu.png +0 -0
- package/workframe-ui/public/assets/avatars/diana.png +0 -0
- package/workframe-ui/public/assets/avatars/elvis.png +0 -0
- package/workframe-ui/public/assets/avatars/frida.png +0 -0
- package/workframe-ui/public/assets/avatars/george.png +0 -0
- package/workframe-ui/public/assets/avatars/grace.png +0 -0
- package/workframe-ui/public/assets/avatars/hedy.png +0 -0
- package/workframe-ui/public/assets/avatars/hermes.png +0 -0
- package/workframe-ui/public/assets/avatars/isaac.png +0 -0
- package/workframe-ui/public/assets/avatars/john.png +0 -0
- package/workframe-ui/public/assets/avatars/joni.png +0 -0
- package/workframe-ui/public/assets/avatars/leo.png +0 -0
- package/workframe-ui/public/assets/avatars/louis.png +0 -0
- package/workframe-ui/public/assets/avatars/ludwig.png +0 -0
- package/workframe-ui/public/assets/avatars/marie.png +0 -0
- package/workframe-ui/public/assets/avatars/marilyn.png +0 -0
- package/workframe-ui/public/assets/avatars/nikola.png +0 -0
- package/workframe-ui/public/assets/avatars/nina.png +0 -0
- package/workframe-ui/public/assets/avatars/paul.png +0 -0
- package/workframe-ui/public/assets/avatars/ringo.png +0 -0
- package/workframe-ui/public/assets/avatars/rosie.png +0 -0
- package/workframe-ui/public/assets/avatars/steve.png +0 -0
- package/workframe-ui/public/assets/avatars/sun.png +0 -0
- package/workframe-ui/public/assets/avatars/warren.png +0 -0
- package/workframe-ui/public/assets/avatars/woz.png +0 -0
- package/workframe-ui/public/assets/avatars/zaha.png +0 -0
- package/workframe-ui/public/assets/blockDiagram-GPEHLZMM-VYHUfVhd.js +132 -0
- package/workframe-ui/public/assets/bob-DRz-48Id.png +0 -0
- package/workframe-ui/public/assets/branding/banner.png +0 -0
- package/workframe-ui/public/assets/branding/og-default.png +0 -0
- package/workframe-ui/public/assets/branding/workframe'white.png +0 -0
- package/workframe-ui/public/assets/branding/workframe-1.png +0 -0
- package/workframe-ui/public/assets/branding/workframe-2.png +0 -0
- package/workframe-ui/public/assets/branding/workframe-3.png +0 -0
- package/workframe-ui/public/assets/branding/workframe-4.png +0 -0
- package/workframe-ui/public/assets/branding/workframe-5.png +0 -0
- package/workframe-ui/public/assets/branding/workframe-banner.png +0 -0
- package/workframe-ui/public/assets/branding/workframe-logo-horizontal-mini.png +0 -0
- package/workframe-ui/public/assets/branding/workframe-logo-horizontal-nano.png +0 -0
- package/workframe-ui/public/assets/branding/workframe-logo-horizontal.png +0 -0
- package/workframe-ui/public/assets/branding/workframe-logo-vertical-alt.png +0 -0
- package/workframe-ui/public/assets/branding/workframe-logo-vertical.png +0 -0
- package/workframe-ui/public/assets/branding/workframe.png +0 -0
- package/workframe-ui/public/assets/buzz-mC4PtMvC.png +0 -0
- package/workframe-ui/public/assets/c4Diagram-AAUBKEIU-BTjUcJpm.js +10 -0
- package/workframe-ui/public/assets/carl-CtE74db_.png +0 -0
- package/workframe-ui/public/assets/channel-Dy4Z4-jn.js +1 -0
- package/workframe-ui/public/assets/chunk-2J33WTMH-w7uu7R-b.js +1 -0
- package/workframe-ui/public/assets/chunk-3OPIFGDE-Cb9LtnDX.js +62 -0
- package/workframe-ui/public/assets/chunk-4BX2VUAB-DiQ-qCwH.js +1 -0
- package/workframe-ui/public/assets/chunk-55IACEB6-C-mLFr7z.js +1 -0
- package/workframe-ui/public/assets/chunk-5ZQYHXKU-DOesfiCI.js +2 -0
- package/workframe-ui/public/assets/chunk-727SXJPM-BJ3oBZuz.js +206 -0
- package/workframe-ui/public/assets/chunk-AQP2D5EJ-CCA6xpGs.js +231 -0
- package/workframe-ui/public/assets/chunk-BSJP7CBP-a0cMNFb2.js +1 -0
- package/workframe-ui/public/assets/chunk-CSCIHK7Q-kuqN8EIY.js +122 -0
- package/workframe-ui/public/assets/chunk-FMBD7UC4-DyPgYHCg.js +15 -0
- package/workframe-ui/public/assets/chunk-KSCS5N6A-CdUuvR0V.js +10 -0
- package/workframe-ui/public/assets/chunk-L5ZTLDWV-Dq9NoWmK.js +1 -0
- package/workframe-ui/public/assets/chunk-LZXEDZCA-p74rddlO.js +2 -0
- package/workframe-ui/public/assets/chunk-ND2GUHAM-DBD2u1Gz.js +1 -0
- package/workframe-ui/public/assets/chunk-NNHCCRGN-DlpIbxXb.js +159 -0
- package/workframe-ui/public/assets/chunk-NZK2D7GU-BeIeYFnd.js +1 -0
- package/workframe-ui/public/assets/chunk-O5CBEL6O-ClHc56ib.js +70 -0
- package/workframe-ui/public/assets/chunk-QZHKN3VN-CtBEchFK.js +1 -0
- package/workframe-ui/public/assets/chunk-WU5MYG2G-B9pBtriN.js +1 -0
- package/workframe-ui/public/assets/chunk-XPW4576I-EFr8R_1p.js +32 -0
- package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-BMAEA8jI.js +1 -0
- package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-BMAEA8jI.js +1 -0
- package/workframe-ui/public/assets/corbu-KiaMXzXQ.png +0 -0
- package/workframe-ui/public/assets/cose-bilkent-S5V4N54A-C7aPBODd.js +1 -0
- package/workframe-ui/public/assets/cytoscape.esm-h6BdjjI9.js +321 -0
- package/workframe-ui/public/assets/dagre-BM42HDAG-BdU1Rv-H.js +4 -0
- package/workframe-ui/public/assets/dagre-Bx709z4p.js +1 -0
- package/workframe-ui/public/assets/defaultLocale-C8Fc0cco.js +1 -0
- package/workframe-ui/public/assets/diagram-2AECGRRQ-DWowSo85.js +43 -0
- package/workframe-ui/public/assets/diagram-5GNKFQAL-MnxBbceO.js +10 -0
- package/workframe-ui/public/assets/diagram-KO2AKTUF-DQaLRXFf.js +3 -0
- package/workframe-ui/public/assets/diagram-LMA3HP47-CQaBud9k.js +24 -0
- package/workframe-ui/public/assets/diagram-OG6HWLK6-D8bAXbY9.js +24 -0
- package/workframe-ui/public/assets/diana-DW0MsL38.png +0 -0
- package/workframe-ui/public/assets/dist-DGpTLHr_.js +1 -0
- package/workframe-ui/public/assets/elvis-LCFaZIcT.png +0 -0
- package/workframe-ui/public/assets/erDiagram-TEJ5UH35-1E-xSvBK.js +85 -0
- package/workframe-ui/public/assets/eventmodeling-FCH6USID-D75cstNT.js +1 -0
- package/workframe-ui/public/assets/flowDiagram-I6XJVG4X-CgOVD5hu.js +162 -0
- package/workframe-ui/public/assets/frida-CXFA0w3F.png +0 -0
- package/workframe-ui/public/assets/ganttDiagram-6RSMTGT7-JFYAIauo.js +292 -0
- package/workframe-ui/public/assets/george-DBSH2Sm2.png +0 -0
- package/workframe-ui/public/assets/gitGraph-WXDBUCRP-B9REenIl.js +1 -0
- package/workframe-ui/public/assets/gitGraphDiagram-PVQCEYII-BQ7NcMSn.js +106 -0
- package/workframe-ui/public/assets/grace-BhV0UPc0.png +0 -0
- package/workframe-ui/public/assets/graphlib-B8gBHxth.js +1 -0
- package/workframe-ui/public/assets/hedy-BR2IHift.png +0 -0
- package/workframe-ui/public/assets/hermes-CqCzcE0y.png +0 -0
- package/workframe-ui/public/assets/index-Dnw6vjqb.js +133 -0
- package/workframe-ui/public/assets/index-DpAGxump.css +1 -0
- package/workframe-ui/public/assets/info-J43DQDTF-CL6-eTjH.js +1 -0
- package/workframe-ui/public/assets/infoDiagram-5YYISTIA-LJTODW4W.js +2 -0
- package/workframe-ui/public/assets/init-D6jRqBbL.js +1 -0
- package/workframe-ui/public/assets/isaac-D1nhJAuv.png +0 -0
- package/workframe-ui/public/assets/ishikawaDiagram-YF4QCWOH-bchrQVuo.js +70 -0
- package/workframe-ui/public/assets/john-zSPWwNi4.png +0 -0
- package/workframe-ui/public/assets/joni-BFLoyfJP.png +0 -0
- package/workframe-ui/public/assets/journeyDiagram-JHISSGLW-DkrvYuxP.js +139 -0
- package/workframe-ui/public/assets/kanban-definition-UN3LZRKU-DFRbj0IG.js +89 -0
- package/workframe-ui/public/assets/katex-Vhh-h91d.js +257 -0
- package/workframe-ui/public/assets/leo-C_3IOL11.png +0 -0
- package/workframe-ui/public/assets/line-Vd48P7-O.js +1 -0
- package/workframe-ui/public/assets/linear-Ckizh2G7.js +1 -0
- package/workframe-ui/public/assets/louis-DEEECFSX.png +0 -0
- package/workframe-ui/public/assets/ludwig-_hoKhhyK.png +0 -0
- package/workframe-ui/public/assets/marie-DET6MsfO.png +0 -0
- package/workframe-ui/public/assets/marilyn-DTqwt8Yh.png +0 -0
- package/workframe-ui/public/assets/mermaid-parser.core-Bkimsnqj.js +4 -0
- package/workframe-ui/public/assets/mermaid.core-x0TvVuPo.js +9 -0
- package/workframe-ui/public/assets/mindmap-definition-RKZ34NQL-6ykAFPEz.js +96 -0
- package/workframe-ui/public/assets/nikola-B4PtHrJv.png +0 -0
- package/workframe-ui/public/assets/nina-BYbrOn0d.png +0 -0
- package/workframe-ui/public/assets/ordinal-hYBb2elL.js +1 -0
- package/workframe-ui/public/assets/packet-YPE3B663-Dw3xgMDt.js +1 -0
- package/workframe-ui/public/assets/path-BWPyau1x.js +1 -0
- package/workframe-ui/public/assets/paul-CGURYQIn.png +0 -0
- package/workframe-ui/public/assets/pie-LRSECV5Y-DATysawG.js +1 -0
- package/workframe-ui/public/assets/pieDiagram-4H26LBE5-SJKD1S0S.js +30 -0
- package/workframe-ui/public/assets/project-logos/1.png +0 -0
- package/workframe-ui/public/assets/project-logos/10.png +0 -0
- package/workframe-ui/public/assets/project-logos/11.png +0 -0
- package/workframe-ui/public/assets/project-logos/12.png +0 -0
- package/workframe-ui/public/assets/project-logos/13.png +0 -0
- package/workframe-ui/public/assets/project-logos/14.png +0 -0
- package/workframe-ui/public/assets/project-logos/15.png +0 -0
- package/workframe-ui/public/assets/project-logos/16.png +0 -0
- package/workframe-ui/public/assets/project-logos/2.png +0 -0
- package/workframe-ui/public/assets/project-logos/3.png +0 -0
- package/workframe-ui/public/assets/project-logos/4.png +0 -0
- package/workframe-ui/public/assets/project-logos/5.png +0 -0
- package/workframe-ui/public/assets/project-logos/6.png +0 -0
- package/workframe-ui/public/assets/project-logos/7.png +0 -0
- package/workframe-ui/public/assets/project-logos/8.png +0 -0
- package/workframe-ui/public/assets/project-logos/9.png +0 -0
- package/workframe-ui/public/assets/project-logos/catalog.json +86 -0
- package/workframe-ui/public/assets/quadrantDiagram-W4KKPZXB-BrYDZX8q.js +7 -0
- package/workframe-ui/public/assets/radar-GUYGQ44K-BmWYPCds.js +1 -0
- package/workframe-ui/public/assets/requirementDiagram-4Y6WPE33-DwL9Mc8e.js +84 -0
- package/workframe-ui/public/assets/ringo-WhfUNOyY.png +0 -0
- package/workframe-ui/public/assets/rosie-CAtcIf87.png +0 -0
- package/workframe-ui/public/assets/rough.esm-CSKSodPl.js +1 -0
- package/workframe-ui/public/assets/sankeyDiagram-5OEKKPKP-DYIFsL8h.js +40 -0
- package/workframe-ui/public/assets/sequenceDiagram-3UESZ5HK-0-FPkFk8.js +162 -0
- package/workframe-ui/public/assets/src-B_od6b6h.js +1 -0
- package/workframe-ui/public/assets/stateDiagram-AJRCARHV-BQCiBk6u.js +1 -0
- package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-B89jAMFF.js +1 -0
- package/workframe-ui/public/assets/steve-CgXXJ9EZ.png +0 -0
- package/workframe-ui/public/assets/sun-BLNAhoZd.png +0 -0
- package/workframe-ui/public/assets/timeline-definition-PNZ67QCA-DS3tFcXj.js +120 -0
- package/workframe-ui/public/assets/treeView-BLDUP644-DSyUCKLY.js +1 -0
- package/workframe-ui/public/assets/treemap-LRROVOQU-CEZaNh5Y.js +1 -0
- package/workframe-ui/public/assets/vennDiagram-CIIHVFJN-CD-Vc9NF.js +34 -0
- package/workframe-ui/public/assets/wardley-L42UT6IY-Drq5w1Mc.js +1 -0
- package/workframe-ui/public/assets/wardleyDiagram-YWT4CUSO-DouXDJoF.js +78 -0
- package/workframe-ui/public/assets/warren-DIH7UKMY.png +0 -0
- package/workframe-ui/public/assets/woz-D2yleG-V.png +0 -0
- package/workframe-ui/public/assets/xychartDiagram-2RQKCTM6-DDf_Lol5.js +7 -0
- package/workframe-ui/public/assets/zaha-wersOEq9.png +0 -0
- package/workframe-ui/public/favicon.ico +0 -0
- package/workframe-ui/public/favicon.svg +7 -0
- package/workframe-ui/public/icons.svg +24 -0
- package/workframe-ui/public/index.html +50 -0
- package/workframe-ui/public/manifest.webmanifest +18 -0
- package/workframe-ui/public/workframe-config.json +4 -0
|
@@ -0,0 +1,2814 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import readline from 'node:readline/promises';
|
|
6
|
+
import { stdin as input, stdout as output } from 'node:process';
|
|
7
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
8
|
+
import { allocateInstall, envFileLines, portsForSlot, detectHermesHome, resolveDeployMode } from '../scripts/lib/install-identity.mjs';
|
|
9
|
+
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = path.dirname(__filename);
|
|
12
|
+
const PKG_ROOT = path.resolve(__dirname, '..');
|
|
13
|
+
const PKG_VERSION = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf8')).version;
|
|
14
|
+
const PACKS_PATH = path.join(PKG_ROOT, 'shared', 'WORKFRAME_AGENT_PACKS.json');
|
|
15
|
+
const PROFILES_DIR = path.join(PKG_ROOT, 'profiles');
|
|
16
|
+
|
|
17
|
+
const PROJECT_AGENT_SLOT = 'project-agent';
|
|
18
|
+
const NATIVE_SOUL_TEMPLATE = 'workframe-agent';
|
|
19
|
+
// Hermes HERMES_WRITE_SAFE_ROOT=/opt/data — bind Files under /opt/data/workspace; symlink /workspace.
|
|
20
|
+
const WORKSPACE_DATA_MOUNT = '/opt/data/workspace';
|
|
21
|
+
|
|
22
|
+
/** Bash: auto-apply public overlay when .env says public_multi_user. */
|
|
23
|
+
function composeUpBashBlock() {
|
|
24
|
+
return `compose_file_args() {
|
|
25
|
+
local args=(-f docker-compose.yml)
|
|
26
|
+
if [ -f .env ] && grep -q '^WORKFRAME_DEPLOYMENT_MODE=public_multi_user' .env; then
|
|
27
|
+
args+=(-f docker-compose.public.yml)
|
|
28
|
+
fi
|
|
29
|
+
printf '%s\\n' "\${args[@]}"
|
|
30
|
+
}
|
|
31
|
+
mapfile -t COMPOSE_FILE_ARGS < <(compose_file_args)
|
|
32
|
+
docker compose "\${COMPOSE_FILE_ARGS[@]}" up -d`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** PowerShell: public overlay + Windows host-bindings overlay. */
|
|
36
|
+
function composeUpPs1Block() {
|
|
37
|
+
return `$composeArgs = @('-f', 'docker-compose.yml')
|
|
38
|
+
if ((Test-Path '.env') -and (Select-String -Path '.env' -Pattern '^WORKFRAME_DEPLOYMENT_MODE=public_multi_user' -Quiet)) {
|
|
39
|
+
$composeArgs += '-f', 'docker-compose.public.yml'
|
|
40
|
+
}
|
|
41
|
+
if (Test-Path 'docker-compose.host-bindings.yml') {
|
|
42
|
+
$composeArgs += '-f', 'docker-compose.host-bindings.yml'
|
|
43
|
+
}
|
|
44
|
+
docker compose @composeArgs up -d`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const PROFILE_DESCRIPTIONS = {
|
|
48
|
+
visionary: 'Clarifies product purpose, positioning, strategy, user value, and long-term alignment.',
|
|
49
|
+
architect: 'Defines system design, technical boundaries, implementation plans, and code-review standards.',
|
|
50
|
+
docs: 'Maintains AGENTS.md, .hermes.md, docs indexes, source-of-truth maps, and change summaries.',
|
|
51
|
+
dev: 'Builds and modifies project files, scripts, tests, and implementation artifacts.',
|
|
52
|
+
research: 'Performs technical research, market research, references, competitive analysis, and R&D notes.',
|
|
53
|
+
designer: 'Handles UI direction, design docs, visual assets, image prompts, brand direction, and layout feedback.',
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const GITIGNORE = `# Runtime state: do not commit instance data
|
|
57
|
+
Agents/
|
|
58
|
+
*.db
|
|
59
|
+
*.db-shm
|
|
60
|
+
*.db-wal
|
|
61
|
+
*.log
|
|
62
|
+
logs/
|
|
63
|
+
cache/
|
|
64
|
+
memories/
|
|
65
|
+
sessions/
|
|
66
|
+
kanban/
|
|
67
|
+
state/
|
|
68
|
+
|
|
69
|
+
# Bootstrap seed (optional cleanup after profile bootstrap)
|
|
70
|
+
scripts/seed/
|
|
71
|
+
|
|
72
|
+
# Secrets
|
|
73
|
+
.env
|
|
74
|
+
.env.local
|
|
75
|
+
.env.*.local
|
|
76
|
+
*.pem
|
|
77
|
+
*.key
|
|
78
|
+
*.p12
|
|
79
|
+
*.pfx
|
|
80
|
+
secrets/
|
|
81
|
+
|
|
82
|
+
# Build/tool noise
|
|
83
|
+
node_modules/
|
|
84
|
+
.venv/
|
|
85
|
+
__pycache__/
|
|
86
|
+
.pytest_cache/
|
|
87
|
+
.DS_Store
|
|
88
|
+
Thumbs.db
|
|
89
|
+
.vscode/
|
|
90
|
+
.idea/
|
|
91
|
+
`;
|
|
92
|
+
|
|
93
|
+
const DOCKERIGNORE = `.git
|
|
94
|
+
.gitignore
|
|
95
|
+
node_modules
|
|
96
|
+
.venv
|
|
97
|
+
__pycache__
|
|
98
|
+
.pytest_cache
|
|
99
|
+
*.pyc
|
|
100
|
+
*.pyo
|
|
101
|
+
*.db
|
|
102
|
+
*.db-shm
|
|
103
|
+
*.db-wal
|
|
104
|
+
*.log
|
|
105
|
+
.env
|
|
106
|
+
.env.*
|
|
107
|
+
Agents
|
|
108
|
+
cache
|
|
109
|
+
logs
|
|
110
|
+
memories
|
|
111
|
+
sessions
|
|
112
|
+
kanban
|
|
113
|
+
state
|
|
114
|
+
scripts/seed
|
|
115
|
+
`;
|
|
116
|
+
|
|
117
|
+
const VALID_PROJECT_NAME = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
|
|
118
|
+
|
|
119
|
+
/** @deprecated use allocateInstall / portsForSlot */
|
|
120
|
+
function defaultPortsFromSlug(slug) {
|
|
121
|
+
return portsForSlot(1);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function validateProjectName(name) {
|
|
125
|
+
if (!name || typeof name !== 'string') throw new Error('Project name is required.');
|
|
126
|
+
const trimmed = name.trim();
|
|
127
|
+
if (!trimmed || trimmed === '.' || trimmed === '..') {
|
|
128
|
+
throw new Error('Project name must be a folder basename, not "." or "..".');
|
|
129
|
+
}
|
|
130
|
+
if (trimmed.includes('/') || trimmed.includes('\\') || path.isAbsolute(trimmed)) {
|
|
131
|
+
throw new Error('Project name must be a folder basename, not a path.');
|
|
132
|
+
}
|
|
133
|
+
if (!VALID_PROJECT_NAME.test(trimmed)) {
|
|
134
|
+
throw new Error('Project name must start with a letter or digit and use only letters, digits, hyphens, and underscores.');
|
|
135
|
+
}
|
|
136
|
+
return trimmed;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function resolveProjectTarget(out, name) {
|
|
140
|
+
const safeName = validateProjectName(name);
|
|
141
|
+
const outRoot = path.resolve(out);
|
|
142
|
+
const target = path.resolve(outRoot, safeName);
|
|
143
|
+
const relative = path.relative(outRoot, target);
|
|
144
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
145
|
+
throw new Error('Project path escapes the output directory.');
|
|
146
|
+
}
|
|
147
|
+
if (target === outRoot) {
|
|
148
|
+
throw new Error('Refusing to scaffold directly into the output directory root.');
|
|
149
|
+
}
|
|
150
|
+
return { outRoot, target, name: safeName };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function envFileContent(install, { example = false, nativeProfile = '', deploy = 'docker', hermesHome = '' } = {}) {
|
|
154
|
+
return envFileLines(install, { example, nativeProfile, deploy, hermesHome });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function routesJsonContent(profiles, nativeProfile, projectName) {
|
|
158
|
+
const ctx = renderContext(projectName);
|
|
159
|
+
const routes = profiles.map((profile) => {
|
|
160
|
+
const isNative = profile === nativeProfile;
|
|
161
|
+
const displayName = isNative
|
|
162
|
+
? ctx.nativeAgentName
|
|
163
|
+
: profile.replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
|
|
164
|
+
return {
|
|
165
|
+
id: profile,
|
|
166
|
+
surface: 'ui',
|
|
167
|
+
channel_id: `ui://agent/${profile}`,
|
|
168
|
+
profile,
|
|
169
|
+
display_name: displayName,
|
|
170
|
+
role: profileDescription(profile, projectName),
|
|
171
|
+
mode: 'lane',
|
|
172
|
+
};
|
|
173
|
+
});
|
|
174
|
+
return {
|
|
175
|
+
version: 1,
|
|
176
|
+
default_profile: nativeProfile,
|
|
177
|
+
routes,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function writeRoutesJsonBlockSh(profiles, nativeProfile, projectName) {
|
|
182
|
+
const payload = JSON.stringify(routesJsonContent(profiles, nativeProfile, projectName), null, 2);
|
|
183
|
+
return `mkdir -p "$ROOT/Agents/workframe"
|
|
184
|
+
cat > "$ROOT/Agents/workframe/routes.json" <<'WF_ROUTES_EOF'
|
|
185
|
+
${payload}
|
|
186
|
+
WF_ROUTES_EOF
|
|
187
|
+
`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function writeRoutesJsonBlockPs1(profiles, nativeProfile, projectName) {
|
|
191
|
+
const payload = JSON.stringify(routesJsonContent(profiles, nativeProfile, projectName), null, 2);
|
|
192
|
+
const escaped = payload.replace(/'/g, "''");
|
|
193
|
+
return `$routesDir = Join-Path $Root "..\\Agents\\workframe"
|
|
194
|
+
New-Item -ItemType Directory -Force -Path $routesDir | Out-Null
|
|
195
|
+
@'
|
|
196
|
+
${payload}
|
|
197
|
+
'@ | Set-Content -Path (Join-Path $routesDir "routes.json") -Encoding utf8
|
|
198
|
+
`;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function hermesServiceVolumesBlock(_hermesHome = '', { proxyToken = true } = {}) {
|
|
202
|
+
// ponytail: gateway + API + supervisor must share install Agents/ — never host %LOCALAPPDATA%\\hermes
|
|
203
|
+
const agentsMount = './Agents:/opt/data';
|
|
204
|
+
const proxyVol = proxyToken ? ' - workframe-proxy-token:/run/workframe-proxy:ro\n' : '';
|
|
205
|
+
return ` volumes:
|
|
206
|
+
- ${agentsMount}
|
|
207
|
+
- ./Files:${WORKSPACE_DATA_MOUNT}
|
|
208
|
+
${proxyVol} - ./scripts:/opt/install/scripts:ro
|
|
209
|
+
- ./docker/cont-init-workspace-link.sh:/etc/cont-init.d/03-workspace-link:ro`;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function gatewayProxyBootstrap(profileEsc) {
|
|
213
|
+
const proxy =
|
|
214
|
+
'if [ -z "$WORKFRAME_PROXY_TOKEN" ] && [ -f /run/workframe-proxy/token ]; then export WORKFRAME_PROXY_TOKEN="$(tr -d \'\\r\\n\' < /run/workframe-proxy/token)"; fi; ';
|
|
215
|
+
const base = `mkdir -p /opt/data/Avatars; chmod 0777 /opt/data/Avatars 2>/dev/null || true; if [ -d /opt/data/profiles/${profileEsc} ]; then export HERMES_HOME=/opt/data/profiles/${profileEsc}; export HOME=/opt/data/profiles/${profileEsc}; cd /opt/data/profiles/${profileEsc}; PROFILE_FLAG='-p ${profileEsc}'; else export HERMES_HOME=/opt/data; export HOME=/opt/data; PROFILE_FLAG=''; fi; exec /opt/hermes/bin/hermes $PROFILE_FLAG gateway run --replace`;
|
|
216
|
+
return proxy + base;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function dockerComposePublicYaml(docker) {
|
|
220
|
+
return `# Public VPS overlay — API has no docker.sock or host project mounts.
|
|
221
|
+
# Usage: docker compose -f docker-compose.yml -f docker-compose.public.yml up -d
|
|
222
|
+
name: ${docker.stack}
|
|
223
|
+
|
|
224
|
+
services:
|
|
225
|
+
workframe-api:
|
|
226
|
+
volumes:
|
|
227
|
+
- ./workframe-api/public:/app/public:ro
|
|
228
|
+
- ./workframe-api:/app:ro
|
|
229
|
+
- ./workframe-api/data:/app/data
|
|
230
|
+
- ./Agents:/opt/data
|
|
231
|
+
- ./Files:${WORKSPACE_DATA_MOUNT}
|
|
232
|
+
- ./scripts:/opt/install/scripts:ro
|
|
233
|
+
- workframe-proxy-token:/run/workframe-proxy
|
|
234
|
+
environment:
|
|
235
|
+
- WORKFRAME_COMPOSE_DIR=/compose
|
|
236
|
+
- WORKFRAME_PROJECT_ROOT=/compose
|
|
237
|
+
|
|
238
|
+
workframe-supervisor:
|
|
239
|
+
volumes:
|
|
240
|
+
- ./Agents:/opt/data
|
|
241
|
+
- /var/run/docker.sock:/var/run/docker.sock
|
|
242
|
+
- ./scripts:/opt/install/scripts:ro
|
|
243
|
+
- .:/compose
|
|
244
|
+
environment:
|
|
245
|
+
- WORKFRAME_SCRIPTS_DIR=/opt/install/scripts
|
|
246
|
+
- WORKFRAME_COMPOSE_DIR=/compose
|
|
247
|
+
- WORKFRAME_PROJECT_ROOT=/compose
|
|
248
|
+
- WORKFRAME_SUPERVISOR_ALLOW_RAW_EXEC=0
|
|
249
|
+
`;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function nginxConfYaml() {
|
|
253
|
+
return `map $http_upgrade $connection_upgrade {
|
|
254
|
+
default upgrade;
|
|
255
|
+
'' close;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
server {
|
|
259
|
+
listen 80;
|
|
260
|
+
server_name localhost;
|
|
261
|
+
root /usr/share/nginx/html;
|
|
262
|
+
index index.html;
|
|
263
|
+
|
|
264
|
+
# Workframe API: snapshot-lite, files, chat history, profile routing
|
|
265
|
+
location /api/ {
|
|
266
|
+
proxy_pass http://workframe-api:8080;
|
|
267
|
+
proxy_http_version 1.1;
|
|
268
|
+
proxy_set_header Host $host;
|
|
269
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
270
|
+
proxy_read_timeout 86400s;
|
|
271
|
+
proxy_send_timeout 86400s;
|
|
272
|
+
proxy_buffering off;
|
|
273
|
+
proxy_cache off;
|
|
274
|
+
add_header X-Accel-Buffering no;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
# Hermes admin dashboard: ops, secrets, skills
|
|
278
|
+
location /hermes-dashboard/ {
|
|
279
|
+
proxy_pass http://dashboard:9119/;
|
|
280
|
+
proxy_http_version 1.1;
|
|
281
|
+
proxy_set_header Host $host;
|
|
282
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
283
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
284
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
285
|
+
proxy_set_header X-Forwarded-Host $host;
|
|
286
|
+
proxy_set_header X-Forwarded-Prefix /hermes-dashboard;
|
|
287
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
288
|
+
proxy_set_header Connection $connection_upgrade;
|
|
289
|
+
proxy_read_timeout 86400s;
|
|
290
|
+
proxy_send_timeout 86400s;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
location / {
|
|
294
|
+
try_files $uri $uri/ /index.html;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
location ~* \\.(js|css|svg|webmanifest|png|jpg|jpeg|gif|ico|woff2?)$ {
|
|
298
|
+
expires 1h;
|
|
299
|
+
add_header Cache-Control "public, immutable";
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
`;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function contInitWorkspaceLinkSh() {
|
|
306
|
+
return `#!/bin/with-contenv sh
|
|
307
|
+
# Runs as root during s6 cont-init — before Hermes gateway starts.
|
|
308
|
+
. /opt/install/scripts/bootstrap-workspace-link.sh
|
|
309
|
+
`;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function dashboardProxyConf() {
|
|
313
|
+
return `server {
|
|
314
|
+
listen 9119;
|
|
315
|
+
server_name _;
|
|
316
|
+
|
|
317
|
+
location / {
|
|
318
|
+
proxy_pass http://gateway:9119/;
|
|
319
|
+
proxy_http_version 1.1;
|
|
320
|
+
proxy_set_header Host $host;
|
|
321
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
322
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
`;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function dockerComposeYaml(projectName, docker, nativeProfile, _packProfiles, installId = '', hermesHome = '') {
|
|
329
|
+
const labelProject = projectName.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
330
|
+
const installIdEsc = String(installId || '').replace(/"/g, '\\"');
|
|
331
|
+
const profileEsc = nativeProfile.replace(/"/g, '\\"');
|
|
332
|
+
const gatewayCmd = gatewayProxyBootstrap(profileEsc);
|
|
333
|
+
return `name: ${docker.stack}
|
|
334
|
+
|
|
335
|
+
services:
|
|
336
|
+
gateway:
|
|
337
|
+
image: ${docker.image}
|
|
338
|
+
container_name: ${docker.gateway}
|
|
339
|
+
restart: unless-stopped
|
|
340
|
+
command: [ "sh", "-lc", '${gatewayCmd.replace(/'/g, "''")}' ]
|
|
341
|
+
environment:
|
|
342
|
+
- HERMES_DASHBOARD=1
|
|
343
|
+
- HERMES_DASHBOARD_HOST=0.0.0.0
|
|
344
|
+
- HERMES_DASHBOARD_PORT=9119
|
|
345
|
+
- HERMES_DASHBOARD_TUI=1
|
|
346
|
+
- HERMES_DASHBOARD_BASIC_AUTH_USERNAME=\${HERMES_DASHBOARD_BASIC_AUTH_USERNAME:-workframe}
|
|
347
|
+
- HERMES_DASHBOARD_BASIC_AUTH_PASSWORD=\${HERMES_DASHBOARD_BASIC_AUTH_PASSWORD:-workframe-local}
|
|
348
|
+
- HERMES_DASHBOARD_BASIC_AUTH_SECRET=\${HERMES_DASHBOARD_BASIC_AUTH_SECRET:-workframe-local-dashboard-secret}
|
|
349
|
+
- HERMES_DASHBOARD_PUBLIC_URL=\${APP_BASE_URL:-http://127.0.0.1:\${WORKFRAME_UI_PORT}/hermes-dashboard}
|
|
350
|
+
- WORKFRAME_PROXY_TOKEN=\${WORKFRAME_PROXY_TOKEN:-}
|
|
351
|
+
labels:
|
|
352
|
+
com.workframe.project: "${labelProject}"
|
|
353
|
+
com.workframe.role: gateway
|
|
354
|
+
ports:
|
|
355
|
+
- "127.0.0.1:\${WORKFRAME_GATEWAY_PORT}:8642"
|
|
356
|
+
${hermesServiceVolumesBlock(hermesHome)}
|
|
357
|
+
networks:
|
|
358
|
+
- ${docker.network}
|
|
359
|
+
|
|
360
|
+
dashboard:
|
|
361
|
+
image: nginx:alpine
|
|
362
|
+
container_name: ${docker.dashboard}
|
|
363
|
+
restart: unless-stopped
|
|
364
|
+
command: ["nginx", "-g", "daemon off;"]
|
|
365
|
+
labels:
|
|
366
|
+
com.workframe.project: "${labelProject}"
|
|
367
|
+
com.workframe.role: dashboard
|
|
368
|
+
ports:
|
|
369
|
+
- "127.0.0.1:\${WORKFRAME_DASHBOARD_PORT}:9119"
|
|
370
|
+
volumes:
|
|
371
|
+
- ./docker/dashboard-proxy.conf:/etc/nginx/conf.d/default.conf:ro
|
|
372
|
+
depends_on:
|
|
373
|
+
- gateway
|
|
374
|
+
networks:
|
|
375
|
+
- ${docker.network}
|
|
376
|
+
|
|
377
|
+
workframe-api:
|
|
378
|
+
build:
|
|
379
|
+
context: ./workframe-api
|
|
380
|
+
dockerfile: Dockerfile
|
|
381
|
+
image: ${docker.workframeApi}-image
|
|
382
|
+
container_name: ${docker.workframeApi}
|
|
383
|
+
restart: unless-stopped
|
|
384
|
+
working_dir: /app
|
|
385
|
+
command: ["sh", "-lc", ". /opt/install/scripts/bootstrap-workspace-link.sh; exec python3 server.py"]
|
|
386
|
+
labels:
|
|
387
|
+
com.workframe.project: "${labelProject}"
|
|
388
|
+
com.workframe.role: workframe-api
|
|
389
|
+
ports:
|
|
390
|
+
- "127.0.0.1:\${WORKFRAME_API_PORT}:8080"
|
|
391
|
+
volumes:
|
|
392
|
+
- ./workframe-api/public:/app/public:ro
|
|
393
|
+
- ./workframe-api:/app:ro
|
|
394
|
+
- ./workframe-api/data:/app/data
|
|
395
|
+
- ./Agents:/opt/data
|
|
396
|
+
- ./Files:${WORKSPACE_DATA_MOUNT}
|
|
397
|
+
- ./scripts:/opt/install/scripts:ro
|
|
398
|
+
- workframe-proxy-token:/run/workframe-proxy
|
|
399
|
+
env_file:
|
|
400
|
+
- ./.env
|
|
401
|
+
environment:
|
|
402
|
+
- HERMES_DATA=/opt/data
|
|
403
|
+
- WORKSPACE=/workspace
|
|
404
|
+
- WORKFRAME_API_DATA_DIR=/app/data
|
|
405
|
+
- BOARD_DB=/app/data/board.db
|
|
406
|
+
- HOST=0.0.0.0
|
|
407
|
+
- PORT=8080
|
|
408
|
+
- WORKFRAME_COMPOSE_DIR=/compose
|
|
409
|
+
- WORKFRAME_PROJECT_ROOT=/compose
|
|
410
|
+
- WORKFRAME_HOST_COMPOSE_DIR=\${WORKFRAME_HOST_COMPOSE_DIR:-}
|
|
411
|
+
- WORKFRAME_HOST_PROJECT_ROOT=\${WORKFRAME_HOST_PROJECT_ROOT:-}
|
|
412
|
+
- WORKFRAME_API_VERSION=${PKG_VERSION}
|
|
413
|
+
- WORKFRAME_NATIVE_PROFILE=${profileEsc}
|
|
414
|
+
- WORKFRAME_PROJECT=${labelProject}
|
|
415
|
+
- WORKFRAME_INSTALL_ID=${installIdEsc}
|
|
416
|
+
- WORKFRAME_SLOT=\${WORKFRAME_SLOT:-1}
|
|
417
|
+
- HERMES_DASHBOARD_URL=http://dashboard:9119
|
|
418
|
+
- HERMES_DASHBOARD_PUBLIC_URL=\${APP_BASE_URL:-http://127.0.0.1:\${WORKFRAME_UI_PORT}/hermes-dashboard}
|
|
419
|
+
- HERMES_DASHBOARD_BASIC_AUTH_USERNAME=\${HERMES_DASHBOARD_BASIC_AUTH_USERNAME:-workframe}
|
|
420
|
+
- HERMES_DASHBOARD_BASIC_AUTH_PASSWORD=\${HERMES_DASHBOARD_BASIC_AUTH_PASSWORD:-workframe-local}
|
|
421
|
+
- WORKFRAME_GATEWAY_CONTAINER=${docker.gateway}
|
|
422
|
+
- WORKFRAME_SUPERVISOR_URL=http://workframe-supervisor:8090
|
|
423
|
+
- WORKFRAME_SUPERVISOR_TOKEN=\${WORKFRAME_SUPERVISOR_TOKEN}
|
|
424
|
+
- WORKFRAME_DEPLOYMENT_MODE=\${WORKFRAME_DEPLOYMENT_MODE:-trusted_team}
|
|
425
|
+
- WORKFRAME_PROXY_TOKEN=\${WORKFRAME_PROXY_TOKEN:-}
|
|
426
|
+
depends_on:
|
|
427
|
+
- gateway
|
|
428
|
+
- workframe-supervisor
|
|
429
|
+
networks:
|
|
430
|
+
- ${docker.network}
|
|
431
|
+
- ${docker.controlNetwork}
|
|
432
|
+
|
|
433
|
+
workframe-supervisor:
|
|
434
|
+
build:
|
|
435
|
+
context: ./workframe-supervisor
|
|
436
|
+
dockerfile: Dockerfile
|
|
437
|
+
image: ${docker.workframeSupervisor}-image
|
|
438
|
+
container_name: ${docker.workframeSupervisor}
|
|
439
|
+
restart: unless-stopped
|
|
440
|
+
labels:
|
|
441
|
+
com.workframe.project: "${labelProject}"
|
|
442
|
+
com.workframe.role: supervisor
|
|
443
|
+
ports:
|
|
444
|
+
- "127.0.0.1:\${WORKFRAME_SUPERVISOR_PORT}:8090"
|
|
445
|
+
volumes:
|
|
446
|
+
- ./Agents:/opt/data
|
|
447
|
+
- /var/run/docker.sock:/var/run/docker.sock
|
|
448
|
+
- ./scripts:/opt/install/scripts:ro
|
|
449
|
+
- .:/compose
|
|
450
|
+
env_file:
|
|
451
|
+
- ./.env
|
|
452
|
+
environment:
|
|
453
|
+
- HERMES_DATA=/opt/data
|
|
454
|
+
- WORKFRAME_NATIVE_PROFILE=${profileEsc}
|
|
455
|
+
- WORKFRAME_GATEWAY_CONTAINER=${docker.gateway}
|
|
456
|
+
- HOST=0.0.0.0
|
|
457
|
+
- PORT=8090
|
|
458
|
+
- WORKFRAME_SUPERVISOR_TOKEN=\${WORKFRAME_SUPERVISOR_TOKEN}
|
|
459
|
+
- WORKFRAME_DEPLOYMENT_MODE=\${WORKFRAME_DEPLOYMENT_MODE:-trusted_team}
|
|
460
|
+
- WORKFRAME_SCRIPTS_DIR=/opt/install/scripts
|
|
461
|
+
- WORKFRAME_COMPOSE_DIR=/compose
|
|
462
|
+
- WORKFRAME_PROJECT_ROOT=/compose
|
|
463
|
+
depends_on:
|
|
464
|
+
- gateway
|
|
465
|
+
networks:
|
|
466
|
+
- ${docker.controlNetwork}
|
|
467
|
+
|
|
468
|
+
workframe:
|
|
469
|
+
image: nginx:alpine
|
|
470
|
+
container_name: ${docker.workframe}
|
|
471
|
+
restart: unless-stopped
|
|
472
|
+
labels:
|
|
473
|
+
com.workframe.project: "${labelProject}"
|
|
474
|
+
com.workframe.role: ui
|
|
475
|
+
ports:
|
|
476
|
+
- "127.0.0.1:\${WORKFRAME_UI_PORT}:80"
|
|
477
|
+
volumes:
|
|
478
|
+
- \${WORKFRAME_UI_STATIC_DIR:-./workframe-ui/public}:/usr/share/nginx/html:ro
|
|
479
|
+
- ./workframe-ui/docker/nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
|
480
|
+
environment:
|
|
481
|
+
- WORKFRAME_GATEWAY_URL=http://gateway:8642
|
|
482
|
+
- WORKFRAME_PROJECT=${labelProject}
|
|
483
|
+
depends_on:
|
|
484
|
+
- gateway
|
|
485
|
+
- dashboard
|
|
486
|
+
- workframe-api
|
|
487
|
+
networks:
|
|
488
|
+
- ${docker.network}
|
|
489
|
+
|
|
490
|
+
networks:
|
|
491
|
+
${docker.network}:
|
|
492
|
+
driver: bridge
|
|
493
|
+
${docker.controlNetwork}:
|
|
494
|
+
driver: bridge
|
|
495
|
+
internal: true
|
|
496
|
+
|
|
497
|
+
volumes:
|
|
498
|
+
workframe-proxy-token:
|
|
499
|
+
`;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function dockerComposeHostBindingsYaml(docker) {
|
|
503
|
+
return `# ponytail: supervisor stack.apply via docker.sock — absolute host binds (Windows/macOS).
|
|
504
|
+
# Usage: docker compose -f docker-compose.yml -f docker-compose.host-bindings.yml up -d --force-recreate gateway
|
|
505
|
+
name: ${docker.stack}
|
|
506
|
+
|
|
507
|
+
services:
|
|
508
|
+
gateway:
|
|
509
|
+
volumes:
|
|
510
|
+
- \${WORKFRAME_HOST_PROJECT_ROOT}/Agents:/opt/data
|
|
511
|
+
- \${WORKFRAME_HOST_PROJECT_ROOT}/Files:${WORKSPACE_DATA_MOUNT}
|
|
512
|
+
- workframe-proxy-token:/run/workframe-proxy:ro
|
|
513
|
+
- \${WORKFRAME_HOST_PROJECT_ROOT}/scripts:/opt/install/scripts:ro
|
|
514
|
+
- \${WORKFRAME_HOST_COMPOSE_DIR}/docker/cont-init-workspace-link.sh:/etc/cont-init.d/03-workspace-link:ro
|
|
515
|
+
|
|
516
|
+
dashboard:
|
|
517
|
+
volumes:
|
|
518
|
+
- \${WORKFRAME_HOST_COMPOSE_DIR}/docker/dashboard-proxy.conf:/etc/nginx/conf.d/default.conf:ro
|
|
519
|
+
|
|
520
|
+
workframe-api:
|
|
521
|
+
build:
|
|
522
|
+
context: \${WORKFRAME_HOST_PROJECT_ROOT}/workframe-api
|
|
523
|
+
volumes:
|
|
524
|
+
- \${WORKFRAME_HOST_PROJECT_ROOT}/workframe-api/public:/app/public:ro
|
|
525
|
+
- \${WORKFRAME_HOST_PROJECT_ROOT}/workframe-api:/app:ro
|
|
526
|
+
- \${WORKFRAME_HOST_PROJECT_ROOT}/workframe-api/data:/app/data
|
|
527
|
+
- \${WORKFRAME_HOST_PROJECT_ROOT}/Agents:/opt/data
|
|
528
|
+
- \${WORKFRAME_HOST_PROJECT_ROOT}/Files:${WORKSPACE_DATA_MOUNT}
|
|
529
|
+
- \${WORKFRAME_HOST_PROJECT_ROOT}/scripts:/opt/install/scripts:ro
|
|
530
|
+
- workframe-proxy-token:/run/workframe-proxy
|
|
531
|
+
|
|
532
|
+
workframe-supervisor:
|
|
533
|
+
build:
|
|
534
|
+
context: \${WORKFRAME_HOST_PROJECT_ROOT}/workframe-supervisor
|
|
535
|
+
volumes:
|
|
536
|
+
- \${WORKFRAME_HOST_PROJECT_ROOT}/Agents:/opt/data
|
|
537
|
+
|
|
538
|
+
workframe:
|
|
539
|
+
volumes:
|
|
540
|
+
- \${WORKFRAME_HOST_PROJECT_ROOT}/workframe-ui/public:/usr/share/nginx/html:ro
|
|
541
|
+
- \${WORKFRAME_HOST_COMPOSE_DIR}/workframe-ui/docker/nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
|
542
|
+
`;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function setupSh(docker, nativeProfile, nativeAgentName) {
|
|
546
|
+
return `#!/usr/bin/env bash
|
|
547
|
+
set -euo pipefail
|
|
548
|
+
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
549
|
+
cd "$ROOT"
|
|
550
|
+
|
|
551
|
+
mkdir -p Agents Files
|
|
552
|
+
|
|
553
|
+
echo "Pulling Hermes image..."
|
|
554
|
+
docker pull ${docker.image}
|
|
555
|
+
|
|
556
|
+
cat <<'EOF'
|
|
557
|
+
|
|
558
|
+
Workframe Phase B — use ONE of these:
|
|
559
|
+
|
|
560
|
+
FULL (recommended — credentials → native agent → Workframe UI):
|
|
561
|
+
./scripts/start-install.sh
|
|
562
|
+
|
|
563
|
+
Open full installer in new terminal (TTY-friendly):
|
|
564
|
+
./scripts/launch-install.sh
|
|
565
|
+
|
|
566
|
+
Already ran Hermes setup? Finish boot:
|
|
567
|
+
./scripts/install.sh
|
|
568
|
+
|
|
569
|
+
Credentials / dashboard only:
|
|
570
|
+
./scripts/open-setup.sh
|
|
571
|
+
|
|
572
|
+
Phase C — chat with ${nativeAgentName}:
|
|
573
|
+
./scripts/chat.sh
|
|
574
|
+
|
|
575
|
+
WARNING: docker run ... hermes-agent:latest WITHOUT -p ${nativeProfile}
|
|
576
|
+
starts generic default Hermes (OWL) — not ${nativeAgentName}.
|
|
577
|
+
EOF
|
|
578
|
+
`;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function setupPs1(docker, nativeProfile, nativeAgentName) {
|
|
582
|
+
return `$ErrorActionPreference = 'Stop'
|
|
583
|
+
$Root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
|
|
584
|
+
Set-Location $Root
|
|
585
|
+
|
|
586
|
+
New-Item -ItemType Directory -Force -Path Agents, Files | Out-Null
|
|
587
|
+
|
|
588
|
+
Write-Host 'Pulling Hermes image...'
|
|
589
|
+
docker pull ${docker.image}
|
|
590
|
+
|
|
591
|
+
Write-Host @'
|
|
592
|
+
|
|
593
|
+
Workframe Phase B — use ONE of these:
|
|
594
|
+
|
|
595
|
+
FULL (recommended — credentials → native agent → Workframe UI):
|
|
596
|
+
.\Workframe\scripts\start-install.ps1
|
|
597
|
+
|
|
598
|
+
Open full installer in new window (TTY-friendly):
|
|
599
|
+
.\Workframe\scripts\launch-install.ps1
|
|
600
|
+
|
|
601
|
+
Already ran Hermes setup? Finish boot:
|
|
602
|
+
.\Workframe\scripts\install.ps1
|
|
603
|
+
|
|
604
|
+
Credentials / dashboard only:
|
|
605
|
+
.\Workframe\scripts\open-setup.ps1
|
|
606
|
+
|
|
607
|
+
Phase C — chat with ${nativeAgentName}:
|
|
608
|
+
.\Workframe\scripts\chat.ps1
|
|
609
|
+
|
|
610
|
+
WARNING: docker run ... hermes-agent:latest WITHOUT -p ${nativeProfile}
|
|
611
|
+
starts generic default Hermes (OWL) — not ${nativeAgentName}.
|
|
612
|
+
'@
|
|
613
|
+
`;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function chatSh(nativeProfile, docker) {
|
|
617
|
+
return `#!/usr/bin/env bash
|
|
618
|
+
set -euo pipefail
|
|
619
|
+
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
620
|
+
cd "$ROOT"
|
|
621
|
+
"$ROOT/scripts/verify-bootstrap.sh"
|
|
622
|
+
if docker ps -aq -f "name=^${docker.chat}$" | grep -q .; then
|
|
623
|
+
docker rm -f "${docker.chat}"
|
|
624
|
+
fi
|
|
625
|
+
exec docker run --rm -it --name "${docker.chat}" --entrypoint hermes \\
|
|
626
|
+
-v "$ROOT/Agents:/opt/data" \\
|
|
627
|
+
-v "$ROOT/Files:/opt/data/workspace" \\
|
|
628
|
+
${docker.image} -p ${nativeProfile} chat "$@"
|
|
629
|
+
`;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function chatPs1(nativeProfile, docker) {
|
|
633
|
+
return `$ErrorActionPreference = 'Stop'
|
|
634
|
+
$Root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
|
|
635
|
+
Set-Location $Root
|
|
636
|
+
& "$Root\\scripts\\verify-bootstrap.ps1"
|
|
637
|
+
if (docker ps -aq -f "name=^${docker.chat}$") {
|
|
638
|
+
docker rm -f "${docker.chat}"
|
|
639
|
+
}
|
|
640
|
+
docker run --rm -it --name "${docker.chat}" --entrypoint hermes \`
|
|
641
|
+
-v "$Root\\Agents:/opt/data" \`
|
|
642
|
+
-v "$Root\\Files:/opt/data/workspace" \`
|
|
643
|
+
${docker.image} -p ${nativeProfile} chat @args
|
|
644
|
+
`;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function verifyBootstrapSh(nativeProfile) {
|
|
648
|
+
return `#!/usr/bin/env bash
|
|
649
|
+
set -euo pipefail
|
|
650
|
+
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
651
|
+
if [ ! -f "$ROOT/Agents/profiles/${nativeProfile}/SOUL.md" ]; then
|
|
652
|
+
echo "ERROR: Workframe not bootstrapped." >&2
|
|
653
|
+
echo "Run: ./scripts/bootstrap-native.sh" >&2
|
|
654
|
+
echo "Full pack fallback: ./scripts/bootstrap-profiles.sh" >&2
|
|
655
|
+
echo "Bare Hermes default profile is NOT a Workframe install." >&2
|
|
656
|
+
exit 1
|
|
657
|
+
fi
|
|
658
|
+
if [ ! -f "$ROOT/Agents/SOUL.md" ] || ! grep -q 'Workframe concierge' "$ROOT/Agents/SOUL.md"; then
|
|
659
|
+
echo "ERROR: Agents/SOUL.md missing Workframe native identity." >&2
|
|
660
|
+
echo "Re-run: ./scripts/bootstrap-native.sh" >&2
|
|
661
|
+
exit 1
|
|
662
|
+
fi
|
|
663
|
+
_cfg="$ROOT/Agents/profiles/${nativeProfile}/config.yaml"
|
|
664
|
+
if [ -f "$_cfg" ] && grep -q '^ cwd: \\.' "$_cfg"; then
|
|
665
|
+
echo "ERROR: profile terminal.cwd must be /workspace (Files/), not ." >&2
|
|
666
|
+
echo "Re-run: ./scripts/bootstrap-native.sh" >&2
|
|
667
|
+
exit 1
|
|
668
|
+
fi
|
|
669
|
+
`;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
function verifyBootstrapPs1(nativeProfile) {
|
|
673
|
+
return `$ErrorActionPreference = 'Stop'
|
|
674
|
+
$Root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
|
|
675
|
+
$soul = Join-Path $Root 'Agents\\profiles\\${nativeProfile}\\SOUL.md'
|
|
676
|
+
if (-not (Test-Path $soul)) {
|
|
677
|
+
throw @"
|
|
678
|
+
Workframe not bootstrapped.
|
|
679
|
+
Run: .\\scripts\\bootstrap-native.ps1
|
|
680
|
+
Full pack fallback: .\\scripts\\bootstrap-profiles.ps1
|
|
681
|
+
Bare Hermes default profile is NOT a Workframe install.
|
|
682
|
+
"@
|
|
683
|
+
}
|
|
684
|
+
$homeSoul = Join-Path $Root 'Agents\\SOUL.md'
|
|
685
|
+
if (-not (Test-Path $homeSoul) -or -not (Select-String -Path $homeSoul -Pattern 'Workframe concierge' -Quiet)) {
|
|
686
|
+
throw @"
|
|
687
|
+
Agents/SOUL.md missing Workframe native identity.
|
|
688
|
+
Re-run: .\\scripts\\bootstrap-native.ps1
|
|
689
|
+
"@
|
|
690
|
+
}
|
|
691
|
+
$cfg = Join-Path $Root "Agents\\profiles\\${nativeProfile}\\config.yaml"
|
|
692
|
+
if ((Test-Path $cfg) -and (Select-String -Path $cfg -Pattern '^ cwd: \\.$' -Quiet)) {
|
|
693
|
+
throw @"
|
|
694
|
+
Profile terminal.cwd must be /workspace (Files/), not .
|
|
695
|
+
Re-run: .\\scripts\\bootstrap-native.ps1
|
|
696
|
+
"@
|
|
697
|
+
}
|
|
698
|
+
`;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
function profileCreateBlockSh(p, projectName, docker, containerSuffix) {
|
|
702
|
+
const desc = profileDescription(p, projectName);
|
|
703
|
+
const suffix = containerSuffix || p;
|
|
704
|
+
const createName = `${docker.stack}-bootstrap-${suffix}`;
|
|
705
|
+
const showName = `${createName}-show`;
|
|
706
|
+
const retryName = `${createName}-retry`;
|
|
707
|
+
const cleanName = `${createName}-clean`;
|
|
708
|
+
return `mkdir -p "$ROOT/Agents/profiles"
|
|
709
|
+
echo "Creating profile: ${p}"
|
|
710
|
+
profile_config="$ROOT/Agents/profiles/${p}/config.yaml"
|
|
711
|
+
clean_profile_orphan_${suffix}() {
|
|
712
|
+
docker run --rm --name "${cleanName}" --entrypoint hermes \\
|
|
713
|
+
-v "$ROOT/Agents:/opt/data" \\
|
|
714
|
+
-v "$ROOT/Files:/opt/data/workspace" \\
|
|
715
|
+
${docker.image} profile delete -y ${p} >/dev/null 2>&1 || true
|
|
716
|
+
rm -rf "$ROOT/Agents/profiles/${p}"
|
|
717
|
+
}
|
|
718
|
+
if docker run --rm --name "${showName}-precheck" --entrypoint hermes \\
|
|
719
|
+
-v "$ROOT/Agents:/opt/data" \\
|
|
720
|
+
-v "$ROOT/Files:/opt/data/workspace" \\
|
|
721
|
+
${docker.image} profile show ${p} >/dev/null 2>&1 && [ -f "$profile_config" ]; then
|
|
722
|
+
echo "Profile ${p} already exists, continuing."
|
|
723
|
+
else
|
|
724
|
+
clean_profile_orphan_${suffix}
|
|
725
|
+
set +e
|
|
726
|
+
create_out=$(docker run --rm --name "${createName}" --entrypoint hermes \\
|
|
727
|
+
-v "$ROOT/Agents:/opt/data" \\
|
|
728
|
+
-v "$ROOT/Files:/opt/data/workspace" \\
|
|
729
|
+
${docker.image} profile create ${p} --clone --description "${desc.replace(/"/g, '\\"')}" 2>&1)
|
|
730
|
+
create_code=$?
|
|
731
|
+
set -e
|
|
732
|
+
if [ "$create_code" -ne 0 ]; then
|
|
733
|
+
if docker run --rm --name "${showName}" --entrypoint hermes \\
|
|
734
|
+
-v "$ROOT/Agents:/opt/data" \\
|
|
735
|
+
-v "$ROOT/Files:/opt/data/workspace" \\
|
|
736
|
+
${docker.image} profile show ${p} >/dev/null 2>&1 && [ -f "$profile_config" ]; then
|
|
737
|
+
echo "Profile ${p} already exists, continuing."
|
|
738
|
+
else
|
|
739
|
+
echo "Retrying profile create after cleaning incomplete registration: ${p}"
|
|
740
|
+
clean_profile_orphan_${suffix}
|
|
741
|
+
docker run --rm --name "${retryName}" --entrypoint hermes \\
|
|
742
|
+
-v "$ROOT/Agents:/opt/data" \\
|
|
743
|
+
-v "$ROOT/Files:/opt/data/workspace" \\
|
|
744
|
+
${docker.image} profile create ${p} --clone --description "${desc.replace(/"/g, '\\"')}" || {
|
|
745
|
+
echo "$create_out" >&2
|
|
746
|
+
echo "ERROR: failed to create profile ${p}" >&2
|
|
747
|
+
exit 1
|
|
748
|
+
}
|
|
749
|
+
fi
|
|
750
|
+
fi
|
|
751
|
+
fi
|
|
752
|
+
`;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
function profileCreateBlockPs1(p, projectName, docker, containerSuffix) {
|
|
756
|
+
const desc = profileDescription(p, projectName).replace(/'/g, "''");
|
|
757
|
+
const suffix = containerSuffix || p;
|
|
758
|
+
const createName = `${docker.stack}-bootstrap-${suffix}`;
|
|
759
|
+
const showName = `${createName}-show`;
|
|
760
|
+
const retryName = `${createName}-retry`;
|
|
761
|
+
const cleanName = `${createName}-clean`;
|
|
762
|
+
return `Write-Host "Creating profile: ${p}"
|
|
763
|
+
$profilesRoot = Join-Path $Root "Agents\\profiles"
|
|
764
|
+
$profileDir = Join-Path $profilesRoot "${p}"
|
|
765
|
+
$profileConfig = Join-Path $profileDir "config.yaml"
|
|
766
|
+
New-Item -ItemType Directory -Force -Path $profilesRoot | Out-Null
|
|
767
|
+
|
|
768
|
+
docker run --rm --name "${showName}-precheck" --entrypoint hermes \`
|
|
769
|
+
-v "$Root\\Agents:/opt/data" \`
|
|
770
|
+
-v "$Root\\Files:/opt/data/workspace" \`
|
|
771
|
+
${docker.image} profile show ${p} 2>$null | Out-Null
|
|
772
|
+
$profileReady = ($LASTEXITCODE -eq 0) -and (Test-Path $profileConfig)
|
|
773
|
+
if ($profileReady) {
|
|
774
|
+
Write-Host "Profile ${p} already exists, continuing."
|
|
775
|
+
} else {
|
|
776
|
+
docker run --rm --name "${cleanName}" --entrypoint hermes \`
|
|
777
|
+
-v "$Root\\Agents:/opt/data" \`
|
|
778
|
+
-v "$Root\\Files:/opt/data/workspace" \`
|
|
779
|
+
${docker.image} profile delete -y ${p} 2>$null | Out-Null
|
|
780
|
+
if (Test-Path $profileDir) {
|
|
781
|
+
Remove-Item -Recurse -Force $profileDir
|
|
782
|
+
}
|
|
783
|
+
$createOut = docker run --rm --name "${createName}" --entrypoint hermes \`
|
|
784
|
+
-v "$Root\\Agents:/opt/data" \`
|
|
785
|
+
-v "$Root\\Files:/opt/data/workspace" \`
|
|
786
|
+
${docker.image} profile create ${p} --clone --description "${desc.replace(/"/g, '`"')}" 2>&1
|
|
787
|
+
if ($LASTEXITCODE -ne 0) {
|
|
788
|
+
docker run --rm --name "${showName}" --entrypoint hermes \`
|
|
789
|
+
-v "$Root\\Agents:/opt/data" \`
|
|
790
|
+
-v "$Root\\Files:/opt/data/workspace" \`
|
|
791
|
+
${docker.image} profile show ${p} 2>$null | Out-Null
|
|
792
|
+
$profileReady = ($LASTEXITCODE -eq 0) -and (Test-Path $profileConfig)
|
|
793
|
+
if ($profileReady) {
|
|
794
|
+
Write-Host "Profile ${p} already exists, continuing."
|
|
795
|
+
} else {
|
|
796
|
+
Write-Host "Retrying profile create after cleaning incomplete registration: ${p}"
|
|
797
|
+
docker run --rm --name "${cleanName}-retry" --entrypoint hermes \`
|
|
798
|
+
-v "$Root\\Agents:/opt/data" \`
|
|
799
|
+
-v "$Root\\Files:/opt/data/workspace" \`
|
|
800
|
+
${docker.image} profile delete -y ${p} 2>$null | Out-Null
|
|
801
|
+
if (Test-Path $profileDir) {
|
|
802
|
+
Remove-Item -Recurse -Force $profileDir
|
|
803
|
+
}
|
|
804
|
+
$createOut = docker run --rm --name "${retryName}" --entrypoint hermes \`
|
|
805
|
+
-v "$Root\\Agents:/opt/data" \`
|
|
806
|
+
-v "$Root\\Files:/opt/data/workspace" \`
|
|
807
|
+
${docker.image} profile create ${p} --clone --description "${desc.replace(/"/g, '`"')}" 2>&1
|
|
808
|
+
if ($LASTEXITCODE -ne 0) {
|
|
809
|
+
throw "Failed to create profile ${p}: $createOut"
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
`;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
function copySeedArtifactsSh(profile, nativeProfile, { includeSetup = false } = {}) {
|
|
818
|
+
let out = `if [ -f "$ROOT/scripts/seed/profiles/${profile}/SOUL.md" ]; then
|
|
819
|
+
mkdir -p "$ROOT/Agents/profiles/${profile}"
|
|
820
|
+
cp "$ROOT/scripts/seed/profiles/${profile}/SOUL.md" "$ROOT/Agents/profiles/${profile}/SOUL.md"
|
|
821
|
+
fi
|
|
822
|
+
if [ -f "$ROOT/scripts/seed/profiles/${profile}/AGENTS.md" ]; then
|
|
823
|
+
mkdir -p "$ROOT/Agents/profiles/${profile}"
|
|
824
|
+
cp "$ROOT/scripts/seed/profiles/${profile}/AGENTS.md" "$ROOT/Agents/profiles/${profile}/AGENTS.md"
|
|
825
|
+
fi
|
|
826
|
+
`;
|
|
827
|
+
if (includeSetup && profile === nativeProfile) {
|
|
828
|
+
out += `# Hermes identity reads HERMES_HOME/SOUL.md (= Agents/SOUL.md), not profile subdir alone
|
|
829
|
+
if [ -f "$ROOT/scripts/seed/profiles/${profile}/SOUL.md" ]; then
|
|
830
|
+
cp "$ROOT/scripts/seed/profiles/${profile}/SOUL.md" "$ROOT/Agents/SOUL.md"
|
|
831
|
+
fi
|
|
832
|
+
`;
|
|
833
|
+
out += `if [ -f "$ROOT/scripts/seed/profiles/${profile}/SETUP.md" ]; then
|
|
834
|
+
cp "$ROOT/scripts/seed/profiles/${profile}/SETUP.md" "$ROOT/Agents/profiles/${profile}/SETUP.md"
|
|
835
|
+
fi
|
|
836
|
+
`;
|
|
837
|
+
out += `if [ -d "$ROOT/scripts/seed/profiles/${profile}/skills" ]; then
|
|
838
|
+
mkdir -p "$ROOT/Agents/profiles/${profile}/skills"
|
|
839
|
+
cp -R "$ROOT/scripts/seed/profiles/${profile}/skills/." "$ROOT/Agents/profiles/${profile}/skills/"
|
|
840
|
+
fi
|
|
841
|
+
`;
|
|
842
|
+
}
|
|
843
|
+
return out;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
function patchTerminalCwdSh(profileSlug) {
|
|
847
|
+
return `# Project workspace is /workspace (Files/) — terminal cwd + AGENTS.md auto-load
|
|
848
|
+
_cfg="$ROOT/Agents/profiles/${profileSlug}/config.yaml"
|
|
849
|
+
if [ -f "$_cfg" ] && grep -q '^ cwd: ' "$_cfg"; then
|
|
850
|
+
if sed --version >/dev/null 2>&1; then
|
|
851
|
+
sed -i.bak 's|^ cwd: .*| cwd: /workspace|' "$_cfg" && rm -f "$_cfg.bak"
|
|
852
|
+
else
|
|
853
|
+
sed -i '' 's|^ cwd: .*| cwd: /workspace|' "$_cfg"
|
|
854
|
+
fi
|
|
855
|
+
fi
|
|
856
|
+
`;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
function patchTerminalCwdPs1(profileSlug) {
|
|
860
|
+
return `$cfg = Join-Path $Root "Agents\\profiles\\${profileSlug}\\config.yaml"
|
|
861
|
+
if (Test-Path $cfg) {
|
|
862
|
+
$content = Get-Content $cfg -Raw
|
|
863
|
+
$content = $content -replace '(?m)^ cwd: .*', ' cwd: /workspace'
|
|
864
|
+
Set-Content -Path $cfg -Value $content -NoNewline
|
|
865
|
+
}
|
|
866
|
+
`;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
function copySeedArtifactsPs1(profile, nativeProfile, { includeSetup = false } = {}) {
|
|
870
|
+
let out = `$seed = Join-Path $Root "scripts\\seed\\profiles\\${profile}\\SOUL.md"
|
|
871
|
+
$destDir = Join-Path $Root "Agents\\profiles\\${profile}"
|
|
872
|
+
if (Test-Path $seed) {
|
|
873
|
+
New-Item -ItemType Directory -Force -Path $destDir | Out-Null
|
|
874
|
+
Copy-Item $seed (Join-Path $destDir "SOUL.md") -Force
|
|
875
|
+
}
|
|
876
|
+
$agentsSeed = Join-Path $Root "scripts\\seed\\profiles\\${profile}\\AGENTS.md"
|
|
877
|
+
if (Test-Path $agentsSeed) {
|
|
878
|
+
New-Item -ItemType Directory -Force -Path $destDir | Out-Null
|
|
879
|
+
Copy-Item $agentsSeed (Join-Path $destDir "AGENTS.md") -Force
|
|
880
|
+
}
|
|
881
|
+
`;
|
|
882
|
+
if (includeSetup && profile === nativeProfile) {
|
|
883
|
+
out += `# Hermes identity reads HERMES_HOME/SOUL.md (= Agents/SOUL.md), not profile subdir alone
|
|
884
|
+
$homeSoulDest = Join-Path $Root "Agents\\SOUL.md"
|
|
885
|
+
if (Test-Path $seed) {
|
|
886
|
+
Copy-Item $seed $homeSoulDest -Force
|
|
887
|
+
}
|
|
888
|
+
`;
|
|
889
|
+
out += `$setupSeed = Join-Path $Root "scripts\\seed\\profiles\\${profile}\\SETUP.md"
|
|
890
|
+
if (Test-Path $setupSeed) {
|
|
891
|
+
Copy-Item $setupSeed (Join-Path $destDir "SETUP.md") -Force
|
|
892
|
+
}
|
|
893
|
+
`;
|
|
894
|
+
out += `$skillsSeed = Join-Path $Root "scripts\\seed\\profiles\\${profile}\\skills"
|
|
895
|
+
if (Test-Path $skillsSeed) {
|
|
896
|
+
$skillsDest = Join-Path $destDir "skills"
|
|
897
|
+
New-Item -ItemType Directory -Force -Path $skillsDest | Out-Null
|
|
898
|
+
Copy-Item -Path (Join-Path $skillsSeed '*') -Destination $skillsDest -Recurse -Force
|
|
899
|
+
}
|
|
900
|
+
`;
|
|
901
|
+
}
|
|
902
|
+
return out;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
function profileUseBlockSh(nativeProfile, docker) {
|
|
906
|
+
return `echo "Setting default profile to ${nativeProfile}..."
|
|
907
|
+
docker run --rm --name "${docker.bootstrapUse}" --entrypoint hermes \\
|
|
908
|
+
-v "$ROOT/Agents:/opt/data" \\
|
|
909
|
+
-v "$ROOT/Files:/opt/data/workspace" \\
|
|
910
|
+
${docker.image} profile use ${nativeProfile}
|
|
911
|
+
`;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
function profileUseBlockPs1(nativeProfile, docker) {
|
|
915
|
+
return `Write-Host "Setting default profile to ${nativeProfile}..."
|
|
916
|
+
docker run --rm --name "${docker.bootstrapUse}" --entrypoint hermes \`
|
|
917
|
+
-v "$Root\\Agents:/opt/data" \`
|
|
918
|
+
-v "$Root\\Files:/opt/data/workspace" \`
|
|
919
|
+
${docker.image} profile use ${nativeProfile}
|
|
920
|
+
`;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
function configureProfileApiSh(nativeProfile, docker) {
|
|
924
|
+
const stack = docker.stack;
|
|
925
|
+
return `# Ensure api_server platform is configured for the native profile
|
|
926
|
+
docker run --rm --name "${stack}-bootstrap-api" --entrypoint hermes \\
|
|
927
|
+
-v "$ROOT/Agents:/opt/data" \\
|
|
928
|
+
-v "$ROOT/Files:/opt/data/workspace" \\
|
|
929
|
+
${docker.image} -p ${nativeProfile} config set platforms.api_server.enabled true 2>/dev/null || true
|
|
930
|
+
docker run --rm --name "${stack}-bootstrap-api-host" --entrypoint hermes \\
|
|
931
|
+
-v "$ROOT/Agents:/opt/data" \\
|
|
932
|
+
-v "$ROOT/Files:/opt/data/workspace" \\
|
|
933
|
+
${docker.image} -p ${nativeProfile} config set platforms.api_server.extra.host 0.0.0.0 2>/dev/null || true
|
|
934
|
+
docker run --rm --name "${stack}-bootstrap-api-key" --entrypoint hermes \\
|
|
935
|
+
-v "$ROOT/Agents:/opt/data" \\
|
|
936
|
+
-v "$ROOT/Files:/opt/data/workspace" \\
|
|
937
|
+
${docker.image} -p ${nativeProfile} config set platforms.api_server.extra.key workframe-local-key 2>/dev/null || true
|
|
938
|
+
`;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
function bootstrapNativeSh(nativeProfile, projectName, docker) {
|
|
942
|
+
return `#!/usr/bin/env bash
|
|
943
|
+
set -euo pipefail
|
|
944
|
+
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
945
|
+
cd "$ROOT"
|
|
946
|
+
|
|
947
|
+
mkdir -p Agents Files
|
|
948
|
+
|
|
949
|
+
${seedNativeFromPackageSh(nativeProfile)}
|
|
950
|
+
|
|
951
|
+
if [ ! -d "$ROOT/Agents" ]; then
|
|
952
|
+
echo "Agents/ missing. Run Hermes setup first."
|
|
953
|
+
exit 1
|
|
954
|
+
fi
|
|
955
|
+
|
|
956
|
+
${profileCreateBlockSh(nativeProfile, projectName, docker, 'native')}
|
|
957
|
+
${configureProfileApiSh(nativeProfile, docker)}
|
|
958
|
+
${copySeedArtifactsSh(nativeProfile, nativeProfile, { includeSetup: true })}
|
|
959
|
+
|
|
960
|
+
# Strip UTF-8 BOM from SOUL.md if present (hermes setup writes BOM which silently blocks agent startup)
|
|
961
|
+
for _soul in "$ROOT/Agents/SOUL.md" "$ROOT/Agents/profiles/${nativeProfile}/SOUL.md"; do
|
|
962
|
+
if [ -f "$_soul" ] && head -c 3 "$_soul" | grep -qP '\xef\xbb\xbf'; then
|
|
963
|
+
tail -c +4 "$_soul" > "\${_soul}.nobom" && mv "\${_soul}.nobom" "$_soul"
|
|
964
|
+
echo "Stripped BOM from $_soul"
|
|
965
|
+
fi
|
|
966
|
+
done
|
|
967
|
+
|
|
968
|
+
${patchTerminalCwdSh(nativeProfile)}
|
|
969
|
+
${profileUseBlockSh(nativeProfile, docker)}
|
|
970
|
+
${writeRoutesJsonBlockSh([nativeProfile], nativeProfile, projectName)}
|
|
971
|
+
echo "Native bootstrap complete (${nativeProfile}). Create agents: node scripts/agent-lifecycle.mjs create --slug <name> --spawn"
|
|
972
|
+
docker run --rm --name "${docker.bootstrapList}" --entrypoint hermes \\
|
|
973
|
+
-v "$ROOT/Agents:/opt/data" \\
|
|
974
|
+
-v "$ROOT/Files:/opt/data/workspace" \\
|
|
975
|
+
${docker.image} profile list
|
|
976
|
+
`;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
function configureProfileApiPs1(nativeProfile, docker) {
|
|
980
|
+
const stack = docker.stack;
|
|
981
|
+
return `$cfg = Join-Path $Root "Agents\\profiles\\${nativeProfile}\\config.yaml"
|
|
982
|
+
docker run --rm --name "${stack}-bootstrap-api" --entrypoint hermes \`
|
|
983
|
+
-v "$Root\\Agents:/opt/data" \`
|
|
984
|
+
-v "$Root\\Files:/opt/data/workspace" \`
|
|
985
|
+
${docker.image} -p ${nativeProfile} config set platforms.api_server.enabled true 2>$null
|
|
986
|
+
docker run --rm --name "${stack}-bootstrap-api-host" --entrypoint hermes \`
|
|
987
|
+
-v "$Root\\Agents:/opt/data" \`
|
|
988
|
+
-v "$Root\\Files:/opt/data/workspace" \`
|
|
989
|
+
${docker.image} -p ${nativeProfile} config set platforms.api_server.extra.host 0.0.0.0 2>$null
|
|
990
|
+
docker run --rm --name "${stack}-bootstrap-api-key" --entrypoint hermes \`
|
|
991
|
+
-v "$Root\\Agents:/opt/data" \`
|
|
992
|
+
-v "$Root\\Files:/opt/data/workspace" \`
|
|
993
|
+
${docker.image} -p ${nativeProfile} config set platforms.api_server.extra.key workframe-local-key 2>$null
|
|
994
|
+
`;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
function bootstrapNativePs1(nativeProfile, projectName, docker) {
|
|
998
|
+
return `$ErrorActionPreference = 'Stop'
|
|
999
|
+
$Root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
|
|
1000
|
+
Set-Location $Root
|
|
1001
|
+
|
|
1002
|
+
New-Item -ItemType Directory -Force -Path Agents, Files | Out-Null
|
|
1003
|
+
|
|
1004
|
+
${seedNativeFromPackagePs1(nativeProfile)}
|
|
1005
|
+
|
|
1006
|
+
if (-not (Test-Path "$Root\\Agents")) {
|
|
1007
|
+
throw "Agents/ missing. Run Hermes setup first."
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
${profileCreateBlockPs1(nativeProfile, projectName, docker, 'native')}
|
|
1011
|
+
${configureProfileApiPs1(nativeProfile, docker)}
|
|
1012
|
+
${copySeedArtifactsPs1(nativeProfile, nativeProfile, { includeSetup: true })}
|
|
1013
|
+
|
|
1014
|
+
# Strip UTF-8 BOM from SOUL.md if present
|
|
1015
|
+
foreach ($soul in @("$Root\Agents\SOUL.md", "$Root\Agents\profiles\$nativeProfile\SOUL.md")) {
|
|
1016
|
+
if (Test-Path $soul) {
|
|
1017
|
+
$bytes = [System.IO.File]::ReadAllBytes($soul)
|
|
1018
|
+
if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xef -and $bytes[1] -eq 0xbb -and $bytes[2] -eq 0xbf) {
|
|
1019
|
+
[System.IO.File]::WriteAllBytes($soul, $bytes[3..($bytes.Length-1)])
|
|
1020
|
+
Write-Host "Stripped BOM from $soul"
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
${patchTerminalCwdPs1(nativeProfile)}
|
|
1026
|
+
${profileUseBlockPs1(nativeProfile, docker)}
|
|
1027
|
+
${writeRoutesJsonBlockPs1([nativeProfile], nativeProfile, projectName)}
|
|
1028
|
+
Write-Host "Native bootstrap complete (${nativeProfile}). Create agents: node scripts/agent-lifecycle.mjs create --slug <name> --spawn"
|
|
1029
|
+
docker run --rm --name "${docker.bootstrapList}" --entrypoint hermes \`
|
|
1030
|
+
-v "$Root\\Agents:/opt/data" \`
|
|
1031
|
+
-v "$Root\\Files:/opt/data/workspace" \`
|
|
1032
|
+
${docker.image} profile list
|
|
1033
|
+
`;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
function addProfileSh(nativeProfile, specialists, projectName, docker) {
|
|
1037
|
+
const allowed = specialists.join(' ');
|
|
1038
|
+
const caseArms = specialists.map((s) => ` "${s}") PROFILE="${s}" ;;`).join('\n');
|
|
1039
|
+
return `#!/usr/bin/env bash
|
|
1040
|
+
set -euo pipefail
|
|
1041
|
+
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
1042
|
+
cd "$ROOT"
|
|
1043
|
+
|
|
1044
|
+
INPUT="\${1:-}"
|
|
1045
|
+
if [ -z "$INPUT" ]; then
|
|
1046
|
+
echo "Usage: $0 <profile>" >&2
|
|
1047
|
+
echo "Specialists: ${allowed}" >&2
|
|
1048
|
+
exit 1
|
|
1049
|
+
fi
|
|
1050
|
+
if [ "$INPUT" = "${nativeProfile}" ]; then
|
|
1051
|
+
echo "Use bootstrap-native for ${nativeProfile}." >&2
|
|
1052
|
+
exit 1
|
|
1053
|
+
fi
|
|
1054
|
+
PROFILE=""
|
|
1055
|
+
case "$INPUT" in
|
|
1056
|
+
${caseArms}
|
|
1057
|
+
*)
|
|
1058
|
+
echo "Unknown profile: $INPUT" >&2
|
|
1059
|
+
echo "Available: ${allowed}" >&2
|
|
1060
|
+
exit 1
|
|
1061
|
+
;;
|
|
1062
|
+
esac
|
|
1063
|
+
|
|
1064
|
+
if [ ! -d "$ROOT/Agents" ]; then
|
|
1065
|
+
echo "Agents/ missing. Run Hermes setup first." >&2
|
|
1066
|
+
exit 1
|
|
1067
|
+
fi
|
|
1068
|
+
|
|
1069
|
+
DESC=""
|
|
1070
|
+
case "$PROFILE" in
|
|
1071
|
+
${specialists.map((s) => {
|
|
1072
|
+
const d = profileDescription(s, projectName).replace(/"/g, '\\"');
|
|
1073
|
+
return ` "${s}") DESC="${d}" ;;`;
|
|
1074
|
+
}).join('\n')}
|
|
1075
|
+
esac
|
|
1076
|
+
|
|
1077
|
+
set +e
|
|
1078
|
+
create_out=$(docker run --rm --name "${docker.stack}-add-$PROFILE" --entrypoint hermes \\
|
|
1079
|
+
-v "$ROOT/Agents:/opt/data" \\
|
|
1080
|
+
-v "$ROOT/Files:/opt/data/workspace" \\
|
|
1081
|
+
${docker.image} profile create "$PROFILE" --clone --description "$DESC" 2>&1)
|
|
1082
|
+
create_code=$?
|
|
1083
|
+
set -e
|
|
1084
|
+
if [ "$create_code" -ne 0 ]; then
|
|
1085
|
+
if docker run --rm --name "${docker.stack}-add-$PROFILE-show" --entrypoint hermes \\
|
|
1086
|
+
-v "$ROOT/Agents:/opt/data" \\
|
|
1087
|
+
-v "$ROOT/Files:/opt/data/workspace" \\
|
|
1088
|
+
${docker.image} profile show "$PROFILE" >/dev/null 2>&1; then
|
|
1089
|
+
echo "Profile $PROFILE already exists, continuing."
|
|
1090
|
+
else
|
|
1091
|
+
echo "$create_out" >&2
|
|
1092
|
+
echo "ERROR: failed to create profile $PROFILE" >&2
|
|
1093
|
+
exit 1
|
|
1094
|
+
fi
|
|
1095
|
+
fi
|
|
1096
|
+
|
|
1097
|
+
if [ -f "$ROOT/scripts/seed/profiles/$PROFILE/SOUL.md" ]; then
|
|
1098
|
+
mkdir -p "$ROOT/Agents/profiles/$PROFILE"
|
|
1099
|
+
cp "$ROOT/scripts/seed/profiles/$PROFILE/SOUL.md" "$ROOT/Agents/profiles/$PROFILE/SOUL.md"
|
|
1100
|
+
fi
|
|
1101
|
+
if [ -f "$ROOT/scripts/seed/profiles/$PROFILE/AGENTS.md" ]; then
|
|
1102
|
+
mkdir -p "$ROOT/Agents/profiles/$PROFILE"
|
|
1103
|
+
cp "$ROOT/scripts/seed/profiles/$PROFILE/AGENTS.md" "$ROOT/Agents/profiles/$PROFILE/AGENTS.md"
|
|
1104
|
+
fi
|
|
1105
|
+
${patchTerminalCwdSh('$PROFILE')}
|
|
1106
|
+
|
|
1107
|
+
echo "Profile $PROFILE ready."
|
|
1108
|
+
echo "Register + DM bootstrap: open Workframe → Agents → open $PROFILE (or Create agent) so your u-* runtime and session bind."
|
|
1109
|
+
echo "If services are not running, start the base stack: docker compose up -d"
|
|
1110
|
+
`;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
function addProfilePs1(nativeProfile, specialists, projectName, docker) {
|
|
1114
|
+
const allowed = specialists.map((s) => `'${s}'`).join(', ');
|
|
1115
|
+
const descMap = specialists.map((s) => {
|
|
1116
|
+
const d = profileDescription(s, projectName).replace(/'/g, "''");
|
|
1117
|
+
return ` '${s}' = '${d}'`;
|
|
1118
|
+
}).join('\n');
|
|
1119
|
+
return `param(
|
|
1120
|
+
[Parameter(Mandatory = $true, Position = 0)]
|
|
1121
|
+
[string]$Profile
|
|
1122
|
+
)
|
|
1123
|
+
|
|
1124
|
+
$ErrorActionPreference = 'Stop'
|
|
1125
|
+
$Root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
|
|
1126
|
+
Set-Location $Root
|
|
1127
|
+
|
|
1128
|
+
$Allowed = @(${allowed})
|
|
1129
|
+
if ($Profile -eq '${nativeProfile}') {
|
|
1130
|
+
throw "Use bootstrap-native for ${nativeProfile}."
|
|
1131
|
+
}
|
|
1132
|
+
if ($Allowed -notcontains $Profile) {
|
|
1133
|
+
throw "Unknown profile '$Profile'. Available: $($Allowed -join ', ')"
|
|
1134
|
+
}
|
|
1135
|
+
if (-not (Test-Path "$Root\\Agents")) {
|
|
1136
|
+
throw "Agents/ missing. Run Hermes setup first."
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
$Descriptions = @{
|
|
1140
|
+
${descMap}
|
|
1141
|
+
}
|
|
1142
|
+
$desc = $Descriptions[$Profile]
|
|
1143
|
+
|
|
1144
|
+
$createOut = docker run --rm --name "${docker.stack}-add-$Profile" --entrypoint hermes \`
|
|
1145
|
+
-v "$Root\\Agents:/opt/data" \`
|
|
1146
|
+
-v "$Root\\Files:/opt/data/workspace" \`
|
|
1147
|
+
${docker.image} profile create $Profile --clone --description "$desc" 2>&1
|
|
1148
|
+
if ($LASTEXITCODE -ne 0) {
|
|
1149
|
+
docker run --rm --name "${docker.stack}-add-$Profile-show" --entrypoint hermes \`
|
|
1150
|
+
-v "$Root\\Agents:/opt/data" \`
|
|
1151
|
+
-v "$Root\\Files:/opt/data/workspace" \`
|
|
1152
|
+
${docker.image} profile show $Profile 2>$null | Out-Null
|
|
1153
|
+
if ($LASTEXITCODE -eq 0) {
|
|
1154
|
+
Write-Host "Profile $Profile already exists, continuing."
|
|
1155
|
+
} else {
|
|
1156
|
+
throw "Failed to create profile ${'$'}($Profile): ${'$'}createOut"
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
$seed = Join-Path $Root "scripts\\seed\\profiles\\$Profile\\SOUL.md"
|
|
1161
|
+
$destDir = Join-Path $Root "Agents\\profiles\\$Profile"
|
|
1162
|
+
if (Test-Path $seed) {
|
|
1163
|
+
New-Item -ItemType Directory -Force -Path $destDir | Out-Null
|
|
1164
|
+
Copy-Item $seed (Join-Path $destDir "SOUL.md") -Force
|
|
1165
|
+
}
|
|
1166
|
+
$agentsSeed = Join-Path $Root "scripts\\seed\\profiles\\$Profile\\AGENTS.md"
|
|
1167
|
+
if (Test-Path $agentsSeed) {
|
|
1168
|
+
New-Item -ItemType Directory -Force -Path $destDir | Out-Null
|
|
1169
|
+
Copy-Item $agentsSeed (Join-Path $destDir "AGENTS.md") -Force
|
|
1170
|
+
}
|
|
1171
|
+
${patchTerminalCwdPs1('$Profile')}
|
|
1172
|
+
|
|
1173
|
+
Write-Host "Profile $Profile ready."
|
|
1174
|
+
Write-Host "Register + DM bootstrap: open Workframe → Agents → open $Profile (or Create agent) so your u-* runtime and session bind."
|
|
1175
|
+
Write-Host "If services are not running, start the base stack: docker compose up -d"
|
|
1176
|
+
`;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
function openSetupSh(docker, ports) {
|
|
1180
|
+
return `#!/usr/bin/env bash
|
|
1181
|
+
set -euo pipefail
|
|
1182
|
+
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
1183
|
+
cd "$ROOT"
|
|
1184
|
+
|
|
1185
|
+
DASH_PORT="${ports.dashboard}"
|
|
1186
|
+
if [ -f "$ROOT/.env" ]; then
|
|
1187
|
+
val=$(grep -E '^WORKFRAME_DASHBOARD_PORT=' "$ROOT/.env" | tail -n1 | cut -d= -f2- || true)
|
|
1188
|
+
if [ -n "$val" ]; then DASH_PORT="$val"; fi
|
|
1189
|
+
fi
|
|
1190
|
+
|
|
1191
|
+
echo "Workframe secure setup — credentials never belong in chat."
|
|
1192
|
+
echo ""
|
|
1193
|
+
echo "Full Phase B (recommended): ./scripts/start-install.sh"
|
|
1194
|
+
echo ""
|
|
1195
|
+
echo "Hermes setup (interactive, writes to Agents/):"
|
|
1196
|
+
echo " docker run --rm -it --name ${docker.setup} --entrypoint hermes \\\\"
|
|
1197
|
+
echo " -v \\"\\$PWD/Agents:/opt/data\\" \\\\"
|
|
1198
|
+
echo " -v \\"\\$PWD/Files:/opt/data/workspace\\" \\\\"
|
|
1199
|
+
echo " ${docker.image} setup"
|
|
1200
|
+
echo ""
|
|
1201
|
+
echo "Dashboard (ops UI): http://127.0.0.1:\${DASH_PORT}"
|
|
1202
|
+
if command -v xdg-open >/dev/null 2>&1; then
|
|
1203
|
+
xdg-open "http://127.0.0.1:\${DASH_PORT}" >/dev/null 2>&1 || true
|
|
1204
|
+
elif command -v open >/dev/null 2>&1; then
|
|
1205
|
+
open "http://127.0.0.1:\${DASH_PORT}" >/dev/null 2>&1 || true
|
|
1206
|
+
fi
|
|
1207
|
+
`;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
function openChatPs1(ports) {
|
|
1211
|
+
return `$ErrorActionPreference = 'Stop'
|
|
1212
|
+
$Root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
|
|
1213
|
+
Set-Location $Root
|
|
1214
|
+
|
|
1215
|
+
$DashPort = '${ports.dashboard}'
|
|
1216
|
+
$envFile = Join-Path $Root '.env'
|
|
1217
|
+
if (Test-Path $envFile) {
|
|
1218
|
+
$line = Get-Content $envFile | Where-Object { $_ -match '^WORKFRAME_DASHBOARD_PORT=' } | Select-Object -Last 1
|
|
1219
|
+
if ($line) { $DashPort = ($line -split '=', 2)[1].Trim() }
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
$url = "http://127.0.0.1:$DashPort/chat"
|
|
1223
|
+
Write-Host "Opening Hermes browser chat (embedded TUI): $url"
|
|
1224
|
+
$deadline = (Get-Date).AddSeconds(45)
|
|
1225
|
+
while ((Get-Date) -lt $deadline) {
|
|
1226
|
+
try {
|
|
1227
|
+
$null = Invoke-WebRequest -Uri $url -UseBasicParsing -TimeoutSec 2
|
|
1228
|
+
break
|
|
1229
|
+
} catch {
|
|
1230
|
+
Start-Sleep -Seconds 2
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
try {
|
|
1234
|
+
Start-Process $url | Out-Null
|
|
1235
|
+
} catch {
|
|
1236
|
+
Write-Host "Open manually: $url"
|
|
1237
|
+
}
|
|
1238
|
+
`;
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
function openChatSh(ports) {
|
|
1242
|
+
return `#!/usr/bin/env bash
|
|
1243
|
+
set -euo pipefail
|
|
1244
|
+
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
1245
|
+
cd "$ROOT"
|
|
1246
|
+
|
|
1247
|
+
DASH_PORT="${ports.dashboard}"
|
|
1248
|
+
if [ -f "$ROOT/.env" ]; then
|
|
1249
|
+
val=$(grep -E '^WORKFRAME_DASHBOARD_PORT=' "$ROOT/.env" | tail -n1 | cut -d= -f2- || true)
|
|
1250
|
+
if [ -n "$val" ]; then DASH_PORT="$val"; fi
|
|
1251
|
+
fi
|
|
1252
|
+
|
|
1253
|
+
URL="http://127.0.0.1:\${DASH_PORT}/chat"
|
|
1254
|
+
echo "Opening Hermes browser chat (embedded TUI): $URL"
|
|
1255
|
+
for _ in $(seq 1 22); do
|
|
1256
|
+
if curl -fsS -o /dev/null "$URL" 2>/dev/null; then break; fi
|
|
1257
|
+
sleep 2
|
|
1258
|
+
done
|
|
1259
|
+
if command -v xdg-open >/dev/null 2>&1; then xdg-open "$URL" >/dev/null 2>&1 || true
|
|
1260
|
+
elif command -v open >/dev/null 2>&1; then open "$URL" >/dev/null 2>&1 || true
|
|
1261
|
+
else echo "Open manually: $URL"
|
|
1262
|
+
fi
|
|
1263
|
+
`;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
function openWorkframeApiPs1(ports) {
|
|
1267
|
+
return `$ErrorActionPreference = 'Stop'
|
|
1268
|
+
$Root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
|
|
1269
|
+
Set-Location $Root
|
|
1270
|
+
|
|
1271
|
+
$ApiPort = '${ports.api}'
|
|
1272
|
+
$envFile = Join-Path $Root '.env'
|
|
1273
|
+
if (Test-Path $envFile) {
|
|
1274
|
+
$line = Get-Content $envFile | Where-Object { $_ -match '^WORKFRAME_API_PORT=' } | Select-Object -Last 1
|
|
1275
|
+
if ($line) { $ApiPort = ($line -split '=', 2)[1].Trim() }
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
$url = "http://127.0.0.1:$ApiPort/"
|
|
1279
|
+
Write-Host "Opening Workframe API: $url"
|
|
1280
|
+
$deadline = (Get-Date).AddSeconds(45)
|
|
1281
|
+
while ((Get-Date) -lt $deadline) {
|
|
1282
|
+
try {
|
|
1283
|
+
$null = Invoke-WebRequest -Uri $url -UseBasicParsing -TimeoutSec 2
|
|
1284
|
+
break
|
|
1285
|
+
} catch {
|
|
1286
|
+
Start-Sleep -Seconds 2
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
try {
|
|
1290
|
+
Start-Process $url | Out-Null
|
|
1291
|
+
} catch {
|
|
1292
|
+
Write-Host "Open manually: $url"
|
|
1293
|
+
}
|
|
1294
|
+
`;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
function openWorkframeApiSh(ports) {
|
|
1298
|
+
return `#!/usr/bin/env bash
|
|
1299
|
+
set -euo pipefail
|
|
1300
|
+
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
1301
|
+
cd "$ROOT"
|
|
1302
|
+
|
|
1303
|
+
API_PORT="${ports.api}"
|
|
1304
|
+
if [ -f "$ROOT/.env" ]; then
|
|
1305
|
+
val=$(grep -E '^WORKFRAME_API_PORT=' "$ROOT/.env" | tail -n1 | cut -d= -f2- || true)
|
|
1306
|
+
if [ -n "$val" ]; then API_PORT="$val"; fi
|
|
1307
|
+
fi
|
|
1308
|
+
|
|
1309
|
+
URL="http://127.0.0.1:\${API_PORT}/"
|
|
1310
|
+
echo "Opening Workframe API: $URL"
|
|
1311
|
+
for _ in $(seq 1 22); do
|
|
1312
|
+
if curl -fsS -o /dev/null "$URL" 2>/dev/null; then break; fi
|
|
1313
|
+
sleep 2
|
|
1314
|
+
done
|
|
1315
|
+
if command -v xdg-open >/dev/null 2>&1; then xdg-open "$URL" >/dev/null 2>&1 || true
|
|
1316
|
+
elif command -v open >/dev/null 2>&1; then open "$URL" >/dev/null 2>&1 || true
|
|
1317
|
+
else echo "Open manually: $URL"
|
|
1318
|
+
fi
|
|
1319
|
+
`;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
function updateHermesPs1(_packProfiles) {
|
|
1323
|
+
return `$ErrorActionPreference = 'Stop'
|
|
1324
|
+
$Root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
|
|
1325
|
+
Set-Location $Root
|
|
1326
|
+
|
|
1327
|
+
$services = @(docker compose config --services 2>$null | Where-Object { $_ -match '^(gateway|dashboard)$' })
|
|
1328
|
+
if (-not $services.Count) { $services = @('gateway', 'dashboard') }
|
|
1329
|
+
|
|
1330
|
+
Write-Host 'Pulling latest Hermes image...'
|
|
1331
|
+
docker compose pull @services
|
|
1332
|
+
Write-Host 'Recreating Hermes containers (in-container hermes update is unsupported in Docker)...'
|
|
1333
|
+
docker compose up -d --force-recreate @services
|
|
1334
|
+
Write-Host 'Done. Mission control and workframe unchanged unless you recreate the full stack.'
|
|
1335
|
+
`;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
function updateHermesSh(_packProfiles) {
|
|
1339
|
+
return `#!/usr/bin/env bash
|
|
1340
|
+
set -euo pipefail
|
|
1341
|
+
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
1342
|
+
cd "$ROOT"
|
|
1343
|
+
|
|
1344
|
+
mapfile -t SERVICES < <(docker compose config --services 2>/dev/null | grep -E '^(gateway|dashboard)$' || true)
|
|
1345
|
+
if [ "\${#SERVICES[@]}" -eq 0 ]; then
|
|
1346
|
+
SERVICES=(gateway dashboard)
|
|
1347
|
+
fi
|
|
1348
|
+
|
|
1349
|
+
echo "Pulling latest Hermes image..."
|
|
1350
|
+
docker compose pull "\${SERVICES[@]}"
|
|
1351
|
+
echo "Recreating Hermes containers (in-container hermes update is unsupported in Docker)..."
|
|
1352
|
+
docker compose up -d --force-recreate "\${SERVICES[@]}"
|
|
1353
|
+
echo "Done. Mission control and workframe unchanged unless you recreate the full stack."
|
|
1354
|
+
`;
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
function launchPhaseBInstaller(target) {
|
|
1358
|
+
if (process.platform === 'win32') {
|
|
1359
|
+
const script = path.join(target, 'scripts', 'start-install.ps1');
|
|
1360
|
+
if (!fs.existsSync(script)) return false;
|
|
1361
|
+
const escaped = script.replace(/'/g, "''");
|
|
1362
|
+
const child = spawn(
|
|
1363
|
+
'powershell.exe',
|
|
1364
|
+
[
|
|
1365
|
+
'-NoProfile',
|
|
1366
|
+
'-ExecutionPolicy',
|
|
1367
|
+
'Bypass',
|
|
1368
|
+
'-Command',
|
|
1369
|
+
`Start-Process powershell.exe -WindowStyle Normal -ArgumentList '-NoExit','-NoProfile','-ExecutionPolicy','Bypass','-File','${escaped}'`,
|
|
1370
|
+
],
|
|
1371
|
+
{
|
|
1372
|
+
cwd: target,
|
|
1373
|
+
detached: true,
|
|
1374
|
+
stdio: 'ignore',
|
|
1375
|
+
windowsHide: false,
|
|
1376
|
+
},
|
|
1377
|
+
);
|
|
1378
|
+
child.unref();
|
|
1379
|
+
return true;
|
|
1380
|
+
}
|
|
1381
|
+
const script = path.join(target, 'scripts', 'start-install.sh');
|
|
1382
|
+
if (!fs.existsSync(script)) return false;
|
|
1383
|
+
const child = spawn(script, [], {
|
|
1384
|
+
cwd: target,
|
|
1385
|
+
detached: true,
|
|
1386
|
+
stdio: 'ignore',
|
|
1387
|
+
});
|
|
1388
|
+
child.unref();
|
|
1389
|
+
return true;
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
function openInstallBrowserBlockSh() {
|
|
1393
|
+
return `echo ""
|
|
1394
|
+
echo "Opening Workframe setup wizard (/install)..."
|
|
1395
|
+
"$ROOT/scripts/open-install-ui.sh" || true
|
|
1396
|
+
`;
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
function openInstallBrowserBlockPs1() {
|
|
1400
|
+
return `Write-Host ''
|
|
1401
|
+
Write-Host 'Opening Workframe setup wizard (/install)...' -ForegroundColor Green
|
|
1402
|
+
& "$Root\\scripts\\open-install-ui.ps1"
|
|
1403
|
+
`;
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
function openWorkframeBrowserBlockSh() {
|
|
1407
|
+
return `echo ""
|
|
1408
|
+
echo "Opening Workframe UI..."
|
|
1409
|
+
"$ROOT/scripts/open-workframe-ui.sh" || true
|
|
1410
|
+
`;
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
function openWorkframeBrowserBlockPs1() {
|
|
1414
|
+
return `Write-Host ''
|
|
1415
|
+
Write-Host 'Opening Workframe UI...' -ForegroundColor Green
|
|
1416
|
+
& "$Root\\scripts\\open-workframe-ui.ps1"
|
|
1417
|
+
`;
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
function openSetupPs1(docker, ports) {
|
|
1421
|
+
return `$ErrorActionPreference = 'Stop'
|
|
1422
|
+
$Root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
|
|
1423
|
+
Set-Location $Root
|
|
1424
|
+
|
|
1425
|
+
$DashPort = '${ports.dashboard}'
|
|
1426
|
+
$envFile = Join-Path $Root '.env'
|
|
1427
|
+
if (Test-Path $envFile) {
|
|
1428
|
+
$line = Get-Content $envFile | Where-Object { $_ -match '^WORKFRAME_DASHBOARD_PORT=' } | Select-Object -Last 1
|
|
1429
|
+
if ($line) {
|
|
1430
|
+
$DashPort = ($line -split '=', 2)[1].Trim()
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
Write-Host 'Workframe secure setup - credentials never belong in chat.'
|
|
1435
|
+
Write-Host ''
|
|
1436
|
+
Write-Host 'Full Phase B (recommended): .\\scripts\\start-install.ps1'
|
|
1437
|
+
Write-Host ''
|
|
1438
|
+
Write-Host 'Hermes setup only (interactive, writes to Agents/):'
|
|
1439
|
+
Write-Host " docker run --rm -it --name ${docker.setup} --entrypoint hermes \`"
|
|
1440
|
+
Write-Host ' -v "$PWD\\Agents:/opt/data" \`'
|
|
1441
|
+
Write-Host ' -v "$PWD\\Files:/opt/data/workspace" \`'
|
|
1442
|
+
Write-Host ' ${docker.image} setup'
|
|
1443
|
+
Write-Host ''
|
|
1444
|
+
$url = "http://127.0.0.1:$DashPort"
|
|
1445
|
+
Write-Host "Dashboard (ops UI): $url"
|
|
1446
|
+
try {
|
|
1447
|
+
Start-Process $url | Out-Null
|
|
1448
|
+
} catch {
|
|
1449
|
+
Write-Host 'Open the dashboard URL manually in your browser.'
|
|
1450
|
+
}
|
|
1451
|
+
`;
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
function installPs1(nativeProfile, nativeAgentName, docker, ports) {
|
|
1455
|
+
return `$ErrorActionPreference = 'Stop'
|
|
1456
|
+
$Root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
|
|
1457
|
+
Set-Location $Root
|
|
1458
|
+
|
|
1459
|
+
$config = Join-Path $Root 'Agents\\config.yaml'
|
|
1460
|
+
if (-not (Test-Path $config)) {
|
|
1461
|
+
throw @"
|
|
1462
|
+
Hermes is not initialized in Agents/.
|
|
1463
|
+
Run the full Phase B installer: .\\scripts\\start-install.ps1
|
|
1464
|
+
Or run Hermes setup first, then re-run: .\\scripts\\install.ps1
|
|
1465
|
+
"@
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
Write-Host "Bootstrapping ${nativeAgentName} (${nativeProfile})..."
|
|
1469
|
+
& "$Root\\scripts\\bootstrap-native.ps1"
|
|
1470
|
+
& "$Root\\scripts\\verify-bootstrap.ps1"
|
|
1471
|
+
|
|
1472
|
+
Write-Host 'Starting full stack (gateway, dashboard, workframe-api, Workframe UI)...'
|
|
1473
|
+
${composeUpPs1Block()}
|
|
1474
|
+
|
|
1475
|
+
Write-Host ''
|
|
1476
|
+
Write-Host "Phase B complete - ${nativeAgentName} is ready." -ForegroundColor Green
|
|
1477
|
+
Write-Host ' Browser chat (TUI): http://127.0.0.1:${ports.dashboard}/chat'
|
|
1478
|
+
Write-Host ' Terminal chat: .\\scripts\\chat.ps1'
|
|
1479
|
+
Write-Host ' Workframe UI: http://127.0.0.1:${ports.ui}'
|
|
1480
|
+
Write-Host ' Hermes ops dashboard: http://127.0.0.1:${ports.dashboard}'
|
|
1481
|
+
Write-Host ' Add specialists: .\\scripts\\add-profile.ps1 <slug>'
|
|
1482
|
+
${openWorkframeBrowserBlockPs1()}
|
|
1483
|
+
`;
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
function installSh(nativeProfile, nativeAgentName, docker, ports) {
|
|
1487
|
+
return `#!/usr/bin/env bash
|
|
1488
|
+
set -euo pipefail
|
|
1489
|
+
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
1490
|
+
cd "$ROOT"
|
|
1491
|
+
|
|
1492
|
+
if [ ! -f "$ROOT/Agents/config.yaml" ]; then
|
|
1493
|
+
echo "Hermes is not initialized in Agents/." >&2
|
|
1494
|
+
echo "Run: ./scripts/start-install.sh" >&2
|
|
1495
|
+
exit 1
|
|
1496
|
+
fi
|
|
1497
|
+
|
|
1498
|
+
echo "Bootstrapping ${nativeAgentName} (${nativeProfile})..."
|
|
1499
|
+
"$ROOT/scripts/bootstrap-native.sh"
|
|
1500
|
+
"$ROOT/scripts/verify-bootstrap.sh"
|
|
1501
|
+
|
|
1502
|
+
echo "Starting full stack (gateway, dashboard, workframe-api, Workframe UI)..."
|
|
1503
|
+
${composeUpBashBlock()}
|
|
1504
|
+
|
|
1505
|
+
echo ""
|
|
1506
|
+
echo "Phase B complete — ${nativeAgentName} is ready."
|
|
1507
|
+
echo " Browser chat (TUI): http://127.0.0.1:${ports.dashboard}/chat"
|
|
1508
|
+
echo " Terminal chat: ./scripts/chat.sh"
|
|
1509
|
+
echo " Workframe UI: http://127.0.0.1:${ports.ui}"
|
|
1510
|
+
echo " Hermes ops dashboard: http://127.0.0.1:${ports.dashboard}"
|
|
1511
|
+
echo " Add specialists: ./scripts/add-profile.sh <slug>"
|
|
1512
|
+
${openWorkframeBrowserBlockSh()}
|
|
1513
|
+
`;
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
function startInstallPs1(docker, nativeProfile, nativeAgentName, ports) {
|
|
1517
|
+
return `$ErrorActionPreference = 'Stop'
|
|
1518
|
+
$Root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
|
|
1519
|
+
Set-Location $Root
|
|
1520
|
+
|
|
1521
|
+
New-Item -ItemType Directory -Force -Path Agents, Files | Out-Null
|
|
1522
|
+
|
|
1523
|
+
Write-Host ''
|
|
1524
|
+
Write-Host '========================================' -ForegroundColor Cyan
|
|
1525
|
+
Write-Host ' Workframe install (UI-first)' -ForegroundColor Cyan
|
|
1526
|
+
Write-Host " Project: ${nativeAgentName}" -ForegroundColor Cyan
|
|
1527
|
+
Write-Host '========================================' -ForegroundColor Cyan
|
|
1528
|
+
Write-Host ''
|
|
1529
|
+
|
|
1530
|
+
& "$Root\\scripts\\open-install-ui.ps1"
|
|
1531
|
+
|
|
1532
|
+
Write-Host 'Step 1/2 - Starting Docker stack...' -ForegroundColor Yellow
|
|
1533
|
+
${composeUpPs1Block()}
|
|
1534
|
+
if ($LASTEXITCODE -ne 0) {
|
|
1535
|
+
Write-Host 'docker compose up failed.' -ForegroundColor Red
|
|
1536
|
+
Read-Host 'Press Enter to close'
|
|
1537
|
+
exit $LASTEXITCODE
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
Write-Host ''
|
|
1541
|
+
Write-Host "Step 2/2 - Bootstrap ${nativeAgentName} (preserves existing profiles)..." -ForegroundColor Yellow
|
|
1542
|
+
& "$Root\\scripts\\bootstrap-native.ps1"
|
|
1543
|
+
& "$Root\\scripts\\verify-bootstrap.ps1"
|
|
1544
|
+
|
|
1545
|
+
Write-Host ''
|
|
1546
|
+
Write-Host '========================================' -ForegroundColor Green
|
|
1547
|
+
Write-Host " Install ready - continue in the browser" -ForegroundColor Green
|
|
1548
|
+
Write-Host '========================================' -ForegroundColor Green
|
|
1549
|
+
Write-Host ''
|
|
1550
|
+
Write-Host ' Install UI: http://127.0.0.1:${ports.ui}/install'
|
|
1551
|
+
Write-Host ' Workframe UI: http://127.0.0.1:${ports.ui}'
|
|
1552
|
+
Write-Host ' Hermes dashboard: http://127.0.0.1:${ports.dashboard}'
|
|
1553
|
+
Write-Host ''
|
|
1554
|
+
Write-Host 'LLM keys are configured in the onboarding UI - no Hermes TUI required.' -ForegroundColor DarkGray
|
|
1555
|
+
${openInstallBrowserBlockPs1()}
|
|
1556
|
+
Read-Host 'Press Enter to close'
|
|
1557
|
+
`;
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
function startInstallSh(docker, nativeProfile, nativeAgentName, ports) {
|
|
1561
|
+
return `#!/usr/bin/env bash
|
|
1562
|
+
set -euo pipefail
|
|
1563
|
+
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
1564
|
+
cd "$ROOT"
|
|
1565
|
+
|
|
1566
|
+
mkdir -p Agents Files
|
|
1567
|
+
|
|
1568
|
+
echo ""
|
|
1569
|
+
echo "========================================"
|
|
1570
|
+
echo " Workframe install (UI-first)"
|
|
1571
|
+
echo " Project: ${nativeAgentName}"
|
|
1572
|
+
echo "========================================"
|
|
1573
|
+
echo ""
|
|
1574
|
+
|
|
1575
|
+
"$ROOT/scripts/open-install-ui.sh" || true
|
|
1576
|
+
|
|
1577
|
+
echo "Step 1/2 — Starting Docker stack..."
|
|
1578
|
+
${composeUpBashBlock()}
|
|
1579
|
+
|
|
1580
|
+
echo ""
|
|
1581
|
+
echo "Step 2/2 — Bootstrap ${nativeAgentName} (preserves existing profiles)..."
|
|
1582
|
+
"$ROOT/scripts/bootstrap-native.sh"
|
|
1583
|
+
"$ROOT/scripts/verify-bootstrap.sh"
|
|
1584
|
+
|
|
1585
|
+
echo ""
|
|
1586
|
+
echo "========================================"
|
|
1587
|
+
echo " Install ready - continue in the browser"
|
|
1588
|
+
echo "========================================"
|
|
1589
|
+
echo ""
|
|
1590
|
+
echo " Install UI: http://127.0.0.1:${ports.ui}/install"
|
|
1591
|
+
echo " Workframe UI: http://127.0.0.1:${ports.ui}"
|
|
1592
|
+
echo " Hermes dashboard: http://127.0.0.1:${ports.dashboard}/"
|
|
1593
|
+
echo ""
|
|
1594
|
+
echo "LLM keys are configured in the onboarding UI - no Hermes TUI required."
|
|
1595
|
+
${openInstallBrowserBlockSh()}
|
|
1596
|
+
`;
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
function launchInstallPs1() {
|
|
1600
|
+
return `$ErrorActionPreference = 'Stop'
|
|
1601
|
+
$Root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
|
|
1602
|
+
$Script = Join-Path $Root 'scripts\\start-install.ps1'
|
|
1603
|
+
Start-Process powershell -ArgumentList @('-NoExit', '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', $Script)
|
|
1604
|
+
Write-Host 'Opened installer in a new window.'
|
|
1605
|
+
Write-Host 'Browser opens /install immediately — configure everything in the UI.'
|
|
1606
|
+
`;
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
function launchInstallSh() {
|
|
1610
|
+
return `#!/usr/bin/env bash
|
|
1611
|
+
set -euo pipefail
|
|
1612
|
+
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
1613
|
+
START="$ROOT/scripts/start-install.sh"
|
|
1614
|
+
PAUSE='read -r -p "Press Enter to close..." _'
|
|
1615
|
+
|
|
1616
|
+
launch_in_terminal() {
|
|
1617
|
+
local cmd="cd $(printf '%q' "$ROOT") && exec $(printf '%q' "$START"); echo; $PAUSE"
|
|
1618
|
+
case "$(uname -s)" in
|
|
1619
|
+
Darwin)
|
|
1620
|
+
local root_esc
|
|
1621
|
+
root_esc=$(printf %s "$ROOT" | sed "s/'/'\\\\''/g")
|
|
1622
|
+
osascript <<APPLESCRIPT >/dev/null 2>&1
|
|
1623
|
+
tell application "Terminal"
|
|
1624
|
+
activate
|
|
1625
|
+
do script "cd '\${root_esc}' && exec './scripts/start-install.sh'"
|
|
1626
|
+
end tell
|
|
1627
|
+
APPLESCRIPT
|
|
1628
|
+
return 0
|
|
1629
|
+
;;
|
|
1630
|
+
Linux)
|
|
1631
|
+
if command -v gnome-terminal >/dev/null 2>&1; then
|
|
1632
|
+
gnome-terminal -- bash -lc "$cmd"
|
|
1633
|
+
return 0
|
|
1634
|
+
fi
|
|
1635
|
+
if command -v konsole >/dev/null 2>&1; then
|
|
1636
|
+
konsole -e bash -lc "$cmd"
|
|
1637
|
+
return 0
|
|
1638
|
+
fi
|
|
1639
|
+
if command -v xfce4-terminal >/dev/null 2>&1; then
|
|
1640
|
+
xfce4-terminal -e bash -lc "$cmd"
|
|
1641
|
+
return 0
|
|
1642
|
+
fi
|
|
1643
|
+
if command -v xterm >/dev/null 2>&1; then
|
|
1644
|
+
xterm -e bash -lc "$cmd"
|
|
1645
|
+
return 0
|
|
1646
|
+
fi
|
|
1647
|
+
;;
|
|
1648
|
+
esac
|
|
1649
|
+
return 1
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
if [ -z "\${WORKFRAME_LAUNCH_IN_PLACE:-}" ]; then
|
|
1653
|
+
if launch_in_terminal; then
|
|
1654
|
+
echo "Opened Phase B installer in a new terminal."
|
|
1655
|
+
echo "Complete Hermes setup there — Workframe UI opens automatically when the stack is up."
|
|
1656
|
+
exit 0
|
|
1657
|
+
fi
|
|
1658
|
+
fi
|
|
1659
|
+
|
|
1660
|
+
echo "Running Phase B installer in this terminal..."
|
|
1661
|
+
exec "$START"
|
|
1662
|
+
`;
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
function bootstrapProfilesSh(profiles, projectName, nativeProfile, docker) {
|
|
1666
|
+
const blocks = profiles.map((p) => {
|
|
1667
|
+
const isNative = p === nativeProfile;
|
|
1668
|
+
return `${profileCreateBlockSh(p, projectName, docker, p)}
|
|
1669
|
+
${copySeedArtifactsSh(p, nativeProfile, { includeSetup: isNative })}
|
|
1670
|
+
${patchTerminalCwdSh(p)}
|
|
1671
|
+
`;
|
|
1672
|
+
});
|
|
1673
|
+
return `#!/usr/bin/env bash
|
|
1674
|
+
set -euo pipefail
|
|
1675
|
+
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
1676
|
+
cd "$ROOT"
|
|
1677
|
+
|
|
1678
|
+
if [ ! -d "$ROOT/Agents" ]; then
|
|
1679
|
+
echo "Agents/ missing. Run Hermes setup first."
|
|
1680
|
+
exit 1
|
|
1681
|
+
fi
|
|
1682
|
+
|
|
1683
|
+
${blocks.join('\n')}
|
|
1684
|
+
${profileUseBlockSh(nativeProfile, docker)}
|
|
1685
|
+
${writeRoutesJsonBlockSh(profiles, nativeProfile, projectName)}
|
|
1686
|
+
echo "Full pack bootstrap complete. Start services with: docker compose up -d"
|
|
1687
|
+
docker run --rm --name "${docker.bootstrapList}" --entrypoint hermes \\
|
|
1688
|
+
-v "$ROOT/Agents:/opt/data" \\
|
|
1689
|
+
-v "$ROOT/Files:/opt/data/workspace" \\
|
|
1690
|
+
${docker.image} profile list
|
|
1691
|
+
`;
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
function bootstrapProfilesPs1(profiles, projectName, nativeProfile, docker) {
|
|
1695
|
+
const blocks = profiles.map((p) => {
|
|
1696
|
+
const isNative = p === nativeProfile;
|
|
1697
|
+
return `${profileCreateBlockPs1(p, projectName, docker, p)}
|
|
1698
|
+
${copySeedArtifactsPs1(p, nativeProfile, { includeSetup: isNative })}
|
|
1699
|
+
${patchTerminalCwdPs1(p)}
|
|
1700
|
+
`;
|
|
1701
|
+
});
|
|
1702
|
+
return `$ErrorActionPreference = 'Stop'
|
|
1703
|
+
$Root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
|
|
1704
|
+
Set-Location $Root
|
|
1705
|
+
|
|
1706
|
+
if (-not (Test-Path "$Root\\Agents")) {
|
|
1707
|
+
throw "Agents/ missing. Run Hermes setup first."
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
${blocks.join('\n')}
|
|
1711
|
+
${profileUseBlockPs1(nativeProfile, docker)}
|
|
1712
|
+
${writeRoutesJsonBlockPs1(profiles, nativeProfile, projectName)}
|
|
1713
|
+
Write-Host "Full pack bootstrap complete. Start services with: docker compose up -d"
|
|
1714
|
+
docker run --rm --name "${docker.bootstrapList}" --entrypoint hermes \`
|
|
1715
|
+
-v "$Root\\Agents:/opt/data" \`
|
|
1716
|
+
-v "$Root\\Files:/opt/data/workspace" \`
|
|
1717
|
+
${docker.image} profile list
|
|
1718
|
+
`;
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
const CI_WORKFLOW = `name: workframe-security
|
|
1722
|
+
|
|
1723
|
+
on:
|
|
1724
|
+
push:
|
|
1725
|
+
pull_request:
|
|
1726
|
+
|
|
1727
|
+
jobs:
|
|
1728
|
+
security-and-scaffold:
|
|
1729
|
+
runs-on: ubuntu-latest
|
|
1730
|
+
defaults:
|
|
1731
|
+
run:
|
|
1732
|
+
working-directory: packages/create-workframe
|
|
1733
|
+
steps:
|
|
1734
|
+
- uses: actions/checkout@v4
|
|
1735
|
+
- uses: actions/setup-python@v5
|
|
1736
|
+
with:
|
|
1737
|
+
python-version: '3.12'
|
|
1738
|
+
- uses: actions/setup-node@v4
|
|
1739
|
+
with:
|
|
1740
|
+
node-version: '20'
|
|
1741
|
+
|
|
1742
|
+
- name: Run security audit
|
|
1743
|
+
run: python3 scripts/security_audit.py
|
|
1744
|
+
|
|
1745
|
+
- name: Generate scaffold with Node installer
|
|
1746
|
+
run: node bin/create-workframe.js --name CiNodeDemo --pack core --out /tmp --ci --force
|
|
1747
|
+
|
|
1748
|
+
- name: Verify generated layout
|
|
1749
|
+
run: |
|
|
1750
|
+
test -f /tmp/CiNodeDemo/docker-compose.yml
|
|
1751
|
+
test -f /tmp/CiNodeDemo/Files/AGENTS.md
|
|
1752
|
+
test -f /tmp/CiNodeDemo/SETUP.md
|
|
1753
|
+
test -f /tmp/CiNodeDemo/scripts/bootstrap-profiles.sh
|
|
1754
|
+
test -f /tmp/CiNodeDemo/scripts/bootstrap-profiles.ps1
|
|
1755
|
+
test -f /tmp/CiNodeDemo/scripts/bootstrap-native.sh
|
|
1756
|
+
test -f /tmp/CiNodeDemo/scripts/bootstrap-native.ps1
|
|
1757
|
+
test -f /tmp/CiNodeDemo/scripts/add-profile.ps1
|
|
1758
|
+
test -f /tmp/CiNodeDemo/scripts/open-setup.ps1
|
|
1759
|
+
test -f /tmp/CiNodeDemo/workframe-ui/public/index.html
|
|
1760
|
+
test -f /tmp/CiNodeDemo/workframe-ui/public/workframe-config.json
|
|
1761
|
+
test -f /tmp/CiNodeDemo/workframe-ui/docker/nginx.conf
|
|
1762
|
+
test -f /tmp/CiNodeDemo/scripts/open-workframe-ui.ps1
|
|
1763
|
+
test -f /tmp/CiNodeDemo/scripts/open-workframe-ui.sh
|
|
1764
|
+
test -f /tmp/CiNodeDemo/.github/workflows/workframe-security.yml
|
|
1765
|
+
node -e "
|
|
1766
|
+
const fs=require('fs');
|
|
1767
|
+
for (const dir of ['CiNodeDemo']) {
|
|
1768
|
+
const root='/tmp/'+dir;
|
|
1769
|
+
const m=JSON.parse(fs.readFileSync(root+'/workframe-manifest.json','utf8'));
|
|
1770
|
+
const slug=m.native_agent?.profile_slug;
|
|
1771
|
+
if (!slug) throw new Error(dir+': manifest missing native_agent.profile_slug');
|
|
1772
|
+
if (!fs.existsSync(root+'/scripts/seed/profiles/'+slug+'/SOUL.md')) throw new Error(dir+': missing native SOUL seed');
|
|
1773
|
+
if (!fs.existsSync(root+'/scripts/seed/profiles/'+slug+'/SETUP.md')) throw new Error(dir+': missing native SETUP playbook seed');
|
|
1774
|
+
if (m.bootstrap?.default !== 'native') throw new Error(dir+': bootstrap.default should be native');
|
|
1775
|
+
if (!m.bootstrap?.open_workframe_ui_script_ps1) throw new Error(dir+': manifest missing open_workframe_ui_script_ps1');
|
|
1776
|
+
const cfg=JSON.parse(fs.readFileSync(root+'/workframe-ui/public/workframe-config.json','utf8'));
|
|
1777
|
+
if (cfg.native_profile!==slug) throw new Error(dir+': workframe-config native_profile mismatch');
|
|
1778
|
+
const chat=fs.readFileSync(root+'/scripts/chat.sh','utf8');
|
|
1779
|
+
if (!chat.includes('-p '+slug)) throw new Error(dir+': chat.sh missing native profile');
|
|
1780
|
+
if (!chat.includes('--name '+slug+'-chat')) throw new Error(dir+': chat.sh missing named chat container');
|
|
1781
|
+
const compose=fs.readFileSync(root+'/docker-compose.yml','utf8');
|
|
1782
|
+
if (!compose.includes(slug+'-gateway')) throw new Error(dir+': compose missing named gateway container');
|
|
1783
|
+
if (!compose.includes('WORKFRAME_UI_STATIC_DIR:-./workframe-ui/public') && !compose.includes('./workframe-ui/public:/usr/share/nginx/html')) throw new Error(dir+': compose missing workframe-ui mount');
|
|
1784
|
+
const start=fs.readFileSync(root+'/scripts/start-install.ps1','utf8');
|
|
1785
|
+
if (!start.includes('open-workframe-ui.ps1')) throw new Error(dir+': start-install should open Workframe UI');
|
|
1786
|
+
const setup=fs.readFileSync(root+'/SETUP.md','utf8');
|
|
1787
|
+
if (!setup.includes('bootstrap-native')) throw new Error(dir+': SETUP.md missing native bootstrap');
|
|
1788
|
+
}
|
|
1789
|
+
"
|
|
1790
|
+
`;
|
|
1791
|
+
|
|
1792
|
+
function parseArgs(argv) {
|
|
1793
|
+
const args = {
|
|
1794
|
+
yes: false,
|
|
1795
|
+
ci: false,
|
|
1796
|
+
force: false,
|
|
1797
|
+
telegram: null,
|
|
1798
|
+
discord: null,
|
|
1799
|
+
runDockerPull: false,
|
|
1800
|
+
installGuideOnly: true,
|
|
1801
|
+
slot: null,
|
|
1802
|
+
installId: null,
|
|
1803
|
+
deploy: 'docker',
|
|
1804
|
+
};
|
|
1805
|
+
const positional = [];
|
|
1806
|
+
for (let i = 0; i < argv.length; i++) {
|
|
1807
|
+
const a = argv[i];
|
|
1808
|
+
if (a === '--yes' || a === '-y') args.yes = true;
|
|
1809
|
+
else if (a === '--ci') { args.ci = true; args.yes = true; }
|
|
1810
|
+
else if (a === '--force') args.force = true;
|
|
1811
|
+
else if (a === '--name') args.name = argv[++i];
|
|
1812
|
+
else if (a.startsWith('--name=')) args.name = a.split('=', 2)[1];
|
|
1813
|
+
else if (a === '--pack') args.pack = argv[++i];
|
|
1814
|
+
else if (a.startsWith('--pack=')) args.pack = a.split('=', 2)[1];
|
|
1815
|
+
else if (a === '--out') args.out = argv[++i];
|
|
1816
|
+
else if (a.startsWith('--out=')) args.out = a.split('=', 2)[1];
|
|
1817
|
+
else if (a === '--telegram') args.telegram = true;
|
|
1818
|
+
else if (a === '--no-telegram') args.telegram = false;
|
|
1819
|
+
else if (a === '--discord') args.discord = true;
|
|
1820
|
+
else if (a === '--no-discord') args.discord = false;
|
|
1821
|
+
else if (a === '--run-docker-pull') args.runDockerPull = true;
|
|
1822
|
+
else if (a === '--allow-install-actions') args.installGuideOnly = false;
|
|
1823
|
+
else if (a === '--no-launch') args.noLaunch = true;
|
|
1824
|
+
else if (a === '--slot') args.slot = Number(argv[++i]);
|
|
1825
|
+
else if (a.startsWith('--slot=')) args.slot = Number(a.split('=', 2)[1]);
|
|
1826
|
+
else if (a === '--deploy') args.deploy = argv[++i];
|
|
1827
|
+
else if (a.startsWith('--deploy=')) args.deploy = a.split('=', 2)[1];
|
|
1828
|
+
else if (a === '--help' || a === '-h') args.help = true;
|
|
1829
|
+
else if (a.startsWith('-')) throw new Error(`Unknown option: ${a}`);
|
|
1830
|
+
else positional.push(a);
|
|
1831
|
+
}
|
|
1832
|
+
if (!args.name && positional.length > 0) args.name = positional[0];
|
|
1833
|
+
// Project name on CLI → scaffold with defaults (native-only pack, cwd out) and launch Phase B.
|
|
1834
|
+
if (args.name && !args.help) {
|
|
1835
|
+
args.yes = true;
|
|
1836
|
+
args.force = true;
|
|
1837
|
+
if (!args.ci) {
|
|
1838
|
+
args.installGuideOnly = false;
|
|
1839
|
+
args.runDockerPull = true;
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
return args;
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
function usage() {
|
|
1846
|
+
console.log(`create-workframe
|
|
1847
|
+
|
|
1848
|
+
Usage:
|
|
1849
|
+
npx create-workframe [ProjectName] [--name MyProject] [--pack native] [--out .] [--deploy auto|native|docker]
|
|
1850
|
+
|
|
1851
|
+
Examples:
|
|
1852
|
+
npx create-workframe MyProject --out ./projects
|
|
1853
|
+
npx create-workframe --name MyProject --pack engineering --out D:/Projects
|
|
1854
|
+
|
|
1855
|
+
One command does scaffold + Phase B installer (new terminal). Meta dev: scripts/new-project.ps1 or npm run new-project.
|
|
1856
|
+
|
|
1857
|
+
Flags:
|
|
1858
|
+
--force Overwrite target directory if it exists (validated paths only)
|
|
1859
|
+
--ci Non-interactive strict mode (implies -y)
|
|
1860
|
+
--run-docker-pull Attempt docker pull after scaffold
|
|
1861
|
+
--allow-install-actions Allow installer to execute shell install actions
|
|
1862
|
+
-y, --yes Non-interactive defaults
|
|
1863
|
+
--no-launch Scaffold only; do not open Phase B installer
|
|
1864
|
+
`);
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
function readText(file) { return fs.readFileSync(file, 'utf8'); }
|
|
1868
|
+
function writeText(file, content) {
|
|
1869
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
1870
|
+
fs.writeFileSync(file, content, 'utf8');
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
function copyWorkframeApiTemplate(target) {
|
|
1874
|
+
const src = path.join(PKG_ROOT, 'workframe-api');
|
|
1875
|
+
const dest = path.join(target, 'workframe-api');
|
|
1876
|
+
if (!fs.existsSync(src)) throw new Error(`Missing workframe-api template: ${src}`);
|
|
1877
|
+
fs.cpSync(src, dest, {
|
|
1878
|
+
recursive: true,
|
|
1879
|
+
filter: (from) => {
|
|
1880
|
+
const base = path.basename(from);
|
|
1881
|
+
return base !== '__pycache__' && !base.endsWith('.pyc');
|
|
1882
|
+
},
|
|
1883
|
+
});
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
function copyWorkframeSupervisorTemplate(target) {
|
|
1887
|
+
const src = path.join(PKG_ROOT, 'workframe-supervisor');
|
|
1888
|
+
const dest = path.join(target, 'workframe-supervisor');
|
|
1889
|
+
if (!fs.existsSync(src)) {
|
|
1890
|
+
throw new Error(`Missing workframe-supervisor template: ${src}. Run sync-canonical-to-package.mjs`);
|
|
1891
|
+
}
|
|
1892
|
+
fs.cpSync(src, dest, {
|
|
1893
|
+
recursive: true,
|
|
1894
|
+
filter: (from) => {
|
|
1895
|
+
const base = path.basename(from);
|
|
1896
|
+
return base !== '__pycache__' && !base.endsWith('.pyc');
|
|
1897
|
+
},
|
|
1898
|
+
});
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
function copyWorkframeUiTemplate(target, packProfiles) {
|
|
1902
|
+
const src = path.join(PKG_ROOT, 'workframe-ui');
|
|
1903
|
+
const dest = path.join(target, 'workframe-ui');
|
|
1904
|
+
const publicSrc = path.join(src, 'public');
|
|
1905
|
+
if (!fs.existsSync(publicSrc) || !fs.existsSync(path.join(publicSrc, 'index.html'))) {
|
|
1906
|
+
throw new Error(
|
|
1907
|
+
'Missing bundled Workframe UI. Run: node packages/create-workframe/scripts/bundle-workframe-ui.mjs',
|
|
1908
|
+
);
|
|
1909
|
+
}
|
|
1910
|
+
fs.mkdirSync(path.join(target, 'docker'), { recursive: true });
|
|
1911
|
+
fs.mkdirSync(path.join(dest, 'docker'), { recursive: true });
|
|
1912
|
+
writeText(path.join(dest, 'docker', 'nginx.conf'), nginxConfYaml(packProfiles));
|
|
1913
|
+
writeText(path.join(target, 'docker', 'dashboard-proxy.conf'), dashboardProxyConf());
|
|
1914
|
+
writeText(path.join(target, 'docker', 'cont-init-workspace-link.sh'), contInitWorkspaceLinkSh());
|
|
1915
|
+
fs.cpSync(publicSrc, path.join(dest, 'public'), { recursive: true });
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
function copyTree(src, dst) {
|
|
1919
|
+
if (!fs.existsSync(src)) return;
|
|
1920
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
1921
|
+
const from = path.join(src, entry.name);
|
|
1922
|
+
const to = path.join(dst, entry.name);
|
|
1923
|
+
if (entry.isDirectory()) copyTree(from, to);
|
|
1924
|
+
else if (entry.isFile()) {
|
|
1925
|
+
fs.mkdirSync(path.dirname(to), { recursive: true });
|
|
1926
|
+
fs.copyFileSync(from, to);
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
function slugify(name) {
|
|
1932
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'workframe';
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
function dockerContainerNames(projectName) {
|
|
1936
|
+
const slug = slugify(projectName);
|
|
1937
|
+
return {
|
|
1938
|
+
slug,
|
|
1939
|
+
stack: slug,
|
|
1940
|
+
displayName: projectName,
|
|
1941
|
+
image: 'nousresearch/hermes-agent:latest',
|
|
1942
|
+
network: `${slug}-net`,
|
|
1943
|
+
gateway: `${slug}-gateway`,
|
|
1944
|
+
dashboard: `${slug}-dashboard`,
|
|
1945
|
+
workframeApi: `${slug}-workframe-api`,
|
|
1946
|
+
workframeSupervisor: `${slug}-workframe-supervisor`,
|
|
1947
|
+
workframe: `${slug}-workframe`,
|
|
1948
|
+
chat: `${slug}-chat`,
|
|
1949
|
+
setup: `${slug}-setup`,
|
|
1950
|
+
controlNetwork: `${slug}-control-net`,
|
|
1951
|
+
profileDashboard: (profile) => `${slug}-dashboard-${profile}`,
|
|
1952
|
+
bootstrapProfile: (profile) => `${slug}-bootstrap-${profile}`,
|
|
1953
|
+
bootstrapProfileShow: (profile) => `${slug}-bootstrap-${profile}-show`,
|
|
1954
|
+
bootstrapUse: `${slug}-bootstrap-use`,
|
|
1955
|
+
bootstrapList: `${slug}-bootstrap-list`,
|
|
1956
|
+
};
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
function nativeProfileSlug(projectName) {
|
|
1960
|
+
return `${slugify(projectName)}-agent`;
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
function nativeAgentName(projectName) {
|
|
1964
|
+
return `${projectName} Agent`;
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
function renderContext(projectName, ports = null) {
|
|
1968
|
+
const projectSlug = slugify(projectName);
|
|
1969
|
+
return {
|
|
1970
|
+
projectName,
|
|
1971
|
+
projectSlug,
|
|
1972
|
+
nativeProfileSlug: nativeProfileSlug(projectName),
|
|
1973
|
+
nativeAgentName: nativeAgentName(projectName),
|
|
1974
|
+
dashboardPort: ports?.dashboard != null ? String(ports.dashboard) : '',
|
|
1975
|
+
};
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
function render(text, ctx) {
|
|
1979
|
+
const context = typeof ctx === 'string' ? renderContext(ctx) : ctx;
|
|
1980
|
+
let out = text;
|
|
1981
|
+
for (const [key, value] of Object.entries(context)) {
|
|
1982
|
+
out = out.replaceAll(`{${key}}`, value);
|
|
1983
|
+
}
|
|
1984
|
+
return out;
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
function resolvePackProfiles(baseProfiles, projectName) {
|
|
1988
|
+
const native = nativeProfileSlug(projectName);
|
|
1989
|
+
return [...new Set(baseProfiles.map((p) => (p === PROJECT_AGENT_SLOT ? native : p)))];
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1992
|
+
function profileDescription(profile, projectName) {
|
|
1993
|
+
if (profile === nativeProfileSlug(projectName)) {
|
|
1994
|
+
return `${nativeAgentName(projectName)}: host, concierge, project manager, orchestrator, and Workframe admin.`;
|
|
1995
|
+
}
|
|
1996
|
+
return PROFILE_DESCRIPTIONS[profile] ?? `${profile} specialist profile.`;
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
function profileSoulSource(profile, projectName) {
|
|
2000
|
+
const templateDir = profile === nativeProfileSlug(projectName) ? NATIVE_SOUL_TEMPLATE : profile;
|
|
2001
|
+
return path.join(PROFILES_DIR, templateDir, 'SOUL.md');
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
function profileAgentsSource(profile, projectName) {
|
|
2005
|
+
const templateDir = profile === nativeProfileSlug(projectName) ? NATIVE_SOUL_TEMPLATE : profile;
|
|
2006
|
+
return path.join(PROFILES_DIR, templateDir, 'AGENTS.md');
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
function profileSetupSource() {
|
|
2010
|
+
return path.join(PROFILES_DIR, NATIVE_SOUL_TEMPLATE, 'SETUP.md');
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
function nativeConfigYaml(projectName, agentName, personality) {
|
|
2014
|
+
const personalityLine = personality ? `# Personality: ${personality}
|
|
2015
|
+
` : "";
|
|
2016
|
+
return `${personalityLine}model:
|
|
2017
|
+
default: openrouter/owl-alpha
|
|
2018
|
+
provider: openrouter
|
|
2019
|
+
base_url: https://openrouter.ai/api/v1
|
|
2020
|
+
api_mode: chat_completions
|
|
2021
|
+
fallback_providers:
|
|
2022
|
+
- provider: openrouter
|
|
2023
|
+
model: openrouter/nex-agi
|
|
2024
|
+
- provider: openrouter
|
|
2025
|
+
model: openrouter/nvidia/nemotron-3-ultra-550b-a55b:free
|
|
2026
|
+
providers: {}
|
|
2027
|
+
credential_pool_strategies: {}
|
|
2028
|
+
toolsets:
|
|
2029
|
+
- hermes-cli
|
|
2030
|
+
- terminal
|
|
2031
|
+
agent:
|
|
2032
|
+
max_turns: 90
|
|
2033
|
+
gateway_timeout: 1800
|
|
2034
|
+
restart_drain_timeout: 180
|
|
2035
|
+
api_max_retries: 3
|
|
2036
|
+
service_tier: ''
|
|
2037
|
+
tool_use_enforcement: auto
|
|
2038
|
+
task_completion_guidance: true
|
|
2039
|
+
environment_probe: true
|
|
2040
|
+
gateway_timeout_warning: 900
|
|
2041
|
+
clarify_timeout: 600
|
|
2042
|
+
gateway_notify_interval: 180
|
|
2043
|
+
gateway_auto_continue_freshness: 3600
|
|
2044
|
+
image_input_mode: auto
|
|
2045
|
+
disabled_toolsets: []
|
|
2046
|
+
verbose: false
|
|
2047
|
+
reasoning_effort: medium
|
|
2048
|
+
terminal:
|
|
2049
|
+
backend: local
|
|
2050
|
+
modal_mode: auto
|
|
2051
|
+
cwd: /workspace
|
|
2052
|
+
timeout: 180
|
|
2053
|
+
web:
|
|
2054
|
+
backend: ''
|
|
2055
|
+
search_backend: ''
|
|
2056
|
+
extract_backend: ''
|
|
2057
|
+
browser:
|
|
2058
|
+
inactivity_timeout: 120
|
|
2059
|
+
allow_private_urls: false
|
|
2060
|
+
file_read_max_chars: 100000
|
|
2061
|
+
tool_output:
|
|
2062
|
+
max_bytes: 50000
|
|
2063
|
+
max_lines: 2000
|
|
2064
|
+
max_line_length: 2000
|
|
2065
|
+
compression:
|
|
2066
|
+
enabled: true
|
|
2067
|
+
threshold: 0.5
|
|
2068
|
+
target_ratio: 0.2
|
|
2069
|
+
openrouter:
|
|
2070
|
+
response_cache: true
|
|
2071
|
+
response_cache_ttl: 300
|
|
2072
|
+
display:
|
|
2073
|
+
streaming: true
|
|
2074
|
+
language: en
|
|
2075
|
+
show_reasoning: false
|
|
2076
|
+
skills:
|
|
2077
|
+
external_dirs: []
|
|
2078
|
+
template_vars: true
|
|
2079
|
+
inline_shell: false
|
|
2080
|
+
context:
|
|
2081
|
+
engine: compressor
|
|
2082
|
+
delegation:
|
|
2083
|
+
inherit_mcp_toolsets: true
|
|
2084
|
+
max_iterations: 50
|
|
2085
|
+
child_timeout_seconds: 600
|
|
2086
|
+
max_spawn_depth: 1
|
|
2087
|
+
orchestrator_enabled: false
|
|
2088
|
+
gateway:
|
|
2089
|
+
strict: false
|
|
2090
|
+
trust_recent_files: true
|
|
2091
|
+
trust_recent_files_seconds: 600
|
|
2092
|
+
sessions:
|
|
2093
|
+
auto_prune: true
|
|
2094
|
+
retention_days: 14
|
|
2095
|
+
logging:
|
|
2096
|
+
level: INFO
|
|
2097
|
+
memory:
|
|
2098
|
+
memory_enabled: true
|
|
2099
|
+
user_profile_enabled: true
|
|
2100
|
+
platforms:
|
|
2101
|
+
api_server:
|
|
2102
|
+
enabled: true
|
|
2103
|
+
extra:
|
|
2104
|
+
host: 0.0.0.0
|
|
2105
|
+
port: 18639
|
|
2106
|
+
key: workframe-local-key
|
|
2107
|
+
onboarding:
|
|
2108
|
+
seen:
|
|
2109
|
+
tool_progress_prompt: true
|
|
2110
|
+
`;
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
function loadPacks() { return JSON.parse(readText(PACKS_PATH)).packs ?? {}; }
|
|
2114
|
+
|
|
2115
|
+
function listAllProfiles() {
|
|
2116
|
+
return fs.readdirSync(PROFILES_DIR, { withFileTypes: true })
|
|
2117
|
+
.filter((e) => e.isDirectory())
|
|
2118
|
+
.map((e) => e.name)
|
|
2119
|
+
.sort();
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
function listSpecialistProfiles() {
|
|
2123
|
+
return listAllProfiles().filter((p) => p !== NATIVE_SOUL_TEMPLATE);
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
function listProfilesToSeed(projectName) {
|
|
2127
|
+
const native = nativeProfileSlug(projectName);
|
|
2128
|
+
return [native, ...listSpecialistProfiles()];
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
function openInstallUiPs1(ports) {
|
|
2132
|
+
return `$ErrorActionPreference = 'SilentlyContinue'
|
|
2133
|
+
$Root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
|
|
2134
|
+
$UiPort = '${ports.ui}'
|
|
2135
|
+
$envFile = Join-Path $Root '.env'
|
|
2136
|
+
if (Test-Path $envFile) {
|
|
2137
|
+
$line = Get-Content $envFile | Where-Object { $_ -match '^WORKFRAME_UI_PORT=' } | Select-Object -Last 1
|
|
2138
|
+
if ($line) { $UiPort = ($line -split '=', 2)[1].Trim() }
|
|
2139
|
+
}
|
|
2140
|
+
$url = "http://127.0.0.1:$UiPort/install"
|
|
2141
|
+
Write-Host "Opening install UI: $url" -ForegroundColor Cyan
|
|
2142
|
+
Start-Process $url | Out-Null
|
|
2143
|
+
`;
|
|
2144
|
+
}
|
|
2145
|
+
|
|
2146
|
+
function openInstallUiSh(ports) {
|
|
2147
|
+
return `#!/usr/bin/env bash
|
|
2148
|
+
set -euo pipefail
|
|
2149
|
+
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
2150
|
+
cd "$ROOT"
|
|
2151
|
+
|
|
2152
|
+
UI_PORT="${ports.ui}"
|
|
2153
|
+
if [ -f "$ROOT/.env" ]; then
|
|
2154
|
+
val=$(grep -E '^WORKFRAME_UI_PORT=' "$ROOT/.env" | tail -n1 | cut -d= -f2- || true)
|
|
2155
|
+
if [ -n "$val" ]; then UI_PORT="$val"; fi
|
|
2156
|
+
fi
|
|
2157
|
+
URL="http://127.0.0.1:\${UI_PORT}/install"
|
|
2158
|
+
echo "Opening install UI: $URL"
|
|
2159
|
+
if command -v xdg-open >/dev/null 2>&1; then xdg-open "$URL" >/dev/null 2>&1 || true
|
|
2160
|
+
elif command -v open >/dev/null 2>&1; then open "$URL" >/dev/null 2>&1 || true
|
|
2161
|
+
else echo "Open manually: $URL"
|
|
2162
|
+
fi
|
|
2163
|
+
`;
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
function seedNativeFromPackagePs1(nativeProfile) {
|
|
2167
|
+
return `$seedBase = Join-Path $Root "scripts\\seed\\profiles\\${nativeProfile}"
|
|
2168
|
+
$profileDir = Join-Path $Root "Agents\\profiles\\${nativeProfile}"
|
|
2169
|
+
$profileConfig = Join-Path $profileDir "config.yaml"
|
|
2170
|
+
if (-not (Test-Path $profileConfig) -and (Test-Path (Join-Path $seedBase "config.yaml"))) {
|
|
2171
|
+
Write-Host "Seeding ${nativeProfile} from package (UI-first install)..." -ForegroundColor DarkGray
|
|
2172
|
+
New-Item -ItemType Directory -Force -Path $profileDir | Out-Null
|
|
2173
|
+
Copy-Item -Path (Join-Path $seedBase '*') -Destination $profileDir -Recurse -Force
|
|
2174
|
+
$homeSoul = Join-Path $Root "Agents\\SOUL.md"
|
|
2175
|
+
$seedSoul = Join-Path $seedBase "SOUL.md"
|
|
2176
|
+
if (Test-Path $seedSoul) { Copy-Item $seedSoul $homeSoul -Force }
|
|
2177
|
+
}
|
|
2178
|
+
`;
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
function seedNativeFromPackageSh(nativeProfile) {
|
|
2182
|
+
return `seed_base="$ROOT/scripts/seed/profiles/${nativeProfile}"
|
|
2183
|
+
profile_dir="$ROOT/Agents/profiles/${nativeProfile}"
|
|
2184
|
+
profile_config="$profile_dir/config.yaml"
|
|
2185
|
+
if [ ! -f "$profile_config" ] && [ -f "$seed_base/config.yaml" ]; then
|
|
2186
|
+
echo "Seeding ${nativeProfile} from package (UI-first install)..."
|
|
2187
|
+
mkdir -p "$profile_dir"
|
|
2188
|
+
cp -R "$seed_base/." "$profile_dir/"
|
|
2189
|
+
if [ -f "$seed_base/SOUL.md" ]; then cp "$seed_base/SOUL.md" "$ROOT/Agents/SOUL.md"; fi
|
|
2190
|
+
fi
|
|
2191
|
+
`;
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
function openWorkframeUiPs1(ports) {
|
|
2195
|
+
return `$ErrorActionPreference = 'Stop'
|
|
2196
|
+
$Root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
|
|
2197
|
+
Set-Location $Root
|
|
2198
|
+
|
|
2199
|
+
$UiPort = '${ports.ui}'
|
|
2200
|
+
$envFile = Join-Path $Root '.env'
|
|
2201
|
+
if (Test-Path $envFile) {
|
|
2202
|
+
$line = Get-Content $envFile | Where-Object { $_ -match '^WORKFRAME_UI_PORT=' } | Select-Object -Last 1
|
|
2203
|
+
if ($line) { $UiPort = ($line -split '=', 2)[1].Trim() }
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
Write-Host 'Starting Workframe stack (if needed)...'
|
|
2207
|
+
docker compose up -d
|
|
2208
|
+
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
2209
|
+
|
|
2210
|
+
$url = "http://127.0.0.1:$UiPort/"
|
|
2211
|
+
Write-Host "Opening Workframe UI: $url"
|
|
2212
|
+
$deadline = (Get-Date).AddSeconds(90)
|
|
2213
|
+
while ((Get-Date) -lt $deadline) {
|
|
2214
|
+
try {
|
|
2215
|
+
$null = Invoke-WebRequest -Uri $url -UseBasicParsing -TimeoutSec 2
|
|
2216
|
+
$boot = Invoke-RestMethod -Uri "http://127.0.0.1:$UiPort/api/hermes/bootstrap" -TimeoutSec 3
|
|
2217
|
+
if ($boot.ok) { break }
|
|
2218
|
+
} catch {
|
|
2219
|
+
Start-Sleep -Seconds 2
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
try {
|
|
2223
|
+
Start-Process $url | Out-Null
|
|
2224
|
+
} catch {
|
|
2225
|
+
Write-Host "Open manually: $url"
|
|
2226
|
+
}
|
|
2227
|
+
`;
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
function openWorkframeUiSh(ports) {
|
|
2231
|
+
return `#!/usr/bin/env bash
|
|
2232
|
+
set -euo pipefail
|
|
2233
|
+
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
2234
|
+
cd "$ROOT"
|
|
2235
|
+
|
|
2236
|
+
UI_PORT="${ports.ui}"
|
|
2237
|
+
if [ -f "$ROOT/.env" ]; then
|
|
2238
|
+
val=$(grep -E '^WORKFRAME_UI_PORT=' "$ROOT/.env" | tail -n1 | cut -d= -f2- || true)
|
|
2239
|
+
if [ -n "$val" ]; then UI_PORT="$val"; fi
|
|
2240
|
+
fi
|
|
2241
|
+
|
|
2242
|
+
echo "Starting Workframe stack (if needed)..."
|
|
2243
|
+
docker compose up -d
|
|
2244
|
+
|
|
2245
|
+
URL="http://127.0.0.1:\${UI_PORT}/"
|
|
2246
|
+
echo "Opening Workframe UI: $URL"
|
|
2247
|
+
for _ in $(seq 1 45); do
|
|
2248
|
+
if curl -fsS -o /dev/null "$URL" 2>/dev/null && curl -fsS "$URL/api/hermes/bootstrap" 2>/dev/null | grep -q '"ok"[[:space:]]*:[[:space:]]*true'; then break; fi
|
|
2249
|
+
sleep 2
|
|
2250
|
+
done
|
|
2251
|
+
if command -v xdg-open >/dev/null 2>&1; then xdg-open "$URL" >/dev/null 2>&1 || true
|
|
2252
|
+
elif command -v open >/dev/null 2>&1; then open "$URL" >/dev/null 2>&1 || true
|
|
2253
|
+
else echo "Open manually: $URL"
|
|
2254
|
+
fi
|
|
2255
|
+
`;
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
function prepareTarget(target, force) {
|
|
2259
|
+
if (fs.existsSync(target)) {
|
|
2260
|
+
if (!force) throw new Error(`Target already exists: ${target}. Use --force to overwrite.`);
|
|
2261
|
+
const stat = fs.statSync(target);
|
|
2262
|
+
if (!stat.isDirectory()) throw new Error(`Refusing to remove non-directory target: ${target}`);
|
|
2263
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
function onboardingDoc({ projectName, pack, profiles, specialists, telegram, discord, ports, nativeProfileSlug: nativeSlug, nativeAgentName: nativeName, docker }) {
|
|
2268
|
+
const tg = telegram ? 'Enabled in plan (optional integration chosen).' : 'Skipped (can enable later).';
|
|
2269
|
+
const dc = discord ? 'Enabled in plan (optional integration chosen).' : 'Skipped (can enable later).';
|
|
2270
|
+
return `# ${projectName} setup
|
|
2271
|
+
|
|
2272
|
+
Operator guide for this **generated project instance**. Workframe method docs live in the meta installer repo — not here.
|
|
2273
|
+
|
|
2274
|
+
## Install phases (Workframe method)
|
|
2275
|
+
|
|
2276
|
+
| Phase | What | Command |
|
|
2277
|
+
|-------|------|---------|
|
|
2278
|
+
| A | Scaffold | Already done (\`create-workframe\`) |
|
|
2279
|
+
| B | Credentials + minimal boot | Hermes setup → \`bootstrap-native\` |
|
|
2280
|
+
| C | Agent-guided setup | Chat with **${nativeName}** — installs specialists, Discord/TG |
|
|
2281
|
+
|
|
2282
|
+
## Boot order (Phase B)
|
|
2283
|
+
|
|
2284
|
+
| Step | Command | Purpose |
|
|
2285
|
+
|------|---------|---------|
|
|
2286
|
+
| **All-in-one** | \`./scripts/start-install.sh\` or \`./scripts/start-install.ps1\` | Opens \`/install\` in browser → Docker stack → onboarding UI |
|
|
2287
|
+
| New terminal | \`./scripts/launch-install.sh\` or \`./scripts/launch-install.ps1\` | Same, TTY-friendly |
|
|
2288
|
+
| After Hermes setup only | \`./scripts/install.sh\` or \`./scripts/install.ps1\` | bootstrap-native + compose + open chat |
|
|
2289
|
+
| Phase C chat | \`./scripts/chat.sh\` / \`./scripts/chat.ps1\` or browser \`/chat\` | **${nativeName}** (dashboard runs with \`--tui\`) |
|
|
2290
|
+
|
|
2291
|
+
> **Do not** run bare \`docker run ... hermes-agent:latest\` without \`-p ${nativeSlug}\`.
|
|
2292
|
+
> That starts generic default Hermes (OWL) — not **${nativeName}**.
|
|
2293
|
+
|
|
2294
|
+
### Secure credentials
|
|
2295
|
+
|
|
2296
|
+
**macOS / Linux**
|
|
2297
|
+
|
|
2298
|
+
\`\`\`bash
|
|
2299
|
+
./scripts/start-install.sh # full Phase B (recommended)
|
|
2300
|
+
./scripts/launch-install.sh # same, new terminal
|
|
2301
|
+
./scripts/open-setup.sh # credentials / dashboard link only
|
|
2302
|
+
\`\`\`
|
|
2303
|
+
|
|
2304
|
+
**Windows**
|
|
2305
|
+
|
|
2306
|
+
\`\`\`powershell
|
|
2307
|
+
.\\scripts\\start-install.ps1 # full install (UI-first, recommended)
|
|
2308
|
+
.\\scripts\\launch-install.ps1 # same, new window
|
|
2309
|
+
.\\scripts\\open-setup.ps1 # credentials / dashboard link only
|
|
2310
|
+
\`\`\`
|
|
2311
|
+
|
|
2312
|
+
## Native agent
|
|
2313
|
+
|
|
2314
|
+
- **Display name:** ${nativeName}
|
|
2315
|
+
- **Hermes profile:** \`${nativeSlug}\`
|
|
2316
|
+
- **Setup playbook (after bootstrap):** \`Agents/profiles/${nativeSlug}/SETUP.md\`
|
|
2317
|
+
|
|
2318
|
+
## Add specialists (Phase C — or run yourself)
|
|
2319
|
+
|
|
2320
|
+
**macOS / Linux:** \`./scripts/add-profile.sh dev\` · **Windows:** \`.\\scripts\\add-profile.ps1 dev\`
|
|
2321
|
+
|
|
2322
|
+
Catalog: ${specialists.join(', ')}
|
|
2323
|
+
|
|
2324
|
+
**Reference pack fallback:** \`./scripts/bootstrap-profiles.sh\` or \`.\\scripts\\bootstrap-profiles.ps1\`
|
|
2325
|
+
|
|
2326
|
+
Creates: ${profiles.join(', ')}
|
|
2327
|
+
|
|
2328
|
+
## 1) Phase B — complete boot (recommended)
|
|
2329
|
+
|
|
2330
|
+
\`create-workframe\` launches the installer automatically. Or run manually:
|
|
2331
|
+
|
|
2332
|
+
\`\`\`bash
|
|
2333
|
+
./scripts/start-install.sh # macOS / Linux
|
|
2334
|
+
\`\`\`
|
|
2335
|
+
|
|
2336
|
+
\`\`\`powershell
|
|
2337
|
+
.\\scripts\\start-install.ps1 # Windows
|
|
2338
|
+
\`\`\`
|
|
2339
|
+
|
|
2340
|
+
Runs: Hermes setup (interactive) → \`bootstrap-native\` → \`docker compose up -d\` → browser opens \`/chat\`.
|
|
2341
|
+
|
|
2342
|
+
## 1b) Or finish after Hermes setup alone
|
|
2343
|
+
|
|
2344
|
+
\`\`\`bash
|
|
2345
|
+
./scripts/install.sh
|
|
2346
|
+
\`\`\`
|
|
2347
|
+
|
|
2348
|
+
\`\`\`powershell
|
|
2349
|
+
.\\scripts\\install.ps1
|
|
2350
|
+
\`\`\`
|
|
2351
|
+
|
|
2352
|
+
## 2) Chat with ${nativeName} (Phase C)
|
|
2353
|
+
|
|
2354
|
+
\`\`\`bash
|
|
2355
|
+
./scripts/chat.sh
|
|
2356
|
+
\`\`\`
|
|
2357
|
+
|
|
2358
|
+
\`\`\`powershell
|
|
2359
|
+
.\\scripts\\chat.ps1
|
|
2360
|
+
\`\`\`
|
|
2361
|
+
|
|
2362
|
+
${nativeName} reads the setup playbook and can walk you through Discord/TG channel binding (tutorial-style: one channel per specialist).
|
|
2363
|
+
|
|
2364
|
+
## 4) Stack (gateway + dashboard + workframe-api + Workframe UI)
|
|
2365
|
+
|
|
2366
|
+
\`\`\`bash
|
|
2367
|
+
docker compose up -d
|
|
2368
|
+
\`\`\`
|
|
2369
|
+
|
|
2370
|
+
| Service | URL | Container | Role |
|
|
2371
|
+
|---------|-----|-----------|------|
|
|
2372
|
+
| **Browser chat (TUI)** | http://127.0.0.1:${ports.dashboard}/chat | \`${docker.dashboard}\` | Hermes embedded terminal chat |
|
|
2373
|
+
| **Workframe API** | http://127.0.0.1:${ports.api} | \`${docker.workframeApi}\` | UI backend + profile control plane |
|
|
2374
|
+
| **Workframe UI** | http://127.0.0.1:${ports.ui} | \`${docker.workframe}\` | Product shell — chat, files, activity |
|
|
2375
|
+
| Hermes dashboard | http://127.0.0.1:${ports.dashboard} | \`${docker.dashboard}\` | Ops: profiles, keys, logs |
|
|
2376
|
+
| Gateway (API) | http://127.0.0.1:${ports.gateway} | \`${docker.gateway}\` | Discord / Telegram / API |
|
|
2377
|
+
|
|
2378
|
+
Open Workframe UI: \`./scripts/open-workframe-ui.sh\` or \`.\\scripts\\open-workframe-ui.ps1\`
|
|
2379
|
+
|
|
2380
|
+
Open Workframe API: \`./scripts/open-workframe-api.sh\` or \`.\\scripts\\open-workframe-api.ps1\`
|
|
2381
|
+
|
|
2382
|
+
Update Hermes (Docker): \`./scripts/update-hermes.sh\` or \`.\\scripts\\update-hermes.ps1\`
|
|
2383
|
+
|
|
2384
|
+
## 6) Workframe CLI (project lifecycle)
|
|
2385
|
+
|
|
2386
|
+
A \`workframe.mjs\` CLI is included in \`scripts/\` for ongoing project management:
|
|
2387
|
+
|
|
2388
|
+
\`\`\`bash
|
|
2389
|
+
node scripts/workframe.mjs doctor # Validate layout, compose, manifest, Docker runtime
|
|
2390
|
+
node scripts/workframe.mjs setup # Open Hermes setup (credentials)
|
|
2391
|
+
node scripts/workframe.mjs start # Start full stack (docker compose up -d)
|
|
2392
|
+
node scripts/workframe.mjs stop # Stop all stack containers
|
|
2393
|
+
node scripts/workframe.mjs restart # Restart the full stack
|
|
2394
|
+
node scripts/workframe.mjs status # Show running containers
|
|
2395
|
+
node scripts/workframe.mjs logs [-f] # Tail gateway logs
|
|
2396
|
+
node scripts/workframe.mjs ui # Open Workframe UI in browser
|
|
2397
|
+
\`\`\`
|
|
2398
|
+
|
|
2399
|
+
Run from the project root (where \`workframe-manifest.json\` lives).
|
|
2400
|
+
|
|
2401
|
+
## 5) Chat integrations (optional)
|
|
2402
|
+
- Telegram: ${tg} — control channel → **${nativeSlug}**
|
|
2403
|
+
- Discord: ${dc} — \`#dev\` → \`dev\`, etc. (exclusive channel ID binds)
|
|
2404
|
+
|
|
2405
|
+
## Agent pack
|
|
2406
|
+
- Pack: **${pack}**
|
|
2407
|
+
- Pack reference set: ${profiles.join(', ')}
|
|
2408
|
+
- Specialist catalog: ${specialists.join(', ')}
|
|
2409
|
+
|
|
2410
|
+
## Layout
|
|
2411
|
+
- \`Files/\` → \`/workspace\` (project truth — commit this)
|
|
2412
|
+
- \`Agents/\` → \`/opt/data\` (runtime only — never commit)
|
|
2413
|
+
|
|
2414
|
+
## Requirements
|
|
2415
|
+
- Docker (Desktop on macOS/Windows, Engine on Linux)
|
|
2416
|
+
- Interactive terminal for Hermes setup (\`-it\`)
|
|
2417
|
+
- Node.js for \`npx create-workframe\` (scaffold only)
|
|
2418
|
+
`;
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
async function main() {
|
|
2422
|
+
const args = parseArgs(process.argv.slice(2));
|
|
2423
|
+
if (args.help) return usage();
|
|
2424
|
+
|
|
2425
|
+
const packs = loadPacks();
|
|
2426
|
+
const packNames = Object.keys(packs).sort();
|
|
2427
|
+
const defaults = {
|
|
2428
|
+
name: args.name ?? 'workframe-app',
|
|
2429
|
+
agentName: null, // null means "use default derived from project name"
|
|
2430
|
+
personality: '',
|
|
2431
|
+
pack: args.pack ?? 'native',
|
|
2432
|
+
out: args.out ?? process.cwd(),
|
|
2433
|
+
telegram: args.telegram ?? false,
|
|
2434
|
+
discord: args.discord ?? false,
|
|
2435
|
+
};
|
|
2436
|
+
|
|
2437
|
+
if (!packNames.includes(defaults.pack)) {
|
|
2438
|
+
throw new Error(`Unknown pack '${defaults.pack}'. Available: ${packNames.join(', ')}`);
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
const rl = readline.createInterface({ input, output });
|
|
2442
|
+
try {
|
|
2443
|
+
let name = defaults.name;
|
|
2444
|
+
let pack = defaults.pack;
|
|
2445
|
+
let out = defaults.out;
|
|
2446
|
+
let telegram = defaults.telegram;
|
|
2447
|
+
let discord = defaults.discord;
|
|
2448
|
+
let agentName = defaults.agentName;
|
|
2449
|
+
let personality = defaults.personality;
|
|
2450
|
+
|
|
2451
|
+
if (!args.yes) {
|
|
2452
|
+
name = (await rl.question(`Project name [${name}]: `)).trim() || name;
|
|
2453
|
+
const suggestedAgentName = nativeAgentName(name);
|
|
2454
|
+
const agentNameInput = (await rl.question(`Agent name [${suggestedAgentName}]: `)).trim();
|
|
2455
|
+
agentName = agentNameInput || null; // null = use default
|
|
2456
|
+
const personalityInput = (await rl.question(`Agent personality (optional, e.g. "friendly, methodical, builder"): `)).trim();
|
|
2457
|
+
personality = personalityInput || defaults.personality;
|
|
2458
|
+
pack = (await rl.question(`Agent pack ${packNames.join('/')} [${pack}]: `)).trim() || pack;
|
|
2459
|
+
out = (await rl.question(`Output directory [${out}]: `)).trim() || out;
|
|
2460
|
+
if (args.telegram === null) telegram = /^y(es)?$/i.test((await rl.question('Enable Telegram steps? [y/N]: ')).trim());
|
|
2461
|
+
if (args.discord === null) discord = /^y(es)?$/i.test((await rl.question('Enable Discord steps? [y/N]: ')).trim());
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2464
|
+
if (!packNames.includes(pack)) throw new Error(`Unknown pack '${pack}'. Available: ${packNames.join(', ')}`);
|
|
2465
|
+
|
|
2466
|
+
const { target, name: safeName } = resolveProjectTarget(out, name);
|
|
2467
|
+
|
|
2468
|
+
const allProfiles = listSpecialistProfiles();
|
|
2469
|
+
const baseProfiles = packs[pack].profiles || [];
|
|
2470
|
+
const extraProfiles = [];
|
|
2471
|
+
|
|
2472
|
+
if (!args.yes) {
|
|
2473
|
+
const extra = (await rl.question(`Optional extra profiles from [${allProfiles.join(', ')}], or blank: `)).trim();
|
|
2474
|
+
if (extra) {
|
|
2475
|
+
for (const prof of extra.split(',').map((v) => v.trim()).filter(Boolean)) {
|
|
2476
|
+
if (!allProfiles.includes(prof)) throw new Error(`Unknown profile '${prof}'. Allowed: ${allProfiles.join(', ')}`);
|
|
2477
|
+
extraProfiles.push(prof);
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
const profiles = resolvePackProfiles([...baseProfiles, ...extraProfiles], safeName);
|
|
2483
|
+
const specialists = listSpecialistProfiles();
|
|
2484
|
+
const slug = slugify(safeName);
|
|
2485
|
+
const install = await allocateInstall({
|
|
2486
|
+
projectName: safeName,
|
|
2487
|
+
preferredSlot: args.slot ?? null,
|
|
2488
|
+
installId: args.installId ?? null,
|
|
2489
|
+
});
|
|
2490
|
+
const ports = install.ports;
|
|
2491
|
+
const effectiveAgentName = agentName || nativeAgentName(safeName);
|
|
2492
|
+
const ctx = { ...renderContext(safeName, ports), nativeAgentName: effectiveAgentName, agentPersonality: personality };
|
|
2493
|
+
const nativeSlug = ctx.nativeProfileSlug;
|
|
2494
|
+
const nativeName = effectiveAgentName;
|
|
2495
|
+
const docker = dockerContainerNames(safeName);
|
|
2496
|
+
const deploy = resolveDeployMode(args.deploy ?? 'docker');
|
|
2497
|
+
const hermesHome = deploy === 'native' ? detectHermesHome() : '';
|
|
2498
|
+
if (deploy === 'native' && !hermesHome) {
|
|
2499
|
+
throw new Error('Native deploy needs an existing Hermes install (config.yaml). Run hermes setup first, or use --deploy docker.');
|
|
2500
|
+
}
|
|
2501
|
+
const filesRoot = path.join(target, 'Files');
|
|
2502
|
+
|
|
2503
|
+
prepareTarget(target, args.force);
|
|
2504
|
+
|
|
2505
|
+
fs.mkdirSync(filesRoot, { recursive: true });
|
|
2506
|
+
fs.mkdirSync(path.join(target, 'Agents'), { recursive: true });
|
|
2507
|
+
writeText(path.join(target, 'Agents', '.gitkeep'), '');
|
|
2508
|
+
if (deploy === 'native') {
|
|
2509
|
+
writeText(
|
|
2510
|
+
path.join(target, 'NATIVE-HERMES.txt'),
|
|
2511
|
+
`Native deploy — host Hermes detected at:\n${hermesHome}\n\nDocker stack still uses ./Agents at /opt/data (isolated install runtime).\nOptional: run host gateway with hermes gateway run using host HERMES_HOME.\n`,
|
|
2512
|
+
);
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
const workspaceReadme = readText(path.join(PKG_ROOT, 'rules', 'workspace-README.md')).replace(
|
|
2516
|
+
/\{projectName\}/g,
|
|
2517
|
+
safeName,
|
|
2518
|
+
);
|
|
2519
|
+
writeText(path.join(filesRoot, 'README.md'), workspaceReadme);
|
|
2520
|
+
writeText(path.join(filesRoot, 'AGENTS.md'), render(readText(path.join(PKG_ROOT, 'rules', 'AGENTS.md')), ctx));
|
|
2521
|
+
writeText(path.join(filesRoot, '.hermes.md'), render(readText(path.join(PKG_ROOT, 'rules', '.hermes.md')), ctx));
|
|
2522
|
+
|
|
2523
|
+
copyWorkframeApiTemplate(target);
|
|
2524
|
+
copyWorkframeSupervisorTemplate(target);
|
|
2525
|
+
copyWorkframeUiTemplate(target, [nativeSlug]);
|
|
2526
|
+
writeText(
|
|
2527
|
+
path.join(target, 'workframe-ui', 'public', 'workframe-config.json'),
|
|
2528
|
+
`${JSON.stringify({ project_name: safeName, native_profile: nativeSlug }, null, 2)}\n`,
|
|
2529
|
+
);
|
|
2530
|
+
|
|
2531
|
+
writeText(path.join(target, 'SETUP.md'), onboardingDoc({
|
|
2532
|
+
projectName: safeName,
|
|
2533
|
+
pack,
|
|
2534
|
+
profiles,
|
|
2535
|
+
specialists,
|
|
2536
|
+
telegram,
|
|
2537
|
+
discord,
|
|
2538
|
+
ports,
|
|
2539
|
+
nativeProfileSlug: nativeSlug,
|
|
2540
|
+
nativeAgentName: nativeName,
|
|
2541
|
+
docker,
|
|
2542
|
+
}));
|
|
2543
|
+
|
|
2544
|
+
const seedProfiles = listProfilesToSeed(safeName);
|
|
2545
|
+
const setupTemplate = profileSetupSource();
|
|
2546
|
+
if (!fs.existsSync(setupTemplate)) throw new Error(`Missing native setup playbook: ${setupTemplate}`);
|
|
2547
|
+
|
|
2548
|
+
for (const prof of seedProfiles) {
|
|
2549
|
+
const src = profileSoulSource(prof, safeName);
|
|
2550
|
+
if (!fs.existsSync(src)) throw new Error(`Missing profile template: ${src}`);
|
|
2551
|
+
writeText(
|
|
2552
|
+
path.join(target, 'scripts', 'seed', 'profiles', prof, 'SOUL.md'),
|
|
2553
|
+
render(readText(src), ctx),
|
|
2554
|
+
);
|
|
2555
|
+
const agentsSrc = profileAgentsSource(prof, safeName);
|
|
2556
|
+
if (fs.existsSync(agentsSrc)) {
|
|
2557
|
+
writeText(
|
|
2558
|
+
path.join(target, 'scripts', 'seed', 'profiles', prof, 'AGENTS.md'),
|
|
2559
|
+
render(readText(agentsSrc), ctx),
|
|
2560
|
+
);
|
|
2561
|
+
}
|
|
2562
|
+
if (prof === nativeSlug) {
|
|
2563
|
+
writeText(
|
|
2564
|
+
path.join(target, 'scripts', 'seed', 'profiles', prof, 'SETUP.md'),
|
|
2565
|
+
render(readText(setupTemplate), ctx),
|
|
2566
|
+
);
|
|
2567
|
+
// Generate config.yaml for native profile
|
|
2568
|
+
writeText(
|
|
2569
|
+
path.join(target, 'scripts', 'seed', 'profiles', prof, 'config.yaml'),
|
|
2570
|
+
nativeConfigYaml(safeName, effectiveAgentName, personality),
|
|
2571
|
+
);
|
|
2572
|
+
const nativeSkillsSrc = path.join(PKG_ROOT, 'profiles', 'workframe-agent', 'skills');
|
|
2573
|
+
if (fs.existsSync(nativeSkillsSrc)) {
|
|
2574
|
+
copyTree(nativeSkillsSrc, path.join(target, 'scripts', 'seed', 'profiles', prof, 'skills'));
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
const agentTemplateSrc = path.join(PKG_ROOT, 'scripts', 'seed', 'agent-template', 'SOUL.md');
|
|
2580
|
+
if (fs.existsSync(agentTemplateSrc)) {
|
|
2581
|
+
writeText(path.join(target, 'scripts', 'seed', 'agent-template', 'SOUL.md'), readText(agentTemplateSrc));
|
|
2582
|
+
}
|
|
2583
|
+
const agentAgentsSrc = path.join(PKG_ROOT, 'scripts', 'seed', 'agent-template', 'AGENTS.md');
|
|
2584
|
+
if (fs.existsSync(agentAgentsSrc)) {
|
|
2585
|
+
writeText(path.join(target, 'scripts', 'seed', 'agent-template', 'AGENTS.md'), readText(agentAgentsSrc));
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2588
|
+
writeText(path.join(target, '.gitignore'), GITIGNORE);
|
|
2589
|
+
writeText(path.join(target, '.dockerignore'), DOCKERIGNORE);
|
|
2590
|
+
writeText(path.join(target, '.env.example'), envFileContent(install, { example: true, nativeProfile: nativeSlug, deploy, hermesHome }) + `WORKFRAME_HOST_COMPOSE_DIR=/path/to/${slug}\nWORKFRAME_HOST_PROJECT_ROOT=/path/to/${slug}\n`);
|
|
2591
|
+
const hostRoot = target.replace(/\\/g, '/');
|
|
2592
|
+
writeText(
|
|
2593
|
+
path.join(target, '.env'),
|
|
2594
|
+
envFileContent(install, { nativeProfile: nativeSlug, deploy, hermesHome })
|
|
2595
|
+
+ `WORKFRAME_HOST_COMPOSE_DIR=${hostRoot}\nWORKFRAME_HOST_PROJECT_ROOT=${hostRoot}\n`,
|
|
2596
|
+
);
|
|
2597
|
+
writeText(path.join(target, 'docker-compose.yml'), dockerComposeYaml(safeName, docker, nativeSlug, [nativeSlug], install.installId, hermesHome));
|
|
2598
|
+
writeText(path.join(target, 'docker-compose.host-bindings.yml'), dockerComposeHostBindingsYaml(docker));
|
|
2599
|
+
writeText(path.join(target, 'docker-compose.public.yml'), dockerComposePublicYaml(docker));
|
|
2600
|
+
const publicDeployDoc = path.join(PKG_ROOT, 'docs', 'PUBLIC_DEPLOY.md');
|
|
2601
|
+
if (fs.existsSync(publicDeployDoc)) {
|
|
2602
|
+
writeText(path.join(target, 'docs', 'PUBLIC_DEPLOY.md'), readText(publicDeployDoc));
|
|
2603
|
+
}
|
|
2604
|
+
writeText(
|
|
2605
|
+
path.join(target, 'docker-compose.dev-authority.yml'),
|
|
2606
|
+
`# NEVER use on public/VPS hosts — trusted local dev only (docker.sock on workframe-api).\n# Trusted local dev — API admin updates (docker.sock on workframe-api)\nname: ${docker.stack}\n\nservices:\n workframe-api:\n volumes:\n - ./workframe-api/public:/app/public:ro\n - ./workframe-api:/app\n - ./workframe-api/data:/app/data\n - ./Agents:/opt/data\n - ./Files:${WORKSPACE_DATA_MOUNT}\n - ./scripts:/opt/install/scripts:ro\n - .:/project\n - /var/run/docker.sock:/var/run/docker.sock\n environment:\n - WORKFRAME_ENABLE_ADMIN_UPDATES=1\n - WORKFRAME_COMPOSE_DIR=/project\n - WORKFRAME_PROJECT_ROOT=/project\n`,
|
|
2607
|
+
);
|
|
2608
|
+
|
|
2609
|
+
writeText(
|
|
2610
|
+
path.join(target, 'README.md'),
|
|
2611
|
+
`# ${safeName}\n\nWorkframe project generated by \`create-workframe\`.\n\n## Quick start\n\n\`create-workframe\` opens the installer automatically. Your browser lands on **/install** while Docker starts — then the onboarding UI walks you through mode, SMTP, and setup.\n\n**macOS / Linux**\n\n\`\`\`bash\n./scripts/start-install.sh\n# or open install UI anytime:\n./scripts/open-install-ui.sh\n\`\`\`\n\n**Windows**\n\n\`\`\`powershell\n.\\scripts\\start-install.ps1\n.\\scripts\\open-install-ui.ps1\n\`\`\`\n\nPrimary surface: **Workframe UI** at \`http://127.0.0.1:WORKFRAME_UI_PORT/install\` then \`/onboarding\`. LLM keys are configured in onboarding — no Hermes TUI required.\n\nRe-bootstrap only: \`./scripts/install.sh\` or \`.\\scripts\\install.ps1\`\n\n## Project management (workframe CLI)\n\n\`\`\`bash\nnode scripts/workframe.mjs doctor # Validate layout, compose, manifest, Docker runtime\nnode scripts/workframe.mjs setup # Open Hermes setup (credentials)\nnode scripts/workframe.mjs start # Start full stack (docker compose up -d)\nnode scripts/workframe.mjs stop # Stop all stack containers\nnode scripts/workframe.mjs restart # Restart the full stack\nnode scripts/workframe.mjs status # Show running containers\nnode scripts/workframe.mjs logs [-f] # Tail gateway logs\nnode scripts/workframe.mjs ui # Open Workframe UI in browser\n\`\`\`\n\nRun from the project root (where \`workframe-manifest.json\` lives).\n\nWorkspace: \`Files/\` · Runtime: \`Agents/\`\n`,
|
|
2612
|
+
);
|
|
2613
|
+
|
|
2614
|
+
writeText(path.join(target, 'scripts', 'setup.sh'), setupSh(docker, nativeSlug, nativeName));
|
|
2615
|
+
fs.chmodSync(path.join(target, 'scripts', 'setup.sh'), 0o755);
|
|
2616
|
+
writeText(path.join(target, 'scripts', 'setup.ps1'), setupPs1(docker, nativeSlug, nativeName));
|
|
2617
|
+
writeText(path.join(target, 'scripts', 'bootstrap-native.sh'), bootstrapNativeSh(nativeSlug, safeName, docker));
|
|
2618
|
+
fs.chmodSync(path.join(target, 'scripts', 'bootstrap-native.sh'), 0o755);
|
|
2619
|
+
writeText(path.join(target, 'scripts', 'bootstrap-native.ps1'), bootstrapNativePs1(nativeSlug, safeName, docker));
|
|
2620
|
+
writeText(path.join(target, 'scripts', 'bootstrap-profiles.sh'), bootstrapProfilesSh(profiles, safeName, nativeSlug, docker));
|
|
2621
|
+
fs.chmodSync(path.join(target, 'scripts', 'bootstrap-profiles.sh'), 0o755);
|
|
2622
|
+
writeText(path.join(target, 'scripts', 'bootstrap-profiles.ps1'), bootstrapProfilesPs1(profiles, safeName, nativeSlug, docker));
|
|
2623
|
+
writeText(path.join(target, 'scripts', 'add-profile.sh'), addProfileSh(nativeSlug, specialists, safeName, docker));
|
|
2624
|
+
fs.chmodSync(path.join(target, 'scripts', 'add-profile.sh'), 0o755);
|
|
2625
|
+
writeText(path.join(target, 'scripts', 'add-profile.ps1'), addProfilePs1(nativeSlug, specialists, safeName, docker));
|
|
2626
|
+
writeText(path.join(target, 'scripts', 'open-setup.sh'), openSetupSh(docker, ports));
|
|
2627
|
+
fs.chmodSync(path.join(target, 'scripts', 'open-setup.sh'), 0o755);
|
|
2628
|
+
writeText(path.join(target, 'scripts', 'open-setup.ps1'), openSetupPs1(docker, ports));
|
|
2629
|
+
writeText(path.join(target, 'scripts', 'install.sh'), installSh(nativeSlug, nativeName, docker, ports));
|
|
2630
|
+
fs.chmodSync(path.join(target, 'scripts', 'install.sh'), 0o755);
|
|
2631
|
+
writeText(path.join(target, 'scripts', 'install.ps1'), installPs1(nativeSlug, nativeName, docker, ports));
|
|
2632
|
+
writeText(path.join(target, 'scripts', 'start-install.sh'), startInstallSh(docker, nativeSlug, nativeName, ports));
|
|
2633
|
+
fs.chmodSync(path.join(target, 'scripts', 'start-install.sh'), 0o755);
|
|
2634
|
+
writeText(path.join(target, 'scripts', 'start-install.ps1'), startInstallPs1(docker, nativeSlug, nativeName, ports));
|
|
2635
|
+
writeText(path.join(target, 'scripts', 'launch-install.ps1'), launchInstallPs1());
|
|
2636
|
+
writeText(path.join(target, 'scripts', 'launch-install.sh'), launchInstallSh());
|
|
2637
|
+
fs.chmodSync(path.join(target, 'scripts', 'launch-install.sh'), 0o755);
|
|
2638
|
+
writeText(path.join(target, 'scripts', 'open-chat.ps1'), openChatPs1(ports));
|
|
2639
|
+
writeText(path.join(target, 'scripts', 'open-chat.sh'), openChatSh(ports));
|
|
2640
|
+
fs.chmodSync(path.join(target, 'scripts', 'open-chat.sh'), 0o755);
|
|
2641
|
+
writeText(path.join(target, 'scripts', 'open-workframe-api.ps1'), openWorkframeApiPs1(ports));
|
|
2642
|
+
writeText(path.join(target, 'scripts', 'open-workframe-ui.ps1'), openWorkframeUiPs1(ports));
|
|
2643
|
+
writeText(path.join(target, 'scripts', 'open-install-ui.ps1'), openInstallUiPs1(ports));
|
|
2644
|
+
writeText(path.join(target, 'scripts', 'open-workframe-api.sh'), openWorkframeApiSh(ports));
|
|
2645
|
+
writeText(path.join(target, 'scripts', 'open-workframe-ui.sh'), openWorkframeUiSh(ports));
|
|
2646
|
+
writeText(path.join(target, 'scripts', 'open-install-ui.sh'), openInstallUiSh(ports));
|
|
2647
|
+
fs.chmodSync(path.join(target, 'scripts', 'open-workframe-api.sh'), 0o755);
|
|
2648
|
+
fs.chmodSync(path.join(target, 'scripts', 'open-workframe-ui.sh'), 0o755);
|
|
2649
|
+
fs.chmodSync(path.join(target, 'scripts', 'open-install-ui.sh'), 0o755);
|
|
2650
|
+
writeText(path.join(target, 'scripts', 'update-hermes.ps1'), updateHermesPs1(profiles));
|
|
2651
|
+
writeText(path.join(target, 'scripts', 'update-hermes.sh'), updateHermesSh(profiles));
|
|
2652
|
+
fs.chmodSync(path.join(target, 'scripts', 'update-hermes.sh'), 0o755);
|
|
2653
|
+
for (const scriptName of ['apply-update-hermes.sh', 'apply-update-workframe.sh', 'restart-gateway-hermes.sh', 'compose-docker-host.sh', 'setup-stack-secrets.sh', 'bootstrap-workspace-link.sh', 'verify-public-deploy.sh', 'fix-zk-encryption-key.sh']) {
|
|
2654
|
+
const src = path.join(PKG_ROOT, 'scripts', scriptName);
|
|
2655
|
+
if (fs.existsSync(src)) {
|
|
2656
|
+
fs.copyFileSync(src, path.join(target, 'scripts', scriptName));
|
|
2657
|
+
fs.chmodSync(path.join(target, 'scripts', scriptName), 0o755);
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
const lifecycleSrc = path.join(PKG_ROOT, 'scripts', 'agent-lifecycle.mjs');
|
|
2661
|
+
if (fs.existsSync(lifecycleSrc)) {
|
|
2662
|
+
writeText(path.join(target, 'scripts', 'agent-lifecycle.mjs'), readText(lifecycleSrc));
|
|
2663
|
+
}
|
|
2664
|
+
const registryLib = path.join(PKG_ROOT, 'scripts', 'lib', 'workframe-registry.mjs');
|
|
2665
|
+
if (fs.existsSync(registryLib)) {
|
|
2666
|
+
fs.mkdirSync(path.join(target, 'scripts', 'lib'), { recursive: true });
|
|
2667
|
+
writeText(path.join(target, 'scripts', 'lib', 'workframe-registry.mjs'), readText(registryLib));
|
|
2668
|
+
}
|
|
2669
|
+
const identityLib = path.join(PKG_ROOT, 'scripts', 'lib', 'install-identity.mjs');
|
|
2670
|
+
if (fs.existsSync(identityLib)) {
|
|
2671
|
+
fs.mkdirSync(path.join(target, 'scripts', 'lib'), { recursive: true });
|
|
2672
|
+
writeText(path.join(target, 'scripts', 'lib', 'install-identity.mjs'), readText(identityLib));
|
|
2673
|
+
}
|
|
2674
|
+
// Copy workframe CLI for global project management (doctor, setup, stop, start, restart, status, logs, ui)
|
|
2675
|
+
const workframeCliSrc = path.join(PKG_ROOT, 'bin', 'workframe.js');
|
|
2676
|
+
if (fs.existsSync(workframeCliSrc)) {
|
|
2677
|
+
writeText(path.join(target, 'scripts', 'workframe.mjs'), readText(workframeCliSrc));
|
|
2678
|
+
fs.chmodSync(path.join(target, 'scripts', 'workframe.mjs'), 0o755);
|
|
2679
|
+
}
|
|
2680
|
+
const presetDirs = ['agents', 'users', 'logos'];
|
|
2681
|
+
const publicAssets = path.join(PKG_ROOT, 'workframe-ui', 'public', 'assets');
|
|
2682
|
+
for (const dir of presetDirs) {
|
|
2683
|
+
const src = path.join(publicAssets, dir);
|
|
2684
|
+
if (!fs.existsSync(src)) continue;
|
|
2685
|
+
const seedDir = path.join(target, 'scripts', 'seed', 'assets', dir);
|
|
2686
|
+
fs.mkdirSync(seedDir, { recursive: true });
|
|
2687
|
+
fs.cpSync(src, seedDir, { recursive: true });
|
|
2688
|
+
}
|
|
2689
|
+
writeText(path.join(target, 'scripts', 'chat.sh'), chatSh(nativeSlug, docker));
|
|
2690
|
+
fs.chmodSync(path.join(target, 'scripts', 'chat.sh'), 0o755);
|
|
2691
|
+
writeText(path.join(target, 'scripts', 'chat.ps1'), chatPs1(nativeSlug, docker));
|
|
2692
|
+
writeText(path.join(target, 'scripts', 'verify-bootstrap.sh'), verifyBootstrapSh(nativeSlug));
|
|
2693
|
+
fs.chmodSync(path.join(target, 'scripts', 'verify-bootstrap.sh'), 0o755);
|
|
2694
|
+
writeText(path.join(target, 'scripts', 'verify-bootstrap.ps1'), verifyBootstrapPs1(nativeSlug));
|
|
2695
|
+
|
|
2696
|
+
const manifest = {
|
|
2697
|
+
generator: `create-workframe@${PKG_VERSION}`,
|
|
2698
|
+
generated_at_utc: new Date().toISOString(),
|
|
2699
|
+
package_version: PKG_VERSION,
|
|
2700
|
+
hermes_tag: 'latest',
|
|
2701
|
+
deployment_posture: 'trusted_team',
|
|
2702
|
+
compose_overlays: {
|
|
2703
|
+
public: 'docker-compose.public.yml',
|
|
2704
|
+
dev_authority: 'docker-compose.dev-authority.yml',
|
|
2705
|
+
},
|
|
2706
|
+
project_name: safeName,
|
|
2707
|
+
project_slug: slug,
|
|
2708
|
+
install_id: install.installId,
|
|
2709
|
+
install_slot: install.slot,
|
|
2710
|
+
pack,
|
|
2711
|
+
profiles,
|
|
2712
|
+
profiles_pack_bootstrap: profiles,
|
|
2713
|
+
profiles_catalog: specialists,
|
|
2714
|
+
profiles_seeded: seedProfiles,
|
|
2715
|
+
profiles_installed_after_native_bootstrap: [nativeSlug],
|
|
2716
|
+
bootstrap: {
|
|
2717
|
+
default: 'native',
|
|
2718
|
+
phase_b_script: 'scripts/start-install.ps1',
|
|
2719
|
+
phase_b_script_sh: 'scripts/start-install.sh',
|
|
2720
|
+
phase_b_script_ps1: 'scripts/start-install.ps1',
|
|
2721
|
+
launch_script_sh: 'scripts/launch-install.sh',
|
|
2722
|
+
launch_script_ps1: 'scripts/launch-install.ps1',
|
|
2723
|
+
post_credentials_script: 'scripts/install.ps1',
|
|
2724
|
+
post_credentials_script_sh: 'scripts/install.sh',
|
|
2725
|
+
post_credentials_script_ps1: 'scripts/install.ps1',
|
|
2726
|
+
native_script: 'scripts/bootstrap-native.ps1',
|
|
2727
|
+
native_script_sh: 'scripts/bootstrap-native.sh',
|
|
2728
|
+
native_script_ps1: 'scripts/bootstrap-native.ps1',
|
|
2729
|
+
full_pack_script: 'scripts/bootstrap-profiles.ps1',
|
|
2730
|
+
full_pack_script_sh: 'scripts/bootstrap-profiles.sh',
|
|
2731
|
+
full_pack_script_ps1: 'scripts/bootstrap-profiles.ps1',
|
|
2732
|
+
add_profile_script: 'scripts/add-profile.ps1',
|
|
2733
|
+
add_profile_script_sh: 'scripts/add-profile.sh',
|
|
2734
|
+
add_profile_script_ps1: 'scripts/add-profile.ps1',
|
|
2735
|
+
secure_setup_script: 'scripts/open-setup.ps1',
|
|
2736
|
+
secure_setup_script_sh: 'scripts/open-setup.sh',
|
|
2737
|
+
secure_setup_script_ps1: 'scripts/open-setup.ps1',
|
|
2738
|
+
open_chat_script_sh: 'scripts/open-chat.sh',
|
|
2739
|
+
open_chat_script_ps1: 'scripts/open-chat.ps1',
|
|
2740
|
+
open_workframe_api_script_sh: 'scripts/open-workframe-api.sh',
|
|
2741
|
+
open_workframe_api_script_ps1: 'scripts/open-workframe-api.ps1',
|
|
2742
|
+
open_workframe_ui_script_sh: 'scripts/open-workframe-ui.sh',
|
|
2743
|
+
open_workframe_ui_script_ps1: 'scripts/open-workframe-ui.ps1',
|
|
2744
|
+
update_hermes_script_sh: 'scripts/update-hermes.sh',
|
|
2745
|
+
update_hermes_script_ps1: 'scripts/update-hermes.ps1',
|
|
2746
|
+
},
|
|
2747
|
+
native_agent: {
|
|
2748
|
+
display_name: nativeName,
|
|
2749
|
+
profile_slug: nativeSlug,
|
|
2750
|
+
setup_playbook_seed: `scripts/seed/profiles/${nativeSlug}/SETUP.md`,
|
|
2751
|
+
},
|
|
2752
|
+
docker: {
|
|
2753
|
+
image: docker.image,
|
|
2754
|
+
stack: docker.stack,
|
|
2755
|
+
network: docker.network,
|
|
2756
|
+
runtime: 'docker',
|
|
2757
|
+
containers: {
|
|
2758
|
+
gateway: docker.gateway,
|
|
2759
|
+
dashboard: docker.dashboard,
|
|
2760
|
+
workframe_api: docker.workframeApi,
|
|
2761
|
+
workframe: docker.workframe,
|
|
2762
|
+
chat: docker.chat,
|
|
2763
|
+
setup: docker.setup,
|
|
2764
|
+
},
|
|
2765
|
+
workframe_api_image: 'python:3-alpine',
|
|
2766
|
+
ui_image_placeholder: 'nginx:alpine',
|
|
2767
|
+
ui_image_target: 'workframe-ui (bundled SPA in workframe-ui/public)',
|
|
2768
|
+
},
|
|
2769
|
+
ports,
|
|
2770
|
+
integrations: { telegram, discord },
|
|
2771
|
+
layout: { workspace: 'Files', runtime: 'Agents' },
|
|
2772
|
+
security: {
|
|
2773
|
+
no_instance_data_in_template: true,
|
|
2774
|
+
runtime_state_directory: 'Agents',
|
|
2775
|
+
credentials_never_in_llm: true,
|
|
2776
|
+
},
|
|
2777
|
+
};
|
|
2778
|
+
writeText(path.join(target, 'workframe-manifest.json'), JSON.stringify(manifest, null, 2) + '\n');
|
|
2779
|
+
|
|
2780
|
+
if (args.runDockerPull) {
|
|
2781
|
+
if (args.installGuideOnly) {
|
|
2782
|
+
console.log('Skipped docker pull: run with --allow-install-actions to execute install commands.');
|
|
2783
|
+
} else {
|
|
2784
|
+
const res = spawnSync('docker', ['pull', 'nousresearch/hermes-agent:latest'], { stdio: 'inherit' });
|
|
2785
|
+
if (res.status !== 0) console.warn('docker pull failed; continue manually.');
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
2788
|
+
|
|
2789
|
+
console.log(`✅ Scaffold created: ${target}`);
|
|
2790
|
+
console.log(`Native agent: ${nativeName} (${nativeSlug})`);
|
|
2791
|
+
console.log(`Install id: ${install.installId} (slot ${install.slot})`);
|
|
2792
|
+
console.log(`Ports: UI ${ports.ui}, API ${ports.api}, gateway ${ports.gateway}, dashboard ${ports.dashboard}`);
|
|
2793
|
+
console.log(`Bootstrap default: bootstrap-native (full pack: ${profiles.length} profiles)`);
|
|
2794
|
+
console.log(`SOUL seeds: ${seedProfiles.length} profiles + native SETUP playbook`);
|
|
2795
|
+
if (!args.ci && !args.noLaunch) {
|
|
2796
|
+
if (launchPhaseBInstaller(target)) {
|
|
2797
|
+
console.log('');
|
|
2798
|
+
console.log('Launching installer — browser opens /install immediately.');
|
|
2799
|
+
console.log(`Continue setup at http://127.0.0.1:${ports.ui}/install`);
|
|
2800
|
+
}
|
|
2801
|
+
} else if (args.noLaunch) {
|
|
2802
|
+
console.log('Next: ./scripts/start-install.sh or .\\scripts\\start-install.ps1');
|
|
2803
|
+
} else {
|
|
2804
|
+
console.log('Next: SETUP.md');
|
|
2805
|
+
}
|
|
2806
|
+
} finally {
|
|
2807
|
+
rl.close();
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2811
|
+
main().catch((err) => {
|
|
2812
|
+
console.error(`ERROR: ${err.message}`);
|
|
2813
|
+
process.exit(1);
|
|
2814
|
+
});
|