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.
- package/README.md +1 -1
- package/dist/accounts/googleAuth.d.ts.map +1 -1
- package/dist/accounts/googleAuth.js +7 -0
- package/dist/accounts/googleAuth.js.map +1 -1
- package/dist/accounts/microsoftAuth.d.ts.map +1 -1
- package/dist/accounts/microsoftAuth.js +4 -1
- package/dist/accounts/microsoftAuth.js.map +1 -1
- package/dist/accounts/registry.d.ts +1 -0
- package/dist/accounts/registry.d.ts.map +1 -1
- package/dist/accounts/registry.js +3 -2
- package/dist/accounts/registry.js.map +1 -1
- package/dist/accounts/telemetry.d.ts +6 -1
- package/dist/accounts/telemetry.d.ts.map +1 -1
- package/dist/accounts/telemetry.js +17 -0
- package/dist/accounts/telemetry.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +137 -31
- package/dist/cli.js.map +1 -1
- package/dist/cliInstances.d.ts +16 -4
- package/dist/cliInstances.d.ts.map +1 -1
- package/dist/cliInstances.js +98 -15
- package/dist/cliInstances.js.map +1 -1
- package/dist/cliMail.d.ts +3 -1
- package/dist/cliMail.d.ts.map +1 -1
- package/dist/cliMail.js +8 -8
- package/dist/cliMail.js.map +1 -1
- package/dist/http/app.d.ts.map +1 -1
- package/dist/http/app.js +16 -1
- package/dist/http/app.js.map +1 -1
- package/dist/http/identity.d.ts.map +1 -1
- package/dist/http/identity.js +65 -10
- package/dist/http/identity.js.map +1 -1
- package/dist/http/rest.d.ts +1 -1
- package/dist/http/rest.d.ts.map +1 -1
- package/dist/http/rest.js +42 -9
- package/dist/http/rest.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/attachmentContent.d.ts +32 -0
- package/dist/mcp/attachmentContent.d.ts.map +1 -0
- package/dist/mcp/attachmentContent.js +155 -0
- package/dist/mcp/attachmentContent.js.map +1 -0
- package/dist/mcp/buildServer.d.ts +4 -2
- package/dist/mcp/buildServer.d.ts.map +1 -1
- package/dist/mcp/buildServer.js +138 -50
- package/dist/mcp/buildServer.js.map +1 -1
- package/dist/mcp/pdfTextWorker.d.ts +2 -0
- package/dist/mcp/pdfTextWorker.d.ts.map +1 -0
- package/dist/mcp/pdfTextWorker.js +39 -0
- package/dist/mcp/pdfTextWorker.js.map +1 -0
- package/dist/permissions.d.ts +19 -0
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +42 -2
- package/dist/permissions.js.map +1 -1
- package/dist/service/emailService.d.ts +3 -0
- package/dist/service/emailService.d.ts.map +1 -1
- package/dist/service/emailService.js +24 -9
- package/dist/service/emailService.js.map +1 -1
- package/dist/service/publicErrors.d.ts +4 -0
- package/dist/service/publicErrors.d.ts.map +1 -1
- package/dist/service/publicErrors.js +10 -1
- package/dist/service/publicErrors.js.map +1 -1
- package/dist/storage/apiKeys.d.ts.map +1 -1
- package/dist/storage/apiKeys.js +2 -2
- package/dist/storage/apiKeys.js.map +1 -1
- package/dist/telemetry.d.ts +7 -0
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +28 -0
- package/dist/telemetry.js.map +1 -1
- package/package.json +9 -7
- package/pdf-cmaps/78-EUC-H.bcmap +0 -0
- package/pdf-cmaps/78-EUC-V.bcmap +0 -0
- package/pdf-cmaps/78-H.bcmap +0 -0
- package/pdf-cmaps/78-RKSJ-H.bcmap +0 -0
- package/pdf-cmaps/78-RKSJ-V.bcmap +0 -0
- package/pdf-cmaps/78-V.bcmap +0 -0
- package/pdf-cmaps/78ms-RKSJ-H.bcmap +0 -0
- package/pdf-cmaps/78ms-RKSJ-V.bcmap +0 -0
- package/pdf-cmaps/83pv-RKSJ-H.bcmap +0 -0
- package/pdf-cmaps/90ms-RKSJ-H.bcmap +0 -0
- package/pdf-cmaps/90ms-RKSJ-V.bcmap +0 -0
- package/pdf-cmaps/90msp-RKSJ-H.bcmap +0 -0
- package/pdf-cmaps/90msp-RKSJ-V.bcmap +0 -0
- package/pdf-cmaps/90pv-RKSJ-H.bcmap +0 -0
- package/pdf-cmaps/90pv-RKSJ-V.bcmap +0 -0
- package/pdf-cmaps/Add-H.bcmap +0 -0
- package/pdf-cmaps/Add-RKSJ-H.bcmap +0 -0
- package/pdf-cmaps/Add-RKSJ-V.bcmap +0 -0
- package/pdf-cmaps/Add-V.bcmap +0 -0
- package/pdf-cmaps/Adobe-CNS1-0.bcmap +0 -0
- package/pdf-cmaps/Adobe-CNS1-1.bcmap +0 -0
- package/pdf-cmaps/Adobe-CNS1-2.bcmap +0 -0
- package/pdf-cmaps/Adobe-CNS1-3.bcmap +0 -0
- package/pdf-cmaps/Adobe-CNS1-4.bcmap +0 -0
- package/pdf-cmaps/Adobe-CNS1-5.bcmap +0 -0
- package/pdf-cmaps/Adobe-CNS1-6.bcmap +0 -0
- package/pdf-cmaps/Adobe-CNS1-UCS2.bcmap +0 -0
- package/pdf-cmaps/Adobe-GB1-0.bcmap +0 -0
- package/pdf-cmaps/Adobe-GB1-1.bcmap +0 -0
- package/pdf-cmaps/Adobe-GB1-2.bcmap +0 -0
- package/pdf-cmaps/Adobe-GB1-3.bcmap +0 -0
- package/pdf-cmaps/Adobe-GB1-4.bcmap +0 -0
- package/pdf-cmaps/Adobe-GB1-5.bcmap +0 -0
- package/pdf-cmaps/Adobe-GB1-UCS2.bcmap +0 -0
- package/pdf-cmaps/Adobe-Japan1-0.bcmap +0 -0
- package/pdf-cmaps/Adobe-Japan1-1.bcmap +0 -0
- package/pdf-cmaps/Adobe-Japan1-2.bcmap +0 -0
- package/pdf-cmaps/Adobe-Japan1-3.bcmap +0 -0
- package/pdf-cmaps/Adobe-Japan1-4.bcmap +0 -0
- package/pdf-cmaps/Adobe-Japan1-5.bcmap +0 -0
- package/pdf-cmaps/Adobe-Japan1-6.bcmap +0 -0
- package/pdf-cmaps/Adobe-Japan1-UCS2.bcmap +0 -0
- package/pdf-cmaps/Adobe-Korea1-0.bcmap +0 -0
- package/pdf-cmaps/Adobe-Korea1-1.bcmap +0 -0
- package/pdf-cmaps/Adobe-Korea1-2.bcmap +0 -0
- package/pdf-cmaps/Adobe-Korea1-UCS2.bcmap +0 -0
- package/pdf-cmaps/B5-H.bcmap +0 -0
- package/pdf-cmaps/B5-V.bcmap +0 -0
- package/pdf-cmaps/B5pc-H.bcmap +0 -0
- package/pdf-cmaps/B5pc-V.bcmap +0 -0
- package/pdf-cmaps/CNS-EUC-H.bcmap +0 -0
- package/pdf-cmaps/CNS-EUC-V.bcmap +0 -0
- package/pdf-cmaps/CNS1-H.bcmap +0 -0
- package/pdf-cmaps/CNS1-V.bcmap +0 -0
- package/pdf-cmaps/CNS2-H.bcmap +0 -0
- package/pdf-cmaps/CNS2-V.bcmap +3 -0
- package/pdf-cmaps/ETHK-B5-H.bcmap +0 -0
- package/pdf-cmaps/ETHK-B5-V.bcmap +0 -0
- package/pdf-cmaps/ETen-B5-H.bcmap +0 -0
- package/pdf-cmaps/ETen-B5-V.bcmap +0 -0
- package/pdf-cmaps/ETenms-B5-H.bcmap +3 -0
- package/pdf-cmaps/ETenms-B5-V.bcmap +0 -0
- package/pdf-cmaps/EUC-H.bcmap +0 -0
- package/pdf-cmaps/EUC-V.bcmap +0 -0
- package/pdf-cmaps/Ext-H.bcmap +0 -0
- package/pdf-cmaps/Ext-RKSJ-H.bcmap +0 -0
- package/pdf-cmaps/Ext-RKSJ-V.bcmap +0 -0
- package/pdf-cmaps/Ext-V.bcmap +0 -0
- package/pdf-cmaps/GB-EUC-H.bcmap +0 -0
- package/pdf-cmaps/GB-EUC-V.bcmap +0 -0
- package/pdf-cmaps/GB-H.bcmap +4 -0
- package/pdf-cmaps/GB-V.bcmap +0 -0
- package/pdf-cmaps/GBK-EUC-H.bcmap +0 -0
- package/pdf-cmaps/GBK-EUC-V.bcmap +0 -0
- package/pdf-cmaps/GBK2K-H.bcmap +0 -0
- package/pdf-cmaps/GBK2K-V.bcmap +0 -0
- package/pdf-cmaps/GBKp-EUC-H.bcmap +0 -0
- package/pdf-cmaps/GBKp-EUC-V.bcmap +0 -0
- package/pdf-cmaps/GBT-EUC-H.bcmap +0 -0
- package/pdf-cmaps/GBT-EUC-V.bcmap +0 -0
- package/pdf-cmaps/GBT-H.bcmap +0 -0
- package/pdf-cmaps/GBT-V.bcmap +0 -0
- package/pdf-cmaps/GBTpc-EUC-H.bcmap +0 -0
- package/pdf-cmaps/GBTpc-EUC-V.bcmap +0 -0
- package/pdf-cmaps/GBpc-EUC-H.bcmap +0 -0
- package/pdf-cmaps/GBpc-EUC-V.bcmap +0 -0
- package/pdf-cmaps/H.bcmap +0 -0
- package/pdf-cmaps/HKdla-B5-H.bcmap +0 -0
- package/pdf-cmaps/HKdla-B5-V.bcmap +0 -0
- package/pdf-cmaps/HKdlb-B5-H.bcmap +0 -0
- package/pdf-cmaps/HKdlb-B5-V.bcmap +0 -0
- package/pdf-cmaps/HKgccs-B5-H.bcmap +0 -0
- package/pdf-cmaps/HKgccs-B5-V.bcmap +0 -0
- package/pdf-cmaps/HKm314-B5-H.bcmap +0 -0
- package/pdf-cmaps/HKm314-B5-V.bcmap +0 -0
- package/pdf-cmaps/HKm471-B5-H.bcmap +0 -0
- package/pdf-cmaps/HKm471-B5-V.bcmap +0 -0
- package/pdf-cmaps/HKscs-B5-H.bcmap +0 -0
- package/pdf-cmaps/HKscs-B5-V.bcmap +0 -0
- package/pdf-cmaps/Hankaku.bcmap +0 -0
- package/pdf-cmaps/Hiragana.bcmap +0 -0
- package/pdf-cmaps/KSC-EUC-H.bcmap +0 -0
- package/pdf-cmaps/KSC-EUC-V.bcmap +0 -0
- package/pdf-cmaps/KSC-H.bcmap +0 -0
- package/pdf-cmaps/KSC-Johab-H.bcmap +0 -0
- package/pdf-cmaps/KSC-Johab-V.bcmap +0 -0
- package/pdf-cmaps/KSC-V.bcmap +0 -0
- package/pdf-cmaps/KSCms-UHC-H.bcmap +0 -0
- package/pdf-cmaps/KSCms-UHC-HW-H.bcmap +0 -0
- package/pdf-cmaps/KSCms-UHC-HW-V.bcmap +0 -0
- package/pdf-cmaps/KSCms-UHC-V.bcmap +0 -0
- package/pdf-cmaps/KSCpc-EUC-H.bcmap +0 -0
- package/pdf-cmaps/KSCpc-EUC-V.bcmap +0 -0
- package/pdf-cmaps/Katakana.bcmap +0 -0
- package/pdf-cmaps/LICENSE +36 -0
- package/pdf-cmaps/NWP-H.bcmap +0 -0
- package/pdf-cmaps/NWP-V.bcmap +0 -0
- package/pdf-cmaps/RKSJ-H.bcmap +0 -0
- package/pdf-cmaps/RKSJ-V.bcmap +0 -0
- package/pdf-cmaps/Roman.bcmap +0 -0
- package/pdf-cmaps/UniCNS-UCS2-H.bcmap +0 -0
- package/pdf-cmaps/UniCNS-UCS2-V.bcmap +0 -0
- package/pdf-cmaps/UniCNS-UTF16-H.bcmap +0 -0
- package/pdf-cmaps/UniCNS-UTF16-V.bcmap +0 -0
- package/pdf-cmaps/UniCNS-UTF32-H.bcmap +0 -0
- package/pdf-cmaps/UniCNS-UTF32-V.bcmap +0 -0
- package/pdf-cmaps/UniCNS-UTF8-H.bcmap +0 -0
- package/pdf-cmaps/UniCNS-UTF8-V.bcmap +0 -0
- package/pdf-cmaps/UniGB-UCS2-H.bcmap +0 -0
- package/pdf-cmaps/UniGB-UCS2-V.bcmap +0 -0
- package/pdf-cmaps/UniGB-UTF16-H.bcmap +0 -0
- package/pdf-cmaps/UniGB-UTF16-V.bcmap +0 -0
- package/pdf-cmaps/UniGB-UTF32-H.bcmap +0 -0
- package/pdf-cmaps/UniGB-UTF32-V.bcmap +0 -0
- package/pdf-cmaps/UniGB-UTF8-H.bcmap +0 -0
- package/pdf-cmaps/UniGB-UTF8-V.bcmap +0 -0
- package/pdf-cmaps/UniJIS-UCS2-H.bcmap +0 -0
- package/pdf-cmaps/UniJIS-UCS2-HW-H.bcmap +0 -0
- package/pdf-cmaps/UniJIS-UCS2-HW-V.bcmap +0 -0
- package/pdf-cmaps/UniJIS-UCS2-V.bcmap +0 -0
- package/pdf-cmaps/UniJIS-UTF16-H.bcmap +0 -0
- package/pdf-cmaps/UniJIS-UTF16-V.bcmap +0 -0
- package/pdf-cmaps/UniJIS-UTF32-H.bcmap +0 -0
- package/pdf-cmaps/UniJIS-UTF32-V.bcmap +0 -0
- package/pdf-cmaps/UniJIS-UTF8-H.bcmap +0 -0
- package/pdf-cmaps/UniJIS-UTF8-V.bcmap +0 -0
- package/pdf-cmaps/UniJIS2004-UTF16-H.bcmap +0 -0
- package/pdf-cmaps/UniJIS2004-UTF16-V.bcmap +0 -0
- package/pdf-cmaps/UniJIS2004-UTF32-H.bcmap +0 -0
- package/pdf-cmaps/UniJIS2004-UTF32-V.bcmap +0 -0
- package/pdf-cmaps/UniJIS2004-UTF8-H.bcmap +0 -0
- package/pdf-cmaps/UniJIS2004-UTF8-V.bcmap +0 -0
- package/pdf-cmaps/UniJISPro-UCS2-HW-V.bcmap +0 -0
- package/pdf-cmaps/UniJISPro-UCS2-V.bcmap +0 -0
- package/pdf-cmaps/UniJISPro-UTF8-V.bcmap +0 -0
- package/pdf-cmaps/UniJISX0213-UTF32-H.bcmap +0 -0
- package/pdf-cmaps/UniJISX0213-UTF32-V.bcmap +0 -0
- package/pdf-cmaps/UniJISX02132004-UTF32-H.bcmap +0 -0
- package/pdf-cmaps/UniJISX02132004-UTF32-V.bcmap +0 -0
- package/pdf-cmaps/UniKS-UCS2-H.bcmap +0 -0
- package/pdf-cmaps/UniKS-UCS2-V.bcmap +0 -0
- package/pdf-cmaps/UniKS-UTF16-H.bcmap +0 -0
- package/pdf-cmaps/UniKS-UTF16-V.bcmap +0 -0
- package/pdf-cmaps/UniKS-UTF32-H.bcmap +0 -0
- package/pdf-cmaps/UniKS-UTF32-V.bcmap +0 -0
- package/pdf-cmaps/UniKS-UTF8-H.bcmap +0 -0
- package/pdf-cmaps/UniKS-UTF8-V.bcmap +0 -0
- package/pdf-cmaps/V.bcmap +0 -0
- 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,
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
...(
|
|
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
|
|
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
|
|
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({
|
|
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:
|
|
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:
|
|
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
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
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
|
});
|