outlook-cli 1.2.1 → 1.2.2
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/CLI.md +14 -0
- package/README.md +38 -1
- package/cli.js +450 -69
- package/docs/REFERENCE.md +40 -2
- package/email/search.js +18 -12
- package/package.json +1 -1
- package/utils/graph-api.js +11 -5
package/CLI.md
CHANGED
|
@@ -13,7 +13,9 @@ outlook-cli --help
|
|
|
13
13
|
## Global options
|
|
14
14
|
|
|
15
15
|
- `--json`
|
|
16
|
+
- `--ai`
|
|
16
17
|
- `--output text|json`
|
|
18
|
+
- `--theme k9s|ocean|mono`
|
|
17
19
|
- `--plain`
|
|
18
20
|
- `--no-color`
|
|
19
21
|
- `--no-animate`
|
|
@@ -34,9 +36,20 @@ outlook-cli tools schema send-email
|
|
|
34
36
|
outlook-cli auth status
|
|
35
37
|
outlook-cli auth url
|
|
36
38
|
outlook-cli auth login --open --start-server --wait --timeout 180
|
|
39
|
+
outlook-cli auth login --open --client-id <id> --client-secret <secret>
|
|
40
|
+
outlook-cli auth server --start
|
|
41
|
+
outlook-cli auth server --status
|
|
37
42
|
outlook-cli auth logout
|
|
38
43
|
```
|
|
39
44
|
|
|
45
|
+
## AI Agents
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
outlook-cli agents guide
|
|
49
|
+
outlook-cli tools list --ai
|
|
50
|
+
outlook-cli call list-emails --args-json '{"folder":"inbox","count":5}' --ai
|
|
51
|
+
```
|
|
52
|
+
|
|
40
53
|
## Email
|
|
41
54
|
|
|
42
55
|
```bash
|
|
@@ -86,4 +99,5 @@ outlook-cli call create-event --args-json '{"subject":"Standup","start":"2026-04
|
|
|
86
99
|
outlook-cli doctor
|
|
87
100
|
outlook-cli update
|
|
88
101
|
outlook-cli update --run
|
|
102
|
+
outlook-cli mcp-server
|
|
89
103
|
```
|
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ Production-ready CLI and MCP server for Microsoft Outlook through Microsoft Grap
|
|
|
10
10
|
|
|
11
11
|
- Global CLI command: `outlook-cli`
|
|
12
12
|
- One-time OAuth token storage — reused across runs and updates
|
|
13
|
-
- Human-friendly commands with rich terminal output,
|
|
13
|
+
- Human-friendly commands with rich terminal output, theme presets, and machine-friendly `--json`/`--ai` mode
|
|
14
14
|
- 19 MCP tools available to Claude and other AI assistants via the shared tool registry
|
|
15
15
|
- Full email, calendar, folder, and rules management through Microsoft Graph API
|
|
16
16
|
|
|
@@ -139,12 +139,16 @@ outlook-cli calendar list # list upcoming events
|
|
|
139
139
|
| `auth status` | Show current authentication status | `outlook-cli auth status` |
|
|
140
140
|
| `auth login` | Start authentication flow | `outlook-cli auth login --open --start-server --wait` |
|
|
141
141
|
| `auth url` | Show the OAuth URL without opening browser | `outlook-cli auth url` |
|
|
142
|
+
| `auth server` | Check/start local OAuth callback server | `outlook-cli auth server --start` |
|
|
142
143
|
| `auth logout` | Clear stored tokens | `outlook-cli auth logout` |
|
|
143
144
|
|
|
144
145
|
**Flags for `auth login`:**
|
|
145
146
|
- `--open` — Automatically open the auth URL in your browser
|
|
146
147
|
- `--start-server` — Start the OAuth callback server automatically
|
|
147
148
|
- `--wait` — Wait for authentication to complete before returning
|
|
149
|
+
- `--client-id` — Provide Application (client) ID at runtime (optional)
|
|
150
|
+
- `--client-secret` — Provide client secret value at runtime (optional)
|
|
151
|
+
- `--prompt-credentials` — Prompt for missing credentials in interactive terminals
|
|
148
152
|
|
|
149
153
|
**Examples:**
|
|
150
154
|
```bash
|
|
@@ -159,6 +163,13 @@ outlook-cli auth status
|
|
|
159
163
|
|
|
160
164
|
# Force re-authentication
|
|
161
165
|
outlook-cli auth login --open --force
|
|
166
|
+
|
|
167
|
+
# Runtime credentials (useful outside repo where .env is not loaded)
|
|
168
|
+
outlook-cli auth login --open --client-id <id> --client-secret <secret>
|
|
169
|
+
|
|
170
|
+
# Start or check auth callback server
|
|
171
|
+
outlook-cli auth server --start
|
|
172
|
+
outlook-cli auth server --status
|
|
162
173
|
```
|
|
163
174
|
|
|
164
175
|
---
|
|
@@ -750,6 +761,24 @@ outlook-cli call list-events --args-json '{"count":10}' --json
|
|
|
750
761
|
|
|
751
762
|
---
|
|
752
763
|
|
|
764
|
+
### `agents` — AI Agent Guide
|
|
765
|
+
|
|
766
|
+
Shows best-practice command flow for AI agents (Claude, Codex, VS Code, automation scripts).
|
|
767
|
+
|
|
768
|
+
```bash
|
|
769
|
+
outlook-cli agents guide
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
**Agent workflow (recommended):**
|
|
773
|
+
```bash
|
|
774
|
+
outlook-cli auth status --json
|
|
775
|
+
outlook-cli tools list --json
|
|
776
|
+
outlook-cli tools schema send-email --json
|
|
777
|
+
outlook-cli call list-emails --args-json '{"folder":"inbox","count":5}' --ai
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
---
|
|
781
|
+
|
|
753
782
|
### `doctor` — Diagnostics
|
|
754
783
|
|
|
755
784
|
Runs a series of diagnostic checks and reports what's working and what needs fixing.
|
|
@@ -784,14 +813,22 @@ npm i -g outlook-cli@latest
|
|
|
784
813
|
| Flag | Description |
|
|
785
814
|
|---|---|
|
|
786
815
|
| `--json` | Output raw JSON instead of human-readable text |
|
|
816
|
+
| `--ai` | Agent-safe alias for JSON mode (suppresses rich UI output) |
|
|
817
|
+
| `--theme k9s|ocean|mono` | Select color theme for text output |
|
|
787
818
|
| `--plain` | No colors or formatting |
|
|
788
819
|
| `--no-color` | Disable color only |
|
|
789
820
|
| `--no-animate` | Disable spinner animations |
|
|
790
821
|
|
|
822
|
+
In JSON mode, responses include both:
|
|
823
|
+
- `result` (original MCP tool payload)
|
|
824
|
+
- `structured` (normalized machine-friendly fields like `summary`, `items`, and parsed metadata)
|
|
825
|
+
|
|
791
826
|
**Examples:**
|
|
792
827
|
```bash
|
|
793
828
|
outlook-cli auth status --json
|
|
829
|
+
outlook-cli tools list --ai
|
|
794
830
|
outlook-cli email list --count 10 --json
|
|
831
|
+
outlook-cli --theme ocean --help
|
|
795
832
|
outlook-cli calendar list --plain
|
|
796
833
|
```
|
|
797
834
|
|
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
|
*
|
|
@@ -22,10 +22,51 @@ const { listTools, getTool, invokeTool } = require('./tool-registry');
|
|
|
22
22
|
|
|
23
23
|
const CLI_BIN_NAME = 'outlook-cli';
|
|
24
24
|
const SPINNER_FRAMES = ['|', '/', '-', '\\'];
|
|
25
|
+
const THEMES = Object.freeze({
|
|
26
|
+
k9s: {
|
|
27
|
+
title: ['bold', 'cyanBright'],
|
|
28
|
+
section: ['bold', 'greenBright'],
|
|
29
|
+
ok: ['bold', 'greenBright'],
|
|
30
|
+
warn: ['bold', 'yellowBright'],
|
|
31
|
+
err: ['bold', 'redBright'],
|
|
32
|
+
muted: ['gray'],
|
|
33
|
+
accent: ['bold', 'cyan'],
|
|
34
|
+
key: ['bold', 'whiteBright'],
|
|
35
|
+
value: ['cyan'],
|
|
36
|
+
border: ['cyanBright']
|
|
37
|
+
},
|
|
38
|
+
ocean: {
|
|
39
|
+
title: ['bold', 'blueBright'],
|
|
40
|
+
section: ['bold', 'cyanBright'],
|
|
41
|
+
ok: ['bold', 'green'],
|
|
42
|
+
warn: ['bold', 'yellow'],
|
|
43
|
+
err: ['bold', 'redBright'],
|
|
44
|
+
muted: ['gray'],
|
|
45
|
+
accent: ['bold', 'magentaBright'],
|
|
46
|
+
key: ['bold', 'white'],
|
|
47
|
+
value: ['blueBright'],
|
|
48
|
+
border: ['blue']
|
|
49
|
+
},
|
|
50
|
+
mono: {
|
|
51
|
+
title: ['bold', 'white'],
|
|
52
|
+
section: ['bold', 'white'],
|
|
53
|
+
ok: ['bold', 'white'],
|
|
54
|
+
warn: ['bold', 'white'],
|
|
55
|
+
err: ['bold', 'white'],
|
|
56
|
+
muted: ['gray'],
|
|
57
|
+
accent: ['bold', 'white'],
|
|
58
|
+
key: ['bold', 'white'],
|
|
59
|
+
value: ['white'],
|
|
60
|
+
border: ['gray']
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
const DEFAULT_THEME_NAME = 'k9s';
|
|
25
64
|
const UI_STATE = {
|
|
26
65
|
plain: false,
|
|
27
66
|
color: false,
|
|
28
|
-
animate: false
|
|
67
|
+
animate: false,
|
|
68
|
+
themeName: DEFAULT_THEME_NAME,
|
|
69
|
+
theme: THEMES[DEFAULT_THEME_NAME]
|
|
29
70
|
};
|
|
30
71
|
|
|
31
72
|
function cliCommand(pathTail = '') {
|
|
@@ -207,42 +248,75 @@ function asCsv(value) {
|
|
|
207
248
|
}
|
|
208
249
|
|
|
209
250
|
function setUiState(options, outputMode) {
|
|
251
|
+
const aiMode = asBoolean(readOption(options, 'ai', false), false);
|
|
210
252
|
const plain = asBoolean(readOption(options, 'plain', false), false);
|
|
211
253
|
const colorOption = readOption(options, 'color');
|
|
212
254
|
const animateOption = readOption(options, 'animate');
|
|
255
|
+
const requestedTheme = String(readOption(
|
|
256
|
+
options,
|
|
257
|
+
'theme',
|
|
258
|
+
process.env.OUTLOOK_CLI_THEME || DEFAULT_THEME_NAME
|
|
259
|
+
)).trim().toLowerCase();
|
|
260
|
+
const themeName = THEMES[requestedTheme] ? requestedTheme : DEFAULT_THEME_NAME;
|
|
213
261
|
const isTextMode = outputMode === 'text';
|
|
214
262
|
const isInteractive = isTextMode && process.stdout.isTTY;
|
|
215
263
|
|
|
216
|
-
UI_STATE.plain = plain;
|
|
217
|
-
UI_STATE.color = isInteractive && !plain && colorOption !== false;
|
|
218
|
-
UI_STATE.animate = isInteractive && !plain && animateOption !== false;
|
|
264
|
+
UI_STATE.plain = plain || aiMode;
|
|
265
|
+
UI_STATE.color = isInteractive && !UI_STATE.plain && colorOption !== false;
|
|
266
|
+
UI_STATE.animate = isInteractive && !UI_STATE.plain && animateOption !== false;
|
|
267
|
+
UI_STATE.themeName = themeName;
|
|
268
|
+
UI_STATE.theme = THEMES[themeName];
|
|
219
269
|
|
|
220
270
|
chalk.level = UI_STATE.color ? Math.max(chalk.level, 1) : 0;
|
|
221
271
|
}
|
|
222
272
|
|
|
223
|
-
function
|
|
224
|
-
if (!UI_STATE.color) {
|
|
273
|
+
function applyThemeStyle(text, stylePath) {
|
|
274
|
+
if (!UI_STATE.color || !stylePath) {
|
|
225
275
|
return text;
|
|
226
276
|
}
|
|
227
277
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
case 'ok':
|
|
234
|
-
return chalk.green(text);
|
|
235
|
-
case 'warn':
|
|
236
|
-
return chalk.yellow(text);
|
|
237
|
-
case 'err':
|
|
238
|
-
return chalk.red(text);
|
|
239
|
-
case 'muted':
|
|
240
|
-
return chalk.gray(text);
|
|
241
|
-
case 'accent':
|
|
242
|
-
return chalk.magenta(text);
|
|
243
|
-
default:
|
|
278
|
+
const chain = Array.isArray(stylePath) ? stylePath : [stylePath];
|
|
279
|
+
let painter = chalk;
|
|
280
|
+
|
|
281
|
+
for (const segment of chain) {
|
|
282
|
+
if (!(segment in painter)) {
|
|
244
283
|
return text;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
painter = painter[segment];
|
|
245
287
|
}
|
|
288
|
+
|
|
289
|
+
return painter(text);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function terminalWidth() {
|
|
293
|
+
const raw = Number(process.stdout.columns || 100);
|
|
294
|
+
if (!Number.isFinite(raw)) {
|
|
295
|
+
return 100;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return Math.max(72, Math.min(120, raw));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function padRight(value, width) {
|
|
302
|
+
const text = String(value);
|
|
303
|
+
if (text.length >= width) {
|
|
304
|
+
return text;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return `${text}${' '.repeat(width - text.length)}`;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function separator(width = terminalWidth()) {
|
|
311
|
+
return '-'.repeat(Math.max(24, width));
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function formatRow(left, right, leftWidth = 34) {
|
|
315
|
+
return ` ${tone(padRight(left, leftWidth), 'key')} ${tone(right, 'muted')}`;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function tone(text, kind) {
|
|
319
|
+
return applyThemeStyle(text, UI_STATE.theme[kind]);
|
|
246
320
|
}
|
|
247
321
|
|
|
248
322
|
function badge(kind) {
|
|
@@ -317,9 +391,242 @@ function formatResultText(result) {
|
|
|
317
391
|
return JSON.stringify(result, null, 2);
|
|
318
392
|
}
|
|
319
393
|
|
|
394
|
+
function normalizeLineEndings(value) {
|
|
395
|
+
return String(value || '').replace(/\r\n/g, '\n').trim();
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function extractTextChunksFromResult(result) {
|
|
399
|
+
if (!result || !Array.isArray(result.content)) {
|
|
400
|
+
return [];
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return result.content
|
|
404
|
+
.filter((entry) => entry && entry.type === 'text' && typeof entry.text === 'string')
|
|
405
|
+
.map((entry) => normalizeLineEndings(entry.text))
|
|
406
|
+
.filter(Boolean);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function firstNonEmptyLine(text) {
|
|
410
|
+
const line = normalizeLineEndings(text)
|
|
411
|
+
.split('\n')
|
|
412
|
+
.map((entry) => entry.trim())
|
|
413
|
+
.find(Boolean);
|
|
414
|
+
|
|
415
|
+
return line || '';
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function parseNumberedBlocks(text) {
|
|
419
|
+
const normalized = normalizeLineEndings(text);
|
|
420
|
+
const matches = normalized.match(/\d+\.\s[\s\S]*?(?=(?:\n\d+\.\s)|$)/g);
|
|
421
|
+
if (!matches) {
|
|
422
|
+
return [];
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return matches.map((entry) => entry.trim()).filter(Boolean);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function parseEmailListFromText(text) {
|
|
429
|
+
const normalized = normalizeLineEndings(text);
|
|
430
|
+
const body = normalized.replace(/^Found[^\n]*:\n\n?/i, '');
|
|
431
|
+
const blocks = parseNumberedBlocks(body);
|
|
432
|
+
|
|
433
|
+
return blocks.map((block) => {
|
|
434
|
+
const lines = block.split('\n').map((line) => line.trim()).filter(Boolean);
|
|
435
|
+
const headline = lines[0] || '';
|
|
436
|
+
const subjectLine = lines.find((line) => line.startsWith('Subject:')) || '';
|
|
437
|
+
const idLine = lines.find((line) => line.startsWith('ID:')) || '';
|
|
438
|
+
const match = headline.match(/^(\d+)\.\s(?:\[UNREAD\]\s)?(.+?)\s-\sFrom:\s(.+?)\s\((.+)\)$/);
|
|
439
|
+
|
|
440
|
+
return {
|
|
441
|
+
index: match ? Number(match[1]) : null,
|
|
442
|
+
unread: headline.includes('[UNREAD]'),
|
|
443
|
+
receivedAt: match ? match[2] : null,
|
|
444
|
+
from: match ? { name: match[3], email: match[4] } : null,
|
|
445
|
+
subject: subjectLine ? subjectLine.replace(/^Subject:\s*/, '') : null,
|
|
446
|
+
id: idLine ? idLine.replace(/^ID:\s*/, '') : null,
|
|
447
|
+
raw: block
|
|
448
|
+
};
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function parseEventListFromText(text) {
|
|
453
|
+
const normalized = normalizeLineEndings(text);
|
|
454
|
+
const body = normalized.replace(/^Found[^\n]*:\n\n?/i, '');
|
|
455
|
+
const blocks = parseNumberedBlocks(body);
|
|
456
|
+
|
|
457
|
+
return blocks.map((block) => {
|
|
458
|
+
const lines = block.split('\n').map((line) => line.trim()).filter(Boolean);
|
|
459
|
+
const headline = lines[0] || '';
|
|
460
|
+
const startLine = lines.find((line) => line.startsWith('Start:')) || '';
|
|
461
|
+
const endLine = lines.find((line) => line.startsWith('End:')) || '';
|
|
462
|
+
const summaryLine = lines.find((line) => line.startsWith('Summary:')) || '';
|
|
463
|
+
const idLine = lines.find((line) => line.startsWith('ID:')) || '';
|
|
464
|
+
const match = headline.match(/^(\d+)\.\s(.+?)\s-\sLocation:\s(.+)$/);
|
|
465
|
+
|
|
466
|
+
return {
|
|
467
|
+
index: match ? Number(match[1]) : null,
|
|
468
|
+
subject: match ? match[2] : null,
|
|
469
|
+
location: match ? match[3] : null,
|
|
470
|
+
start: startLine ? startLine.replace(/^Start:\s*/, '') : null,
|
|
471
|
+
end: endLine ? endLine.replace(/^End:\s*/, '') : null,
|
|
472
|
+
summary: summaryLine ? summaryLine.replace(/^Summary:\s*/, '') : null,
|
|
473
|
+
id: idLine ? idLine.replace(/^ID:\s*/, '') : null,
|
|
474
|
+
raw: block
|
|
475
|
+
};
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function parseFolderListFromText(text) {
|
|
480
|
+
const normalized = normalizeLineEndings(text);
|
|
481
|
+
if (normalized.startsWith('Folder Hierarchy:')) {
|
|
482
|
+
const lines = normalized
|
|
483
|
+
.replace(/^Folder Hierarchy:\n\n?/i, '')
|
|
484
|
+
.split('\n')
|
|
485
|
+
.filter(Boolean);
|
|
486
|
+
|
|
487
|
+
return lines.map((line) => {
|
|
488
|
+
const leadingSpaces = line.match(/^\s*/);
|
|
489
|
+
const level = leadingSpaces ? Math.floor(leadingSpaces[0].length / 2) : 0;
|
|
490
|
+
return {
|
|
491
|
+
level,
|
|
492
|
+
name: line.trim(),
|
|
493
|
+
raw: line
|
|
494
|
+
};
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const body = normalized.replace(/^Found[^\n]*:\n\n?/i, '');
|
|
499
|
+
return body
|
|
500
|
+
.split('\n')
|
|
501
|
+
.map((line) => line.trim())
|
|
502
|
+
.filter(Boolean)
|
|
503
|
+
.map((line, index) => ({
|
|
504
|
+
index: index + 1,
|
|
505
|
+
name: line,
|
|
506
|
+
raw: line
|
|
507
|
+
}));
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function parseRuleListFromText(text) {
|
|
511
|
+
const normalized = normalizeLineEndings(text);
|
|
512
|
+
const body = normalized.replace(/^Found[^\n]*:\n\n?/i, '');
|
|
513
|
+
const blocks = parseNumberedBlocks(body);
|
|
514
|
+
|
|
515
|
+
return blocks.map((block) => {
|
|
516
|
+
const lines = block.split('\n').map((line) => line.trim()).filter(Boolean);
|
|
517
|
+
const headline = lines[0] || '';
|
|
518
|
+
const conditionsLine = lines.find((line) => line.startsWith('Conditions:')) || '';
|
|
519
|
+
const actionsLine = lines.find((line) => line.startsWith('Actions:')) || '';
|
|
520
|
+
const match = headline.match(/^(\d+)\.\s(.+?)(?:\s-\sSequence:\s(.+))?$/);
|
|
521
|
+
|
|
522
|
+
return {
|
|
523
|
+
index: match ? Number(match[1]) : null,
|
|
524
|
+
name: match ? match[2] : null,
|
|
525
|
+
sequence: match && match[3] ? match[3] : null,
|
|
526
|
+
conditions: conditionsLine ? conditionsLine.replace(/^Conditions:\s*/, '') : null,
|
|
527
|
+
actions: actionsLine ? actionsLine.replace(/^Actions:\s*/, '') : null,
|
|
528
|
+
raw: block
|
|
529
|
+
};
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function parseReadEmailFromText(text) {
|
|
534
|
+
const normalized = normalizeLineEndings(text);
|
|
535
|
+
const sections = normalized.split(/\n\n+/);
|
|
536
|
+
const headerSection = sections.shift() || '';
|
|
537
|
+
const headerLines = headerSection.split('\n').map((line) => line.trim()).filter(Boolean);
|
|
538
|
+
const headers = {};
|
|
539
|
+
|
|
540
|
+
headerLines.forEach((line) => {
|
|
541
|
+
const index = line.indexOf(':');
|
|
542
|
+
if (index > 0) {
|
|
543
|
+
const key = line.slice(0, index).trim();
|
|
544
|
+
const value = line.slice(index + 1).trim();
|
|
545
|
+
headers[key] = value;
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
return {
|
|
550
|
+
headers,
|
|
551
|
+
body: sections.join('\n\n').trim(),
|
|
552
|
+
raw: normalized
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function normalizeToolResult(toolName, args, result) {
|
|
557
|
+
const textChunks = extractTextChunksFromResult(result);
|
|
558
|
+
const text = textChunks.join('\n\n');
|
|
559
|
+
const normalized = {
|
|
560
|
+
tool: toolName,
|
|
561
|
+
args: args || {},
|
|
562
|
+
summary: firstNonEmptyLine(text),
|
|
563
|
+
textChunks
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
switch (toolName) {
|
|
567
|
+
case 'list-emails':
|
|
568
|
+
case 'search-emails':
|
|
569
|
+
normalized.kind = 'email-list';
|
|
570
|
+
normalized.items = parseEmailListFromText(text);
|
|
571
|
+
normalized.count = normalized.items.length;
|
|
572
|
+
return normalized;
|
|
573
|
+
|
|
574
|
+
case 'list-events':
|
|
575
|
+
normalized.kind = 'event-list';
|
|
576
|
+
normalized.items = parseEventListFromText(text);
|
|
577
|
+
normalized.count = normalized.items.length;
|
|
578
|
+
return normalized;
|
|
579
|
+
|
|
580
|
+
case 'list-folders':
|
|
581
|
+
normalized.kind = 'folder-list';
|
|
582
|
+
normalized.items = parseFolderListFromText(text);
|
|
583
|
+
normalized.count = normalized.items.length;
|
|
584
|
+
return normalized;
|
|
585
|
+
|
|
586
|
+
case 'list-rules':
|
|
587
|
+
normalized.kind = 'rule-list';
|
|
588
|
+
normalized.items = parseRuleListFromText(text);
|
|
589
|
+
normalized.count = normalized.items.length;
|
|
590
|
+
return normalized;
|
|
591
|
+
|
|
592
|
+
case 'read-email':
|
|
593
|
+
normalized.kind = 'email-detail';
|
|
594
|
+
normalized.record = parseReadEmailFromText(text);
|
|
595
|
+
return normalized;
|
|
596
|
+
|
|
597
|
+
default:
|
|
598
|
+
normalized.kind = 'text';
|
|
599
|
+
normalized.lines = normalizeLineEndings(text).split('\n').filter(Boolean);
|
|
600
|
+
return normalized;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function buildStructuredPayload(payload) {
|
|
605
|
+
const response = { ...payload };
|
|
606
|
+
|
|
607
|
+
if (payload.tool && payload.result) {
|
|
608
|
+
response.structured = normalizeToolResult(payload.tool, payload.args, payload.result);
|
|
609
|
+
return response;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
response.structured = {
|
|
613
|
+
command: payload.command || null,
|
|
614
|
+
summary: typeof payload.message === 'string' ? payload.message : null,
|
|
615
|
+
data: payload.data || null
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
return response;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
function printTextBlock(title, body) {
|
|
622
|
+
process.stdout.write(`${tone(title, 'section')}\n`);
|
|
623
|
+
process.stdout.write(`${tone(separator(32), 'border')}\n`);
|
|
624
|
+
process.stdout.write(`${body}\n`);
|
|
625
|
+
}
|
|
626
|
+
|
|
320
627
|
function printSuccess(outputMode, payload) {
|
|
321
628
|
if (outputMode === 'json') {
|
|
322
|
-
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
629
|
+
process.stdout.write(`${JSON.stringify(buildStructuredPayload(payload), null, 2)}\n`);
|
|
323
630
|
return;
|
|
324
631
|
}
|
|
325
632
|
|
|
@@ -328,11 +635,11 @@ function printSuccess(outputMode, payload) {
|
|
|
328
635
|
}
|
|
329
636
|
|
|
330
637
|
if (payload.result) {
|
|
331
|
-
|
|
638
|
+
printTextBlock('Result', formatResultText(payload.result));
|
|
332
639
|
}
|
|
333
640
|
|
|
334
641
|
if (payload.data && !payload.result) {
|
|
335
|
-
|
|
642
|
+
printTextBlock('Details', JSON.stringify(payload.data, null, 2));
|
|
336
643
|
}
|
|
337
644
|
}
|
|
338
645
|
|
|
@@ -351,11 +658,16 @@ function printError(outputMode, error) {
|
|
|
351
658
|
|
|
352
659
|
process.stderr.write(`${badge('err')} ${error.message}\n`);
|
|
353
660
|
if (error instanceof UsageError) {
|
|
354
|
-
process.stderr.write(`${tone(
|
|
661
|
+
process.stderr.write(`${tone(separator(42), 'border')}\n`);
|
|
662
|
+
process.stderr.write(`${tone(formatRow('help', `Run '${cliCommand('help')}' to see valid usage.`, 14), 'muted')}\n`);
|
|
355
663
|
}
|
|
356
664
|
}
|
|
357
665
|
|
|
358
666
|
function buildOutputMode(options) {
|
|
667
|
+
if (asBoolean(readOption(options, 'ai', false), false)) {
|
|
668
|
+
return 'json';
|
|
669
|
+
}
|
|
670
|
+
|
|
359
671
|
const outputOption = readOption(options, 'output');
|
|
360
672
|
if (outputOption === 'json') {
|
|
361
673
|
return 'json';
|
|
@@ -422,43 +734,49 @@ async function callTool(toolName, args, outputMode, commandLabel) {
|
|
|
422
734
|
function printUsage() {
|
|
423
735
|
const usage = [
|
|
424
736
|
`${tone(CLI_BIN_NAME, 'title')} ${tone(`v${pkg.version}`, 'muted')}`,
|
|
737
|
+
tone(`Theme: ${UI_STATE.themeName} | AI mode: --ai`, 'muted'),
|
|
738
|
+
tone(separator(), 'border'),
|
|
425
739
|
'',
|
|
426
|
-
tone('
|
|
427
|
-
`
|
|
740
|
+
tone('Install', 'section'),
|
|
741
|
+
formatRow('global', `npm i -g ${pkg.name}`, 18),
|
|
742
|
+
formatRow('local', 'npm install', 18),
|
|
428
743
|
'',
|
|
429
|
-
tone('Usage
|
|
430
|
-
` ${cliCommand('<command> [subcommand] [options]')}`,
|
|
744
|
+
tone('Usage', 'section'),
|
|
745
|
+
` ${tone(cliCommand('<command> [subcommand] [options]'), 'accent')}`,
|
|
431
746
|
'',
|
|
432
|
-
tone('Global options
|
|
433
|
-
'
|
|
434
|
-
'
|
|
435
|
-
'
|
|
436
|
-
'
|
|
437
|
-
'
|
|
438
|
-
'
|
|
439
|
-
'
|
|
747
|
+
tone('Global options', 'section'),
|
|
748
|
+
formatRow('--json', 'Machine-readable JSON output'),
|
|
749
|
+
formatRow('--ai', 'Agent-safe alias for JSON mode (no spinners/colors)'),
|
|
750
|
+
formatRow('--output text|json', 'Explicit output mode'),
|
|
751
|
+
formatRow('--theme k9s|ocean|mono', 'Theme preset for text output'),
|
|
752
|
+
formatRow('--plain', 'Disable rich styling and animations'),
|
|
753
|
+
formatRow('--no-color', 'Disable colors explicitly'),
|
|
754
|
+
formatRow('--no-animate', 'Disable spinner animations'),
|
|
755
|
+
formatRow('--help', 'Show help'),
|
|
756
|
+
formatRow('--version', 'Show version'),
|
|
440
757
|
'',
|
|
441
|
-
tone('Core commands
|
|
442
|
-
'
|
|
443
|
-
'
|
|
444
|
-
'
|
|
445
|
-
'
|
|
446
|
-
'
|
|
447
|
-
'
|
|
448
|
-
'
|
|
449
|
-
'
|
|
450
|
-
'
|
|
758
|
+
tone('Core commands', 'section'),
|
|
759
|
+
formatRow('commands', 'Show command groups and available tools'),
|
|
760
|
+
formatRow('tools list', 'List all tool names and descriptions'),
|
|
761
|
+
formatRow('tools schema <tool-name>', 'Show JSON schema for one tool'),
|
|
762
|
+
formatRow('call <tool-name> [--args-json] [--arg]', 'Invoke any tool directly'),
|
|
763
|
+
formatRow('auth status|url|login|logout|server', 'Authentication and OAuth server control'),
|
|
764
|
+
formatRow('agents guide', 'AI agent quick-start and orchestration tips'),
|
|
765
|
+
formatRow('doctor', 'Run diagnostics and environment checks'),
|
|
766
|
+
formatRow('update [--run] [--to latest|x.y.z]', 'Check or apply global update'),
|
|
767
|
+
formatRow('mcp-server', 'Run stdio MCP server (for Claude/Codex/VS Code)'),
|
|
451
768
|
'',
|
|
452
|
-
tone('
|
|
453
|
-
'
|
|
454
|
-
'
|
|
455
|
-
'
|
|
456
|
-
'
|
|
769
|
+
tone('Command groups', 'section'),
|
|
770
|
+
formatRow('email', 'list|search|read|send|mark-read'),
|
|
771
|
+
formatRow('calendar', 'list|create|decline|cancel|delete'),
|
|
772
|
+
formatRow('folder', 'list|create|move'),
|
|
773
|
+
formatRow('rule', 'list|create|sequence'),
|
|
457
774
|
'',
|
|
458
|
-
tone('Examples
|
|
775
|
+
tone('Examples', 'section'),
|
|
459
776
|
` ${cliCommand('auth login --open --start-server --wait --timeout 180')}`,
|
|
460
|
-
` ${cliCommand('
|
|
461
|
-
` ${cliCommand('
|
|
777
|
+
` ${cliCommand('auth login --open --client-id <id> --client-secret <secret>')}`,
|
|
778
|
+
` ${cliCommand('agents guide --json')}`,
|
|
779
|
+
` ${cliCommand("call list-emails --args-json '{\"folder\":\"inbox\",\"count\":5}' --ai")}`,
|
|
462
780
|
` ${cliCommand('tools schema send-email')}`,
|
|
463
781
|
` ${cliCommand('update --run')}`
|
|
464
782
|
];
|
|
@@ -553,9 +871,10 @@ function buildCommandCatalog() {
|
|
|
553
871
|
calendar: ['list', 'create', 'decline', 'cancel', 'delete'],
|
|
554
872
|
folder: ['list', 'create', 'move'],
|
|
555
873
|
rule: ['list', 'create', 'sequence'],
|
|
874
|
+
agents: ['guide'],
|
|
556
875
|
tools: ['list', 'schema'],
|
|
557
876
|
generic: ['call'],
|
|
558
|
-
system: ['doctor', 'update', 'version', 'help']
|
|
877
|
+
system: ['doctor', 'update', 'version', 'help', 'mcp-server']
|
|
559
878
|
},
|
|
560
879
|
tools: toolCatalog
|
|
561
880
|
};
|
|
@@ -570,18 +889,21 @@ function printCommandCatalog(outputMode) {
|
|
|
570
889
|
data: catalog,
|
|
571
890
|
message: outputMode === 'text'
|
|
572
891
|
? [
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
892
|
+
tone('Command groups', 'section'),
|
|
893
|
+
tone(separator(48), 'border'),
|
|
894
|
+
formatRow('auth', catalog.commandGroups.auth.join(', '), 14),
|
|
895
|
+
formatRow('email', catalog.commandGroups.email.join(', '), 14),
|
|
896
|
+
formatRow('calendar', catalog.commandGroups.calendar.join(', '), 14),
|
|
897
|
+
formatRow('folder', catalog.commandGroups.folder.join(', '), 14),
|
|
898
|
+
formatRow('rule', catalog.commandGroups.rule.join(', '), 14),
|
|
899
|
+
formatRow('agents', catalog.commandGroups.agents.join(', '), 14),
|
|
900
|
+
formatRow('tools', catalog.commandGroups.tools.join(', '), 14),
|
|
901
|
+
formatRow('generic', catalog.commandGroups.generic.join(', '), 14),
|
|
902
|
+
formatRow('system', catalog.commandGroups.system.join(', '), 14),
|
|
582
903
|
'',
|
|
583
|
-
`Available MCP tools (${catalog.tools.length})
|
|
584
|
-
|
|
904
|
+
tone(`Available MCP tools (${catalog.tools.length})`, 'section'),
|
|
905
|
+
tone(separator(48), 'border'),
|
|
906
|
+
...catalog.tools.map((tool) => formatRow(tool.name, tool.description || 'No description', 28))
|
|
585
907
|
].join('\n')
|
|
586
908
|
: undefined
|
|
587
909
|
});
|
|
@@ -1290,6 +1612,56 @@ async function handleRuleCommand(action, options, outputMode) {
|
|
|
1290
1612
|
}
|
|
1291
1613
|
}
|
|
1292
1614
|
|
|
1615
|
+
function startMcpServerProcess() {
|
|
1616
|
+
// eslint-disable-next-line global-require
|
|
1617
|
+
require('./index');
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
async function handleAgentsCommand(action, outputMode) {
|
|
1621
|
+
const subcommand = action || 'guide';
|
|
1622
|
+
if (subcommand !== 'guide') {
|
|
1623
|
+
throw new UsageError('Unknown agents command. Use: agents guide');
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
const tools = listTools();
|
|
1627
|
+
const guide = {
|
|
1628
|
+
totalTools: tools.length,
|
|
1629
|
+
toolNames: tools.map((tool) => tool.name),
|
|
1630
|
+
mcpServerCommand: `${CLI_BIN_NAME} mcp-server`,
|
|
1631
|
+
recommendedFlow: [
|
|
1632
|
+
`${CLI_BIN_NAME} auth status --json`,
|
|
1633
|
+
`${CLI_BIN_NAME} tools list --json`,
|
|
1634
|
+
`${CLI_BIN_NAME} tools schema <tool-name> --json`,
|
|
1635
|
+
`${CLI_BIN_NAME} call <tool-name> --args-json '{"...":"..."}' --json`
|
|
1636
|
+
],
|
|
1637
|
+
safetyNotes: [
|
|
1638
|
+
'Prefer --json or --ai for agent workflows.',
|
|
1639
|
+
'Use tools schema before call for strict argument validation.',
|
|
1640
|
+
'Use auth status before calling Microsoft Graph tools.'
|
|
1641
|
+
]
|
|
1642
|
+
};
|
|
1643
|
+
|
|
1644
|
+
printSuccess(outputMode, {
|
|
1645
|
+
ok: true,
|
|
1646
|
+
command: 'agents guide',
|
|
1647
|
+
data: guide,
|
|
1648
|
+
message: outputMode === 'text'
|
|
1649
|
+
? [
|
|
1650
|
+
tone('Agent guide', 'section'),
|
|
1651
|
+
tone(separator(48), 'border'),
|
|
1652
|
+
formatRow('mcp server', guide.mcpServerCommand, 18),
|
|
1653
|
+
formatRow('available tools', String(guide.totalTools), 18),
|
|
1654
|
+
'',
|
|
1655
|
+
tone('Recommended workflow', 'section'),
|
|
1656
|
+
...guide.recommendedFlow.map((line) => ` ${tone(line, 'accent')}`),
|
|
1657
|
+
'',
|
|
1658
|
+
tone('Best practices', 'section'),
|
|
1659
|
+
...guide.safetyNotes.map((note) => formatRow('note', note, 18))
|
|
1660
|
+
].join('\n')
|
|
1661
|
+
: undefined
|
|
1662
|
+
});
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1293
1665
|
async function handleDoctorCommand(outputMode) {
|
|
1294
1666
|
const tokenPath = config.AUTH_CONFIG.tokenStorePath;
|
|
1295
1667
|
const tokenExists = fs.existsSync(tokenPath);
|
|
@@ -1445,6 +1817,11 @@ async function run() {
|
|
|
1445
1817
|
await handleGenericCall(positional.slice(1), options, outputMode);
|
|
1446
1818
|
return;
|
|
1447
1819
|
|
|
1820
|
+
case 'agents':
|
|
1821
|
+
case 'agent':
|
|
1822
|
+
await handleAgentsCommand(action, outputMode);
|
|
1823
|
+
return;
|
|
1824
|
+
|
|
1448
1825
|
case 'auth':
|
|
1449
1826
|
await handleAuthCommand(action, options, outputMode);
|
|
1450
1827
|
return;
|
|
@@ -1473,6 +1850,10 @@ async function run() {
|
|
|
1473
1850
|
await handleUpdateCommand(options, outputMode);
|
|
1474
1851
|
return;
|
|
1475
1852
|
|
|
1853
|
+
case 'mcp-server':
|
|
1854
|
+
startMcpServerProcess();
|
|
1855
|
+
return;
|
|
1856
|
+
|
|
1476
1857
|
default:
|
|
1477
1858
|
throw new UsageError(`Unknown command: ${command}. Run '${cliCommand('help')}' for usage.`);
|
|
1478
1859
|
}
|
package/docs/REFERENCE.md
CHANGED
|
@@ -11,7 +11,8 @@ This document is the complete, publish-ready reference for:
|
|
|
11
11
|
- CLI binary: `outlook-cli`
|
|
12
12
|
- Output mode:
|
|
13
13
|
- `text` (default)
|
|
14
|
-
- `json` (with `--json
|
|
14
|
+
- `json` (with `--json`, `--ai`, or `--output json`)
|
|
15
|
+
- JSON responses include `structured` for normalized, machine-friendly fields while preserving raw `result`.
|
|
15
16
|
- Option key normalization:
|
|
16
17
|
- `--event-id` and `--eventId` map to the same internal key.
|
|
17
18
|
- `--start-server` and `--startServer` are equivalent.
|
|
@@ -24,7 +25,9 @@ This document is the complete, publish-ready reference for:
|
|
|
24
25
|
| Option | Type | Required | Default | Description |
|
|
25
26
|
|---|---|---:|---|---|
|
|
26
27
|
| `--json` | boolean | No | `false` | Forces JSON output for automation and agents. |
|
|
28
|
+
| `--ai` | boolean | No | `false` | Alias for JSON-first agent mode (no rich UI noise). |
|
|
27
29
|
| `--output` | `text` \| `json` | No | `text` | Explicit output mode override. |
|
|
30
|
+
| `--theme` | `k9s` \| `ocean` \| `mono` | No | `k9s` | Text-mode color theme preset. |
|
|
28
31
|
| `--plain` | boolean | No | `false` | Disables rich color and animation UI output. |
|
|
29
32
|
| `--no-color` | boolean | No | `false` | Disables terminal colors. |
|
|
30
33
|
| `--no-animate` | boolean | No | `false` | Disables loading/waiting spinner animation. |
|
|
@@ -40,11 +43,13 @@ This document is the complete, publish-ready reference for:
|
|
|
40
43
|
| `tools list` | Lists all tools with descriptions and schemas. |
|
|
41
44
|
| `tools schema <tool-name>` | Prints schema for one tool. |
|
|
42
45
|
| `call <tool-name>` | Calls any registered MCP tool directly. |
|
|
46
|
+
| `agents guide` | Prints AI-agent workflow guidance and best practices. |
|
|
43
47
|
| `auth ...` | Authentication command group. |
|
|
44
48
|
| `email ...` | Email command group. |
|
|
45
49
|
| `calendar ...` | Calendar command group. |
|
|
46
50
|
| `folder ...` | Folder command group. |
|
|
47
51
|
| `rule ...` | Rules command group. |
|
|
52
|
+
| `mcp-server` | Starts the stdio MCP server (same behavior as `node index.js`). |
|
|
48
53
|
| `doctor` | Environment and auth diagnostics. |
|
|
49
54
|
| `update` | Prints or runs global npm update command. |
|
|
50
55
|
| `help` | Same as `--help`. |
|
|
@@ -118,6 +123,18 @@ Examples:
|
|
|
118
123
|
```bash
|
|
119
124
|
outlook-cli call list-emails --args-json '{"count":5}'
|
|
120
125
|
outlook-cli call search-emails --arg query=invoice --arg unreadOnly=true --json
|
|
126
|
+
outlook-cli call send-email --args-json '{"to":"a@example.com","subject":"Hi","body":"Hello"}' --ai
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### agents guide
|
|
130
|
+
|
|
131
|
+
Prints AI-agent command patterns and orchestration best practices.
|
|
132
|
+
|
|
133
|
+
Usage:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
outlook-cli agents guide
|
|
137
|
+
outlook-cli agents guide --json
|
|
121
138
|
```
|
|
122
139
|
|
|
123
140
|
### auth status
|
|
@@ -137,7 +154,7 @@ Prints the Microsoft OAuth URL generated from current config.
|
|
|
137
154
|
Usage:
|
|
138
155
|
|
|
139
156
|
```bash
|
|
140
|
-
outlook-cli auth url
|
|
157
|
+
outlook-cli auth url [--client-id <id>]
|
|
141
158
|
```
|
|
142
159
|
|
|
143
160
|
### auth login
|
|
@@ -159,6 +176,27 @@ Options:
|
|
|
159
176
|
| `--start-server` | boolean | No | `true` | Starts local auth server if not already running. |
|
|
160
177
|
| `--wait` | boolean | No | `true` | Waits for token completion before returning. |
|
|
161
178
|
| `--timeout` | number (seconds) | No | `180` | Max wait time when `--wait` is true. Min effective timeout is 5s. |
|
|
179
|
+
| `--client-id` | string | No | env/config | Runtime override for client ID. |
|
|
180
|
+
| `--client-secret` | string | No | env/config | Runtime override for client secret value. |
|
|
181
|
+
| `--prompt-credentials` | boolean | No | `true` | Prompt for missing credentials in interactive terminals. |
|
|
182
|
+
|
|
183
|
+
### auth server
|
|
184
|
+
|
|
185
|
+
Checks or starts the local OAuth callback server.
|
|
186
|
+
|
|
187
|
+
Usage:
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
outlook-cli auth server --status
|
|
191
|
+
outlook-cli auth server --start
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Options:
|
|
195
|
+
|
|
196
|
+
| Option | Type | Required | Default | Description |
|
|
197
|
+
|---|---|---:|---|---|
|
|
198
|
+
| `--status` | boolean | No | `true` when `--start` is not passed | Prints reachability status only. |
|
|
199
|
+
| `--start` | boolean | No | `false` | Starts callback server in background if not running. |
|
|
162
200
|
|
|
163
201
|
### auth logout
|
|
164
202
|
|
package/email/search.js
CHANGED
|
@@ -6,6 +6,12 @@ const { callGraphAPI } = require('../utils/graph-api');
|
|
|
6
6
|
const { ensureAuthenticated } = require('../auth');
|
|
7
7
|
const { resolveFolderPath } = require('./folder-utils');
|
|
8
8
|
|
|
9
|
+
function debugLog(...args) {
|
|
10
|
+
if (config.DEBUG_LOGS) {
|
|
11
|
+
console.error(...args);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
9
15
|
/**
|
|
10
16
|
* Search emails handler
|
|
11
17
|
* @param {object} args - Tool arguments
|
|
@@ -27,7 +33,7 @@ async function handleSearchEmails(args) {
|
|
|
27
33
|
|
|
28
34
|
// Resolve the folder path
|
|
29
35
|
const endpoint = await resolveFolderPath(accessToken, folder);
|
|
30
|
-
|
|
36
|
+
debugLog(`Using endpoint: ${endpoint} for folder: ${folder}`);
|
|
31
37
|
|
|
32
38
|
// Execute progressive search
|
|
33
39
|
const response = await progressiveSearch(
|
|
@@ -76,16 +82,16 @@ async function progressiveSearch(endpoint, accessToken, searchTerms, filterTerms
|
|
|
76
82
|
// 1. Try combined search (most specific)
|
|
77
83
|
try {
|
|
78
84
|
const params = buildSearchParams(searchTerms, filterTerms, count);
|
|
79
|
-
|
|
85
|
+
debugLog('Attempting combined search with params:', params);
|
|
80
86
|
searchAttempts.push("combined-search");
|
|
81
87
|
|
|
82
88
|
const response = await callGraphAPI(accessToken, 'GET', endpoint, null, params);
|
|
83
89
|
if (response.value && response.value.length > 0) {
|
|
84
|
-
|
|
90
|
+
debugLog(`Combined search successful: found ${response.value.length} results`);
|
|
85
91
|
return response;
|
|
86
92
|
}
|
|
87
93
|
} catch (error) {
|
|
88
|
-
|
|
94
|
+
debugLog(`Combined search failed: ${error.message}`);
|
|
89
95
|
}
|
|
90
96
|
|
|
91
97
|
// 2. Try each search term individually, starting with most specific
|
|
@@ -94,7 +100,7 @@ async function progressiveSearch(endpoint, accessToken, searchTerms, filterTerms
|
|
|
94
100
|
for (const term of searchPriority) {
|
|
95
101
|
if (searchTerms[term]) {
|
|
96
102
|
try {
|
|
97
|
-
|
|
103
|
+
debugLog(`Attempting search with only ${term}: "${searchTerms[term]}"`);
|
|
98
104
|
searchAttempts.push(`single-term-${term}`);
|
|
99
105
|
|
|
100
106
|
// For single term search, only use $search with that term
|
|
@@ -118,11 +124,11 @@ async function progressiveSearch(endpoint, accessToken, searchTerms, filterTerms
|
|
|
118
124
|
|
|
119
125
|
const response = await callGraphAPI(accessToken, 'GET', endpoint, null, simplifiedParams);
|
|
120
126
|
if (response.value && response.value.length > 0) {
|
|
121
|
-
|
|
127
|
+
debugLog(`Search with ${term} successful: found ${response.value.length} results`);
|
|
122
128
|
return response;
|
|
123
129
|
}
|
|
124
130
|
} catch (error) {
|
|
125
|
-
|
|
131
|
+
debugLog(`Search with ${term} failed: ${error.message}`);
|
|
126
132
|
}
|
|
127
133
|
}
|
|
128
134
|
}
|
|
@@ -130,7 +136,7 @@ async function progressiveSearch(endpoint, accessToken, searchTerms, filterTerms
|
|
|
130
136
|
// 3. Try with only boolean filters
|
|
131
137
|
if (filterTerms.hasAttachments === true || filterTerms.unreadOnly === true) {
|
|
132
138
|
try {
|
|
133
|
-
|
|
139
|
+
debugLog('Attempting search with only boolean filters');
|
|
134
140
|
searchAttempts.push("boolean-filters-only");
|
|
135
141
|
|
|
136
142
|
const filterOnlyParams = {
|
|
@@ -143,15 +149,15 @@ async function progressiveSearch(endpoint, accessToken, searchTerms, filterTerms
|
|
|
143
149
|
addBooleanFilters(filterOnlyParams, filterTerms);
|
|
144
150
|
|
|
145
151
|
const response = await callGraphAPI(accessToken, 'GET', endpoint, null, filterOnlyParams);
|
|
146
|
-
|
|
152
|
+
debugLog(`Boolean filter search found ${response.value?.length || 0} results`);
|
|
147
153
|
return response;
|
|
148
154
|
} catch (error) {
|
|
149
|
-
|
|
155
|
+
debugLog(`Boolean filter search failed: ${error.message}`);
|
|
150
156
|
}
|
|
151
157
|
}
|
|
152
158
|
|
|
153
159
|
// 4. Final fallback: just get recent emails
|
|
154
|
-
|
|
160
|
+
debugLog('All search strategies failed, falling back to recent emails');
|
|
155
161
|
searchAttempts.push("recent-emails");
|
|
156
162
|
|
|
157
163
|
const basicParams = {
|
|
@@ -161,7 +167,7 @@ async function progressiveSearch(endpoint, accessToken, searchTerms, filterTerms
|
|
|
161
167
|
};
|
|
162
168
|
|
|
163
169
|
const response = await callGraphAPI(accessToken, 'GET', endpoint, null, basicParams);
|
|
164
|
-
|
|
170
|
+
debugLog(`Fallback to recent emails found ${response.value?.length || 0} results`);
|
|
165
171
|
|
|
166
172
|
// Add a note to the response about the search attempts
|
|
167
173
|
response._searchInfo = {
|
package/package.json
CHANGED
package/utils/graph-api.js
CHANGED
|
@@ -5,6 +5,12 @@ const https = require('https');
|
|
|
5
5
|
const config = require('../config');
|
|
6
6
|
const mockData = require('./mock-data');
|
|
7
7
|
|
|
8
|
+
function debugLog(...args) {
|
|
9
|
+
if (config.DEBUG_LOGS) {
|
|
10
|
+
console.error(...args);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
8
14
|
/**
|
|
9
15
|
* Makes a request to the Microsoft Graph API
|
|
10
16
|
* @param {string} accessToken - The access token for authentication
|
|
@@ -17,12 +23,12 @@ const mockData = require('./mock-data');
|
|
|
17
23
|
async function callGraphAPI(accessToken, method, path, data = null, queryParams = {}) {
|
|
18
24
|
// For test tokens, we'll simulate the API call
|
|
19
25
|
if (config.USE_TEST_MODE && accessToken.startsWith('test_access_token_')) {
|
|
20
|
-
|
|
26
|
+
debugLog(`TEST MODE: Simulating ${method} ${path} API call`);
|
|
21
27
|
return mockData.simulateGraphAPIResponse(method, path, data, queryParams);
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
try {
|
|
25
|
-
|
|
31
|
+
debugLog(`Making real API call: ${method} ${path}`);
|
|
26
32
|
|
|
27
33
|
// Encode path segments properly
|
|
28
34
|
const encodedPath = path.split('/')
|
|
@@ -59,11 +65,11 @@ async function callGraphAPI(accessToken, method, path, data = null, queryParams
|
|
|
59
65
|
queryString = '?' + queryString;
|
|
60
66
|
}
|
|
61
67
|
|
|
62
|
-
|
|
68
|
+
debugLog(`Query string: ${queryString}`);
|
|
63
69
|
}
|
|
64
70
|
|
|
65
71
|
const url = `${config.GRAPH_API_ENDPOINT}${encodedPath}${queryString}`;
|
|
66
|
-
|
|
72
|
+
debugLog(`Full URL: ${url}`);
|
|
67
73
|
|
|
68
74
|
return new Promise((resolve, reject) => {
|
|
69
75
|
const options = {
|
|
@@ -110,7 +116,7 @@ async function callGraphAPI(accessToken, method, path, data = null, queryParams
|
|
|
110
116
|
req.end();
|
|
111
117
|
});
|
|
112
118
|
} catch (error) {
|
|
113
|
-
|
|
119
|
+
debugLog('Error calling Graph API:', error);
|
|
114
120
|
throw error;
|
|
115
121
|
}
|
|
116
122
|
}
|