@bobfrankston/mailx 1.0.465 → 1.0.500

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 (344) hide show
  1. package/.globalize.json5 +25 -0
  2. package/README.md +17 -420
  3. package/bin/mailx.js +87 -84
  4. package/bin/mailx.js.map +1 -1
  5. package/bin/mailx.ts +87 -84
  6. package/client/android.html +5 -5
  7. package/client/app.js +42 -38
  8. package/client/components/folder-tree.js +7 -5
  9. package/client/components/message-list.js +485 -448
  10. package/client/components/message-viewer.js +36 -41
  11. package/client/index.html +8 -8
  12. package/client/lib/message-state.js +46 -65
  13. package/index.js +59 -0
  14. package/package.json +12 -114
  15. package/packages/mailx-send/mailsend/{package-lock.json → node_modules/.package-lock.json} +0 -16
  16. package/packages/mailx-send/mailsend/node_modules/@types/node/LICENSE +21 -0
  17. package/packages/mailx-send/mailsend/node_modules/@types/node/README.md +15 -0
  18. package/packages/mailx-send/mailsend/node_modules/@types/node/assert/strict.d.ts +111 -0
  19. package/packages/mailx-send/mailsend/node_modules/@types/node/assert.d.ts +1078 -0
  20. package/packages/mailx-send/mailsend/node_modules/@types/node/async_hooks.d.ts +603 -0
  21. package/packages/mailx-send/mailsend/node_modules/@types/node/buffer.buffer.d.ts +472 -0
  22. package/packages/mailx-send/mailsend/node_modules/@types/node/buffer.d.ts +1934 -0
  23. package/packages/mailx-send/mailsend/node_modules/@types/node/child_process.d.ts +1476 -0
  24. package/packages/mailx-send/mailsend/node_modules/@types/node/cluster.d.ts +578 -0
  25. package/packages/mailx-send/mailsend/node_modules/@types/node/compatibility/disposable.d.ts +14 -0
  26. package/packages/mailx-send/mailsend/node_modules/@types/node/compatibility/index.d.ts +9 -0
  27. package/packages/mailx-send/mailsend/node_modules/@types/node/compatibility/indexable.d.ts +20 -0
  28. package/packages/mailx-send/mailsend/node_modules/@types/node/compatibility/iterators.d.ts +20 -0
  29. package/packages/mailx-send/mailsend/node_modules/@types/node/console.d.ts +452 -0
  30. package/packages/mailx-send/mailsend/node_modules/@types/node/constants.d.ts +21 -0
  31. package/packages/mailx-send/mailsend/node_modules/@types/node/crypto.d.ts +4545 -0
  32. package/packages/mailx-send/mailsend/node_modules/@types/node/dgram.d.ts +600 -0
  33. package/packages/mailx-send/mailsend/node_modules/@types/node/diagnostics_channel.d.ts +578 -0
  34. package/packages/mailx-send/mailsend/node_modules/@types/node/dns/promises.d.ts +503 -0
  35. package/packages/mailx-send/mailsend/node_modules/@types/node/dns.d.ts +923 -0
  36. package/packages/mailx-send/mailsend/node_modules/@types/node/domain.d.ts +170 -0
  37. package/packages/mailx-send/mailsend/node_modules/@types/node/events.d.ts +976 -0
  38. package/packages/mailx-send/mailsend/node_modules/@types/node/fs/promises.d.ts +1295 -0
  39. package/packages/mailx-send/mailsend/node_modules/@types/node/fs.d.ts +4461 -0
  40. package/packages/mailx-send/mailsend/node_modules/@types/node/globals.d.ts +172 -0
  41. package/packages/mailx-send/mailsend/node_modules/@types/node/globals.typedarray.d.ts +38 -0
  42. package/packages/mailx-send/mailsend/node_modules/@types/node/http.d.ts +2089 -0
  43. package/packages/mailx-send/mailsend/node_modules/@types/node/http2.d.ts +2644 -0
  44. package/packages/mailx-send/mailsend/node_modules/@types/node/https.d.ts +579 -0
  45. package/packages/mailx-send/mailsend/node_modules/@types/node/index.d.ts +97 -0
  46. package/packages/mailx-send/mailsend/node_modules/@types/node/inspector.d.ts +253 -0
  47. package/packages/mailx-send/mailsend/node_modules/@types/node/inspector.generated.d.ts +4052 -0
  48. package/packages/mailx-send/mailsend/node_modules/@types/node/module.d.ts +891 -0
  49. package/packages/mailx-send/mailsend/node_modules/@types/node/net.d.ts +1057 -0
  50. package/packages/mailx-send/mailsend/node_modules/@types/node/os.d.ts +506 -0
  51. package/packages/mailx-send/mailsend/node_modules/@types/node/package.json +145 -0
  52. package/packages/mailx-send/mailsend/node_modules/@types/node/path.d.ts +200 -0
  53. package/packages/mailx-send/mailsend/node_modules/@types/node/perf_hooks.d.ts +968 -0
  54. package/packages/mailx-send/mailsend/node_modules/@types/node/process.d.ts +2084 -0
  55. package/packages/mailx-send/mailsend/node_modules/@types/node/punycode.d.ts +117 -0
  56. package/packages/mailx-send/mailsend/node_modules/@types/node/querystring.d.ts +152 -0
  57. package/packages/mailx-send/mailsend/node_modules/@types/node/readline/promises.d.ts +161 -0
  58. package/packages/mailx-send/mailsend/node_modules/@types/node/readline.d.ts +594 -0
  59. package/packages/mailx-send/mailsend/node_modules/@types/node/repl.d.ts +428 -0
  60. package/packages/mailx-send/mailsend/node_modules/@types/node/sea.d.ts +153 -0
  61. package/packages/mailx-send/mailsend/node_modules/@types/node/sqlite.d.ts +721 -0
  62. package/packages/mailx-send/mailsend/node_modules/@types/node/stream/consumers.d.ts +38 -0
  63. package/packages/mailx-send/mailsend/node_modules/@types/node/stream/promises.d.ts +90 -0
  64. package/packages/mailx-send/mailsend/node_modules/@types/node/stream/web.d.ts +622 -0
  65. package/packages/mailx-send/mailsend/node_modules/@types/node/stream.d.ts +1664 -0
  66. package/packages/mailx-send/mailsend/node_modules/@types/node/string_decoder.d.ts +67 -0
  67. package/packages/mailx-send/mailsend/node_modules/@types/node/test.d.ts +2163 -0
  68. package/packages/mailx-send/mailsend/node_modules/@types/node/timers/promises.d.ts +108 -0
  69. package/packages/mailx-send/mailsend/node_modules/@types/node/timers.d.ts +287 -0
  70. package/packages/mailx-send/mailsend/node_modules/@types/node/tls.d.ts +1319 -0
  71. package/packages/mailx-send/mailsend/node_modules/@types/node/trace_events.d.ts +197 -0
  72. package/packages/mailx-send/mailsend/node_modules/@types/node/ts5.6/buffer.buffer.d.ts +468 -0
  73. package/packages/mailx-send/mailsend/node_modules/@types/node/ts5.6/globals.typedarray.d.ts +34 -0
  74. package/packages/mailx-send/mailsend/node_modules/@types/node/ts5.6/index.d.ts +97 -0
  75. package/packages/mailx-send/mailsend/node_modules/@types/node/tty.d.ts +208 -0
  76. package/packages/mailx-send/mailsend/node_modules/@types/node/url.d.ts +984 -0
  77. package/packages/mailx-send/mailsend/node_modules/@types/node/util.d.ts +2606 -0
  78. package/packages/mailx-send/mailsend/node_modules/@types/node/v8.d.ts +920 -0
  79. package/packages/mailx-send/mailsend/node_modules/@types/node/vm.d.ts +1000 -0
  80. package/packages/mailx-send/mailsend/node_modules/@types/node/wasi.d.ts +181 -0
  81. package/packages/mailx-send/mailsend/node_modules/@types/node/web-globals/abortcontroller.d.ts +34 -0
  82. package/packages/mailx-send/mailsend/node_modules/@types/node/web-globals/domexception.d.ts +68 -0
  83. package/packages/mailx-send/mailsend/node_modules/@types/node/web-globals/events.d.ts +97 -0
  84. package/packages/mailx-send/mailsend/node_modules/@types/node/web-globals/fetch.d.ts +55 -0
  85. package/packages/mailx-send/mailsend/node_modules/@types/node/web-globals/navigator.d.ts +22 -0
  86. package/packages/mailx-send/mailsend/node_modules/@types/node/web-globals/storage.d.ts +24 -0
  87. package/packages/mailx-send/mailsend/node_modules/@types/node/worker_threads.d.ts +784 -0
  88. package/packages/mailx-send/mailsend/node_modules/@types/node/zlib.d.ts +747 -0
  89. package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/LICENSE +21 -0
  90. package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/README.md +15 -0
  91. package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/index.d.ts +82 -0
  92. package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/addressparser/index.d.ts +31 -0
  93. package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/base64/index.d.ts +22 -0
  94. package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/dkim/index.d.ts +45 -0
  95. package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/dkim/message-parser.d.ts +75 -0
  96. package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/dkim/relaxed-body.d.ts +75 -0
  97. package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/dkim/sign.d.ts +21 -0
  98. package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/fetch/cookies.d.ts +54 -0
  99. package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/fetch/index.d.ts +38 -0
  100. package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/json-transport/index.d.ts +53 -0
  101. package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/mail-composer/index.d.ts +25 -0
  102. package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/mailer/index.d.ts +283 -0
  103. package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/mailer/mail-message.d.ts +32 -0
  104. package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/mime-funcs/index.d.ts +87 -0
  105. package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/mime-funcs/mime-types.d.ts +2 -0
  106. package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/mime-node/index.d.ts +224 -0
  107. package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/mime-node/last-newline.d.ts +9 -0
  108. package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/qp/index.d.ts +23 -0
  109. package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/sendmail-transport/index.d.ts +53 -0
  110. package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/sendmail-transport/le-unix.d.ts +7 -0
  111. package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/sendmail-transport/le-windows.d.ts +7 -0
  112. package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/ses-transport/index.d.ts +146 -0
  113. package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/shared/index.d.ts +58 -0
  114. package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/smtp-connection/data-stream.d.ts +11 -0
  115. package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/smtp-connection/http-proxy-client.d.ts +16 -0
  116. package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/smtp-connection/index.d.ts +270 -0
  117. package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/smtp-pool/index.d.ts +93 -0
  118. package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/smtp-pool/pool-resource.d.ts +66 -0
  119. package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/smtp-transport/index.d.ts +115 -0
  120. package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/stream-transport/index.d.ts +59 -0
  121. package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/well-known/index.d.ts +6 -0
  122. package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/lib/xoauth2/index.d.ts +114 -0
  123. package/packages/mailx-send/mailsend/node_modules/@types/nodemailer/package.json +38 -0
  124. package/packages/mailx-send/mailsend/node_modules/nodemailer/.ncurc.js +9 -0
  125. package/packages/mailx-send/mailsend/node_modules/nodemailer/.prettierignore +8 -0
  126. package/packages/mailx-send/mailsend/node_modules/nodemailer/.prettierrc +12 -0
  127. package/packages/mailx-send/mailsend/node_modules/nodemailer/.prettierrc.js +10 -0
  128. package/packages/mailx-send/mailsend/node_modules/nodemailer/.release-please-config.json +9 -0
  129. package/packages/mailx-send/mailsend/node_modules/nodemailer/LICENSE +16 -0
  130. package/packages/mailx-send/mailsend/node_modules/nodemailer/README.md +86 -0
  131. package/packages/mailx-send/mailsend/node_modules/nodemailer/SECURITY.txt +22 -0
  132. package/packages/mailx-send/mailsend/node_modules/nodemailer/eslint.config.js +88 -0
  133. package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/addressparser/index.js +383 -0
  134. package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/base64/index.js +139 -0
  135. package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/dkim/index.js +253 -0
  136. package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/dkim/message-parser.js +155 -0
  137. package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/dkim/relaxed-body.js +154 -0
  138. package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/dkim/sign.js +117 -0
  139. package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/fetch/cookies.js +281 -0
  140. package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/fetch/index.js +280 -0
  141. package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/json-transport/index.js +82 -0
  142. package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/mail-composer/index.js +629 -0
  143. package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/mailer/index.js +441 -0
  144. package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/mailer/mail-message.js +316 -0
  145. package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/mime-funcs/index.js +625 -0
  146. package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/mime-funcs/mime-types.js +2113 -0
  147. package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/mime-node/index.js +1316 -0
  148. package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/mime-node/last-newline.js +33 -0
  149. package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/mime-node/le-unix.js +43 -0
  150. package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/mime-node/le-windows.js +52 -0
  151. package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/nodemailer.js +157 -0
  152. package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/punycode/index.js +460 -0
  153. package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/qp/index.js +227 -0
  154. package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/sendmail-transport/index.js +210 -0
  155. package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/ses-transport/index.js +234 -0
  156. package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/shared/index.js +754 -0
  157. package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/smtp-connection/data-stream.js +108 -0
  158. package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +143 -0
  159. package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/smtp-connection/index.js +1870 -0
  160. package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/smtp-pool/index.js +652 -0
  161. package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +259 -0
  162. package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/smtp-transport/index.js +421 -0
  163. package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/stream-transport/index.js +135 -0
  164. package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/well-known/index.js +47 -0
  165. package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/well-known/services.json +611 -0
  166. package/packages/mailx-send/mailsend/node_modules/nodemailer/lib/xoauth2/index.js +427 -0
  167. package/packages/mailx-send/mailsend/node_modules/nodemailer/package.json +47 -0
  168. package/packages/mailx-send/mailsend/node_modules/undici-types/LICENSE +21 -0
  169. package/packages/mailx-send/mailsend/node_modules/undici-types/README.md +6 -0
  170. package/packages/mailx-send/mailsend/node_modules/undici-types/agent.d.ts +31 -0
  171. package/packages/mailx-send/mailsend/node_modules/undici-types/api.d.ts +43 -0
  172. package/packages/mailx-send/mailsend/node_modules/undici-types/balanced-pool.d.ts +29 -0
  173. package/packages/mailx-send/mailsend/node_modules/undici-types/cache.d.ts +36 -0
  174. package/packages/mailx-send/mailsend/node_modules/undici-types/client.d.ts +108 -0
  175. package/packages/mailx-send/mailsend/node_modules/undici-types/connector.d.ts +34 -0
  176. package/packages/mailx-send/mailsend/node_modules/undici-types/content-type.d.ts +21 -0
  177. package/packages/mailx-send/mailsend/node_modules/undici-types/cookies.d.ts +28 -0
  178. package/packages/mailx-send/mailsend/node_modules/undici-types/diagnostics-channel.d.ts +66 -0
  179. package/packages/mailx-send/mailsend/node_modules/undici-types/dispatcher.d.ts +256 -0
  180. package/packages/mailx-send/mailsend/node_modules/undici-types/env-http-proxy-agent.d.ts +21 -0
  181. package/packages/mailx-send/mailsend/node_modules/undici-types/errors.d.ts +149 -0
  182. package/packages/mailx-send/mailsend/node_modules/undici-types/eventsource.d.ts +61 -0
  183. package/packages/mailx-send/mailsend/node_modules/undici-types/fetch.d.ts +209 -0
  184. package/packages/mailx-send/mailsend/node_modules/undici-types/file.d.ts +39 -0
  185. package/packages/mailx-send/mailsend/node_modules/undici-types/filereader.d.ts +54 -0
  186. package/packages/mailx-send/mailsend/node_modules/undici-types/formdata.d.ts +108 -0
  187. package/packages/mailx-send/mailsend/node_modules/undici-types/global-dispatcher.d.ts +9 -0
  188. package/packages/mailx-send/mailsend/node_modules/undici-types/global-origin.d.ts +7 -0
  189. package/packages/mailx-send/mailsend/node_modules/undici-types/handlers.d.ts +15 -0
  190. package/packages/mailx-send/mailsend/node_modules/undici-types/header.d.ts +4 -0
  191. package/packages/mailx-send/mailsend/node_modules/undici-types/index.d.ts +71 -0
  192. package/packages/mailx-send/mailsend/node_modules/undici-types/interceptors.d.ts +17 -0
  193. package/packages/mailx-send/mailsend/node_modules/undici-types/mock-agent.d.ts +50 -0
  194. package/packages/mailx-send/mailsend/node_modules/undici-types/mock-client.d.ts +25 -0
  195. package/packages/mailx-send/mailsend/node_modules/undici-types/mock-errors.d.ts +12 -0
  196. package/packages/mailx-send/mailsend/node_modules/undici-types/mock-interceptor.d.ts +93 -0
  197. package/packages/mailx-send/mailsend/node_modules/undici-types/mock-pool.d.ts +25 -0
  198. package/packages/mailx-send/mailsend/node_modules/undici-types/package.json +55 -0
  199. package/packages/mailx-send/mailsend/node_modules/undici-types/patch.d.ts +33 -0
  200. package/packages/mailx-send/mailsend/node_modules/undici-types/pool-stats.d.ts +19 -0
  201. package/packages/mailx-send/mailsend/node_modules/undici-types/pool.d.ts +39 -0
  202. package/packages/mailx-send/mailsend/node_modules/undici-types/proxy-agent.d.ts +28 -0
  203. package/packages/mailx-send/mailsend/node_modules/undici-types/readable.d.ts +65 -0
  204. package/packages/mailx-send/mailsend/node_modules/undici-types/retry-agent.d.ts +8 -0
  205. package/packages/mailx-send/mailsend/node_modules/undici-types/retry-handler.d.ts +116 -0
  206. package/packages/mailx-send/mailsend/node_modules/undici-types/util.d.ts +18 -0
  207. package/packages/mailx-send/mailsend/node_modules/undici-types/webidl.d.ts +228 -0
  208. package/packages/mailx-send/mailsend/node_modules/undici-types/websocket.d.ts +150 -0
  209. package/packages/mailx-server/index.js +1 -1
  210. package/packages/mailx-settings/cloud.js +12 -7
  211. package/packages/mailx-settings/index.js +22 -1
  212. package/client/.gitattributes +0 -10
  213. package/client/app.js.map +0 -1
  214. package/client/app.ts +0 -3190
  215. package/client/components/address-book.js.map +0 -1
  216. package/client/components/address-book.ts +0 -204
  217. package/client/components/alarms.js.map +0 -1
  218. package/client/components/alarms.ts +0 -276
  219. package/client/components/calendar-sidebar.js.map +0 -1
  220. package/client/components/calendar-sidebar.ts +0 -474
  221. package/client/components/calendar.js.map +0 -1
  222. package/client/components/calendar.ts +0 -211
  223. package/client/components/context-menu.js.map +0 -1
  224. package/client/components/context-menu.ts +0 -95
  225. package/client/components/folder-picker.js.map +0 -1
  226. package/client/components/folder-picker.ts +0 -127
  227. package/client/components/folder-tree.js.map +0 -1
  228. package/client/components/folder-tree.ts +0 -1069
  229. package/client/components/message-list.js.map +0 -1
  230. package/client/components/message-list.ts +0 -1129
  231. package/client/components/message-viewer.js.map +0 -1
  232. package/client/components/message-viewer.ts +0 -1257
  233. package/client/components/outbox-view.js.map +0 -1
  234. package/client/components/outbox-view.ts +0 -102
  235. package/client/components/tasks.js.map +0 -1
  236. package/client/components/tasks.ts +0 -234
  237. package/client/compose/compose.js.map +0 -1
  238. package/client/compose/compose.ts +0 -1231
  239. package/client/compose/editor.js.map +0 -1
  240. package/client/compose/editor.ts +0 -599
  241. package/client/compose/ghost-text.js.map +0 -1
  242. package/client/compose/ghost-text.ts +0 -140
  243. package/client/lib/android-bootstrap.js.map +0 -1
  244. package/client/lib/android-bootstrap.ts +0 -9
  245. package/client/lib/api-client.js.map +0 -1
  246. package/client/lib/api-client.ts +0 -439
  247. package/client/lib/local-service.js.map +0 -1
  248. package/client/lib/local-service.ts +0 -646
  249. package/client/lib/local-store.js.map +0 -1
  250. package/client/lib/local-store.ts +0 -283
  251. package/client/lib/message-state.js.map +0 -1
  252. package/client/lib/message-state.ts +0 -160
  253. package/client/tsconfig.json +0 -19
  254. package/packages/mailx-api/.gitattributes +0 -10
  255. package/packages/mailx-api/index.d.ts.map +0 -1
  256. package/packages/mailx-api/index.js.map +0 -1
  257. package/packages/mailx-api/index.ts +0 -283
  258. package/packages/mailx-api/tsconfig.json +0 -9
  259. package/packages/mailx-compose/.gitattributes +0 -10
  260. package/packages/mailx-compose/index.d.ts.map +0 -1
  261. package/packages/mailx-compose/index.js.map +0 -1
  262. package/packages/mailx-compose/index.ts +0 -85
  263. package/packages/mailx-compose/tsconfig.json +0 -9
  264. package/packages/mailx-host/.gitattributes +0 -10
  265. package/packages/mailx-host/index.d.ts +0 -21
  266. package/packages/mailx-host/index.d.ts.map +0 -1
  267. package/packages/mailx-host/index.js +0 -29
  268. package/packages/mailx-host/index.js.map +0 -1
  269. package/packages/mailx-host/index.ts +0 -38
  270. package/packages/mailx-host/package.json +0 -23
  271. package/packages/mailx-host/tsconfig.json +0 -9
  272. package/packages/mailx-host/types-shim.d.ts +0 -14
  273. package/packages/mailx-imap/.gitattributes +0 -10
  274. package/packages/mailx-imap/index.d.ts +0 -442
  275. package/packages/mailx-imap/index.d.ts.map +0 -1
  276. package/packages/mailx-imap/index.js +0 -3684
  277. package/packages/mailx-imap/index.js.map +0 -1
  278. package/packages/mailx-imap/index.ts +0 -3652
  279. package/packages/mailx-imap/package-lock.json +0 -131
  280. package/packages/mailx-imap/package.json +0 -28
  281. package/packages/mailx-imap/providers/gmail-api.d.ts +0 -8
  282. package/packages/mailx-imap/providers/gmail-api.d.ts.map +0 -1
  283. package/packages/mailx-imap/providers/gmail-api.js +0 -8
  284. package/packages/mailx-imap/providers/gmail-api.js.map +0 -1
  285. package/packages/mailx-imap/providers/gmail-api.ts +0 -8
  286. package/packages/mailx-imap/providers/outlook-api.ts +0 -7
  287. package/packages/mailx-imap/providers/types.d.ts +0 -9
  288. package/packages/mailx-imap/providers/types.d.ts.map +0 -1
  289. package/packages/mailx-imap/providers/types.js +0 -9
  290. package/packages/mailx-imap/providers/types.js.map +0 -1
  291. package/packages/mailx-imap/providers/types.ts +0 -9
  292. package/packages/mailx-imap/tsconfig.json +0 -9
  293. package/packages/mailx-imap/tsconfig.tsbuildinfo +0 -1
  294. package/packages/mailx-send/.gitattributes +0 -10
  295. package/packages/mailx-send/cli-queue.d.ts.map +0 -1
  296. package/packages/mailx-send/cli-queue.js.map +0 -1
  297. package/packages/mailx-send/cli-queue.ts +0 -62
  298. package/packages/mailx-send/cli-send.d.ts.map +0 -1
  299. package/packages/mailx-send/cli-send.js.map +0 -1
  300. package/packages/mailx-send/cli-send.ts +0 -83
  301. package/packages/mailx-send/cli.d.ts.map +0 -1
  302. package/packages/mailx-send/cli.js.map +0 -1
  303. package/packages/mailx-send/cli.ts +0 -126
  304. package/packages/mailx-send/index.d.ts.map +0 -1
  305. package/packages/mailx-send/index.js.map +0 -1
  306. package/packages/mailx-send/index.ts +0 -333
  307. package/packages/mailx-send/mailsend/cli.d.ts.map +0 -1
  308. package/packages/mailx-send/mailsend/cli.js.map +0 -1
  309. package/packages/mailx-send/mailsend/cli.ts +0 -81
  310. package/packages/mailx-send/mailsend/index.d.ts.map +0 -1
  311. package/packages/mailx-send/mailsend/index.js.map +0 -1
  312. package/packages/mailx-send/mailsend/index.ts +0 -333
  313. package/packages/mailx-send/mailsend/tsconfig.json +0 -21
  314. package/packages/mailx-send/package-lock.json +0 -65
  315. package/packages/mailx-send/tsconfig.json +0 -21
  316. package/packages/mailx-server/.gitattributes +0 -10
  317. package/packages/mailx-server/index.d.ts.map +0 -1
  318. package/packages/mailx-server/index.js.map +0 -1
  319. package/packages/mailx-server/index.ts +0 -429
  320. package/packages/mailx-server/tsconfig.json +0 -9
  321. package/packages/mailx-settings/.gitattributes +0 -10
  322. package/packages/mailx-settings/cloud.d.ts.map +0 -1
  323. package/packages/mailx-settings/cloud.js.map +0 -1
  324. package/packages/mailx-settings/cloud.ts +0 -388
  325. package/packages/mailx-settings/index.d.ts.map +0 -1
  326. package/packages/mailx-settings/index.js.map +0 -1
  327. package/packages/mailx-settings/index.ts +0 -892
  328. package/packages/mailx-settings/tsconfig.json +0 -9
  329. package/packages/mailx-store/.gitattributes +0 -10
  330. package/packages/mailx-store/db.d.ts.map +0 -1
  331. package/packages/mailx-store/db.js.map +0 -1
  332. package/packages/mailx-store/db.ts +0 -2007
  333. package/packages/mailx-store/file-store.d.ts.map +0 -1
  334. package/packages/mailx-store/file-store.js.map +0 -1
  335. package/packages/mailx-store/file-store.ts +0 -82
  336. package/packages/mailx-store/index.d.ts.map +0 -1
  337. package/packages/mailx-store/index.js.map +0 -1
  338. package/packages/mailx-store/index.ts +0 -7
  339. package/packages/mailx-store/tsconfig.json +0 -9
  340. package/packages/mailx-types/.gitattributes +0 -10
  341. package/packages/mailx-types/index.d.ts.map +0 -1
  342. package/packages/mailx-types/index.js.map +0 -1
  343. package/packages/mailx-types/index.ts +0 -498
  344. package/packages/mailx-types/tsconfig.json +0 -9
@@ -1,892 +0,0 @@
1
- /**
2
- * @bobfrankston/mailx-settings
3
- * Settings management with shared/local split.
4
- *
5
- * Files (shared — on OneDrive or similar):
6
- * accounts.jsonc — IMAP/SMTP account configs
7
- * preferences.jsonc — UI, sync, font settings
8
- * allowlist.jsonc — remote content allow-list
9
- *
10
- * Local overrides (~/.mailx/):
11
- * config.jsonc — pointer to shared dir + local-only settings (storePath, historyDays)
12
- * accounts.jsonc — cached copy, fallback when shared unavailable
13
- * preferences.jsonc — local overrides merged on top of shared
14
- * allowlist.jsonc — cached copy
15
- *
16
- * The old settings.jsonc is still supported for backward compatibility.
17
- */
18
-
19
- import * as fs from "node:fs";
20
- import * as path from "node:path";
21
- import { parse as parseJsonc } from "jsonc-parser";
22
- import type { MailxSettings, AccountConfig, AutocompleteSettings } from "@bobfrankston/mailx-types";
23
- import { getCloudProvider, gDriveFindOrCreateFolder, type CloudProvider } from "./cloud.js";
24
-
25
- // ── Paths ──
26
-
27
- const LOCAL_DIR = path.join(process.env.USERPROFILE || process.env.HOME || ".", ".mailx");
28
- const LOCAL_CONFIG_PATH = path.join(LOCAL_DIR, "config.jsonc");
29
- const LEGACY_CONFIG_PATH = path.join(LOCAL_DIR, "config.json");
30
- const DEFAULT_STORE_PATH = path.join(LOCAL_DIR, "mailxstore");
31
-
32
- interface SharedDirConfig {
33
- provider: "gdrive" | "google" | "local";
34
- path: string;
35
- /** Google Drive folder ID — cached locally, avoids name-based search */
36
- folderId?: string;
37
- }
38
-
39
- interface AccountOverride {
40
- enabled?: boolean;
41
- historyDays?: number;
42
- }
43
-
44
- interface LocalConfig {
45
- /** Directory containing shared settings — string, { provider, path }, or array to try in order */
46
- sharedDir?: string | SharedDirConfig | (string | SharedDirConfig)[];
47
- /** Legacy: single settings file path */
48
- settingsPath?: string;
49
- /** Local message store path */
50
- storePath?: string;
51
- /** Per-machine: days of history to sync (overrides shared) */
52
- historyDays?: number;
53
- /** Per-account overrides on this machine (enabled, historyDays) */
54
- accountOverrides?: Record<string, AccountOverride>;
55
- }
56
-
57
- /** Resolve a path from config — relative to ~/.mailx/, ~ expands to home */
58
- function resolvePath(p: string): string {
59
- if (!p) return p;
60
- const home = process.env.USERPROFILE || process.env.HOME || ".";
61
- // Expand ~ to home directory
62
- if (p.startsWith("~/") || p.startsWith("~\\")) return path.join(home, p.slice(2));
63
- if (p === "~") return home;
64
- // Absolute path — use as-is
65
- if (path.isAbsolute(p)) return p;
66
- // Relative — resolve from config directory (~/.mailx/)
67
- return path.resolve(LOCAL_DIR, p);
68
- }
69
-
70
- function readLocalConfig(): LocalConfig {
71
- // Migrate config.json → config.jsonc
72
- if (!fs.existsSync(LOCAL_CONFIG_PATH) && fs.existsSync(LEGACY_CONFIG_PATH)) {
73
- fs.renameSync(LEGACY_CONFIG_PATH, LOCAL_CONFIG_PATH);
74
- }
75
- if (!fs.existsSync(LOCAL_CONFIG_PATH)) return {};
76
- return readJsonc(LOCAL_CONFIG_PATH) || {};
77
- }
78
-
79
- /** Resolve provider config to a filesystem path (checks for local Google Drive mount) */
80
- function resolveProvider(cfg: SharedDirConfig): string | undefined {
81
- // Cloud providers use API only — no filesystem mount scanning
82
- if (cfg.provider === "gdrive" || cfg.provider === "google") return undefined;
83
- if (cfg.provider === "local") return resolvePath(cfg.path);
84
- return undefined;
85
- }
86
-
87
- /** Pending cloud config for API fallback (set when mount not found) */
88
- let pendingCloudConfig: SharedDirConfig | null = null;
89
- /** Last cloud API error (for UI display) */
90
- let lastCloudError: string | null = null;
91
-
92
- /** Subscribers notified whenever lastCloudError changes (push to UI immediately). */
93
- type CloudErrorListener = (error: string | null, context?: { op: "read" | "write"; filename: string }) => void;
94
- const cloudErrorListeners: CloudErrorListener[] = [];
95
- export function onCloudError(cb: CloudErrorListener): () => void {
96
- cloudErrorListeners.push(cb);
97
- return () => {
98
- const i = cloudErrorListeners.indexOf(cb);
99
- if (i >= 0) cloudErrorListeners.splice(i, 1);
100
- };
101
- }
102
- function setCloudError(error: string | null, context?: { op: "read" | "write"; filename: string }): void {
103
- lastCloudError = error;
104
- for (const cb of cloudErrorListeners) {
105
- try { cb(error, context); } catch { /* listener faults shouldn't break writes */ }
106
- }
107
- }
108
- export function getLastCloudError(): string | null { return lastCloudError; }
109
-
110
- function resolveSharedEntry(entry: string | SharedDirConfig): string | undefined {
111
- if (typeof entry === "string") {
112
- const p = resolvePath(entry);
113
- return fs.existsSync(p) ? p : undefined;
114
- }
115
- return resolveProvider(entry);
116
- }
117
-
118
- function getSharedDir(): string {
119
- const config = readLocalConfig();
120
- if (config.sharedDir) {
121
- const entries = Array.isArray(config.sharedDir) ? config.sharedDir : [config.sharedDir];
122
- for (const entry of entries) {
123
- const resolved = resolveSharedEntry(entry);
124
- if (resolved) return resolved;
125
- }
126
- // Set pending cloud config for API access
127
- if (!pendingCloudConfig) {
128
- const lastProvider = [...entries].reverse().find(e => typeof e !== "string") as SharedDirConfig | undefined;
129
- if (lastProvider) pendingCloudConfig = lastProvider;
130
- }
131
- }
132
- // Legacy settingsPath no longer used for shared dir — use loadLegacySettings() for reading only.
133
- return LOCAL_DIR;
134
- }
135
-
136
- /** Read a file via cloud API (when filesystem mount not available) */
137
- export async function cloudRead(filename: string): Promise<string | null> {
138
- if (!pendingCloudConfig) return null;
139
- // Ensure we have a folder ID
140
- if (!pendingCloudConfig.folderId) {
141
- pendingCloudConfig.folderId = await gDriveFindOrCreateFolder() || undefined;
142
- if (pendingCloudConfig.folderId) saveFolderIdToConfig(pendingCloudConfig.folderId);
143
- if (!pendingCloudConfig.folderId) {
144
- setCloudError(`Cannot read ${filename}: Google Drive folder unavailable (OAuth not granted?)`, { op: "read", filename });
145
- return null;
146
- }
147
- }
148
- const provider = getCloudProvider(pendingCloudConfig.provider as string, pendingCloudConfig.folderId);
149
- if (!provider) { setCloudError(`No cloud provider for ${pendingCloudConfig.provider}`, { op: "read", filename }); return null; }
150
- console.log(` [cloud] Reading ${filename} via ${pendingCloudConfig.provider} API...`);
151
- const content = await provider.read(filename);
152
- if (content) {
153
- setCloudError(null);
154
- // Cache locally
155
- try {
156
- fs.mkdirSync(LOCAL_DIR, { recursive: true });
157
- fs.writeFileSync(path.join(LOCAL_DIR, filename), content);
158
- } catch { /* ignore cache write failure */ }
159
- }
160
- // Don't set error for missing files — they may not exist yet (e.g., clients.jsonc on first run)
161
- return content;
162
- }
163
-
164
- /** Write a file via cloud API. Throws on failure with a descriptive error,
165
- * and updates lastCloudError so UI banners pick it up via getStorageInfo()
166
- * and the onCloudError listener. */
167
- export async function cloudWrite(filename: string, content: string): Promise<void> {
168
- if (!pendingCloudConfig) {
169
- const err = `Cloud not initialized yet — cannot save ${filename} (pendingCloudConfig is null; loadSettings may not have run, or config.jsonc has no sharedDir)`;
170
- console.error(` [cloud] cloudWrite: ${err}`);
171
- setCloudError(err, { op: "write", filename });
172
- throw new Error(err);
173
- }
174
- // Ensure we have a folder ID
175
- if (!pendingCloudConfig.folderId) {
176
- pendingCloudConfig.folderId = await gDriveFindOrCreateFolder() || undefined;
177
- if (pendingCloudConfig.folderId) saveFolderIdToConfig(pendingCloudConfig.folderId);
178
- if (!pendingCloudConfig.folderId) {
179
- const err = `Cannot save ${filename}: Google Drive folder unavailable (OAuth not granted or token expired)`;
180
- setCloudError(err, { op: "write", filename });
181
- throw new Error(err);
182
- }
183
- }
184
- const provider = getCloudProvider(pendingCloudConfig.provider as string, pendingCloudConfig.folderId);
185
- if (!provider) {
186
- const err = `No cloud provider for ${pendingCloudConfig.provider}`;
187
- setCloudError(err, { op: "write", filename });
188
- throw new Error(err);
189
- }
190
- try {
191
- await provider.write(filename, content);
192
- setCloudError(null);
193
- } catch (e: any) {
194
- setCloudError(e.message, { op: "write", filename });
195
- throw e;
196
- }
197
- }
198
-
199
- /** Persist the discovered folder ID back to config.jsonc so we don't search again */
200
- function saveFolderIdToConfig(folderId: string): void {
201
- try {
202
- const config = readLocalConfig();
203
- if (config.sharedDir && typeof config.sharedDir === "object" && !Array.isArray(config.sharedDir)) {
204
- (config.sharedDir as SharedDirConfig).folderId = folderId;
205
- atomicWrite(LOCAL_CONFIG_PATH, config);
206
- }
207
- } catch { /* non-critical */ }
208
- }
209
-
210
- /** Whether cloud API fallback is active */
211
- export function isCloudMode(): boolean {
212
- return pendingCloudConfig !== null;
213
- }
214
-
215
- /** Get storage provider info for display */
216
- export function getStorageInfo(): { provider: string; mode: "mount" | "api" | "local"; cloudPath?: string; cloudError?: string } {
217
- const config = readLocalConfig();
218
- if (config.sharedDir) {
219
- const entries = Array.isArray(config.sharedDir) ? config.sharedDir : [config.sharedDir];
220
- for (const entry of entries) {
221
- if (typeof entry === "string") {
222
- const resolved = resolveSharedEntry(entry);
223
- if (resolved && resolved !== LOCAL_DIR) {
224
- return { provider: "local", mode: "local", cloudPath: resolved };
225
- }
226
- continue;
227
- }
228
- // Provider-based entry — check for API mode (folderId) first, then mount
229
- const name = (entry.provider === "gdrive" || entry.provider === "google") ? "gdrive" : entry.provider;
230
- if (entry.folderId) {
231
- // Has folder ID → API mode (don't scan filesystem for mounts)
232
- return { provider: name, mode: "api", cloudPath: entry.path, cloudError: lastCloudError || undefined };
233
- }
234
- const resolved = resolveSharedEntry(entry);
235
- if (resolved && resolved !== LOCAL_DIR) {
236
- return { provider: name, mode: "mount", cloudPath: entry.path };
237
- }
238
- }
239
- // Not mounted and no folderId — check pendingCloudConfig from initCloudConfig()
240
- if (pendingCloudConfig) {
241
- const name = (pendingCloudConfig.provider === "gdrive" || pendingCloudConfig.provider === "google") ? "gdrive" : pendingCloudConfig.provider;
242
- return { provider: name, mode: "api", cloudPath: pendingCloudConfig.path, cloudError: lastCloudError || undefined };
243
- }
244
- }
245
- return { provider: "local", mode: "local" };
246
- }
247
-
248
- // ── File helpers ──
249
-
250
- /** Read JSON or JSONC file. If exact path not found, tries .json/.jsonc variant. */
251
- function readJsonc(filePath: string): any {
252
- let actual = filePath;
253
- if (!fs.existsSync(actual)) {
254
- // Try alternate extension
255
- if (actual.endsWith(".jsonc")) actual = actual.replace(/\.jsonc$/, ".json");
256
- else if (actual.endsWith(".json")) actual = actual.replace(/\.json$/, ".jsonc");
257
- if (!fs.existsSync(actual)) return null;
258
- }
259
- try {
260
- return parseJsonc(fs.readFileSync(actual, "utf-8").replace(/\r/g, ""));
261
- } catch (e: any) {
262
- console.error(`Failed to read ${actual}: ${e.message}`);
263
- return null;
264
- }
265
- }
266
-
267
- function atomicWrite(filePath: string, data: any): void {
268
- const dir = path.dirname(filePath);
269
- fs.mkdirSync(dir, { recursive: true });
270
- const tmp = filePath + ".tmp";
271
- fs.writeFileSync(tmp, JSON.stringify(data, null, 2));
272
- fs.renameSync(tmp, filePath);
273
- }
274
-
275
- /** Read a config file: shared first, local fallback, merge local overrides */
276
- function loadFile<T>(filename: string, defaults: T): T {
277
- const sharedDir = getSharedDir();
278
- const sharedPath = path.join(sharedDir, filename);
279
- const localPath = path.join(LOCAL_DIR, filename);
280
-
281
- // Read shared version
282
- let data = readJsonc(sharedPath);
283
-
284
- // If no shared, try local
285
- if (!data) data = readJsonc(localPath);
286
-
287
- // If neither exists, use defaults
288
- if (!data) return defaults;
289
-
290
- // Cache shared → local (for offline fallback)
291
- if (sharedDir !== LOCAL_DIR && fs.existsSync(sharedPath)) {
292
- try {
293
- const shared = fs.readFileSync(sharedPath, "utf-8");
294
- fs.mkdirSync(LOCAL_DIR, { recursive: true });
295
- fs.writeFileSync(localPath, shared);
296
- } catch { /* ignore cache failures */ }
297
- }
298
-
299
- return { ...defaults, ...data };
300
- }
301
-
302
- /** Save a config file to the shared directory (and cloud API if active).
303
- * Always writes the local copy first so the data is never lost.
304
- * If a cloud provider is configured, also writes there — failures set
305
- * lastCloudError and notify onCloudError listeners so the UI can show
306
- * a banner. The cloud write is fire-and-forget at this layer; callers
307
- * who need the result should use the typed save* helpers below. */
308
- function saveFile(filename: string, data: any): void {
309
- const sharedDir = getSharedDir();
310
- atomicWrite(path.join(sharedDir, filename), data);
311
- // Also update local cache
312
- if (sharedDir !== LOCAL_DIR) {
313
- try { atomicWrite(path.join(LOCAL_DIR, filename), data); } catch { /* ignore */ }
314
- }
315
- // Write to cloud API if filesystem mount unavailable (creates app-owned file for drive.file scope)
316
- if (pendingCloudConfig) {
317
- cloudWrite(filename, JSON.stringify(data, null, 2))
318
- .then(() => console.log(` [cloud] Saved ${filename} via ${pendingCloudConfig!.provider} API`))
319
- .catch(e => console.error(` [cloud] Failed to save ${filename}: ${e.message}`));
320
- // Note: we don't await — saveFile is sync. cloudWrite() already calls
321
- // setCloudError(), which fires onCloudError listeners synchronously
322
- // when the promise rejects, so the UI gets the failure even though
323
- // we don't propagate it back through the call chain here.
324
- }
325
- }
326
-
327
- // ── Provider defaults ──
328
-
329
- interface ProviderDefaults {
330
- label: string;
331
- imap: { host: string; port: number; tls: boolean; auth: "password" | "oauth2" };
332
- smtp: { host: string; port: number; tls: boolean; auth: "password" | "oauth2" };
333
- // The former per-provider `spam` path field was removed 2026-04-22 —
334
- // markAsSpamMessages now finds the junk folder via `specialUse === "junk"`
335
- // on the DB-stored folder record, populated by mailx-imap from iflow's
336
- // getSpecialFolders() (RFC 6154 flags with sensible defaults for servers
337
- // that don't advertise them). The provider-default path was never
338
- // authoritative anyway — servers don't agree ("Junk Email" vs "Junk"
339
- // vs "SPAM" vs "_Spam" vs "Bulk Mail") and we already have the right
340
- // answer from the server.
341
- }
342
-
343
- const PROVIDERS: Record<string, ProviderDefaults> = {
344
- "gmail.com": {
345
- label: "Gmail",
346
- imap: { host: "imap.gmail.com", port: 993, tls: true, auth: "oauth2" },
347
- smtp: { host: "smtp.gmail.com", port: 587, tls: true, auth: "oauth2" },
348
- },
349
- "googlemail.com": {
350
- label: "Gmail",
351
- imap: { host: "imap.gmail.com", port: 993, tls: true, auth: "oauth2" },
352
- smtp: { host: "smtp.gmail.com", port: 587, tls: true, auth: "oauth2" },
353
- },
354
- "outlook.com": {
355
- label: "Outlook",
356
- imap: { host: "outlook.office365.com", port: 993, tls: true, auth: "oauth2" },
357
- smtp: { host: "smtp.office365.com", port: 587, tls: true, auth: "oauth2" },
358
- },
359
- "hotmail.com": {
360
- label: "Hotmail",
361
- imap: { host: "outlook.office365.com", port: 993, tls: true, auth: "oauth2" },
362
- smtp: { host: "smtp.office365.com", port: 587, tls: true, auth: "oauth2" },
363
- },
364
- "yahoo.com": {
365
- label: "Yahoo",
366
- imap: { host: "imap.mail.yahoo.com", port: 993, tls: true, auth: "password" },
367
- smtp: { host: "smtp.mail.yahoo.com", port: 587, tls: true, auth: "password" },
368
- },
369
- "aol.com": {
370
- label: "AOL",
371
- imap: { host: "imap.aol.com", port: 993, tls: true, auth: "password" },
372
- smtp: { host: "smtp.aol.com", port: 587, tls: true, auth: "password" },
373
- },
374
- "icloud.com": {
375
- label: "iCloud",
376
- imap: { host: "imap.mail.me.com", port: 993, tls: true, auth: "password" },
377
- smtp: { host: "smtp.mail.me.com", port: 587, tls: true, auth: "password" },
378
- },
379
- };
380
-
381
- /** Fill in provider defaults for an account based on email domain.
382
- * Exported so mailx-service's leanAccountsJsonc helper can reuse the same
383
- * canonicalization rules that loadAccounts uses. */
384
- export function normalizeAccount(acct: any, globalName?: string): AccountConfig {
385
- const email = acct.email || "";
386
- const localPart = email.split("@")[0]?.toLowerCase() || "";
387
- const domain = email.split("@")[1]?.toLowerCase() || "";
388
- const provider = PROVIDERS[domain];
389
- const user = acct.imap?.user || acct.user || email;
390
-
391
- // P14: auto-derive id and label so a known-provider account works with just
392
- // { email, password? } in accounts.jsonc. id defaults to local-part (most
393
- // accounts have a unique local-part); label defaults to provider name or id.
394
- // Generic local-parts (info, admin, support, no-reply, ...) fall back to
395
- // the domain stem to avoid collisions across accounts.
396
- const GENERIC_LOCALS = new Set(["info", "admin", "support", "no-reply", "noreply", "contact", "hello", "mail", "office"]);
397
- const domainStem = domain.split(".")[0] || "";
398
- const autoId = (localPart && !GENERIC_LOCALS.has(localPart)) ? localPart : (domainStem || "account");
399
-
400
- return {
401
- id: acct.id || autoId,
402
- name: acct.name || globalName || localPart,
403
- label: acct.label || provider?.label || acct.id || autoId,
404
- email,
405
- imap: {
406
- host: acct.imap?.host || provider?.imap.host || `imap.${domain}`,
407
- port: acct.imap?.port || provider?.imap.port || 993,
408
- tls: acct.imap?.tls ?? provider?.imap.tls ?? true,
409
- auth: acct.imap?.auth || provider?.imap.auth || "password",
410
- user: acct.imap?.user || user,
411
- password: acct.imap?.password || acct.password,
412
- },
413
- smtp: {
414
- host: acct.smtp?.host || provider?.smtp.host || `smtp.${domain}`,
415
- port: acct.smtp?.port || provider?.smtp.port || 587,
416
- tls: acct.smtp?.tls ?? provider?.smtp.tls ?? true,
417
- auth: acct.smtp?.auth || provider?.smtp.auth || "password",
418
- user: acct.smtp?.user || user,
419
- password: acct.smtp?.password || acct.password,
420
- },
421
- enabled: acct.enabled ?? true,
422
- primary: acct.primary,
423
- primaryCalendar: (acct as any).primaryCalendar,
424
- primaryTasks: (acct as any).primaryTasks,
425
- primaryContacts: (acct as any).primaryContacts,
426
- defaultSend: acct.defaultSend,
427
- syncContacts: acct.syncContacts ?? (provider?.imap.auth === "oauth2"),
428
- relayDomains: acct.relayDomains,
429
- deliveredToPrefix: acct.deliveredToPrefix,
430
- identityDomains: acct.identityDomains,
431
- // `spam` passthrough retired 2026-04-22 — markAsSpamMessages now finds
432
- // the junk folder via `specialUse === "junk"` on the DB folder record
433
- // (populated by mailx-imap from iflow's getSpecialFolders()). Authoritative
434
- // per-server info beats per-domain guesses.
435
- // `signature` is on AccountConfig in mailx-types but the workspace
436
- // build order sometimes leaves a stale .d.ts for type-check; using
437
- // `as any` is the minimum-blast-radius way to add the field without
438
- // blocking the build. Once mailx-types has rebuilt once (post-field)
439
- // this cast can be removed.
440
- ...(acct.signature ? { signature: acct.signature } : {}),
441
- ...(acct.sig && typeof acct.sig === "object" && typeof acct.sig.text === "string"
442
- ? { sig: { text: acct.sig.text, html: !!acct.sig.html } } : {}),
443
- } as AccountConfig;
444
- }
445
-
446
- // ── Defaults ──
447
-
448
- const DEFAULT_ACCOUNTS: AccountConfig[] = [];
449
-
450
- const DEFAULT_PREFERENCES = {
451
- ui: {
452
- theme: "system" as "system" | "dark" | "light",
453
- editor: "quill" as "quill" | "tiptap",
454
- folderWidth: 220,
455
- listViewerSplit: 40,
456
- fontSize: 15,
457
- },
458
- sync: {
459
- intervalMinutes: 5,
460
- historyDays: 30,
461
- prefetch: true,
462
- },
463
- autocomplete: {
464
- enabled: false,
465
- provider: "ollama" as const,
466
- ollamaUrl: "http://localhost:11434",
467
- ollamaModel: "qwen2.5-coder:1.5b",
468
- cloudApiKey: "",
469
- cloudModel: "claude-sonnet-4-20250514",
470
- debounceMs: 600,
471
- maxTokens: 60,
472
- },
473
- };
474
-
475
- const DEFAULT_AUTOCOMPLETE: AutocompleteSettings = {
476
- enabled: false,
477
- provider: "ollama",
478
- ollamaUrl: "http://localhost:11434",
479
- ollamaModel: "qwen2.5-coder:1.5b",
480
- cloudApiKey: "",
481
- cloudModel: "claude-sonnet-4-20250514",
482
- debounceMs: 600,
483
- maxTokens: 60,
484
- };
485
-
486
- const DEFAULT_ALLOWLIST = {
487
- senders: [] as string[],
488
- domains: [] as string[],
489
- recipients: [] as string[],
490
- // Flagged senders/domains — surfaced as a red warning on the
491
- // remote-content banner. Distinct from the positive lists above:
492
- // these don't auto-allow anything, they make the banner louder when
493
- // a known-suspicious correspondent's mail shows up. Future: a shared
494
- // GitHub list users can opt in to (community-flagged phishing /
495
- // tracker-heavy senders) — see TODO.md.
496
- flaggedSenders: [] as string[],
497
- flaggedDomains: [] as string[],
498
- };
499
-
500
- // ── Public API ──
501
-
502
- /** Load account configs */
503
- export function loadAccounts(): AccountConfig[] {
504
- const sharedDir = getSharedDir();
505
- const sharedPath = path.join(sharedDir, "accounts.jsonc");
506
- const localPath = path.join(LOCAL_DIR, "accounts.jsonc");
507
-
508
- // Try shared first, then local cache
509
- let accounts = readJsonc(sharedPath);
510
- if (!accounts) accounts = readJsonc(localPath);
511
-
512
- if (accounts?.accounts || Array.isArray(accounts)) {
513
- // Cache shared to local for offline fallback — but ONLY if the
514
- // content actually differs. Unconditionally writing on every load
515
- // retriggers fs.watch on the local copy, which fires the config-
516
- // changed banner and cloud-poll cycle even when nothing changed.
517
- // Result: "accounts.jsonc changed" notification firing constantly.
518
- if (sharedDir !== LOCAL_DIR && fs.existsSync(sharedPath)) {
519
- try {
520
- const sharedContent = fs.readFileSync(sharedPath, "utf-8");
521
- let localContent = "";
522
- try { localContent = fs.readFileSync(localPath, "utf-8"); } catch { /* missing */ }
523
- // Normalize before comparing — GDrive-mounted copies often
524
- // differ in BOM / line endings / trailing newline without any
525
- // semantic change, and that triggered the spurious banner.
526
- const norm = (s: string): string =>
527
- s.replace(/^\uFEFF/, "").replace(/\r\n/g, "\n").replace(/[ \t\r\n]+$/, "");
528
- if (norm(sharedContent) !== norm(localContent)) {
529
- fs.mkdirSync(LOCAL_DIR, { recursive: true });
530
- fs.writeFileSync(localPath, sharedContent);
531
- }
532
- } catch { /* ignore */ }
533
- }
534
- const raw: any[] = accounts.accounts || accounts;
535
- const globalName: string = accounts.name || "";
536
- const result = deduplicateAccounts(raw.map((a: any) => normalizeAccount(a, globalName)));
537
- return applyAccountOverrides(result);
538
- }
539
-
540
- // Legacy: read from settings.jsonc
541
- const legacy = loadLegacySettings();
542
- if (legacy?.accounts) return applyAccountOverrides(legacy.accounts.map((a: any) => normalizeAccount(a, legacy.name)));
543
-
544
- return DEFAULT_ACCOUNTS;
545
- }
546
-
547
- /** Normalize email for dedup — Gmail ignores dots before @ */
548
- function normalizeEmail(email: string): string {
549
- const [local, domain] = email.toLowerCase().split("@");
550
- if (!domain) return email.toLowerCase();
551
- if (domain === "gmail.com" || domain === "googlemail.com") {
552
- return local.replace(/\./g, "") + "@gmail.com";
553
- }
554
- return `${local}@${domain}`;
555
- }
556
-
557
- /** Remove duplicate accounts (same email after normalization) */
558
- function deduplicateAccounts(accounts: AccountConfig[]): AccountConfig[] {
559
- const seen = new Set<string>();
560
- return accounts.filter(a => {
561
- const key = normalizeEmail(a.email);
562
- if (seen.has(key)) return false;
563
- seen.add(key);
564
- return true;
565
- });
566
- }
567
-
568
- /** Apply local per-account overrides (enabled, etc.) */
569
- function applyAccountOverrides(accounts: AccountConfig[]): AccountConfig[] {
570
- const localConfig = readLocalConfig();
571
- const overrides = localConfig.accountOverrides;
572
- if (!overrides) return accounts;
573
- for (const acct of accounts) {
574
- const ov = overrides[acct.id];
575
- if (!ov) continue;
576
- if (ov.enabled !== undefined) acct.enabled = ov.enabled;
577
- }
578
- return accounts;
579
- }
580
-
581
- /** Load accounts with cloud API fallback (async — use when cloud settings may not be mounted) */
582
- export async function loadAccountsAsync(): Promise<AccountConfig[]> {
583
- // Try sync first (filesystem)
584
- const accounts = loadAccounts();
585
- if (accounts.length > 0) return accounts;
586
-
587
- // Try cloud API fallback
588
- if (pendingCloudConfig) {
589
- console.log(" [cloud] Trying cloud API for accounts...");
590
- const content = await cloudRead("accounts.jsonc");
591
- if (content) {
592
- const data = parseJsonc(content);
593
- if (data?.accounts || Array.isArray(data)) {
594
- const raw: any[] = data.accounts || data;
595
- const globalName: string = data.name || "";
596
- return applyAccountOverrides(raw.map((a: any) => normalizeAccount(a, globalName)));
597
- }
598
- }
599
- // Legacy settings.jsonc is no longer read — use accounts.jsonc only
600
- }
601
- return [];
602
- }
603
-
604
- /** Strip default-valued fields from a normalized AccountConfig so the
605
- * serialized JSONC stays compact and human-editable. The previous version
606
- * was round-tripping every defaulted field (port: 993, tls: true, auth:
607
- * "password", enabled: true, etc.), which bloated a 10-line accounts.jsonc
608
- * to 60+ lines and embedded unnecessary "knowledge" about defaults.
609
- *
610
- * Rules:
611
- * - Drop fields that match the provider default (host/port/tls/auth derived
612
- * from email domain via PROVIDERS).
613
- * - Drop `enabled: true` (default).
614
- * - Drop `name` if equal to the file-level `globalName`.
615
- * - Drop `imap.user` / `smtp.user` if they equal the email.
616
- * - Drop `sig.html: false` (default; only keep when user enabled HTML sigs).
617
- * - Keep field order: id → label → email → primary* → imap → smtp → defaultSend
618
- * → relayDomains → deliveredToPrefix → identityDomains → syncContacts → sig.
619
- * Curated for readability, not alphabetic. */
620
- export function denormalizeAccount(acct: AccountConfig, globalName?: string): any {
621
- const domain = (acct.email || "").split("@")[1]?.toLowerCase() || "";
622
- const provider = PROVIDERS[domain];
623
- const out: any = {};
624
- out.id = acct.id;
625
- if (acct.label && acct.label !== provider?.label && acct.label !== acct.id) out.label = acct.label;
626
- out.email = acct.email;
627
- if (acct.name && acct.name !== globalName) out.name = acct.name;
628
- if (acct.primary) out.primary = true;
629
- if ((acct as any).primaryCalendar !== undefined) out.primaryCalendar = (acct as any).primaryCalendar;
630
- if ((acct as any).primaryTasks !== undefined) out.primaryTasks = (acct as any).primaryTasks;
631
- if ((acct as any).primaryContacts !== undefined) out.primaryContacts = (acct as any).primaryContacts;
632
- // imap — keep only fields that differ from provider defaults.
633
- const imapOut: any = {};
634
- if (acct.imap?.host && acct.imap.host !== provider?.imap.host) imapOut.host = acct.imap.host;
635
- if (acct.imap?.user && acct.imap.user !== acct.email) imapOut.user = acct.imap.user;
636
- if (acct.imap?.password) imapOut.password = acct.imap.password;
637
- if (acct.imap?.port && acct.imap.port !== (provider?.imap.port ?? 993)) imapOut.port = acct.imap.port;
638
- if (acct.imap?.tls !== undefined && acct.imap.tls !== (provider?.imap.tls ?? true)) imapOut.tls = acct.imap.tls;
639
- if (acct.imap?.auth && acct.imap.auth !== (provider?.imap.auth ?? "password")) imapOut.auth = acct.imap.auth;
640
- if (Object.keys(imapOut).length > 0) out.imap = imapOut;
641
- // smtp — same treatment.
642
- const smtpOut: any = {};
643
- if (acct.smtp?.host && acct.smtp.host !== provider?.smtp.host) smtpOut.host = acct.smtp.host;
644
- if (acct.smtp?.user && acct.smtp.user !== acct.email && acct.smtp.user !== acct.imap?.user) smtpOut.user = acct.smtp.user;
645
- if (acct.smtp?.password) smtpOut.password = acct.smtp.password;
646
- if (acct.smtp?.port && acct.smtp.port !== (provider?.smtp.port ?? 587)) smtpOut.port = acct.smtp.port;
647
- if (acct.smtp?.tls !== undefined && acct.smtp.tls !== (provider?.smtp.tls ?? true)) smtpOut.tls = acct.smtp.tls;
648
- if (acct.smtp?.auth && acct.smtp.auth !== (provider?.smtp.auth ?? "password")) smtpOut.auth = acct.smtp.auth;
649
- if (Object.keys(smtpOut).length > 0) out.smtp = smtpOut;
650
- if (acct.defaultSend) out.defaultSend = true;
651
- if (acct.enabled === false) out.enabled = false; // default true → omit
652
- if (acct.relayDomains && acct.relayDomains.length > 0) out.relayDomains = acct.relayDomains;
653
- if (acct.deliveredToPrefix && acct.deliveredToPrefix.length > 0) out.deliveredToPrefix = acct.deliveredToPrefix;
654
- if (acct.identityDomains && acct.identityDomains.length > 0) out.identityDomains = acct.identityDomains;
655
- // syncContacts default: true for OAuth, false otherwise. Only emit when
656
- // the user overrode the default.
657
- const syncContactsDefault = provider?.imap.auth === "oauth2";
658
- if (acct.syncContacts !== undefined && acct.syncContacts !== syncContactsDefault) {
659
- out.syncContacts = acct.syncContacts;
660
- }
661
- if ((acct as any).signature) out.signature = (acct as any).signature;
662
- if (acct.sig?.text) {
663
- // html: false is default; only keep the html flag when explicitly true.
664
- out.sig = (acct.sig as any).html ? { text: acct.sig.text, html: true } : { text: acct.sig.text };
665
- }
666
- return out;
667
- }
668
-
669
- /** Save account configs */
670
- /** Save accounts — merges with cloud copy by email (multi-client safe).
671
- * Writes the lean form via denormalizeAccount so accounts.jsonc stays
672
- * compact and human-editable. */
673
- export async function saveAccounts(accounts: AccountConfig[]): Promise<void> {
674
- // Merge with cloud: keep all accounts, deduplicate by normalized email
675
- try {
676
- const cloudContent = await cloudRead("accounts.jsonc");
677
- if (cloudContent) {
678
- const cloud = parseJsonc(cloudContent);
679
- const cloudAccts: any[] = cloud?.accounts || (Array.isArray(cloud) ? cloud : []);
680
- if (cloudAccts.length > 0) {
681
- const seen = new Set(accounts.map(a => normalizeEmail(a.email)));
682
- for (const ca of cloudAccts) {
683
- if (ca.email && !seen.has(normalizeEmail(ca.email))) {
684
- // Cloud entries are already lean — feed through
685
- // normalizeAccount to coerce to AccountConfig shape.
686
- accounts.push(normalizeAccount(ca));
687
- seen.add(normalizeEmail(ca.email));
688
- }
689
- }
690
- }
691
- }
692
- } catch { /* cloud read failed — save local version */ }
693
- // Promote a shared "name" to file level when every account has the same
694
- // name — keeps the JSONC tidy ({ "name": "Bob Frankston", "accounts": [...] }
695
- // instead of repeating "name" on each entry).
696
- const names = new Set(accounts.map(a => a.name).filter(Boolean));
697
- const globalName = names.size === 1 ? [...names][0] : undefined;
698
- const lean = accounts.map(a => denormalizeAccount(a, globalName));
699
- const payload: any = globalName ? { name: globalName, accounts: lean } : { accounts: lean };
700
- saveFile("accounts.jsonc", payload);
701
- }
702
-
703
- /** Load preferences (shared + local overrides, with legacy fallback) */
704
- export function loadPreferences(): typeof DEFAULT_PREFERENCES {
705
- let shared = loadFile("preferences.jsonc", DEFAULT_PREFERENCES);
706
-
707
- // Legacy fallback: read ui/sync from settings.jsonc if preferences.jsonc had only defaults
708
- const legacy = loadLegacySettings();
709
- if (legacy?.ui) shared = { ...shared, ui: { ...shared.ui, ...legacy.ui } };
710
- if (legacy?.sync) shared = { ...shared, sync: { ...shared.sync, ...legacy.sync } };
711
-
712
- const localConfig = readLocalConfig();
713
-
714
- // Local overrides
715
- if (localConfig.historyDays !== undefined) {
716
- shared.sync.historyDays = localConfig.historyDays;
717
- }
718
-
719
- return {
720
- ui: { ...DEFAULT_PREFERENCES.ui, ...shared.ui },
721
- sync: { ...DEFAULT_PREFERENCES.sync, ...shared.sync },
722
- autocomplete: { ...DEFAULT_AUTOCOMPLETE, ...shared.autocomplete },
723
- };
724
- }
725
-
726
- /** Save preferences */
727
- export function savePreferences(prefs: any): void {
728
- saveFile("preferences.jsonc", prefs);
729
- }
730
-
731
- /** Load autocomplete settings */
732
- export function loadAutocomplete(): AutocompleteSettings {
733
- const prefs = loadPreferences();
734
- return prefs.autocomplete;
735
- }
736
-
737
- /** Save autocomplete settings */
738
- export function saveAutocomplete(settings: AutocompleteSettings): void {
739
- const prefs = loadPreferences() as any;
740
- prefs.autocomplete = settings;
741
- savePreferences(prefs);
742
- }
743
-
744
- /** Load remote content allow-list */
745
- export function loadAllowlist(): typeof DEFAULT_ALLOWLIST {
746
- return loadFile("allowlist.jsonc", DEFAULT_ALLOWLIST);
747
- }
748
-
749
- /** Save allow-list — merges with existing cloud copy (multi-client safe) */
750
- export async function saveAllowlist(list: typeof DEFAULT_ALLOWLIST): Promise<void> {
751
- // Read current cloud version and merge (other clients may have added entries)
752
- let merged = { ...list };
753
- try {
754
- const cloudContent = await cloudRead("allowlist.jsonc");
755
- if (cloudContent) {
756
- const cloud = parseJsonc(cloudContent);
757
- if (cloud) {
758
- const mergeArrays = (local: string[], remote: string[]): string[] =>
759
- [...new Set([...local, ...remote])];
760
- merged = {
761
- senders: mergeArrays(list.senders || [], cloud.senders || []),
762
- domains: mergeArrays(list.domains || [], cloud.domains || []),
763
- recipients: mergeArrays(list.recipients || [], cloud.recipients || []),
764
- flaggedSenders: mergeArrays(list.flaggedSenders || [], cloud.flaggedSenders || []),
765
- flaggedDomains: mergeArrays(list.flaggedDomains || [], cloud.flaggedDomains || []),
766
- };
767
- }
768
- }
769
- } catch { /* cloud read failed — save local version */ }
770
- saveFile("allowlist.jsonc", merged);
771
- }
772
-
773
- // ── Legacy compatibility ──
774
-
775
- function loadLegacySettings(): any {
776
- const config = readLocalConfig();
777
- if (config.settingsPath) return readJsonc(resolvePath(config.settingsPath));
778
- // Try shared dir first, then local
779
- const sharedDir = getSharedDir();
780
- const shared = readJsonc(path.join(sharedDir, "settings.jsonc"));
781
- if (shared) return shared;
782
- return readJsonc(path.join(LOCAL_DIR, "settings.jsonc"));
783
- }
784
-
785
- /** Load settings — unified view combining all files (backward compatible) */
786
- export function loadSettings(): MailxSettings {
787
- const accounts = loadAccounts();
788
- const prefs = loadPreferences();
789
- const localConfig = readLocalConfig();
790
-
791
- return {
792
- accounts,
793
- ui: prefs.ui,
794
- sync: prefs.sync,
795
- autocomplete: prefs.autocomplete,
796
- store: {
797
- basePath: localConfig.storePath || DEFAULT_STORE_PATH,
798
- compressionBoundaryDays: 365,
799
- },
800
- };
801
- }
802
-
803
- /** Save settings — writes to split files */
804
- export async function saveSettings(settings: MailxSettings): Promise<void> {
805
- await saveAccounts(settings.accounts);
806
- savePreferences({ ui: settings.ui, sync: settings.sync });
807
- }
808
-
809
- /** Get the local store base path */
810
- export function getStorePath(): string {
811
- const config = readLocalConfig();
812
- return config.storePath ? resolvePath(config.storePath) : DEFAULT_STORE_PATH;
813
- }
814
-
815
- /** Get the local data directory (DB, store, etc.) */
816
- export function getConfigDir(): string {
817
- return LOCAL_DIR;
818
- }
819
-
820
- /** Get the shared settings directory */
821
- export { getSharedDir };
822
-
823
- // detectSharedDir() removed — cloud storage is configured via API (gdrive/onedrive),
824
- // not auto-detected from filesystem mounts. Setup form triggers initCloudConfig().
825
-
826
- /** Initialize local config if it doesn't exist */
827
- export function initLocalConfig(sharedDir?: string, storePath?: string): void {
828
- if (fs.existsSync(LOCAL_CONFIG_PATH) && !sharedDir && !storePath) return;
829
- const existing = readLocalConfig();
830
-
831
- // Use explicit sharedDir or preserve existing — no auto-detection.
832
- // Cloud storage is configured when user adds an account (initCloudConfig).
833
- const resolvedSharedDir: LocalConfig["sharedDir"] = sharedDir || existing.sharedDir;
834
-
835
- const config: LocalConfig = {
836
- ...existing,
837
- sharedDir: resolvedSharedDir,
838
- storePath: storePath || existing.storePath || DEFAULT_STORE_PATH,
839
- };
840
-
841
- fs.mkdirSync(LOCAL_DIR, { recursive: true });
842
- atomicWrite(LOCAL_CONFIG_PATH, config);
843
- }
844
-
845
- /** Initialize config with Google Drive cloud storage.
846
- * Finds or creates the app-owned "mailx" folder via Drive API and stores its ID.
847
- * No mount scanning — API only. Existing settings at other paths (e.g., home/.mailx
848
- * from Desktop sync) must be migrated manually or via config.jsonc importPath. */
849
- export async function initCloudConfig(provider: "gdrive" = "gdrive"): Promise<void> {
850
- const existing = readLocalConfig();
851
- if (existing.sharedDir) return; // Already configured
852
- // Find or create the settings folder via Drive API — tries "home/.mailx"
853
- // first (family convention), then "mailx" (default). The found path gets
854
- // saved so future lookups don't re-scan.
855
- const folderId = await gDriveFindOrCreateFolder();
856
- // Detect which path was actually found by reading back from the API
857
- // (gDriveFindOrCreateFolder logs it). For now use "mailx" as default
858
- // label — the folderId is what matters for subsequent reads/writes.
859
- const sharedDir: SharedDirConfig = { provider, path: "home/.mailx", folderId: folderId || undefined };
860
- const config: LocalConfig = { ...existing, sharedDir, storePath: existing.storePath || DEFAULT_STORE_PATH };
861
- fs.mkdirSync(LOCAL_DIR, { recursive: true });
862
- atomicWrite(LOCAL_CONFIG_PATH, config);
863
- pendingCloudConfig = sharedDir;
864
- console.log(` Initialized cloud config: ${provider} (folder ID: ${folderId || "pending"})`);
865
- }
866
-
867
- const DEFAULT_SETTINGS: MailxSettings = {
868
- accounts: [],
869
- ui: DEFAULT_PREFERENCES.ui,
870
- sync: DEFAULT_PREFERENCES.sync,
871
- autocomplete: DEFAULT_AUTOCOMPLETE,
872
- store: { basePath: DEFAULT_STORE_PATH, compressionBoundaryDays: 365 },
873
- };
874
-
875
- /** Get historyDays for an account: per-account override > system override > shared default */
876
- export function getHistoryDays(accountId?: string): number {
877
- const localConfig = readLocalConfig();
878
- if (accountId && localConfig.accountOverrides?.[accountId]?.historyDays !== undefined) {
879
- return localConfig.accountOverrides[accountId].historyDays!;
880
- }
881
- if (localConfig.historyDays !== undefined) return localConfig.historyDays;
882
- const prefs = loadPreferences();
883
- return prefs.sync.historyDays || 0;
884
- }
885
-
886
- /** Get prefetch setting: download bodies during sync (default true) */
887
- export function getPrefetch(): boolean {
888
- const prefs = loadPreferences();
889
- return prefs.sync.prefetch !== false;
890
- }
891
-
892
- export { DEFAULT_SETTINGS, DEFAULT_ALLOWLIST, DEFAULT_PREFERENCES, DEFAULT_AUTOCOMPLETE, LOCAL_DIR };