MetaList 0.3.10__tar.gz → 0.3.12__tar.gz

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 (365) hide show
  1. {metalist-0.3.10 → metalist-0.3.12}/PKG-INFO +52 -69
  2. {metalist-0.3.10 → metalist-0.3.12}/README.md +47 -68
  3. {metalist-0.3.10 → metalist-0.3.12}/app/api/middleware/auth.py +30 -29
  4. {metalist-0.3.10 → metalist-0.3.12}/app/api/request_auth.py +12 -1
  5. {metalist-0.3.10 → metalist-0.3.12}/app/api/routes/auth.py +21 -22
  6. {metalist-0.3.10 → metalist-0.3.12}/app/api/routes/backups.py +49 -39
  7. {metalist-0.3.10 → metalist-0.3.12}/app/api/routes/notes.py +74 -2
  8. metalist-0.3.12/app/api/routes/remote_images.py +88 -0
  9. {metalist-0.3.10 → metalist-0.3.12}/app/config.py +5 -0
  10. {metalist-0.3.10 → metalist-0.3.12}/app/db/file_session.py +2 -2
  11. {metalist-0.3.10 → metalist-0.3.12}/app/db/migrations.py +13 -3
  12. {metalist-0.3.10 → metalist-0.3.12}/app/db/schema.py +24 -0
  13. metalist-0.3.12/app/db/version.py +3 -0
  14. {metalist-0.3.10 → metalist-0.3.12}/app/encryption_audit.py +123 -8
  15. {metalist-0.3.10 → metalist-0.3.12}/app/main.py +63 -42
  16. {metalist-0.3.10 → metalist-0.3.12}/app/models/database.py +2 -2
  17. {metalist-0.3.10 → metalist-0.3.12}/app/models/note_crud.py +6 -4
  18. {metalist-0.3.10 → metalist-0.3.12}/app/models/utils.py +22 -19
  19. metalist-0.3.12/app/security/http_headers.py +43 -0
  20. metalist-0.3.12/app/security/note_html.py +204 -0
  21. metalist-0.3.12/app/security/request_boundary.py +244 -0
  22. metalist-0.3.12/app/security/sensitive_logging.py +14 -0
  23. metalist-0.3.12/app/security/shell_execution.py +40 -0
  24. metalist-0.3.12/app/security/validation_errors.py +29 -0
  25. {metalist-0.3.10 → metalist-0.3.12}/app/server_runtime.py +22 -95
  26. {metalist-0.3.10 → metalist-0.3.12}/app/services/auth_service.py +43 -8
  27. {metalist-0.3.10 → metalist-0.3.12}/app/services/base_service.py +5 -2
  28. {metalist-0.3.10 → metalist-0.3.12}/app/services/client_state_service.py +3 -0
  29. {metalist-0.3.10 → metalist-0.3.12}/app/services/content_cache.py +4 -1
  30. {metalist-0.3.10 → metalist-0.3.12}/app/services/content_formatting.py +459 -28
  31. {metalist-0.3.10 → metalist-0.3.12}/app/services/diagnostics.py +36 -28
  32. {metalist-0.3.10 → metalist-0.3.12}/app/services/embedded_references.py +21 -4
  33. {metalist-0.3.10 → metalist-0.3.12}/app/services/html_export.py +2 -2
  34. {metalist-0.3.10 → metalist-0.3.12}/app/services/hydration_state.py +10 -0
  35. metalist-0.3.12/app/services/image_size_formatting.py +325 -0
  36. metalist-0.3.12/app/services/inline_image_occurrences.py +31 -0
  37. {metalist-0.3.10 → metalist-0.3.12}/app/services/link_titles.py +207 -22
  38. {metalist-0.3.10 → metalist-0.3.12}/app/services/namespace_deletion_worker.py +1 -3
  39. {metalist-0.3.10 → metalist-0.3.12}/app/services/namespace_rename_worker.py +0 -2
  40. {metalist-0.3.10 → metalist-0.3.12}/app/services/namespace_switcher.py +88 -75
  41. {metalist-0.3.10 → metalist-0.3.12}/app/services/ontology_rules_store.py +15 -0
  42. {metalist-0.3.10 → metalist-0.3.12}/app/services/query_service.py +0 -2
  43. metalist-0.3.12/app/services/remote_image_proxy.py +299 -0
  44. metalist-0.3.12/app/services/runtime_lock.py +67 -0
  45. {metalist-0.3.10 → metalist-0.3.12}/app/services/search_index.py +0 -3
  46. metalist-0.3.12/app/services/selected_text_tag.py +129 -0
  47. {metalist-0.3.10 → metalist-0.3.12}/app/services/snapshot.py +0 -3
  48. {metalist-0.3.10 → metalist-0.3.12}/app/services/sync_state.py +8 -0
  49. {metalist-0.3.10 → metalist-0.3.12}/app/services/tokens.py +4 -0
  50. {metalist-0.3.10 → metalist-0.3.12}/app/services/transaction_manager.py +6 -3
  51. {metalist-0.3.10 → metalist-0.3.12}/app/services/undo_state.py +5 -0
  52. {metalist-0.3.10 → metalist-0.3.12}/app/startup_sanity_config.py +1 -1
  53. {metalist-0.3.10 → metalist-0.3.12}/app/static/config/command_palette_tags.json +6 -6
  54. {metalist-0.3.10 → metalist-0.3.12}/app/static/css/main.css +314 -159
  55. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/main.js +3 -0
  56. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/api-client.js +43 -35
  57. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/auth.js +24 -17
  58. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/client-state-api.js +17 -2
  59. metalist-0.3.12/app/static/js/modules/command-palette/activation-auth-policy.js +27 -0
  60. metalist-0.3.12/app/static/js/modules/command-palette/backup-command-availability.js +49 -0
  61. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/command-palette/command-palette-controller.js +85 -18
  62. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/command-palette/endpoint-registry.js +59 -14
  63. metalist-0.3.12/app/static/js/modules/command-palette/note-layout-preferences.js +66 -0
  64. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/command-palette/preferences-store.js +20 -0
  65. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/config.js +4 -5
  66. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/connectivity-monitor.js +1 -1
  67. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/context-menu/context-menu-registry.js +177 -8
  68. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/context-menu/context-menu-service.js +237 -12
  69. metalist-0.3.12/app/static/js/modules/context-menu/context-menu-target-service.js +6 -0
  70. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/dom-utils-fixed.js +0 -1
  71. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/dom-utils.js +0 -1
  72. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/error-handler.js +4 -4
  73. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/login-namespace-picker.js +25 -7
  74. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/modals/backup-restore-modal.js +15 -7
  75. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/modals/backup-result-modal.js +1 -1
  76. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/modals/backup-retention-modal.js +2 -2
  77. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/modals/backup-settings-modal.js +70 -29
  78. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/modals/base-modal.js +29 -2
  79. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/modals/delete-namespace-modal.js +57 -23
  80. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/modals/delete-namespace-validation.js +19 -0
  81. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/modals/help-modal.js +7 -13
  82. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/modals/namespace-loading-page.js +14 -0
  83. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/modals/namespace-modals.js +23 -42
  84. metalist-0.3.12/app/static/js/modules/modals/note-layout-appearance-modal.js +169 -0
  85. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/modals/ontology-modal.js +1 -1
  86. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/modals/password-modal.js +16 -12
  87. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/modals/random-password-modal.js +150 -10
  88. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/modals/reminder-modal.js +1 -1
  89. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/modals/sound-manager-modal.js +1 -1
  90. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/modals/version-info-modal.js +1 -1
  91. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/actions/content-actions.js +3 -2
  92. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/actions/note-actions.js +89 -4
  93. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/actions/ui-actions.js +2 -1
  94. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/events/context-menu-events.js +273 -75
  95. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/events/keyboard-events.js +261 -311
  96. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/events/mouse-events.js +6 -4
  97. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/mode-context.js +1 -1
  98. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/mode-logger.js +7 -12
  99. metalist-0.3.12/app/static/js/modules/mode-manager/services/add-style-service.js +171 -0
  100. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/clipboard-shortcut-policy-service.js +30 -0
  101. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/context-menu-target-service.js +4 -0
  102. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/differential-view-service.js +47 -16
  103. metalist-0.3.12/app/static/js/modules/mode-manager/services/file-drop-policy-service.js +33 -0
  104. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/html-paste-sanitizer-service.js +10 -1
  105. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/image-context-menu-action-service.js +37 -0
  106. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/image-file-insert-choice-modal-service.js +6 -0
  107. metalist-0.3.12/app/static/js/modules/mode-manager/services/note-delete-shortcut-policy-service.js +31 -0
  108. metalist-0.3.12/app/static/js/modules/mode-manager/services/remote-image-proxy-service.js +216 -0
  109. metalist-0.3.12/app/static/js/modules/mode-manager/services/remove-formatting-service.js +502 -0
  110. metalist-0.3.12/app/static/js/modules/mode-manager/services/selected-formatting-removal-service.js +207 -0
  111. metalist-0.3.12/app/static/js/modules/mode-manager/services/selected-text-tag-service.js +63 -0
  112. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/tag-syntax-service.js +47 -12
  113. metalist-0.3.12/app/static/js/modules/note-html-sanitizer.js +193 -0
  114. metalist-0.3.12/app/static/js/modules/password-policy.js +23 -0
  115. metalist-0.3.12/app/static/js/modules/password-strength.js +81 -0
  116. metalist-0.3.12/app/static/js/vendor/dompurify-3.4.12.LICENSE.txt +202 -0
  117. metalist-0.3.12/app/static/js/vendor/dompurify-3.4.12.min.js +3 -0
  118. metalist-0.3.12/app/static/js/vendor/zxcvbn-4.4.2.LICENSE.txt +20 -0
  119. metalist-0.3.12/app/static/js/vendor/zxcvbn-4.4.2.js +28 -0
  120. metalist-0.3.12/app/static/note-html-policy.json +99 -0
  121. {metalist-0.3.10 → metalist-0.3.12}/app/templates/base.html +2 -1
  122. {metalist-0.3.10 → metalist-0.3.12}/app/templates/locked.html +1 -1
  123. {metalist-0.3.10 → metalist-0.3.12}/app/templates/maintenance.html +1 -1
  124. {metalist-0.3.10 → metalist-0.3.12}/app/templates/namespace_deleted.html +1 -1
  125. {metalist-0.3.10 → metalist-0.3.12}/app/templates/namespace_renamed.html +1 -1
  126. {metalist-0.3.10 → metalist-0.3.12}/app/templates/notes_list.html +1 -5
  127. metalist-0.3.12/app/usecases/add_selected_text_tag.py +104 -0
  128. {metalist-0.3.10 → metalist-0.3.12}/app/usecases/create_note.py +6 -2
  129. {metalist-0.3.10 → metalist-0.3.12}/app/usecases/delete_subtree.py +14 -3
  130. metalist-0.3.12/app/usecases/resize_image.py +83 -0
  131. {metalist-0.3.10 → metalist-0.3.12}/app/usecases/run_shell.py +18 -0
  132. {metalist-0.3.10 → metalist-0.3.12}/app/usecases/split_note.py +5 -3
  133. {metalist-0.3.10 → metalist-0.3.12}/app/usecases/unformat_content.py +12 -3
  134. {metalist-0.3.10 → metalist-0.3.12}/app/usecases/update_content.py +8 -5
  135. {metalist-0.3.10 → metalist-0.3.12}/app/version.py +1 -1
  136. {metalist-0.3.10 → metalist-0.3.12}/convert-from-legacy.py +2 -16
  137. {metalist-0.3.10 → metalist-0.3.12}/main.py +93 -202
  138. {metalist-0.3.10 → metalist-0.3.12}/metalist.egg-info/PKG-INFO +52 -69
  139. {metalist-0.3.10 → metalist-0.3.12}/metalist.egg-info/SOURCES.txt +35 -13
  140. {metalist-0.3.10 → metalist-0.3.12}/metalist.egg-info/entry_points.txt +0 -1
  141. {metalist-0.3.10 → metalist-0.3.12}/metalist.egg-info/requires.txt +4 -0
  142. metalist-0.3.12/metalist.egg-info/top_level.txt +2 -0
  143. {metalist-0.3.10 → metalist-0.3.12}/pyproject.toml +5 -2
  144. metalist-0.3.10/app/api/routes/mcp.py +0 -30
  145. metalist-0.3.10/app/api/routes/memory.py +0 -107
  146. metalist-0.3.10/app/mcp/__init__.py +0 -2
  147. metalist-0.3.10/app/mcp/__main__.py +0 -8
  148. metalist-0.3.10/app/mcp/errors.py +0 -18
  149. metalist-0.3.10/app/mcp/policy.py +0 -55
  150. metalist-0.3.10/app/mcp/read_service.py +0 -1124
  151. metalist-0.3.10/app/mcp/server.py +0 -230
  152. metalist-0.3.10/app/mcp/tools.py +0 -480
  153. metalist-0.3.10/app/services/memory.py +0 -9
  154. metalist-0.3.10/app/services/memory_service.py +0 -213
  155. metalist-0.3.10/app/static/js/modules/modals/memory-modal.js +0 -282
  156. metalist-0.3.10/mcp_client.py +0 -10083
  157. metalist-0.3.10/metalist.egg-info/top_level.txt +0 -3
  158. {metalist-0.3.10 → metalist-0.3.12}/MANIFEST.in +0 -0
  159. {metalist-0.3.10 → metalist-0.3.12}/app/__init__.py +0 -0
  160. {metalist-0.3.10 → metalist-0.3.12}/app/api/__init__.py +0 -0
  161. {metalist-0.3.10 → metalist-0.3.12}/app/api/deps.py +0 -0
  162. {metalist-0.3.10 → metalist-0.3.12}/app/api/dev.py +0 -0
  163. {metalist-0.3.10 → metalist-0.3.12}/app/api/routes/files.py +0 -0
  164. {metalist-0.3.10 → metalist-0.3.12}/app/api/routes/ontology.py +0 -0
  165. {metalist-0.3.10 → metalist-0.3.12}/app/api/routes/reminders.py +0 -0
  166. {metalist-0.3.10 → metalist-0.3.12}/app/api/routes/sounds.py +0 -0
  167. {metalist-0.3.10 → metalist-0.3.12}/app/api/routes/test.py +0 -0
  168. {metalist-0.3.10 → metalist-0.3.12}/app/api/transactions.py +0 -0
  169. {metalist-0.3.10 → metalist-0.3.12}/app/db/__init__.py +0 -0
  170. {metalist-0.3.10 → metalist-0.3.12}/app/db/backup_settings_sql.py +0 -0
  171. {metalist-0.3.10 → metalist-0.3.12}/app/db/engine.py +0 -0
  172. {metalist-0.3.10 → metalist-0.3.12}/app/db/file_schema.py +0 -0
  173. {metalist-0.3.10 → metalist-0.3.12}/app/db/files_sql.py +0 -0
  174. {metalist-0.3.10 → metalist-0.3.12}/app/db/link_titles_sql.py +0 -0
  175. {metalist-0.3.10 → metalist-0.3.12}/app/db/notes_sql.py +0 -0
  176. {metalist-0.3.10 → metalist-0.3.12}/app/db/ontology_rules_sql.py +0 -0
  177. {metalist-0.3.10 → metalist-0.3.12}/app/db/reminders_sql.py +0 -0
  178. {metalist-0.3.10 → metalist-0.3.12}/app/db/search_history_sql.py +0 -0
  179. {metalist-0.3.10 → metalist-0.3.12}/app/db/session.py +0 -0
  180. {metalist-0.3.10 → metalist-0.3.12}/app/db/settings_sql.py +0 -0
  181. {metalist-0.3.10 → metalist-0.3.12}/app/db/sounds_sql.py +0 -0
  182. {metalist-0.3.10 → metalist-0.3.12}/app/db/tab_state_sql.py +0 -0
  183. {metalist-0.3.10 → metalist-0.3.12}/app/models/commands.py +0 -0
  184. {metalist-0.3.10 → metalist-0.3.12}/app/models/entities.py +0 -0
  185. {metalist-0.3.10 → metalist-0.3.12}/app/models/enums.py +0 -0
  186. {metalist-0.3.10 → metalist-0.3.12}/app/models/linked_list.py +0 -0
  187. {metalist-0.3.10 → metalist-0.3.12}/app/models/list_operations.py +0 -0
  188. {metalist-0.3.10 → metalist-0.3.12}/app/models/list_traversal.py +0 -0
  189. {metalist-0.3.10 → metalist-0.3.12}/app/presentation/__init__.py +0 -0
  190. {metalist-0.3.10 → metalist-0.3.12}/app/presentation/render/__init__.py +0 -0
  191. {metalist-0.3.10 → metalist-0.3.12}/app/presentation/render/note_renderer.py +0 -0
  192. {metalist-0.3.10 → metalist-0.3.12}/app/presentation/templates.py +0 -0
  193. {metalist-0.3.10 → metalist-0.3.12}/app/security/encryption.py +0 -0
  194. {metalist-0.3.10 → metalist-0.3.12}/app/services/__init__.py +0 -0
  195. {metalist-0.3.10 → metalist-0.3.12}/app/services/auth.py +0 -0
  196. {metalist-0.3.10 → metalist-0.3.12}/app/services/auth_cache_state.py +0 -0
  197. {metalist-0.3.10 → metalist-0.3.12}/app/services/backlinks.py +0 -0
  198. {metalist-0.3.10 → metalist-0.3.12}/app/services/backup_service.py +0 -0
  199. {metalist-0.3.10 → metalist-0.3.12}/app/services/backup_settings_service.py +0 -0
  200. {metalist-0.3.10 → metalist-0.3.12}/app/services/date_filtering.py +0 -0
  201. {metalist-0.3.10 → metalist-0.3.12}/app/services/dependencies.py +0 -0
  202. {metalist-0.3.10 → metalist-0.3.12}/app/services/encryption.py +0 -0
  203. {metalist-0.3.10 → metalist-0.3.12}/app/services/exception_capture.py +0 -0
  204. {metalist-0.3.10 → metalist-0.3.12}/app/services/file_registry.py +0 -0
  205. {metalist-0.3.10 → metalist-0.3.12}/app/services/file_storage.py +0 -0
  206. {metalist-0.3.10 → metalist-0.3.12}/app/services/html_unformatting.py +0 -0
  207. {metalist-0.3.10 → metalist-0.3.12}/app/services/integrity.py +0 -0
  208. {metalist-0.3.10 → metalist-0.3.12}/app/services/latex_rendering.py +0 -0
  209. {metalist-0.3.10 → metalist-0.3.12}/app/services/login_rate_limit.py +0 -0
  210. {metalist-0.3.10 → metalist-0.3.12}/app/services/maintenance_mode.py +0 -0
  211. {metalist-0.3.10 → metalist-0.3.12}/app/services/markdown_rendering.py +0 -0
  212. {metalist-0.3.10 → metalist-0.3.12}/app/services/namespace_deletion_jobs.py +0 -0
  213. {metalist-0.3.10 → metalist-0.3.12}/app/services/namespace_rename_jobs.py +0 -0
  214. {metalist-0.3.10 → metalist-0.3.12}/app/services/note_image_tags.py +0 -0
  215. {metalist-0.3.10 → metalist-0.3.12}/app/services/note_service.py +0 -0
  216. {metalist-0.3.10 → metalist-0.3.12}/app/services/note_store.py +0 -0
  217. {metalist-0.3.10 → metalist-0.3.12}/app/services/reminders.py +0 -0
  218. {metalist-0.3.10 → metalist-0.3.12}/app/services/root_sorting.py +0 -0
  219. {metalist-0.3.10 → metalist-0.3.12}/app/services/runtime_hardening.py +0 -0
  220. {metalist-0.3.10 → metalist-0.3.12}/app/services/search_history.py +0 -0
  221. {metalist-0.3.10 → metalist-0.3.12}/app/services/search_query.py +0 -0
  222. {metalist-0.3.10 → metalist-0.3.12}/app/services/search_text.py +0 -0
  223. {metalist-0.3.10 → metalist-0.3.12}/app/services/self_update.py +0 -0
  224. {metalist-0.3.10 → metalist-0.3.12}/app/services/session_timeout_service.py +0 -0
  225. {metalist-0.3.10 → metalist-0.3.12}/app/services/shell_session_service.py +0 -0
  226. {metalist-0.3.10 → metalist-0.3.12}/app/services/sound_storage.py +0 -0
  227. {metalist-0.3.10 → metalist-0.3.12}/app/services/store.py +0 -0
  228. {metalist-0.3.10 → metalist-0.3.12}/app/services/sync.py +0 -0
  229. {metalist-0.3.10 → metalist-0.3.12}/app/services/tab_state.py +0 -0
  230. {metalist-0.3.10 → metalist-0.3.12}/app/services/tag_case.py +0 -0
  231. {metalist-0.3.10 → metalist-0.3.12}/app/services/tag_ontology.py +0 -0
  232. {metalist-0.3.10 → metalist-0.3.12}/app/services/tag_rename.py +0 -0
  233. {metalist-0.3.10 → metalist-0.3.12}/app/services/tag_suggestions.py +0 -0
  234. {metalist-0.3.10 → metalist-0.3.12}/app/services/tag_term_matching.py +0 -0
  235. {metalist-0.3.10 → metalist-0.3.12}/app/services/test_reset.py +0 -0
  236. {metalist-0.3.10 → metalist-0.3.12}/app/services/undo_service.py +0 -0
  237. {metalist-0.3.10 → metalist-0.3.12}/app/services/view_cache.py +0 -0
  238. {metalist-0.3.10 → metalist-0.3.12}/app/services/view_diff.py +0 -0
  239. {metalist-0.3.10 → metalist-0.3.12}/app/services/view_state.py +0 -0
  240. {metalist-0.3.10 → metalist-0.3.12}/app/services/windows_process_control.py +0 -0
  241. {metalist-0.3.10 → metalist-0.3.12}/app/startup_js_sanity.py +0 -0
  242. {metalist-0.3.10 → metalist-0.3.12}/app/startup_sanity.py +0 -0
  243. {metalist-0.3.10 → metalist-0.3.12}/app/static/css/forms.css +0 -0
  244. {metalist-0.3.10 → metalist-0.3.12}/app/static/img/favicon.ico +0 -0
  245. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/locked.js +0 -0
  246. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/activity-tracker.js +0 -0
  247. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/async-result.js +0 -0
  248. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/client-state-migration.js +0 -0
  249. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/command-palette/click-activation-service.js +0 -0
  250. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/command-palette/tag-config-loader.js +0 -0
  251. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/command-palette/usage-store.js +0 -0
  252. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/context-menu/input-caret-service.js +0 -0
  253. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/editor-commands.js +0 -0
  254. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/editor-selection.js +0 -0
  255. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/editor-toolbar.js +0 -0
  256. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/error-overlay.js +0 -0
  257. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/location-flags.js +0 -0
  258. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/modals/alphabetize-root-notes-modal.js +0 -0
  259. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/modals/namespace-ports-profile.js +0 -0
  260. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/modals/namespace-rename-validation.js +0 -0
  261. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/modals/prioritize-modal.js +0 -0
  262. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/modals/reset-updated-at-modal.js +0 -0
  263. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/modals/session-timeout-modal.js +0 -0
  264. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/actions/history-actions.js +0 -0
  265. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/actions/search-actions.js +0 -0
  266. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/actions/selection-actions.js +0 -0
  267. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/events/focus-events.js +0 -0
  268. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/events/inactivity-events.js +0 -0
  269. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/events/input-events.js +0 -0
  270. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/events/search-events.js +0 -0
  271. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/mode-manager-controller.js +0 -0
  272. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/animated-scroll-service.js +0 -0
  273. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/backlinks-panel-service.js +0 -0
  274. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/collapse-affordance-service.js +0 -0
  275. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/collapse-editing-policy-service.js +0 -0
  276. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/command-gate-service.js +0 -0
  277. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/date-filter-indicator-service.js +0 -0
  278. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/date-filter-service.js +0 -0
  279. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/edit-session-collapse-policy-service.js +0 -0
  280. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/edit-session-collapse-service.js +0 -0
  281. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/embedded-image-service.js +0 -0
  282. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/file-image-preview-service.js +0 -0
  283. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/file-reference-service.js +0 -0
  284. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/filtered-refresh-selection-service.js +0 -0
  285. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/infinite-scroll-service.js +0 -0
  286. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/link-context-menu-action-service.js +0 -0
  287. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/markdown-render-service.js +0 -0
  288. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/note-click-target-service.js +0 -0
  289. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/note-clipboard-write-service.js +0 -0
  290. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/note-collapse-animation-service.js +0 -0
  291. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/note-context-menu-action-service.js +0 -0
  292. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/note-drag-service.js +0 -0
  293. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/note-reposition-animation-service.js +0 -0
  294. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/note-split-service.js +0 -0
  295. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/password-clipboard-service.js +0 -0
  296. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/polling-service.js +0 -0
  297. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/rhs-panel-service.js +0 -0
  298. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/root-date-separator-service.js +0 -0
  299. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/root-sort-indicator-service.js +0 -0
  300. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/root-sort-service.js +0 -0
  301. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/scroll-anchor-service.js +0 -0
  302. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/scroll-restoration-service.js +0 -0
  303. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/scroll-to-top-service.js +0 -0
  304. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/search-contexts-overlay-service.js +0 -0
  305. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/search-debounce-service.js +0 -0
  306. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/search-input-service.js +0 -0
  307. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/search-interaction-service.js +0 -0
  308. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/search-redaction-reveal-service.js +0 -0
  309. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/search-suggestions-service.js +0 -0
  310. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/search-syntax-service.js +0 -0
  311. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/shell-output-service.js +0 -0
  312. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/tab-dom-cache-service.js +0 -0
  313. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/tab-duplication-service.js +0 -0
  314. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/tab-state-service.js +0 -0
  315. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/tag-bar-service.js +0 -0
  316. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/tag-suggestions-service.js +0 -0
  317. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/todo-toggle-editing-service.js +0 -0
  318. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/view-mode-search-shortcut-service.js +0 -0
  319. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/password-generator.js +0 -0
  320. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/reminder-store.js +0 -0
  321. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/reminder-surface-service.js +0 -0
  322. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/selection-markers.js +0 -0
  323. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/session-auth.js +0 -0
  324. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/sound-service.js +0 -0
  325. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/tag-token.js +0 -0
  326. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/modules/uuid.js +0 -0
  327. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/namespace-deleted.js +0 -0
  328. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/namespace-renamed.js +0 -0
  329. {metalist-0.3.10 → metalist-0.3.12}/app/static/js/vendor/markdown-it.min.js +0 -0
  330. {metalist-0.3.10 → metalist-0.3.12}/app/static/video/login-startup.mp4 +0 -0
  331. {metalist-0.3.10 → metalist-0.3.12}/app/templates/index.html +0 -0
  332. {metalist-0.3.10 → metalist-0.3.12}/app/usecases/__init__.py +0 -0
  333. {metalist-0.3.10 → metalist-0.3.12}/app/usecases/alphabetize_root_notes.py +0 -0
  334. {metalist-0.3.10 → metalist-0.3.12}/app/usecases/base.py +0 -0
  335. {metalist-0.3.10 → metalist-0.3.12}/app/usecases/collapse.py +0 -0
  336. {metalist-0.3.10 → metalist-0.3.12}/app/usecases/copy_note.py +0 -0
  337. {metalist-0.3.10 → metalist-0.3.12}/app/usecases/create_child.py +0 -0
  338. {metalist-0.3.10 → metalist-0.3.12}/app/usecases/create_sibling.py +0 -0
  339. {metalist-0.3.10 → metalist-0.3.12}/app/usecases/expand.py +0 -0
  340. {metalist-0.3.10 → metalist-0.3.12}/app/usecases/indent.py +0 -0
  341. {metalist-0.3.10 → metalist-0.3.12}/app/usecases/lock.py +0 -0
  342. {metalist-0.3.10 → metalist-0.3.12}/app/usecases/move.py +0 -0
  343. {metalist-0.3.10 → metalist-0.3.12}/app/usecases/move_to_top.py +0 -0
  344. {metalist-0.3.10 → metalist-0.3.12}/app/usecases/outdent.py +0 -0
  345. {metalist-0.3.10 → metalist-0.3.12}/app/usecases/paste_child.py +0 -0
  346. {metalist-0.3.10 → metalist-0.3.12}/app/usecases/paste_sibling.py +0 -0
  347. {metalist-0.3.10 → metalist-0.3.12}/app/usecases/prioritize.py +0 -0
  348. {metalist-0.3.10 → metalist-0.3.12}/app/usecases/record_edit_mode.py +0 -0
  349. {metalist-0.3.10 → metalist-0.3.12}/app/usecases/redo.py +0 -0
  350. {metalist-0.3.10 → metalist-0.3.12}/app/usecases/rename_tag.py +0 -0
  351. {metalist-0.3.10 → metalist-0.3.12}/app/usecases/reset_updated_at.py +0 -0
  352. {metalist-0.3.10 → metalist-0.3.12}/app/usecases/search_comment_autofill.py +0 -0
  353. {metalist-0.3.10 → metalist-0.3.12}/app/usecases/search_context_tags.py +0 -0
  354. {metalist-0.3.10 → metalist-0.3.12}/app/usecases/set_collapse_bulk.py +0 -0
  355. {metalist-0.3.10 → metalist-0.3.12}/app/usecases/set_collapse_in_context.py +0 -0
  356. {metalist-0.3.10 → metalist-0.3.12}/app/usecases/toggle_reference_mode.py +0 -0
  357. {metalist-0.3.10 → metalist-0.3.12}/app/usecases/toggle_todo_done.py +0 -0
  358. {metalist-0.3.10 → metalist-0.3.12}/app/usecases/undo.py +0 -0
  359. {metalist-0.3.10 → metalist-0.3.12}/app/usecases/view.py +0 -0
  360. {metalist-0.3.10 → metalist-0.3.12}/app/utils/__init__.py +0 -0
  361. {metalist-0.3.10 → metalist-0.3.12}/app/utils/encryption.py +0 -0
  362. {metalist-0.3.10 → metalist-0.3.12}/app/utils/text_utils.py +0 -0
  363. {metalist-0.3.10 → metalist-0.3.12}/metalist.egg-info/dependency_links.txt +0 -0
  364. {metalist-0.3.10 → metalist-0.3.12}/scripts/generate-lan-cert.sh +0 -0
  365. {metalist-0.3.10 → metalist-0.3.12}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metalist
3
- Version: 0.3.10
3
+ Version: 0.3.12
4
4
  Summary: A minimalist single-user note-taking app with SSR, fast in-memory tree operations, and efficient sync.
5
5
  Project-URL: Homepage, https://github.com/evolvingstuff/metalist
6
6
  Project-URL: Repository, https://github.com/evolvingstuff/metalist
@@ -10,11 +10,14 @@ Description-Content-Type: text/markdown
10
10
  Requires-Dist: argon2-cffi==25.1.0
11
11
  Requires-Dist: cryptography==50.0.0
12
12
  Requires-Dist: fastapi==0.141.1
13
+ Requires-Dist: httpcore==1.0.9
13
14
  Requires-Dist: httpx==0.28.1
14
15
  Requires-Dist: latex2mathml==3.81.0
15
16
  Requires-Dist: loguru==0.7.3
16
17
  Requires-Dist: Mako==1.3.12
17
18
  Requires-Dist: mutagen==1.48.1
19
+ Requires-Dist: nh3==0.3.6
20
+ Requires-Dist: pillow==12.3.0
18
21
  Requires-Dist: pydantic==2.13.4
19
22
  Requires-Dist: python-multipart==0.0.32
20
23
  Requires-Dist: starlette==1.3.1
@@ -22,6 +25,7 @@ Requires-Dist: tqdm==4.70.0
22
25
  Requires-Dist: tree-sitter==0.25.2
23
26
  Requires-Dist: tree-sitter-javascript==0.25.0
24
27
  Requires-Dist: uvicorn==0.52.0
28
+ Requires-Dist: zxcvbn==4.5.0
25
29
  Provides-Extra: dev
26
30
  Requires-Dist: hypothesis==6.164.0; extra == "dev"
27
31
  Requires-Dist: pytest==9.1.1; extra == "dev"
@@ -35,7 +39,7 @@ A minimalist single-user note-taking app focused on server-side rendering (SSR),
35
39
  - Rich text editing (ContentEditable) with image support
36
40
  - Drag-and-drop note reordering
37
41
  - Real-time content saving
38
- - Keyboard shortcuts (press `?` in the app)
42
+ - Keyboard shortcuts / cheatsheet (press `?` in the app)
39
43
  - Linked-list ordering model for efficient reorders
40
44
  - Optional password protection + encryption at rest (AES-GCM)
41
45
  - Multi-tab search contexts with server-persisted scroll/search state (survives browser restarts)
@@ -99,8 +103,22 @@ metalist
99
103
  ```
100
104
  For source-checkout compatibility, `python main.py` performs the same orchestration. Use `metalist work`, `python main.py --namespace work`, or `python main.py work` when you want one foreground namespace process.
101
105
 
102
- `metalist` and explicit single-namespace source runs bind HTTP on `0.0.0.0:8000` by default, matching the old MetaList LAN-friendly behavior.
103
- On first startup, MetaList also auto-generates a self-signed TLS pair at `~/MetaList/certs/metalist-cert.pem` and `~/MetaList/certs/metalist-key.pem`, then enables HTTPS on `0.0.0.0:8443`. If you already have real PEM files, point `METALIST_TLS_CERT` and `METALIST_TLS_KEY` at them instead. Set `METALIST_AUTO_GENERATE_TLS=0` only if you explicitly want HTTP-only startup.
106
+ Shell execution is disabled by default. To enable `@shell` for every namespace
107
+ started by the top-level orchestrator, use either:
108
+
109
+ ```bash
110
+ metalist --enable-shell
111
+ python main.py --enable-shell
112
+ ```
113
+
114
+ This flag is propagated to every namespace child process. The top-level process
115
+ prints a conspicuous shell-enabled banner before its namespace launch results.
116
+ Use `python main.py work --enable-shell` for one foreground namespace. Shell
117
+ routes remain restricted to loopback clients using a loopback request host,
118
+ even when the rest of MetaList is intentionally exposed to a LAN.
119
+
120
+ `metalist` and explicit single-namespace source runs bind to loopback at `127.0.0.1:8000` by default. This keeps a normal laptop launch off LAN and public interfaces unless remote access is explicitly configured.
121
+ On first startup, MetaList also auto-generates a self-signed TLS pair at `~/MetaList/certs/metalist-cert.pem` and `~/MetaList/certs/metalist-key.pem`, then enables HTTPS on the same bind host at port `8443`. If you already have real PEM files, point `METALIST_TLS_CERT` and `METALIST_TLS_KEY` at them instead. Set `METALIST_AUTO_GENERATE_TLS=0` only if you explicitly want HTTP-only startup.
104
122
 
105
123
  Database selection:
106
124
  - No explicit namespace on a single-namespace launch: `~/MetaList/namespaces/default/default.metalist.db`
@@ -116,26 +134,40 @@ Useful env flags:
116
134
  - `CRASH_SERVER_ON_FAIL=1` (default): fail-fast on validation errors
117
135
  - `API_PREFIX=/api2`: override API prefix (client assumes `/api2` by default)
118
136
  - `METALIST_NAMESPACE=work`: select `~/MetaList/namespaces/work/work.metalist.db`
119
- - `METALIST_HOST=0.0.0.0` (default): bind the main app to a different interface such as `127.0.0.1`
137
+ - `METALIST_HOST=127.0.0.1` (default): bind the main app to a specific interface; use a LAN IP or `0.0.0.0` only for intentional remote access
138
+ - `METALIST_ALLOWED_HOSTS=notes.example.com,192.168.1.20`: comma-separated public/LAN hostnames accepted in HTTP `Host` headers; loopback aliases and a specific non-wildcard `METALIST_HOST` are accepted automatically
120
139
  - `METALIST_PORT=8000` (default): bind the main app to a different port
121
140
  - `METALIST_HTTPS_PORT=8443`: override the HTTPS port when TLS is enabled
122
- - `MCP_AGENT_WEB_PORT=8765` (default): bind the MCP sidecar web UI to a different port
123
141
  - `METALIST_TLS_CERT=/path/to/fullchain.pem` + `METALIST_TLS_KEY=/path/to/privkey.pem`: override TLS paths
124
142
  - `METALIST_AUTO_GENERATE_TLS=0`: disable automatic creation of the default self-signed TLS pair
125
143
  - default TLS paths: `~/MetaList/certs/metalist-cert.pem` and `~/MetaList/certs/metalist-key.pem`
126
144
  - `METALIST_FORWARDED_ALLOW_IPS=127.0.0.1,::1` (default): trust proxy headers only from those reverse-proxy IPs
127
- - `MCP_AGENT_PUBLIC_ORIGIN=https://notes.example.com:8765`: public origin for the MCP sidecar redirect when it is exposed behind HTTPS or a separate hostname/port
145
+ - `--enable-shell`: opt in to local `@shell` execution for this launch; shell routes require a loopback client and loopback request host, and the capability is never persisted in namespace data
128
146
 
129
147
  ### Remote Access / HTTPS
130
- Plain LAN or VPN HTTP works with a normal PyCharm run:
148
+ LAN or VPN access must be enabled explicitly. Prefer binding the machine's specific LAN address, which also adds that address to the accepted-host set:
131
149
  ```bash
132
- metalist
150
+ METALIST_HOST=192.168.1.20 metalist
151
+ ```
152
+ On a fresh machine, that first launch also creates the default TLS cert pair automatically. Then open either `http://192.168.1.20:8000` or `https://192.168.1.20:8443` from the other machine.
153
+
154
+ To keep `@shell` available on the host laptop while allowing another laptop to
155
+ use normal MetaList features, bind both interfaces and explicitly allow the LAN
156
+ address:
157
+
158
+ ```bash
159
+ METALIST_HOST=0.0.0.0 \
160
+ METALIST_ALLOWED_HOSTS=10.0.0.31 \
161
+ metalist --enable-shell
133
162
  ```
134
- On a fresh machine, that first launch also creates the default TLS cert pair automatically. Then open either `http://<laptop-ip>:8000` or `https://<laptop-ip>:8443` from the other machine.
163
+
164
+ Use `http://127.0.0.1:<namespace-http-port>` on the host laptop when running
165
+ `@shell`. Other devices may use `https://10.0.0.31:<namespace-https-port>`, but
166
+ their shell start/status requests receive `403`.
135
167
 
136
168
  Namespaced launch example:
137
169
  ```bash
138
- metalist --namespace work --port 8001 --mcp-port 8766
170
+ metalist --namespace work --port 8001
139
171
  ```
140
172
  This starts a separate process backed by `~/MetaList/namespaces/work/work.metalist.db` on `http://127.0.0.1:8001`.
141
173
  Its backup snapshots live under `~/MetaList/namespaces/work/backups/` with filenames like `work-<timestamp>.metalist-backup.tar.gz`. New backups are versioned `.tar.gz` workspace archives; legacy `.bak` backups remain restorable.
@@ -144,11 +176,12 @@ After you launch a namespace once with explicit ports, MetaList remembers them i
144
176
  ```bash
145
177
  metalist work
146
178
  ```
147
- and MetaList will reuse the saved HTTP / HTTPS / MCP sidecar ports for `work`. The same applies to the default namespace: `metalist` will reuse the saved default-namespace profile.
179
+ and MetaList will reuse the saved HTTP / HTTPS ports for `work`. The same applies to the default namespace: `metalist` will reuse the saved default-namespace profile.
148
180
 
149
181
  Equivalent explicit launch, if you want it:
150
182
  ```bash
151
183
  METALIST_HOST=0.0.0.0 \
184
+ METALIST_ALLOWED_HOSTS=192.168.1.20 \
152
185
  METALIST_PORT=8000 \
153
186
  METALIST_HTTPS_PORT=8443 \
154
187
  metalist
@@ -158,6 +191,7 @@ From the other machine, open `https://<laptop-ip>:8443`.
158
191
  If you already have a real certificate and key, use the same dual-listener flow:
159
192
  ```bash
160
193
  METALIST_HOST=0.0.0.0 \
194
+ METALIST_ALLOWED_HOSTS=192.168.1.20 \
161
195
  METALIST_PORT=8000 \
162
196
  METALIST_HTTPS_PORT=8443 \
163
197
  METALIST_TLS_CERT=/path/to/fullchain.pem \
@@ -177,67 +211,16 @@ When HTTPS is enabled:
177
211
  If TLS is terminated by a reverse proxy on the same machine instead, keep MetaList on loopback and let the proxy forward to it:
178
212
  ```bash
179
213
  METALIST_HOST=127.0.0.1 \
214
+ METALIST_ALLOWED_HOSTS=notes.example.com \
180
215
  METALIST_PORT=8000 \
181
216
  METALIST_FORWARDED_ALLOW_IPS=127.0.0.1,::1 \
182
217
  metalist
183
218
  ```
184
219
 
185
- If you do not need the MCP sidecar remotely, disable it:
186
- ```bash
187
- MCP_AGENT_WEB_ENABLED=0 metalist
188
- ```
189
-
190
- ### MCP (Phase 1 Read-Only)
191
- MCP is available automatically when you run:
192
- ```bash
193
- metalist
194
- ```
195
-
196
- `metalist` also auto-starts the agent web app sidecar and prints:
197
- - `Agent web app: http://127.0.0.1:8765`
198
- - The sidecar default MCP URL follows the resolved MetaList HTTP port for the current process.
199
- - Use `--mcp-port` when you want multiple MetaList instances to auto-start sidecars without colliding on `8765`.
200
- - On startup, local Ollama (`127.0.0.1`) is reset by default so a fresh runner is used.
201
- - Sidecar Ollama auto-start uses `OLLAMA_CONTEXT_LENGTH=16384` by default.
202
-
203
- Manual web mode (optional):
204
- ```bash
205
- metalist-mcp web --port 8765
206
- ```
207
- Then open `http://127.0.0.1:8765`.
208
-
209
- Run direct MCP CLI calls:
210
- ```bash
211
- metalist-mcp cli tools/list
212
- metalist-mcp cli tools/call health_check '{}'
213
- ```
214
-
215
- Compatibility shortcut (still works):
216
- ```bash
217
- python mcp_client.py tools/list
218
- ```
219
-
220
- Disable auto sidecar if needed:
221
- ```bash
222
- MCP_AGENT_WEB_ENABLED=0 metalist
223
- ```
224
-
225
- Control Ollama startup behavior:
226
- ```bash
227
- # disable Ollama reset-on-start (default is enabled)
228
- MCP_AGENT_RESET_OLLAMA_ON_START=0 metalist
229
-
230
- # override auto-start context length (default 16384)
231
- MCP_AGENT_OLLAMA_CONTEXT_LENGTH=32768 metalist
232
- ```
233
-
234
- Optional: direct stdio transport (advanced/manual):
235
- ```bash
236
- python -m app.mcp
237
- ```
238
-
239
- Tool catalog and schemas:
240
- - `docs/mcp_tools.md`
220
+ The reverse proxy must preserve the browser-facing `Host` header and set
221
+ `X-Forwarded-Proto`. MetaList never trusts `X-Forwarded-Host`; forwarded
222
+ scheme/client metadata is accepted only from `METALIST_FORWARDED_ALLOW_IPS`.
223
+ Do not widen that list beyond the actual proxy addresses.
241
224
 
242
225
  ### Legacy Import
243
226
  `convert-from-legacy.py` replaces the SQLite database referenced by `app.config.DATABASE_URL`, clears its files/sounds sidecar, and imports notes from a legacy JSON export.
@@ -254,7 +237,7 @@ Target a namespaced database during import:
254
237
  convert-from-legacy.py --namespace work --input /path/to/legacy-export.json
255
238
  ```
256
239
 
257
- If `--namespace`, `--port`, `--https-port`, or `--mcp-port` are omitted, the import script prompts for them and saves the resulting launch profile inside the target namespace DB. That means a one-time import into `work` can immediately seed later shorthand launches like `metalist work`.
240
+ If `--namespace`, `--port`, or `--https-port` are omitted, the import script prompts for them and saves the resulting launch profile inside the target namespace DB. That means a one-time import into `work` can immediately seed later shorthand launches like `metalist work`.
258
241
 
259
242
  ### Publishing
260
243
  For the real user-facing install flow:
@@ -6,7 +6,7 @@ A minimalist single-user note-taking app focused on server-side rendering (SSR),
6
6
  - Rich text editing (ContentEditable) with image support
7
7
  - Drag-and-drop note reordering
8
8
  - Real-time content saving
9
- - Keyboard shortcuts (press `?` in the app)
9
+ - Keyboard shortcuts / cheatsheet (press `?` in the app)
10
10
  - Linked-list ordering model for efficient reorders
11
11
  - Optional password protection + encryption at rest (AES-GCM)
12
12
  - Multi-tab search contexts with server-persisted scroll/search state (survives browser restarts)
@@ -70,8 +70,22 @@ metalist
70
70
  ```
71
71
  For source-checkout compatibility, `python main.py` performs the same orchestration. Use `metalist work`, `python main.py --namespace work`, or `python main.py work` when you want one foreground namespace process.
72
72
 
73
- `metalist` and explicit single-namespace source runs bind HTTP on `0.0.0.0:8000` by default, matching the old MetaList LAN-friendly behavior.
74
- On first startup, MetaList also auto-generates a self-signed TLS pair at `~/MetaList/certs/metalist-cert.pem` and `~/MetaList/certs/metalist-key.pem`, then enables HTTPS on `0.0.0.0:8443`. If you already have real PEM files, point `METALIST_TLS_CERT` and `METALIST_TLS_KEY` at them instead. Set `METALIST_AUTO_GENERATE_TLS=0` only if you explicitly want HTTP-only startup.
73
+ Shell execution is disabled by default. To enable `@shell` for every namespace
74
+ started by the top-level orchestrator, use either:
75
+
76
+ ```bash
77
+ metalist --enable-shell
78
+ python main.py --enable-shell
79
+ ```
80
+
81
+ This flag is propagated to every namespace child process. The top-level process
82
+ prints a conspicuous shell-enabled banner before its namespace launch results.
83
+ Use `python main.py work --enable-shell` for one foreground namespace. Shell
84
+ routes remain restricted to loopback clients using a loopback request host,
85
+ even when the rest of MetaList is intentionally exposed to a LAN.
86
+
87
+ `metalist` and explicit single-namespace source runs bind to loopback at `127.0.0.1:8000` by default. This keeps a normal laptop launch off LAN and public interfaces unless remote access is explicitly configured.
88
+ On first startup, MetaList also auto-generates a self-signed TLS pair at `~/MetaList/certs/metalist-cert.pem` and `~/MetaList/certs/metalist-key.pem`, then enables HTTPS on the same bind host at port `8443`. If you already have real PEM files, point `METALIST_TLS_CERT` and `METALIST_TLS_KEY` at them instead. Set `METALIST_AUTO_GENERATE_TLS=0` only if you explicitly want HTTP-only startup.
75
89
 
76
90
  Database selection:
77
91
  - No explicit namespace on a single-namespace launch: `~/MetaList/namespaces/default/default.metalist.db`
@@ -87,26 +101,40 @@ Useful env flags:
87
101
  - `CRASH_SERVER_ON_FAIL=1` (default): fail-fast on validation errors
88
102
  - `API_PREFIX=/api2`: override API prefix (client assumes `/api2` by default)
89
103
  - `METALIST_NAMESPACE=work`: select `~/MetaList/namespaces/work/work.metalist.db`
90
- - `METALIST_HOST=0.0.0.0` (default): bind the main app to a different interface such as `127.0.0.1`
104
+ - `METALIST_HOST=127.0.0.1` (default): bind the main app to a specific interface; use a LAN IP or `0.0.0.0` only for intentional remote access
105
+ - `METALIST_ALLOWED_HOSTS=notes.example.com,192.168.1.20`: comma-separated public/LAN hostnames accepted in HTTP `Host` headers; loopback aliases and a specific non-wildcard `METALIST_HOST` are accepted automatically
91
106
  - `METALIST_PORT=8000` (default): bind the main app to a different port
92
107
  - `METALIST_HTTPS_PORT=8443`: override the HTTPS port when TLS is enabled
93
- - `MCP_AGENT_WEB_PORT=8765` (default): bind the MCP sidecar web UI to a different port
94
108
  - `METALIST_TLS_CERT=/path/to/fullchain.pem` + `METALIST_TLS_KEY=/path/to/privkey.pem`: override TLS paths
95
109
  - `METALIST_AUTO_GENERATE_TLS=0`: disable automatic creation of the default self-signed TLS pair
96
110
  - default TLS paths: `~/MetaList/certs/metalist-cert.pem` and `~/MetaList/certs/metalist-key.pem`
97
111
  - `METALIST_FORWARDED_ALLOW_IPS=127.0.0.1,::1` (default): trust proxy headers only from those reverse-proxy IPs
98
- - `MCP_AGENT_PUBLIC_ORIGIN=https://notes.example.com:8765`: public origin for the MCP sidecar redirect when it is exposed behind HTTPS or a separate hostname/port
112
+ - `--enable-shell`: opt in to local `@shell` execution for this launch; shell routes require a loopback client and loopback request host, and the capability is never persisted in namespace data
99
113
 
100
114
  ### Remote Access / HTTPS
101
- Plain LAN or VPN HTTP works with a normal PyCharm run:
115
+ LAN or VPN access must be enabled explicitly. Prefer binding the machine's specific LAN address, which also adds that address to the accepted-host set:
102
116
  ```bash
103
- metalist
117
+ METALIST_HOST=192.168.1.20 metalist
118
+ ```
119
+ On a fresh machine, that first launch also creates the default TLS cert pair automatically. Then open either `http://192.168.1.20:8000` or `https://192.168.1.20:8443` from the other machine.
120
+
121
+ To keep `@shell` available on the host laptop while allowing another laptop to
122
+ use normal MetaList features, bind both interfaces and explicitly allow the LAN
123
+ address:
124
+
125
+ ```bash
126
+ METALIST_HOST=0.0.0.0 \
127
+ METALIST_ALLOWED_HOSTS=10.0.0.31 \
128
+ metalist --enable-shell
104
129
  ```
105
- On a fresh machine, that first launch also creates the default TLS cert pair automatically. Then open either `http://<laptop-ip>:8000` or `https://<laptop-ip>:8443` from the other machine.
130
+
131
+ Use `http://127.0.0.1:<namespace-http-port>` on the host laptop when running
132
+ `@shell`. Other devices may use `https://10.0.0.31:<namespace-https-port>`, but
133
+ their shell start/status requests receive `403`.
106
134
 
107
135
  Namespaced launch example:
108
136
  ```bash
109
- metalist --namespace work --port 8001 --mcp-port 8766
137
+ metalist --namespace work --port 8001
110
138
  ```
111
139
  This starts a separate process backed by `~/MetaList/namespaces/work/work.metalist.db` on `http://127.0.0.1:8001`.
112
140
  Its backup snapshots live under `~/MetaList/namespaces/work/backups/` with filenames like `work-<timestamp>.metalist-backup.tar.gz`. New backups are versioned `.tar.gz` workspace archives; legacy `.bak` backups remain restorable.
@@ -115,11 +143,12 @@ After you launch a namespace once with explicit ports, MetaList remembers them i
115
143
  ```bash
116
144
  metalist work
117
145
  ```
118
- and MetaList will reuse the saved HTTP / HTTPS / MCP sidecar ports for `work`. The same applies to the default namespace: `metalist` will reuse the saved default-namespace profile.
146
+ and MetaList will reuse the saved HTTP / HTTPS ports for `work`. The same applies to the default namespace: `metalist` will reuse the saved default-namespace profile.
119
147
 
120
148
  Equivalent explicit launch, if you want it:
121
149
  ```bash
122
150
  METALIST_HOST=0.0.0.0 \
151
+ METALIST_ALLOWED_HOSTS=192.168.1.20 \
123
152
  METALIST_PORT=8000 \
124
153
  METALIST_HTTPS_PORT=8443 \
125
154
  metalist
@@ -129,6 +158,7 @@ From the other machine, open `https://<laptop-ip>:8443`.
129
158
  If you already have a real certificate and key, use the same dual-listener flow:
130
159
  ```bash
131
160
  METALIST_HOST=0.0.0.0 \
161
+ METALIST_ALLOWED_HOSTS=192.168.1.20 \
132
162
  METALIST_PORT=8000 \
133
163
  METALIST_HTTPS_PORT=8443 \
134
164
  METALIST_TLS_CERT=/path/to/fullchain.pem \
@@ -148,67 +178,16 @@ When HTTPS is enabled:
148
178
  If TLS is terminated by a reverse proxy on the same machine instead, keep MetaList on loopback and let the proxy forward to it:
149
179
  ```bash
150
180
  METALIST_HOST=127.0.0.1 \
181
+ METALIST_ALLOWED_HOSTS=notes.example.com \
151
182
  METALIST_PORT=8000 \
152
183
  METALIST_FORWARDED_ALLOW_IPS=127.0.0.1,::1 \
153
184
  metalist
154
185
  ```
155
186
 
156
- If you do not need the MCP sidecar remotely, disable it:
157
- ```bash
158
- MCP_AGENT_WEB_ENABLED=0 metalist
159
- ```
160
-
161
- ### MCP (Phase 1 Read-Only)
162
- MCP is available automatically when you run:
163
- ```bash
164
- metalist
165
- ```
166
-
167
- `metalist` also auto-starts the agent web app sidecar and prints:
168
- - `Agent web app: http://127.0.0.1:8765`
169
- - The sidecar default MCP URL follows the resolved MetaList HTTP port for the current process.
170
- - Use `--mcp-port` when you want multiple MetaList instances to auto-start sidecars without colliding on `8765`.
171
- - On startup, local Ollama (`127.0.0.1`) is reset by default so a fresh runner is used.
172
- - Sidecar Ollama auto-start uses `OLLAMA_CONTEXT_LENGTH=16384` by default.
173
-
174
- Manual web mode (optional):
175
- ```bash
176
- metalist-mcp web --port 8765
177
- ```
178
- Then open `http://127.0.0.1:8765`.
179
-
180
- Run direct MCP CLI calls:
181
- ```bash
182
- metalist-mcp cli tools/list
183
- metalist-mcp cli tools/call health_check '{}'
184
- ```
185
-
186
- Compatibility shortcut (still works):
187
- ```bash
188
- python mcp_client.py tools/list
189
- ```
190
-
191
- Disable auto sidecar if needed:
192
- ```bash
193
- MCP_AGENT_WEB_ENABLED=0 metalist
194
- ```
195
-
196
- Control Ollama startup behavior:
197
- ```bash
198
- # disable Ollama reset-on-start (default is enabled)
199
- MCP_AGENT_RESET_OLLAMA_ON_START=0 metalist
200
-
201
- # override auto-start context length (default 16384)
202
- MCP_AGENT_OLLAMA_CONTEXT_LENGTH=32768 metalist
203
- ```
204
-
205
- Optional: direct stdio transport (advanced/manual):
206
- ```bash
207
- python -m app.mcp
208
- ```
209
-
210
- Tool catalog and schemas:
211
- - `docs/mcp_tools.md`
187
+ The reverse proxy must preserve the browser-facing `Host` header and set
188
+ `X-Forwarded-Proto`. MetaList never trusts `X-Forwarded-Host`; forwarded
189
+ scheme/client metadata is accepted only from `METALIST_FORWARDED_ALLOW_IPS`.
190
+ Do not widen that list beyond the actual proxy addresses.
212
191
 
213
192
  ### Legacy Import
214
193
  `convert-from-legacy.py` replaces the SQLite database referenced by `app.config.DATABASE_URL`, clears its files/sounds sidecar, and imports notes from a legacy JSON export.
@@ -225,7 +204,7 @@ Target a namespaced database during import:
225
204
  convert-from-legacy.py --namespace work --input /path/to/legacy-export.json
226
205
  ```
227
206
 
228
- If `--namespace`, `--port`, `--https-port`, or `--mcp-port` are omitted, the import script prompts for them and saves the resulting launch profile inside the target namespace DB. That means a one-time import into `work` can immediately seed later shorthand launches like `metalist work`.
207
+ If `--namespace`, `--port`, or `--https-port` are omitted, the import script prompts for them and saves the resulting launch profile inside the target namespace DB. That means a one-time import into `work` can immediately seed later shorthand launches like `metalist work`.
229
208
 
230
209
  ### Publishing
231
210
  For the real user-facing install flow:
@@ -7,32 +7,35 @@ from app.api.request_auth import read_request_auth_token
7
7
  from app.services.tokens import token_service
8
8
  from app.services.auth_service import AuthService
9
9
  from app.services.maintenance_mode import maintenance_service
10
+ from app.services.runtime_lock import purge_decrypted_runtime_state
10
11
  from app.models.database import SafeSession
11
12
  from app.config import API_PREFIX, TEST_MODE
12
13
 
13
14
 
14
15
  class AuthMiddleware(BaseHTTPMiddleware):
15
16
  """Middleware to check authentication on protected routes."""
16
-
17
- # Paths that don't require authentication
18
- PUBLIC_PATHS = [
17
+
18
+ PUBLIC_EXACT_PATHS = frozenset({
19
+ "/",
19
20
  f"{API_PREFIX}/auth/login",
21
+ f"{API_PREFIX}/auth/login-namespaces",
22
+ f"{API_PREFIX}/auth/login-namespaces/open",
20
23
  f"{API_PREFIX}/auth/status",
21
24
  f"{API_PREFIX}/auth/session",
22
- f"{API_PREFIX}/auth/namespaces/delete-jobs/",
23
- f"{API_PREFIX}/auth/namespaces/rename-jobs/",
24
- f"{API_PREFIX}/mcp",
25
- "/mcp-client-v2",
26
- "/mcp-client",
27
- "/namespace-deleted",
28
- "/namespace-renamed",
29
- "/static/", # CSS/JS files needed for login page
30
25
  "/favicon.ico",
31
26
  "/locked",
32
- ]
27
+ "/namespace-deleted",
28
+ "/namespace-deleted/open",
29
+ "/namespace-renamed",
30
+ "/namespace-renamed/open",
31
+ } | ({f"{API_PREFIX}/test/reset"} if TEST_MODE else set()))
33
32
 
34
- if TEST_MODE:
35
- PUBLIC_PATHS.append(f"{API_PREFIX}/test/reset")
33
+ PUBLIC_PREFIX_PATHS = (
34
+ f"{API_PREFIX}/auth/namespaces/delete-jobs/",
35
+ f"{API_PREFIX}/auth/namespaces/rename-jobs/",
36
+ "/static/", # CSS/JS files needed for login page
37
+ )
38
+ assert all(path.endswith("/") for path in PUBLIC_PREFIX_PATHS)
36
39
 
37
40
  # Paths to suppress verbose logging for (frequent polling endpoints)
38
41
  QUIET_PATHS = [
@@ -46,11 +49,22 @@ class AuthMiddleware(BaseHTTPMiddleware):
46
49
  ]
47
50
 
48
51
  # Note: /api/notes/* paths are NOT in this list - they require auth when password is set
52
+
53
+ @classmethod
54
+ def is_public_path(cls, *, path: str) -> bool:
55
+ if not isinstance(path, str) or not path.startswith("/"):
56
+ raise TypeError("path must be an absolute URL path")
57
+ if path in cls.PUBLIC_EXACT_PATHS:
58
+ return True
59
+ return any(path.startswith(prefix) for prefix in cls.PUBLIC_PREFIX_PATHS)
49
60
 
50
61
  async def dispatch(self, request: Request, call_next):
51
62
  """Check authentication for protected routes."""
52
63
  path = request.url.path
53
64
 
65
+ if not token_service.has_active_tokens():
66
+ purge_decrypted_runtime_state()
67
+
54
68
  # Block any v1 API usage with an explicit 410 Gone (no DB access)
55
69
  if path.startswith('/api') and not (path.startswith(API_PREFIX) or path == API_PREFIX):
56
70
  ref = request.headers.get('referer', '-')
@@ -73,22 +87,9 @@ class AuthMiddleware(BaseHTTPMiddleware):
73
87
  if not is_quiet:
74
88
  print(f"Middleware checking path: {path}")
75
89
 
76
- # Skip authentication for public paths
77
- public_match = None
78
-
79
- # Handle exact root path match
80
- if path == "/":
81
- public_match = "/ (root)"
82
- else:
83
- # Check other public paths with startswith
84
- for public in self.PUBLIC_PATHS:
85
- if path.startswith(public):
86
- public_match = public
87
- break
88
-
89
- if public_match:
90
+ if self.is_public_path(path=path):
90
91
  if not is_quiet:
91
- print(f"Path {path} is public (matched '{public_match}'), skipping auth")
92
+ print(f"Path {path} is explicitly public, skipping auth")
92
93
  return await call_next(request)
93
94
 
94
95
  if not is_quiet:
@@ -2,8 +2,19 @@ from __future__ import annotations
2
2
 
3
3
  from fastapi import HTTPException, Request, Response
4
4
 
5
+ from app.config import ACTIVE_NAMESPACE
6
+ from app.server_runtime import validate_namespace
5
7
 
6
- AUTH_COOKIE_NAME = "metalist_auth"
8
+
9
+ AUTH_COOKIE_PREFIX = "metalist_auth"
10
+
11
+
12
+ def auth_cookie_name_for_namespace(namespace: str) -> str:
13
+ normalized_namespace = validate_namespace(namespace=namespace)
14
+ return f"{AUTH_COOKIE_PREFIX}_{normalized_namespace}"
15
+
16
+
17
+ AUTH_COOKIE_NAME = auth_cookie_name_for_namespace(ACTIVE_NAMESPACE)
7
18
 
8
19
 
9
20
  def read_request_auth_token(request: Request) -> tuple[str | None, str | None]:
@@ -52,9 +52,11 @@ from app.services.namespace_switcher import save_namespace_port_profiles
52
52
  from app.services.namespace_deletion_jobs import load_namespace_deletion_job
53
53
  from app.services.namespace_rename_jobs import load_namespace_rename_job
54
54
  from app.server_runtime import NamespaceLaunchProfile
55
+ from app.server_runtime import load_all_namespace_launch_profiles
55
56
  from app.server_runtime import resolve_namespace_directory
56
57
  from app.server_runtime import resolve_namespaced_database_path
57
58
  from app.server_runtime import validate_namespace
59
+ from app.security.sensitive_logging import traceback_frame_summary
58
60
  from app.services.encryption import EncryptionService
59
61
  from app.services.tokens import token_service
60
62
  from app.services.note_store import store as note_store
@@ -67,6 +69,7 @@ from app.services.link_titles import link_title_store
67
69
  from app.services.reminders import reminder_store
68
70
  from app.services.search_history import search_history_store
69
71
  from app.services.sound_storage import sound_store
72
+ from app.services.runtime_lock import purge_decrypted_runtime_state
70
73
  from app.services.hydration_state import hydration_state
71
74
  from app.services.file_registry import file_registry
72
75
  from app.services.file_storage import bootstrap_file_registry
@@ -441,11 +444,11 @@ def _on_hydration_done(future: Future) -> None:
441
444
  return
442
445
  error = future.exception()
443
446
  if error is not None:
444
- error_message = f"{type(error).__name__}: {error}"
447
+ error_message = type(error).__name__
445
448
  logger.error(
446
- "Hydration worker failed: %s",
449
+ "Hydration worker failed: %s frames=%s",
447
450
  error_message,
448
- exc_info=(type(error), error, error.__traceback__),
451
+ traceback_frame_summary(error.__traceback__),
449
452
  )
450
453
  hydration_state.fail(error_message)
451
454
 
@@ -538,17 +541,9 @@ def _client_info(request: Request) -> str:
538
541
 
539
542
 
540
543
  def _login_rate_limit_key(request: Request) -> str:
541
- forwarded_for = request.headers.get("x-forwarded-for")
542
- if forwarded_for is not None:
543
- forwarded_for = forwarded_for.strip()
544
- if forwarded_for:
545
- first_hop = forwarded_for.split(",")[0].strip()
546
- if first_hop:
547
- return f"ip:{first_hop}"
548
- if request.client and request.client.host:
549
- return f"ip:{request.client.host}"
550
- user_agent = request.headers.get("user-agent", "Unknown")
551
- return f"ua:{user_agent[:128]}"
544
+ if request.client is None or request.client.host == "":
545
+ raise RuntimeError("Login request is missing client metadata")
546
+ return f"ip:{request.client.host}"
552
547
 
553
548
 
554
549
  def _require_tab_id(
@@ -681,8 +676,9 @@ def logout(
681
676
  token: Annotated[str, Depends(_require_auth)],
682
677
  ):
683
678
  token_service.revoke_token(token)
684
- clear_all_locks()
685
- clear_encryption_key()
679
+ if not purge_decrypted_runtime_state():
680
+ clear_all_locks()
681
+ clear_encryption_key()
686
682
  clear_auth_cookie(response=response)
687
683
  return {"message": "Logout successful"}
688
684
 
@@ -882,7 +878,6 @@ def open_namespace(
882
878
  namespace = _require_string_field(body, "namespace")
883
879
  port = _require_int_field(body, "port")
884
880
  https_port = _optional_int_field(body, "https_port")
885
- mcp_port = _require_int_field(body, "mcp_port")
886
881
  launch_capture = CapturedExceptionContext(
887
882
  RuntimeError,
888
883
  ValueError,
@@ -897,7 +892,6 @@ def open_namespace(
897
892
  namespace=namespace,
898
893
  port=port,
899
894
  https_port=https_port,
900
- mcp_port=mcp_port,
901
895
  )
902
896
  if launch_capture.captured_exception is not None:
903
897
  exc = launch_capture.captured_exception
@@ -912,7 +906,6 @@ def open_namespace(
912
906
  "namespace": result.saved_profile.namespace,
913
907
  "port": result.saved_profile.port,
914
908
  "https_port": result.saved_profile.https_port,
915
- "mcp_port": result.saved_profile.mcp_port,
916
909
  },
917
910
  "saved_for_next_launch": result.saved_for_next_launch,
918
911
  "message": result.message,
@@ -927,6 +920,10 @@ def save_namespace_ports(
927
920
  ):
928
921
  body = _require_body_object(payload)
929
922
  raw_profiles = _require_list_field(body, "profiles")
923
+ saved_profiles = {
924
+ profile.namespace: profile
925
+ for profile in load_all_namespace_launch_profiles()
926
+ }
930
927
  requested_profiles: list[NamespaceLaunchProfile] = []
931
928
  for raw_profile in raw_profiles:
932
929
  if not isinstance(raw_profile, dict):
@@ -934,13 +931,16 @@ def save_namespace_ports(
934
931
  namespace = _require_string_field(raw_profile, "namespace")
935
932
  port = _require_int_field(raw_profile, "port")
936
933
  https_port = _optional_int_field(raw_profile, "https_port")
937
- mcp_port = _require_int_field(raw_profile, "mcp_port")
934
+ saved_profile = saved_profiles.get(namespace)
935
+ legacy_mcp_port = None
936
+ if saved_profile is not None:
937
+ legacy_mcp_port = saved_profile.mcp_port
938
938
  requested_profiles.append(
939
939
  NamespaceLaunchProfile(
940
940
  namespace=namespace,
941
941
  port=port,
942
942
  https_port=https_port,
943
- mcp_port=mcp_port,
943
+ mcp_port=legacy_mcp_port,
944
944
  )
945
945
  )
946
946
 
@@ -968,7 +968,6 @@ def save_namespace_ports(
968
968
  "namespace": profile.namespace,
969
969
  "port": profile.port,
970
970
  "https_port": profile.https_port,
971
- "mcp_port": profile.mcp_port,
972
971
  }
973
972
  for profile in result.saved_profiles
974
973
  ],