fluxmail 0.12.0 → 0.14.0

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 (261) hide show
  1. package/README.md +1 -1
  2. package/dist/accounts/registry.d.ts +1 -0
  3. package/dist/accounts/registry.d.ts.map +1 -1
  4. package/dist/accounts/registry.js +7 -3
  5. package/dist/accounts/registry.js.map +1 -1
  6. package/dist/accounts/telemetry.d.ts +6 -1
  7. package/dist/accounts/telemetry.d.ts.map +1 -1
  8. package/dist/accounts/telemetry.js +17 -0
  9. package/dist/accounts/telemetry.js.map +1 -1
  10. package/dist/cli.d.ts.map +1 -1
  11. package/dist/cli.js +206 -40
  12. package/dist/cli.js.map +1 -1
  13. package/dist/cliInstances.d.ts +6 -4
  14. package/dist/cliInstances.d.ts.map +1 -1
  15. package/dist/cliInstances.js +38 -17
  16. package/dist/cliInstances.js.map +1 -1
  17. package/dist/cliMail.d.ts.map +1 -1
  18. package/dist/cliMail.js +1 -1
  19. package/dist/cliMail.js.map +1 -1
  20. package/dist/cliStartupDiagnostics.d.ts +12 -0
  21. package/dist/cliStartupDiagnostics.d.ts.map +1 -0
  22. package/dist/cliStartupDiagnostics.js +47 -0
  23. package/dist/cliStartupDiagnostics.js.map +1 -0
  24. package/dist/context.d.ts +1 -1
  25. package/dist/context.d.ts.map +1 -1
  26. package/dist/context.js +15 -16
  27. package/dist/context.js.map +1 -1
  28. package/dist/http/app.d.ts.map +1 -1
  29. package/dist/http/app.js +26 -7
  30. package/dist/http/app.js.map +1 -1
  31. package/dist/http/identity.d.ts.map +1 -1
  32. package/dist/http/identity.js +65 -10
  33. package/dist/http/identity.js.map +1 -1
  34. package/dist/http/rest.d.ts.map +1 -1
  35. package/dist/http/rest.js +30 -8
  36. package/dist/http/rest.js.map +1 -1
  37. package/dist/index.d.ts +1 -1
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +1 -1
  40. package/dist/index.js.map +1 -1
  41. package/dist/licensing/refresher.d.ts +1 -1
  42. package/dist/licensing/refresher.d.ts.map +1 -1
  43. package/dist/licensing/refresher.js +5 -2
  44. package/dist/licensing/refresher.js.map +1 -1
  45. package/dist/lifecycle.d.ts +28 -0
  46. package/dist/lifecycle.d.ts.map +1 -0
  47. package/dist/lifecycle.js +115 -0
  48. package/dist/lifecycle.js.map +1 -0
  49. package/dist/mcp/attachmentContent.d.ts +32 -0
  50. package/dist/mcp/attachmentContent.d.ts.map +1 -0
  51. package/dist/mcp/attachmentContent.js +155 -0
  52. package/dist/mcp/attachmentContent.js.map +1 -0
  53. package/dist/mcp/buildServer.d.ts +3 -1
  54. package/dist/mcp/buildServer.d.ts.map +1 -1
  55. package/dist/mcp/buildServer.js +132 -51
  56. package/dist/mcp/buildServer.js.map +1 -1
  57. package/dist/mcp/pdfTextWorker.d.ts +2 -0
  58. package/dist/mcp/pdfTextWorker.d.ts.map +1 -0
  59. package/dist/mcp/pdfTextWorker.js +39 -0
  60. package/dist/mcp/pdfTextWorker.js.map +1 -0
  61. package/dist/permissions.d.ts +19 -0
  62. package/dist/permissions.d.ts.map +1 -1
  63. package/dist/permissions.js +42 -2
  64. package/dist/permissions.js.map +1 -1
  65. package/dist/scheduler/sendScheduler.d.ts +8 -15
  66. package/dist/scheduler/sendScheduler.d.ts.map +1 -1
  67. package/dist/scheduler/sendScheduler.js +96 -69
  68. package/dist/scheduler/sendScheduler.js.map +1 -1
  69. package/dist/service/deliveryCoordinator.d.ts +1 -1
  70. package/dist/service/deliveryCoordinator.d.ts.map +1 -1
  71. package/dist/service/deliveryCoordinator.js +3 -1
  72. package/dist/service/deliveryCoordinator.js.map +1 -1
  73. package/dist/service/emailService.d.ts +2 -1
  74. package/dist/service/emailService.d.ts.map +1 -1
  75. package/dist/service/emailService.js +8 -3
  76. package/dist/service/emailService.js.map +1 -1
  77. package/dist/service/publicErrors.d.ts +4 -0
  78. package/dist/service/publicErrors.d.ts.map +1 -1
  79. package/dist/service/publicErrors.js +10 -1
  80. package/dist/service/publicErrors.js.map +1 -1
  81. package/dist/storage/apiKeys.d.ts.map +1 -1
  82. package/dist/storage/apiKeys.js +2 -2
  83. package/dist/storage/apiKeys.js.map +1 -1
  84. package/dist/storage/db.d.ts +19 -2
  85. package/dist/storage/db.d.ts.map +1 -1
  86. package/dist/storage/db.js +6 -1
  87. package/dist/storage/db.js.map +1 -1
  88. package/dist/storage/scheduledSends.d.ts +6 -1
  89. package/dist/storage/scheduledSends.d.ts.map +1 -1
  90. package/dist/storage/scheduledSends.js +36 -19
  91. package/dist/storage/scheduledSends.js.map +1 -1
  92. package/package.json +11 -9
  93. package/pdf-cmaps/78-EUC-H.bcmap +0 -0
  94. package/pdf-cmaps/78-EUC-V.bcmap +0 -0
  95. package/pdf-cmaps/78-H.bcmap +0 -0
  96. package/pdf-cmaps/78-RKSJ-H.bcmap +0 -0
  97. package/pdf-cmaps/78-RKSJ-V.bcmap +0 -0
  98. package/pdf-cmaps/78-V.bcmap +0 -0
  99. package/pdf-cmaps/78ms-RKSJ-H.bcmap +0 -0
  100. package/pdf-cmaps/78ms-RKSJ-V.bcmap +0 -0
  101. package/pdf-cmaps/83pv-RKSJ-H.bcmap +0 -0
  102. package/pdf-cmaps/90ms-RKSJ-H.bcmap +0 -0
  103. package/pdf-cmaps/90ms-RKSJ-V.bcmap +0 -0
  104. package/pdf-cmaps/90msp-RKSJ-H.bcmap +0 -0
  105. package/pdf-cmaps/90msp-RKSJ-V.bcmap +0 -0
  106. package/pdf-cmaps/90pv-RKSJ-H.bcmap +0 -0
  107. package/pdf-cmaps/90pv-RKSJ-V.bcmap +0 -0
  108. package/pdf-cmaps/Add-H.bcmap +0 -0
  109. package/pdf-cmaps/Add-RKSJ-H.bcmap +0 -0
  110. package/pdf-cmaps/Add-RKSJ-V.bcmap +0 -0
  111. package/pdf-cmaps/Add-V.bcmap +0 -0
  112. package/pdf-cmaps/Adobe-CNS1-0.bcmap +0 -0
  113. package/pdf-cmaps/Adobe-CNS1-1.bcmap +0 -0
  114. package/pdf-cmaps/Adobe-CNS1-2.bcmap +0 -0
  115. package/pdf-cmaps/Adobe-CNS1-3.bcmap +0 -0
  116. package/pdf-cmaps/Adobe-CNS1-4.bcmap +0 -0
  117. package/pdf-cmaps/Adobe-CNS1-5.bcmap +0 -0
  118. package/pdf-cmaps/Adobe-CNS1-6.bcmap +0 -0
  119. package/pdf-cmaps/Adobe-CNS1-UCS2.bcmap +0 -0
  120. package/pdf-cmaps/Adobe-GB1-0.bcmap +0 -0
  121. package/pdf-cmaps/Adobe-GB1-1.bcmap +0 -0
  122. package/pdf-cmaps/Adobe-GB1-2.bcmap +0 -0
  123. package/pdf-cmaps/Adobe-GB1-3.bcmap +0 -0
  124. package/pdf-cmaps/Adobe-GB1-4.bcmap +0 -0
  125. package/pdf-cmaps/Adobe-GB1-5.bcmap +0 -0
  126. package/pdf-cmaps/Adobe-GB1-UCS2.bcmap +0 -0
  127. package/pdf-cmaps/Adobe-Japan1-0.bcmap +0 -0
  128. package/pdf-cmaps/Adobe-Japan1-1.bcmap +0 -0
  129. package/pdf-cmaps/Adobe-Japan1-2.bcmap +0 -0
  130. package/pdf-cmaps/Adobe-Japan1-3.bcmap +0 -0
  131. package/pdf-cmaps/Adobe-Japan1-4.bcmap +0 -0
  132. package/pdf-cmaps/Adobe-Japan1-5.bcmap +0 -0
  133. package/pdf-cmaps/Adobe-Japan1-6.bcmap +0 -0
  134. package/pdf-cmaps/Adobe-Japan1-UCS2.bcmap +0 -0
  135. package/pdf-cmaps/Adobe-Korea1-0.bcmap +0 -0
  136. package/pdf-cmaps/Adobe-Korea1-1.bcmap +0 -0
  137. package/pdf-cmaps/Adobe-Korea1-2.bcmap +0 -0
  138. package/pdf-cmaps/Adobe-Korea1-UCS2.bcmap +0 -0
  139. package/pdf-cmaps/B5-H.bcmap +0 -0
  140. package/pdf-cmaps/B5-V.bcmap +0 -0
  141. package/pdf-cmaps/B5pc-H.bcmap +0 -0
  142. package/pdf-cmaps/B5pc-V.bcmap +0 -0
  143. package/pdf-cmaps/CNS-EUC-H.bcmap +0 -0
  144. package/pdf-cmaps/CNS-EUC-V.bcmap +0 -0
  145. package/pdf-cmaps/CNS1-H.bcmap +0 -0
  146. package/pdf-cmaps/CNS1-V.bcmap +0 -0
  147. package/pdf-cmaps/CNS2-H.bcmap +0 -0
  148. package/pdf-cmaps/CNS2-V.bcmap +3 -0
  149. package/pdf-cmaps/ETHK-B5-H.bcmap +0 -0
  150. package/pdf-cmaps/ETHK-B5-V.bcmap +0 -0
  151. package/pdf-cmaps/ETen-B5-H.bcmap +0 -0
  152. package/pdf-cmaps/ETen-B5-V.bcmap +0 -0
  153. package/pdf-cmaps/ETenms-B5-H.bcmap +3 -0
  154. package/pdf-cmaps/ETenms-B5-V.bcmap +0 -0
  155. package/pdf-cmaps/EUC-H.bcmap +0 -0
  156. package/pdf-cmaps/EUC-V.bcmap +0 -0
  157. package/pdf-cmaps/Ext-H.bcmap +0 -0
  158. package/pdf-cmaps/Ext-RKSJ-H.bcmap +0 -0
  159. package/pdf-cmaps/Ext-RKSJ-V.bcmap +0 -0
  160. package/pdf-cmaps/Ext-V.bcmap +0 -0
  161. package/pdf-cmaps/GB-EUC-H.bcmap +0 -0
  162. package/pdf-cmaps/GB-EUC-V.bcmap +0 -0
  163. package/pdf-cmaps/GB-H.bcmap +4 -0
  164. package/pdf-cmaps/GB-V.bcmap +0 -0
  165. package/pdf-cmaps/GBK-EUC-H.bcmap +0 -0
  166. package/pdf-cmaps/GBK-EUC-V.bcmap +0 -0
  167. package/pdf-cmaps/GBK2K-H.bcmap +0 -0
  168. package/pdf-cmaps/GBK2K-V.bcmap +0 -0
  169. package/pdf-cmaps/GBKp-EUC-H.bcmap +0 -0
  170. package/pdf-cmaps/GBKp-EUC-V.bcmap +0 -0
  171. package/pdf-cmaps/GBT-EUC-H.bcmap +0 -0
  172. package/pdf-cmaps/GBT-EUC-V.bcmap +0 -0
  173. package/pdf-cmaps/GBT-H.bcmap +0 -0
  174. package/pdf-cmaps/GBT-V.bcmap +0 -0
  175. package/pdf-cmaps/GBTpc-EUC-H.bcmap +0 -0
  176. package/pdf-cmaps/GBTpc-EUC-V.bcmap +0 -0
  177. package/pdf-cmaps/GBpc-EUC-H.bcmap +0 -0
  178. package/pdf-cmaps/GBpc-EUC-V.bcmap +0 -0
  179. package/pdf-cmaps/H.bcmap +0 -0
  180. package/pdf-cmaps/HKdla-B5-H.bcmap +0 -0
  181. package/pdf-cmaps/HKdla-B5-V.bcmap +0 -0
  182. package/pdf-cmaps/HKdlb-B5-H.bcmap +0 -0
  183. package/pdf-cmaps/HKdlb-B5-V.bcmap +0 -0
  184. package/pdf-cmaps/HKgccs-B5-H.bcmap +0 -0
  185. package/pdf-cmaps/HKgccs-B5-V.bcmap +0 -0
  186. package/pdf-cmaps/HKm314-B5-H.bcmap +0 -0
  187. package/pdf-cmaps/HKm314-B5-V.bcmap +0 -0
  188. package/pdf-cmaps/HKm471-B5-H.bcmap +0 -0
  189. package/pdf-cmaps/HKm471-B5-V.bcmap +0 -0
  190. package/pdf-cmaps/HKscs-B5-H.bcmap +0 -0
  191. package/pdf-cmaps/HKscs-B5-V.bcmap +0 -0
  192. package/pdf-cmaps/Hankaku.bcmap +0 -0
  193. package/pdf-cmaps/Hiragana.bcmap +0 -0
  194. package/pdf-cmaps/KSC-EUC-H.bcmap +0 -0
  195. package/pdf-cmaps/KSC-EUC-V.bcmap +0 -0
  196. package/pdf-cmaps/KSC-H.bcmap +0 -0
  197. package/pdf-cmaps/KSC-Johab-H.bcmap +0 -0
  198. package/pdf-cmaps/KSC-Johab-V.bcmap +0 -0
  199. package/pdf-cmaps/KSC-V.bcmap +0 -0
  200. package/pdf-cmaps/KSCms-UHC-H.bcmap +0 -0
  201. package/pdf-cmaps/KSCms-UHC-HW-H.bcmap +0 -0
  202. package/pdf-cmaps/KSCms-UHC-HW-V.bcmap +0 -0
  203. package/pdf-cmaps/KSCms-UHC-V.bcmap +0 -0
  204. package/pdf-cmaps/KSCpc-EUC-H.bcmap +0 -0
  205. package/pdf-cmaps/KSCpc-EUC-V.bcmap +0 -0
  206. package/pdf-cmaps/Katakana.bcmap +0 -0
  207. package/pdf-cmaps/LICENSE +36 -0
  208. package/pdf-cmaps/NWP-H.bcmap +0 -0
  209. package/pdf-cmaps/NWP-V.bcmap +0 -0
  210. package/pdf-cmaps/RKSJ-H.bcmap +0 -0
  211. package/pdf-cmaps/RKSJ-V.bcmap +0 -0
  212. package/pdf-cmaps/Roman.bcmap +0 -0
  213. package/pdf-cmaps/UniCNS-UCS2-H.bcmap +0 -0
  214. package/pdf-cmaps/UniCNS-UCS2-V.bcmap +0 -0
  215. package/pdf-cmaps/UniCNS-UTF16-H.bcmap +0 -0
  216. package/pdf-cmaps/UniCNS-UTF16-V.bcmap +0 -0
  217. package/pdf-cmaps/UniCNS-UTF32-H.bcmap +0 -0
  218. package/pdf-cmaps/UniCNS-UTF32-V.bcmap +0 -0
  219. package/pdf-cmaps/UniCNS-UTF8-H.bcmap +0 -0
  220. package/pdf-cmaps/UniCNS-UTF8-V.bcmap +0 -0
  221. package/pdf-cmaps/UniGB-UCS2-H.bcmap +0 -0
  222. package/pdf-cmaps/UniGB-UCS2-V.bcmap +0 -0
  223. package/pdf-cmaps/UniGB-UTF16-H.bcmap +0 -0
  224. package/pdf-cmaps/UniGB-UTF16-V.bcmap +0 -0
  225. package/pdf-cmaps/UniGB-UTF32-H.bcmap +0 -0
  226. package/pdf-cmaps/UniGB-UTF32-V.bcmap +0 -0
  227. package/pdf-cmaps/UniGB-UTF8-H.bcmap +0 -0
  228. package/pdf-cmaps/UniGB-UTF8-V.bcmap +0 -0
  229. package/pdf-cmaps/UniJIS-UCS2-H.bcmap +0 -0
  230. package/pdf-cmaps/UniJIS-UCS2-HW-H.bcmap +0 -0
  231. package/pdf-cmaps/UniJIS-UCS2-HW-V.bcmap +0 -0
  232. package/pdf-cmaps/UniJIS-UCS2-V.bcmap +0 -0
  233. package/pdf-cmaps/UniJIS-UTF16-H.bcmap +0 -0
  234. package/pdf-cmaps/UniJIS-UTF16-V.bcmap +0 -0
  235. package/pdf-cmaps/UniJIS-UTF32-H.bcmap +0 -0
  236. package/pdf-cmaps/UniJIS-UTF32-V.bcmap +0 -0
  237. package/pdf-cmaps/UniJIS-UTF8-H.bcmap +0 -0
  238. package/pdf-cmaps/UniJIS-UTF8-V.bcmap +0 -0
  239. package/pdf-cmaps/UniJIS2004-UTF16-H.bcmap +0 -0
  240. package/pdf-cmaps/UniJIS2004-UTF16-V.bcmap +0 -0
  241. package/pdf-cmaps/UniJIS2004-UTF32-H.bcmap +0 -0
  242. package/pdf-cmaps/UniJIS2004-UTF32-V.bcmap +0 -0
  243. package/pdf-cmaps/UniJIS2004-UTF8-H.bcmap +0 -0
  244. package/pdf-cmaps/UniJIS2004-UTF8-V.bcmap +0 -0
  245. package/pdf-cmaps/UniJISPro-UCS2-HW-V.bcmap +0 -0
  246. package/pdf-cmaps/UniJISPro-UCS2-V.bcmap +0 -0
  247. package/pdf-cmaps/UniJISPro-UTF8-V.bcmap +0 -0
  248. package/pdf-cmaps/UniJISX0213-UTF32-H.bcmap +0 -0
  249. package/pdf-cmaps/UniJISX0213-UTF32-V.bcmap +0 -0
  250. package/pdf-cmaps/UniJISX02132004-UTF32-H.bcmap +0 -0
  251. package/pdf-cmaps/UniJISX02132004-UTF32-V.bcmap +0 -0
  252. package/pdf-cmaps/UniKS-UCS2-H.bcmap +0 -0
  253. package/pdf-cmaps/UniKS-UCS2-V.bcmap +0 -0
  254. package/pdf-cmaps/UniKS-UTF16-H.bcmap +0 -0
  255. package/pdf-cmaps/UniKS-UTF16-V.bcmap +0 -0
  256. package/pdf-cmaps/UniKS-UTF32-H.bcmap +0 -0
  257. package/pdf-cmaps/UniKS-UTF32-V.bcmap +0 -0
  258. package/pdf-cmaps/UniKS-UTF8-H.bcmap +0 -0
  259. package/pdf-cmaps/UniKS-UTF8-V.bcmap +0 -0
  260. package/pdf-cmaps/V.bcmap +0 -0
  261. package/pdf-cmaps/WP-Symbol.bcmap +0 -0
package/dist/cli.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { existsSync, readFileSync, realpathSync } from 'node:fs';
3
+ import { randomUUID } from 'node:crypto';
3
4
  import { emitKeypressEvents } from 'node:readline';
4
5
  import { createInterface } from 'node:readline/promises';
5
6
  import { hostname } from 'node:os';
@@ -10,12 +11,14 @@ import { serve } from '@hono/node-server';
10
11
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
11
12
  import { EmailError, isEmailError } from '@fluxmail/core';
12
13
  import { createContext } from './context.js';
14
+ import { RuntimeLifecycle } from './lifecycle.js';
13
15
  import { configTomlPath, createConfigurationService, deploymentToml, readLoggingSettings, resolveDataDir, resolveStoreLocation, writeDeploymentConfig, } from './config.js';
14
16
  import { migrateConfigurationFile, parseEnvContent, recognizedMigrationSettings } from './configMigration.js';
15
17
  import { createApp } from './http/app.js';
16
18
  import { buildMcpServer } from './mcp/buildServer.js';
17
19
  import { runLoopbackFlow } from './accounts/googleAuth.js';
18
20
  import { runMicrosoftLoopbackFlow } from './accounts/microsoftAuth.js';
21
+ import { IMAP_CONNECTION_TEST_TIMEOUT_MS } from './accounts/registry.js';
19
22
  import { assertHostedConnectionReady, prepareHostedGmailConnection, prepareHostedOutlookConnection, selectGmailConnectionMode, validateAccountConnectionFlags, } from './accounts/gmailConnection.js';
20
23
  import { listApiKeys } from './storage/apiKeys.js';
21
24
  import { findMember } from './storage/members.js';
@@ -23,16 +26,17 @@ import { LICENSE_KEY_PATTERN } from './licensing/client.js';
23
26
  import { checkLicenseState, getEntitlements } from './licensing/entitlements.js';
24
27
  import { VERSION } from './version.js';
25
28
  import { inspectStoreCompatibility, MIN_SUPPORTED_STORE_FORMAT, openDb, openReadonlyDb, } from './storage/db.js';
26
- import { customPermissionPolicy, ADMIN_CAPABILITIES, FULL_PERMISSION_POLICY, isNamedPermissionProfile, MCP_CAPABILITIES, NAMED_PERMISSION_PROFILES, permissionPolicyForProfile, } from './permissions.js';
29
+ import { customPermissionPolicy, ADMIN_CAPABILITIES, DEFAULT_PERMISSION_POLICY, DEFAULT_PERMISSION_PROFILE, isNamedPermissionProfile, MCP_CAPABILITIES, NAMED_PERMISSION_PROFILES, permissionPolicyForProfile, } from './permissions.js';
27
30
  import { captureOperation, getTelemetry, installTelemetryStreamEndHandler, isTelemetryEnabled, setTelemetryEnabled, shutdownTelemetry, telemetryDisabledInAnyEnvironment, } from './telemetry.js';
28
31
  import { apiErrorCode, apiRequestId, clearSessionToken, instanceClient, loadInstanceConfig, removeInstance, resolveInstance, resolveStdioInstance, saveLocalInstance, saveRemoteInstance, saveSessionToken, setRequestDeadlineMs, useInstance, } from './cliInstances.js';
29
32
  import { cliExitCode, parseOutputFormat, printOutput } from './cliOutput.js';
33
+ import { CliStartupError, cliStartupDiagnostics } from './cliStartupDiagnostics.js';
30
34
  import { authenticateBearer, isBootstrapComplete, normalizeAndValidatePassword, recoverAdminPassword, setupInitialAdmin, } from './auth.js';
31
35
  import { recordAdminAuditEvent } from './storage/adminAudit.js';
32
36
  import { canManageOwnedAccount } from './authorization.js';
33
37
  import { createCliUpdateNotifier } from './updateNotifier.js';
34
38
  import { registerMailCommands } from './cliMail.js';
35
- import { accountInventoryProperties, configuredOAuthAppKind, connectionProperties, instanceUsageProperties, } from './accounts/telemetry.js';
39
+ import { accountInventoryProperties, connectionFailureKind, configuredOAuthAppKind, connectionProperties, instanceUsageProperties, } from './accounts/telemetry.js';
36
40
  import { flushLogging, escapeLogTextForConsole, getLogger, logCodedFailure, logFailure, readLocalLogs, shutdownLogging, } from './logging.js';
37
41
  function collectOption(value, previous) {
38
42
  return [...previous, value];
@@ -43,25 +47,24 @@ function accountIdsFromRefs(ctx, refs) {
43
47
  export function permissionPolicyFromOptions(opts, requireSelection = false) {
44
48
  try {
45
49
  const supplemental = opts.admin ?? [];
46
- if (opts.profile && opts.allow.length) {
47
- throw new Error('--profile cannot be combined with --allow.');
48
- }
49
- if (opts.allow.length && supplemental.length) {
50
+ if (!opts.profile && opts.allow.length && supplemental.length) {
50
51
  throw new Error('--admin can only be combined with a named --profile. Put admin capabilities in --allow for a custom policy.');
51
52
  }
52
53
  if (opts.profile) {
53
54
  if (!isNamedPermissionProfile(opts.profile)) {
54
55
  throw new Error(`Unknown profile "${opts.profile}". Expected one of: ${NAMED_PERMISSION_PROFILES.join(', ')}.`);
55
56
  }
56
- return permissionPolicyForProfile(opts.profile, supplemental);
57
+ const named = permissionPolicyForProfile(opts.profile, supplemental);
58
+ // --allow adds capabilities to the profile, which makes the result a custom policy.
59
+ return opts.allow.length ? customPermissionPolicy([...named.capabilities, ...opts.allow]) : named;
57
60
  }
58
61
  if (opts.allow.length)
59
62
  return customPermissionPolicy(opts.allow);
60
63
  if (supplemental.length)
61
- return permissionPolicyForProfile('full', supplemental);
64
+ return permissionPolicyForProfile(DEFAULT_PERMISSION_PROFILE, supplemental);
62
65
  if (requireSelection)
63
66
  throw new Error('Choose --profile or at least one --allow capability.');
64
- return FULL_PERMISSION_POLICY;
67
+ return DEFAULT_PERMISSION_POLICY;
65
68
  }
66
69
  catch (error) {
67
70
  throw new EmailError('invalid_request', error instanceof Error ? error.message : String(error));
@@ -85,7 +88,12 @@ function connectionPort(value, option) {
85
88
  }
86
89
  return port;
87
90
  }
88
- function connectionSecurity(value, option) {
91
+ // Without an explicit mode, follow the port's standard: implicit TLS on 993 and
92
+ // 465, STARTTLS elsewhere. A mismatched mode fails the TLS handshake or waits
93
+ // for a greeting the server never sends.
94
+ function connectionSecurity(value, port, option) {
95
+ if (value === undefined)
96
+ return port === 993 || port === 465 ? 'tls' : 'starttls';
89
97
  if (value !== 'tls' && value !== 'starttls') {
90
98
  throw new EmailError('invalid_request', `${option} must be "tls" or "starttls".`);
91
99
  }
@@ -263,8 +271,43 @@ function finishCliOperation(program, outcome, errorCode, error) {
263
271
  if (!active)
264
272
  return;
265
273
  activeCliOperations.delete(program);
274
+ if (active.operation === 'accounts add' && outcome === 'error') {
275
+ const status = apiHttpStatus(error);
276
+ const connectionStage = apiConnectionStage(error);
277
+ active.properties = {
278
+ ...active.properties,
279
+ failure_kind: connectionFailureKind(errorCode ?? 'internal', status, error instanceof TypeError),
280
+ ...(status === undefined ? {} : { http_status: status }),
281
+ ...(connectionStage ? { connection_stage: connectionStage } : {}),
282
+ };
283
+ }
284
+ const startupProperties = active.operation === 'stdio' && outcome === 'error'
285
+ ? {
286
+ startup_phase: active.properties?.startup_phase,
287
+ startup_data_dir_source: active.properties?.startup_data_dir_source,
288
+ startup_instance_selection: active.properties?.startup_instance_selection,
289
+ ...cliStartupDiagnostics(error),
290
+ }
291
+ : {};
266
292
  if (outcome === 'error') {
267
- const context = { productSurface: 'cli', operation: active.operation, skipConsole: true };
293
+ const connectionDetails = active.operation === 'accounts add'
294
+ ? {
295
+ request_id: apiRequestId(error),
296
+ connection_stage: active.properties?.connection_stage,
297
+ failure_kind: active.properties?.failure_kind,
298
+ http_status: active.properties?.http_status,
299
+ }
300
+ : undefined;
301
+ const details = {
302
+ ...(active.operation === 'stdio' ? startupProperties : {}),
303
+ ...connectionDetails,
304
+ };
305
+ const context = {
306
+ productSurface: 'cli',
307
+ operation: active.operation,
308
+ skipConsole: true,
309
+ ...(Object.values(details).some((value) => value !== undefined) ? { details } : {}),
310
+ };
268
311
  if (error !== undefined)
269
312
  logFailure(active.logger, 'cli.operation_failed', error, context);
270
313
  else
@@ -276,9 +319,24 @@ function finishCliOperation(program, outcome, errorCode, error) {
276
319
  outcome,
277
320
  durationMs: performance.now() - active.startedAt,
278
321
  errorCode,
279
- ...(active.properties ? { properties: active.properties } : {}),
322
+ properties: { ...active.properties, ...startupProperties },
280
323
  });
281
324
  }
325
+ function apiHttpStatus(error) {
326
+ if (!error || typeof error !== 'object' || !('status' in error))
327
+ return undefined;
328
+ const status = error.status;
329
+ return typeof status === 'number' && Number.isInteger(status) && status >= 0 && status <= 599 ? status : undefined;
330
+ }
331
+ function apiConnectionStage(error) {
332
+ if (!error || typeof error !== 'object' || !('connectionStage' in error))
333
+ return undefined;
334
+ const stage = error.connectionStage;
335
+ return typeof stage === 'string' &&
336
+ ['server_validation', 'provider_check', 'account_persist', 'oauth_prepare', 'complete'].includes(stage)
337
+ ? stage
338
+ : undefined;
339
+ }
282
340
  /** End a failed command with the error's own telemetry code instead of a generic one. */
283
341
  function failCliOperation(program, error, prefix = 'Error: ') {
284
342
  const code = apiErrorCode(error);
@@ -446,7 +504,7 @@ export function createCliProgram(options = {}) {
446
504
  const alreadyStructured = ['folders', 'labels', 'emails', 'threads', 'drafts', 'scheduled', 'attachments'].includes(group ?? '') ||
447
505
  command === 'accounts list' ||
448
506
  command === 'status';
449
- const interactive = ['setup', 'login', 'serve', 'stdio'].includes(command) ||
507
+ const interactive = ['setup', 'login', 'serve', 'stdio', 'scheduled run'].includes(command) ||
450
508
  group === 'auth' ||
451
509
  command === 'accounts add' ||
452
510
  command === 'accounts configure' ||
@@ -487,7 +545,9 @@ export function createCliProgram(options = {}) {
487
545
  process.exitCode !== 0;
488
546
  finishCliOperation(program, failed ? 'error' : 'success', failed ? 'command_failed' : undefined);
489
547
  try {
490
- if (actionCommand.name() !== 'serve' && actionCommand.name() !== 'stdio') {
548
+ if (actionCommand.name() !== 'serve' &&
549
+ actionCommand.name() !== 'stdio' &&
550
+ commandPath(actionCommand) !== 'scheduled run') {
491
551
  await Promise.all([options.telemetry ? Promise.resolve() : shutdownTelemetry(), shutdownLogging()]);
492
552
  }
493
553
  }
@@ -717,11 +777,22 @@ export function createCliProgram(options = {}) {
717
777
  .command('serve')
718
778
  .description('Run the HTTP server (MCP at /mcp and REST at /api/v1)')
719
779
  .action(async () => {
720
- installTelemetrySignalHandlers();
721
- const ctx = createContext();
722
- const app = createApp(ctx);
723
- ctx.scheduler.start();
724
- const server = serve({ fetch: app.fetch, port: ctx.config.port }, () => {
780
+ let lifecycle;
781
+ try {
782
+ const ctx = createContext();
783
+ lifecycle = new RuntimeLifecycle(ctx, shutdownTelemetryAndLogging);
784
+ const app = createApp(ctx);
785
+ const runtime = lifecycle;
786
+ const server = serve({
787
+ fetch: (request, env) => runtime.stopping
788
+ ? new Response('Server is shutting down.', { status: 503 })
789
+ : runtime.request(() => app.fetch(request, env)),
790
+ port: ctx.config.port,
791
+ });
792
+ lifecycle.attachServer(server);
793
+ await waitForServerListening(server);
794
+ if (lifecycle.stopping)
795
+ return;
725
796
  ctx.logger.info('server.started', 'Fluxmail HTTP server started', {
726
797
  details: { port: ctx.config.port },
727
798
  skipConsole: true,
@@ -750,33 +821,42 @@ export function createCliProgram(options = {}) {
750
821
  console.log(` Plan: ${planLine(getEntitlements(ctx.db))}`);
751
822
  warnLicense(ctx.db, console.log);
752
823
  ctx.licenseController.start();
753
- });
754
- await waitForServerListening(server);
824
+ ctx.scheduler.start();
825
+ }
826
+ catch (error) {
827
+ finishCliOperation(program, 'error', apiErrorCode(error), error);
828
+ await lifecycle?.close();
829
+ throw error;
830
+ }
755
831
  });
756
832
  program
757
833
  .command('stdio')
758
834
  .description('Run as a stdio MCP server (for Claude Desktop / Claude Code local config)')
759
835
  .option('--account <account>', 'Limit access to one mailbox; repeat as needed', collectOption, [])
760
836
  .option('--profile <profile>', `Tool profile: ${NAMED_PERMISSION_PROFILES.join(', ')}`)
761
- .option('--allow <capability>', 'Allow one MCP capability; repeat as needed', collectOption, [])
837
+ .option('--allow <capability>', 'Allow one MCP capability, or add it to --profile; repeat as needed', collectOption, [])
762
838
  .action(async (opts) => {
763
839
  const startupPhase = (phase) => recordCliOperationProperties(program, { startup_phase: phase });
764
840
  let ctx;
765
841
  let server;
766
842
  try {
843
+ recordCliOperationProperties(program, {
844
+ startup_data_dir_source: process.env.FLUXMAIL_DATA_DIR ? 'environment' : 'default',
845
+ startup_instance_selection: selectedInstance() === undefined ? 'automatic' : 'explicit',
846
+ });
767
847
  startupPhase('permissions');
768
848
  const permissions = permissionPolicyFromOptions(opts);
769
849
  startupPhase('instance');
770
850
  const selected = resolveStdioInstance(selectedInstance());
771
851
  startupPhase('authentication');
772
852
  if (!selected.token)
773
- throw new EmailError('permission_denied', `Log in to the local instance before starting stdio MCP. Run "fluxmail --instance ${selected.name} login".`);
853
+ throw new CliStartupError('permission_denied', `Log in to the local instance before starting stdio MCP. Run "fluxmail --instance ${selected.name} login".`, 'session_missing');
774
854
  startupPhase('context');
775
855
  ctx = createContext();
776
856
  startupPhase('authentication');
777
857
  const principal = authenticateBearer(ctx.db, selected.token);
778
858
  if (!principal || principal.kind !== 'session')
779
- throw new EmailError('permission_denied', `The local CLI session has expired. Run "fluxmail --instance ${selected.name} login".`);
859
+ throw new CliStartupError('permission_denied', `The local CLI session has expired. Run "fluxmail --instance ${selected.name} login".`, 'session_invalid');
780
860
  startupPhase('account_scope');
781
861
  const accountIds = accountIdsFromRefs(ctx, opts.account);
782
862
  const scopedService = ctx.service.withPrincipal({ ...principal, accountIds });
@@ -791,7 +871,6 @@ export function createCliProgram(options = {}) {
791
871
  installTelemetrySignalHandlers();
792
872
  installStdioShutdownHandler(process.stdin);
793
873
  await server.connect(new StdioServerTransport());
794
- ctx.scheduler.start();
795
874
  ctx.telemetry.capture('mcp server started', {
796
875
  ...instanceUsageProperties(ctx.db),
797
876
  product_surface: 'mcp',
@@ -805,10 +884,16 @@ export function createCliProgram(options = {}) {
805
884
  // stdout belongs to the MCP protocol; log to stderr only.
806
885
  ctx.licenseController.start();
807
886
  console.error('Fluxmail MCP server running on stdio');
887
+ if (!opts.profile && !opts.allow.length) {
888
+ console.error('No permission profile selected, so this connection is read-only. ' +
889
+ 'Add --profile or --allow to enable drafts, organizing, or sending: https://fluxmail.ai/docs/permissions');
890
+ }
808
891
  warnLicense(ctx.db);
809
- const pending = scopedService.listScheduled().filter((send) => send.status === 'pending').length;
892
+ const pending = permissions.capabilities.includes('mail.read')
893
+ ? scopedService.listScheduled().filter((send) => send.status === 'pending').length
894
+ : 0;
810
895
  if (pending > 0)
811
- console.error(`Scheduled sends pending: ${pending}`);
896
+ console.error(`Scheduled sends pending: ${pending}. Delivery requires "fluxmail serve" or "fluxmail scheduled run" using the same data directory.`);
812
897
  startupPhase('ready');
813
898
  }
814
899
  catch (error) {
@@ -905,12 +990,12 @@ export function createCliProgram(options = {}) {
905
990
  .option('--display-name <name>', 'Sender name for IMAP messages')
906
991
  .option('--imap-host <host>', 'IMAP server hostname')
907
992
  .option('--imap-port <port>', 'IMAP server port', '993')
908
- .option('--imap-security <mode>', 'IMAP security: tls or starttls', 'tls')
993
+ .option('--imap-security <mode>', 'IMAP security: tls or starttls; defaults to tls on port 993, otherwise starttls')
909
994
  .option('--imap-user <user>', 'IMAP username; defaults to the mailbox address')
910
995
  .option('--imap-password-env <name>', 'Read the IMAP password from this environment variable')
911
996
  .option('--smtp-host <host>', 'SMTP server hostname')
912
997
  .option('--smtp-port <port>', 'SMTP server port', '587')
913
- .option('--smtp-security <mode>', 'SMTP security: tls or starttls', 'starttls')
998
+ .option('--smtp-security <mode>', 'SMTP security: tls or starttls; defaults to tls on port 465, otherwise starttls')
914
999
  .option('--smtp-user <user>', 'SMTP username; defaults to the IMAP username')
915
1000
  .option('--smtp-password-env <name>', 'Read a separate SMTP password from this environment variable')
916
1001
  .option('--sent-folder <path>', 'Sent mailbox path')
@@ -936,7 +1021,12 @@ export function createCliProgram(options = {}) {
936
1021
  : selected.profile.kind === 'remote' && provider === 'imap'
937
1022
  ? undefined
938
1023
  : false;
939
- recordCliOperationProperties(program, connectionProperties({ provider, reauthorize }));
1024
+ recordCliOperationProperties(program, connectionProperties({
1025
+ provider,
1026
+ reauthorize,
1027
+ stage: 'cli_validation',
1028
+ ...(provider === 'imap' ? { flow: 'imap_credentials' } : {}),
1029
+ }));
940
1030
  if (!selected.token) {
941
1031
  failCliOperation(program, new EmailError('permission_denied', `Log in to instance "${selected.name}" before connecting a mailbox.`));
942
1032
  return;
@@ -1002,21 +1092,23 @@ export function createCliProgram(options = {}) {
1002
1092
  ? await accountSecret(opts.smtpPasswordEnv, 'SMTP password')
1003
1093
  : imapPassword;
1004
1094
  const imapUser = opts.imapUser ?? email;
1095
+ const imapPort = connectionPort(opts.imapPort, '--imap-port');
1096
+ const smtpPort = connectionPort(opts.smtpPort, '--smtp-port');
1005
1097
  request = {
1006
1098
  ...request,
1007
1099
  email,
1008
1100
  ...(opts.displayName ? { displayName: opts.displayName } : {}),
1009
1101
  imap: {
1010
1102
  host: opts.imapHost,
1011
- port: connectionPort(opts.imapPort, '--imap-port'),
1012
- security: connectionSecurity(opts.imapSecurity, '--imap-security'),
1103
+ port: imapPort,
1104
+ security: connectionSecurity(opts.imapSecurity, imapPort, '--imap-security'),
1013
1105
  user: imapUser,
1014
1106
  password: imapPassword,
1015
1107
  },
1016
1108
  smtp: {
1017
1109
  host: opts.smtpHost,
1018
- port: connectionPort(opts.smtpPort, '--smtp-port'),
1019
- security: connectionSecurity(opts.smtpSecurity, '--smtp-security'),
1110
+ port: smtpPort,
1111
+ security: connectionSecurity(opts.smtpSecurity, smtpPort, '--smtp-security'),
1020
1112
  user: opts.smtpUser ?? imapUser,
1021
1113
  password: smtpPassword,
1022
1114
  },
@@ -1030,10 +1122,45 @@ export function createCliProgram(options = {}) {
1030
1122
  },
1031
1123
  };
1032
1124
  }
1033
- const result = await instanceClient(selected.name).json('/api/v1/accounts/connections', {
1034
- method: 'POST',
1035
- headers: { 'content-type': 'application/json' },
1036
- body: JSON.stringify(request),
1125
+ // The server's IMAP connection check has its own 30-second limit. Give
1126
+ // its response a small margin so the CLI reports the server's result.
1127
+ const requestTimeoutMs = provider === 'imap' && program.getOptionValueSource('timeout') === 'default'
1128
+ ? IMAP_CONNECTION_TEST_TIMEOUT_MS + 5_000
1129
+ : undefined;
1130
+ const requestTimeoutClass = program.getOptionValueSource('timeout') !== 'default'
1131
+ ? 'custom'
1132
+ : requestTimeoutMs
1133
+ ? 'imap_margin'
1134
+ : 'default';
1135
+ const requestId = randomUUID();
1136
+ recordCliOperationProperties(program, {
1137
+ connection_stage: 'connection_request',
1138
+ request_timeout_class: requestTimeoutClass,
1139
+ });
1140
+ const requestStartedAt = performance.now();
1141
+ let result;
1142
+ try {
1143
+ result = await instanceClient(selected.name, true, requestTimeoutMs).json('/api/v1/accounts/connections', {
1144
+ method: 'POST',
1145
+ headers: { 'content-type': 'application/json', 'x-request-id': requestId },
1146
+ body: JSON.stringify(request),
1147
+ });
1148
+ }
1149
+ catch (error) {
1150
+ const status = apiHttpStatus(error);
1151
+ recordCliOperationProperties(program, {
1152
+ connection_request_duration_ms: performance.now() - requestStartedAt,
1153
+ ...(status === undefined ? { http_status: 0 } : { http_status: status }),
1154
+ });
1155
+ throw error;
1156
+ }
1157
+ recordCliOperationProperties(program, {
1158
+ connection_request_duration_ms: performance.now() - requestStartedAt,
1159
+ ...(result.connectionUrl
1160
+ ? { connection_stage: 'oauth_prepare' }
1161
+ : result.account
1162
+ ? { connection_stage: 'complete' }
1163
+ : {}),
1037
1164
  });
1038
1165
  if (result.connectionUrl)
1039
1166
  console.log(`Open this URL in your browser:\n\n ${result.connectionUrl}\n`);
@@ -1091,6 +1218,7 @@ export function createCliProgram(options = {}) {
1091
1218
  if (provider === 'outlook') {
1092
1219
  const connectionMode = selectGmailConnectionMode(ctx.config, opts);
1093
1220
  if (connectionMode === 'hosted') {
1221
+ recordCliOperationProperties(program, { connection_stage: 'oauth_prepare' });
1094
1222
  const { connectionUrl } = prepareHostedOutlookConnection(ctx.db, ctx.config, {
1095
1223
  ownerMemberId: owner.id,
1096
1224
  ...(existing ? { reauthorizeAccountId: existing.id } : {}),
@@ -1102,6 +1230,7 @@ export function createCliProgram(options = {}) {
1102
1230
  console.log('This link expires in 10 minutes and can only be used once.');
1103
1231
  return;
1104
1232
  }
1233
+ recordCliOperationProperties(program, { connection_stage: 'oauth_prepare' });
1105
1234
  const account = await runMicrosoftLoopbackFlow(ctx.config, (url, context) => printLoopbackInstructions('outlook', url, context, ctx.config.oauthPort), (result) => {
1106
1235
  const duplicate = ctx.registry
1107
1236
  .listAccounts()
@@ -1113,10 +1242,12 @@ export function createCliProgram(options = {}) {
1113
1242
  throw new EmailError('invalid_request', `Microsoft authorized ${result.email}, but account ${existing.id} belongs to ${existing.email}. ` +
1114
1243
  'Try again and choose the matching Microsoft account.');
1115
1244
  }
1245
+ recordCliOperationProperties(program, { connection_stage: 'account_persist' });
1116
1246
  return ctx.registry.addOutlookAccount(result.email, result.credentials, result.displayName, owner.id, existing?.id, access);
1117
1247
  }, loopbackFlowOptions(program, 'outlook'));
1118
1248
  auditConnection('account.connection.complete', account.id);
1119
1249
  recordCliOperationProperties(program, {
1250
+ connection_stage: 'complete',
1120
1251
  reauthorize: accountIdsBeforeConnection.has(account.id),
1121
1252
  ...accountInventoryProperties(ctx.registry),
1122
1253
  });
@@ -1126,6 +1257,7 @@ export function createCliProgram(options = {}) {
1126
1257
  }
1127
1258
  const gmailConnectionMode = selectGmailConnectionMode(ctx.config, opts);
1128
1259
  if (gmailConnectionMode === 'hosted') {
1260
+ recordCliOperationProperties(program, { connection_stage: 'oauth_prepare' });
1129
1261
  const { connectionUrl } = prepareHostedGmailConnection(ctx.db, ctx.config, {
1130
1262
  ownerMemberId: owner.id,
1131
1263
  ...(existing ? { reauthorizeAccountId: existing.id } : {}),
@@ -1137,6 +1269,7 @@ export function createCliProgram(options = {}) {
1137
1269
  console.log('This link expires in 10 minutes and can only be used once.');
1138
1270
  return;
1139
1271
  }
1272
+ recordCliOperationProperties(program, { connection_stage: 'oauth_prepare' });
1140
1273
  const account = await runLoopbackFlow(ctx.config, (url, context) => printLoopbackInstructions('gmail', url, context, ctx.config.oauthPort), (result) => {
1141
1274
  const duplicate = ctx.registry
1142
1275
  .listAccounts()
@@ -1148,10 +1281,12 @@ export function createCliProgram(options = {}) {
1148
1281
  throw new EmailError('invalid_request', `Google authorized ${result.email}, but account ${existing.id} belongs to ${existing.email}. ` +
1149
1282
  'Try again and choose the matching Google account.');
1150
1283
  }
1284
+ recordCliOperationProperties(program, { connection_stage: 'account_persist' });
1151
1285
  return ctx.registry.addGmailAccount(result.email, result.tokens, result.displayName, owner.id, access, result.oauthClient);
1152
1286
  }, loopbackFlowOptions(program, 'gmail'));
1153
1287
  auditConnection('account.connection.complete', account.id);
1154
1288
  recordCliOperationProperties(program, {
1289
+ connection_stage: 'complete',
1155
1290
  reauthorize: accountIdsBeforeConnection.has(account.id),
1156
1291
  ...accountInventoryProperties(ctx.registry),
1157
1292
  });
@@ -1448,7 +1583,7 @@ export function createCliProgram(options = {}) {
1448
1583
  .option('--member <member>', 'Admin only: issue the key to another member')
1449
1584
  .option('--account <account>', 'Limit the key to one mailbox; repeat as needed', collectOption, [])
1450
1585
  .option('--profile <profile>', `Tool profile: ${NAMED_PERMISSION_PROFILES.join(', ')}`)
1451
- .option('--allow <capability>', 'Allow one capability in a custom policy; repeat as needed', collectOption, [])
1586
+ .option('--allow <capability>', 'Allow one capability in a custom policy, or add it to --profile; repeat as needed', collectOption, [])
1452
1587
  .option('--admin <capability>', 'Add one admin capability to a named profile; repeat as needed', collectOption, [])
1453
1588
  .description('Create an API key (shown once)')
1454
1589
  .action(async (opts) => {
@@ -1543,7 +1678,7 @@ export function createCliProgram(options = {}) {
1543
1678
  .command('permissions')
1544
1679
  .argument('<keyId>')
1545
1680
  .option('--profile <profile>', `Tool profile: ${NAMED_PERMISSION_PROFILES.join(', ')}`)
1546
- .option('--allow <capability>', 'Allow one capability in a custom policy; repeat as needed', collectOption, [])
1681
+ .option('--allow <capability>', 'Allow one capability in a custom policy, or add it to --profile; repeat as needed', collectOption, [])
1547
1682
  .option('--admin <capability>', 'Add one admin capability to a named profile; repeat as needed', collectOption, [])
1548
1683
  .description('Change the permissions for an API key')
1549
1684
  .action(async (keyId, opts) => {
@@ -1872,6 +2007,37 @@ export function createCliProgram(options = {}) {
1872
2007
  process.exitCode = cliExitCode(code);
1873
2008
  },
1874
2009
  });
2010
+ program.commands
2011
+ .find((command) => command.name() === 'scheduled')
2012
+ .command('run')
2013
+ .description('Run scheduled delivery for this local instance without HTTP or MCP')
2014
+ .action(async () => {
2015
+ let lifecycle;
2016
+ try {
2017
+ if (selectedAccount() !== undefined) {
2018
+ throw new EmailError('invalid_request', 'scheduled run processes the whole instance; omit --mail-account.');
2019
+ }
2020
+ const name = selectedInstance();
2021
+ if (name !== undefined) {
2022
+ const profile = loadInstanceConfig().instances[name];
2023
+ if (!profile)
2024
+ throw new EmailError('not_found', `No CLI instance named "${name}".`);
2025
+ if (profile.kind !== 'local') {
2026
+ throw new EmailError('invalid_request', 'scheduled run requires a local instance. Run it on the host that stores the schedules.');
2027
+ }
2028
+ }
2029
+ const ctx = createContext();
2030
+ lifecycle = new RuntimeLifecycle(ctx, shutdownTelemetryAndLogging);
2031
+ ctx.licenseController.start();
2032
+ ctx.scheduler.start();
2033
+ console.log('Fluxmail scheduled delivery running. Press Ctrl-C to stop.');
2034
+ }
2035
+ catch (error) {
2036
+ finishCliOperation(program, 'error', apiErrorCode(error), error);
2037
+ await lifecycle?.close();
2038
+ throw error;
2039
+ }
2040
+ });
1875
2041
  program
1876
2042
  .command('status')
1877
2043
  .description('Show mailbox and provider status for the selected instance')