MetaList 0.3.11__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 (351) hide show
  1. {metalist-0.3.11 → metalist-0.3.12}/PKG-INFO +49 -7
  2. {metalist-0.3.11 → metalist-0.3.12}/README.md +44 -6
  3. {metalist-0.3.11 → metalist-0.3.12}/app/api/middleware/auth.py +26 -26
  4. {metalist-0.3.11 → metalist-0.3.12}/app/api/request_auth.py +12 -1
  5. {metalist-0.3.11 → metalist-0.3.12}/app/api/routes/auth.py +7 -14
  6. {metalist-0.3.11 → metalist-0.3.12}/app/api/routes/backups.py +15 -13
  7. {metalist-0.3.11 → metalist-0.3.12}/app/api/routes/notes.py +21 -2
  8. metalist-0.3.12/app/api/routes/remote_images.py +88 -0
  9. {metalist-0.3.11 → metalist-0.3.12}/app/config.py +5 -0
  10. {metalist-0.3.11 → metalist-0.3.12}/app/db/file_session.py +2 -2
  11. {metalist-0.3.11 → metalist-0.3.12}/app/db/migrations.py +13 -3
  12. {metalist-0.3.11 → 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.11 → metalist-0.3.12}/app/encryption_audit.py +123 -8
  15. {metalist-0.3.11 → metalist-0.3.12}/app/main.py +60 -11
  16. {metalist-0.3.11 → metalist-0.3.12}/app/models/database.py +2 -2
  17. {metalist-0.3.11 → metalist-0.3.12}/app/models/note_crud.py +6 -4
  18. {metalist-0.3.11 → 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.11 → metalist-0.3.12}/app/server_runtime.py +16 -6
  26. {metalist-0.3.11 → metalist-0.3.12}/app/services/auth_service.py +43 -8
  27. {metalist-0.3.11 → metalist-0.3.12}/app/services/base_service.py +5 -2
  28. {metalist-0.3.11 → metalist-0.3.12}/app/services/content_cache.py +4 -1
  29. {metalist-0.3.11 → metalist-0.3.12}/app/services/content_formatting.py +290 -35
  30. {metalist-0.3.11 → metalist-0.3.12}/app/services/diagnostics.py +36 -28
  31. {metalist-0.3.11 → metalist-0.3.12}/app/services/embedded_references.py +11 -1
  32. {metalist-0.3.11 → metalist-0.3.12}/app/services/link_titles.py +207 -22
  33. {metalist-0.3.11 → metalist-0.3.12}/app/services/namespace_switcher.py +52 -12
  34. metalist-0.3.12/app/services/remote_image_proxy.py +299 -0
  35. {metalist-0.3.11 → metalist-0.3.12}/app/services/runtime_lock.py +2 -0
  36. {metalist-0.3.11 → metalist-0.3.12}/app/services/search_index.py +0 -3
  37. {metalist-0.3.11 → metalist-0.3.12}/app/services/snapshot.py +0 -1
  38. {metalist-0.3.11 → metalist-0.3.12}/app/services/transaction_manager.py +6 -3
  39. {metalist-0.3.11 → metalist-0.3.12}/app/static/css/main.css +106 -36
  40. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/main.js +3 -0
  41. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/api-client.js +9 -35
  42. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/auth.js +24 -17
  43. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/client-state-api.js +17 -2
  44. metalist-0.3.12/app/static/js/modules/command-palette/activation-auth-policy.js +27 -0
  45. metalist-0.3.12/app/static/js/modules/command-palette/backup-command-availability.js +49 -0
  46. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/command-palette/command-palette-controller.js +28 -7
  47. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/config.js +2 -2
  48. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/connectivity-monitor.js +1 -1
  49. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/context-menu/context-menu-registry.js +60 -26
  50. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/context-menu/context-menu-service.js +12 -7
  51. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/dom-utils-fixed.js +0 -1
  52. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/dom-utils.js +0 -1
  53. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/error-handler.js +4 -4
  54. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/login-namespace-picker.js +25 -7
  55. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/backup-settings-modal.js +69 -28
  56. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/base-modal.js +1 -2
  57. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/delete-namespace-modal.js +55 -17
  58. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/delete-namespace-validation.js +19 -0
  59. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/namespace-loading-page.js +14 -0
  60. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/namespace-modals.js +20 -14
  61. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/ontology-modal.js +1 -1
  62. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/password-modal.js +15 -8
  63. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/random-password-modal.js +150 -10
  64. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/actions/content-actions.js +3 -2
  65. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/actions/note-actions.js +3 -1
  66. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/actions/ui-actions.js +2 -1
  67. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/events/context-menu-events.js +24 -0
  68. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/mode-context.js +1 -1
  69. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/mode-logger.js +7 -12
  70. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/add-style-service.js +1 -0
  71. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/differential-view-service.js +45 -8
  72. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/html-paste-sanitizer-service.js +10 -1
  73. metalist-0.3.12/app/static/js/modules/mode-manager/services/remote-image-proxy-service.js +216 -0
  74. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/tag-syntax-service.js +36 -13
  75. metalist-0.3.12/app/static/js/modules/note-html-sanitizer.js +193 -0
  76. metalist-0.3.12/app/static/js/modules/password-policy.js +23 -0
  77. metalist-0.3.12/app/static/js/modules/password-strength.js +81 -0
  78. metalist-0.3.12/app/static/js/vendor/dompurify-3.4.12.LICENSE.txt +202 -0
  79. metalist-0.3.12/app/static/js/vendor/dompurify-3.4.12.min.js +3 -0
  80. metalist-0.3.12/app/static/js/vendor/zxcvbn-4.4.2.LICENSE.txt +20 -0
  81. metalist-0.3.12/app/static/js/vendor/zxcvbn-4.4.2.js +28 -0
  82. metalist-0.3.12/app/static/note-html-policy.json +99 -0
  83. {metalist-0.3.11 → metalist-0.3.12}/app/templates/base.html +2 -1
  84. {metalist-0.3.11 → metalist-0.3.12}/app/templates/locked.html +1 -1
  85. {metalist-0.3.11 → metalist-0.3.12}/app/templates/maintenance.html +1 -1
  86. {metalist-0.3.11 → metalist-0.3.12}/app/templates/namespace_deleted.html +1 -1
  87. {metalist-0.3.11 → metalist-0.3.12}/app/templates/namespace_renamed.html +1 -1
  88. {metalist-0.3.11 → metalist-0.3.12}/app/usecases/create_note.py +6 -2
  89. {metalist-0.3.11 → metalist-0.3.12}/app/usecases/delete_subtree.py +14 -3
  90. {metalist-0.3.11 → metalist-0.3.12}/app/usecases/run_shell.py +18 -0
  91. {metalist-0.3.11 → metalist-0.3.12}/app/usecases/split_note.py +5 -3
  92. {metalist-0.3.11 → metalist-0.3.12}/app/usecases/update_content.py +8 -5
  93. {metalist-0.3.11 → metalist-0.3.12}/app/version.py +1 -1
  94. {metalist-0.3.11 → metalist-0.3.12}/main.py +90 -32
  95. {metalist-0.3.11 → metalist-0.3.12}/metalist.egg-info/PKG-INFO +49 -7
  96. {metalist-0.3.11 → metalist-0.3.12}/metalist.egg-info/SOURCES.txt +20 -0
  97. {metalist-0.3.11 → metalist-0.3.12}/metalist.egg-info/requires.txt +4 -0
  98. {metalist-0.3.11 → metalist-0.3.12}/pyproject.toml +4 -0
  99. {metalist-0.3.11 → metalist-0.3.12}/MANIFEST.in +0 -0
  100. {metalist-0.3.11 → metalist-0.3.12}/app/__init__.py +0 -0
  101. {metalist-0.3.11 → metalist-0.3.12}/app/api/__init__.py +0 -0
  102. {metalist-0.3.11 → metalist-0.3.12}/app/api/deps.py +0 -0
  103. {metalist-0.3.11 → metalist-0.3.12}/app/api/dev.py +0 -0
  104. {metalist-0.3.11 → metalist-0.3.12}/app/api/routes/files.py +0 -0
  105. {metalist-0.3.11 → metalist-0.3.12}/app/api/routes/ontology.py +0 -0
  106. {metalist-0.3.11 → metalist-0.3.12}/app/api/routes/reminders.py +0 -0
  107. {metalist-0.3.11 → metalist-0.3.12}/app/api/routes/sounds.py +0 -0
  108. {metalist-0.3.11 → metalist-0.3.12}/app/api/routes/test.py +0 -0
  109. {metalist-0.3.11 → metalist-0.3.12}/app/api/transactions.py +0 -0
  110. {metalist-0.3.11 → metalist-0.3.12}/app/db/__init__.py +0 -0
  111. {metalist-0.3.11 → metalist-0.3.12}/app/db/backup_settings_sql.py +0 -0
  112. {metalist-0.3.11 → metalist-0.3.12}/app/db/engine.py +0 -0
  113. {metalist-0.3.11 → metalist-0.3.12}/app/db/file_schema.py +0 -0
  114. {metalist-0.3.11 → metalist-0.3.12}/app/db/files_sql.py +0 -0
  115. {metalist-0.3.11 → metalist-0.3.12}/app/db/link_titles_sql.py +0 -0
  116. {metalist-0.3.11 → metalist-0.3.12}/app/db/notes_sql.py +0 -0
  117. {metalist-0.3.11 → metalist-0.3.12}/app/db/ontology_rules_sql.py +0 -0
  118. {metalist-0.3.11 → metalist-0.3.12}/app/db/reminders_sql.py +0 -0
  119. {metalist-0.3.11 → metalist-0.3.12}/app/db/search_history_sql.py +0 -0
  120. {metalist-0.3.11 → metalist-0.3.12}/app/db/session.py +0 -0
  121. {metalist-0.3.11 → metalist-0.3.12}/app/db/settings_sql.py +0 -0
  122. {metalist-0.3.11 → metalist-0.3.12}/app/db/sounds_sql.py +0 -0
  123. {metalist-0.3.11 → metalist-0.3.12}/app/db/tab_state_sql.py +0 -0
  124. {metalist-0.3.11 → metalist-0.3.12}/app/models/commands.py +0 -0
  125. {metalist-0.3.11 → metalist-0.3.12}/app/models/entities.py +0 -0
  126. {metalist-0.3.11 → metalist-0.3.12}/app/models/enums.py +0 -0
  127. {metalist-0.3.11 → metalist-0.3.12}/app/models/linked_list.py +0 -0
  128. {metalist-0.3.11 → metalist-0.3.12}/app/models/list_operations.py +0 -0
  129. {metalist-0.3.11 → metalist-0.3.12}/app/models/list_traversal.py +0 -0
  130. {metalist-0.3.11 → metalist-0.3.12}/app/presentation/__init__.py +0 -0
  131. {metalist-0.3.11 → metalist-0.3.12}/app/presentation/render/__init__.py +0 -0
  132. {metalist-0.3.11 → metalist-0.3.12}/app/presentation/render/note_renderer.py +0 -0
  133. {metalist-0.3.11 → metalist-0.3.12}/app/presentation/templates.py +0 -0
  134. {metalist-0.3.11 → metalist-0.3.12}/app/security/encryption.py +0 -0
  135. {metalist-0.3.11 → metalist-0.3.12}/app/services/__init__.py +0 -0
  136. {metalist-0.3.11 → metalist-0.3.12}/app/services/auth.py +0 -0
  137. {metalist-0.3.11 → metalist-0.3.12}/app/services/auth_cache_state.py +0 -0
  138. {metalist-0.3.11 → metalist-0.3.12}/app/services/backlinks.py +0 -0
  139. {metalist-0.3.11 → metalist-0.3.12}/app/services/backup_service.py +0 -0
  140. {metalist-0.3.11 → metalist-0.3.12}/app/services/backup_settings_service.py +0 -0
  141. {metalist-0.3.11 → metalist-0.3.12}/app/services/client_state_service.py +0 -0
  142. {metalist-0.3.11 → metalist-0.3.12}/app/services/date_filtering.py +0 -0
  143. {metalist-0.3.11 → metalist-0.3.12}/app/services/dependencies.py +0 -0
  144. {metalist-0.3.11 → metalist-0.3.12}/app/services/encryption.py +0 -0
  145. {metalist-0.3.11 → metalist-0.3.12}/app/services/exception_capture.py +0 -0
  146. {metalist-0.3.11 → metalist-0.3.12}/app/services/file_registry.py +0 -0
  147. {metalist-0.3.11 → metalist-0.3.12}/app/services/file_storage.py +0 -0
  148. {metalist-0.3.11 → metalist-0.3.12}/app/services/html_export.py +0 -0
  149. {metalist-0.3.11 → metalist-0.3.12}/app/services/html_unformatting.py +0 -0
  150. {metalist-0.3.11 → metalist-0.3.12}/app/services/hydration_state.py +0 -0
  151. {metalist-0.3.11 → metalist-0.3.12}/app/services/image_size_formatting.py +0 -0
  152. {metalist-0.3.11 → metalist-0.3.12}/app/services/inline_image_occurrences.py +0 -0
  153. {metalist-0.3.11 → metalist-0.3.12}/app/services/integrity.py +0 -0
  154. {metalist-0.3.11 → metalist-0.3.12}/app/services/latex_rendering.py +0 -0
  155. {metalist-0.3.11 → metalist-0.3.12}/app/services/login_rate_limit.py +0 -0
  156. {metalist-0.3.11 → metalist-0.3.12}/app/services/maintenance_mode.py +0 -0
  157. {metalist-0.3.11 → metalist-0.3.12}/app/services/markdown_rendering.py +0 -0
  158. {metalist-0.3.11 → metalist-0.3.12}/app/services/namespace_deletion_jobs.py +0 -0
  159. {metalist-0.3.11 → metalist-0.3.12}/app/services/namespace_deletion_worker.py +0 -0
  160. {metalist-0.3.11 → metalist-0.3.12}/app/services/namespace_rename_jobs.py +0 -0
  161. {metalist-0.3.11 → metalist-0.3.12}/app/services/namespace_rename_worker.py +0 -0
  162. {metalist-0.3.11 → metalist-0.3.12}/app/services/note_image_tags.py +0 -0
  163. {metalist-0.3.11 → metalist-0.3.12}/app/services/note_service.py +0 -0
  164. {metalist-0.3.11 → metalist-0.3.12}/app/services/note_store.py +0 -0
  165. {metalist-0.3.11 → metalist-0.3.12}/app/services/ontology_rules_store.py +0 -0
  166. {metalist-0.3.11 → metalist-0.3.12}/app/services/query_service.py +0 -0
  167. {metalist-0.3.11 → metalist-0.3.12}/app/services/reminders.py +0 -0
  168. {metalist-0.3.11 → metalist-0.3.12}/app/services/root_sorting.py +0 -0
  169. {metalist-0.3.11 → metalist-0.3.12}/app/services/runtime_hardening.py +0 -0
  170. {metalist-0.3.11 → metalist-0.3.12}/app/services/search_history.py +0 -0
  171. {metalist-0.3.11 → metalist-0.3.12}/app/services/search_query.py +0 -0
  172. {metalist-0.3.11 → metalist-0.3.12}/app/services/search_text.py +0 -0
  173. {metalist-0.3.11 → metalist-0.3.12}/app/services/selected_text_tag.py +0 -0
  174. {metalist-0.3.11 → metalist-0.3.12}/app/services/self_update.py +0 -0
  175. {metalist-0.3.11 → metalist-0.3.12}/app/services/session_timeout_service.py +0 -0
  176. {metalist-0.3.11 → metalist-0.3.12}/app/services/shell_session_service.py +0 -0
  177. {metalist-0.3.11 → metalist-0.3.12}/app/services/sound_storage.py +0 -0
  178. {metalist-0.3.11 → metalist-0.3.12}/app/services/store.py +0 -0
  179. {metalist-0.3.11 → metalist-0.3.12}/app/services/sync.py +0 -0
  180. {metalist-0.3.11 → metalist-0.3.12}/app/services/sync_state.py +0 -0
  181. {metalist-0.3.11 → metalist-0.3.12}/app/services/tab_state.py +0 -0
  182. {metalist-0.3.11 → metalist-0.3.12}/app/services/tag_case.py +0 -0
  183. {metalist-0.3.11 → metalist-0.3.12}/app/services/tag_ontology.py +0 -0
  184. {metalist-0.3.11 → metalist-0.3.12}/app/services/tag_rename.py +0 -0
  185. {metalist-0.3.11 → metalist-0.3.12}/app/services/tag_suggestions.py +0 -0
  186. {metalist-0.3.11 → metalist-0.3.12}/app/services/tag_term_matching.py +0 -0
  187. {metalist-0.3.11 → metalist-0.3.12}/app/services/test_reset.py +0 -0
  188. {metalist-0.3.11 → metalist-0.3.12}/app/services/tokens.py +0 -0
  189. {metalist-0.3.11 → metalist-0.3.12}/app/services/undo_service.py +0 -0
  190. {metalist-0.3.11 → metalist-0.3.12}/app/services/undo_state.py +0 -0
  191. {metalist-0.3.11 → metalist-0.3.12}/app/services/view_cache.py +0 -0
  192. {metalist-0.3.11 → metalist-0.3.12}/app/services/view_diff.py +0 -0
  193. {metalist-0.3.11 → metalist-0.3.12}/app/services/view_state.py +0 -0
  194. {metalist-0.3.11 → metalist-0.3.12}/app/services/windows_process_control.py +0 -0
  195. {metalist-0.3.11 → metalist-0.3.12}/app/startup_js_sanity.py +0 -0
  196. {metalist-0.3.11 → metalist-0.3.12}/app/startup_sanity.py +0 -0
  197. {metalist-0.3.11 → metalist-0.3.12}/app/startup_sanity_config.py +0 -0
  198. {metalist-0.3.11 → metalist-0.3.12}/app/static/config/command_palette_tags.json +0 -0
  199. {metalist-0.3.11 → metalist-0.3.12}/app/static/css/forms.css +0 -0
  200. {metalist-0.3.11 → metalist-0.3.12}/app/static/img/favicon.ico +0 -0
  201. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/locked.js +0 -0
  202. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/activity-tracker.js +0 -0
  203. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/async-result.js +0 -0
  204. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/client-state-migration.js +0 -0
  205. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/command-palette/click-activation-service.js +0 -0
  206. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/command-palette/endpoint-registry.js +0 -0
  207. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/command-palette/note-layout-preferences.js +0 -0
  208. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/command-palette/preferences-store.js +0 -0
  209. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/command-palette/tag-config-loader.js +0 -0
  210. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/command-palette/usage-store.js +0 -0
  211. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/context-menu/context-menu-target-service.js +0 -0
  212. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/context-menu/input-caret-service.js +0 -0
  213. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/editor-commands.js +0 -0
  214. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/editor-selection.js +0 -0
  215. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/editor-toolbar.js +0 -0
  216. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/error-overlay.js +0 -0
  217. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/location-flags.js +0 -0
  218. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/alphabetize-root-notes-modal.js +0 -0
  219. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/backup-restore-modal.js +0 -0
  220. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/backup-result-modal.js +0 -0
  221. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/backup-retention-modal.js +0 -0
  222. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/help-modal.js +0 -0
  223. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/namespace-ports-profile.js +0 -0
  224. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/namespace-rename-validation.js +0 -0
  225. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/note-layout-appearance-modal.js +0 -0
  226. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/prioritize-modal.js +0 -0
  227. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/reminder-modal.js +0 -0
  228. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/reset-updated-at-modal.js +0 -0
  229. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/session-timeout-modal.js +0 -0
  230. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/sound-manager-modal.js +0 -0
  231. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/modals/version-info-modal.js +0 -0
  232. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/actions/history-actions.js +0 -0
  233. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/actions/search-actions.js +0 -0
  234. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/actions/selection-actions.js +0 -0
  235. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/events/focus-events.js +0 -0
  236. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/events/inactivity-events.js +0 -0
  237. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/events/input-events.js +0 -0
  238. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/events/keyboard-events.js +0 -0
  239. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/events/mouse-events.js +0 -0
  240. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/events/search-events.js +0 -0
  241. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/mode-manager-controller.js +0 -0
  242. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/animated-scroll-service.js +0 -0
  243. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/backlinks-panel-service.js +0 -0
  244. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/clipboard-shortcut-policy-service.js +0 -0
  245. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/collapse-affordance-service.js +0 -0
  246. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/collapse-editing-policy-service.js +0 -0
  247. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/command-gate-service.js +0 -0
  248. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/context-menu-target-service.js +0 -0
  249. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/date-filter-indicator-service.js +0 -0
  250. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/date-filter-service.js +0 -0
  251. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/edit-session-collapse-policy-service.js +0 -0
  252. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/edit-session-collapse-service.js +0 -0
  253. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/embedded-image-service.js +0 -0
  254. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/file-drop-policy-service.js +0 -0
  255. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/file-image-preview-service.js +0 -0
  256. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/file-reference-service.js +0 -0
  257. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/filtered-refresh-selection-service.js +0 -0
  258. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/image-context-menu-action-service.js +0 -0
  259. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/image-file-insert-choice-modal-service.js +0 -0
  260. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/infinite-scroll-service.js +0 -0
  261. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/link-context-menu-action-service.js +0 -0
  262. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/markdown-render-service.js +0 -0
  263. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/note-click-target-service.js +0 -0
  264. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/note-clipboard-write-service.js +0 -0
  265. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/note-collapse-animation-service.js +0 -0
  266. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/note-context-menu-action-service.js +0 -0
  267. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/note-delete-shortcut-policy-service.js +0 -0
  268. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/note-drag-service.js +0 -0
  269. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/note-reposition-animation-service.js +0 -0
  270. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/note-split-service.js +0 -0
  271. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/password-clipboard-service.js +0 -0
  272. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/polling-service.js +0 -0
  273. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/remove-formatting-service.js +0 -0
  274. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/rhs-panel-service.js +0 -0
  275. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/root-date-separator-service.js +0 -0
  276. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/root-sort-indicator-service.js +0 -0
  277. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/root-sort-service.js +0 -0
  278. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/scroll-anchor-service.js +0 -0
  279. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/scroll-restoration-service.js +0 -0
  280. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/scroll-to-top-service.js +0 -0
  281. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/search-contexts-overlay-service.js +0 -0
  282. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/search-debounce-service.js +0 -0
  283. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/search-input-service.js +0 -0
  284. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/search-interaction-service.js +0 -0
  285. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/search-redaction-reveal-service.js +0 -0
  286. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/search-suggestions-service.js +0 -0
  287. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/search-syntax-service.js +0 -0
  288. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/selected-formatting-removal-service.js +0 -0
  289. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/selected-text-tag-service.js +0 -0
  290. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/shell-output-service.js +0 -0
  291. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/tab-dom-cache-service.js +0 -0
  292. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/tab-duplication-service.js +0 -0
  293. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/tab-state-service.js +0 -0
  294. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/tag-bar-service.js +0 -0
  295. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/tag-suggestions-service.js +0 -0
  296. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/todo-toggle-editing-service.js +0 -0
  297. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/mode-manager/services/view-mode-search-shortcut-service.js +0 -0
  298. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/password-generator.js +0 -0
  299. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/reminder-store.js +0 -0
  300. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/reminder-surface-service.js +0 -0
  301. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/selection-markers.js +0 -0
  302. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/session-auth.js +0 -0
  303. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/sound-service.js +0 -0
  304. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/tag-token.js +0 -0
  305. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/modules/uuid.js +0 -0
  306. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/namespace-deleted.js +0 -0
  307. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/namespace-renamed.js +0 -0
  308. {metalist-0.3.11 → metalist-0.3.12}/app/static/js/vendor/markdown-it.min.js +0 -0
  309. {metalist-0.3.11 → metalist-0.3.12}/app/static/video/login-startup.mp4 +0 -0
  310. {metalist-0.3.11 → metalist-0.3.12}/app/templates/index.html +0 -0
  311. {metalist-0.3.11 → metalist-0.3.12}/app/templates/notes_list.html +0 -0
  312. {metalist-0.3.11 → metalist-0.3.12}/app/usecases/__init__.py +0 -0
  313. {metalist-0.3.11 → metalist-0.3.12}/app/usecases/add_selected_text_tag.py +0 -0
  314. {metalist-0.3.11 → metalist-0.3.12}/app/usecases/alphabetize_root_notes.py +0 -0
  315. {metalist-0.3.11 → metalist-0.3.12}/app/usecases/base.py +0 -0
  316. {metalist-0.3.11 → metalist-0.3.12}/app/usecases/collapse.py +0 -0
  317. {metalist-0.3.11 → metalist-0.3.12}/app/usecases/copy_note.py +0 -0
  318. {metalist-0.3.11 → metalist-0.3.12}/app/usecases/create_child.py +0 -0
  319. {metalist-0.3.11 → metalist-0.3.12}/app/usecases/create_sibling.py +0 -0
  320. {metalist-0.3.11 → metalist-0.3.12}/app/usecases/expand.py +0 -0
  321. {metalist-0.3.11 → metalist-0.3.12}/app/usecases/indent.py +0 -0
  322. {metalist-0.3.11 → metalist-0.3.12}/app/usecases/lock.py +0 -0
  323. {metalist-0.3.11 → metalist-0.3.12}/app/usecases/move.py +0 -0
  324. {metalist-0.3.11 → metalist-0.3.12}/app/usecases/move_to_top.py +0 -0
  325. {metalist-0.3.11 → metalist-0.3.12}/app/usecases/outdent.py +0 -0
  326. {metalist-0.3.11 → metalist-0.3.12}/app/usecases/paste_child.py +0 -0
  327. {metalist-0.3.11 → metalist-0.3.12}/app/usecases/paste_sibling.py +0 -0
  328. {metalist-0.3.11 → metalist-0.3.12}/app/usecases/prioritize.py +0 -0
  329. {metalist-0.3.11 → metalist-0.3.12}/app/usecases/record_edit_mode.py +0 -0
  330. {metalist-0.3.11 → metalist-0.3.12}/app/usecases/redo.py +0 -0
  331. {metalist-0.3.11 → metalist-0.3.12}/app/usecases/rename_tag.py +0 -0
  332. {metalist-0.3.11 → metalist-0.3.12}/app/usecases/reset_updated_at.py +0 -0
  333. {metalist-0.3.11 → metalist-0.3.12}/app/usecases/resize_image.py +0 -0
  334. {metalist-0.3.11 → metalist-0.3.12}/app/usecases/search_comment_autofill.py +0 -0
  335. {metalist-0.3.11 → metalist-0.3.12}/app/usecases/search_context_tags.py +0 -0
  336. {metalist-0.3.11 → metalist-0.3.12}/app/usecases/set_collapse_bulk.py +0 -0
  337. {metalist-0.3.11 → metalist-0.3.12}/app/usecases/set_collapse_in_context.py +0 -0
  338. {metalist-0.3.11 → metalist-0.3.12}/app/usecases/toggle_reference_mode.py +0 -0
  339. {metalist-0.3.11 → metalist-0.3.12}/app/usecases/toggle_todo_done.py +0 -0
  340. {metalist-0.3.11 → metalist-0.3.12}/app/usecases/undo.py +0 -0
  341. {metalist-0.3.11 → metalist-0.3.12}/app/usecases/unformat_content.py +0 -0
  342. {metalist-0.3.11 → metalist-0.3.12}/app/usecases/view.py +0 -0
  343. {metalist-0.3.11 → metalist-0.3.12}/app/utils/__init__.py +0 -0
  344. {metalist-0.3.11 → metalist-0.3.12}/app/utils/encryption.py +0 -0
  345. {metalist-0.3.11 → metalist-0.3.12}/app/utils/text_utils.py +0 -0
  346. {metalist-0.3.11 → metalist-0.3.12}/convert-from-legacy.py +0 -0
  347. {metalist-0.3.11 → metalist-0.3.12}/metalist.egg-info/dependency_links.txt +0 -0
  348. {metalist-0.3.11 → metalist-0.3.12}/metalist.egg-info/entry_points.txt +0 -0
  349. {metalist-0.3.11 → metalist-0.3.12}/metalist.egg-info/top_level.txt +0 -0
  350. {metalist-0.3.11 → metalist-0.3.12}/scripts/generate-lan-cert.sh +0 -0
  351. {metalist-0.3.11 → 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.11
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"
@@ -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,20 +134,36 @@ 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
141
  - `METALIST_TLS_CERT=/path/to/fullchain.pem` + `METALIST_TLS_KEY=/path/to/privkey.pem`: override TLS paths
123
142
  - `METALIST_AUTO_GENERATE_TLS=0`: disable automatic creation of the default self-signed TLS pair
124
143
  - default TLS paths: `~/MetaList/certs/metalist-cert.pem` and `~/MetaList/certs/metalist-key.pem`
125
144
  - `METALIST_FORWARDED_ALLOW_IPS=127.0.0.1,::1` (default): trust proxy headers only from those reverse-proxy IPs
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
126
146
 
127
147
  ### Remote Access / HTTPS
128
- 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:
129
149
  ```bash
130
- metalist
150
+ METALIST_HOST=192.168.1.20 metalist
131
151
  ```
132
- 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.
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
162
+ ```
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`.
133
167
 
134
168
  Namespaced launch example:
135
169
  ```bash
@@ -147,6 +181,7 @@ and MetaList will reuse the saved HTTP / HTTPS ports for `work`. The same applie
147
181
  Equivalent explicit launch, if you want it:
148
182
  ```bash
149
183
  METALIST_HOST=0.0.0.0 \
184
+ METALIST_ALLOWED_HOSTS=192.168.1.20 \
150
185
  METALIST_PORT=8000 \
151
186
  METALIST_HTTPS_PORT=8443 \
152
187
  metalist
@@ -156,6 +191,7 @@ From the other machine, open `https://<laptop-ip>:8443`.
156
191
  If you already have a real certificate and key, use the same dual-listener flow:
157
192
  ```bash
158
193
  METALIST_HOST=0.0.0.0 \
194
+ METALIST_ALLOWED_HOSTS=192.168.1.20 \
159
195
  METALIST_PORT=8000 \
160
196
  METALIST_HTTPS_PORT=8443 \
161
197
  METALIST_TLS_CERT=/path/to/fullchain.pem \
@@ -175,11 +211,17 @@ When HTTPS is enabled:
175
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:
176
212
  ```bash
177
213
  METALIST_HOST=127.0.0.1 \
214
+ METALIST_ALLOWED_HOSTS=notes.example.com \
178
215
  METALIST_PORT=8000 \
179
216
  METALIST_FORWARDED_ALLOW_IPS=127.0.0.1,::1 \
180
217
  metalist
181
218
  ```
182
219
 
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.
224
+
183
225
  ### Legacy Import
184
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.
185
227
 
@@ -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,20 +101,36 @@ 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
108
  - `METALIST_TLS_CERT=/path/to/fullchain.pem` + `METALIST_TLS_KEY=/path/to/privkey.pem`: override TLS paths
94
109
  - `METALIST_AUTO_GENERATE_TLS=0`: disable automatic creation of the default self-signed TLS pair
95
110
  - default TLS paths: `~/MetaList/certs/metalist-cert.pem` and `~/MetaList/certs/metalist-key.pem`
96
111
  - `METALIST_FORWARDED_ALLOW_IPS=127.0.0.1,::1` (default): trust proxy headers only from those reverse-proxy IPs
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
97
113
 
98
114
  ### Remote Access / HTTPS
99
- 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:
100
116
  ```bash
101
- metalist
117
+ METALIST_HOST=192.168.1.20 metalist
102
118
  ```
103
- 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.
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
129
+ ```
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`.
104
134
 
105
135
  Namespaced launch example:
106
136
  ```bash
@@ -118,6 +148,7 @@ and MetaList will reuse the saved HTTP / HTTPS ports for `work`. The same applie
118
148
  Equivalent explicit launch, if you want it:
119
149
  ```bash
120
150
  METALIST_HOST=0.0.0.0 \
151
+ METALIST_ALLOWED_HOSTS=192.168.1.20 \
121
152
  METALIST_PORT=8000 \
122
153
  METALIST_HTTPS_PORT=8443 \
123
154
  metalist
@@ -127,6 +158,7 @@ From the other machine, open `https://<laptop-ip>:8443`.
127
158
  If you already have a real certificate and key, use the same dual-listener flow:
128
159
  ```bash
129
160
  METALIST_HOST=0.0.0.0 \
161
+ METALIST_ALLOWED_HOSTS=192.168.1.20 \
130
162
  METALIST_PORT=8000 \
131
163
  METALIST_HTTPS_PORT=8443 \
132
164
  METALIST_TLS_CERT=/path/to/fullchain.pem \
@@ -146,11 +178,17 @@ When HTTPS is enabled:
146
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:
147
179
  ```bash
148
180
  METALIST_HOST=127.0.0.1 \
181
+ METALIST_ALLOWED_HOSTS=notes.example.com \
149
182
  METALIST_PORT=8000 \
150
183
  METALIST_FORWARDED_ALLOW_IPS=127.0.0.1,::1 \
151
184
  metalist
152
185
  ```
153
186
 
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.
191
+
154
192
  ### Legacy Import
155
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.
156
194
 
@@ -14,23 +14,28 @@ from app.config import API_PREFIX, TEST_MODE
14
14
 
15
15
  class AuthMiddleware(BaseHTTPMiddleware):
16
16
  """Middleware to check authentication on protected routes."""
17
-
18
- # Paths that don't require authentication
19
- PUBLIC_PATHS = [
17
+
18
+ PUBLIC_EXACT_PATHS = frozenset({
19
+ "/",
20
20
  f"{API_PREFIX}/auth/login",
21
+ f"{API_PREFIX}/auth/login-namespaces",
22
+ f"{API_PREFIX}/auth/login-namespaces/open",
21
23
  f"{API_PREFIX}/auth/status",
22
24
  f"{API_PREFIX}/auth/session",
23
- f"{API_PREFIX}/auth/namespaces/delete-jobs/",
24
- f"{API_PREFIX}/auth/namespaces/rename-jobs/",
25
- "/namespace-deleted",
26
- "/namespace-renamed",
27
- "/static/", # CSS/JS files needed for login page
28
25
  "/favicon.ico",
29
26
  "/locked",
30
- ]
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()))
31
32
 
32
- if TEST_MODE:
33
- 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)
34
39
 
35
40
  # Paths to suppress verbose logging for (frequent polling endpoints)
36
41
  QUIET_PATHS = [
@@ -44,6 +49,14 @@ class AuthMiddleware(BaseHTTPMiddleware):
44
49
  ]
45
50
 
46
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)
47
60
 
48
61
  async def dispatch(self, request: Request, call_next):
49
62
  """Check authentication for protected routes."""
@@ -74,22 +87,9 @@ class AuthMiddleware(BaseHTTPMiddleware):
74
87
  if not is_quiet:
75
88
  print(f"Middleware checking path: {path}")
76
89
 
77
- # Skip authentication for public paths
78
- public_match = None
79
-
80
- # Handle exact root path match
81
- if path == "/":
82
- public_match = "/ (root)"
83
- else:
84
- # Check other public paths with startswith
85
- for public in self.PUBLIC_PATHS:
86
- if path.startswith(public):
87
- public_match = public
88
- break
89
-
90
- if public_match:
90
+ if self.is_public_path(path=path):
91
91
  if not is_quiet:
92
- print(f"Path {path} is public (matched '{public_match}'), skipping auth")
92
+ print(f"Path {path} is explicitly public, skipping auth")
93
93
  return await call_next(request)
94
94
 
95
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]:
@@ -56,6 +56,7 @@ from app.server_runtime import load_all_namespace_launch_profiles
56
56
  from app.server_runtime import resolve_namespace_directory
57
57
  from app.server_runtime import resolve_namespaced_database_path
58
58
  from app.server_runtime import validate_namespace
59
+ from app.security.sensitive_logging import traceback_frame_summary
59
60
  from app.services.encryption import EncryptionService
60
61
  from app.services.tokens import token_service
61
62
  from app.services.note_store import store as note_store
@@ -443,11 +444,11 @@ def _on_hydration_done(future: Future) -> None:
443
444
  return
444
445
  error = future.exception()
445
446
  if error is not None:
446
- error_message = f"{type(error).__name__}: {error}"
447
+ error_message = type(error).__name__
447
448
  logger.error(
448
- "Hydration worker failed: %s",
449
+ "Hydration worker failed: %s frames=%s",
449
450
  error_message,
450
- exc_info=(type(error), error, error.__traceback__),
451
+ traceback_frame_summary(error.__traceback__),
451
452
  )
452
453
  hydration_state.fail(error_message)
453
454
 
@@ -540,17 +541,9 @@ def _client_info(request: Request) -> str:
540
541
 
541
542
 
542
543
  def _login_rate_limit_key(request: Request) -> str:
543
- forwarded_for = request.headers.get("x-forwarded-for")
544
- if forwarded_for is not None:
545
- forwarded_for = forwarded_for.strip()
546
- if forwarded_for:
547
- first_hop = forwarded_for.split(",")[0].strip()
548
- if first_hop:
549
- return f"ip:{first_hop}"
550
- if request.client and request.client.host:
551
- return f"ip:{request.client.host}"
552
- user_agent = request.headers.get("user-agent", "Unknown")
553
- 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}"
554
547
 
555
548
 
556
549
  def _require_tab_id(
@@ -870,19 +870,21 @@ def list_backups(token: Annotated[str, Depends(_require_auth)]):
870
870
 
871
871
  @router.post("/run", response_model=BackupRunResponse)
872
872
  @transactional_route
873
- def run_backup(token: Annotated[str, Depends(_require_auth)]):
874
- settings = load_backup_settings(token=token)
875
- folder_path = settings["folder_path"]
876
- selected_namespaces = settings["selected_namespaces"]
877
- retention_count = settings["retention_count"]
878
- if not isinstance(folder_path, str):
879
- raise RuntimeError("backup settings folder_path must be a string")
880
- if not isinstance(selected_namespaces, list):
881
- raise RuntimeError("backup settings selected_namespaces must be a list")
882
- if not isinstance(retention_count, int) or retention_count <= 0:
883
- raise RuntimeError("backup settings retention_count must be a positive integer")
873
+ def run_backup(
874
+ payload: BackupSettingsUpdateRequest,
875
+ token: Annotated[str, Depends(_require_auth)],
876
+ ):
877
+ folder_path = _prepare_folder_backup_directory_for_settings(
878
+ folder_path=payload.folder_path,
879
+ )
884
880
  normalized_selected_namespaces = _filter_deleted_selected_namespaces(
885
- selected_namespaces=selected_namespaces,
881
+ selected_namespaces=payload.selected_namespaces,
882
+ )
883
+ update_backup_settings(
884
+ token=token,
885
+ folder_path=folder_path,
886
+ selected_namespaces=normalized_selected_namespaces,
887
+ retention_count=payload.retention_count,
886
888
  )
887
889
 
888
890
  folder_directory = _normalize_folder_backup_path(folder_path=folder_path)
@@ -895,7 +897,7 @@ def run_backup(token: Annotated[str, Depends(_require_auth)]):
895
897
  database_path = resolve_namespaced_database_path(namespace=namespace)
896
898
  backup_info = create_timestamped_backup_for_paths(database_path, folder_directory)
897
899
  current_folder_backups = list_backups_in_directory(folder_directory, database_path=database_path)
898
- folder_delete_count = len(current_folder_backups) - retention_count
900
+ folder_delete_count = len(current_folder_backups) - payload.retention_count
899
901
  deleted_folder_count = 0
900
902
  if folder_delete_count > 0:
901
903
  deleted_folder_count = len(
@@ -71,6 +71,7 @@ from app.services.undo_state import reset_undo_stack
71
71
  from app.usecases.prioritize import list_prioritize_tag_suggestions
72
72
  from app.services.selected_text_tag import SelectedTextTagValidationError
73
73
  from app.api.request_auth import require_request_auth_token
74
+ from app.security.shell_execution import is_loopback_host
74
75
 
75
76
 
76
77
  logger = logging.getLogger(__name__)
@@ -93,6 +94,22 @@ def _require_note_present(note_id: str, *, context: str) -> None:
93
94
  raise HTTPException(status_code=404, detail=f"Note not found: {note_id}")
94
95
 
95
96
 
97
+ def _require_loopback_shell_request(request: Request) -> None:
98
+ if not isinstance(request, Request):
99
+ raise TypeError("request must be a Request")
100
+ if request.client is None or not is_loopback_host(host=request.client.host):
101
+ raise HTTPException(
102
+ status_code=403,
103
+ detail="Shell execution is restricted to loopback clients",
104
+ )
105
+ request_hostname = request.url.hostname
106
+ if request_hostname is None or not is_loopback_host(host=request_hostname):
107
+ raise HTTPException(
108
+ status_code=403,
109
+ detail="Shell execution requires a loopback request host",
110
+ )
111
+
112
+
96
113
  def _resolve_tab_sort_mode(tab_id: object) -> str:
97
114
  if tab_id is not None and (not isinstance(tab_id, str) or tab_id == ""):
98
115
  raise TypeError("tabId must be a non-empty string")
@@ -883,7 +900,8 @@ def resize_note_image(request: Request, note_id: str, body: dict):
883
900
 
884
901
  @router.post("/notes/{note_id}/run-shell")
885
902
  @transactional_route
886
- def run_shell_endpoint(note_id: str, body: dict) -> Dict[str, object]:
903
+ def run_shell_endpoint(request: Request, note_id: str, body: dict) -> Dict[str, object]:
904
+ _require_loopback_shell_request(request)
887
905
  _require_note_present(note_id, context="notes.run-shell")
888
906
  timeout_seconds = body["timeoutSeconds"]
889
907
  cmd = CmdRunShellStart(
@@ -894,7 +912,8 @@ def run_shell_endpoint(note_id: str, body: dict) -> Dict[str, object]:
894
912
 
895
913
 
896
914
  @router.get("/notes/{note_id}/run-shell/{run_id}")
897
- def run_shell_status_endpoint(note_id: str, run_id: str) -> Dict[str, object]:
915
+ def run_shell_status_endpoint(request: Request, note_id: str, run_id: str) -> Dict[str, object]:
916
+ _require_loopback_shell_request(request)
898
917
  run_capture = CapturedExceptionContext(RuntimeError, TypeError, ValueError)
899
918
  result: Dict[str, object] | None = None
900
919
  with run_capture:
@@ -0,0 +1,88 @@
1
+ from __future__ import annotations
2
+
3
+ from fastapi import APIRouter, HTTPException, Request, Response
4
+ from pydantic import BaseModel, Field
5
+
6
+ from app.api.request_auth import require_request_auth_token
7
+ from app.api.transactions import transactional_route
8
+ from app.services.exception_capture import CapturedExceptionContext
9
+ from app.services.remote_image_proxy import (
10
+ RemoteImageFetchError,
11
+ fetch_remote_image,
12
+ remote_image_proxy_registry,
13
+ )
14
+
15
+
16
+ router = APIRouter(prefix="/remote-images", tags=["remote-images"])
17
+
18
+
19
+ class RemoteImageRegistrationRequest(BaseModel):
20
+ source_urls: list[str] = Field(min_length=1, max_length=100)
21
+
22
+
23
+ class RemoteImageRegistrationEntryResponse(BaseModel):
24
+ source_url: str
25
+ proxy_path: str
26
+
27
+
28
+ class RemoteImageRegistrationResponse(BaseModel):
29
+ images: list[RemoteImageRegistrationEntryResponse]
30
+
31
+
32
+ @router.post("/registrations", response_model=RemoteImageRegistrationResponse)
33
+ @transactional_route
34
+ def register_remote_images(
35
+ request: Request,
36
+ registration: RemoteImageRegistrationRequest,
37
+ ) -> RemoteImageRegistrationResponse:
38
+ require_request_auth_token(request)
39
+ images: list[RemoteImageRegistrationEntryResponse] = []
40
+ for source_url in registration.source_urls:
41
+ registration_capture = CapturedExceptionContext(ValueError)
42
+ token = None
43
+ with registration_capture:
44
+ token = remote_image_proxy_registry.register(source_url)
45
+ if registration_capture.captured_exception is not None:
46
+ raise HTTPException(
47
+ status_code=422,
48
+ detail="Remote image URL must use HTTP or HTTPS",
49
+ )
50
+ if token is None:
51
+ raise RuntimeError("Remote image registration completed without a token")
52
+ images.append(
53
+ RemoteImageRegistrationEntryResponse(
54
+ source_url=source_url,
55
+ proxy_path=f"/api2/remote-images/{token}",
56
+ )
57
+ )
58
+ return RemoteImageRegistrationResponse(images=images)
59
+
60
+
61
+ @router.get("/{proxy_token}")
62
+ def proxy_remote_image(request: Request, proxy_token: str) -> Response:
63
+ require_request_auth_token(request)
64
+ resolve_capture = CapturedExceptionContext(KeyError)
65
+ source_url = None
66
+ with resolve_capture:
67
+ source_url = remote_image_proxy_registry.resolve(proxy_token)
68
+ if resolve_capture.captured_exception is not None:
69
+ raise HTTPException(status_code=404, detail="Remote image reference not found")
70
+ if source_url is None:
71
+ raise RuntimeError("Remote image proxy resolution completed without a source URL")
72
+
73
+ fetch_capture = CapturedExceptionContext(RemoteImageFetchError)
74
+ payload = None
75
+ with fetch_capture:
76
+ payload = fetch_remote_image(source_url)
77
+ if fetch_capture.captured_exception is not None:
78
+ raise HTTPException(status_code=502, detail="Remote image unavailable")
79
+ if payload is None:
80
+ raise RuntimeError("Remote image fetch completed without a payload")
81
+ return Response(
82
+ content=payload.content,
83
+ media_type=payload.mime_type,
84
+ headers={
85
+ "Cache-Control": "no-store, private",
86
+ "Cross-Origin-Resource-Policy": "same-origin",
87
+ },
88
+ )
@@ -69,6 +69,9 @@ KDF_MIN_PARALLELISM = 1
69
69
  KDF_MAX_PARALLELISM = 16
70
70
  KDF_ALGORITHM = "ARGON2ID"
71
71
  VAULT_VERSION = 3
72
+ PASSWORD_MIN_LENGTH = 12
73
+ PASSWORD_MAX_LENGTH = 72
74
+ PASSWORD_MIN_ZXCVBN_SCORE = 3
72
75
 
73
76
  # Login brute-force mitigation
74
77
  LOGIN_RATE_LIMIT_MAX_ATTEMPTS = _env_int("LOGIN_RATE_LIMIT_MAX_ATTEMPTS", 5)
@@ -83,6 +86,8 @@ SECURITY_REQUIRE_MACOS_NO_HIBERNATION = _env_flag("SECURITY_REQUIRE_MACOS_NO_HIB
83
86
  assert KDF_MIN_TIME_COST <= KDF_TIME_COST <= KDF_MAX_TIME_COST
84
87
  assert KDF_MIN_MEMORY_COST_KIB <= KDF_MEMORY_COST_KIB <= KDF_MAX_MEMORY_COST_KIB
85
88
  assert KDF_MIN_PARALLELISM <= KDF_PARALLELISM <= KDF_MAX_PARALLELISM
89
+ assert 0 < PASSWORD_MIN_LENGTH <= PASSWORD_MAX_LENGTH
90
+ assert 0 <= PASSWORD_MIN_ZXCVBN_SCORE <= 4
86
91
  assert MAX_SEARCH_SUGGESTIONS > 0
87
92
  assert MAX_TAG_SUGGESTIONS > 0
88
93
  assert LOGIN_RATE_LIMIT_MAX_ATTEMPTS > 0
@@ -108,9 +108,9 @@ class FileSession:
108
108
  extra={"stack": stack},
109
109
  )
110
110
  else:
111
- logger.opt(exception=True).error(
111
+ logger.error(
112
112
  "FATAL: file DB rollback executed; crashing process",
113
- extra={"stack": stack},
113
+ extra={"stack": stack, "error_type": exc_type.__name__},
114
114
  )
115
115
  sys.stderr.write("FATAL: file DB rollback executed; crashing process\n")
116
116
  sys.stderr.flush()
@@ -6,12 +6,11 @@ from collections.abc import Callable
6
6
  from dataclasses import dataclass
7
7
  import sqlite3
8
8
 
9
+ from app.db.version import CURRENT_DATABASE_VERSION
10
+ from app.db.schema import create_namespace_content_migrations_table
9
11
  from app.services.encryption import EncryptionService
10
12
 
11
13
 
12
- CURRENT_DATABASE_VERSION = 1
13
-
14
-
15
14
  @dataclass(frozen=True, slots=True)
16
15
  class MigrationResult:
17
16
  initial_version: int
@@ -124,11 +123,22 @@ def _migration_0_to_1(
124
123
  return rewritten_count
125
124
 
126
125
 
126
+ def _migration_1_to_2(
127
+ *,
128
+ connection: sqlite3.Connection,
129
+ encryption_enabled: bool, # noqa: ARG001
130
+ encryption_service: EncryptionService | None, # noqa: ARG001
131
+ ) -> int:
132
+ create_namespace_content_migrations_table(connection)
133
+ return 0
134
+
135
+
127
136
  _MIGRATIONS: dict[
128
137
  int,
129
138
  Callable[..., int],
130
139
  ] = {
131
140
  0: _migration_0_to_1,
141
+ 1: _migration_1_to_2,
132
142
  }
133
143
 
134
144