create-workframe 0.1.12 → 0.1.14

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 (188) hide show
  1. package/README.md +1 -1
  2. package/bin/create-workframe.js +2 -2
  3. package/bin/workframe.js +130 -2
  4. package/docs/security.md +2 -2
  5. package/package.json +1 -1
  6. package/scripts/bundle-workframe-ui.mjs +9 -0
  7. package/scripts/sync-canonical-to-package.mjs +1 -0
  8. package/scripts/verify-public-deploy.sh +65 -3
  9. package/workframe-api/action_proxy.py +23 -17
  10. package/workframe-api/activity_feed.py +798 -0
  11. package/workframe-api/api_meta.py +157 -0
  12. package/workframe-api/auth_gate.py +566 -0
  13. package/workframe-api/avatar_registry.py +343 -0
  14. package/workframe-api/broker_audit.py +164 -0
  15. package/workframe-api/cell_authority.py +231 -0
  16. package/workframe-api/chat_bind.py +319 -0
  17. package/workframe-api/chat_sessions.py +509 -0
  18. package/workframe-api/chat_stream.py +694 -0
  19. package/workframe-api/cleanup_dogfood_smoke.py +252 -0
  20. package/workframe-api/credential_broker.py +162 -0
  21. package/workframe-api/credential_resolve.py +202 -0
  22. package/workframe-api/credential_store.py +245 -0
  23. package/workframe-api/crew_registry.py +250 -0
  24. package/workframe-api/db_schema.py +755 -0
  25. package/workframe-api/docker_gateway.py +166 -0
  26. package/workframe-api/doctor_runtime.py +109 -0
  27. package/workframe-api/domain/__init__.py +47 -0
  28. package/workframe-api/domain/entities.py +252 -0
  29. package/workframe-api/domain/schema/workframe-domain.schema.json +278 -0
  30. package/workframe-api/egress_policy.py +42 -0
  31. package/workframe-api/handler_modules/__init__.py +17 -0
  32. package/workframe-api/handler_modules/handler_admin.py +542 -0
  33. package/workframe-api/handler_modules/handler_auth.py +512 -0
  34. package/workframe-api/handler_modules/handler_chat.py +641 -0
  35. package/workframe-api/handler_modules/handler_install.py +178 -0
  36. package/workframe-api/handler_modules/handler_provider.py +325 -0
  37. package/workframe-api/handler_modules/handler_workspace.py +1420 -0
  38. package/workframe-api/health_monitor.py +80 -0
  39. package/workframe-api/hermes_admin.py +756 -0
  40. package/workframe-api/hermes_profiles.py +1328 -0
  41. package/workframe-api/install_api.py +123 -0
  42. package/workframe-api/kanban_cron.py +473 -0
  43. package/workframe-api/lane_bindings.py +423 -0
  44. package/workframe-api/llm_proxy.py +17 -43
  45. package/workframe-api/mention_helpers.py +180 -0
  46. package/workframe-api/mention_invoke.py +431 -0
  47. package/workframe-api/model_surface.py +1139 -0
  48. package/workframe-api/oauth_pending.py +85 -0
  49. package/workframe-api/oauth_redirect.py +381 -0
  50. package/workframe-api/package.json +2 -2
  51. package/workframe-api/profile_api_lifecycle.py +66 -0
  52. package/workframe-api/profile_gateway.py +436 -0
  53. package/workframe-api/provider_bindings.py +673 -0
  54. package/workframe-api/provider_bootstrap.py +347 -0
  55. package/workframe-api/provider_catalog.py +180 -0
  56. package/workframe-api/rooms.py +1613 -0
  57. package/workframe-api/route_registry.py +331 -191
  58. package/workframe-api/run-typecheck.mjs +2 -1
  59. package/workframe-api/run_authority.py +287 -0
  60. package/workframe-api/run_ledger.py +470 -0
  61. package/workframe-api/run_surface_wiring.py +344 -0
  62. package/workframe-api/runtime_cohort.py +752 -0
  63. package/workframe-api/runtime_tokens.py +127 -0
  64. package/workframe-api/server.py +1453 -19513
  65. package/workframe-api/snapshot_feed.py +49 -0
  66. package/workframe-api/stack_config.py +33 -1
  67. package/workframe-api/supervisor_client.py +154 -0
  68. package/workframe-api/test_api_meta_build_stamp.py +34 -0
  69. package/workframe-api/test_cell_authority.py +79 -0
  70. package/workframe-api/test_chat_bind.py +48 -0
  71. package/workframe-api/test_credential_lease_matrix.py +171 -0
  72. package/workframe-api/test_credential_resolve.py +51 -0
  73. package/workframe-api/test_credential_store.py +27 -0
  74. package/workframe-api/test_dogfood_flows.py +346 -0
  75. package/workframe-api/test_domain_entities.py +152 -0
  76. package/workframe-api/test_exception_hygiene.py +12 -0
  77. package/workframe-api/test_hermes_admin.py +72 -0
  78. package/workframe-api/test_install_wizard_resume.py +78 -0
  79. package/workframe-api/test_local_bootstrap.py +67 -0
  80. package/workframe-api/test_mention_helpers.py +35 -0
  81. package/workframe-api/test_mention_invoke.py +26 -0
  82. package/workframe-api/test_model_surface_consistency.py +1 -0
  83. package/workframe-api/test_oauth_pending.py +41 -0
  84. package/workframe-api/test_provider_bindings.py +52 -0
  85. package/workframe-api/test_provider_catalog.py +28 -0
  86. package/workframe-api/test_route_registry.py +171 -35
  87. package/workframe-api/test_run_authority.py +112 -0
  88. package/workframe-api/test_run_ledger.py +157 -0
  89. package/workframe-api/test_run_surface_wiring.py +130 -0
  90. package/workframe-api/test_runtime_cohort.py +85 -0
  91. package/workframe-api/test_secure_mode_docker_boundary.py +39 -0
  92. package/workframe-api/test_server_reexports.py +99 -0
  93. package/workframe-api/test_stack_config_install_smtp.py +7 -0
  94. package/workframe-api/turn_credentials.py +28 -13
  95. package/workframe-api/turn_overlay.py +715 -0
  96. package/workframe-api/updates.py +16 -1
  97. package/workframe-api/user_prefs.py +387 -0
  98. package/workframe-api/wf032_extract_remaining_handlers.py +417 -0
  99. package/workframe-api/workspace_bootstrap.py +536 -0
  100. package/workframe-api/workspace_files.py +344 -0
  101. package/workframe-api/workspace_messaging.py +206 -0
  102. package/workframe-api/zk_auth.py +3 -2
  103. package/workframe-supervisor/server.py +10 -1
  104. package/workframe-supervisor/test_supervisor_negative.py +75 -0
  105. package/workframe-ui/public/assets/{arc-B0OFRGmJ.js → arc-BT9iZUSd.js} +1 -1
  106. package/workframe-ui/public/assets/architecture-7EHR7CIX-m1aPUQ_r.js +1 -0
  107. package/workframe-ui/public/assets/{architectureDiagram-3BPJPVTR-DeBNltHS.js → architectureDiagram-3BPJPVTR-CaEDd3np.js} +1 -1
  108. package/workframe-ui/public/assets/{blockDiagram-GPEHLZMM-BDhCHu7I.js → blockDiagram-GPEHLZMM-DHJhMrIS.js} +1 -1
  109. package/workframe-ui/public/assets/{c4Diagram-AAUBKEIU-olcDYvtI.js → c4Diagram-AAUBKEIU-Cdyz9hIV.js} +1 -1
  110. package/workframe-ui/public/assets/channel-3M69ekE5.js +1 -0
  111. package/workframe-ui/public/assets/{chunk-2J33WTMH-D0obCBn_.js → chunk-2J33WTMH-BbZx76LX.js} +1 -1
  112. package/workframe-ui/public/assets/{chunk-3OPIFGDE-DX93f-ZZ.js → chunk-3OPIFGDE-DcSxjTIP.js} +1 -1
  113. package/workframe-ui/public/assets/{chunk-4BX2VUAB-BX9B5wUs.js → chunk-4BX2VUAB-B7F-kTY1.js} +1 -1
  114. package/workframe-ui/public/assets/{chunk-55IACEB6-C4DGc6RO.js → chunk-55IACEB6-BwwEiRTK.js} +1 -1
  115. package/workframe-ui/public/assets/{chunk-5ZQYHXKU-Crlja9Lf.js → chunk-5ZQYHXKU-DAi_NksX.js} +1 -1
  116. package/workframe-ui/public/assets/{chunk-727SXJPM-JYSm3szI.js → chunk-727SXJPM-BIaF4XCN.js} +1 -1
  117. package/workframe-ui/public/assets/{chunk-AQP2D5EJ-_KslxVEl.js → chunk-AQP2D5EJ-DjYSk1dG.js} +1 -1
  118. package/workframe-ui/public/assets/{chunk-BSJP7CBP-CpTO-jU8.js → chunk-BSJP7CBP-_twnyfgV.js} +1 -1
  119. package/workframe-ui/public/assets/{chunk-CSCIHK7Q-JGUkCmbI.js → chunk-CSCIHK7Q-CkNmdy5s.js} +2 -2
  120. package/workframe-ui/public/assets/{chunk-FMBD7UC4-BscBwGaC.js → chunk-FMBD7UC4-CjLqd-gl.js} +1 -1
  121. package/workframe-ui/public/assets/{chunk-KSCS5N6A-5ZTZ0bpX.js → chunk-KSCS5N6A-_6oNqFjj.js} +1 -1
  122. package/workframe-ui/public/assets/{chunk-L5ZTLDWV-DxvitYbW.js → chunk-L5ZTLDWV-CeO9Am_D.js} +1 -1
  123. package/workframe-ui/public/assets/chunk-LZXEDZCA-sRM4YeIe.js +2 -0
  124. package/workframe-ui/public/assets/{chunk-ND2GUHAM-Cayl0iV9.js → chunk-ND2GUHAM-CH8tMb21.js} +1 -1
  125. package/workframe-ui/public/assets/{chunk-NZK2D7GU-_7dyBR5G.js → chunk-NZK2D7GU-DAI1bsII.js} +1 -1
  126. package/workframe-ui/public/assets/{chunk-O5CBEL6O--xTpD0yi.js → chunk-O5CBEL6O-B2e-5a2G.js} +1 -1
  127. package/workframe-ui/public/assets/chunk-QZHKN3VN-BMl9ed8P.js +1 -0
  128. package/workframe-ui/public/assets/chunk-WU5MYG2G-C08HH6BU.js +1 -0
  129. package/workframe-ui/public/assets/{chunk-XPW4576I-CwxJQaeK.js → chunk-XPW4576I-DM2-0q37.js} +1 -1
  130. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-BFcOE4Aq.js +1 -0
  131. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-BFcOE4Aq.js +1 -0
  132. package/workframe-ui/public/assets/{cose-bilkent-S5V4N54A-CCspENYv.js → cose-bilkent-S5V4N54A-BnbbewLv.js} +1 -1
  133. package/workframe-ui/public/assets/{dagre-BM42HDAG-Dv8V6R60.js → dagre-BM42HDAG-C4zmB1Vt.js} +1 -1
  134. package/workframe-ui/public/assets/{diagram-2AECGRRQ-Da48hFPT.js → diagram-2AECGRRQ-n-BJmsiC.js} +1 -1
  135. package/workframe-ui/public/assets/{diagram-5GNKFQAL-dTihc7-3.js → diagram-5GNKFQAL-BzQGQnZi.js} +1 -1
  136. package/workframe-ui/public/assets/{diagram-KO2AKTUF-D_Jqu699.js → diagram-KO2AKTUF-Cx_De-wS.js} +1 -1
  137. package/workframe-ui/public/assets/{diagram-LMA3HP47-Bt8PtEaZ.js → diagram-LMA3HP47-CIOI3Lok.js} +1 -1
  138. package/workframe-ui/public/assets/{diagram-OG6HWLK6-BtmPjlh0.js → diagram-OG6HWLK6-DkRuPLWY.js} +1 -1
  139. package/workframe-ui/public/assets/{dist-Cz2turIT.js → dist-XhCT1Q-V.js} +1 -1
  140. package/workframe-ui/public/assets/{erDiagram-TEJ5UH35-BxMGCflX.js → erDiagram-TEJ5UH35-C3GwhDU1.js} +1 -1
  141. package/workframe-ui/public/assets/eventmodeling-FCH6USID-DIVBL6CF.js +1 -0
  142. package/workframe-ui/public/assets/{flowDiagram-I6XJVG4X-ivyroIZt.js → flowDiagram-I6XJVG4X-CLWH8z2e.js} +1 -1
  143. package/workframe-ui/public/assets/{ganttDiagram-6RSMTGT7-B9llwShx.js → ganttDiagram-6RSMTGT7-D4h66Vy3.js} +1 -1
  144. package/workframe-ui/public/assets/{gitGraph-WXDBUCRP-BFQHk4bw.js → gitGraph-WXDBUCRP-IW5F-Q6c.js} +1 -1
  145. package/workframe-ui/public/assets/{gitGraphDiagram-PVQCEYII-ursegLbc.js → gitGraphDiagram-PVQCEYII-BjNIbmRM.js} +1 -1
  146. package/workframe-ui/public/assets/index-DL2vJP4L.css +1 -0
  147. package/workframe-ui/public/assets/index-ElS_D59P.js +130 -0
  148. package/workframe-ui/public/assets/{info-J43DQDTF-DQpifAsB.js → info-J43DQDTF-CcJspM79.js} +1 -1
  149. package/workframe-ui/public/assets/{infoDiagram-5YYISTIA-C16XP2Q7.js → infoDiagram-5YYISTIA-DP4xR9jt.js} +1 -1
  150. package/workframe-ui/public/assets/{ishikawaDiagram-YF4QCWOH-CiJoz7rP.js → ishikawaDiagram-YF4QCWOH-D4cZb21V.js} +1 -1
  151. package/workframe-ui/public/assets/{journeyDiagram-JHISSGLW-CVwEvvUL.js → journeyDiagram-JHISSGLW-DsLuHkIJ.js} +1 -1
  152. package/workframe-ui/public/assets/{kanban-definition-UN3LZRKU-CWQ6hX5i.js → kanban-definition-UN3LZRKU-Wu65nOsR.js} +1 -1
  153. package/workframe-ui/public/assets/{line-CgQsmU35.js → line-ByBDzRpy.js} +1 -1
  154. package/workframe-ui/public/assets/{linear-Bxbc77dL.js → linear-DzXi2vnq.js} +1 -1
  155. package/workframe-ui/public/assets/{mermaid-parser.core-C_jMeF0a.js → mermaid-parser.core-CaB-1Ckn.js} +2 -2
  156. package/workframe-ui/public/assets/{mermaid.core-4RKV9MZP.js → mermaid.core-D7HTHC17.js} +3 -3
  157. package/workframe-ui/public/assets/{mindmap-definition-RKZ34NQL-DZ7y7Sz0.js → mindmap-definition-RKZ34NQL-BfKsC9aj.js} +1 -1
  158. package/workframe-ui/public/assets/{packet-YPE3B663-B9h2I7Vq.js → packet-YPE3B663-ClpU98TO.js} +1 -1
  159. package/workframe-ui/public/assets/{pie-LRSECV5Y-B2mP5uHm.js → pie-LRSECV5Y-KeOyY_-u.js} +1 -1
  160. package/workframe-ui/public/assets/{pieDiagram-4H26LBE5-DyYKyKiP.js → pieDiagram-4H26LBE5-CUpjnzbs.js} +1 -1
  161. package/workframe-ui/public/assets/{quadrantDiagram-W4KKPZXB-CL_6nej-.js → quadrantDiagram-W4KKPZXB-CJdPTszF.js} +1 -1
  162. package/workframe-ui/public/assets/{radar-GUYGQ44K-CNKNDQ7S.js → radar-GUYGQ44K-C9BpandZ.js} +1 -1
  163. package/workframe-ui/public/assets/{requirementDiagram-4Y6WPE33-eWU_mhFa.js → requirementDiagram-4Y6WPE33-CHDZPwry.js} +1 -1
  164. package/workframe-ui/public/assets/{sankeyDiagram-5OEKKPKP-DrjcXvEQ.js → sankeyDiagram-5OEKKPKP-CMJJh91s.js} +1 -1
  165. package/workframe-ui/public/assets/{sequenceDiagram-3UESZ5HK-BHWR1xTJ.js → sequenceDiagram-3UESZ5HK-BsV4GppP.js} +1 -1
  166. package/workframe-ui/public/assets/{src-DfJB8kV1.js → src-WHks82Up.js} +1 -1
  167. package/workframe-ui/public/assets/{stateDiagram-AJRCARHV-BGJbSd3I.js → stateDiagram-AJRCARHV-eSX9P8z0.js} +1 -1
  168. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-FzMK4PkM.js +1 -0
  169. package/workframe-ui/public/assets/{timeline-definition-PNZ67QCA-DkwoCjiZ.js → timeline-definition-PNZ67QCA-Dg1UkhGo.js} +1 -1
  170. package/workframe-ui/public/assets/{treeView-BLDUP644-GxfhiqoW.js → treeView-BLDUP644-B7yGsBuj.js} +1 -1
  171. package/workframe-ui/public/assets/{treemap-LRROVOQU-DxrOsars.js → treemap-LRROVOQU-j_G2oyQ_.js} +1 -1
  172. package/workframe-ui/public/assets/{vennDiagram-CIIHVFJN-BsWhpUfr.js → vennDiagram-CIIHVFJN-BRrT0jqC.js} +1 -1
  173. package/workframe-ui/public/assets/{wardley-L42UT6IY-CrFZWMHS.js → wardley-L42UT6IY-DEdUqez2.js} +1 -1
  174. package/workframe-ui/public/assets/{wardleyDiagram-YWT4CUSO-DV8Xpf5I.js → wardleyDiagram-YWT4CUSO-C-bBK9Bb.js} +1 -1
  175. package/workframe-ui/public/assets/{xychartDiagram-2RQKCTM6-DqMqnwdZ.js → xychartDiagram-2RQKCTM6-DhH5xuR2.js} +1 -1
  176. package/workframe-ui/public/index.html +11 -7
  177. package/workframe-ui/public/workframe-build.json +5 -0
  178. package/workframe-ui/public/assets/architecture-7EHR7CIX-Dwwi9yA0.js +0 -1
  179. package/workframe-ui/public/assets/channel-fNHbDpV4.js +0 -1
  180. package/workframe-ui/public/assets/chunk-LZXEDZCA-DMfdMUwz.js +0 -2
  181. package/workframe-ui/public/assets/chunk-QZHKN3VN-ewTgEQK8.js +0 -1
  182. package/workframe-ui/public/assets/chunk-WU5MYG2G-D4Tg-A0l.js +0 -1
  183. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-CFAgBpEl.js +0 -1
  184. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-CFAgBpEl.js +0 -1
  185. package/workframe-ui/public/assets/eventmodeling-FCH6USID-BIJTP5k-.js +0 -1
  186. package/workframe-ui/public/assets/index-8CuZDEIG.css +0 -1
  187. package/workframe-ui/public/assets/index-xS9lFekI.js +0 -129
  188. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-CMLuNyeC.js +0 -1
@@ -0,0 +1,417 @@
1
+ #!/usr/bin/env python3
2
+ """WF-032: extract remaining Handler route groups into handler_modules mixins."""
3
+ from __future__ import annotations
4
+
5
+ import ast
6
+ import re
7
+ import textwrap
8
+ from pathlib import Path
9
+
10
+ ROOT = Path(__file__).resolve().parent
11
+ SERVER = ROOT / "server.py"
12
+
13
+ AUTH_METHODS = [
14
+ "_route_post_auth_google_start",
15
+ "_route_post_auth_local_bootstrap",
16
+ "_route_post_auth_bootstrap",
17
+ "_route_post_setup",
18
+ "_route_post_auth_start",
19
+ "_route_post_auth_verify",
20
+ "_route_post_auth_logout",
21
+ "_route_post_auth_refresh",
22
+ "_route_get_auth_hermes_dashboard_gate",
23
+ "_route_get_auth_google_callback",
24
+ "_route_get_oauth_github_callback",
25
+ "_route_get_oauth_discord_callback",
26
+ "_route_get_oauth_stripe_callback",
27
+ "_route_get_me",
28
+ "_route_get_me_onboarding",
29
+ "_route_patch_me",
30
+ "_route_patch_me_native_agent",
31
+ ]
32
+
33
+ PROVIDER_METHODS = [
34
+ "_handle_internal_llm_proxy",
35
+ "_handle_internal_action_proxy",
36
+ "_handle_internal_run_record",
37
+ "_route_get_user_credentials",
38
+ "_route_get_me_credentials",
39
+ "_route_get_me_providers",
40
+ "_route_post_me_credentials",
41
+ "_route_post_me_telegram_link",
42
+ "_route_pattern_get_me_oauth_status",
43
+ "_route_pattern_get_agent_credentials",
44
+ "_route_pattern_post_me_oauth_start",
45
+ "_route_pattern_post_me_providers_disconnect",
46
+ "_route_pattern_delete_me_credentials",
47
+ ]
48
+
49
+ ADMIN_METHODS = [
50
+ "_route_get_meta",
51
+ "_route_get_health",
52
+ "_route_get_setup_status",
53
+ "_route_get_public_site_meta",
54
+ "_route_get_public_link_preview",
55
+ "_route_get_public_manifest",
56
+ "_route_get_me_cohort",
57
+ "_route_get_agents",
58
+ "_route_get_snapshot",
59
+ "_route_get_activity_detail",
60
+ "_route_get_files_tree",
61
+ "_route_get_files_list",
62
+ "_route_get_files_state",
63
+ "_route_get_files_read",
64
+ "_route_get_files_raw",
65
+ "_route_get_routes",
66
+ "_route_post_files_write",
67
+ "_route_post_files_upload",
68
+ "_route_get_admin_vault_status",
69
+ "_route_get_doctor_agent_dm_runtimes",
70
+ "_route_get_admin_updates",
71
+ "_route_post_admin_updates_apply",
72
+ "_route_post_admin_stack_restart_gateway",
73
+ "_route_post_admin_vault_init",
74
+ "_route_post_admin_vault_unlock",
75
+ "_route_post_admin_vault_seal",
76
+ "_route_post_admin_vault_wipe",
77
+ "_route_get_supervisor_profile_status",
78
+ "_route_get_supervisor_stack_status",
79
+ "_route_get_admin_audit",
80
+ "_route_get_events",
81
+ "_route_pattern_get_public_branding",
82
+ "_supervisor_proxy_post",
83
+ "_route_post_supervisor_profile_start",
84
+ "_route_post_supervisor_profile_stop",
85
+ "_route_post_supervisor_profile_disable",
86
+ "_route_pattern_post_memory",
87
+ "_route_pattern_delete_memory",
88
+ "_route_patch_doctor_repair",
89
+ ]
90
+
91
+ MODULE_SPECS: dict[str, tuple[str, str, list[str]]] = {
92
+ "handler_auth.py": (
93
+ "AuthRoutesMixin",
94
+ "Auth / session / OAuth callback route handlers (WF-032 slice).",
95
+ AUTH_METHODS,
96
+ ),
97
+ "handler_provider.py": (
98
+ "ProviderRoutesMixin",
99
+ "Provider credentials / OAuth connect / internal LLM proxy (WF-032 slice).",
100
+ PROVIDER_METHODS,
101
+ ),
102
+ "handler_admin.py": (
103
+ "AdminRoutesMixin",
104
+ "Admin / meta / doctor / supervisor / data-read routes (WF-032 slice).",
105
+ ADMIN_METHODS,
106
+ ),
107
+ }
108
+
109
+ IMPORTS_BY_MODULE = {
110
+ "handler_auth.py": """\
111
+ from __future__ import annotations
112
+
113
+ import json
114
+ import os
115
+ import re
116
+ import secrets
117
+ import sqlite3
118
+ import time
119
+ import urllib.parse
120
+ import uuid
121
+ from datetime import datetime, timezone
122
+ from typing import Any
123
+
124
+ import auth_rate_limit
125
+ import google_auth
126
+ import stack_config
127
+ from email_sender import APP_BASE_URL
128
+ """,
129
+ "handler_provider.py": """\
130
+ from __future__ import annotations
131
+
132
+ import sqlite3
133
+ from typing import Any
134
+
135
+ import action_proxy
136
+ import internal_proxy_auth
137
+ import llm_proxy
138
+ import openrouter_catalog
139
+ import platform_auth
140
+ import run_surface_wiring
141
+ """,
142
+ "handler_admin.py": """\
143
+ from __future__ import annotations
144
+
145
+ import json
146
+ import os
147
+ import re
148
+ import sqlite3
149
+ import time
150
+ import urllib.parse
151
+ from pathlib import Path
152
+ from typing import Any
153
+
154
+ import activity_feed
155
+ import credential_vault
156
+ import doctor_audit
157
+ import site_meta
158
+ import stack_updates
159
+ from supervisor_client import _supervisor_request
160
+ """,
161
+ }
162
+
163
+
164
+ def _read_server() -> str:
165
+ return SERVER.read_text(encoding="utf-8-sig")
166
+
167
+
168
+ def _server_names() -> set[str]:
169
+ tree = ast.parse(_read_server())
170
+ names: set[str] = set()
171
+ for node in tree.body:
172
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
173
+ names.add(node.name)
174
+ elif isinstance(node, ast.Assign):
175
+ for t in node.targets:
176
+ if isinstance(t, ast.Name):
177
+ names.add(t.id)
178
+ elif isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name):
179
+ names.add(node.target.id)
180
+ return names
181
+
182
+
183
+ def _extract_handler_methods(source: str, wanted: set[str]) -> dict[str, str]:
184
+ lines = source.splitlines()
185
+ in_handler = False
186
+ class_indent = 0
187
+ body_indent = 4
188
+ current: str | None = None
189
+ buf: list[str] = []
190
+ out: dict[str, list[str]] = {}
191
+
192
+ for line in lines:
193
+ if not in_handler:
194
+ if line.startswith("class Handler("):
195
+ in_handler = True
196
+ class_indent = len(line) - len(line.lstrip())
197
+ body_indent = class_indent + 4
198
+ continue
199
+ stripped = line.lstrip()
200
+ if not stripped:
201
+ if current:
202
+ buf.append(line)
203
+ continue
204
+ indent = len(line) - len(stripped)
205
+ if indent <= class_indent and stripped and not stripped.startswith("#"):
206
+ break
207
+ if stripped.startswith("def ") and indent == body_indent:
208
+ if current and current in wanted:
209
+ out[current] = buf
210
+ name = stripped.split("(")[0].replace("def ", "").strip()
211
+ current = name if name in wanted else None
212
+ buf = [line] if current else []
213
+ continue
214
+ if current:
215
+ buf.append(line)
216
+
217
+ if current and current in wanted:
218
+ out[current] = buf
219
+ return {k: "\n".join(v).rstrip() + "\n" for k, v in out.items()}
220
+
221
+
222
+ def _rewrite_method_body(method_src: str, server_names: set[str]) -> str:
223
+ lines = method_src.splitlines()
224
+ if not lines:
225
+ return method_src
226
+ def_line = lines[0]
227
+ body_lines = lines[1:]
228
+ while body_lines and not body_lines[0].strip():
229
+ body_lines.pop(0)
230
+ if not body_lines:
231
+ return method_src
232
+
233
+ body_indent = len(body_lines[0]) - len(body_lines[0].lstrip())
234
+ dedented = textwrap.dedent("\n".join(body_lines))
235
+
236
+ # Lazy rewrite: prefix server-level bare names with srv.
237
+ tokens = set(re.findall(r"\b([A-Za-z_][A-Za-z0-9_]*)\b", dedented))
238
+ skip = {
239
+ "self",
240
+ "True",
241
+ "False",
242
+ "None",
243
+ "dict",
244
+ "list",
245
+ "str",
246
+ "int",
247
+ "bool",
248
+ "Any",
249
+ "Exception",
250
+ "ValueError",
251
+ "RuntimeError",
252
+ "OSError",
253
+ "sqlite3",
254
+ "json",
255
+ "os",
256
+ "re",
257
+ "time",
258
+ "uuid",
259
+ "secrets",
260
+ "urllib",
261
+ "Path",
262
+ "datetime",
263
+ "timezone",
264
+ "auth_rate_limit",
265
+ "google_auth",
266
+ "stack_config",
267
+ "APP_BASE_URL",
268
+ "action_proxy",
269
+ "internal_proxy_auth",
270
+ "llm_proxy",
271
+ "openrouter_catalog",
272
+ "platform_auth",
273
+ "run_surface_wiring",
274
+ "activity_feed",
275
+ "credential_vault",
276
+ "doctor_audit",
277
+ "site_meta",
278
+ "stack_updates",
279
+ "_supervisor_request",
280
+ }
281
+ replace_names = sorted((tokens & server_names) - skip, key=len, reverse=True)
282
+ for name in replace_names:
283
+ dedented = re.sub(rf"\b{name}\b", f"srv.{name}", dedented)
284
+
285
+ new_body = textwrap.indent(
286
+ "srv = _srv()\n" + dedented.rstrip() + "\n",
287
+ " " * body_indent,
288
+ )
289
+ return def_line + "\n" + new_body
290
+
291
+
292
+ def _build_module(filename: str, class_name: str, doc: str, methods: list[str], extracted: dict[str, str], server_names: set[str]) -> str:
293
+ parts = [f'"""{doc}"""', "", IMPORTS_BY_MODULE[filename].rstrip(), "", "", "def _srv():", " import server", "", " return server", "", "", f"class {class_name}:"]
294
+ missing = []
295
+ for name in methods:
296
+ if name not in extracted:
297
+ missing.append(name)
298
+ continue
299
+ rewritten = _rewrite_method_body(extracted[name], server_names)
300
+ parts.append("")
301
+ parts.append(textwrap.indent(rewritten.rstrip(), " "))
302
+ if missing:
303
+ raise SystemExit(f"{filename}: missing methods {missing}")
304
+ return "\n".join(parts) + "\n"
305
+
306
+
307
+ def main() -> None:
308
+ source = _read_server()
309
+ all_methods = set(AUTH_METHODS + PROVIDER_METHODS + ADMIN_METHODS)
310
+ extracted = _extract_handler_methods(source, all_methods)
311
+ server_names = _server_names()
312
+
313
+ for filename, (class_name, doc, methods) in MODULE_SPECS.items():
314
+ mod_path = ROOT / "handler_modules" / filename
315
+ mod_path.write_text(_build_module(filename, class_name, doc, methods, extracted, server_names), encoding="utf-8")
316
+ print(f"wrote {mod_path.name} ({len(methods)} methods)")
317
+
318
+ # Strip extracted methods from server.py Handler class
319
+ lines = source.splitlines(keepends=True)
320
+ in_handler = False
321
+ class_indent = 0
322
+ body_indent = 4
323
+ skip = False
324
+ out: list[str] = []
325
+ i = 0
326
+ while i < len(lines):
327
+ line = lines[i]
328
+ if not in_handler:
329
+ out.append(line)
330
+ if line.startswith("class Handler("):
331
+ in_handler = True
332
+ class_indent = len(line) - len(line.lstrip())
333
+ body_indent = class_indent + 4
334
+ i += 1
335
+ continue
336
+ stripped = line.lstrip()
337
+ indent = len(line) - len(stripped)
338
+ if indent <= class_indent and stripped and not stripped.startswith("#"):
339
+ skip = False
340
+ out.append(line)
341
+ i += 1
342
+ continue
343
+ if stripped.startswith("def ") and indent == body_indent:
344
+ name = stripped.split("(")[0].replace("def ", "").strip()
345
+ if name in all_methods:
346
+ skip = True
347
+ i += 1
348
+ continue
349
+ skip = False
350
+ out.append(line)
351
+ i += 1
352
+ continue
353
+ if skip:
354
+ i += 1
355
+ continue
356
+ out.append(line)
357
+ i += 1
358
+
359
+ new_source = "".join(out)
360
+ # Update Handler class bases
361
+ new_source = new_source.replace(
362
+ "class Handler(WorkspaceRoutesMixin, ChatRoutesMixin, InstallRoutesMixin, BaseHTTPRequestHandler):",
363
+ "class Handler(\n"
364
+ " AdminRoutesMixin,\n"
365
+ " ProviderRoutesMixin,\n"
366
+ " AuthRoutesMixin,\n"
367
+ " WorkspaceRoutesMixin,\n"
368
+ " ChatRoutesMixin,\n"
369
+ " InstallRoutesMixin,\n"
370
+ " BaseHTTPRequestHandler,\n"
371
+ "):",
372
+ )
373
+ new_source = new_source.replace(
374
+ "from handler_modules import ChatRoutesMixin, InstallRoutesMixin, WorkspaceRoutesMixin",
375
+ "from handler_modules import (\n"
376
+ " AdminRoutesMixin,\n"
377
+ " AuthRoutesMixin,\n"
378
+ " ChatRoutesMixin,\n"
379
+ " InstallRoutesMixin,\n"
380
+ " ProviderRoutesMixin,\n"
381
+ " WorkspaceRoutesMixin,\n"
382
+ ")",
383
+ )
384
+ # Remove WF-037 comment block before first remaining route if empty gap
385
+ new_source = re.sub(
386
+ r"\n # WF-037 data-read GET handlers \(registered in route_registry\.ROUTES\)\n\n",
387
+ "\n",
388
+ new_source,
389
+ count=1,
390
+ )
391
+ SERVER.write_text(new_source, encoding="utf-8")
392
+ print(f"patched server.py -> {(len(new_source.splitlines()))} lines")
393
+
394
+ init = ROOT / "handler_modules" / "__init__.py"
395
+ init.write_text(
396
+ '"""Handler route mixins extracted from server.Handler (WF-032)."""\n\n'
397
+ "from handler_modules.handler_admin import AdminRoutesMixin\n"
398
+ "from handler_modules.handler_auth import AuthRoutesMixin\n"
399
+ "from handler_modules.handler_chat import ChatRoutesMixin\n"
400
+ "from handler_modules.handler_install import InstallRoutesMixin\n"
401
+ "from handler_modules.handler_provider import ProviderRoutesMixin\n"
402
+ "from handler_modules.handler_workspace import WorkspaceRoutesMixin\n\n"
403
+ '__all__ = [\n'
404
+ ' "AdminRoutesMixin",\n'
405
+ ' "AuthRoutesMixin",\n'
406
+ ' "ChatRoutesMixin",\n'
407
+ ' "InstallRoutesMixin",\n'
408
+ ' "ProviderRoutesMixin",\n'
409
+ ' "WorkspaceRoutesMixin",\n'
410
+ "]\n",
411
+ encoding="utf-8",
412
+ )
413
+ print("updated handler_modules/__init__.py")
414
+
415
+
416
+ if __name__ == "__main__":
417
+ main()