outlook-cli 1.2.0 → 1.2.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/auth/token-manager.js +30 -1
- package/cli.js +145 -7
- package/package.json +1 -1
- package/rules/index.js +1 -4
package/auth/token-manager.js
CHANGED
|
@@ -50,6 +50,34 @@ function getTokenStorage() {
|
|
|
50
50
|
return tokenStorage;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
function updateAuthConfig(partialConfig = {}) {
|
|
54
|
+
const nextConfig = {
|
|
55
|
+
...config.AUTH_CONFIG,
|
|
56
|
+
...partialConfig
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
config.AUTH_CONFIG.clientId = nextConfig.clientId;
|
|
60
|
+
config.AUTH_CONFIG.clientSecret = nextConfig.clientSecret;
|
|
61
|
+
config.AUTH_CONFIG.redirectUri = nextConfig.redirectUri;
|
|
62
|
+
config.AUTH_CONFIG.scopes = nextConfig.scopes;
|
|
63
|
+
config.AUTH_CONFIG.tokenStorePath = nextConfig.tokenStorePath;
|
|
64
|
+
config.AUTH_CONFIG.authServerUrl = nextConfig.authServerUrl;
|
|
65
|
+
config.AUTH_CONFIG.tokenEndpoint = nextConfig.tokenEndpoint;
|
|
66
|
+
|
|
67
|
+
if (tokenStorage) {
|
|
68
|
+
const existingTokens = tokenStorage.tokens;
|
|
69
|
+
tokenStorage = new TokenStorage({
|
|
70
|
+
tokenStorePath: config.AUTH_CONFIG.tokenStorePath,
|
|
71
|
+
clientId: config.AUTH_CONFIG.clientId,
|
|
72
|
+
clientSecret: config.AUTH_CONFIG.clientSecret,
|
|
73
|
+
redirectUri: config.AUTH_CONFIG.redirectUri,
|
|
74
|
+
scopes: config.AUTH_CONFIG.scopes,
|
|
75
|
+
tokenEndpoint: config.AUTH_CONFIG.tokenEndpoint
|
|
76
|
+
});
|
|
77
|
+
tokenStorage.tokens = existingTokens;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
53
81
|
/**
|
|
54
82
|
* Loads authentication tokens from the token file
|
|
55
83
|
* @returns {object|null} - The loaded tokens or null if not available
|
|
@@ -195,5 +223,6 @@ module.exports = {
|
|
|
195
223
|
getAccessToken,
|
|
196
224
|
getValidAccessToken,
|
|
197
225
|
createTestTokens,
|
|
198
|
-
clearTokenCache
|
|
226
|
+
clearTokenCache,
|
|
227
|
+
updateAuthConfig
|
|
199
228
|
};
|
package/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* Production-oriented local CLI for outlook-cli.
|
|
4
4
|
*
|
|
@@ -11,6 +11,7 @@ const fs = require('fs');
|
|
|
11
11
|
const http = require('http');
|
|
12
12
|
const https = require('https');
|
|
13
13
|
const path = require('path');
|
|
14
|
+
const readline = require('readline');
|
|
14
15
|
const { spawn, spawnSync } = require('child_process');
|
|
15
16
|
const chalk = require('chalk');
|
|
16
17
|
|
|
@@ -442,7 +443,9 @@ function printUsage() {
|
|
|
442
443
|
' tools list',
|
|
443
444
|
' tools schema <tool-name>',
|
|
444
445
|
' call <tool-name> [--args-json "{...}"] [--arg key=value]',
|
|
445
|
-
' auth status|url|login|logout',
|
|
446
|
+
' auth status|url|login|logout|server',
|
|
447
|
+
' auth login [--client-id <id>] [--client-secret <secret>] [--prompt-credentials true|false]',
|
|
448
|
+
' auth server [--status] [--start]',
|
|
446
449
|
' update [--run] [--to latest|x.y.z]',
|
|
447
450
|
' doctor',
|
|
448
451
|
'',
|
|
@@ -471,12 +474,81 @@ function getAuthUrl() {
|
|
|
471
474
|
return `${config.AUTH_CONFIG.authServerUrl}/auth?client_id=${config.AUTH_CONFIG.clientId}`;
|
|
472
475
|
}
|
|
473
476
|
|
|
477
|
+
function applyRuntimeAuthConfig(partialConfig = {}) {
|
|
478
|
+
const nextConfig = {
|
|
479
|
+
...config.AUTH_CONFIG,
|
|
480
|
+
...partialConfig
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
if (nextConfig.clientId) {
|
|
484
|
+
process.env.OUTLOOK_CLIENT_ID = nextConfig.clientId;
|
|
485
|
+
process.env.MS_CLIENT_ID = nextConfig.clientId;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (nextConfig.clientSecret) {
|
|
489
|
+
process.env.OUTLOOK_CLIENT_SECRET = nextConfig.clientSecret;
|
|
490
|
+
process.env.MS_CLIENT_SECRET = nextConfig.clientSecret;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
tokenManager.updateAuthConfig(nextConfig);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function promptLine(promptText) {
|
|
497
|
+
return new Promise((resolve) => {
|
|
498
|
+
const rl = readline.createInterface({
|
|
499
|
+
input: process.stdin,
|
|
500
|
+
output: process.stdout
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
rl.question(promptText, (answer) => {
|
|
504
|
+
rl.close();
|
|
505
|
+
resolve(String(answer || '').trim());
|
|
506
|
+
});
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
async function ensureAuthCredentials(options, outputMode) {
|
|
511
|
+
let clientId = readOption(options, 'clientId', config.AUTH_CONFIG.clientId);
|
|
512
|
+
let clientSecret = readOption(options, 'clientSecret', config.AUTH_CONFIG.clientSecret);
|
|
513
|
+
const promptCredentials = asBoolean(readOption(options, 'promptCredentials', true), true);
|
|
514
|
+
const canPrompt = outputMode === 'text' && process.stdin.isTTY && process.stdout.isTTY;
|
|
515
|
+
|
|
516
|
+
if ((!clientId || !clientSecret) && promptCredentials && canPrompt) {
|
|
517
|
+
if (outputMode === 'text') {
|
|
518
|
+
process.stdout.write(`${badge('warn')} Missing Microsoft app credentials. Provide values now or pass --client-id/--client-secret.\n`);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if (!clientId) {
|
|
522
|
+
clientId = await promptLine('MS_CLIENT_ID (or OUTLOOK_CLIENT_ID): ');
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (!clientSecret) {
|
|
526
|
+
clientSecret = await promptLine('MS_CLIENT_SECRET (or OUTLOOK_CLIENT_SECRET): ');
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
if (clientId || clientSecret) {
|
|
531
|
+
applyRuntimeAuthConfig({
|
|
532
|
+
clientId,
|
|
533
|
+
clientSecret
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (!config.AUTH_CONFIG.clientId) {
|
|
538
|
+
throw new CliError('Client ID is missing. Set OUTLOOK_CLIENT_ID or MS_CLIENT_ID, or pass --client-id.', 1);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (!config.AUTH_CONFIG.clientSecret) {
|
|
542
|
+
throw new CliError('Client secret is missing. Set OUTLOOK_CLIENT_SECRET or MS_CLIENT_SECRET, or pass --client-secret.', 1);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
474
546
|
function buildCommandCatalog() {
|
|
475
547
|
const toolCatalog = listTools();
|
|
476
548
|
|
|
477
549
|
return {
|
|
478
550
|
commandGroups: {
|
|
479
|
-
auth: ['status', 'url', 'login', 'logout'],
|
|
551
|
+
auth: ['status', 'url', 'login', 'logout', 'server'],
|
|
480
552
|
email: ['list', 'search', 'read', 'send', 'mark-read'],
|
|
481
553
|
calendar: ['list', 'create', 'decline', 'cancel', 'delete'],
|
|
482
554
|
folder: ['list', 'create', 'move'],
|
|
@@ -594,20 +666,27 @@ function openInBrowser(url) {
|
|
|
594
666
|
spawn('xdg-open', [url], { detached: true, stdio: 'ignore' }).unref();
|
|
595
667
|
}
|
|
596
668
|
|
|
597
|
-
function startAuthServer(outputMode) {
|
|
669
|
+
function startAuthServer(outputMode, runtime = {}) {
|
|
598
670
|
const scriptPath = path.join(__dirname, 'outlook-auth-server.js');
|
|
599
671
|
|
|
600
672
|
if (!fs.existsSync(scriptPath)) {
|
|
601
673
|
throw new CliError(`Auth server script not found: ${scriptPath}`, 1);
|
|
602
674
|
}
|
|
603
675
|
|
|
676
|
+
const detached = asBoolean(runtime.detached, false);
|
|
677
|
+
|
|
604
678
|
const child = spawn(process.execPath, [scriptPath], {
|
|
605
679
|
cwd: __dirname,
|
|
606
680
|
windowsHide: true,
|
|
607
681
|
stdio: 'ignore',
|
|
608
|
-
env: process.env
|
|
682
|
+
env: process.env,
|
|
683
|
+
detached
|
|
609
684
|
});
|
|
610
685
|
|
|
686
|
+
if (detached) {
|
|
687
|
+
child.unref();
|
|
688
|
+
}
|
|
689
|
+
|
|
611
690
|
child.on('error', (error) => {
|
|
612
691
|
if (outputMode === 'text') {
|
|
613
692
|
process.stderr.write(`Auth server failed to start: ${error.message}\n`);
|
|
@@ -617,6 +696,53 @@ function startAuthServer(outputMode) {
|
|
|
617
696
|
return child;
|
|
618
697
|
}
|
|
619
698
|
|
|
699
|
+
async function handleAuthServerCommand(options, outputMode) {
|
|
700
|
+
const shouldStart = asBoolean(readOption(options, 'start', false), false);
|
|
701
|
+
const shouldStatus = asBoolean(readOption(options, 'status', !shouldStart), !shouldStart);
|
|
702
|
+
|
|
703
|
+
if (!shouldStart && !shouldStatus) {
|
|
704
|
+
throw new UsageError(`Usage: ${cliCommand('auth server [--status] [--start]')}`);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
if (shouldStart) {
|
|
708
|
+
const isAlreadyRunning = await httpProbe(config.AUTH_CONFIG.authServerUrl);
|
|
709
|
+
if (!isAlreadyRunning) {
|
|
710
|
+
startAuthServer(outputMode, { detached: true });
|
|
711
|
+
const ready = await waitForProbe(config.AUTH_CONFIG.authServerUrl, 10000);
|
|
712
|
+
if (!ready) {
|
|
713
|
+
throw new CliError('Auth server did not become ready on time.', 1);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
printSuccess(outputMode, {
|
|
718
|
+
ok: true,
|
|
719
|
+
command: 'auth server --start',
|
|
720
|
+
data: {
|
|
721
|
+
authServerUrl: config.AUTH_CONFIG.authServerUrl,
|
|
722
|
+
alreadyRunning: isAlreadyRunning
|
|
723
|
+
},
|
|
724
|
+
message: isAlreadyRunning
|
|
725
|
+
? `Auth server already running at ${config.AUTH_CONFIG.authServerUrl}.`
|
|
726
|
+
: `Auth server started at ${config.AUTH_CONFIG.authServerUrl}.`
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
const reachable = await httpProbe(config.AUTH_CONFIG.authServerUrl);
|
|
733
|
+
printSuccess(outputMode, {
|
|
734
|
+
ok: true,
|
|
735
|
+
command: 'auth server --status',
|
|
736
|
+
data: {
|
|
737
|
+
authServerUrl: config.AUTH_CONFIG.authServerUrl,
|
|
738
|
+
running: reachable
|
|
739
|
+
},
|
|
740
|
+
message: reachable
|
|
741
|
+
? `Auth server is reachable at ${config.AUTH_CONFIG.authServerUrl}.`
|
|
742
|
+
: `Auth server is not reachable at ${config.AUTH_CONFIG.authServerUrl}.`
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
|
|
620
746
|
async function waitForValidToken(timeoutMs, onTick) {
|
|
621
747
|
const start = Date.now();
|
|
622
748
|
let attempts = 0;
|
|
@@ -665,6 +791,11 @@ async function handleAuthCommand(action, options, outputMode) {
|
|
|
665
791
|
}
|
|
666
792
|
|
|
667
793
|
case 'url': {
|
|
794
|
+
const providedClientId = readOption(options, 'clientId');
|
|
795
|
+
if (providedClientId) {
|
|
796
|
+
applyRuntimeAuthConfig({ clientId: String(providedClientId).trim() });
|
|
797
|
+
}
|
|
798
|
+
|
|
668
799
|
const authUrl = getAuthUrl();
|
|
669
800
|
printSuccess(outputMode, {
|
|
670
801
|
ok: true,
|
|
@@ -687,6 +818,8 @@ async function handleAuthCommand(action, options, outputMode) {
|
|
|
687
818
|
}
|
|
688
819
|
|
|
689
820
|
case 'login': {
|
|
821
|
+
await ensureAuthCredentials(options, outputMode);
|
|
822
|
+
|
|
690
823
|
const force = asBoolean(readOption(options, 'force', false));
|
|
691
824
|
const shouldOpen = asBoolean(readOption(options, 'open', true), true);
|
|
692
825
|
const shouldStartServer = asBoolean(readOption(options, 'startServer', true), true);
|
|
@@ -718,7 +851,7 @@ async function handleAuthCommand(action, options, outputMode) {
|
|
|
718
851
|
const runningBefore = await httpProbe(config.AUTH_CONFIG.authServerUrl);
|
|
719
852
|
if (!runningBefore && shouldStartServer) {
|
|
720
853
|
startSpinner = createSpinner('Starting auth server...', outputMode).start();
|
|
721
|
-
startedServer = startAuthServer(outputMode);
|
|
854
|
+
startedServer = startAuthServer(outputMode, { detached: false });
|
|
722
855
|
const ready = await waitForProbe(
|
|
723
856
|
config.AUTH_CONFIG.authServerUrl,
|
|
724
857
|
10000,
|
|
@@ -812,8 +945,13 @@ async function handleAuthCommand(action, options, outputMode) {
|
|
|
812
945
|
return;
|
|
813
946
|
}
|
|
814
947
|
|
|
948
|
+
case 'server': {
|
|
949
|
+
await handleAuthServerCommand(options, outputMode);
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
|
|
815
953
|
default:
|
|
816
|
-
throw new UsageError('Unknown auth command. Use: auth status|url|login|logout');
|
|
954
|
+
throw new UsageError('Unknown auth command. Use: auth status|url|login|logout|server');
|
|
817
955
|
}
|
|
818
956
|
}
|
|
819
957
|
|
package/package.json
CHANGED
package/rules/index.js
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Email rules management module for Outlook MCP server
|
|
3
3
|
*/
|
|
4
|
-
const handleListRules = require('./list');
|
|
4
|
+
const { handleListRules, getInboxRules } = require('./list');
|
|
5
5
|
const handleCreateRule = require('./create');
|
|
6
6
|
const { ensureAuthenticated } = require('../auth');
|
|
7
7
|
const { callGraphAPI } = require('../utils/graph-api');
|
|
8
8
|
|
|
9
|
-
// Import getInboxRules for the edit sequence tool
|
|
10
|
-
const { getInboxRules } = require('./list');
|
|
11
|
-
|
|
12
9
|
/**
|
|
13
10
|
* Edit rule sequence handler
|
|
14
11
|
* @param {object} args - Tool arguments
|