create-workframe 0.1.0 → 0.1.2

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 (198) hide show
  1. package/LICENSE +201 -201
  2. package/NOTICE +12 -12
  3. package/README.md +8 -92
  4. package/SECURITY.md +38 -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/bundle-workframe-ui.mjs +3 -3
  28. package/scripts/ensure-compose-host-paths.mjs +51 -51
  29. package/scripts/lib/install-identity.mjs +212 -212
  30. package/scripts/set-compose-public-url.mjs +92 -92
  31. package/scripts/sync-canonical-to-package.mjs +27 -9
  32. package/shared/WORKFRAME_AGENT_LIBRARY.md +17 -17
  33. package/shared/WORKFRAME_AGENT_OPERATIONS.md +15 -15
  34. package/shared/WORKFRAME_AGENT_PACKS.json +18 -18
  35. package/shared/WORKFRAME_AGENT_PACKS.yaml +8 -8
  36. package/shared/WORKFRAME_SKILL_CURATION.md +4 -4
  37. package/workframe-api/README.md +26 -28
  38. package/workframe-api/action_proxy.py +131 -131
  39. package/workframe-api/auth_rate_limit.py +49 -49
  40. package/workframe-api/credential_vault.py +445 -445
  41. package/workframe-api/data/avatar-catalog.json +41 -41
  42. package/workframe-api/email_sender.py +220 -220
  43. package/workframe-api/google_auth.py +90 -90
  44. package/workframe-api/install_api.py +359 -359
  45. package/workframe-api/internal_proxy_auth.py +150 -150
  46. package/workframe-api/llm_proxy.py +277 -277
  47. package/workframe-api/oidc_jwt.py +108 -108
  48. package/workframe-api/package.json +12 -13
  49. package/workframe-api/public/assets/index-DPXu_lGn.css +1 -1
  50. package/workframe-api/public/assets/index-DYnLrCZZ.js +8 -8
  51. package/workframe-api/requirements.txt +2 -2
  52. package/workframe-api/site_meta.py +271 -271
  53. package/workframe-api/stack_config.py +427 -427
  54. package/workframe-api/time-bind-chat.py +99 -99
  55. package/workframe-api/turn_credentials.py +226 -226
  56. package/workframe-api/updates.py +417 -417
  57. package/workframe-api/vault_kek.py +159 -159
  58. package/workframe-api/zk_auth.py +633 -633
  59. package/workframe-supervisor/Dockerfile +11 -11
  60. package/workframe-supervisor/server.py +787 -787
  61. package/workframe-ui/docker/nginx.conf +85 -85
  62. package/workframe-ui/public/assets/{arc-CBDYvkAF.js → arc-COAT3laO.js} +1 -1
  63. package/workframe-ui/public/assets/architecture-7EHR7CIX-DUyH3hWG.js +1 -0
  64. package/workframe-ui/public/assets/{architectureDiagram-3BPJPVTR-XnBRKeW0.js → architectureDiagram-3BPJPVTR-BFjWV24l.js} +1 -1
  65. package/workframe-ui/public/assets/{blockDiagram-GPEHLZMM-VYHUfVhd.js → blockDiagram-GPEHLZMM-DSQLPfrj.js} +1 -1
  66. package/workframe-ui/public/assets/{c4Diagram-AAUBKEIU-BTjUcJpm.js → c4Diagram-AAUBKEIU-DKEHv1t2.js} +1 -1
  67. package/workframe-ui/public/assets/channel-g7r_RGaY.js +1 -0
  68. package/workframe-ui/public/assets/{chunk-2J33WTMH-w7uu7R-b.js → chunk-2J33WTMH-DHZg-DUi.js} +1 -1
  69. package/workframe-ui/public/assets/{chunk-3OPIFGDE-Cb9LtnDX.js → chunk-3OPIFGDE-BB-OYTfp.js} +1 -1
  70. package/workframe-ui/public/assets/{chunk-4BX2VUAB-DiQ-qCwH.js → chunk-4BX2VUAB-C93q0YIm.js} +1 -1
  71. package/workframe-ui/public/assets/{chunk-55IACEB6-C-mLFr7z.js → chunk-55IACEB6-MAYniqik.js} +1 -1
  72. package/workframe-ui/public/assets/{chunk-5ZQYHXKU-DOesfiCI.js → chunk-5ZQYHXKU-ChgN6YJs.js} +1 -1
  73. package/workframe-ui/public/assets/{chunk-727SXJPM-BJ3oBZuz.js → chunk-727SXJPM-B_FYwdAv.js} +1 -1
  74. package/workframe-ui/public/assets/{chunk-AQP2D5EJ-CCA6xpGs.js → chunk-AQP2D5EJ-1_Hw_h1A.js} +1 -1
  75. package/workframe-ui/public/assets/{chunk-BSJP7CBP-a0cMNFb2.js → chunk-BSJP7CBP-CFiDQ1Rv.js} +1 -1
  76. package/workframe-ui/public/assets/{chunk-CSCIHK7Q-kuqN8EIY.js → chunk-CSCIHK7Q-DZ9UMTlB.js} +1 -1
  77. package/workframe-ui/public/assets/{chunk-FMBD7UC4-DyPgYHCg.js → chunk-FMBD7UC4-DlMlyFgw.js} +1 -1
  78. package/workframe-ui/public/assets/{chunk-KSCS5N6A-CdUuvR0V.js → chunk-KSCS5N6A-DHXtQ_Hf.js} +1 -1
  79. package/workframe-ui/public/assets/{chunk-L5ZTLDWV-Dq9NoWmK.js → chunk-L5ZTLDWV-CuQzg-QG.js} +1 -1
  80. package/workframe-ui/public/assets/{chunk-LZXEDZCA-p74rddlO.js → chunk-LZXEDZCA-BHzjzCGg.js} +2 -2
  81. package/workframe-ui/public/assets/{chunk-ND2GUHAM-DBD2u1Gz.js → chunk-ND2GUHAM-DHXx05n2.js} +1 -1
  82. package/workframe-ui/public/assets/{chunk-NZK2D7GU-BeIeYFnd.js → chunk-NZK2D7GU-CV5pmDM_.js} +1 -1
  83. package/workframe-ui/public/assets/{chunk-O5CBEL6O-ClHc56ib.js → chunk-O5CBEL6O-6tkCHxsV.js} +1 -1
  84. package/workframe-ui/public/assets/chunk-QZHKN3VN-C5UQehWY.js +1 -0
  85. package/workframe-ui/public/assets/chunk-WU5MYG2G-DhWllrI8.js +1 -0
  86. package/workframe-ui/public/assets/{chunk-XPW4576I-EFr8R_1p.js → chunk-XPW4576I-BClwIiCp.js} +1 -1
  87. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-BBM_8T8E.js +1 -0
  88. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-BBM_8T8E.js +1 -0
  89. package/workframe-ui/public/assets/{cose-bilkent-S5V4N54A-C7aPBODd.js → cose-bilkent-S5V4N54A-DOrGV6DQ.js} +1 -1
  90. package/workframe-ui/public/assets/{dagre-BM42HDAG-BdU1Rv-H.js → dagre-BM42HDAG-DXTPvJkX.js} +1 -1
  91. package/workframe-ui/public/assets/{diagram-2AECGRRQ-DWowSo85.js → diagram-2AECGRRQ-xX_v-pbf.js} +1 -1
  92. package/workframe-ui/public/assets/{diagram-5GNKFQAL-MnxBbceO.js → diagram-5GNKFQAL-Cd2pXbBe.js} +1 -1
  93. package/workframe-ui/public/assets/{diagram-KO2AKTUF-DQaLRXFf.js → diagram-KO2AKTUF-Df3XvUtk.js} +1 -1
  94. package/workframe-ui/public/assets/{diagram-LMA3HP47-CQaBud9k.js → diagram-LMA3HP47-CsijIPaD.js} +1 -1
  95. package/workframe-ui/public/assets/{diagram-OG6HWLK6-D8bAXbY9.js → diagram-OG6HWLK6-aq5fmfHd.js} +1 -1
  96. package/workframe-ui/public/assets/{dist-DGpTLHr_.js → dist-D1c0mkbB.js} +1 -1
  97. package/workframe-ui/public/assets/{erDiagram-TEJ5UH35-1E-xSvBK.js → erDiagram-TEJ5UH35-DnFysVRY.js} +1 -1
  98. package/workframe-ui/public/assets/eventmodeling-FCH6USID-Ci8mdb44.js +1 -0
  99. package/workframe-ui/public/assets/{flowDiagram-I6XJVG4X-CgOVD5hu.js → flowDiagram-I6XJVG4X-C6Ebi3su.js} +1 -1
  100. package/workframe-ui/public/assets/{ganttDiagram-6RSMTGT7-JFYAIauo.js → ganttDiagram-6RSMTGT7-BQXQtUpa.js} +1 -1
  101. package/workframe-ui/public/assets/{gitGraph-WXDBUCRP-B9REenIl.js → gitGraph-WXDBUCRP-Dt0zIs_M.js} +1 -1
  102. package/workframe-ui/public/assets/{gitGraphDiagram-PVQCEYII-BQ7NcMSn.js → gitGraphDiagram-PVQCEYII-BF8gHzRn.js} +1 -1
  103. package/workframe-ui/public/assets/index-DpoUZAxh.css +1 -0
  104. package/workframe-ui/public/assets/{index-Dnw6vjqb.js → index-lRpzpNPT.js} +2 -2
  105. package/workframe-ui/public/assets/{info-J43DQDTF-CL6-eTjH.js → info-J43DQDTF-CSmszQJT.js} +1 -1
  106. package/workframe-ui/public/assets/{infoDiagram-5YYISTIA-LJTODW4W.js → infoDiagram-5YYISTIA-CVTKGW6p.js} +1 -1
  107. package/workframe-ui/public/assets/{ishikawaDiagram-YF4QCWOH-bchrQVuo.js → ishikawaDiagram-YF4QCWOH-Z8pT09Lv.js} +1 -1
  108. package/workframe-ui/public/assets/{journeyDiagram-JHISSGLW-DkrvYuxP.js → journeyDiagram-JHISSGLW-r3wD68_T.js} +1 -1
  109. package/workframe-ui/public/assets/{kanban-definition-UN3LZRKU-DFRbj0IG.js → kanban-definition-UN3LZRKU-Il8VglqN.js} +1 -1
  110. package/workframe-ui/public/assets/{line-Vd48P7-O.js → line-oyjpfz2A.js} +1 -1
  111. package/workframe-ui/public/assets/{linear-Ckizh2G7.js → linear-Cf7p5tVp.js} +1 -1
  112. package/workframe-ui/public/assets/{mermaid-parser.core-Bkimsnqj.js → mermaid-parser.core-YmbZ-AfY.js} +2 -2
  113. package/workframe-ui/public/assets/{mermaid.core-x0TvVuPo.js → mermaid.core-BFdCAqCo.js} +3 -3
  114. package/workframe-ui/public/assets/{mindmap-definition-RKZ34NQL-6ykAFPEz.js → mindmap-definition-RKZ34NQL-Cy2iCtEl.js} +1 -1
  115. package/workframe-ui/public/assets/{packet-YPE3B663-Dw3xgMDt.js → packet-YPE3B663-DwOBZL6K.js} +1 -1
  116. package/workframe-ui/public/assets/{pie-LRSECV5Y-DATysawG.js → pie-LRSECV5Y-04PPhnKK.js} +1 -1
  117. package/workframe-ui/public/assets/{pieDiagram-4H26LBE5-SJKD1S0S.js → pieDiagram-4H26LBE5-LxIpgHqi.js} +1 -1
  118. package/workframe-ui/public/assets/{quadrantDiagram-W4KKPZXB-BrYDZX8q.js → quadrantDiagram-W4KKPZXB-0nBYfYm4.js} +1 -1
  119. package/workframe-ui/public/assets/{radar-GUYGQ44K-BmWYPCds.js → radar-GUYGQ44K-D2-vBqps.js} +1 -1
  120. package/workframe-ui/public/assets/{requirementDiagram-4Y6WPE33-DwL9Mc8e.js → requirementDiagram-4Y6WPE33-DbuU0nlu.js} +1 -1
  121. package/workframe-ui/public/assets/{sankeyDiagram-5OEKKPKP-DYIFsL8h.js → sankeyDiagram-5OEKKPKP-B2hQ6B2x.js} +1 -1
  122. package/workframe-ui/public/assets/{sequenceDiagram-3UESZ5HK-0-FPkFk8.js → sequenceDiagram-3UESZ5HK-BBrU30e1.js} +1 -1
  123. package/workframe-ui/public/assets/{src-B_od6b6h.js → src-BJEDmV70.js} +1 -1
  124. package/workframe-ui/public/assets/{stateDiagram-AJRCARHV-BQCiBk6u.js → stateDiagram-AJRCARHV-7FGO4kkH.js} +1 -1
  125. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-DLTSizMg.js +1 -0
  126. package/workframe-ui/public/assets/{timeline-definition-PNZ67QCA-DS3tFcXj.js → timeline-definition-PNZ67QCA-ptDm4rCN.js} +1 -1
  127. package/workframe-ui/public/assets/{treeView-BLDUP644-DSyUCKLY.js → treeView-BLDUP644-CS6Z-0q8.js} +1 -1
  128. package/workframe-ui/public/assets/{treemap-LRROVOQU-CEZaNh5Y.js → treemap-LRROVOQU-DqV4Y2VA.js} +1 -1
  129. package/workframe-ui/public/assets/{vennDiagram-CIIHVFJN-CD-Vc9NF.js → vennDiagram-CIIHVFJN-C0UrZJYt.js} +1 -1
  130. package/workframe-ui/public/assets/{wardley-L42UT6IY-Drq5w1Mc.js → wardley-L42UT6IY-bNDN3_Sa.js} +1 -1
  131. package/workframe-ui/public/assets/{wardleyDiagram-YWT4CUSO-DouXDJoF.js → wardleyDiagram-YWT4CUSO-jWiJsefM.js} +1 -1
  132. package/workframe-ui/public/assets/{xychartDiagram-2RQKCTM6-DDf_Lol5.js → xychartDiagram-2RQKCTM6-Dsh_fLCy.js} +1 -1
  133. package/workframe-ui/public/favicon.svg +7 -7
  134. package/workframe-ui/public/index.html +50 -50
  135. package/workframe-ui/public/workframe-config.json +3 -3
  136. package/scripts/security_audit.py +0 -156
  137. package/scripts/test-scaffold.mjs +0 -390
  138. package/workframe-api/tests/__init__.py +0 -0
  139. package/workframe-api/tests/db_setup.py +0 -13
  140. package/workframe-api/tests/test_admin_updates_gated.py +0 -30
  141. package/workframe-api/tests/test_agent_dm_bootstrap.py +0 -196
  142. package/workframe-api/tests/test_agent_profile_sync.py +0 -76
  143. package/workframe-api/tests/test_auth_email.py +0 -222
  144. package/workframe-api/tests/test_auth_hole_fix_selfcheck.py +0 -99
  145. package/workframe-api/tests/test_auth_rate_limit.py +0 -19
  146. package/workframe-api/tests/test_avatar_resolve.py +0 -77
  147. package/workframe-api/tests/test_child_soul_template.py +0 -71
  148. package/workframe-api/tests/test_credential_canary.py +0 -135
  149. package/workframe-api/tests/test_credential_isolation.py +0 -448
  150. package/workframe-api/tests/test_credential_resolution.py +0 -206
  151. package/workframe-api/tests/test_device_oauth.py +0 -108
  152. package/workframe-api/tests/test_doctor_repair.py +0 -103
  153. package/workframe-api/tests/test_ensure_profile_api.py +0 -77
  154. package/workframe-api/tests/test_gateway_compose_security.py +0 -136
  155. package/workframe-api/tests/test_install_secure_host.py +0 -39
  156. package/workframe-api/tests/test_internal_proxy_auth.py +0 -125
  157. package/workframe-api/tests/test_invite_runtime_bootstrap.py +0 -72
  158. package/workframe-api/tests/test_kanban_delegation.py +0 -185
  159. package/workframe-api/tests/test_llm_proxy.py +0 -155
  160. package/workframe-api/tests/test_login_access_policy.py +0 -183
  161. package/workframe-api/tests/test_mvp_model_bootstrap.py +0 -75
  162. package/workframe-api/tests/test_onboarding_bootstrap.py +0 -248
  163. package/workframe-api/tests/test_platform_auth.py +0 -47
  164. package/workframe-api/tests/test_profile_config_path.py +0 -56
  165. package/workframe-api/tests/test_profile_config_yaml_repair.py +0 -63
  166. package/workframe-api/tests/test_profile_create.py +0 -72
  167. package/workframe-api/tests/test_profile_identity_overlay.py +0 -61
  168. package/workframe-api/tests/test_profile_install_health.py +0 -45
  169. package/workframe-api/tests/test_profile_secret_policy.py +0 -57
  170. package/workframe-api/tests/test_profile_workspace_cwd.py +0 -34
  171. package/workframe-api/tests/test_provider_bootstrap.py +0 -75
  172. package/workframe-api/tests/test_provider_connect.py +0 -54
  173. package/workframe-api/tests/test_room_crud.py +0 -192
  174. package/workframe-api/tests/test_room_tenancy.py +0 -701
  175. package/workframe-api/tests/test_runtime_identity_backfill.py +0 -34
  176. package/workframe-api/tests/test_site_meta.py +0 -81
  177. package/workframe-api/tests/test_soul_stub.py +0 -42
  178. package/workframe-api/tests/test_space_member_sync.py +0 -99
  179. package/workframe-api/tests/test_stripe_stack_config.py +0 -37
  180. package/workframe-api/tests/test_supervisor_lifecycle.py +0 -52
  181. package/workframe-api/tests/test_turn_credential_vault.py +0 -125
  182. package/workframe-api/tests/test_updates.py +0 -176
  183. package/workframe-api/tests/test_user_cohort.py +0 -113
  184. package/workframe-api/tests/test_vault_envelope.py +0 -110
  185. package/workframe-api/tests/test_workspace_members.py +0 -183
  186. package/workframe-api/tests/test_workspace_messaging_sync.py +0 -125
  187. package/workframe-api/tests/test_workspace_provider_list.py +0 -57
  188. package/workframe-supervisor/tests/test_exec_guard.py +0 -42
  189. package/workframe-supervisor/tests/test_server_import.py +0 -21
  190. package/workframe-ui/public/assets/architecture-7EHR7CIX-CtbQKTuT.js +0 -1
  191. package/workframe-ui/public/assets/channel-Dy4Z4-jn.js +0 -1
  192. package/workframe-ui/public/assets/chunk-QZHKN3VN-CtBEchFK.js +0 -1
  193. package/workframe-ui/public/assets/chunk-WU5MYG2G-B9pBtriN.js +0 -1
  194. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-BMAEA8jI.js +0 -1
  195. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-BMAEA8jI.js +0 -1
  196. package/workframe-ui/public/assets/eventmodeling-FCH6USID-D75cstNT.js +0 -1
  197. package/workframe-ui/public/assets/index-DpAGxump.css +0 -1
  198. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-B89jAMFF.js +0 -1
@@ -1,34 +0,0 @@
1
- """Profile terminal.cwd and workspace identity file helpers."""
2
- from __future__ import annotations
3
-
4
- import tempfile
5
- import unittest
6
- from pathlib import Path
7
- from unittest import mock
8
-
9
- import server
10
-
11
-
12
- class ProfileWorkspaceCwdTests(unittest.TestCase):
13
- def test_ensure_profile_terminal_cwd_patches_config(self) -> None:
14
- with tempfile.TemporaryDirectory() as tmp:
15
- prof_dir = Path(tmp) / "profiles" / "u-user-dev"
16
- prof_dir.mkdir(parents=True)
17
- cfg = prof_dir / "config.yaml"
18
- cfg.write_text("terminal:\n backend: local\n cwd: .\n", encoding="utf-8")
19
- with mock.patch.object(server, "_profile_gateway_config_path", return_value=cfg):
20
- server._ensure_profile_terminal_cwd("u-user-dev")
21
- self.assertIn("cwd: /workspace", cfg.read_text(encoding="utf-8"))
22
-
23
- def test_copy_text_without_bom_strips_bom(self) -> None:
24
- with tempfile.TemporaryDirectory() as tmp:
25
- root = Path(tmp)
26
- src = root / "AGENTS.md"
27
- dst = root / "out.md"
28
- src.write_bytes(b"\xef\xbb\xbf# hello\n")
29
- server._copy_text_without_bom(src, dst)
30
- self.assertEqual(dst.read_bytes(), b"# hello\n")
31
-
32
-
33
- if __name__ == "__main__":
34
- unittest.main()
@@ -1,75 +0,0 @@
1
- import importlib.util
2
- import json
3
- import tempfile
4
- import unittest
5
- from pathlib import Path
6
- from unittest.mock import patch
7
-
8
- API = Path(__file__).resolve().parents[1] / "server.py"
9
- spec = importlib.util.spec_from_file_location("server", API)
10
- server = importlib.util.module_from_spec(spec)
11
- assert spec and spec.loader
12
- spec.loader.exec_module(server)
13
-
14
-
15
- class ProviderBootstrapTest(unittest.TestCase):
16
- def test_seeds_specialist_auth_from_user_env_only(self) -> None:
17
- with tempfile.TemporaryDirectory() as tmp:
18
- root = Path(tmp)
19
- user_home = root / "profiles" / "user-new"
20
- architect = root / "profiles" / "architect"
21
- user_home.mkdir(parents=True)
22
- architect.mkdir(parents=True)
23
- (architect / "config.yaml").write_text(
24
- "model:\n default: openrouter/owl-alpha\n provider: openrouter\n",
25
- encoding="utf-8",
26
- )
27
- (architect / "auth.json").write_text(
28
- json.dumps({"version": 1, "credential_pool": {"openrouter": []}}),
29
- encoding="utf-8",
30
- )
31
- (user_home / ".env").write_text("OPENROUTER_API_KEY=sk-test-user\n", encoding="utf-8")
32
- with patch.object(server, "HERMES_DATA", root), patch.object(
33
- server, "NATIVE_PROFILE", "workframe-agent"
34
- ), patch.object(server, "_primary_profile", return_value="workframe-agent"), patch.object(
35
- server, "resolve_hermes_profile", side_effect=lambda p: p
36
- ), patch.object(server, "resolve_validated_profile", side_effect=lambda p: p):
37
- server._bootstrap_profile_providers("architect", "user-new", "ws-1")
38
- auth = json.loads((architect / "auth.json").read_text(encoding="utf-8"))
39
- entries = auth["credential_pool"]["openrouter"]
40
- self.assertEqual(len(entries), 1)
41
- self.assertEqual(entries[0]["source"], "env:OPENROUTER_API_KEY")
42
- env = (architect / ".env").read_text(encoding="utf-8")
43
- self.assertIn("OPENROUTER_API_KEY=sk-test-user", env)
44
-
45
- def test_does_not_seed_without_user_credentials(self) -> None:
46
- with tempfile.TemporaryDirectory() as tmp:
47
- root = Path(tmp)
48
- primary = root / "profiles" / "workframe-agent"
49
- architect = root / "profiles" / "architect"
50
- primary.mkdir(parents=True)
51
- architect.mkdir(parents=True)
52
- (primary / ".env").write_text("OPENROUTER_API_KEY=sk-stack\n", encoding="utf-8")
53
- (architect / "config.yaml").write_text(
54
- "model:\n default: openrouter/owl-alpha\n provider: openrouter\n",
55
- encoding="utf-8",
56
- )
57
- (architect / "auth.json").write_text(
58
- json.dumps({"version": 1, "credential_pool": {"openrouter": []}}),
59
- encoding="utf-8",
60
- )
61
- (architect / ".env").write_text("OPENROUTER_API_KEY=sk-stack\n", encoding="utf-8")
62
- with patch.object(server, "HERMES_DATA", root), patch.object(
63
- server, "NATIVE_PROFILE", "workframe-agent"
64
- ), patch.object(server, "_primary_profile", return_value="workframe-agent"), patch.object(
65
- server, "resolve_hermes_profile", side_effect=lambda p: p
66
- ), patch.object(server, "resolve_validated_profile", side_effect=lambda p: p):
67
- changed = server._bootstrap_profile_providers("architect", "user-no-keys", "ws-1")
68
- self.assertTrue(changed)
69
- auth = json.loads((architect / "auth.json").read_text(encoding="utf-8"))
70
- self.assertEqual(auth["credential_pool"]["openrouter"], [])
71
- self.assertNotIn("OPENROUTER_API_KEY", (architect / ".env").read_text(encoding="utf-8"))
72
-
73
-
74
- if __name__ == "__main__":
75
- unittest.main()
@@ -1,54 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import importlib.util
4
- import tempfile
5
- from pathlib import Path
6
-
7
- import pytest
8
-
9
- SERVER_PATH = Path(__file__).resolve().parents[1] / "server.py"
10
- spec = importlib.util.spec_from_file_location("workframe_server", SERVER_PATH)
11
- server = importlib.util.module_from_spec(spec)
12
- assert spec.loader is not None
13
- spec.loader.exec_module(server)
14
-
15
-
16
- @pytest.fixture()
17
- def user_env(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> str:
18
- monkeypatch.setattr(server, "HERMES_DATA", tmp_path)
19
- user_id = "user-test-1"
20
- return user_id
21
-
22
-
23
- def test_read_env_keys_ignores_empty_values(user_env: str) -> None:
24
- env_path = server._user_hermes_env_path(user_env)
25
- env_path.parent.mkdir(parents=True, exist_ok=True)
26
- env_path.write_text("OPENROUTER_API_KEY=sk-test\nEMPTY_KEY=\n# comment\n", encoding="utf-8")
27
-
28
- keys = server._read_env_keys(env_path)
29
-
30
- assert keys == {"OPENROUTER_API_KEY"}
31
-
32
-
33
- def test_list_user_providers_marks_connected_from_env(user_env: str) -> None:
34
- env_path = server._user_hermes_env_path(user_env)
35
- env_path.parent.mkdir(parents=True, exist_ok=True)
36
- env_path.write_text("OPENROUTER_API_KEY=sk-test\n", encoding="utf-8")
37
-
38
- payload = server.list_user_providers(user_env)
39
- openrouter = next(row for row in payload["providers"] if row["id"] == "openrouter")
40
-
41
- assert openrouter["connected"] is True
42
- assert openrouter["env_var"] == "OPENROUTER_API_KEY"
43
-
44
-
45
- def test_disconnect_user_provider_removes_env_secret(user_env: str) -> None:
46
- env_path = server._user_hermes_env_path(user_env)
47
- env_path.parent.mkdir(parents=True, exist_ok=True)
48
- env_path.write_text("DISCORD_BOT_TOKEN=abc\nOTHER=1\n", encoding="utf-8")
49
-
50
- result = server.disconnect_user_provider(user_env, "discord")
51
-
52
- assert result["ok"] is True
53
- assert "DISCORD_BOT_TOKEN" not in server._read_env_keys(env_path)
54
- assert "OTHER" in server._read_env_keys(env_path)
@@ -1,192 +0,0 @@
1
- import tempfile
2
- import unittest
3
- from pathlib import Path
4
-
5
- import server
6
- from db_setup import ensure_workframe_schemas
7
-
8
-
9
- class RoomCrudTests(unittest.TestCase):
10
- def setUp(self) -> None:
11
- self._tmp = tempfile.TemporaryDirectory()
12
- self.addCleanup(self._tmp.cleanup)
13
- self._old_data_dir = server.DATA_DIR
14
- self._old_auth_db_path = server.AUTH_DB_PATH
15
- server.DATA_DIR = Path(self._tmp.name)
16
- server.AUTH_DB_PATH = Path(self._tmp.name) / "auth.db"
17
- ensure_workframe_schemas()
18
- self.workspace_id = "workspace-a"
19
- self.user_id = "user-a"
20
- self.agent_profile_id = "agent-a"
21
-
22
- conn = server._workframe_db()
23
- try:
24
- now = "1"
25
- conn.execute(
26
- """
27
- INSERT INTO users (id, display_name, role, status, created_at, updated_at)
28
- VALUES (?, ?, ?, ?, ?, ?)
29
- """,
30
- (self.user_id, "User A", "member", "active", now, now),
31
- )
32
- conn.execute(
33
- """
34
- INSERT INTO workspaces (id, slug, display_name, owner_id, status, created_at, updated_at)
35
- VALUES (?, ?, ?, ?, ?, ?, ?)
36
- """,
37
- (self.workspace_id, "workspace-a", "Workspace A", self.user_id, "active", now, now),
38
- )
39
- conn.execute(
40
- """
41
- INSERT INTO agent_profiles (
42
- id, workspace_id, slug, display_name, status, created_at, updated_at
43
- ) VALUES (?, ?, ?, ?, ?, ?, ?)
44
- """,
45
- (self.agent_profile_id, self.workspace_id, "agent-a", "Agent A", "available", now, now),
46
- )
47
- conn.commit()
48
- finally:
49
- conn.close()
50
-
51
- def tearDown(self) -> None:
52
- server.DATA_DIR = self._old_data_dir
53
- server.AUTH_DB_PATH = self._old_auth_db_path
54
-
55
- def test_create_lists_and_gets_room(self) -> None:
56
- status, payload = server._create_room(
57
- self.workspace_id,
58
- {
59
- "name": "General Chat",
60
- "slug": "general",
61
- "topic": "Default channel",
62
- "room_type": "channel",
63
- "platform_ids": {"discord": "channel-1"},
64
- },
65
- self.user_id,
66
- )
67
-
68
- self.assertEqual(status, 201)
69
- room = payload["room"]
70
- self.assertEqual(room["workspace_id"], self.workspace_id)
71
- self.assertEqual(room["name"], "General Chat")
72
- self.assertEqual(room["slug"], "general")
73
- self.assertEqual(room["topic"], "Default channel")
74
- self.assertEqual(room["room_type"], "channel")
75
- self.assertEqual(room["platform_ids"], {"discord": "channel-1"})
76
- self.assertEqual(room["created_by"], self.user_id)
77
-
78
- conn = server._workframe_db()
79
- try:
80
- self.assertTrue(server._user_can_access_room(conn, room["id"], self.user_id))
81
- finally:
82
- conn.close()
83
-
84
- status, payload = server._list_rooms(self.workspace_id)
85
- self.assertEqual(status, 200)
86
- self.assertEqual(payload["workspace_id"], self.workspace_id)
87
- self.assertEqual([room["id"] for room in payload["rooms"]], [room["id"]])
88
-
89
- status, payload = server._get_room(room["id"])
90
- self.assertEqual(status, 200)
91
- self.assertEqual(payload["room"]["id"], room["id"])
92
- self.assertEqual(payload["room"]["slug"], "general")
93
-
94
- def test_create_rejects_invalid_inputs(self) -> None:
95
- cases = [
96
- (400, {}, "name required"),
97
- (400, {"name": "Bad Type", "room_type": "bad"}, "invalid_room_type"),
98
- (400, {"name": "Bad Agent", "agent_profile_id": "missing-agent"}, "agent_profile_not_found"),
99
- (404, {"name": "Missing Workspace"}, "workspace_not_found"),
100
- ]
101
- for expected_status, body, expected_error in cases:
102
- wid = self.workspace_id if expected_error != "workspace_not_found" else "missing-workspace"
103
- status, payload = server._create_room(wid, body, self.user_id)
104
- self.assertEqual(status, expected_status)
105
- self.assertEqual(payload["error"], expected_error)
106
-
107
- def test_slug_normalization_and_collision_handling(self) -> None:
108
- status, payload = server._create_room(
109
- self.workspace_id,
110
- {"name": "General Chat!", "slug": "General Chat!"},
111
- self.user_id,
112
- )
113
- self.assertEqual(status, 201)
114
- self.assertEqual(payload["room"]["slug"], "general-chat")
115
-
116
- status, payload = server._create_room(
117
- self.workspace_id,
118
- {"name": "General Chat Copy", "slug": "General Chat!"},
119
- self.user_id,
120
- )
121
- self.assertEqual(status, 201)
122
- self.assertEqual(payload["room"]["slug"], "general-chat-1")
123
-
124
- def test_patch_room_updates_name_and_topic(self) -> None:
125
- status, payload = server._create_room(
126
- self.workspace_id,
127
- {"name": "General Chat", "slug": "general", "topic": "Old topic", "room_type": "channel"},
128
- self.user_id,
129
- )
130
- self.assertEqual(status, 201)
131
- room_id = payload["room"]["id"]
132
-
133
- conn = server._workframe_db()
134
- try:
135
- conn.execute(
136
- """
137
- INSERT INTO workspace_memberships (id, workspace_id, user_id, role, status, created_at, updated_at)
138
- VALUES (?, ?, ?, ?, ?, ?, ?)
139
- """,
140
- ("wm-a", self.workspace_id, self.user_id, "owner", "active", "1", "1"),
141
- )
142
- conn.execute(
143
- """
144
- INSERT INTO room_memberships (id, room_id, user_id, role, status, joined_at, updated_at)
145
- VALUES (?, ?, ?, ?, ?, ?, ?)
146
- """,
147
- ("rm-a", room_id, self.user_id, "admin", "active", "1", "1"),
148
- )
149
- conn.commit()
150
- finally:
151
- conn.close()
152
-
153
- status, payload = server._patch_room(
154
- room_id,
155
- {"name": "Renamed Space", "topic": "New topic"},
156
- self.user_id,
157
- )
158
- self.assertEqual(status, 200)
159
- self.assertEqual(payload["room"]["name"], "Renamed Space")
160
- self.assertEqual(payload["room"]["topic"], "New topic")
161
-
162
- def test_create_room_accepts_hermes_slug_and_returns_hermes_profile(self) -> None:
163
- status, payload = server._create_room(
164
- self.workspace_id,
165
- {
166
- "name": "Agent DM",
167
- "slug": "dm-user-agent",
168
- "room_type": "direct",
169
- "agent_profile_id": "agent-a",
170
- "member_user_ids": [self.user_id],
171
- },
172
- self.user_id,
173
- )
174
- self.assertEqual(status, 201)
175
- self.assertEqual(payload["room"]["agent_profile_id"], self.agent_profile_id)
176
- self.assertEqual(payload["room"]["hermes_profile"], "agent-a")
177
-
178
- def test_hermes_slug_from_agent_ref_maps_workspace_uuid(self) -> None:
179
- conn = server._workframe_db()
180
- try:
181
- row = conn.execute(
182
- "SELECT id FROM agent_profiles WHERE workspace_id = ? AND slug = ?",
183
- (self.workspace_id, "agent-a"),
184
- ).fetchone()
185
- finally:
186
- conn.close()
187
- self.assertIsNotNone(row)
188
- self.assertEqual(server._hermes_slug_from_agent_ref(str(row["id"])), "agent-a")
189
-
190
-
191
- if __name__ == "__main__":
192
- unittest.main()