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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "outlook-cli",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Production-ready Outlook CLI with optional MCP server mode powered by Microsoft Graph API",
5
5
  "keywords": [
6
6
  "outlook-cli",
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