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.
- package/README.md +1 -1
- package/dist/accounts/registry.d.ts +1 -0
- package/dist/accounts/registry.d.ts.map +1 -1
- package/dist/accounts/registry.js +7 -3
- 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 +206 -40
- package/dist/cli.js.map +1 -1
- package/dist/cliInstances.d.ts +6 -4
- package/dist/cliInstances.d.ts.map +1 -1
- package/dist/cliInstances.js +38 -17
- package/dist/cliInstances.js.map +1 -1
- package/dist/cliMail.d.ts.map +1 -1
- package/dist/cliMail.js +1 -1
- package/dist/cliMail.js.map +1 -1
- package/dist/cliStartupDiagnostics.d.ts +12 -0
- package/dist/cliStartupDiagnostics.d.ts.map +1 -0
- package/dist/cliStartupDiagnostics.js +47 -0
- package/dist/cliStartupDiagnostics.js.map +1 -0
- package/dist/context.d.ts +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +15 -16
- package/dist/context.js.map +1 -1
- package/dist/http/app.d.ts.map +1 -1
- package/dist/http/app.js +26 -7
- 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.map +1 -1
- package/dist/http/rest.js +30 -8
- 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/licensing/refresher.d.ts +1 -1
- package/dist/licensing/refresher.d.ts.map +1 -1
- package/dist/licensing/refresher.js +5 -2
- package/dist/licensing/refresher.js.map +1 -1
- package/dist/lifecycle.d.ts +28 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +115 -0
- package/dist/lifecycle.js.map +1 -0
- 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 +3 -1
- package/dist/mcp/buildServer.d.ts.map +1 -1
- package/dist/mcp/buildServer.js +132 -51
- 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/scheduler/sendScheduler.d.ts +8 -15
- package/dist/scheduler/sendScheduler.d.ts.map +1 -1
- package/dist/scheduler/sendScheduler.js +96 -69
- package/dist/scheduler/sendScheduler.js.map +1 -1
- package/dist/service/deliveryCoordinator.d.ts +1 -1
- package/dist/service/deliveryCoordinator.d.ts.map +1 -1
- package/dist/service/deliveryCoordinator.js +3 -1
- package/dist/service/deliveryCoordinator.js.map +1 -1
- package/dist/service/emailService.d.ts +2 -1
- package/dist/service/emailService.d.ts.map +1 -1
- package/dist/service/emailService.js +8 -3
- 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/storage/db.d.ts +19 -2
- package/dist/storage/db.d.ts.map +1 -1
- package/dist/storage/db.js +6 -1
- package/dist/storage/db.js.map +1 -1
- package/dist/storage/scheduledSends.d.ts +6 -1
- package/dist/storage/scheduledSends.d.ts.map +1 -1
- package/dist/storage/scheduledSends.js +36 -19
- package/dist/storage/scheduledSends.js.map +1 -1
- package/package.json +11 -9
- 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';
|
|
@@ -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,
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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' &&
|
|
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
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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({
|
|
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:
|
|
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:
|
|
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
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
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')
|