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.
Files changed (415) hide show
  1. package/.dockerignore +22 -0
  2. package/.gitignore +73 -0
  3. package/LICENSE +201 -0
  4. package/NOTICE +12 -0
  5. package/README.md +111 -0
  6. package/SECURITY.md +40 -0
  7. package/bin/create-workframe.js +2814 -0
  8. package/bin/workframe.js +329 -0
  9. package/docs/workspace-instructions/WORKFRAME_DISCORD.md +20 -0
  10. package/docs/workspace-instructions/WORKFRAME_DOCUMENTS_AND_ARTIFACTS.md +20 -0
  11. package/docs/workspace-instructions/WORKFRAME_KANBAN.md +20 -0
  12. package/docs/workspace-instructions/WORKFRAME_ONBOARDING.md +21 -0
  13. package/docs/workspace-instructions/WORKFRAME_ROUTING.md +29 -0
  14. package/docs/workspace-instructions/WORKFRAME_TELEGRAM.md +19 -0
  15. package/package.json +67 -0
  16. package/profiles/README.md +15 -0
  17. package/profiles/architect/AGENTS.md +29 -0
  18. package/profiles/architect/SOUL.md +44 -0
  19. package/profiles/architect/skills/devops/kanban-worker/SKILL.md +27 -0
  20. package/profiles/designer/AGENTS.md +26 -0
  21. package/profiles/designer/SOUL.md +31 -0
  22. package/profiles/designer/skills/devops/kanban-worker/SKILL.md +27 -0
  23. package/profiles/dev/AGENTS.md +28 -0
  24. package/profiles/dev/SOUL.md +31 -0
  25. package/profiles/dev/skills/devops/kanban-worker/SKILL.md +27 -0
  26. package/profiles/docs/AGENTS.md +27 -0
  27. package/profiles/docs/SOUL.md +30 -0
  28. package/profiles/docs/skills/devops/kanban-worker/SKILL.md +27 -0
  29. package/profiles/research/AGENTS.md +26 -0
  30. package/profiles/research/SOUL.md +31 -0
  31. package/profiles/research/skills/devops/kanban-worker/SKILL.md +27 -0
  32. package/profiles/visionary/AGENTS.md +25 -0
  33. package/profiles/visionary/SOUL.md +31 -0
  34. package/profiles/visionary/skills/devops/kanban-worker/SKILL.md +27 -0
  35. package/profiles/workframe-agent/AGENTS.md +37 -0
  36. package/profiles/workframe-agent/SETUP.md +185 -0
  37. package/profiles/workframe-agent/SOUL.md +61 -0
  38. package/profiles/workframe-agent/skills/devops/botfather/SKILL.md +85 -0
  39. package/profiles/workframe-agent/skills/devops/kanban-handoff-pattern/SKILL.md +58 -0
  40. package/profiles/workframe-agent/skills/devops/workframe-cohort/SKILL.md +54 -0
  41. package/prompts/WORKFRAME_PROMPT_TEMPLATES.md +16 -0
  42. package/rules/.hermes.md +11 -0
  43. package/rules/AGENTS.md +22 -0
  44. package/rules/workspace-README.md +5 -0
  45. package/scripts/apply-update-hermes.sh +17 -0
  46. package/scripts/apply-update-workframe.sh +77 -0
  47. package/scripts/bootstrap-workspace-link.sh +8 -0
  48. package/scripts/bundle-workframe-ui.mjs +77 -0
  49. package/scripts/compose-docker-host.sh +37 -0
  50. package/scripts/create_workframe_scaffold.py +648 -0
  51. package/scripts/ensure-compose-host-paths.mjs +51 -0
  52. package/scripts/fix-zk-encryption-key.sh +35 -0
  53. package/scripts/lib/install-identity.mjs +212 -0
  54. package/scripts/lib/workframe-registry.mjs +290 -0
  55. package/scripts/new-project.mjs +68 -0
  56. package/scripts/restart-gateway-hermes.sh +12 -0
  57. package/scripts/security_audit.py +156 -0
  58. package/scripts/select_agent_pack.py +31 -0
  59. package/scripts/set-compose-public-url.mjs +92 -0
  60. package/scripts/setup-stack-secrets.sh +50 -0
  61. package/scripts/sync-canonical-to-package.mjs +146 -0
  62. package/scripts/test-scaffold.mjs +390 -0
  63. package/scripts/verify-public-deploy.sh +105 -0
  64. package/shared/WORKFRAME_AGENT_LIBRARY.md +31 -0
  65. package/shared/WORKFRAME_AGENT_OPERATIONS.md +29 -0
  66. package/shared/WORKFRAME_AGENT_PACKS.json +64 -0
  67. package/shared/WORKFRAME_AGENT_PACKS.yaml +20 -0
  68. package/shared/WORKFRAME_CHAT_PERMISSION_MODEL.md +20 -0
  69. package/shared/WORKFRAME_HANDOFF_SCHEMA.md +25 -0
  70. package/shared/WORKFRAME_SKILL_CURATION.md +27 -0
  71. package/shared/agent-avatars/ada.png +0 -0
  72. package/shared/agent-avatars/aibert.png +0 -0
  73. package/shared/agent-avatars/amelia.png +0 -0
  74. package/shared/agent-avatars/andy.png +0 -0
  75. package/shared/agent-avatars/arc.png +0 -0
  76. package/shared/agent-avatars/bob.png +0 -0
  77. package/shared/agent-avatars/buzz.png +0 -0
  78. package/shared/agent-avatars/carl.png +0 -0
  79. package/shared/agent-avatars/catalog.json +171 -0
  80. package/shared/agent-avatars/corbu.png +0 -0
  81. package/shared/agent-avatars/diana.png +0 -0
  82. package/shared/agent-avatars/ella.png +0 -0
  83. package/shared/agent-avatars/elvis.png +0 -0
  84. package/shared/agent-avatars/f1.png +0 -0
  85. package/shared/agent-avatars/f2.png +0 -0
  86. package/shared/agent-avatars/f3.png +0 -0
  87. package/shared/agent-avatars/f4.png +0 -0
  88. package/shared/agent-avatars/f5.png +0 -0
  89. package/shared/agent-avatars/f6.png +0 -0
  90. package/shared/agent-avatars/frida.png +0 -0
  91. package/shared/agent-avatars/george.png +0 -0
  92. package/shared/agent-avatars/grace.png +0 -0
  93. package/shared/agent-avatars/hedy.png +0 -0
  94. package/shared/agent-avatars/hermes.png +0 -0
  95. package/shared/agent-avatars/isaac.png +0 -0
  96. package/shared/agent-avatars/jes.png +0 -0
  97. package/shared/agent-avatars/john.png +0 -0
  98. package/shared/agent-avatars/joni.png +0 -0
  99. package/shared/agent-avatars/leo.png +0 -0
  100. package/shared/agent-avatars/louis.png +0 -0
  101. package/shared/agent-avatars/ludwig.png +0 -0
  102. package/shared/agent-avatars/m1.png +0 -0
  103. package/shared/agent-avatars/m2.png +0 -0
  104. package/shared/agent-avatars/m3.png +0 -0
  105. package/shared/agent-avatars/m4.png +0 -0
  106. package/shared/agent-avatars/m5.png +0 -0
  107. package/shared/agent-avatars/m6.png +0 -0
  108. package/shared/agent-avatars/marie.png +0 -0
  109. package/shared/agent-avatars/marilyn.png +0 -0
  110. package/shared/agent-avatars/neil.png +0 -0
  111. package/shared/agent-avatars/nikola.png +0 -0
  112. package/shared/agent-avatars/nina.png +0 -0
  113. package/shared/agent-avatars/paul.png +0 -0
  114. package/shared/agent-avatars/ringo.png +0 -0
  115. package/shared/agent-avatars/rosie.png +0 -0
  116. package/shared/agent-avatars/ste.png +0 -0
  117. package/shared/agent-avatars/steve.png +0 -0
  118. package/shared/agent-avatars/sun.png +0 -0
  119. package/shared/agent-avatars/tom.png +0 -0
  120. package/shared/agent-avatars/warren.png +0 -0
  121. package/shared/agent-avatars/woz.png +0 -0
  122. package/shared/agent-avatars/zaha.png +0 -0
  123. package/workframe-api/Dockerfile +14 -0
  124. package/workframe-api/README.md +28 -0
  125. package/workframe-api/action_proxy.py +131 -0
  126. package/workframe-api/auth_rate_limit.py +49 -0
  127. package/workframe-api/catalog/avatar-catalog.json +171 -0
  128. package/workframe-api/catalog/logo-catalog.json +86 -0
  129. package/workframe-api/catalog/user-avatar-catalog.json +171 -0
  130. package/workframe-api/credential_vault.py +445 -0
  131. package/workframe-api/data/.gitkeep +0 -0
  132. package/workframe-api/data/avatar-catalog.json +41 -0
  133. package/workframe-api/data/logo-catalog.json +14 -0
  134. package/workframe-api/data/user-avatar-catalog.json +18 -0
  135. package/workframe-api/email_sender.py +220 -0
  136. package/workframe-api/google_auth.py +90 -0
  137. package/workframe-api/install_api.py +359 -0
  138. package/workframe-api/internal_proxy_auth.py +150 -0
  139. package/workframe-api/llm_proxy.py +277 -0
  140. package/workframe-api/oidc_jwt.py +108 -0
  141. package/workframe-api/package.json +13 -0
  142. package/workframe-api/platform_auth.py +194 -0
  143. package/workframe-api/profile_secret_policy.py +86 -0
  144. package/workframe-api/public/assets/index-DPXu_lGn.css +1 -0
  145. package/workframe-api/public/assets/index-DYnLrCZZ.js +9 -0
  146. package/workframe-api/public/assets/index-DglUqFB_.js +9 -0
  147. package/workframe-api/public/index.html +12 -0
  148. package/workframe-api/requirements.txt +2 -0
  149. package/workframe-api/server.py +19646 -0
  150. package/workframe-api/site_meta.py +271 -0
  151. package/workframe-api/stack_config.py +427 -0
  152. package/workframe-api/tests/__init__.py +0 -0
  153. package/workframe-api/tests/db_setup.py +13 -0
  154. package/workframe-api/tests/test_admin_updates_gated.py +30 -0
  155. package/workframe-api/tests/test_agent_dm_bootstrap.py +196 -0
  156. package/workframe-api/tests/test_agent_profile_sync.py +76 -0
  157. package/workframe-api/tests/test_auth_email.py +222 -0
  158. package/workframe-api/tests/test_auth_hole_fix_selfcheck.py +99 -0
  159. package/workframe-api/tests/test_auth_rate_limit.py +19 -0
  160. package/workframe-api/tests/test_avatar_resolve.py +77 -0
  161. package/workframe-api/tests/test_child_soul_template.py +71 -0
  162. package/workframe-api/tests/test_credential_canary.py +135 -0
  163. package/workframe-api/tests/test_credential_isolation.py +448 -0
  164. package/workframe-api/tests/test_credential_resolution.py +206 -0
  165. package/workframe-api/tests/test_device_oauth.py +108 -0
  166. package/workframe-api/tests/test_doctor_repair.py +103 -0
  167. package/workframe-api/tests/test_ensure_profile_api.py +77 -0
  168. package/workframe-api/tests/test_gateway_compose_security.py +136 -0
  169. package/workframe-api/tests/test_install_secure_host.py +39 -0
  170. package/workframe-api/tests/test_internal_proxy_auth.py +125 -0
  171. package/workframe-api/tests/test_invite_runtime_bootstrap.py +72 -0
  172. package/workframe-api/tests/test_kanban_delegation.py +185 -0
  173. package/workframe-api/tests/test_llm_proxy.py +155 -0
  174. package/workframe-api/tests/test_login_access_policy.py +183 -0
  175. package/workframe-api/tests/test_mvp_model_bootstrap.py +75 -0
  176. package/workframe-api/tests/test_onboarding_bootstrap.py +248 -0
  177. package/workframe-api/tests/test_platform_auth.py +47 -0
  178. package/workframe-api/tests/test_profile_config_path.py +56 -0
  179. package/workframe-api/tests/test_profile_config_yaml_repair.py +63 -0
  180. package/workframe-api/tests/test_profile_create.py +72 -0
  181. package/workframe-api/tests/test_profile_identity_overlay.py +61 -0
  182. package/workframe-api/tests/test_profile_install_health.py +45 -0
  183. package/workframe-api/tests/test_profile_secret_policy.py +57 -0
  184. package/workframe-api/tests/test_profile_workspace_cwd.py +34 -0
  185. package/workframe-api/tests/test_provider_bootstrap.py +75 -0
  186. package/workframe-api/tests/test_provider_connect.py +54 -0
  187. package/workframe-api/tests/test_room_crud.py +192 -0
  188. package/workframe-api/tests/test_room_tenancy.py +701 -0
  189. package/workframe-api/tests/test_runtime_identity_backfill.py +34 -0
  190. package/workframe-api/tests/test_site_meta.py +81 -0
  191. package/workframe-api/tests/test_soul_stub.py +42 -0
  192. package/workframe-api/tests/test_space_member_sync.py +99 -0
  193. package/workframe-api/tests/test_stripe_stack_config.py +37 -0
  194. package/workframe-api/tests/test_supervisor_lifecycle.py +52 -0
  195. package/workframe-api/tests/test_turn_credential_vault.py +125 -0
  196. package/workframe-api/tests/test_updates.py +176 -0
  197. package/workframe-api/tests/test_user_cohort.py +113 -0
  198. package/workframe-api/tests/test_vault_envelope.py +110 -0
  199. package/workframe-api/tests/test_workspace_members.py +183 -0
  200. package/workframe-api/tests/test_workspace_messaging_sync.py +125 -0
  201. package/workframe-api/tests/test_workspace_provider_list.py +57 -0
  202. package/workframe-api/time-bind-chat.py +99 -0
  203. package/workframe-api/turn_credentials.py +226 -0
  204. package/workframe-api/updates.py +417 -0
  205. package/workframe-api/vault_kek.py +159 -0
  206. package/workframe-api/zk_auth.py +633 -0
  207. package/workframe-supervisor/Dockerfile +11 -0
  208. package/workframe-supervisor/profile_secret_policy.py +76 -0
  209. package/workframe-supervisor/server.py +787 -0
  210. package/workframe-supervisor/tests/test_exec_guard.py +42 -0
  211. package/workframe-supervisor/tests/test_server_import.py +21 -0
  212. package/workframe-ui/docker/nginx.conf +85 -0
  213. package/workframe-ui/public/assets/1-DLJbBkOb.png +0 -0
  214. package/workframe-ui/public/assets/10-uwRwj5ce.png +0 -0
  215. package/workframe-ui/public/assets/11-5OuV9F_e.png +0 -0
  216. package/workframe-ui/public/assets/12-u_axjxW-.png +0 -0
  217. package/workframe-ui/public/assets/13-ldSvcMsH.png +0 -0
  218. package/workframe-ui/public/assets/14-xdcALEYD.png +0 -0
  219. package/workframe-ui/public/assets/15-aZ4snEFB.png +0 -0
  220. package/workframe-ui/public/assets/16-L_5-DttY.png +0 -0
  221. package/workframe-ui/public/assets/2-zOPZTppD.png +0 -0
  222. package/workframe-ui/public/assets/3-Dc3WoVu5.png +0 -0
  223. package/workframe-ui/public/assets/4-C50hk7_m.png +0 -0
  224. package/workframe-ui/public/assets/5-Eweetkq4.png +0 -0
  225. package/workframe-ui/public/assets/6-5sOXgfkw.png +0 -0
  226. package/workframe-ui/public/assets/7-BqRBCbiC.png +0 -0
  227. package/workframe-ui/public/assets/8-DEDKS94h.png +0 -0
  228. package/workframe-ui/public/assets/9-DNj34GW-.png +0 -0
  229. package/workframe-ui/public/assets/ada-DsvuOc9n.png +0 -0
  230. package/workframe-ui/public/assets/aibert-BCz8Lo8H.png +0 -0
  231. package/workframe-ui/public/assets/amelia-DUf3EBGu.png +0 -0
  232. package/workframe-ui/public/assets/andy-Cpymuhhx.png +0 -0
  233. package/workframe-ui/public/assets/arc-CBDYvkAF.js +1 -0
  234. package/workframe-ui/public/assets/architecture-7EHR7CIX-CtbQKTuT.js +1 -0
  235. package/workframe-ui/public/assets/architectureDiagram-3BPJPVTR-XnBRKeW0.js +36 -0
  236. package/workframe-ui/public/assets/array-BifhSqXX.js +1 -0
  237. package/workframe-ui/public/assets/avatars/ada.png +0 -0
  238. package/workframe-ui/public/assets/avatars/aibert.png +0 -0
  239. package/workframe-ui/public/assets/avatars/amelia.png +0 -0
  240. package/workframe-ui/public/assets/avatars/andy.png +0 -0
  241. package/workframe-ui/public/assets/avatars/bob.png +0 -0
  242. package/workframe-ui/public/assets/avatars/buzz.png +0 -0
  243. package/workframe-ui/public/assets/avatars/carl.png +0 -0
  244. package/workframe-ui/public/assets/avatars/catalog.json +171 -0
  245. package/workframe-ui/public/assets/avatars/corbu.png +0 -0
  246. package/workframe-ui/public/assets/avatars/diana.png +0 -0
  247. package/workframe-ui/public/assets/avatars/elvis.png +0 -0
  248. package/workframe-ui/public/assets/avatars/frida.png +0 -0
  249. package/workframe-ui/public/assets/avatars/george.png +0 -0
  250. package/workframe-ui/public/assets/avatars/grace.png +0 -0
  251. package/workframe-ui/public/assets/avatars/hedy.png +0 -0
  252. package/workframe-ui/public/assets/avatars/hermes.png +0 -0
  253. package/workframe-ui/public/assets/avatars/isaac.png +0 -0
  254. package/workframe-ui/public/assets/avatars/john.png +0 -0
  255. package/workframe-ui/public/assets/avatars/joni.png +0 -0
  256. package/workframe-ui/public/assets/avatars/leo.png +0 -0
  257. package/workframe-ui/public/assets/avatars/louis.png +0 -0
  258. package/workframe-ui/public/assets/avatars/ludwig.png +0 -0
  259. package/workframe-ui/public/assets/avatars/marie.png +0 -0
  260. package/workframe-ui/public/assets/avatars/marilyn.png +0 -0
  261. package/workframe-ui/public/assets/avatars/nikola.png +0 -0
  262. package/workframe-ui/public/assets/avatars/nina.png +0 -0
  263. package/workframe-ui/public/assets/avatars/paul.png +0 -0
  264. package/workframe-ui/public/assets/avatars/ringo.png +0 -0
  265. package/workframe-ui/public/assets/avatars/rosie.png +0 -0
  266. package/workframe-ui/public/assets/avatars/steve.png +0 -0
  267. package/workframe-ui/public/assets/avatars/sun.png +0 -0
  268. package/workframe-ui/public/assets/avatars/warren.png +0 -0
  269. package/workframe-ui/public/assets/avatars/woz.png +0 -0
  270. package/workframe-ui/public/assets/avatars/zaha.png +0 -0
  271. package/workframe-ui/public/assets/blockDiagram-GPEHLZMM-VYHUfVhd.js +132 -0
  272. package/workframe-ui/public/assets/bob-DRz-48Id.png +0 -0
  273. package/workframe-ui/public/assets/branding/banner.png +0 -0
  274. package/workframe-ui/public/assets/branding/og-default.png +0 -0
  275. package/workframe-ui/public/assets/branding/workframe'white.png +0 -0
  276. package/workframe-ui/public/assets/branding/workframe-1.png +0 -0
  277. package/workframe-ui/public/assets/branding/workframe-2.png +0 -0
  278. package/workframe-ui/public/assets/branding/workframe-3.png +0 -0
  279. package/workframe-ui/public/assets/branding/workframe-4.png +0 -0
  280. package/workframe-ui/public/assets/branding/workframe-5.png +0 -0
  281. package/workframe-ui/public/assets/branding/workframe-banner.png +0 -0
  282. package/workframe-ui/public/assets/branding/workframe-logo-horizontal-mini.png +0 -0
  283. package/workframe-ui/public/assets/branding/workframe-logo-horizontal-nano.png +0 -0
  284. package/workframe-ui/public/assets/branding/workframe-logo-horizontal.png +0 -0
  285. package/workframe-ui/public/assets/branding/workframe-logo-vertical-alt.png +0 -0
  286. package/workframe-ui/public/assets/branding/workframe-logo-vertical.png +0 -0
  287. package/workframe-ui/public/assets/branding/workframe.png +0 -0
  288. package/workframe-ui/public/assets/buzz-mC4PtMvC.png +0 -0
  289. package/workframe-ui/public/assets/c4Diagram-AAUBKEIU-BTjUcJpm.js +10 -0
  290. package/workframe-ui/public/assets/carl-CtE74db_.png +0 -0
  291. package/workframe-ui/public/assets/channel-Dy4Z4-jn.js +1 -0
  292. package/workframe-ui/public/assets/chunk-2J33WTMH-w7uu7R-b.js +1 -0
  293. package/workframe-ui/public/assets/chunk-3OPIFGDE-Cb9LtnDX.js +62 -0
  294. package/workframe-ui/public/assets/chunk-4BX2VUAB-DiQ-qCwH.js +1 -0
  295. package/workframe-ui/public/assets/chunk-55IACEB6-C-mLFr7z.js +1 -0
  296. package/workframe-ui/public/assets/chunk-5ZQYHXKU-DOesfiCI.js +2 -0
  297. package/workframe-ui/public/assets/chunk-727SXJPM-BJ3oBZuz.js +206 -0
  298. package/workframe-ui/public/assets/chunk-AQP2D5EJ-CCA6xpGs.js +231 -0
  299. package/workframe-ui/public/assets/chunk-BSJP7CBP-a0cMNFb2.js +1 -0
  300. package/workframe-ui/public/assets/chunk-CSCIHK7Q-kuqN8EIY.js +122 -0
  301. package/workframe-ui/public/assets/chunk-FMBD7UC4-DyPgYHCg.js +15 -0
  302. package/workframe-ui/public/assets/chunk-KSCS5N6A-CdUuvR0V.js +10 -0
  303. package/workframe-ui/public/assets/chunk-L5ZTLDWV-Dq9NoWmK.js +1 -0
  304. package/workframe-ui/public/assets/chunk-LZXEDZCA-p74rddlO.js +2 -0
  305. package/workframe-ui/public/assets/chunk-ND2GUHAM-DBD2u1Gz.js +1 -0
  306. package/workframe-ui/public/assets/chunk-NNHCCRGN-DlpIbxXb.js +159 -0
  307. package/workframe-ui/public/assets/chunk-NZK2D7GU-BeIeYFnd.js +1 -0
  308. package/workframe-ui/public/assets/chunk-O5CBEL6O-ClHc56ib.js +70 -0
  309. package/workframe-ui/public/assets/chunk-QZHKN3VN-CtBEchFK.js +1 -0
  310. package/workframe-ui/public/assets/chunk-WU5MYG2G-B9pBtriN.js +1 -0
  311. package/workframe-ui/public/assets/chunk-XPW4576I-EFr8R_1p.js +32 -0
  312. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-BMAEA8jI.js +1 -0
  313. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-BMAEA8jI.js +1 -0
  314. package/workframe-ui/public/assets/corbu-KiaMXzXQ.png +0 -0
  315. package/workframe-ui/public/assets/cose-bilkent-S5V4N54A-C7aPBODd.js +1 -0
  316. package/workframe-ui/public/assets/cytoscape.esm-h6BdjjI9.js +321 -0
  317. package/workframe-ui/public/assets/dagre-BM42HDAG-BdU1Rv-H.js +4 -0
  318. package/workframe-ui/public/assets/dagre-Bx709z4p.js +1 -0
  319. package/workframe-ui/public/assets/defaultLocale-C8Fc0cco.js +1 -0
  320. package/workframe-ui/public/assets/diagram-2AECGRRQ-DWowSo85.js +43 -0
  321. package/workframe-ui/public/assets/diagram-5GNKFQAL-MnxBbceO.js +10 -0
  322. package/workframe-ui/public/assets/diagram-KO2AKTUF-DQaLRXFf.js +3 -0
  323. package/workframe-ui/public/assets/diagram-LMA3HP47-CQaBud9k.js +24 -0
  324. package/workframe-ui/public/assets/diagram-OG6HWLK6-D8bAXbY9.js +24 -0
  325. package/workframe-ui/public/assets/diana-DW0MsL38.png +0 -0
  326. package/workframe-ui/public/assets/dist-DGpTLHr_.js +1 -0
  327. package/workframe-ui/public/assets/elvis-LCFaZIcT.png +0 -0
  328. package/workframe-ui/public/assets/erDiagram-TEJ5UH35-1E-xSvBK.js +85 -0
  329. package/workframe-ui/public/assets/eventmodeling-FCH6USID-D75cstNT.js +1 -0
  330. package/workframe-ui/public/assets/flowDiagram-I6XJVG4X-CgOVD5hu.js +162 -0
  331. package/workframe-ui/public/assets/frida-CXFA0w3F.png +0 -0
  332. package/workframe-ui/public/assets/ganttDiagram-6RSMTGT7-JFYAIauo.js +292 -0
  333. package/workframe-ui/public/assets/george-DBSH2Sm2.png +0 -0
  334. package/workframe-ui/public/assets/gitGraph-WXDBUCRP-B9REenIl.js +1 -0
  335. package/workframe-ui/public/assets/gitGraphDiagram-PVQCEYII-BQ7NcMSn.js +106 -0
  336. package/workframe-ui/public/assets/grace-BhV0UPc0.png +0 -0
  337. package/workframe-ui/public/assets/graphlib-B8gBHxth.js +1 -0
  338. package/workframe-ui/public/assets/hedy-BR2IHift.png +0 -0
  339. package/workframe-ui/public/assets/hermes-CqCzcE0y.png +0 -0
  340. package/workframe-ui/public/assets/index-Dnw6vjqb.js +133 -0
  341. package/workframe-ui/public/assets/index-DpAGxump.css +1 -0
  342. package/workframe-ui/public/assets/info-J43DQDTF-CL6-eTjH.js +1 -0
  343. package/workframe-ui/public/assets/infoDiagram-5YYISTIA-LJTODW4W.js +2 -0
  344. package/workframe-ui/public/assets/init-D6jRqBbL.js +1 -0
  345. package/workframe-ui/public/assets/isaac-D1nhJAuv.png +0 -0
  346. package/workframe-ui/public/assets/ishikawaDiagram-YF4QCWOH-bchrQVuo.js +70 -0
  347. package/workframe-ui/public/assets/john-zSPWwNi4.png +0 -0
  348. package/workframe-ui/public/assets/joni-BFLoyfJP.png +0 -0
  349. package/workframe-ui/public/assets/journeyDiagram-JHISSGLW-DkrvYuxP.js +139 -0
  350. package/workframe-ui/public/assets/kanban-definition-UN3LZRKU-DFRbj0IG.js +89 -0
  351. package/workframe-ui/public/assets/katex-Vhh-h91d.js +257 -0
  352. package/workframe-ui/public/assets/leo-C_3IOL11.png +0 -0
  353. package/workframe-ui/public/assets/line-Vd48P7-O.js +1 -0
  354. package/workframe-ui/public/assets/linear-Ckizh2G7.js +1 -0
  355. package/workframe-ui/public/assets/louis-DEEECFSX.png +0 -0
  356. package/workframe-ui/public/assets/ludwig-_hoKhhyK.png +0 -0
  357. package/workframe-ui/public/assets/marie-DET6MsfO.png +0 -0
  358. package/workframe-ui/public/assets/marilyn-DTqwt8Yh.png +0 -0
  359. package/workframe-ui/public/assets/mermaid-parser.core-Bkimsnqj.js +4 -0
  360. package/workframe-ui/public/assets/mermaid.core-x0TvVuPo.js +9 -0
  361. package/workframe-ui/public/assets/mindmap-definition-RKZ34NQL-6ykAFPEz.js +96 -0
  362. package/workframe-ui/public/assets/nikola-B4PtHrJv.png +0 -0
  363. package/workframe-ui/public/assets/nina-BYbrOn0d.png +0 -0
  364. package/workframe-ui/public/assets/ordinal-hYBb2elL.js +1 -0
  365. package/workframe-ui/public/assets/packet-YPE3B663-Dw3xgMDt.js +1 -0
  366. package/workframe-ui/public/assets/path-BWPyau1x.js +1 -0
  367. package/workframe-ui/public/assets/paul-CGURYQIn.png +0 -0
  368. package/workframe-ui/public/assets/pie-LRSECV5Y-DATysawG.js +1 -0
  369. package/workframe-ui/public/assets/pieDiagram-4H26LBE5-SJKD1S0S.js +30 -0
  370. package/workframe-ui/public/assets/project-logos/1.png +0 -0
  371. package/workframe-ui/public/assets/project-logos/10.png +0 -0
  372. package/workframe-ui/public/assets/project-logos/11.png +0 -0
  373. package/workframe-ui/public/assets/project-logos/12.png +0 -0
  374. package/workframe-ui/public/assets/project-logos/13.png +0 -0
  375. package/workframe-ui/public/assets/project-logos/14.png +0 -0
  376. package/workframe-ui/public/assets/project-logos/15.png +0 -0
  377. package/workframe-ui/public/assets/project-logos/16.png +0 -0
  378. package/workframe-ui/public/assets/project-logos/2.png +0 -0
  379. package/workframe-ui/public/assets/project-logos/3.png +0 -0
  380. package/workframe-ui/public/assets/project-logos/4.png +0 -0
  381. package/workframe-ui/public/assets/project-logos/5.png +0 -0
  382. package/workframe-ui/public/assets/project-logos/6.png +0 -0
  383. package/workframe-ui/public/assets/project-logos/7.png +0 -0
  384. package/workframe-ui/public/assets/project-logos/8.png +0 -0
  385. package/workframe-ui/public/assets/project-logos/9.png +0 -0
  386. package/workframe-ui/public/assets/project-logos/catalog.json +86 -0
  387. package/workframe-ui/public/assets/quadrantDiagram-W4KKPZXB-BrYDZX8q.js +7 -0
  388. package/workframe-ui/public/assets/radar-GUYGQ44K-BmWYPCds.js +1 -0
  389. package/workframe-ui/public/assets/requirementDiagram-4Y6WPE33-DwL9Mc8e.js +84 -0
  390. package/workframe-ui/public/assets/ringo-WhfUNOyY.png +0 -0
  391. package/workframe-ui/public/assets/rosie-CAtcIf87.png +0 -0
  392. package/workframe-ui/public/assets/rough.esm-CSKSodPl.js +1 -0
  393. package/workframe-ui/public/assets/sankeyDiagram-5OEKKPKP-DYIFsL8h.js +40 -0
  394. package/workframe-ui/public/assets/sequenceDiagram-3UESZ5HK-0-FPkFk8.js +162 -0
  395. package/workframe-ui/public/assets/src-B_od6b6h.js +1 -0
  396. package/workframe-ui/public/assets/stateDiagram-AJRCARHV-BQCiBk6u.js +1 -0
  397. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-B89jAMFF.js +1 -0
  398. package/workframe-ui/public/assets/steve-CgXXJ9EZ.png +0 -0
  399. package/workframe-ui/public/assets/sun-BLNAhoZd.png +0 -0
  400. package/workframe-ui/public/assets/timeline-definition-PNZ67QCA-DS3tFcXj.js +120 -0
  401. package/workframe-ui/public/assets/treeView-BLDUP644-DSyUCKLY.js +1 -0
  402. package/workframe-ui/public/assets/treemap-LRROVOQU-CEZaNh5Y.js +1 -0
  403. package/workframe-ui/public/assets/vennDiagram-CIIHVFJN-CD-Vc9NF.js +34 -0
  404. package/workframe-ui/public/assets/wardley-L42UT6IY-Drq5w1Mc.js +1 -0
  405. package/workframe-ui/public/assets/wardleyDiagram-YWT4CUSO-DouXDJoF.js +78 -0
  406. package/workframe-ui/public/assets/warren-DIH7UKMY.png +0 -0
  407. package/workframe-ui/public/assets/woz-D2yleG-V.png +0 -0
  408. package/workframe-ui/public/assets/xychartDiagram-2RQKCTM6-DDf_Lol5.js +7 -0
  409. package/workframe-ui/public/assets/zaha-wersOEq9.png +0 -0
  410. package/workframe-ui/public/favicon.ico +0 -0
  411. package/workframe-ui/public/favicon.svg +7 -0
  412. package/workframe-ui/public/icons.svg +24 -0
  413. package/workframe-ui/public/index.html +50 -0
  414. package/workframe-ui/public/manifest.webmanifest +18 -0
  415. 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
+ });