fluxmail 0.6.1 → 0.8.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 (62) hide show
  1. package/LICENSE.md +66 -84
  2. package/README.md +1 -1
  3. package/dist/accounts/gmailConnection.d.ts +5 -0
  4. package/dist/accounts/gmailConnection.d.ts.map +1 -1
  5. package/dist/accounts/gmailConnection.js +24 -6
  6. package/dist/accounts/gmailConnection.js.map +1 -1
  7. package/dist/accounts/googleAuth.d.ts.map +1 -1
  8. package/dist/accounts/googleAuth.js +6 -1
  9. package/dist/accounts/googleAuth.js.map +1 -1
  10. package/dist/accounts/microsoftAuth.d.ts +1 -0
  11. package/dist/accounts/microsoftAuth.d.ts.map +1 -1
  12. package/dist/accounts/microsoftAuth.js +12 -0
  13. package/dist/accounts/microsoftAuth.js.map +1 -1
  14. package/dist/accounts/registry.d.ts +3 -1
  15. package/dist/accounts/registry.d.ts.map +1 -1
  16. package/dist/accounts/registry.js +17 -5
  17. package/dist/accounts/registry.js.map +1 -1
  18. package/dist/accounts/telemetry.d.ts +27 -0
  19. package/dist/accounts/telemetry.d.ts.map +1 -0
  20. package/dist/accounts/telemetry.js +42 -0
  21. package/dist/accounts/telemetry.js.map +1 -0
  22. package/dist/cli.d.ts.map +1 -1
  23. package/dist/cli.js +141 -125
  24. package/dist/cli.js.map +1 -1
  25. package/dist/cliInstances.d.ts.map +1 -1
  26. package/dist/cliInstances.js +5 -1
  27. package/dist/cliInstances.js.map +1 -1
  28. package/dist/cliMail.d.ts.map +1 -1
  29. package/dist/cliMail.js +33 -16
  30. package/dist/cliMail.js.map +1 -1
  31. package/dist/context.js +1 -1
  32. package/dist/context.js.map +1 -1
  33. package/dist/http/admin.d.ts.map +1 -1
  34. package/dist/http/admin.js +17 -0
  35. package/dist/http/admin.js.map +1 -1
  36. package/dist/http/app.d.ts +1 -1
  37. package/dist/http/app.d.ts.map +1 -1
  38. package/dist/http/app.js +76 -12
  39. package/dist/http/app.js.map +1 -1
  40. package/dist/http/identity.d.ts.map +1 -1
  41. package/dist/http/identity.js +12 -0
  42. package/dist/http/identity.js.map +1 -1
  43. package/dist/http/operationTelemetry.d.ts +14 -0
  44. package/dist/http/operationTelemetry.d.ts.map +1 -0
  45. package/dist/http/operationTelemetry.js +8 -0
  46. package/dist/http/operationTelemetry.js.map +1 -0
  47. package/dist/http/rest.d.ts +2 -1
  48. package/dist/http/rest.d.ts.map +1 -1
  49. package/dist/http/rest.js +100 -12
  50. package/dist/http/rest.js.map +1 -1
  51. package/dist/mcp/buildServer.d.ts.map +1 -1
  52. package/dist/mcp/buildServer.js +33 -12
  53. package/dist/mcp/buildServer.js.map +1 -1
  54. package/dist/service/emailService.d.ts +3 -1
  55. package/dist/service/emailService.d.ts.map +1 -1
  56. package/dist/service/emailService.js +74 -5
  57. package/dist/service/emailService.js.map +1 -1
  58. package/dist/service/searchCursor.d.ts +20 -0
  59. package/dist/service/searchCursor.d.ts.map +1 -0
  60. package/dist/service/searchCursor.js +101 -0
  61. package/dist/service/searchCursor.js.map +1 -0
  62. package/package.json +6 -6
package/dist/cli.js CHANGED
@@ -16,7 +16,7 @@ import { createApp } from './http/app.js';
16
16
  import { buildMcpServer } from './mcp/buildServer.js';
17
17
  import { runLoopbackFlow } from './accounts/googleAuth.js';
18
18
  import { runMicrosoftLoopbackFlow } from './accounts/microsoftAuth.js';
19
- import { prepareHostedGmailConnection, prepareHostedOutlookConnection, selectGmailConnectionMode, validateAccountConnectionFlags, } from './accounts/gmailConnection.js';
19
+ import { assertHostedConnectionReady, prepareHostedGmailConnection, prepareHostedOutlookConnection, selectGmailConnectionMode, validateAccountConnectionFlags, } from './accounts/gmailConnection.js';
20
20
  import { listApiKeys } from './storage/apiKeys.js';
21
21
  import { findMember } from './storage/members.js';
22
22
  import { LICENSE_KEY_PATTERN } from './licensing/client.js';
@@ -25,12 +25,13 @@ import { VERSION } from './version.js';
25
25
  import { inspectStoreCompatibility, MIN_SUPPORTED_STORE_FORMAT, openDb, openReadonlyDb, } from './storage/db.js';
26
26
  import { customPermissionPolicy, ADMIN_CAPABILITIES, FULL_PERMISSION_POLICY, isNamedPermissionProfile, MCP_CAPABILITIES, NAMED_PERMISSION_PROFILES, permissionPolicyForProfile, } from './permissions.js';
27
27
  import { captureOperation, getTelemetry, installTelemetryStreamEndHandler, isTelemetryEnabled, setTelemetryEnabled, shutdownTelemetry, telemetryDisabledInAnyEnvironment, } from './telemetry.js';
28
- import { clearSessionToken, instanceClient, loadInstanceConfig, removeInstance, resolveInstance, saveLocalInstance, saveRemoteInstance, saveSessionToken, useInstance, } from './cliInstances.js';
28
+ import { apiErrorCode, clearSessionToken, instanceClient, loadInstanceConfig, removeInstance, resolveInstance, saveLocalInstance, saveRemoteInstance, saveSessionToken, useInstance, } from './cliInstances.js';
29
29
  import { authenticateBearer, normalizeAndValidatePassword, recoverAdminPassword, setupInitialAdmin } from './auth.js';
30
30
  import { recordAdminAuditEvent } from './storage/adminAudit.js';
31
31
  import { canManageOwnedAccount } from './authorization.js';
32
32
  import { createCliUpdateNotifier } from './updateNotifier.js';
33
33
  import { registerMailCommands } from './cliMail.js';
34
+ import { accountInventoryProperties, configuredOAuthAppKind, connectionProperties } from './accounts/telemetry.js';
34
35
  import { flushLogging, escapeLogTextForConsole, getLogger, logCodedFailure, logFailure, readLocalLogs, shutdownLogging, } from './logging.js';
35
36
  function collectOption(value, previous) {
36
37
  return [...previous, value];
@@ -177,6 +178,8 @@ function readSecretFile(file) {
177
178
  async function secretInput(file, label) {
178
179
  return file === undefined ? hiddenPrompt(label) : readSecretFile(file);
179
180
  }
181
+ /** Offered when a configured public URL, not an explicit --hosted, chose the hosted flow. */
182
+ const LOOPBACK_FLOW_REMEDY = 'Pass --local to use the loopback flow on this computer instead.';
180
183
  function planLine(ent) {
181
184
  const members = `${ent.maxMembers} member${ent.maxMembers === 1 ? '' : 's'}`;
182
185
  return `${ent.plan} (up to ${ent.maxAccounts} mailboxes, ${members})`;
@@ -189,12 +192,45 @@ function warnLicense(db, log = console.error) {
189
192
  }
190
193
  const activeCliOperations = new WeakMap();
191
194
  const activeUpdateNotifiers = new WeakMap();
195
+ /** Attach safe, aggregate properties to the telemetry event for the running command. */
196
+ function recordCliOperationProperties(program, properties) {
197
+ const active = activeCliOperations.get(program);
198
+ if (active)
199
+ active.properties = { ...active.properties, ...properties };
200
+ }
201
+ /** Read local mailbox state without letting telemetry affect the command. */
202
+ async function inspectLocalRegistry(inspect) {
203
+ let context;
204
+ try {
205
+ context = createContext();
206
+ return inspect(context.registry);
207
+ }
208
+ catch {
209
+ return undefined;
210
+ }
211
+ finally {
212
+ if (context) {
213
+ try {
214
+ await context.registry.close();
215
+ }
216
+ catch {
217
+ // Telemetry cleanup must not affect the command.
218
+ }
219
+ try {
220
+ context.db.$client.close();
221
+ }
222
+ catch {
223
+ // Telemetry cleanup must not affect the command.
224
+ }
225
+ }
226
+ }
227
+ }
192
228
  function finishCliOperation(program, outcome, errorCode, error) {
193
229
  const active = activeCliOperations.get(program);
194
230
  if (!active)
195
231
  return;
196
232
  activeCliOperations.delete(program);
197
- if (outcome === 'error' && !active.errorLogged) {
233
+ if (outcome === 'error') {
198
234
  const context = { productSurface: 'cli', operation: active.operation, skipConsole: true };
199
235
  if (error !== undefined)
200
236
  logFailure(active.logger, 'cli.operation_failed', error, context);
@@ -207,18 +243,14 @@ function finishCliOperation(program, outcome, errorCode, error) {
207
243
  outcome,
208
244
  durationMs: performance.now() - active.startedAt,
209
245
  errorCode,
246
+ ...(active.properties ? { properties: active.properties } : {}),
210
247
  });
211
248
  }
212
- function logActiveCliError(program, error) {
213
- const active = activeCliOperations.get(program);
214
- if (!active || active.errorLogged)
215
- return;
216
- active.errorLogged = true;
217
- logFailure(active.logger, 'cli.operation_failed', error, {
218
- productSurface: 'cli',
219
- operation: active.operation,
220
- skipConsole: true,
221
- });
249
+ /** End a failed command with the error's own telemetry code instead of a generic one. */
250
+ function failCliOperation(program, error, prefix = 'Error: ') {
251
+ finishCliOperation(program, 'error', apiErrorCode(error), error);
252
+ console.error(`${prefix}${error instanceof Error ? error.message : String(error)}`);
253
+ process.exitCode = 1;
222
254
  }
223
255
  export function waitForServerListening(server) {
224
256
  return new Promise((resolve, reject) => {
@@ -415,9 +447,7 @@ export function createCliProgram(options = {}) {
415
447
  console.log(`Fluxmail is ready. Logged in to local as ${result.member.name}.`);
416
448
  }
417
449
  catch (err) {
418
- logActiveCliError(program, err);
419
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
420
- process.exitCode = 1;
450
+ failCliOperation(program, err);
421
451
  }
422
452
  });
423
453
  program
@@ -465,9 +495,7 @@ export function createCliProgram(options = {}) {
465
495
  console.log(`Logged in to ${instanceName} as ${result.member.name}.`);
466
496
  }
467
497
  catch (err) {
468
- logActiveCliError(program, err);
469
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
470
- process.exitCode = 1;
498
+ failCliOperation(program, err);
471
499
  }
472
500
  });
473
501
  program
@@ -484,9 +512,7 @@ export function createCliProgram(options = {}) {
484
512
  console.log(`Logged out of ${selected.name}.`);
485
513
  }
486
514
  catch (err) {
487
- logActiveCliError(program, err);
488
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
489
- process.exitCode = 1;
515
+ failCliOperation(program, err);
490
516
  }
491
517
  });
492
518
  const instances = program.command('instances').description('Manage local and remote CLI instances');
@@ -509,9 +535,7 @@ export function createCliProgram(options = {}) {
509
535
  console.log(`Using ${name}.`);
510
536
  }
511
537
  catch (err) {
512
- logActiveCliError(program, err);
513
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
514
- process.exitCode = 1;
538
+ failCliOperation(program, err);
515
539
  }
516
540
  });
517
541
  instances
@@ -524,9 +548,7 @@ export function createCliProgram(options = {}) {
524
548
  console.log(`Removed CLI instance ${name}. Server data was not changed.`);
525
549
  }
526
550
  catch (err) {
527
- logActiveCliError(program, err);
528
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
529
- process.exitCode = 1;
551
+ failCliOperation(program, err);
530
552
  }
531
553
  });
532
554
  const authCommand = program.command('auth').description('Manage interactive authentication');
@@ -554,9 +576,7 @@ export function createCliProgram(options = {}) {
554
576
  console.log(`Reset ${member.name}'s password and revoked existing sessions.`);
555
577
  }
556
578
  catch (err) {
557
- logActiveCliError(program, err);
558
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
559
- process.exitCode = 1;
579
+ failCliOperation(program, err);
560
580
  }
561
581
  });
562
582
  authCommand
@@ -570,9 +590,7 @@ export function createCliProgram(options = {}) {
570
590
  }
571
591
  }
572
592
  catch (err) {
573
- logActiveCliError(program, err);
574
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
575
- process.exitCode = 1;
593
+ failCliOperation(program, err);
576
594
  }
577
595
  });
578
596
  authCommand
@@ -587,9 +605,7 @@ export function createCliProgram(options = {}) {
587
605
  console.log(`Revoked ${sessionId}.`);
588
606
  }
589
607
  catch (err) {
590
- logActiveCliError(program, err);
591
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
592
- process.exitCode = 1;
608
+ failCliOperation(program, err);
593
609
  }
594
610
  });
595
611
  program
@@ -708,33 +724,54 @@ export function createCliProgram(options = {}) {
708
724
  return;
709
725
  }
710
726
  const selected = resolveInstance(selectedInstance());
727
+ // A remote instance reconnects an IMAP mailbox that matches the address,
728
+ // so without --reauthorize only the server knows which one this is.
729
+ const reauthorize = opts.reauthorize !== undefined
730
+ ? true
731
+ : selected.profile.kind === 'remote' && provider === 'imap'
732
+ ? undefined
733
+ : false;
734
+ recordCliOperationProperties(program, connectionProperties({ provider, reauthorize }));
711
735
  if (!selected.token) {
712
- console.error(`Error: Log in to instance "${selected.name}" before connecting a mailbox.`);
713
- process.exitCode = 1;
736
+ failCliOperation(program, new EmailError('permission_denied', `Log in to instance "${selected.name}" before connecting a mailbox.`));
714
737
  return;
715
738
  }
716
739
  try {
717
740
  validateAccountConnectionFlags(provider, opts);
718
741
  }
719
742
  catch (err) {
720
- logActiveCliError(program, err);
721
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
722
- process.exitCode = 1;
743
+ failCliOperation(program, err);
723
744
  return;
724
745
  }
725
746
  let useControlPlane = selected.profile.kind === 'remote' || provider === 'imap';
747
+ if (selected.profile.kind === 'remote' && provider !== 'imap') {
748
+ // A remote instance always completes OAuth through its own public URL.
749
+ recordCliOperationProperties(program, connectionProperties({ provider, reauthorize, flow: 'hosted' }));
750
+ }
726
751
  if (selected.profile.kind === 'local' && provider !== 'imap') {
727
752
  try {
728
- useControlPlane = selectGmailConnectionMode(createContext().config, opts) === 'hosted';
753
+ const { config } = createContext();
754
+ const mode = selectGmailConnectionMode(config, opts);
755
+ recordCliOperationProperties(program, connectionProperties({
756
+ provider,
757
+ reauthorize,
758
+ flow: mode === 'hosted' ? 'hosted' : 'loopback',
759
+ oauthApp: configuredOAuthAppKind(config, provider),
760
+ }));
761
+ // A configured public URL selects the hosted flow on its own, so report a
762
+ // missing OAuth app here instead of after a connection grant is minted.
763
+ if (mode === 'hosted') {
764
+ assertHostedConnectionReady(config, provider, opts.hosted ? undefined : LOOPBACK_FLOW_REMEDY);
765
+ }
766
+ useControlPlane = mode === 'hosted';
729
767
  }
730
768
  catch (err) {
731
- logActiveCliError(program, err);
732
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
733
- process.exitCode = 1;
769
+ failCliOperation(program, err);
734
770
  return;
735
771
  }
736
772
  }
737
773
  if (useControlPlane) {
774
+ let localImapReauthorize = reauthorize ?? false;
738
775
  try {
739
776
  if (selected.profile.kind === 'remote' && opts.local) {
740
777
  throw new EmailError('invalid_request', '--local is only available for the local instance.');
@@ -751,6 +788,10 @@ export function createCliProgram(options = {}) {
751
788
  throw new EmailError('invalid_request', '--imap-host is required for IMAP accounts.');
752
789
  if (!opts.smtpHost)
753
790
  throw new EmailError('invalid_request', '--smtp-host is required for IMAP accounts.');
791
+ if (selected.profile.kind === 'local' && !localImapReauthorize) {
792
+ localImapReauthorize =
793
+ (await inspectLocalRegistry((registry) => registry.listAccounts().some((account) => account.email.toLowerCase() === email.toLowerCase()))) ?? false;
794
+ }
754
795
  const imapPassword = await accountSecret(opts.imapPasswordEnv, 'IMAP password');
755
796
  const smtpPassword = opts.smtpPasswordEnv
756
797
  ? await accountSecret(opts.smtpPasswordEnv, 'SMTP password')
@@ -792,15 +833,20 @@ export function createCliProgram(options = {}) {
792
833
  if (result.connectionUrl)
793
834
  console.log(`Open this URL in your browser:\n\n ${result.connectionUrl}\n`);
794
835
  if (result.account) {
836
+ if (selected.profile.kind === 'local' && provider === 'imap') {
837
+ const inventory = await inspectLocalRegistry((registry) => accountInventoryProperties(registry));
838
+ recordCliOperationProperties(program, {
839
+ reauthorize: localImapReauthorize,
840
+ ...inventory,
841
+ });
842
+ }
795
843
  console.log(`Connected ${result.account.email} (account id: ${result.account.id})`);
796
844
  for (const warning of result.warnings ?? [])
797
845
  console.log(`Warning: ${warning.message}.`);
798
846
  }
799
847
  }
800
848
  catch (err) {
801
- logActiveCliError(program, err);
802
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
803
- process.exitCode = 1;
849
+ failCliOperation(program, err);
804
850
  }
805
851
  return;
806
852
  }
@@ -822,6 +868,7 @@ export function createCliProgram(options = {}) {
822
868
  if (!existing)
823
869
  ctx.registry.assertCanAddAccount();
824
870
  const access = { sharedWithAll: false, grantedMemberIds: [] };
871
+ const accountIdsBeforeConnection = new Set(ctx.registry.listAccounts().map((account) => account.id));
825
872
  const auditConnection = (operation, accountId) => {
826
873
  try {
827
874
  recordAdminAuditEvent(ctx.db, {
@@ -868,6 +915,10 @@ export function createCliProgram(options = {}) {
868
915
  return ctx.registry.addOutlookAccount(result.email, result.credentials, result.displayName, owner.id, existing?.id, access);
869
916
  });
870
917
  auditConnection('account.connection.complete', account.id);
918
+ recordCliOperationProperties(program, {
919
+ reauthorize: accountIdsBeforeConnection.has(account.id),
920
+ ...accountInventoryProperties(ctx.registry),
921
+ });
871
922
  console.log(`\nConnected ${account.email} (account id: ${account.id})` +
872
923
  (account.ownerMemberId === owner.id ? ` for member ${owner.name}` : ''));
873
924
  return;
@@ -903,6 +954,10 @@ export function createCliProgram(options = {}) {
903
954
  return ctx.registry.addGmailAccount(result.email, result.tokens, result.displayName, owner.id, access, result.oauthClient);
904
955
  });
905
956
  auditConnection('account.connection.complete', account.id);
957
+ recordCliOperationProperties(program, {
958
+ reauthorize: accountIdsBeforeConnection.has(account.id),
959
+ ...accountInventoryProperties(ctx.registry),
960
+ });
906
961
  console.log(`\nConnected ${account.email} (account id: ${account.id})` +
907
962
  (account.ownerMemberId === owner.id ? ` for member ${owner.name}` : ''));
908
963
  if (account.ownerMemberId !== owner.id) {
@@ -912,9 +967,7 @@ export function createCliProgram(options = {}) {
912
967
  }
913
968
  }
914
969
  catch (err) {
915
- finishCliOperation(program, 'error', isEmailError(err) ? err.code : 'internal', err);
916
- console.error(`\nError: ${err instanceof Error ? err.message : String(err)}`);
917
- process.exitCode = 1;
970
+ failCliOperation(program, err, '\nError: ');
918
971
  }
919
972
  });
920
973
  accounts
@@ -953,9 +1006,7 @@ export function createCliProgram(options = {}) {
953
1006
  console.log(`Warning: ${warning.message}.`);
954
1007
  }
955
1008
  catch (err) {
956
- logActiveCliError(program, err);
957
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
958
- process.exitCode = 1;
1009
+ failCliOperation(program, err);
959
1010
  }
960
1011
  });
961
1012
  accounts
@@ -978,9 +1029,7 @@ export function createCliProgram(options = {}) {
978
1029
  }
979
1030
  }
980
1031
  catch (err) {
981
- logActiveCliError(program, err);
982
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
983
- process.exitCode = 1;
1032
+ failCliOperation(program, err);
984
1033
  }
985
1034
  });
986
1035
  accounts
@@ -989,15 +1038,24 @@ export function createCliProgram(options = {}) {
989
1038
  .description('Disconnect an account and delete its stored tokens')
990
1039
  .action(async (accountId) => {
991
1040
  try {
992
- await instanceClient(selectedInstance()).json(`/api/v1/accounts/${encodeURIComponent(accountId)}/connection`, {
1041
+ const selected = resolveInstance(selectedInstance());
1042
+ const provider = selected.profile.kind === 'local'
1043
+ ? await inspectLocalRegistry((registry) => registry.getAccount(accountId).provider)
1044
+ : undefined;
1045
+ await instanceClient(selected.name).json(`/api/v1/accounts/${encodeURIComponent(accountId)}/connection`, {
993
1046
  method: 'DELETE',
994
1047
  });
1048
+ if (selected.profile.kind === 'local') {
1049
+ const inventory = await inspectLocalRegistry((registry) => accountInventoryProperties(registry));
1050
+ recordCliOperationProperties(program, {
1051
+ ...(provider ? { provider } : {}),
1052
+ ...inventory,
1053
+ });
1054
+ }
995
1055
  console.log(`Removed ${accountId}`);
996
1056
  }
997
1057
  catch (err) {
998
- logActiveCliError(program, err);
999
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
1000
- process.exitCode = 1;
1058
+ failCliOperation(program, err);
1001
1059
  }
1002
1060
  });
1003
1061
  accounts
@@ -1015,9 +1073,7 @@ export function createCliProgram(options = {}) {
1015
1073
  console.log(`${account.email} is now owned by ${account.ownerMemberId}.`);
1016
1074
  }
1017
1075
  catch (err) {
1018
- logActiveCliError(program, err);
1019
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
1020
- process.exitCode = 1;
1076
+ failCliOperation(program, err);
1021
1077
  }
1022
1078
  });
1023
1079
  accounts
@@ -1041,9 +1097,7 @@ export function createCliProgram(options = {}) {
1041
1097
  console.log(`Updated access for ${account.email}: ${account.sharedWithAll ? 'all members' : `${account.grantedMemberIds.length} explicit grant(s)`}.`);
1042
1098
  }
1043
1099
  catch (err) {
1044
- logActiveCliError(program, err);
1045
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
1046
- process.exitCode = 1;
1100
+ failCliOperation(program, err);
1047
1101
  }
1048
1102
  });
1049
1103
  const membersCmd = program.command('members').description('Manage members (people using this instance)');
@@ -1068,9 +1122,7 @@ export function createCliProgram(options = {}) {
1068
1122
  console.log(`Expires: ${new Date(member.invitation.expiresAt).toISOString()}`);
1069
1123
  }
1070
1124
  catch (err) {
1071
- logActiveCliError(program, err);
1072
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
1073
- process.exitCode = 1;
1125
+ failCliOperation(program, err);
1074
1126
  }
1075
1127
  });
1076
1128
  membersCmd
@@ -1088,9 +1140,7 @@ export function createCliProgram(options = {}) {
1088
1140
  }
1089
1141
  }
1090
1142
  catch (err) {
1091
- logActiveCliError(program, err);
1092
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
1093
- process.exitCode = 1;
1143
+ failCliOperation(program, err);
1094
1144
  }
1095
1145
  });
1096
1146
  membersCmd
@@ -1105,9 +1155,7 @@ export function createCliProgram(options = {}) {
1105
1155
  console.log(`Removed ${memberId}.`);
1106
1156
  }
1107
1157
  catch (err) {
1108
- logActiveCliError(program, err);
1109
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
1110
- process.exitCode = 1;
1158
+ failCliOperation(program, err);
1111
1159
  }
1112
1160
  });
1113
1161
  membersCmd
@@ -1128,9 +1176,7 @@ export function createCliProgram(options = {}) {
1128
1176
  console.log(`${member.name} now has the ${member.role} role.`);
1129
1177
  }
1130
1178
  catch (err) {
1131
- logActiveCliError(program, err);
1132
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
1133
- process.exitCode = 1;
1179
+ failCliOperation(program, err);
1134
1180
  }
1135
1181
  });
1136
1182
  membersCmd
@@ -1151,9 +1197,7 @@ export function createCliProgram(options = {}) {
1151
1197
  console.log(`${member.name} is now ${member.status}.`);
1152
1198
  }
1153
1199
  catch (err) {
1154
- logActiveCliError(program, err);
1155
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
1156
- process.exitCode = 1;
1200
+ failCliOperation(program, err);
1157
1201
  }
1158
1202
  });
1159
1203
  for (const [commandName, endpoint, label] of [
@@ -1171,9 +1215,7 @@ export function createCliProgram(options = {}) {
1171
1215
  console.log(`Expires: ${new Date(token.expiresAt).toISOString()}`);
1172
1216
  }
1173
1217
  catch (err) {
1174
- logActiveCliError(program, err);
1175
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
1176
- process.exitCode = 1;
1218
+ failCliOperation(program, err);
1177
1219
  }
1178
1220
  });
1179
1221
  }
@@ -1189,9 +1231,7 @@ export function createCliProgram(options = {}) {
1189
1231
  }
1190
1232
  }
1191
1233
  catch (err) {
1192
- logActiveCliError(program, err);
1193
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
1194
- process.exitCode = 1;
1234
+ failCliOperation(program, err);
1195
1235
  }
1196
1236
  });
1197
1237
  membersCmd
@@ -1205,9 +1245,7 @@ export function createCliProgram(options = {}) {
1205
1245
  console.log(`Revoked ${sessionId}.`);
1206
1246
  }
1207
1247
  catch (err) {
1208
- logActiveCliError(program, err);
1209
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
1210
- process.exitCode = 1;
1248
+ failCliOperation(program, err);
1211
1249
  }
1212
1250
  });
1213
1251
  const apikey = program.command('apikey').description('Manage API keys for the HTTP MCP and REST APIs');
@@ -1262,9 +1300,7 @@ export function createCliProgram(options = {}) {
1262
1300
  console.log('Store it now; it cannot be shown again.');
1263
1301
  }
1264
1302
  catch (err) {
1265
- logActiveCliError(program, err);
1266
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
1267
- process.exitCode = 1;
1303
+ failCliOperation(program, err);
1268
1304
  }
1269
1305
  });
1270
1306
  apikey
@@ -1289,9 +1325,7 @@ export function createCliProgram(options = {}) {
1289
1325
  }
1290
1326
  }
1291
1327
  catch (err) {
1292
- logActiveCliError(program, err);
1293
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
1294
- process.exitCode = 1;
1328
+ failCliOperation(program, err);
1295
1329
  }
1296
1330
  });
1297
1331
  apikey
@@ -1316,9 +1350,7 @@ export function createCliProgram(options = {}) {
1316
1350
  : `Updated ${keyId} to ${accountIds.length} mailbox(es).`);
1317
1351
  }
1318
1352
  catch (err) {
1319
- logActiveCliError(program, err);
1320
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
1321
- process.exitCode = 1;
1353
+ failCliOperation(program, err);
1322
1354
  }
1323
1355
  });
1324
1356
  apikey
@@ -1351,9 +1383,7 @@ export function createCliProgram(options = {}) {
1351
1383
  console.log(`Updated ${keyId} to profile ${permissions.profile}`);
1352
1384
  }
1353
1385
  catch (err) {
1354
- logActiveCliError(program, err);
1355
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
1356
- process.exitCode = 1;
1386
+ failCliOperation(program, err);
1357
1387
  }
1358
1388
  });
1359
1389
  apikey
@@ -1371,9 +1401,7 @@ export function createCliProgram(options = {}) {
1371
1401
  console.log(`Revoked ${keyId}`);
1372
1402
  }
1373
1403
  catch (err) {
1374
- logActiveCliError(program, err);
1375
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
1376
- process.exitCode = 1;
1404
+ failCliOperation(program, err);
1377
1405
  }
1378
1406
  });
1379
1407
  const license = program.command('license').description('Manage the paid-tier license');
@@ -1413,9 +1441,7 @@ export function createCliProgram(options = {}) {
1413
1441
  }
1414
1442
  }
1415
1443
  catch (err) {
1416
- logActiveCliError(program, err);
1417
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
1418
- process.exitCode = 1;
1444
+ failCliOperation(program, err);
1419
1445
  }
1420
1446
  });
1421
1447
  license
@@ -1436,9 +1462,7 @@ export function createCliProgram(options = {}) {
1436
1462
  console.log(`Warning: ${status.warning}`);
1437
1463
  }
1438
1464
  catch (err) {
1439
- logActiveCliError(program, err);
1440
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
1441
- process.exitCode = 1;
1465
+ failCliOperation(program, err);
1442
1466
  }
1443
1467
  });
1444
1468
  license
@@ -1453,9 +1477,7 @@ export function createCliProgram(options = {}) {
1453
1477
  console.log('This instance is back to Personal plan limits.');
1454
1478
  }
1455
1479
  catch (err) {
1456
- logActiveCliError(program, err);
1457
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
1458
- process.exitCode = 1;
1480
+ failCliOperation(program, err);
1459
1481
  }
1460
1482
  });
1461
1483
  const configCmd = program.command('config').description('Inspect and initialize deployment configuration');
@@ -1470,9 +1492,7 @@ export function createCliProgram(options = {}) {
1470
1492
  console.log(`Created ${file}`);
1471
1493
  }
1472
1494
  catch (err) {
1473
- logActiveCliError(program, err);
1474
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
1475
- process.exitCode = 1;
1495
+ failCliOperation(program, err);
1476
1496
  }
1477
1497
  });
1478
1498
  configCmd
@@ -1665,9 +1685,7 @@ export function createCliProgram(options = {}) {
1665
1685
  console.log(JSON.stringify(status, null, 2));
1666
1686
  }
1667
1687
  catch (err) {
1668
- logActiveCliError(program, err);
1669
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
1670
- process.exitCode = 1;
1688
+ failCliOperation(program, err);
1671
1689
  }
1672
1690
  });
1673
1691
  return program;
@@ -1678,9 +1696,7 @@ export async function runCli(argv = process.argv) {
1678
1696
  await program.parseAsync([...argv]);
1679
1697
  }
1680
1698
  catch (err) {
1681
- finishCliOperation(program, 'error', isEmailError(err) ? err.code : 'internal', err);
1682
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
1683
- process.exitCode = 1;
1699
+ failCliOperation(program, err);
1684
1700
  await Promise.all([shutdownTelemetry(), shutdownLogging()]);
1685
1701
  }
1686
1702
  }