fluxmail 0.7.0 → 0.8.1
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/LICENSE.md +66 -84
- package/README.md +1 -1
- package/dist/accounts/gmailConnection.d.ts +5 -0
- package/dist/accounts/gmailConnection.d.ts.map +1 -1
- package/dist/accounts/gmailConnection.js +24 -6
- package/dist/accounts/gmailConnection.js.map +1 -1
- package/dist/accounts/googleAuth.d.ts.map +1 -1
- package/dist/accounts/googleAuth.js +6 -1
- package/dist/accounts/googleAuth.js.map +1 -1
- package/dist/accounts/microsoftAuth.d.ts +1 -0
- package/dist/accounts/microsoftAuth.d.ts.map +1 -1
- package/dist/accounts/microsoftAuth.js +12 -0
- package/dist/accounts/microsoftAuth.js.map +1 -1
- package/dist/accounts/registry.d.ts +3 -1
- package/dist/accounts/registry.d.ts.map +1 -1
- package/dist/accounts/registry.js +11 -0
- package/dist/accounts/registry.js.map +1 -1
- package/dist/accounts/telemetry.d.ts +27 -0
- package/dist/accounts/telemetry.d.ts.map +1 -0
- package/dist/accounts/telemetry.js +42 -0
- package/dist/accounts/telemetry.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +163 -130
- package/dist/cli.js.map +1 -1
- package/dist/cliInstances.d.ts.map +1 -1
- package/dist/cliInstances.js +6 -2
- package/dist/cliInstances.js.map +1 -1
- package/dist/http/admin.d.ts.map +1 -1
- package/dist/http/admin.js +17 -0
- package/dist/http/admin.js.map +1 -1
- package/dist/http/app.d.ts +1 -1
- package/dist/http/app.d.ts.map +1 -1
- package/dist/http/app.js +76 -12
- package/dist/http/app.js.map +1 -1
- package/dist/http/identity.d.ts.map +1 -1
- package/dist/http/identity.js +12 -0
- package/dist/http/identity.js.map +1 -1
- package/dist/http/operationTelemetry.d.ts +14 -0
- package/dist/http/operationTelemetry.d.ts.map +1 -0
- package/dist/http/operationTelemetry.js +8 -0
- package/dist/http/operationTelemetry.js.map +1 -0
- package/dist/http/rest.d.ts +2 -1
- package/dist/http/rest.d.ts.map +1 -1
- package/dist/http/rest.js +3 -0
- package/dist/http/rest.js.map +1 -1
- 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';
|
|
29
|
-
import { authenticateBearer, normalizeAndValidatePassword, recoverAdminPassword, setupInitialAdmin } from './auth.js';
|
|
28
|
+
import { apiErrorCode, clearSessionToken, instanceClient, loadInstanceConfig, removeInstance, resolveInstance, saveLocalInstance, saveRemoteInstance, saveSessionToken, useInstance, } from './cliInstances.js';
|
|
29
|
+
import { authenticateBearer, isBootstrapComplete, 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'
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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) => {
|
|
@@ -390,9 +422,13 @@ export function createCliProgram(options = {}) {
|
|
|
390
422
|
.option('--existing-admin <member>', 'Existing administrator id or email for a migrated instance')
|
|
391
423
|
.action(async (opts) => {
|
|
392
424
|
try {
|
|
425
|
+
const earlyContext = createContext();
|
|
426
|
+
if (isBootstrapComplete(earlyContext.db)) {
|
|
427
|
+
throw new EmailError('invalid_request', 'This instance has already been set up. Run "fluxmail --instance local login" to log in to the existing instance.');
|
|
428
|
+
}
|
|
393
429
|
const name = opts.name ?? (opts.existingAdmin ? undefined : await textPrompt('Administrator name'));
|
|
394
430
|
const email = opts.email ?? (await textPrompt('Administrator email'));
|
|
395
|
-
const context =
|
|
431
|
+
const context = earlyContext;
|
|
396
432
|
const passwordMember = opts.existingAdmin
|
|
397
433
|
? { ...findMember(context.db, opts.existingAdmin), email }
|
|
398
434
|
: { name: name ?? '', email };
|
|
@@ -415,9 +451,7 @@ export function createCliProgram(options = {}) {
|
|
|
415
451
|
console.log(`Fluxmail is ready. Logged in to local as ${result.member.name}.`);
|
|
416
452
|
}
|
|
417
453
|
catch (err) {
|
|
418
|
-
|
|
419
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
420
|
-
process.exitCode = 1;
|
|
454
|
+
failCliOperation(program, err);
|
|
421
455
|
}
|
|
422
456
|
});
|
|
423
457
|
program
|
|
@@ -433,7 +467,10 @@ export function createCliProgram(options = {}) {
|
|
|
433
467
|
throw new EmailError('invalid_request', '--enroll and --reset cannot be used together.');
|
|
434
468
|
}
|
|
435
469
|
const requestedInstance = selectedInstance();
|
|
436
|
-
|
|
470
|
+
const instanceConfig = loadInstanceConfig();
|
|
471
|
+
if (!opts.server &&
|
|
472
|
+
!instanceConfig.instances.local &&
|
|
473
|
+
(requestedInstance === 'local' || (!requestedInstance && Object.keys(instanceConfig.instances).length === 0))) {
|
|
437
474
|
saveLocalInstance('local');
|
|
438
475
|
}
|
|
439
476
|
const instanceName = requestedInstance ?? (opts.server ? 'remote' : resolveInstance().name);
|
|
@@ -465,9 +502,7 @@ export function createCliProgram(options = {}) {
|
|
|
465
502
|
console.log(`Logged in to ${instanceName} as ${result.member.name}.`);
|
|
466
503
|
}
|
|
467
504
|
catch (err) {
|
|
468
|
-
|
|
469
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
470
|
-
process.exitCode = 1;
|
|
505
|
+
failCliOperation(program, err);
|
|
471
506
|
}
|
|
472
507
|
});
|
|
473
508
|
program
|
|
@@ -484,9 +519,7 @@ export function createCliProgram(options = {}) {
|
|
|
484
519
|
console.log(`Logged out of ${selected.name}.`);
|
|
485
520
|
}
|
|
486
521
|
catch (err) {
|
|
487
|
-
|
|
488
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
489
|
-
process.exitCode = 1;
|
|
522
|
+
failCliOperation(program, err);
|
|
490
523
|
}
|
|
491
524
|
});
|
|
492
525
|
const instances = program.command('instances').description('Manage local and remote CLI instances');
|
|
@@ -495,8 +528,18 @@ export function createCliProgram(options = {}) {
|
|
|
495
528
|
.description('List configured instances')
|
|
496
529
|
.action(() => {
|
|
497
530
|
const config = loadInstanceConfig();
|
|
498
|
-
|
|
499
|
-
|
|
531
|
+
const rows = Object.entries(config.instances).map(([name, profile]) => ({
|
|
532
|
+
name,
|
|
533
|
+
active: config.active === name ? '*' : '',
|
|
534
|
+
kind: profile.kind,
|
|
535
|
+
url: profile.kind === 'remote' ? profile.serverUrl : '',
|
|
536
|
+
}));
|
|
537
|
+
const width = (values) => Math.max(...values.map((value) => value.length));
|
|
538
|
+
const nameWidth = width(['NAME', ...rows.map((row) => row.name)]);
|
|
539
|
+
const kindWidth = width(['KIND', ...rows.map((row) => row.kind)]);
|
|
540
|
+
console.log(`${'NAME'.padEnd(nameWidth)} ACTIVE ${'KIND'.padEnd(kindWidth)} URL`.trimEnd());
|
|
541
|
+
for (const row of rows) {
|
|
542
|
+
console.log(`${row.name.padEnd(nameWidth)} ${(row.active ? '*' : ' ').padEnd('ACTIVE'.length)} ${row.kind.padEnd(kindWidth)} ${row.url}`.trimEnd());
|
|
500
543
|
}
|
|
501
544
|
});
|
|
502
545
|
instances
|
|
@@ -509,9 +552,7 @@ export function createCliProgram(options = {}) {
|
|
|
509
552
|
console.log(`Using ${name}.`);
|
|
510
553
|
}
|
|
511
554
|
catch (err) {
|
|
512
|
-
|
|
513
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
514
|
-
process.exitCode = 1;
|
|
555
|
+
failCliOperation(program, err);
|
|
515
556
|
}
|
|
516
557
|
});
|
|
517
558
|
instances
|
|
@@ -524,9 +565,7 @@ export function createCliProgram(options = {}) {
|
|
|
524
565
|
console.log(`Removed CLI instance ${name}. Server data was not changed.`);
|
|
525
566
|
}
|
|
526
567
|
catch (err) {
|
|
527
|
-
|
|
528
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
529
|
-
process.exitCode = 1;
|
|
568
|
+
failCliOperation(program, err);
|
|
530
569
|
}
|
|
531
570
|
});
|
|
532
571
|
const authCommand = program.command('auth').description('Manage interactive authentication');
|
|
@@ -554,9 +593,7 @@ export function createCliProgram(options = {}) {
|
|
|
554
593
|
console.log(`Reset ${member.name}'s password and revoked existing sessions.`);
|
|
555
594
|
}
|
|
556
595
|
catch (err) {
|
|
557
|
-
|
|
558
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
559
|
-
process.exitCode = 1;
|
|
596
|
+
failCliOperation(program, err);
|
|
560
597
|
}
|
|
561
598
|
});
|
|
562
599
|
authCommand
|
|
@@ -570,9 +607,7 @@ export function createCliProgram(options = {}) {
|
|
|
570
607
|
}
|
|
571
608
|
}
|
|
572
609
|
catch (err) {
|
|
573
|
-
|
|
574
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
575
|
-
process.exitCode = 1;
|
|
610
|
+
failCliOperation(program, err);
|
|
576
611
|
}
|
|
577
612
|
});
|
|
578
613
|
authCommand
|
|
@@ -587,9 +622,7 @@ export function createCliProgram(options = {}) {
|
|
|
587
622
|
console.log(`Revoked ${sessionId}.`);
|
|
588
623
|
}
|
|
589
624
|
catch (err) {
|
|
590
|
-
|
|
591
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
592
|
-
process.exitCode = 1;
|
|
625
|
+
failCliOperation(program, err);
|
|
593
626
|
}
|
|
594
627
|
});
|
|
595
628
|
program
|
|
@@ -708,33 +741,54 @@ export function createCliProgram(options = {}) {
|
|
|
708
741
|
return;
|
|
709
742
|
}
|
|
710
743
|
const selected = resolveInstance(selectedInstance());
|
|
744
|
+
// A remote instance reconnects an IMAP mailbox that matches the address,
|
|
745
|
+
// so without --reauthorize only the server knows which one this is.
|
|
746
|
+
const reauthorize = opts.reauthorize !== undefined
|
|
747
|
+
? true
|
|
748
|
+
: selected.profile.kind === 'remote' && provider === 'imap'
|
|
749
|
+
? undefined
|
|
750
|
+
: false;
|
|
751
|
+
recordCliOperationProperties(program, connectionProperties({ provider, reauthorize }));
|
|
711
752
|
if (!selected.token) {
|
|
712
|
-
|
|
713
|
-
process.exitCode = 1;
|
|
753
|
+
failCliOperation(program, new EmailError('permission_denied', `Log in to instance "${selected.name}" before connecting a mailbox.`));
|
|
714
754
|
return;
|
|
715
755
|
}
|
|
716
756
|
try {
|
|
717
757
|
validateAccountConnectionFlags(provider, opts);
|
|
718
758
|
}
|
|
719
759
|
catch (err) {
|
|
720
|
-
|
|
721
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
722
|
-
process.exitCode = 1;
|
|
760
|
+
failCliOperation(program, err);
|
|
723
761
|
return;
|
|
724
762
|
}
|
|
725
763
|
let useControlPlane = selected.profile.kind === 'remote' || provider === 'imap';
|
|
764
|
+
if (selected.profile.kind === 'remote' && provider !== 'imap') {
|
|
765
|
+
// A remote instance always completes OAuth through its own public URL.
|
|
766
|
+
recordCliOperationProperties(program, connectionProperties({ provider, reauthorize, flow: 'hosted' }));
|
|
767
|
+
}
|
|
726
768
|
if (selected.profile.kind === 'local' && provider !== 'imap') {
|
|
727
769
|
try {
|
|
728
|
-
|
|
770
|
+
const { config } = createContext();
|
|
771
|
+
const mode = selectGmailConnectionMode(config, opts);
|
|
772
|
+
recordCliOperationProperties(program, connectionProperties({
|
|
773
|
+
provider,
|
|
774
|
+
reauthorize,
|
|
775
|
+
flow: mode === 'hosted' ? 'hosted' : 'loopback',
|
|
776
|
+
oauthApp: configuredOAuthAppKind(config, provider),
|
|
777
|
+
}));
|
|
778
|
+
// A configured public URL selects the hosted flow on its own, so report a
|
|
779
|
+
// missing OAuth app here instead of after a connection grant is minted.
|
|
780
|
+
if (mode === 'hosted') {
|
|
781
|
+
assertHostedConnectionReady(config, provider, opts.hosted ? undefined : LOOPBACK_FLOW_REMEDY);
|
|
782
|
+
}
|
|
783
|
+
useControlPlane = mode === 'hosted';
|
|
729
784
|
}
|
|
730
785
|
catch (err) {
|
|
731
|
-
|
|
732
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
733
|
-
process.exitCode = 1;
|
|
786
|
+
failCliOperation(program, err);
|
|
734
787
|
return;
|
|
735
788
|
}
|
|
736
789
|
}
|
|
737
790
|
if (useControlPlane) {
|
|
791
|
+
let localImapReauthorize = reauthorize ?? false;
|
|
738
792
|
try {
|
|
739
793
|
if (selected.profile.kind === 'remote' && opts.local) {
|
|
740
794
|
throw new EmailError('invalid_request', '--local is only available for the local instance.');
|
|
@@ -751,6 +805,10 @@ export function createCliProgram(options = {}) {
|
|
|
751
805
|
throw new EmailError('invalid_request', '--imap-host is required for IMAP accounts.');
|
|
752
806
|
if (!opts.smtpHost)
|
|
753
807
|
throw new EmailError('invalid_request', '--smtp-host is required for IMAP accounts.');
|
|
808
|
+
if (selected.profile.kind === 'local' && !localImapReauthorize) {
|
|
809
|
+
localImapReauthorize =
|
|
810
|
+
(await inspectLocalRegistry((registry) => registry.listAccounts().some((account) => account.email.toLowerCase() === email.toLowerCase()))) ?? false;
|
|
811
|
+
}
|
|
754
812
|
const imapPassword = await accountSecret(opts.imapPasswordEnv, 'IMAP password');
|
|
755
813
|
const smtpPassword = opts.smtpPasswordEnv
|
|
756
814
|
? await accountSecret(opts.smtpPasswordEnv, 'SMTP password')
|
|
@@ -792,15 +850,20 @@ export function createCliProgram(options = {}) {
|
|
|
792
850
|
if (result.connectionUrl)
|
|
793
851
|
console.log(`Open this URL in your browser:\n\n ${result.connectionUrl}\n`);
|
|
794
852
|
if (result.account) {
|
|
853
|
+
if (selected.profile.kind === 'local' && provider === 'imap') {
|
|
854
|
+
const inventory = await inspectLocalRegistry((registry) => accountInventoryProperties(registry));
|
|
855
|
+
recordCliOperationProperties(program, {
|
|
856
|
+
reauthorize: localImapReauthorize,
|
|
857
|
+
...inventory,
|
|
858
|
+
});
|
|
859
|
+
}
|
|
795
860
|
console.log(`Connected ${result.account.email} (account id: ${result.account.id})`);
|
|
796
861
|
for (const warning of result.warnings ?? [])
|
|
797
862
|
console.log(`Warning: ${warning.message}.`);
|
|
798
863
|
}
|
|
799
864
|
}
|
|
800
865
|
catch (err) {
|
|
801
|
-
|
|
802
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
803
|
-
process.exitCode = 1;
|
|
866
|
+
failCliOperation(program, err);
|
|
804
867
|
}
|
|
805
868
|
return;
|
|
806
869
|
}
|
|
@@ -822,6 +885,7 @@ export function createCliProgram(options = {}) {
|
|
|
822
885
|
if (!existing)
|
|
823
886
|
ctx.registry.assertCanAddAccount();
|
|
824
887
|
const access = { sharedWithAll: false, grantedMemberIds: [] };
|
|
888
|
+
const accountIdsBeforeConnection = new Set(ctx.registry.listAccounts().map((account) => account.id));
|
|
825
889
|
const auditConnection = (operation, accountId) => {
|
|
826
890
|
try {
|
|
827
891
|
recordAdminAuditEvent(ctx.db, {
|
|
@@ -868,6 +932,10 @@ export function createCliProgram(options = {}) {
|
|
|
868
932
|
return ctx.registry.addOutlookAccount(result.email, result.credentials, result.displayName, owner.id, existing?.id, access);
|
|
869
933
|
});
|
|
870
934
|
auditConnection('account.connection.complete', account.id);
|
|
935
|
+
recordCliOperationProperties(program, {
|
|
936
|
+
reauthorize: accountIdsBeforeConnection.has(account.id),
|
|
937
|
+
...accountInventoryProperties(ctx.registry),
|
|
938
|
+
});
|
|
871
939
|
console.log(`\nConnected ${account.email} (account id: ${account.id})` +
|
|
872
940
|
(account.ownerMemberId === owner.id ? ` for member ${owner.name}` : ''));
|
|
873
941
|
return;
|
|
@@ -903,6 +971,10 @@ export function createCliProgram(options = {}) {
|
|
|
903
971
|
return ctx.registry.addGmailAccount(result.email, result.tokens, result.displayName, owner.id, access, result.oauthClient);
|
|
904
972
|
});
|
|
905
973
|
auditConnection('account.connection.complete', account.id);
|
|
974
|
+
recordCliOperationProperties(program, {
|
|
975
|
+
reauthorize: accountIdsBeforeConnection.has(account.id),
|
|
976
|
+
...accountInventoryProperties(ctx.registry),
|
|
977
|
+
});
|
|
906
978
|
console.log(`\nConnected ${account.email} (account id: ${account.id})` +
|
|
907
979
|
(account.ownerMemberId === owner.id ? ` for member ${owner.name}` : ''));
|
|
908
980
|
if (account.ownerMemberId !== owner.id) {
|
|
@@ -912,9 +984,7 @@ export function createCliProgram(options = {}) {
|
|
|
912
984
|
}
|
|
913
985
|
}
|
|
914
986
|
catch (err) {
|
|
915
|
-
|
|
916
|
-
console.error(`\nError: ${err instanceof Error ? err.message : String(err)}`);
|
|
917
|
-
process.exitCode = 1;
|
|
987
|
+
failCliOperation(program, err, '\nError: ');
|
|
918
988
|
}
|
|
919
989
|
});
|
|
920
990
|
accounts
|
|
@@ -953,9 +1023,7 @@ export function createCliProgram(options = {}) {
|
|
|
953
1023
|
console.log(`Warning: ${warning.message}.`);
|
|
954
1024
|
}
|
|
955
1025
|
catch (err) {
|
|
956
|
-
|
|
957
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
958
|
-
process.exitCode = 1;
|
|
1026
|
+
failCliOperation(program, err);
|
|
959
1027
|
}
|
|
960
1028
|
});
|
|
961
1029
|
accounts
|
|
@@ -978,9 +1046,7 @@ export function createCliProgram(options = {}) {
|
|
|
978
1046
|
}
|
|
979
1047
|
}
|
|
980
1048
|
catch (err) {
|
|
981
|
-
|
|
982
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
983
|
-
process.exitCode = 1;
|
|
1049
|
+
failCliOperation(program, err);
|
|
984
1050
|
}
|
|
985
1051
|
});
|
|
986
1052
|
accounts
|
|
@@ -989,15 +1055,24 @@ export function createCliProgram(options = {}) {
|
|
|
989
1055
|
.description('Disconnect an account and delete its stored tokens')
|
|
990
1056
|
.action(async (accountId) => {
|
|
991
1057
|
try {
|
|
992
|
-
|
|
1058
|
+
const selected = resolveInstance(selectedInstance());
|
|
1059
|
+
const provider = selected.profile.kind === 'local'
|
|
1060
|
+
? await inspectLocalRegistry((registry) => registry.getAccount(accountId).provider)
|
|
1061
|
+
: undefined;
|
|
1062
|
+
await instanceClient(selected.name).json(`/api/v1/accounts/${encodeURIComponent(accountId)}/connection`, {
|
|
993
1063
|
method: 'DELETE',
|
|
994
1064
|
});
|
|
1065
|
+
if (selected.profile.kind === 'local') {
|
|
1066
|
+
const inventory = await inspectLocalRegistry((registry) => accountInventoryProperties(registry));
|
|
1067
|
+
recordCliOperationProperties(program, {
|
|
1068
|
+
...(provider ? { provider } : {}),
|
|
1069
|
+
...inventory,
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
995
1072
|
console.log(`Removed ${accountId}`);
|
|
996
1073
|
}
|
|
997
1074
|
catch (err) {
|
|
998
|
-
|
|
999
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1000
|
-
process.exitCode = 1;
|
|
1075
|
+
failCliOperation(program, err);
|
|
1001
1076
|
}
|
|
1002
1077
|
});
|
|
1003
1078
|
accounts
|
|
@@ -1015,9 +1090,7 @@ export function createCliProgram(options = {}) {
|
|
|
1015
1090
|
console.log(`${account.email} is now owned by ${account.ownerMemberId}.`);
|
|
1016
1091
|
}
|
|
1017
1092
|
catch (err) {
|
|
1018
|
-
|
|
1019
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1020
|
-
process.exitCode = 1;
|
|
1093
|
+
failCliOperation(program, err);
|
|
1021
1094
|
}
|
|
1022
1095
|
});
|
|
1023
1096
|
accounts
|
|
@@ -1041,9 +1114,7 @@ export function createCliProgram(options = {}) {
|
|
|
1041
1114
|
console.log(`Updated access for ${account.email}: ${account.sharedWithAll ? 'all members' : `${account.grantedMemberIds.length} explicit grant(s)`}.`);
|
|
1042
1115
|
}
|
|
1043
1116
|
catch (err) {
|
|
1044
|
-
|
|
1045
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1046
|
-
process.exitCode = 1;
|
|
1117
|
+
failCliOperation(program, err);
|
|
1047
1118
|
}
|
|
1048
1119
|
});
|
|
1049
1120
|
const membersCmd = program.command('members').description('Manage members (people using this instance)');
|
|
@@ -1068,9 +1139,7 @@ export function createCliProgram(options = {}) {
|
|
|
1068
1139
|
console.log(`Expires: ${new Date(member.invitation.expiresAt).toISOString()}`);
|
|
1069
1140
|
}
|
|
1070
1141
|
catch (err) {
|
|
1071
|
-
|
|
1072
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1073
|
-
process.exitCode = 1;
|
|
1142
|
+
failCliOperation(program, err);
|
|
1074
1143
|
}
|
|
1075
1144
|
});
|
|
1076
1145
|
membersCmd
|
|
@@ -1088,9 +1157,7 @@ export function createCliProgram(options = {}) {
|
|
|
1088
1157
|
}
|
|
1089
1158
|
}
|
|
1090
1159
|
catch (err) {
|
|
1091
|
-
|
|
1092
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1093
|
-
process.exitCode = 1;
|
|
1160
|
+
failCliOperation(program, err);
|
|
1094
1161
|
}
|
|
1095
1162
|
});
|
|
1096
1163
|
membersCmd
|
|
@@ -1105,9 +1172,7 @@ export function createCliProgram(options = {}) {
|
|
|
1105
1172
|
console.log(`Removed ${memberId}.`);
|
|
1106
1173
|
}
|
|
1107
1174
|
catch (err) {
|
|
1108
|
-
|
|
1109
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1110
|
-
process.exitCode = 1;
|
|
1175
|
+
failCliOperation(program, err);
|
|
1111
1176
|
}
|
|
1112
1177
|
});
|
|
1113
1178
|
membersCmd
|
|
@@ -1128,9 +1193,7 @@ export function createCliProgram(options = {}) {
|
|
|
1128
1193
|
console.log(`${member.name} now has the ${member.role} role.`);
|
|
1129
1194
|
}
|
|
1130
1195
|
catch (err) {
|
|
1131
|
-
|
|
1132
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1133
|
-
process.exitCode = 1;
|
|
1196
|
+
failCliOperation(program, err);
|
|
1134
1197
|
}
|
|
1135
1198
|
});
|
|
1136
1199
|
membersCmd
|
|
@@ -1151,9 +1214,7 @@ export function createCliProgram(options = {}) {
|
|
|
1151
1214
|
console.log(`${member.name} is now ${member.status}.`);
|
|
1152
1215
|
}
|
|
1153
1216
|
catch (err) {
|
|
1154
|
-
|
|
1155
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1156
|
-
process.exitCode = 1;
|
|
1217
|
+
failCliOperation(program, err);
|
|
1157
1218
|
}
|
|
1158
1219
|
});
|
|
1159
1220
|
for (const [commandName, endpoint, label] of [
|
|
@@ -1171,9 +1232,7 @@ export function createCliProgram(options = {}) {
|
|
|
1171
1232
|
console.log(`Expires: ${new Date(token.expiresAt).toISOString()}`);
|
|
1172
1233
|
}
|
|
1173
1234
|
catch (err) {
|
|
1174
|
-
|
|
1175
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1176
|
-
process.exitCode = 1;
|
|
1235
|
+
failCliOperation(program, err);
|
|
1177
1236
|
}
|
|
1178
1237
|
});
|
|
1179
1238
|
}
|
|
@@ -1189,9 +1248,7 @@ export function createCliProgram(options = {}) {
|
|
|
1189
1248
|
}
|
|
1190
1249
|
}
|
|
1191
1250
|
catch (err) {
|
|
1192
|
-
|
|
1193
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1194
|
-
process.exitCode = 1;
|
|
1251
|
+
failCliOperation(program, err);
|
|
1195
1252
|
}
|
|
1196
1253
|
});
|
|
1197
1254
|
membersCmd
|
|
@@ -1205,9 +1262,7 @@ export function createCliProgram(options = {}) {
|
|
|
1205
1262
|
console.log(`Revoked ${sessionId}.`);
|
|
1206
1263
|
}
|
|
1207
1264
|
catch (err) {
|
|
1208
|
-
|
|
1209
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1210
|
-
process.exitCode = 1;
|
|
1265
|
+
failCliOperation(program, err);
|
|
1211
1266
|
}
|
|
1212
1267
|
});
|
|
1213
1268
|
const apikey = program.command('apikey').description('Manage API keys for the HTTP MCP and REST APIs');
|
|
@@ -1262,9 +1317,7 @@ export function createCliProgram(options = {}) {
|
|
|
1262
1317
|
console.log('Store it now; it cannot be shown again.');
|
|
1263
1318
|
}
|
|
1264
1319
|
catch (err) {
|
|
1265
|
-
|
|
1266
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1267
|
-
process.exitCode = 1;
|
|
1320
|
+
failCliOperation(program, err);
|
|
1268
1321
|
}
|
|
1269
1322
|
});
|
|
1270
1323
|
apikey
|
|
@@ -1289,9 +1342,7 @@ export function createCliProgram(options = {}) {
|
|
|
1289
1342
|
}
|
|
1290
1343
|
}
|
|
1291
1344
|
catch (err) {
|
|
1292
|
-
|
|
1293
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1294
|
-
process.exitCode = 1;
|
|
1345
|
+
failCliOperation(program, err);
|
|
1295
1346
|
}
|
|
1296
1347
|
});
|
|
1297
1348
|
apikey
|
|
@@ -1316,9 +1367,7 @@ export function createCliProgram(options = {}) {
|
|
|
1316
1367
|
: `Updated ${keyId} to ${accountIds.length} mailbox(es).`);
|
|
1317
1368
|
}
|
|
1318
1369
|
catch (err) {
|
|
1319
|
-
|
|
1320
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1321
|
-
process.exitCode = 1;
|
|
1370
|
+
failCliOperation(program, err);
|
|
1322
1371
|
}
|
|
1323
1372
|
});
|
|
1324
1373
|
apikey
|
|
@@ -1351,9 +1400,7 @@ export function createCliProgram(options = {}) {
|
|
|
1351
1400
|
console.log(`Updated ${keyId} to profile ${permissions.profile}`);
|
|
1352
1401
|
}
|
|
1353
1402
|
catch (err) {
|
|
1354
|
-
|
|
1355
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1356
|
-
process.exitCode = 1;
|
|
1403
|
+
failCliOperation(program, err);
|
|
1357
1404
|
}
|
|
1358
1405
|
});
|
|
1359
1406
|
apikey
|
|
@@ -1371,9 +1418,7 @@ export function createCliProgram(options = {}) {
|
|
|
1371
1418
|
console.log(`Revoked ${keyId}`);
|
|
1372
1419
|
}
|
|
1373
1420
|
catch (err) {
|
|
1374
|
-
|
|
1375
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1376
|
-
process.exitCode = 1;
|
|
1421
|
+
failCliOperation(program, err);
|
|
1377
1422
|
}
|
|
1378
1423
|
});
|
|
1379
1424
|
const license = program.command('license').description('Manage the paid-tier license');
|
|
@@ -1413,9 +1458,7 @@ export function createCliProgram(options = {}) {
|
|
|
1413
1458
|
}
|
|
1414
1459
|
}
|
|
1415
1460
|
catch (err) {
|
|
1416
|
-
|
|
1417
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1418
|
-
process.exitCode = 1;
|
|
1461
|
+
failCliOperation(program, err);
|
|
1419
1462
|
}
|
|
1420
1463
|
});
|
|
1421
1464
|
license
|
|
@@ -1436,9 +1479,7 @@ export function createCliProgram(options = {}) {
|
|
|
1436
1479
|
console.log(`Warning: ${status.warning}`);
|
|
1437
1480
|
}
|
|
1438
1481
|
catch (err) {
|
|
1439
|
-
|
|
1440
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1441
|
-
process.exitCode = 1;
|
|
1482
|
+
failCliOperation(program, err);
|
|
1442
1483
|
}
|
|
1443
1484
|
});
|
|
1444
1485
|
license
|
|
@@ -1453,9 +1494,7 @@ export function createCliProgram(options = {}) {
|
|
|
1453
1494
|
console.log('This instance is back to Personal plan limits.');
|
|
1454
1495
|
}
|
|
1455
1496
|
catch (err) {
|
|
1456
|
-
|
|
1457
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1458
|
-
process.exitCode = 1;
|
|
1497
|
+
failCliOperation(program, err);
|
|
1459
1498
|
}
|
|
1460
1499
|
});
|
|
1461
1500
|
const configCmd = program.command('config').description('Inspect and initialize deployment configuration');
|
|
@@ -1470,9 +1509,7 @@ export function createCliProgram(options = {}) {
|
|
|
1470
1509
|
console.log(`Created ${file}`);
|
|
1471
1510
|
}
|
|
1472
1511
|
catch (err) {
|
|
1473
|
-
|
|
1474
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1475
|
-
process.exitCode = 1;
|
|
1512
|
+
failCliOperation(program, err);
|
|
1476
1513
|
}
|
|
1477
1514
|
});
|
|
1478
1515
|
configCmd
|
|
@@ -1665,9 +1702,7 @@ export function createCliProgram(options = {}) {
|
|
|
1665
1702
|
console.log(JSON.stringify(status, null, 2));
|
|
1666
1703
|
}
|
|
1667
1704
|
catch (err) {
|
|
1668
|
-
|
|
1669
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1670
|
-
process.exitCode = 1;
|
|
1705
|
+
failCliOperation(program, err);
|
|
1671
1706
|
}
|
|
1672
1707
|
});
|
|
1673
1708
|
return program;
|
|
@@ -1678,9 +1713,7 @@ export async function runCli(argv = process.argv) {
|
|
|
1678
1713
|
await program.parseAsync([...argv]);
|
|
1679
1714
|
}
|
|
1680
1715
|
catch (err) {
|
|
1681
|
-
|
|
1682
|
-
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1683
|
-
process.exitCode = 1;
|
|
1716
|
+
failCliOperation(program, err);
|
|
1684
1717
|
await Promise.all([shutdownTelemetry(), shutdownLogging()]);
|
|
1685
1718
|
}
|
|
1686
1719
|
}
|