fluxmail 0.13.0 → 0.15.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 (241) hide show
  1. package/README.md +1 -1
  2. package/dist/accounts/googleAuth.d.ts.map +1 -1
  3. package/dist/accounts/googleAuth.js +7 -0
  4. package/dist/accounts/googleAuth.js.map +1 -1
  5. package/dist/accounts/microsoftAuth.d.ts.map +1 -1
  6. package/dist/accounts/microsoftAuth.js +4 -1
  7. package/dist/accounts/microsoftAuth.js.map +1 -1
  8. package/dist/accounts/registry.d.ts +1 -0
  9. package/dist/accounts/registry.d.ts.map +1 -1
  10. package/dist/accounts/registry.js +3 -2
  11. package/dist/accounts/registry.js.map +1 -1
  12. package/dist/accounts/telemetry.d.ts +6 -1
  13. package/dist/accounts/telemetry.d.ts.map +1 -1
  14. package/dist/accounts/telemetry.js +17 -0
  15. package/dist/accounts/telemetry.js.map +1 -1
  16. package/dist/cli.d.ts.map +1 -1
  17. package/dist/cli.js +137 -31
  18. package/dist/cli.js.map +1 -1
  19. package/dist/cliInstances.d.ts +16 -4
  20. package/dist/cliInstances.d.ts.map +1 -1
  21. package/dist/cliInstances.js +98 -15
  22. package/dist/cliInstances.js.map +1 -1
  23. package/dist/cliMail.d.ts +3 -1
  24. package/dist/cliMail.d.ts.map +1 -1
  25. package/dist/cliMail.js +8 -8
  26. package/dist/cliMail.js.map +1 -1
  27. package/dist/http/app.d.ts.map +1 -1
  28. package/dist/http/app.js +16 -1
  29. package/dist/http/app.js.map +1 -1
  30. package/dist/http/identity.d.ts.map +1 -1
  31. package/dist/http/identity.js +65 -10
  32. package/dist/http/identity.js.map +1 -1
  33. package/dist/http/rest.d.ts +1 -1
  34. package/dist/http/rest.d.ts.map +1 -1
  35. package/dist/http/rest.js +42 -9
  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/mcp/attachmentContent.d.ts +32 -0
  42. package/dist/mcp/attachmentContent.d.ts.map +1 -0
  43. package/dist/mcp/attachmentContent.js +155 -0
  44. package/dist/mcp/attachmentContent.js.map +1 -0
  45. package/dist/mcp/buildServer.d.ts +4 -2
  46. package/dist/mcp/buildServer.d.ts.map +1 -1
  47. package/dist/mcp/buildServer.js +138 -50
  48. package/dist/mcp/buildServer.js.map +1 -1
  49. package/dist/mcp/pdfTextWorker.d.ts +2 -0
  50. package/dist/mcp/pdfTextWorker.d.ts.map +1 -0
  51. package/dist/mcp/pdfTextWorker.js +39 -0
  52. package/dist/mcp/pdfTextWorker.js.map +1 -0
  53. package/dist/permissions.d.ts +19 -0
  54. package/dist/permissions.d.ts.map +1 -1
  55. package/dist/permissions.js +42 -2
  56. package/dist/permissions.js.map +1 -1
  57. package/dist/service/emailService.d.ts +3 -0
  58. package/dist/service/emailService.d.ts.map +1 -1
  59. package/dist/service/emailService.js +24 -9
  60. package/dist/service/emailService.js.map +1 -1
  61. package/dist/service/publicErrors.d.ts +4 -0
  62. package/dist/service/publicErrors.d.ts.map +1 -1
  63. package/dist/service/publicErrors.js +10 -1
  64. package/dist/service/publicErrors.js.map +1 -1
  65. package/dist/storage/apiKeys.d.ts.map +1 -1
  66. package/dist/storage/apiKeys.js +2 -2
  67. package/dist/storage/apiKeys.js.map +1 -1
  68. package/dist/telemetry.d.ts +7 -0
  69. package/dist/telemetry.d.ts.map +1 -1
  70. package/dist/telemetry.js +28 -0
  71. package/dist/telemetry.js.map +1 -1
  72. package/package.json +9 -7
  73. package/pdf-cmaps/78-EUC-H.bcmap +0 -0
  74. package/pdf-cmaps/78-EUC-V.bcmap +0 -0
  75. package/pdf-cmaps/78-H.bcmap +0 -0
  76. package/pdf-cmaps/78-RKSJ-H.bcmap +0 -0
  77. package/pdf-cmaps/78-RKSJ-V.bcmap +0 -0
  78. package/pdf-cmaps/78-V.bcmap +0 -0
  79. package/pdf-cmaps/78ms-RKSJ-H.bcmap +0 -0
  80. package/pdf-cmaps/78ms-RKSJ-V.bcmap +0 -0
  81. package/pdf-cmaps/83pv-RKSJ-H.bcmap +0 -0
  82. package/pdf-cmaps/90ms-RKSJ-H.bcmap +0 -0
  83. package/pdf-cmaps/90ms-RKSJ-V.bcmap +0 -0
  84. package/pdf-cmaps/90msp-RKSJ-H.bcmap +0 -0
  85. package/pdf-cmaps/90msp-RKSJ-V.bcmap +0 -0
  86. package/pdf-cmaps/90pv-RKSJ-H.bcmap +0 -0
  87. package/pdf-cmaps/90pv-RKSJ-V.bcmap +0 -0
  88. package/pdf-cmaps/Add-H.bcmap +0 -0
  89. package/pdf-cmaps/Add-RKSJ-H.bcmap +0 -0
  90. package/pdf-cmaps/Add-RKSJ-V.bcmap +0 -0
  91. package/pdf-cmaps/Add-V.bcmap +0 -0
  92. package/pdf-cmaps/Adobe-CNS1-0.bcmap +0 -0
  93. package/pdf-cmaps/Adobe-CNS1-1.bcmap +0 -0
  94. package/pdf-cmaps/Adobe-CNS1-2.bcmap +0 -0
  95. package/pdf-cmaps/Adobe-CNS1-3.bcmap +0 -0
  96. package/pdf-cmaps/Adobe-CNS1-4.bcmap +0 -0
  97. package/pdf-cmaps/Adobe-CNS1-5.bcmap +0 -0
  98. package/pdf-cmaps/Adobe-CNS1-6.bcmap +0 -0
  99. package/pdf-cmaps/Adobe-CNS1-UCS2.bcmap +0 -0
  100. package/pdf-cmaps/Adobe-GB1-0.bcmap +0 -0
  101. package/pdf-cmaps/Adobe-GB1-1.bcmap +0 -0
  102. package/pdf-cmaps/Adobe-GB1-2.bcmap +0 -0
  103. package/pdf-cmaps/Adobe-GB1-3.bcmap +0 -0
  104. package/pdf-cmaps/Adobe-GB1-4.bcmap +0 -0
  105. package/pdf-cmaps/Adobe-GB1-5.bcmap +0 -0
  106. package/pdf-cmaps/Adobe-GB1-UCS2.bcmap +0 -0
  107. package/pdf-cmaps/Adobe-Japan1-0.bcmap +0 -0
  108. package/pdf-cmaps/Adobe-Japan1-1.bcmap +0 -0
  109. package/pdf-cmaps/Adobe-Japan1-2.bcmap +0 -0
  110. package/pdf-cmaps/Adobe-Japan1-3.bcmap +0 -0
  111. package/pdf-cmaps/Adobe-Japan1-4.bcmap +0 -0
  112. package/pdf-cmaps/Adobe-Japan1-5.bcmap +0 -0
  113. package/pdf-cmaps/Adobe-Japan1-6.bcmap +0 -0
  114. package/pdf-cmaps/Adobe-Japan1-UCS2.bcmap +0 -0
  115. package/pdf-cmaps/Adobe-Korea1-0.bcmap +0 -0
  116. package/pdf-cmaps/Adobe-Korea1-1.bcmap +0 -0
  117. package/pdf-cmaps/Adobe-Korea1-2.bcmap +0 -0
  118. package/pdf-cmaps/Adobe-Korea1-UCS2.bcmap +0 -0
  119. package/pdf-cmaps/B5-H.bcmap +0 -0
  120. package/pdf-cmaps/B5-V.bcmap +0 -0
  121. package/pdf-cmaps/B5pc-H.bcmap +0 -0
  122. package/pdf-cmaps/B5pc-V.bcmap +0 -0
  123. package/pdf-cmaps/CNS-EUC-H.bcmap +0 -0
  124. package/pdf-cmaps/CNS-EUC-V.bcmap +0 -0
  125. package/pdf-cmaps/CNS1-H.bcmap +0 -0
  126. package/pdf-cmaps/CNS1-V.bcmap +0 -0
  127. package/pdf-cmaps/CNS2-H.bcmap +0 -0
  128. package/pdf-cmaps/CNS2-V.bcmap +3 -0
  129. package/pdf-cmaps/ETHK-B5-H.bcmap +0 -0
  130. package/pdf-cmaps/ETHK-B5-V.bcmap +0 -0
  131. package/pdf-cmaps/ETen-B5-H.bcmap +0 -0
  132. package/pdf-cmaps/ETen-B5-V.bcmap +0 -0
  133. package/pdf-cmaps/ETenms-B5-H.bcmap +3 -0
  134. package/pdf-cmaps/ETenms-B5-V.bcmap +0 -0
  135. package/pdf-cmaps/EUC-H.bcmap +0 -0
  136. package/pdf-cmaps/EUC-V.bcmap +0 -0
  137. package/pdf-cmaps/Ext-H.bcmap +0 -0
  138. package/pdf-cmaps/Ext-RKSJ-H.bcmap +0 -0
  139. package/pdf-cmaps/Ext-RKSJ-V.bcmap +0 -0
  140. package/pdf-cmaps/Ext-V.bcmap +0 -0
  141. package/pdf-cmaps/GB-EUC-H.bcmap +0 -0
  142. package/pdf-cmaps/GB-EUC-V.bcmap +0 -0
  143. package/pdf-cmaps/GB-H.bcmap +4 -0
  144. package/pdf-cmaps/GB-V.bcmap +0 -0
  145. package/pdf-cmaps/GBK-EUC-H.bcmap +0 -0
  146. package/pdf-cmaps/GBK-EUC-V.bcmap +0 -0
  147. package/pdf-cmaps/GBK2K-H.bcmap +0 -0
  148. package/pdf-cmaps/GBK2K-V.bcmap +0 -0
  149. package/pdf-cmaps/GBKp-EUC-H.bcmap +0 -0
  150. package/pdf-cmaps/GBKp-EUC-V.bcmap +0 -0
  151. package/pdf-cmaps/GBT-EUC-H.bcmap +0 -0
  152. package/pdf-cmaps/GBT-EUC-V.bcmap +0 -0
  153. package/pdf-cmaps/GBT-H.bcmap +0 -0
  154. package/pdf-cmaps/GBT-V.bcmap +0 -0
  155. package/pdf-cmaps/GBTpc-EUC-H.bcmap +0 -0
  156. package/pdf-cmaps/GBTpc-EUC-V.bcmap +0 -0
  157. package/pdf-cmaps/GBpc-EUC-H.bcmap +0 -0
  158. package/pdf-cmaps/GBpc-EUC-V.bcmap +0 -0
  159. package/pdf-cmaps/H.bcmap +0 -0
  160. package/pdf-cmaps/HKdla-B5-H.bcmap +0 -0
  161. package/pdf-cmaps/HKdla-B5-V.bcmap +0 -0
  162. package/pdf-cmaps/HKdlb-B5-H.bcmap +0 -0
  163. package/pdf-cmaps/HKdlb-B5-V.bcmap +0 -0
  164. package/pdf-cmaps/HKgccs-B5-H.bcmap +0 -0
  165. package/pdf-cmaps/HKgccs-B5-V.bcmap +0 -0
  166. package/pdf-cmaps/HKm314-B5-H.bcmap +0 -0
  167. package/pdf-cmaps/HKm314-B5-V.bcmap +0 -0
  168. package/pdf-cmaps/HKm471-B5-H.bcmap +0 -0
  169. package/pdf-cmaps/HKm471-B5-V.bcmap +0 -0
  170. package/pdf-cmaps/HKscs-B5-H.bcmap +0 -0
  171. package/pdf-cmaps/HKscs-B5-V.bcmap +0 -0
  172. package/pdf-cmaps/Hankaku.bcmap +0 -0
  173. package/pdf-cmaps/Hiragana.bcmap +0 -0
  174. package/pdf-cmaps/KSC-EUC-H.bcmap +0 -0
  175. package/pdf-cmaps/KSC-EUC-V.bcmap +0 -0
  176. package/pdf-cmaps/KSC-H.bcmap +0 -0
  177. package/pdf-cmaps/KSC-Johab-H.bcmap +0 -0
  178. package/pdf-cmaps/KSC-Johab-V.bcmap +0 -0
  179. package/pdf-cmaps/KSC-V.bcmap +0 -0
  180. package/pdf-cmaps/KSCms-UHC-H.bcmap +0 -0
  181. package/pdf-cmaps/KSCms-UHC-HW-H.bcmap +0 -0
  182. package/pdf-cmaps/KSCms-UHC-HW-V.bcmap +0 -0
  183. package/pdf-cmaps/KSCms-UHC-V.bcmap +0 -0
  184. package/pdf-cmaps/KSCpc-EUC-H.bcmap +0 -0
  185. package/pdf-cmaps/KSCpc-EUC-V.bcmap +0 -0
  186. package/pdf-cmaps/Katakana.bcmap +0 -0
  187. package/pdf-cmaps/LICENSE +36 -0
  188. package/pdf-cmaps/NWP-H.bcmap +0 -0
  189. package/pdf-cmaps/NWP-V.bcmap +0 -0
  190. package/pdf-cmaps/RKSJ-H.bcmap +0 -0
  191. package/pdf-cmaps/RKSJ-V.bcmap +0 -0
  192. package/pdf-cmaps/Roman.bcmap +0 -0
  193. package/pdf-cmaps/UniCNS-UCS2-H.bcmap +0 -0
  194. package/pdf-cmaps/UniCNS-UCS2-V.bcmap +0 -0
  195. package/pdf-cmaps/UniCNS-UTF16-H.bcmap +0 -0
  196. package/pdf-cmaps/UniCNS-UTF16-V.bcmap +0 -0
  197. package/pdf-cmaps/UniCNS-UTF32-H.bcmap +0 -0
  198. package/pdf-cmaps/UniCNS-UTF32-V.bcmap +0 -0
  199. package/pdf-cmaps/UniCNS-UTF8-H.bcmap +0 -0
  200. package/pdf-cmaps/UniCNS-UTF8-V.bcmap +0 -0
  201. package/pdf-cmaps/UniGB-UCS2-H.bcmap +0 -0
  202. package/pdf-cmaps/UniGB-UCS2-V.bcmap +0 -0
  203. package/pdf-cmaps/UniGB-UTF16-H.bcmap +0 -0
  204. package/pdf-cmaps/UniGB-UTF16-V.bcmap +0 -0
  205. package/pdf-cmaps/UniGB-UTF32-H.bcmap +0 -0
  206. package/pdf-cmaps/UniGB-UTF32-V.bcmap +0 -0
  207. package/pdf-cmaps/UniGB-UTF8-H.bcmap +0 -0
  208. package/pdf-cmaps/UniGB-UTF8-V.bcmap +0 -0
  209. package/pdf-cmaps/UniJIS-UCS2-H.bcmap +0 -0
  210. package/pdf-cmaps/UniJIS-UCS2-HW-H.bcmap +0 -0
  211. package/pdf-cmaps/UniJIS-UCS2-HW-V.bcmap +0 -0
  212. package/pdf-cmaps/UniJIS-UCS2-V.bcmap +0 -0
  213. package/pdf-cmaps/UniJIS-UTF16-H.bcmap +0 -0
  214. package/pdf-cmaps/UniJIS-UTF16-V.bcmap +0 -0
  215. package/pdf-cmaps/UniJIS-UTF32-H.bcmap +0 -0
  216. package/pdf-cmaps/UniJIS-UTF32-V.bcmap +0 -0
  217. package/pdf-cmaps/UniJIS-UTF8-H.bcmap +0 -0
  218. package/pdf-cmaps/UniJIS-UTF8-V.bcmap +0 -0
  219. package/pdf-cmaps/UniJIS2004-UTF16-H.bcmap +0 -0
  220. package/pdf-cmaps/UniJIS2004-UTF16-V.bcmap +0 -0
  221. package/pdf-cmaps/UniJIS2004-UTF32-H.bcmap +0 -0
  222. package/pdf-cmaps/UniJIS2004-UTF32-V.bcmap +0 -0
  223. package/pdf-cmaps/UniJIS2004-UTF8-H.bcmap +0 -0
  224. package/pdf-cmaps/UniJIS2004-UTF8-V.bcmap +0 -0
  225. package/pdf-cmaps/UniJISPro-UCS2-HW-V.bcmap +0 -0
  226. package/pdf-cmaps/UniJISPro-UCS2-V.bcmap +0 -0
  227. package/pdf-cmaps/UniJISPro-UTF8-V.bcmap +0 -0
  228. package/pdf-cmaps/UniJISX0213-UTF32-H.bcmap +0 -0
  229. package/pdf-cmaps/UniJISX0213-UTF32-V.bcmap +0 -0
  230. package/pdf-cmaps/UniJISX02132004-UTF32-H.bcmap +0 -0
  231. package/pdf-cmaps/UniJISX02132004-UTF32-V.bcmap +0 -0
  232. package/pdf-cmaps/UniKS-UCS2-H.bcmap +0 -0
  233. package/pdf-cmaps/UniKS-UCS2-V.bcmap +0 -0
  234. package/pdf-cmaps/UniKS-UTF16-H.bcmap +0 -0
  235. package/pdf-cmaps/UniKS-UTF16-V.bcmap +0 -0
  236. package/pdf-cmaps/UniKS-UTF32-H.bcmap +0 -0
  237. package/pdf-cmaps/UniKS-UTF32-V.bcmap +0 -0
  238. package/pdf-cmaps/UniKS-UTF8-H.bcmap +0 -0
  239. package/pdf-cmaps/UniKS-UTF8-V.bcmap +0 -0
  240. package/pdf-cmaps/V.bcmap +0 -0
  241. 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';
@@ -17,6 +18,7 @@ import { createApp } from './http/app.js';
17
18
  import { buildMcpServer } from './mcp/buildServer.js';
18
19
  import { runLoopbackFlow } from './accounts/googleAuth.js';
19
20
  import { runMicrosoftLoopbackFlow } from './accounts/microsoftAuth.js';
21
+ import { IMAP_CONNECTION_TEST_TIMEOUT_MS } from './accounts/registry.js';
20
22
  import { assertHostedConnectionReady, prepareHostedGmailConnection, prepareHostedOutlookConnection, selectGmailConnectionMode, validateAccountConnectionFlags, } from './accounts/gmailConnection.js';
21
23
  import { listApiKeys } from './storage/apiKeys.js';
22
24
  import { findMember } from './storage/members.js';
@@ -24,9 +26,9 @@ import { LICENSE_KEY_PATTERN } from './licensing/client.js';
24
26
  import { checkLicenseState, getEntitlements } from './licensing/entitlements.js';
25
27
  import { VERSION } from './version.js';
26
28
  import { inspectStoreCompatibility, MIN_SUPPORTED_STORE_FORMAT, openDb, openReadonlyDb, } from './storage/db.js';
27
- 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';
28
30
  import { captureOperation, getTelemetry, installTelemetryStreamEndHandler, isTelemetryEnabled, setTelemetryEnabled, shutdownTelemetry, telemetryDisabledInAnyEnvironment, } from './telemetry.js';
29
- import { apiErrorCode, apiRequestId, clearSessionToken, instanceClient, loadInstanceConfig, removeInstance, resolveInstance, resolveStdioInstance, saveLocalInstance, saveRemoteInstance, saveSessionToken, setRequestDeadlineMs, useInstance, } from './cliInstances.js';
31
+ import { apiErrorCode, CliCanceledError, apiRequestId, clearSessionToken, instanceClient, loadInstanceConfig, removeInstance, resolveInstance, resolveStdioInstance, saveLocalInstance, saveRemoteInstance, saveSessionToken, setRequestDeadlineMs, useInstance, } from './cliInstances.js';
30
32
  import { cliExitCode, parseOutputFormat, printOutput } from './cliOutput.js';
31
33
  import { CliStartupError, cliStartupDiagnostics } from './cliStartupDiagnostics.js';
32
34
  import { authenticateBearer, isBootstrapComplete, normalizeAndValidatePassword, recoverAdminPassword, setupInitialAdmin, } from './auth.js';
@@ -34,7 +36,7 @@ import { recordAdminAuditEvent } from './storage/adminAudit.js';
34
36
  import { canManageOwnedAccount } from './authorization.js';
35
37
  import { createCliUpdateNotifier } from './updateNotifier.js';
36
38
  import { registerMailCommands } from './cliMail.js';
37
- import { accountInventoryProperties, configuredOAuthAppKind, connectionProperties, instanceUsageProperties, } from './accounts/telemetry.js';
39
+ import { accountInventoryProperties, connectionFailureKind, configuredOAuthAppKind, connectionProperties, instanceUsageProperties, } from './accounts/telemetry.js';
38
40
  import { flushLogging, escapeLogTextForConsole, getLogger, logCodedFailure, logFailure, readLocalLogs, shutdownLogging, } from './logging.js';
39
41
  function collectOption(value, previous) {
40
42
  return [...previous, value];
@@ -45,25 +47,24 @@ function accountIdsFromRefs(ctx, refs) {
45
47
  export function permissionPolicyFromOptions(opts, requireSelection = false) {
46
48
  try {
47
49
  const supplemental = opts.admin ?? [];
48
- if (opts.profile && opts.allow.length) {
49
- throw new Error('--profile cannot be combined with --allow.');
50
- }
51
- if (opts.allow.length && supplemental.length) {
50
+ if (!opts.profile && opts.allow.length && supplemental.length) {
52
51
  throw new Error('--admin can only be combined with a named --profile. Put admin capabilities in --allow for a custom policy.');
53
52
  }
54
53
  if (opts.profile) {
55
54
  if (!isNamedPermissionProfile(opts.profile)) {
56
55
  throw new Error(`Unknown profile "${opts.profile}". Expected one of: ${NAMED_PERMISSION_PROFILES.join(', ')}.`);
57
56
  }
58
- 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;
59
60
  }
60
61
  if (opts.allow.length)
61
62
  return customPermissionPolicy(opts.allow);
62
63
  if (supplemental.length)
63
- return permissionPolicyForProfile('full', supplemental);
64
+ return permissionPolicyForProfile(DEFAULT_PERMISSION_PROFILE, supplemental);
64
65
  if (requireSelection)
65
66
  throw new Error('Choose --profile or at least one --allow capability.');
66
- return FULL_PERMISSION_POLICY;
67
+ return DEFAULT_PERMISSION_POLICY;
67
68
  }
68
69
  catch (error) {
69
70
  throw new EmailError('invalid_request', error instanceof Error ? error.message : String(error));
@@ -87,7 +88,12 @@ function connectionPort(value, option) {
87
88
  }
88
89
  return port;
89
90
  }
90
- 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';
91
97
  if (value !== 'tls' && value !== 'starttls') {
92
98
  throw new EmailError('invalid_request', `${option} must be "tls" or "starttls".`);
93
99
  }
@@ -117,8 +123,8 @@ async function hiddenPrompt(label) {
117
123
  resolve(value);
118
124
  };
119
125
  const onKeypress = (input, key) => {
120
- if (key.ctrl && key.name === 'c')
121
- return finish(new Error('Canceled'));
126
+ if (key.ctrl && (key.name === 'c' || key.name === 'd'))
127
+ return finish(new CliCanceledError());
122
128
  if (key.name === 'return' || key.name === 'enter')
123
129
  return finish();
124
130
  if (key.name === 'backspace') {
@@ -143,6 +149,12 @@ async function textPrompt(label) {
143
149
  try {
144
150
  return (await prompt.question(`${label}: `)).trim();
145
151
  }
152
+ catch (error) {
153
+ // readline rejects a pending question with AbortError on Ctrl+C or Ctrl+D.
154
+ if (error instanceof Error && error.name === 'AbortError')
155
+ throw new CliCanceledError();
156
+ throw error;
157
+ }
146
158
  finally {
147
159
  prompt.close();
148
160
  }
@@ -260,11 +272,23 @@ async function inspectLocalRegistry(inspect) {
260
272
  }
261
273
  }
262
274
  }
263
- function finishCliOperation(program, outcome, errorCode, error) {
275
+ function finishCliOperation(program, outcome, errorCode, error,
276
+ /** Provider failure detail for a partial failure, which has no error. */
277
+ failure) {
264
278
  const active = activeCliOperations.get(program);
265
279
  if (!active)
266
280
  return;
267
281
  activeCliOperations.delete(program);
282
+ if (active.operation === 'accounts add' && outcome === 'error') {
283
+ const status = apiHttpStatus(error);
284
+ const connectionStage = apiConnectionStage(error);
285
+ active.properties = {
286
+ ...active.properties,
287
+ failure_kind: connectionFailureKind(errorCode ?? 'internal', status, error instanceof TypeError),
288
+ ...(status === undefined ? {} : { http_status: status }),
289
+ ...(connectionStage ? { connection_stage: connectionStage } : {}),
290
+ };
291
+ }
268
292
  const startupProperties = active.operation === 'stdio' && outcome === 'error'
269
293
  ? {
270
294
  startup_phase: active.properties?.startup_phase,
@@ -274,11 +298,23 @@ function finishCliOperation(program, outcome, errorCode, error) {
274
298
  }
275
299
  : {};
276
300
  if (outcome === 'error') {
301
+ const connectionDetails = active.operation === 'accounts add'
302
+ ? {
303
+ request_id: apiRequestId(error),
304
+ connection_stage: active.properties?.connection_stage,
305
+ failure_kind: active.properties?.failure_kind,
306
+ http_status: active.properties?.http_status,
307
+ }
308
+ : undefined;
309
+ const details = {
310
+ ...(active.operation === 'stdio' ? startupProperties : {}),
311
+ ...connectionDetails,
312
+ };
277
313
  const context = {
278
314
  productSurface: 'cli',
279
315
  operation: active.operation,
280
316
  skipConsole: true,
281
- ...(active.operation === 'stdio' ? { details: startupProperties } : {}),
317
+ ...(Object.values(details).some((value) => value !== undefined) ? { details } : {}),
282
318
  };
283
319
  if (error !== undefined)
284
320
  logFailure(active.logger, 'cli.operation_failed', error, context);
@@ -291,9 +327,25 @@ function finishCliOperation(program, outcome, errorCode, error) {
291
327
  outcome,
292
328
  durationMs: performance.now() - active.startedAt,
293
329
  errorCode,
330
+ error: error ?? (failure ? { failure } : undefined),
294
331
  properties: { ...active.properties, ...startupProperties },
295
332
  });
296
333
  }
334
+ function apiHttpStatus(error) {
335
+ if (!error || typeof error !== 'object' || !('status' in error))
336
+ return undefined;
337
+ const status = error.status;
338
+ return typeof status === 'number' && Number.isInteger(status) && status >= 0 && status <= 599 ? status : undefined;
339
+ }
340
+ function apiConnectionStage(error) {
341
+ if (!error || typeof error !== 'object' || !('connectionStage' in error))
342
+ return undefined;
343
+ const stage = error.connectionStage;
344
+ return typeof stage === 'string' &&
345
+ ['server_validation', 'provider_check', 'account_persist', 'oauth_prepare', 'complete'].includes(stage)
346
+ ? stage
347
+ : undefined;
348
+ }
297
349
  /** End a failed command with the error's own telemetry code instead of a generic one. */
298
350
  function failCliOperation(program, error, prefix = 'Error: ') {
299
351
  const code = apiErrorCode(error);
@@ -791,7 +843,7 @@ export function createCliProgram(options = {}) {
791
843
  .description('Run as a stdio MCP server (for Claude Desktop / Claude Code local config)')
792
844
  .option('--account <account>', 'Limit access to one mailbox; repeat as needed', collectOption, [])
793
845
  .option('--profile <profile>', `Tool profile: ${NAMED_PERMISSION_PROFILES.join(', ')}`)
794
- .option('--allow <capability>', 'Allow one MCP capability; repeat as needed', collectOption, [])
846
+ .option('--allow <capability>', 'Allow one MCP capability, or add it to --profile; repeat as needed', collectOption, [])
795
847
  .action(async (opts) => {
796
848
  const startupPhase = (phase) => recordCliOperationProperties(program, { startup_phase: phase });
797
849
  let ctx;
@@ -841,6 +893,10 @@ export function createCliProgram(options = {}) {
841
893
  // stdout belongs to the MCP protocol; log to stderr only.
842
894
  ctx.licenseController.start();
843
895
  console.error('Fluxmail MCP server running on stdio');
896
+ if (!opts.profile && !opts.allow.length) {
897
+ console.error('No permission profile selected, so this connection is read-only. ' +
898
+ 'Add --profile or --allow to enable drafts, organizing, or sending: https://fluxmail.ai/docs/permissions');
899
+ }
844
900
  warnLicense(ctx.db);
845
901
  const pending = permissions.capabilities.includes('mail.read')
846
902
  ? scopedService.listScheduled().filter((send) => send.status === 'pending').length
@@ -943,12 +999,12 @@ export function createCliProgram(options = {}) {
943
999
  .option('--display-name <name>', 'Sender name for IMAP messages')
944
1000
  .option('--imap-host <host>', 'IMAP server hostname')
945
1001
  .option('--imap-port <port>', 'IMAP server port', '993')
946
- .option('--imap-security <mode>', 'IMAP security: tls or starttls', 'tls')
1002
+ .option('--imap-security <mode>', 'IMAP security: tls or starttls; defaults to tls on port 993, otherwise starttls')
947
1003
  .option('--imap-user <user>', 'IMAP username; defaults to the mailbox address')
948
1004
  .option('--imap-password-env <name>', 'Read the IMAP password from this environment variable')
949
1005
  .option('--smtp-host <host>', 'SMTP server hostname')
950
1006
  .option('--smtp-port <port>', 'SMTP server port', '587')
951
- .option('--smtp-security <mode>', 'SMTP security: tls or starttls', 'starttls')
1007
+ .option('--smtp-security <mode>', 'SMTP security: tls or starttls; defaults to tls on port 465, otherwise starttls')
952
1008
  .option('--smtp-user <user>', 'SMTP username; defaults to the IMAP username')
953
1009
  .option('--smtp-password-env <name>', 'Read a separate SMTP password from this environment variable')
954
1010
  .option('--sent-folder <path>', 'Sent mailbox path')
@@ -974,7 +1030,12 @@ export function createCliProgram(options = {}) {
974
1030
  : selected.profile.kind === 'remote' && provider === 'imap'
975
1031
  ? undefined
976
1032
  : false;
977
- recordCliOperationProperties(program, connectionProperties({ provider, reauthorize }));
1033
+ recordCliOperationProperties(program, connectionProperties({
1034
+ provider,
1035
+ reauthorize,
1036
+ stage: 'cli_validation',
1037
+ ...(provider === 'imap' ? { flow: 'imap_credentials' } : {}),
1038
+ }));
978
1039
  if (!selected.token) {
979
1040
  failCliOperation(program, new EmailError('permission_denied', `Log in to instance "${selected.name}" before connecting a mailbox.`));
980
1041
  return;
@@ -1040,21 +1101,23 @@ export function createCliProgram(options = {}) {
1040
1101
  ? await accountSecret(opts.smtpPasswordEnv, 'SMTP password')
1041
1102
  : imapPassword;
1042
1103
  const imapUser = opts.imapUser ?? email;
1104
+ const imapPort = connectionPort(opts.imapPort, '--imap-port');
1105
+ const smtpPort = connectionPort(opts.smtpPort, '--smtp-port');
1043
1106
  request = {
1044
1107
  ...request,
1045
1108
  email,
1046
1109
  ...(opts.displayName ? { displayName: opts.displayName } : {}),
1047
1110
  imap: {
1048
1111
  host: opts.imapHost,
1049
- port: connectionPort(opts.imapPort, '--imap-port'),
1050
- security: connectionSecurity(opts.imapSecurity, '--imap-security'),
1112
+ port: imapPort,
1113
+ security: connectionSecurity(opts.imapSecurity, imapPort, '--imap-security'),
1051
1114
  user: imapUser,
1052
1115
  password: imapPassword,
1053
1116
  },
1054
1117
  smtp: {
1055
1118
  host: opts.smtpHost,
1056
- port: connectionPort(opts.smtpPort, '--smtp-port'),
1057
- security: connectionSecurity(opts.smtpSecurity, '--smtp-security'),
1119
+ port: smtpPort,
1120
+ security: connectionSecurity(opts.smtpSecurity, smtpPort, '--smtp-security'),
1058
1121
  user: opts.smtpUser ?? imapUser,
1059
1122
  password: smtpPassword,
1060
1123
  },
@@ -1068,10 +1131,45 @@ export function createCliProgram(options = {}) {
1068
1131
  },
1069
1132
  };
1070
1133
  }
1071
- const result = await instanceClient(selected.name).json('/api/v1/accounts/connections', {
1072
- method: 'POST',
1073
- headers: { 'content-type': 'application/json' },
1074
- body: JSON.stringify(request),
1134
+ // The server's IMAP connection check has its own 30-second limit. Give
1135
+ // its response a small margin so the CLI reports the server's result.
1136
+ const requestTimeoutMs = provider === 'imap' && program.getOptionValueSource('timeout') === 'default'
1137
+ ? IMAP_CONNECTION_TEST_TIMEOUT_MS + 5_000
1138
+ : undefined;
1139
+ const requestTimeoutClass = program.getOptionValueSource('timeout') !== 'default'
1140
+ ? 'custom'
1141
+ : requestTimeoutMs
1142
+ ? 'imap_margin'
1143
+ : 'default';
1144
+ const requestId = randomUUID();
1145
+ recordCliOperationProperties(program, {
1146
+ connection_stage: 'connection_request',
1147
+ request_timeout_class: requestTimeoutClass,
1148
+ });
1149
+ const requestStartedAt = performance.now();
1150
+ let result;
1151
+ try {
1152
+ result = await instanceClient(selected.name, true, requestTimeoutMs).json('/api/v1/accounts/connections', {
1153
+ method: 'POST',
1154
+ headers: { 'content-type': 'application/json', 'x-request-id': requestId },
1155
+ body: JSON.stringify(request),
1156
+ });
1157
+ }
1158
+ catch (error) {
1159
+ const status = apiHttpStatus(error);
1160
+ recordCliOperationProperties(program, {
1161
+ connection_request_duration_ms: performance.now() - requestStartedAt,
1162
+ ...(status === undefined ? { http_status: 0 } : { http_status: status }),
1163
+ });
1164
+ throw error;
1165
+ }
1166
+ recordCliOperationProperties(program, {
1167
+ connection_request_duration_ms: performance.now() - requestStartedAt,
1168
+ ...(result.connectionUrl
1169
+ ? { connection_stage: 'oauth_prepare' }
1170
+ : result.account
1171
+ ? { connection_stage: 'complete' }
1172
+ : {}),
1075
1173
  });
1076
1174
  if (result.connectionUrl)
1077
1175
  console.log(`Open this URL in your browser:\n\n ${result.connectionUrl}\n`);
@@ -1129,6 +1227,7 @@ export function createCliProgram(options = {}) {
1129
1227
  if (provider === 'outlook') {
1130
1228
  const connectionMode = selectGmailConnectionMode(ctx.config, opts);
1131
1229
  if (connectionMode === 'hosted') {
1230
+ recordCliOperationProperties(program, { connection_stage: 'oauth_prepare' });
1132
1231
  const { connectionUrl } = prepareHostedOutlookConnection(ctx.db, ctx.config, {
1133
1232
  ownerMemberId: owner.id,
1134
1233
  ...(existing ? { reauthorizeAccountId: existing.id } : {}),
@@ -1140,6 +1239,7 @@ export function createCliProgram(options = {}) {
1140
1239
  console.log('This link expires in 10 minutes and can only be used once.');
1141
1240
  return;
1142
1241
  }
1242
+ recordCliOperationProperties(program, { connection_stage: 'oauth_prepare' });
1143
1243
  const account = await runMicrosoftLoopbackFlow(ctx.config, (url, context) => printLoopbackInstructions('outlook', url, context, ctx.config.oauthPort), (result) => {
1144
1244
  const duplicate = ctx.registry
1145
1245
  .listAccounts()
@@ -1151,10 +1251,12 @@ export function createCliProgram(options = {}) {
1151
1251
  throw new EmailError('invalid_request', `Microsoft authorized ${result.email}, but account ${existing.id} belongs to ${existing.email}. ` +
1152
1252
  'Try again and choose the matching Microsoft account.');
1153
1253
  }
1254
+ recordCliOperationProperties(program, { connection_stage: 'account_persist' });
1154
1255
  return ctx.registry.addOutlookAccount(result.email, result.credentials, result.displayName, owner.id, existing?.id, access);
1155
1256
  }, loopbackFlowOptions(program, 'outlook'));
1156
1257
  auditConnection('account.connection.complete', account.id);
1157
1258
  recordCliOperationProperties(program, {
1259
+ connection_stage: 'complete',
1158
1260
  reauthorize: accountIdsBeforeConnection.has(account.id),
1159
1261
  ...accountInventoryProperties(ctx.registry),
1160
1262
  });
@@ -1164,6 +1266,7 @@ export function createCliProgram(options = {}) {
1164
1266
  }
1165
1267
  const gmailConnectionMode = selectGmailConnectionMode(ctx.config, opts);
1166
1268
  if (gmailConnectionMode === 'hosted') {
1269
+ recordCliOperationProperties(program, { connection_stage: 'oauth_prepare' });
1167
1270
  const { connectionUrl } = prepareHostedGmailConnection(ctx.db, ctx.config, {
1168
1271
  ownerMemberId: owner.id,
1169
1272
  ...(existing ? { reauthorizeAccountId: existing.id } : {}),
@@ -1175,6 +1278,7 @@ export function createCliProgram(options = {}) {
1175
1278
  console.log('This link expires in 10 minutes and can only be used once.');
1176
1279
  return;
1177
1280
  }
1281
+ recordCliOperationProperties(program, { connection_stage: 'oauth_prepare' });
1178
1282
  const account = await runLoopbackFlow(ctx.config, (url, context) => printLoopbackInstructions('gmail', url, context, ctx.config.oauthPort), (result) => {
1179
1283
  const duplicate = ctx.registry
1180
1284
  .listAccounts()
@@ -1186,10 +1290,12 @@ export function createCliProgram(options = {}) {
1186
1290
  throw new EmailError('invalid_request', `Google authorized ${result.email}, but account ${existing.id} belongs to ${existing.email}. ` +
1187
1291
  'Try again and choose the matching Google account.');
1188
1292
  }
1293
+ recordCliOperationProperties(program, { connection_stage: 'account_persist' });
1189
1294
  return ctx.registry.addGmailAccount(result.email, result.tokens, result.displayName, owner.id, access, result.oauthClient);
1190
1295
  }, loopbackFlowOptions(program, 'gmail'));
1191
1296
  auditConnection('account.connection.complete', account.id);
1192
1297
  recordCliOperationProperties(program, {
1298
+ connection_stage: 'complete',
1193
1299
  reauthorize: accountIdsBeforeConnection.has(account.id),
1194
1300
  ...accountInventoryProperties(ctx.registry),
1195
1301
  });
@@ -1486,7 +1592,7 @@ export function createCliProgram(options = {}) {
1486
1592
  .option('--member <member>', 'Admin only: issue the key to another member')
1487
1593
  .option('--account <account>', 'Limit the key to one mailbox; repeat as needed', collectOption, [])
1488
1594
  .option('--profile <profile>', `Tool profile: ${NAMED_PERMISSION_PROFILES.join(', ')}`)
1489
- .option('--allow <capability>', 'Allow one capability in a custom policy; repeat as needed', collectOption, [])
1595
+ .option('--allow <capability>', 'Allow one capability in a custom policy, or add it to --profile; repeat as needed', collectOption, [])
1490
1596
  .option('--admin <capability>', 'Add one admin capability to a named profile; repeat as needed', collectOption, [])
1491
1597
  .description('Create an API key (shown once)')
1492
1598
  .action(async (opts) => {
@@ -1581,7 +1687,7 @@ export function createCliProgram(options = {}) {
1581
1687
  .command('permissions')
1582
1688
  .argument('<keyId>')
1583
1689
  .option('--profile <profile>', `Tool profile: ${NAMED_PERMISSION_PROFILES.join(', ')}`)
1584
- .option('--allow <capability>', 'Allow one capability in a custom policy; repeat as needed', collectOption, [])
1690
+ .option('--allow <capability>', 'Allow one capability in a custom policy, or add it to --profile; repeat as needed', collectOption, [])
1585
1691
  .option('--admin <capability>', 'Add one admin capability to a named profile; repeat as needed', collectOption, [])
1586
1692
  .description('Change the permissions for an API key')
1587
1693
  .action(async (keyId, opts) => {
@@ -1905,8 +2011,8 @@ export function createCliProgram(options = {}) {
1905
2011
  }));
1906
2012
  process.exitCode = cliExitCode(code);
1907
2013
  },
1908
- reportPartialFailure: (code) => {
1909
- finishCliOperation(program, 'error', code);
2014
+ reportPartialFailure: (code, failure) => {
2015
+ finishCliOperation(program, 'error', code, undefined, failure);
1910
2016
  process.exitCode = cliExitCode(code);
1911
2017
  },
1912
2018
  });