create-workframe 0.1.0 → 0.1.1

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 (206) hide show
  1. package/LICENSE +201 -201
  2. package/NOTICE +12 -12
  3. package/README.md +8 -92
  4. package/SECURITY.md +40 -40
  5. package/bin/workframe.js +329 -329
  6. package/docs/workspace-instructions/WORKFRAME_ONBOARDING.md +1 -1
  7. package/docs/workspace-instructions/WORKFRAME_ROUTING.md +8 -8
  8. package/package.json +3 -6
  9. package/profiles/architect/AGENTS.md +29 -29
  10. package/profiles/architect/SOUL.md +2 -2
  11. package/profiles/architect/skills/devops/kanban-worker/SKILL.md +27 -27
  12. package/profiles/designer/AGENTS.md +26 -26
  13. package/profiles/designer/skills/devops/kanban-worker/SKILL.md +27 -27
  14. package/profiles/dev/AGENTS.md +28 -28
  15. package/profiles/dev/skills/devops/kanban-worker/SKILL.md +27 -27
  16. package/profiles/docs/AGENTS.md +27 -27
  17. package/profiles/docs/skills/devops/kanban-worker/SKILL.md +27 -27
  18. package/profiles/research/AGENTS.md +26 -26
  19. package/profiles/research/skills/devops/kanban-worker/SKILL.md +27 -27
  20. package/profiles/visionary/AGENTS.md +25 -25
  21. package/profiles/visionary/skills/devops/kanban-worker/SKILL.md +27 -27
  22. package/profiles/workframe-agent/AGENTS.md +37 -37
  23. package/profiles/workframe-agent/skills/devops/botfather/SKILL.md +85 -85
  24. package/profiles/workframe-agent/skills/devops/kanban-handoff-pattern/SKILL.md +58 -58
  25. package/profiles/workframe-agent/skills/devops/workframe-cohort/SKILL.md +54 -54
  26. package/rules/workspace-README.md +5 -5
  27. package/scripts/apply-update-hermes.sh +17 -17
  28. package/scripts/apply-update-workframe.sh +77 -77
  29. package/scripts/bootstrap-workspace-link.sh +8 -8
  30. package/scripts/bundle-workframe-ui.mjs +3 -3
  31. package/scripts/compose-docker-host.sh +37 -37
  32. package/scripts/ensure-compose-host-paths.mjs +51 -51
  33. package/scripts/fix-zk-encryption-key.sh +35 -35
  34. package/scripts/lib/install-identity.mjs +212 -212
  35. package/scripts/restart-gateway-hermes.sh +12 -12
  36. package/scripts/set-compose-public-url.mjs +92 -92
  37. package/scripts/setup-stack-secrets.sh +50 -50
  38. package/scripts/sync-canonical-to-package.mjs +8 -7
  39. package/scripts/verify-public-deploy.sh +105 -105
  40. package/shared/WORKFRAME_AGENT_LIBRARY.md +17 -17
  41. package/shared/WORKFRAME_AGENT_OPERATIONS.md +15 -15
  42. package/shared/WORKFRAME_AGENT_PACKS.json +18 -18
  43. package/shared/WORKFRAME_AGENT_PACKS.yaml +8 -8
  44. package/shared/WORKFRAME_SKILL_CURATION.md +4 -4
  45. package/workframe-api/README.md +28 -28
  46. package/workframe-api/action_proxy.py +131 -131
  47. package/workframe-api/auth_rate_limit.py +49 -49
  48. package/workframe-api/credential_vault.py +445 -445
  49. package/workframe-api/data/avatar-catalog.json +41 -41
  50. package/workframe-api/email_sender.py +220 -220
  51. package/workframe-api/google_auth.py +90 -90
  52. package/workframe-api/install_api.py +359 -359
  53. package/workframe-api/internal_proxy_auth.py +150 -150
  54. package/workframe-api/llm_proxy.py +277 -277
  55. package/workframe-api/oidc_jwt.py +108 -108
  56. package/workframe-api/package.json +12 -13
  57. package/workframe-api/public/assets/index-DPXu_lGn.css +1 -1
  58. package/workframe-api/public/assets/index-DYnLrCZZ.js +8 -8
  59. package/workframe-api/requirements.txt +2 -2
  60. package/workframe-api/site_meta.py +271 -271
  61. package/workframe-api/stack_config.py +427 -427
  62. package/workframe-api/time-bind-chat.py +99 -99
  63. package/workframe-api/turn_credentials.py +226 -226
  64. package/workframe-api/updates.py +417 -417
  65. package/workframe-api/vault_kek.py +159 -159
  66. package/workframe-api/zk_auth.py +633 -633
  67. package/workframe-supervisor/Dockerfile +11 -11
  68. package/workframe-supervisor/server.py +787 -787
  69. package/workframe-ui/docker/nginx.conf +85 -85
  70. package/workframe-ui/public/assets/{arc-CBDYvkAF.js → arc-COAT3laO.js} +1 -1
  71. package/workframe-ui/public/assets/architecture-7EHR7CIX-DUyH3hWG.js +1 -0
  72. package/workframe-ui/public/assets/{architectureDiagram-3BPJPVTR-XnBRKeW0.js → architectureDiagram-3BPJPVTR-BFjWV24l.js} +1 -1
  73. package/workframe-ui/public/assets/{blockDiagram-GPEHLZMM-VYHUfVhd.js → blockDiagram-GPEHLZMM-DSQLPfrj.js} +1 -1
  74. package/workframe-ui/public/assets/{c4Diagram-AAUBKEIU-BTjUcJpm.js → c4Diagram-AAUBKEIU-DKEHv1t2.js} +1 -1
  75. package/workframe-ui/public/assets/channel-g7r_RGaY.js +1 -0
  76. package/workframe-ui/public/assets/{chunk-2J33WTMH-w7uu7R-b.js → chunk-2J33WTMH-DHZg-DUi.js} +1 -1
  77. package/workframe-ui/public/assets/{chunk-3OPIFGDE-Cb9LtnDX.js → chunk-3OPIFGDE-BB-OYTfp.js} +1 -1
  78. package/workframe-ui/public/assets/{chunk-4BX2VUAB-DiQ-qCwH.js → chunk-4BX2VUAB-C93q0YIm.js} +1 -1
  79. package/workframe-ui/public/assets/{chunk-55IACEB6-C-mLFr7z.js → chunk-55IACEB6-MAYniqik.js} +1 -1
  80. package/workframe-ui/public/assets/{chunk-5ZQYHXKU-DOesfiCI.js → chunk-5ZQYHXKU-ChgN6YJs.js} +1 -1
  81. package/workframe-ui/public/assets/{chunk-727SXJPM-BJ3oBZuz.js → chunk-727SXJPM-B_FYwdAv.js} +1 -1
  82. package/workframe-ui/public/assets/{chunk-AQP2D5EJ-CCA6xpGs.js → chunk-AQP2D5EJ-1_Hw_h1A.js} +1 -1
  83. package/workframe-ui/public/assets/{chunk-BSJP7CBP-a0cMNFb2.js → chunk-BSJP7CBP-CFiDQ1Rv.js} +1 -1
  84. package/workframe-ui/public/assets/{chunk-CSCIHK7Q-kuqN8EIY.js → chunk-CSCIHK7Q-DZ9UMTlB.js} +1 -1
  85. package/workframe-ui/public/assets/{chunk-FMBD7UC4-DyPgYHCg.js → chunk-FMBD7UC4-DlMlyFgw.js} +1 -1
  86. package/workframe-ui/public/assets/{chunk-KSCS5N6A-CdUuvR0V.js → chunk-KSCS5N6A-DHXtQ_Hf.js} +1 -1
  87. package/workframe-ui/public/assets/{chunk-L5ZTLDWV-Dq9NoWmK.js → chunk-L5ZTLDWV-CuQzg-QG.js} +1 -1
  88. package/workframe-ui/public/assets/{chunk-LZXEDZCA-p74rddlO.js → chunk-LZXEDZCA-BHzjzCGg.js} +2 -2
  89. package/workframe-ui/public/assets/{chunk-ND2GUHAM-DBD2u1Gz.js → chunk-ND2GUHAM-DHXx05n2.js} +1 -1
  90. package/workframe-ui/public/assets/{chunk-NZK2D7GU-BeIeYFnd.js → chunk-NZK2D7GU-CV5pmDM_.js} +1 -1
  91. package/workframe-ui/public/assets/{chunk-O5CBEL6O-ClHc56ib.js → chunk-O5CBEL6O-6tkCHxsV.js} +1 -1
  92. package/workframe-ui/public/assets/chunk-QZHKN3VN-C5UQehWY.js +1 -0
  93. package/workframe-ui/public/assets/chunk-WU5MYG2G-DhWllrI8.js +1 -0
  94. package/workframe-ui/public/assets/{chunk-XPW4576I-EFr8R_1p.js → chunk-XPW4576I-BClwIiCp.js} +1 -1
  95. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-BBM_8T8E.js +1 -0
  96. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-BBM_8T8E.js +1 -0
  97. package/workframe-ui/public/assets/{cose-bilkent-S5V4N54A-C7aPBODd.js → cose-bilkent-S5V4N54A-DOrGV6DQ.js} +1 -1
  98. package/workframe-ui/public/assets/{dagre-BM42HDAG-BdU1Rv-H.js → dagre-BM42HDAG-DXTPvJkX.js} +1 -1
  99. package/workframe-ui/public/assets/{diagram-2AECGRRQ-DWowSo85.js → diagram-2AECGRRQ-xX_v-pbf.js} +1 -1
  100. package/workframe-ui/public/assets/{diagram-5GNKFQAL-MnxBbceO.js → diagram-5GNKFQAL-Cd2pXbBe.js} +1 -1
  101. package/workframe-ui/public/assets/{diagram-KO2AKTUF-DQaLRXFf.js → diagram-KO2AKTUF-Df3XvUtk.js} +1 -1
  102. package/workframe-ui/public/assets/{diagram-LMA3HP47-CQaBud9k.js → diagram-LMA3HP47-CsijIPaD.js} +1 -1
  103. package/workframe-ui/public/assets/{diagram-OG6HWLK6-D8bAXbY9.js → diagram-OG6HWLK6-aq5fmfHd.js} +1 -1
  104. package/workframe-ui/public/assets/{dist-DGpTLHr_.js → dist-D1c0mkbB.js} +1 -1
  105. package/workframe-ui/public/assets/{erDiagram-TEJ5UH35-1E-xSvBK.js → erDiagram-TEJ5UH35-DnFysVRY.js} +1 -1
  106. package/workframe-ui/public/assets/eventmodeling-FCH6USID-Ci8mdb44.js +1 -0
  107. package/workframe-ui/public/assets/{flowDiagram-I6XJVG4X-CgOVD5hu.js → flowDiagram-I6XJVG4X-C6Ebi3su.js} +1 -1
  108. package/workframe-ui/public/assets/{ganttDiagram-6RSMTGT7-JFYAIauo.js → ganttDiagram-6RSMTGT7-BQXQtUpa.js} +1 -1
  109. package/workframe-ui/public/assets/{gitGraph-WXDBUCRP-B9REenIl.js → gitGraph-WXDBUCRP-Dt0zIs_M.js} +1 -1
  110. package/workframe-ui/public/assets/{gitGraphDiagram-PVQCEYII-BQ7NcMSn.js → gitGraphDiagram-PVQCEYII-BF8gHzRn.js} +1 -1
  111. package/workframe-ui/public/assets/index-DpoUZAxh.css +1 -0
  112. package/workframe-ui/public/assets/{index-Dnw6vjqb.js → index-lRpzpNPT.js} +2 -2
  113. package/workframe-ui/public/assets/{info-J43DQDTF-CL6-eTjH.js → info-J43DQDTF-CSmszQJT.js} +1 -1
  114. package/workframe-ui/public/assets/{infoDiagram-5YYISTIA-LJTODW4W.js → infoDiagram-5YYISTIA-CVTKGW6p.js} +1 -1
  115. package/workframe-ui/public/assets/{ishikawaDiagram-YF4QCWOH-bchrQVuo.js → ishikawaDiagram-YF4QCWOH-Z8pT09Lv.js} +1 -1
  116. package/workframe-ui/public/assets/{journeyDiagram-JHISSGLW-DkrvYuxP.js → journeyDiagram-JHISSGLW-r3wD68_T.js} +1 -1
  117. package/workframe-ui/public/assets/{kanban-definition-UN3LZRKU-DFRbj0IG.js → kanban-definition-UN3LZRKU-Il8VglqN.js} +1 -1
  118. package/workframe-ui/public/assets/{line-Vd48P7-O.js → line-oyjpfz2A.js} +1 -1
  119. package/workframe-ui/public/assets/{linear-Ckizh2G7.js → linear-Cf7p5tVp.js} +1 -1
  120. package/workframe-ui/public/assets/{mermaid-parser.core-Bkimsnqj.js → mermaid-parser.core-YmbZ-AfY.js} +2 -2
  121. package/workframe-ui/public/assets/{mermaid.core-x0TvVuPo.js → mermaid.core-BFdCAqCo.js} +3 -3
  122. package/workframe-ui/public/assets/{mindmap-definition-RKZ34NQL-6ykAFPEz.js → mindmap-definition-RKZ34NQL-Cy2iCtEl.js} +1 -1
  123. package/workframe-ui/public/assets/{packet-YPE3B663-Dw3xgMDt.js → packet-YPE3B663-DwOBZL6K.js} +1 -1
  124. package/workframe-ui/public/assets/{pie-LRSECV5Y-DATysawG.js → pie-LRSECV5Y-04PPhnKK.js} +1 -1
  125. package/workframe-ui/public/assets/{pieDiagram-4H26LBE5-SJKD1S0S.js → pieDiagram-4H26LBE5-LxIpgHqi.js} +1 -1
  126. package/workframe-ui/public/assets/{quadrantDiagram-W4KKPZXB-BrYDZX8q.js → quadrantDiagram-W4KKPZXB-0nBYfYm4.js} +1 -1
  127. package/workframe-ui/public/assets/{radar-GUYGQ44K-BmWYPCds.js → radar-GUYGQ44K-D2-vBqps.js} +1 -1
  128. package/workframe-ui/public/assets/{requirementDiagram-4Y6WPE33-DwL9Mc8e.js → requirementDiagram-4Y6WPE33-DbuU0nlu.js} +1 -1
  129. package/workframe-ui/public/assets/{sankeyDiagram-5OEKKPKP-DYIFsL8h.js → sankeyDiagram-5OEKKPKP-B2hQ6B2x.js} +1 -1
  130. package/workframe-ui/public/assets/{sequenceDiagram-3UESZ5HK-0-FPkFk8.js → sequenceDiagram-3UESZ5HK-BBrU30e1.js} +1 -1
  131. package/workframe-ui/public/assets/{src-B_od6b6h.js → src-BJEDmV70.js} +1 -1
  132. package/workframe-ui/public/assets/{stateDiagram-AJRCARHV-BQCiBk6u.js → stateDiagram-AJRCARHV-7FGO4kkH.js} +1 -1
  133. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-DLTSizMg.js +1 -0
  134. package/workframe-ui/public/assets/{timeline-definition-PNZ67QCA-DS3tFcXj.js → timeline-definition-PNZ67QCA-ptDm4rCN.js} +1 -1
  135. package/workframe-ui/public/assets/{treeView-BLDUP644-DSyUCKLY.js → treeView-BLDUP644-CS6Z-0q8.js} +1 -1
  136. package/workframe-ui/public/assets/{treemap-LRROVOQU-CEZaNh5Y.js → treemap-LRROVOQU-DqV4Y2VA.js} +1 -1
  137. package/workframe-ui/public/assets/{vennDiagram-CIIHVFJN-CD-Vc9NF.js → vennDiagram-CIIHVFJN-C0UrZJYt.js} +1 -1
  138. package/workframe-ui/public/assets/{wardley-L42UT6IY-Drq5w1Mc.js → wardley-L42UT6IY-bNDN3_Sa.js} +1 -1
  139. package/workframe-ui/public/assets/{wardleyDiagram-YWT4CUSO-DouXDJoF.js → wardleyDiagram-YWT4CUSO-jWiJsefM.js} +1 -1
  140. package/workframe-ui/public/assets/{xychartDiagram-2RQKCTM6-DDf_Lol5.js → xychartDiagram-2RQKCTM6-Dsh_fLCy.js} +1 -1
  141. package/workframe-ui/public/favicon.svg +7 -7
  142. package/workframe-ui/public/index.html +50 -50
  143. package/workframe-ui/public/workframe-config.json +3 -3
  144. package/scripts/security_audit.py +0 -156
  145. package/scripts/test-scaffold.mjs +0 -390
  146. package/workframe-api/tests/__init__.py +0 -0
  147. package/workframe-api/tests/db_setup.py +0 -13
  148. package/workframe-api/tests/test_admin_updates_gated.py +0 -30
  149. package/workframe-api/tests/test_agent_dm_bootstrap.py +0 -196
  150. package/workframe-api/tests/test_agent_profile_sync.py +0 -76
  151. package/workframe-api/tests/test_auth_email.py +0 -222
  152. package/workframe-api/tests/test_auth_hole_fix_selfcheck.py +0 -99
  153. package/workframe-api/tests/test_auth_rate_limit.py +0 -19
  154. package/workframe-api/tests/test_avatar_resolve.py +0 -77
  155. package/workframe-api/tests/test_child_soul_template.py +0 -71
  156. package/workframe-api/tests/test_credential_canary.py +0 -135
  157. package/workframe-api/tests/test_credential_isolation.py +0 -448
  158. package/workframe-api/tests/test_credential_resolution.py +0 -206
  159. package/workframe-api/tests/test_device_oauth.py +0 -108
  160. package/workframe-api/tests/test_doctor_repair.py +0 -103
  161. package/workframe-api/tests/test_ensure_profile_api.py +0 -77
  162. package/workframe-api/tests/test_gateway_compose_security.py +0 -136
  163. package/workframe-api/tests/test_install_secure_host.py +0 -39
  164. package/workframe-api/tests/test_internal_proxy_auth.py +0 -125
  165. package/workframe-api/tests/test_invite_runtime_bootstrap.py +0 -72
  166. package/workframe-api/tests/test_kanban_delegation.py +0 -185
  167. package/workframe-api/tests/test_llm_proxy.py +0 -155
  168. package/workframe-api/tests/test_login_access_policy.py +0 -183
  169. package/workframe-api/tests/test_mvp_model_bootstrap.py +0 -75
  170. package/workframe-api/tests/test_onboarding_bootstrap.py +0 -248
  171. package/workframe-api/tests/test_platform_auth.py +0 -47
  172. package/workframe-api/tests/test_profile_config_path.py +0 -56
  173. package/workframe-api/tests/test_profile_config_yaml_repair.py +0 -63
  174. package/workframe-api/tests/test_profile_create.py +0 -72
  175. package/workframe-api/tests/test_profile_identity_overlay.py +0 -61
  176. package/workframe-api/tests/test_profile_install_health.py +0 -45
  177. package/workframe-api/tests/test_profile_secret_policy.py +0 -57
  178. package/workframe-api/tests/test_profile_workspace_cwd.py +0 -34
  179. package/workframe-api/tests/test_provider_bootstrap.py +0 -75
  180. package/workframe-api/tests/test_provider_connect.py +0 -54
  181. package/workframe-api/tests/test_room_crud.py +0 -192
  182. package/workframe-api/tests/test_room_tenancy.py +0 -701
  183. package/workframe-api/tests/test_runtime_identity_backfill.py +0 -34
  184. package/workframe-api/tests/test_site_meta.py +0 -81
  185. package/workframe-api/tests/test_soul_stub.py +0 -42
  186. package/workframe-api/tests/test_space_member_sync.py +0 -99
  187. package/workframe-api/tests/test_stripe_stack_config.py +0 -37
  188. package/workframe-api/tests/test_supervisor_lifecycle.py +0 -52
  189. package/workframe-api/tests/test_turn_credential_vault.py +0 -125
  190. package/workframe-api/tests/test_updates.py +0 -176
  191. package/workframe-api/tests/test_user_cohort.py +0 -113
  192. package/workframe-api/tests/test_vault_envelope.py +0 -110
  193. package/workframe-api/tests/test_workspace_members.py +0 -183
  194. package/workframe-api/tests/test_workspace_messaging_sync.py +0 -125
  195. package/workframe-api/tests/test_workspace_provider_list.py +0 -57
  196. package/workframe-supervisor/tests/test_exec_guard.py +0 -42
  197. package/workframe-supervisor/tests/test_server_import.py +0 -21
  198. package/workframe-ui/public/assets/architecture-7EHR7CIX-CtbQKTuT.js +0 -1
  199. package/workframe-ui/public/assets/channel-Dy4Z4-jn.js +0 -1
  200. package/workframe-ui/public/assets/chunk-QZHKN3VN-CtBEchFK.js +0 -1
  201. package/workframe-ui/public/assets/chunk-WU5MYG2G-B9pBtriN.js +0 -1
  202. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-BMAEA8jI.js +0 -1
  203. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-BMAEA8jI.js +0 -1
  204. package/workframe-ui/public/assets/eventmodeling-FCH6USID-D75cstNT.js +0 -1
  205. package/workframe-ui/public/assets/index-DpAGxump.css +0 -1
  206. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-B89jAMFF.js +0 -1
@@ -1,50 +1,50 @@
1
- <!doctype html>
2
- <html lang="en" data-theme="dark">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
6
- <meta name="theme-color" content="#0A0A0F" />
7
- <meta name="description" content="Workframe — project chat and workspace" />
8
- <meta property="og:type" content="website" />
9
- <meta property="og:site_name" content="Workframe" />
10
- <meta property="og:title" content="Workframe" />
11
- <meta property="og:description" content="Project chat and workspace for Hermes agent crews" />
12
- <meta property="og:image" content="/assets/branding/og-default.png" />
13
- <meta name="twitter:card" content="summary_large_image" />
14
- <meta name="twitter:title" content="Workframe" />
15
- <meta name="twitter:description" content="Project chat and workspace for Hermes agent crews" />
16
- <meta name="twitter:image" content="/assets/branding/og-default.png" />
17
- <meta name="mobile-web-app-capable" content="yes" />
18
- <meta name="apple-mobile-web-app-capable" content="yes" />
19
- <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
20
- <link rel="icon" type="image/svg+xml" href="./favicon.svg" />
21
- <link rel="manifest" href="./manifest.webmanifest" />
22
- <link rel="preconnect" href="https://fonts.googleapis.com" />
23
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
24
- <link
25
- href="https://fonts.googleapis.com/css2?family=Inter+Tight:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap"
26
- rel="stylesheet"
27
- />
28
- <title>Workframe</title>
29
- <script>
30
- ;(() => {
31
- try {
32
- const stored = localStorage.getItem('wf-theme')
33
- const theme =
34
- stored === 'light' || stored === 'dark'
35
- ? stored
36
- : window.matchMedia('(prefers-color-scheme: dark)').matches
37
- ? 'dark'
38
- : 'light'
39
- document.documentElement.dataset.theme = theme
40
- document.documentElement.style.colorScheme = theme
41
- } catch (_) {}
42
- })()
43
- </script>
44
- <script type="module" crossorigin src="./assets/index-Dnw6vjqb.js"></script>
45
- <link rel="stylesheet" crossorigin href="./assets/index-DpAGxump.css">
46
- </head>
47
- <body>
48
- <div id="root"></div>
49
- </body>
50
- </html>
1
+ <!doctype html>
2
+ <html lang="en" data-theme="dark">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
6
+ <meta name="theme-color" content="#0A0A0F" />
7
+ <meta name="description" content="Workframe — project chat and workspace" />
8
+ <meta property="og:type" content="website" />
9
+ <meta property="og:site_name" content="Workframe" />
10
+ <meta property="og:title" content="Workframe" />
11
+ <meta property="og:description" content="Project chat and workspace for Hermes agent crews" />
12
+ <meta property="og:image" content="/assets/branding/og-default.png" />
13
+ <meta name="twitter:card" content="summary_large_image" />
14
+ <meta name="twitter:title" content="Workframe" />
15
+ <meta name="twitter:description" content="Project chat and workspace for Hermes agent crews" />
16
+ <meta name="twitter:image" content="/assets/branding/og-default.png" />
17
+ <meta name="mobile-web-app-capable" content="yes" />
18
+ <meta name="apple-mobile-web-app-capable" content="yes" />
19
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
20
+ <link rel="icon" type="image/svg+xml" href="./favicon.svg" />
21
+ <link rel="manifest" href="./manifest.webmanifest" />
22
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
23
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
24
+ <link
25
+ href="https://fonts.googleapis.com/css2?family=Inter+Tight:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap"
26
+ rel="stylesheet"
27
+ />
28
+ <title>Workframe</title>
29
+ <script>
30
+ ;(() => {
31
+ try {
32
+ const stored = localStorage.getItem('wf-theme')
33
+ const theme =
34
+ stored === 'light' || stored === 'dark'
35
+ ? stored
36
+ : window.matchMedia('(prefers-color-scheme: dark)').matches
37
+ ? 'dark'
38
+ : 'light'
39
+ document.documentElement.dataset.theme = theme
40
+ document.documentElement.style.colorScheme = theme
41
+ } catch (_) {}
42
+ })()
43
+ </script>
44
+ <script type="module" crossorigin src="./assets/index-lRpzpNPT.js"></script>
45
+ <link rel="stylesheet" crossorigin href="./assets/index-DpoUZAxh.css">
46
+ </head>
47
+ <body>
48
+ <div id="root"></div>
49
+ </body>
50
+ </html>
@@ -1,4 +1,4 @@
1
- {
2
- "project_name": "Workframe",
3
- "native_profile": "workframe-agent"
1
+ {
2
+ "project_name": "Workframe",
3
+ "native_profile": "workframe-agent"
4
4
  }
@@ -1,156 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Static security hygiene scan for the Workframe package source."""
3
-
4
- from __future__ import annotations
5
- import json
6
- import re
7
- from pathlib import Path
8
-
9
- ROOT = Path(__file__).resolve().parents[1]
10
-
11
- SECRET_PATTERNS = {
12
- 'aws_access_key': re.compile(r'AKIA[0-9A-Z]{16}'),
13
- 'github_pat': re.compile(r'ghp_[A-Za-z0-9]{36}'),
14
- 'slack_token': re.compile(r'xox[baprs]-[A-Za-z0-9-]{10,}'),
15
- 'telegram_bot_token': re.compile(r'\b\d{8,10}:[A-Za-z0-9_-]{35}\b'),
16
- 'discord_token': re.compile(r'[MN][A-Za-z\d]{23}\.[\w-]{6}\.[\w-]{27}'),
17
- 'private_key': re.compile(r'-----BEGIN (?:RSA|EC|OPENSSH|DSA|PGP|PRIVATE) KEY-----'),
18
- 'generic_secret_assignment': re.compile(r'(?i)(api[_-]?key|token|secret)\s*[:=]\s*["\']?[A-Za-z0-9_\-]{16,}'),
19
- }
20
-
21
- IGNORE_PARTS = {'.venv', '__pycache__', '.pytest_cache', 'node_modules'}
22
-
23
- REQUIRED_IGNORE_PATTERNS = [
24
- 'Agents/', '.env', '*.db', 'logs/', 'memories/', 'sessions/', 'kanban/',
25
- ]
26
-
27
- INSTANCE_ARTIFACT_PATTERNS = [
28
- re.compile(r'\buser_id\b', re.IGNORECASE),
29
- re.compile(r'\bmessage_id\b', re.IGNORECASE),
30
- re.compile(r'\bgateway_state\b', re.IGNORECASE),
31
- re.compile(r'\bkanban\.db\b', re.IGNORECASE),
32
- ]
33
-
34
- # Project-specific / PII-adjacent names — must not appear in publishable templates
35
- BANNED_TERM_PATTERNS = [
36
- ('glitch', re.compile(r'\bglitch\b', re.IGNORECASE)),
37
- ('zeta', re.compile(r'\bzeta\b', re.IGNORECASE)),
38
- ('alan', re.compile(r'\balan\b', re.IGNORECASE)),
39
- ]
40
-
41
- # Skip docs that document the ban itself
42
- BANNED_TERM_SKIP = {'security_audit.py'}
43
-
44
-
45
- def iter_files(root: Path):
46
- for p in root.rglob('*'):
47
- if not p.is_file():
48
- continue
49
- if any(part in IGNORE_PARTS for part in p.parts):
50
- continue
51
- yield p
52
-
53
-
54
- def scan_secrets():
55
- findings = []
56
- for p in iter_files(ROOT):
57
- rel = p.relative_to(ROOT).as_posix()
58
- text = p.read_text(errors='ignore')
59
- for name, pattern in SECRET_PATTERNS.items():
60
- for m in pattern.finditer(text):
61
- findings.append({
62
- 'severity': 'high',
63
- 'type': 'secret_pattern',
64
- 'rule': name,
65
- 'file': rel,
66
- 'excerpt': m.group(0)[:80],
67
- })
68
- return findings
69
-
70
-
71
- def scan_gitignore_coverage():
72
- findings = []
73
- gi = ROOT / '.gitignore'
74
- if not gi.exists():
75
- findings.append({
76
- 'severity': 'high',
77
- 'type': 'missing_gitignore',
78
- 'file': '.gitignore',
79
- 'rule': 'required file missing',
80
- })
81
- return findings
82
-
83
- content = gi.read_text(errors='ignore')
84
- for req in REQUIRED_IGNORE_PATTERNS:
85
- if req not in content:
86
- findings.append({
87
- 'severity': 'medium',
88
- 'type': 'gitignore_gap',
89
- 'file': '.gitignore',
90
- 'rule': f'missing pattern {req}',
91
- })
92
- return findings
93
-
94
-
95
- def scan_instance_artifacts():
96
- findings = []
97
- for p in iter_files(ROOT):
98
- if p.name in {'.gitignore', '.dockerignore', '.npmignore'}:
99
- continue
100
- rel = p.relative_to(ROOT).as_posix()
101
- text = p.read_text(errors='ignore')
102
- for pattern in INSTANCE_ARTIFACT_PATTERNS:
103
- for m in pattern.finditer(text):
104
- findings.append({
105
- 'severity': 'medium',
106
- 'type': 'instance_artifact_reference',
107
- 'file': rel,
108
- 'rule': pattern.pattern,
109
- 'excerpt': m.group(0),
110
- })
111
- return findings
112
-
113
-
114
- def scan_banned_terms():
115
- findings = []
116
- for p in iter_files(ROOT):
117
- rel = p.relative_to(ROOT).as_posix()
118
- if p.name in BANNED_TERM_SKIP:
119
- continue
120
- text = p.read_text(errors='ignore')
121
- for term, pattern in BANNED_TERM_PATTERNS:
122
- for m in pattern.finditer(text):
123
- findings.append({
124
- 'severity': 'medium',
125
- 'type': 'banned_term',
126
- 'rule': term,
127
- 'file': rel,
128
- 'excerpt': m.group(0),
129
- })
130
- return findings
131
-
132
-
133
- def main():
134
- findings = []
135
- findings.extend(scan_secrets())
136
- findings.extend(scan_gitignore_coverage())
137
- findings.extend(scan_instance_artifacts())
138
- findings.extend(scan_banned_terms())
139
-
140
- high = [f for f in findings if f['severity'] == 'high']
141
- medium = [f for f in findings if f['severity'] == 'medium']
142
-
143
- report = {
144
- 'root': str(ROOT),
145
- 'findings_total': len(findings),
146
- 'high': len(high),
147
- 'medium': len(medium),
148
- 'findings': findings,
149
- }
150
-
151
- print(json.dumps(report, indent=2))
152
- raise SystemExit(1 if high else 0)
153
-
154
-
155
- if __name__ == '__main__':
156
- main()
@@ -1,390 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Dev/CI smoke test: generate all packs and verify required scaffold files.
4
- * Canonical installer: bin/create-workframe.js
5
- */
6
- import fs from 'node:fs';
7
- import os from 'node:os';
8
- import path from 'node:path';
9
- import { spawnSync } from 'node:child_process';
10
- import { fileURLToPath } from 'node:url';
11
-
12
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
- const PKG_ROOT = path.resolve(__dirname, '..');
14
- const SYNC = path.join(PKG_ROOT, 'scripts', 'sync-canonical-to-package.mjs');
15
- const CLI = path.join(PKG_ROOT, 'bin', 'create-workframe.js');
16
-
17
- const syncRes = spawnSync(process.execPath, [SYNC], { encoding: 'utf8', cwd: PKG_ROOT });
18
- if (syncRes.status !== 0) {
19
- console.error(syncRes.stderr || syncRes.stdout);
20
- process.exit(syncRes.status ?? 1);
21
- }
22
- const PACKS = ['native', 'core', 'product', 'engineering', 'vanilla'];
23
- const REQUIRED = [
24
- 'Agents/.gitkeep',
25
- 'Files/AGENTS.md',
26
- 'Files/.hermes.md',
27
- 'Files/README.md',
28
- 'SETUP.md',
29
- 'README.md',
30
- 'docker-compose.yml',
31
- '.env.example',
32
- '.env',
33
- 'docs/PUBLIC_DEPLOY.md',
34
- 'scripts/bootstrap-native.sh',
35
- 'scripts/bootstrap-native.ps1',
36
- 'scripts/bootstrap-profiles.sh',
37
- 'scripts/bootstrap-profiles.ps1',
38
- 'scripts/add-profile.sh',
39
- 'scripts/add-profile.ps1',
40
- 'scripts/open-setup.sh',
41
- 'scripts/open-setup.ps1',
42
- 'scripts/install.sh',
43
- 'scripts/install.ps1',
44
- 'scripts/start-install.sh',
45
- 'scripts/start-install.ps1',
46
- 'scripts/launch-install.ps1',
47
- 'scripts/launch-install.sh',
48
- 'scripts/open-chat.ps1',
49
- 'scripts/open-chat.sh',
50
- 'scripts/open-workframe-api.ps1',
51
- 'scripts/open-workframe-api.sh',
52
- 'scripts/open-workframe-ui.ps1',
53
- 'scripts/open-workframe-ui.sh',
54
- 'scripts/update-hermes.ps1',
55
- 'scripts/update-hermes.sh',
56
- 'scripts/setup-stack-secrets.sh',
57
- 'scripts/agent-lifecycle.mjs',
58
- 'scripts/lib/workframe-registry.mjs',
59
- 'scripts/chat.sh',
60
- 'scripts/chat.ps1',
61
- 'scripts/verify-bootstrap.sh',
62
- 'scripts/verify-bootstrap.ps1',
63
- 'scripts/seed/agent-template/SOUL.md',
64
- 'workframe-api/server.py',
65
- 'workframe-api/public/index.html',
66
- 'workframe-api/data/.gitkeep',
67
- 'workframe-supervisor/server.py',
68
- 'workframe-supervisor/Dockerfile',
69
- 'workframe-ui/public/index.html',
70
- 'workframe-ui/public/workframe-config.json',
71
- 'workframe-ui/docker/nginx.conf',
72
- 'docker/dashboard-proxy.conf',
73
- 'workframe-manifest.json',
74
- ];
75
-
76
- function slugify(name) {
77
- return name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'workframe';
78
- }
79
-
80
- function nativeProfileSlug(name) {
81
- return `${slugify(name)}-agent`;
82
- }
83
-
84
- function nativeAgentName(name) {
85
- return `${name} Agent`;
86
- }
87
-
88
- function dockerStack(name) {
89
- return slugify(name);
90
- }
91
-
92
- function fail(msg) {
93
- console.error(`FAIL: ${msg}`);
94
- process.exit(1);
95
- }
96
-
97
- function assertFile(root, rel) {
98
- const file = path.join(root, rel);
99
- if (!fs.existsSync(file)) fail(`missing ${rel} in ${root}`);
100
- }
101
-
102
- const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'wf-test-'));
103
- console.log(`Scaffold test dir: ${tmp}`);
104
-
105
- for (const pack of PACKS) {
106
- const name = `Test_${pack}`;
107
- const res = spawnSync(process.execPath, [CLI, name, '--pack', pack, '--out', tmp, '--ci', '--force'], {
108
- encoding: 'utf8',
109
- });
110
- if (res.status !== 0) fail(`pack ${pack}: ${res.stderr || res.stdout}`);
111
- const root = path.join(tmp, name);
112
- for (const rel of REQUIRED) assertFile(root, rel);
113
-
114
- const manifest = JSON.parse(fs.readFileSync(path.join(root, 'workframe-manifest.json'), 'utf8'));
115
- const expectedSlug = nativeProfileSlug(name);
116
- const expectedName = nativeAgentName(name);
117
- const expectedStack = dockerStack(name);
118
-
119
- if (manifest.native_agent?.profile_slug !== expectedSlug) {
120
- fail(`pack ${pack}: expected native slug ${expectedSlug}, got ${manifest.native_agent?.profile_slug}`);
121
- }
122
- if (manifest.native_agent?.display_name !== expectedName) {
123
- fail(`pack ${pack}: expected native name ${expectedName}, got ${manifest.native_agent?.display_name}`);
124
- }
125
- if (!manifest.package_version) fail(`pack ${pack}: manifest missing package_version`);
126
- if (manifest.docker?.stack !== expectedStack) {
127
- fail(`pack ${pack}: expected docker stack ${expectedStack}, got ${manifest.docker?.stack}`);
128
- }
129
- if (manifest.docker?.containers?.gateway !== `${expectedStack}-gateway`) {
130
- fail(`pack ${pack}: unexpected gateway container name`);
131
- }
132
- if (manifest.docker?.containers?.workframe !== `${expectedStack}-workframe`) {
133
- fail(`pack ${pack}: unexpected workframe container name`);
134
- }
135
- if (manifest.docker?.containers?.workframe_api !== `${expectedStack}-workframe-api`) {
136
- fail(`pack ${pack}: unexpected workframe-api container name`);
137
- }
138
- if (!manifest.profiles.includes(expectedSlug)) {
139
- fail(`pack ${pack}: profiles missing native slug ${expectedSlug}`);
140
- }
141
- if (pack === 'native') {
142
- if (manifest.profiles.length !== 1 || manifest.profiles[0] !== expectedSlug) {
143
- fail(`pack ${pack}: expected native-only bootstrap profiles, got ${manifest.profiles.join(', ')}`);
144
- }
145
- if (manifest.profiles_installed_after_native_bootstrap?.length !== 1 || manifest.profiles_installed_after_native_bootstrap?.[0] !== expectedSlug) {
146
- fail(`pack ${pack}: expected native-only installed_after_native_bootstrap`);
147
- }
148
- }
149
- if (!manifest.profiles_catalog?.length) fail(`pack ${pack}: manifest missing profiles_catalog`);
150
- if (manifest.bootstrap?.default !== 'native') fail(`pack ${pack}: bootstrap.default should be native`);
151
- if (!manifest.ports?.gateway || !manifest.ports?.dashboard || !manifest.ports?.ui || !manifest.ports?.api) {
152
- fail(`pack ${pack}: manifest missing ports`);
153
- }
154
- if (manifest.layout?.workspace !== 'Files' || manifest.layout?.runtime !== 'Agents') {
155
- fail(`pack ${pack}: manifest layout should stay Files/Agents`);
156
- }
157
-
158
- assertFile(root, `scripts/seed/profiles/${expectedSlug}/SOUL.md`);
159
- assertFile(root, `scripts/seed/profiles/${expectedSlug}/SETUP.md`);
160
-
161
- const nativeSoul = fs.readFileSync(path.join(root, `scripts/seed/profiles/${expectedSlug}/SOUL.md`), 'utf8');
162
- if (!nativeSoul.includes('Setup gate')) fail(`pack ${pack}: native SOUL missing setup gate`);
163
- const nativeSetup = fs.readFileSync(path.join(root, `scripts/seed/profiles/${expectedSlug}/SETUP.md`), 'utf8');
164
- if (!nativeSetup.includes('Credential security')) fail(`pack ${pack}: native SETUP missing credential section`);
165
-
166
- const compose = fs.readFileSync(path.join(root, 'docker-compose.yml'), 'utf8');
167
- if (!compose.includes(`container_name: ${expectedStack}-gateway`)) {
168
- fail(`pack ${pack}: compose missing named gateway container`);
169
- }
170
- if (!compose.includes(`container_name: ${expectedStack}-workframe`)) {
171
- fail(`pack ${pack}: compose missing workframe UI container`);
172
- }
173
- if (!compose.includes(`container_name: ${expectedStack}-workframe-api`)) {
174
- fail(`pack ${pack}: compose missing workframe-api container`);
175
- }
176
- if (!compose.includes(`container_name: ${expectedStack}-workframe-supervisor`)) {
177
- fail(`pack ${pack}: compose missing workframe-supervisor container`);
178
- }
179
- if (!compose.includes('context: ./workframe-supervisor')) {
180
- fail(`pack ${pack}: compose missing workframe-supervisor build context`);
181
- }
182
- if (!compose.includes('WORKFRAME_SUPERVISOR_URL=http://workframe-supervisor:8090')) {
183
- fail(`pack ${pack}: compose missing supervisor URL for API`);
184
- }
185
- if (!compose.includes('internal: true')) {
186
- fail(`pack ${pack}: compose missing internal control network`);
187
- }
188
- const gatewayBlock = compose.split(/^ (?=[a-z])/m).find((block) => block.startsWith('gateway:'));
189
- if (gatewayBlock?.includes('env_file:')) {
190
- fail(`pack ${pack}: gateway must not use env_file (secrets stay off Hermes container)`);
191
- }
192
- if (!compose.includes('workframe-proxy-token:/run/workframe-proxy')) {
193
- fail(`pack ${pack}: compose missing shared proxy token volume`);
194
- }
195
- if (!compose.includes('WORKFRAME_PROXY_TOKEN')) {
196
- fail(`pack ${pack}: compose missing WORKFRAME_PROXY_TOKEN`);
197
- }
198
- if (!compose.includes('gateway run') || !compose.includes(`-p ${expectedSlug}`)) {
199
- fail(`pack ${pack}: compose gateway missing native profile ${expectedSlug}`);
200
- }
201
- if (!gatewayBlock?.includes('./Agents:/opt/data')) {
202
- fail(`pack ${pack}: gateway must mount ./Agents:/opt/data (not host Hermes)`);
203
- }
204
- if (gatewayBlock?.includes('AppData/Local/hermes') || gatewayBlock?.includes('/.hermes')) {
205
- fail(`pack ${pack}: gateway must not bind host Hermes into the stack`);
206
- }
207
- if (!compose.includes('./workframe-api/public:/app/public:ro')) {
208
- fail(`pack ${pack}: compose missing workframe-api public mount`);
209
- }
210
- if (!compose.includes('WORKFRAME_UI_STATIC_DIR:-./workframe-ui/public') && !compose.includes('./workframe-ui/public:/usr/share/nginx/html:ro')) {
211
- fail(`pack ${pack}: compose missing workframe-ui public mount`);
212
- }
213
- if (!compose.includes('./workframe-ui/docker/nginx.conf:/etc/nginx/conf.d/default.conf:ro')) {
214
- fail(`pack ${pack}: compose missing workframe-ui nginx mount`);
215
- }
216
- if (!compose.includes('./docker/dashboard-proxy.conf:/etc/nginx/conf.d/default.conf:ro')) {
217
- fail(`pack ${pack}: compose missing dashboard proxy mount`);
218
- }
219
- if (!compose.includes('./scripts:/opt/install/scripts:ro')) {
220
- fail(`pack ${pack}: docker-compose must mount scripts`);
221
- }
222
- if (!compose.includes('HERMES_DASHBOARD_TUI=1')) {
223
- fail(`pack ${pack}: compose missing dashboard TUI enablement`);
224
- }
225
- if (gatewayBlock?.includes('HERMES_DASHBOARD_INSECURE=1')) {
226
- fail(`pack ${pack}: gateway must use dashboard basic auth, not HERMES_DASHBOARD_INSECURE`);
227
- }
228
- if (!gatewayBlock?.includes('HERMES_DASHBOARD_BASIC_AUTH_USERNAME')) {
229
- fail(`pack ${pack}: gateway missing dashboard basic auth env`);
230
- }
231
- if (compose.includes('mission-control')) {
232
- fail(`pack ${pack}: compose should not include mission-control in the default stack`);
233
- }
234
-
235
- const publicCompose = fs.readFileSync(path.join(root, 'docker-compose.public.yml'), 'utf8');
236
- if (!fs.existsSync(path.join(root, 'docker-compose.host-bindings.yml'))) {
237
- fail(`pack ${pack}: missing docker-compose.host-bindings.yml for supervisor apply`);
238
- }
239
- const hostBindings = fs.readFileSync(path.join(root, 'docker-compose.host-bindings.yml'), 'utf8');
240
- if (!hostBindings.includes('WORKFRAME_HOST_PROJECT_ROOT')) {
241
- fail(`pack ${pack}: host-bindings overlay must use WORKFRAME_HOST_PROJECT_ROOT`);
242
- }
243
- const publicApiBlock = publicCompose.split(/^ (?=[a-z])/m).find((block) => block.startsWith('workframe-api:'));
244
- if (publicApiBlock?.includes('/var/run/docker.sock')) {
245
- fail(`pack ${pack}: docker-compose.public.yml must not mount docker.sock on workframe-api`);
246
- }
247
- if (!publicCompose.includes('workframe-proxy-token:/run/workframe-proxy')) {
248
- fail(`pack ${pack}: docker-compose.public.yml missing proxy token volume on API`);
249
- }
250
-
251
- const envExample = fs.readFileSync(path.join(root, '.env.example'), 'utf8');
252
- if (!envExample.includes('WORKFRAME_PROXY_TOKEN')) {
253
- fail(`pack ${pack}: .env.example missing WORKFRAME_PROXY_TOKEN`);
254
- }
255
- if (!envExample.includes('WORKFRAME_VAULT_KEK')) {
256
- fail(`pack ${pack}: .env.example missing WORKFRAME_VAULT_KEK`);
257
- }
258
-
259
- const nginx = fs.readFileSync(path.join(root, 'workframe-ui/docker/nginx.conf'), 'utf8');
260
- if (nginx.includes('/hermes-profiles/')) {
261
- fail(`pack ${pack}: nginx should not include /hermes-profiles/ locations`);
262
- }
263
-
264
- const wfCfg = JSON.parse(fs.readFileSync(path.join(root, 'workframe-ui/public/workframe-config.json'), 'utf8'));
265
- if (wfCfg.native_profile !== expectedSlug) {
266
- fail(`pack ${pack}: workframe-config native_profile expected ${expectedSlug}, got ${wfCfg.native_profile}`);
267
- }
268
- if (wfCfg.project_name !== name) {
269
- fail(`pack ${pack}: workframe-config project_name expected ${name}, got ${wfCfg.project_name}`);
270
- }
271
-
272
- const workspaceFiles = fs.readdirSync(path.join(root, 'Files')).sort();
273
- const expectedWorkspaceFiles = ['.hermes.md', 'AGENTS.md', 'README.md'];
274
- if (workspaceFiles.join('|') !== expectedWorkspaceFiles.join('|')) {
275
- fail(`pack ${pack}: Files/ should stay lean, got ${workspaceFiles.join(', ')}`);
276
- }
277
-
278
- const agents = fs.readFileSync(path.join(root, 'Files/AGENTS.md'), 'utf8');
279
- if (!agents.includes(expectedName)) fail(`pack ${pack}: AGENTS.md missing native agent name`);
280
-
281
- const readme = fs.readFileSync(path.join(root, 'README.md'), 'utf8');
282
- if (readme.includes('Workframe/scripts/')) fail(`pack ${pack}: README still references nested Workframe/scripts paths`);
283
-
284
- const setup = fs.readFileSync(path.join(root, 'SETUP.md'), 'utf8');
285
- if (!setup.includes('bootstrap-native')) fail(`pack ${pack}: SETUP.md missing native bootstrap`);
286
- if (!setup.includes('add-profile')) fail(`pack ${pack}: SETUP.md missing add-profile`);
287
- if (!setup.includes('start-install')) fail(`pack ${pack}: SETUP.md missing start-install`);
288
- if (setup.includes('mission control')) fail(`pack ${pack}: SETUP.md should not position mission control in the main flow`);
289
-
290
- const updateHermes = fs.readFileSync(path.join(root, 'scripts/update-hermes.ps1'), 'utf8');
291
- if (updateHermes.includes('dashboard-')) {
292
- fail(`pack ${pack}: update-hermes.ps1 should not recreate profile dashboard services`);
293
- }
294
-
295
- const chat = fs.readFileSync(path.join(root, 'scripts/chat.ps1'), 'utf8');
296
- if (!chat.includes(`-p ${expectedSlug}`)) fail(`pack ${pack}: chat.ps1 missing native profile flag`);
297
- if (!chat.includes(`${expectedStack}-chat`)) fail(`pack ${pack}: chat.ps1 missing named chat container`);
298
-
299
- const bootstrapNative = fs.readFileSync(path.join(root, 'scripts/bootstrap-native.ps1'), 'utf8');
300
- if (!bootstrapNative.includes(`profile use ${expectedSlug}`)) fail(`pack ${pack}: bootstrap-native missing profile use`);
301
- if (!bootstrapNative.includes('routes.json')) fail(`pack ${pack}: bootstrap-native must write Agents/workframe/routes.json`);
302
- if (!bootstrapNative.includes('SETUP.md')) fail(`pack ${pack}: bootstrap-native missing SETUP copy`);
303
- if (!bootstrapNative.includes('Agents\\SOUL.md')) fail(`pack ${pack}: bootstrap-native must install Agents/SOUL.md`);
304
- if (!bootstrapNative.includes('cwd: /workspace')) fail(`pack ${pack}: bootstrap-native must set terminal.cwd to /workspace`);
305
-
306
- const startInstall = fs.readFileSync(path.join(root, 'scripts/start-install.ps1'), 'utf8');
307
- if (!startInstall.includes('open-install-ui.ps1')) fail(`pack ${pack}: start-install.ps1 missing open-install-ui`);
308
- const startInstallSh = fs.readFileSync(path.join(root, 'scripts/start-install.sh'), 'utf8');
309
- if (!startInstallSh.includes('open-install-ui.sh')) fail(`pack ${pack}: start-install.sh missing open-install-ui`);
310
- if (!fs.existsSync(path.join(root, 'scripts/apply-update-hermes.sh'))) {
311
- fail(`pack ${pack}: missing scripts/apply-update-hermes.sh`);
312
- }
313
- if (!fs.existsSync(path.join(root, 'scripts/apply-update-workframe.sh'))) {
314
- fail(`pack ${pack}: missing scripts/apply-update-workframe.sh`);
315
- }
316
- const installPs1 = fs.readFileSync(path.join(root, 'scripts/install.ps1'), 'utf8');
317
- if (!installPs1.includes('open-workframe-ui.ps1')) fail(`pack ${pack}: install.ps1 missing open-workframe-ui`);
318
- const installSh = fs.readFileSync(path.join(root, 'scripts/install.sh'), 'utf8');
319
- if (!installSh.includes('open-workframe-ui.sh')) fail(`pack ${pack}: install.sh missing open-workframe-ui`);
320
- const launchSh = fs.readFileSync(path.join(root, 'scripts/launch-install.sh'), 'utf8');
321
- if (!launchSh.includes('start-install.sh')) fail(`pack ${pack}: launch-install.sh missing start-install`);
322
-
323
- const addProfile = fs.readFileSync(path.join(root, 'scripts/add-profile.ps1'), 'utf8');
324
- if (!addProfile.includes("'dev'")) fail(`pack ${pack}: add-profile missing dev in catalog`);
325
-
326
- const verify = fs.readFileSync(path.join(root, 'scripts/verify-bootstrap.ps1'), 'utf8');
327
- if (!verify.includes('bootstrap-native')) fail(`pack ${pack}: verify-bootstrap should mention bootstrap-native`);
328
- if (!verify.includes('Agents\\SOUL.md')) fail(`pack ${pack}: verify-bootstrap must check Agents\\SOUL.md`);
329
-
330
- const workframeApi = fs.readFileSync(path.join(root, 'workframe-api/server.py'), 'utf8');
331
- if (!workframeApi.includes('active_id = persistent_id if persistent_valid else ""')) {
332
- fail(`pack ${pack}: workframe-api bootstrap should not treat latest native session as active`);
333
- }
334
- if (workframeApi.includes('latest = _latest_session_id(prof)')) {
335
- fail(`pack ${pack}: workframe-api should not fall back to latest session in profile_chat_session`);
336
- }
337
- if (!workframeApi.includes('binding_version = _binding_version(payload.get("binding_version"))')) {
338
- fail(`pack ${pack}: workframe-api should version native UI bindings`);
339
- }
340
- if (!workframeApi.includes('"binding_version": binding_version')) {
341
- fail(`pack ${pack}: workframe-api should persist binding_version in lane bindings`);
342
- }
343
- if (!workframeApi.includes('_wait_profile_api_healthy(profile: str, attempts: int = 60')) {
344
- fail(`pack ${pack}: workframe-api must wait >=30s for cold u-* profile health`);
345
- }
346
- if (!workframeApi.includes('http://gateway:')) {
347
- fail(`pack ${pack}: workframe-api profile health must probe gateway DNS from BFF container`);
348
- }
349
- if (!workframeApi.includes('for attempt in range(3)')) {
350
- fail(`pack ${pack}: bootstrap_agent_dm_lane must retry gateway start up to 3 times`);
351
- }
352
-
353
- if (!workframeApi.includes('internal_proxy_auth')) {
354
- fail(`pack ${pack}: workframe-api missing internal proxy auth`);
355
- }
356
- if (!workframeApi.includes('credential_vault.bootstrap_vault')) {
357
- fail(`pack ${pack}: workframe-api missing vault envelope bootstrap`);
358
- }
359
- if (!workframeApi.includes('_invite_only_login_enforced')) {
360
- fail(`pack ${pack}: workframe-api missing invite-only login gate`);
361
- }
362
-
363
- const supervisorPy = fs.readFileSync(path.join(root, 'workframe-supervisor/server.py'), 'utf8');
364
- if (!supervisorPy.includes('for _ in range(60):')) {
365
- fail(`pack ${pack}: workframe-supervisor must wait >=30s for profile gateway health`);
366
- }
367
-
368
- console.log(`OK pack=${pack} native=${expectedSlug} docker=${expectedStack}`);
369
- }
370
-
371
- const baRes = spawnSync(process.execPath, [CLI, 'BrandAuthority', '--pack', 'vanilla', '--out', tmp, '--ci', '--force'], {
372
- encoding: 'utf8',
373
- });
374
- if (baRes.status !== 0) fail(`BrandAuthority scaffold: ${baRes.stderr || baRes.stdout}`);
375
- const baManifest = JSON.parse(fs.readFileSync(path.join(tmp, 'BrandAuthority', 'workframe-manifest.json'), 'utf8'));
376
- if (baManifest.native_agent.profile_slug !== 'brandauthority-agent') {
377
- fail(`BrandAuthority slug expected brandauthority-agent, got ${baManifest.native_agent.profile_slug}`);
378
- }
379
- if (baManifest.docker.stack !== 'brandauthority') {
380
- fail(`BrandAuthority docker stack expected brandauthority`);
381
- }
382
- console.log('OK BrandAuthority -> brandauthority-agent / brandauthority stack');
383
-
384
- for (const bad of ['.', '..', '../escape', 'bad/name']) {
385
- const res = spawnSync(process.execPath, [CLI, '--name', bad, '--out', tmp, '--ci', '--force'], { encoding: 'utf8' });
386
- if (res.status === 0) fail(`expected rejection for name=${bad}`);
387
- }
388
- console.log('OK rejected unsafe project names');
389
-
390
- console.log('All scaffold tests passed.');
File without changes