outlook-cli 1.2.1 → 1.2.3

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/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` or `--output 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
 
@@ -0,0 +1,359 @@
1
+ /**
2
+ * Email attachment listing and retrieval functionality
3
+ */
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const { callGraphAPI, callGraphAPIRaw } = require('../utils/graph-api');
7
+ const { ensureAuthenticated } = require('../auth');
8
+
9
+ const DEFAULT_ATTACHMENT_COUNT = 25;
10
+ const MAX_ATTACHMENT_COUNT = 50;
11
+
12
+ function textResponse(text) {
13
+ return {
14
+ content: [{
15
+ type: 'text',
16
+ text
17
+ }]
18
+ };
19
+ }
20
+
21
+ function asBoolean(value, defaultValue = false) {
22
+ if (value === undefined || value === null || value === '') {
23
+ return defaultValue;
24
+ }
25
+
26
+ if (typeof value === 'boolean') {
27
+ return value;
28
+ }
29
+
30
+ if (typeof value === 'number') {
31
+ return value !== 0;
32
+ }
33
+
34
+ const normalized = String(value).trim().toLowerCase();
35
+ if (['1', 'true', 'yes', 'y', 'on'].includes(normalized)) {
36
+ return true;
37
+ }
38
+
39
+ if (['0', 'false', 'no', 'n', 'off'].includes(normalized)) {
40
+ return false;
41
+ }
42
+
43
+ return defaultValue;
44
+ }
45
+
46
+ function asCount(value, defaultValue = DEFAULT_ATTACHMENT_COUNT) {
47
+ const parsed = Number(value);
48
+ if (!Number.isFinite(parsed) || parsed <= 0) {
49
+ return defaultValue;
50
+ }
51
+
52
+ return Math.min(parsed, MAX_ATTACHMENT_COUNT);
53
+ }
54
+
55
+ function getAttachmentKind(rawType) {
56
+ const type = String(rawType || '').toLowerCase();
57
+
58
+ if (type.includes('fileattachment')) {
59
+ return 'fileAttachment';
60
+ }
61
+
62
+ if (type.includes('itemattachment')) {
63
+ return 'itemAttachment';
64
+ }
65
+
66
+ if (type.includes('referenceattachment')) {
67
+ return 'referenceAttachment';
68
+ }
69
+
70
+ return 'attachment';
71
+ }
72
+
73
+ function sanitizeFileName(value) {
74
+ const raw = String(value || '').trim();
75
+ if (!raw) {
76
+ return 'attachment.bin';
77
+ }
78
+
79
+ const sanitized = raw
80
+ .replace(/[<>:"/\\|?*\u0000-\u001F]/g, '_')
81
+ .replace(/\s+/g, ' ')
82
+ .trim();
83
+
84
+ return sanitized || 'attachment.bin';
85
+ }
86
+
87
+ function shouldTreatAsDirectory(inputPath) {
88
+ const value = String(inputPath || '');
89
+ return value.endsWith(path.sep) || value.endsWith('/') || value.endsWith('\\');
90
+ }
91
+
92
+ function resolveOutputFilePath(savePath, fallbackFileName) {
93
+ const requested = String(savePath || '').trim();
94
+ if (!requested) {
95
+ return '';
96
+ }
97
+
98
+ const resolved = path.resolve(requested);
99
+ if (fs.existsSync(resolved)) {
100
+ const stats = fs.statSync(resolved);
101
+ if (stats.isDirectory()) {
102
+ return path.join(resolved, fallbackFileName);
103
+ }
104
+
105
+ return resolved;
106
+ }
107
+
108
+ if (shouldTreatAsDirectory(requested)) {
109
+ return path.join(resolved, fallbackFileName);
110
+ }
111
+
112
+ return resolved;
113
+ }
114
+
115
+ function ensureOutputWritable(filePath, overwrite) {
116
+ if (fs.existsSync(filePath) && !overwrite) {
117
+ throw new Error(`File already exists at ${filePath}. Re-run with overwrite=true to replace it.`);
118
+ }
119
+
120
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
121
+ }
122
+
123
+ function looksLikeText(buffer) {
124
+ if (!buffer || buffer.length === 0) {
125
+ return false;
126
+ }
127
+
128
+ const sampleSize = Math.min(buffer.length, 2048);
129
+ let printableCount = 0;
130
+
131
+ for (let index = 0; index < sampleSize; index += 1) {
132
+ const byte = buffer[index];
133
+ const isPrintable = byte === 9 || byte === 10 || byte === 13 || (byte >= 32 && byte <= 126);
134
+ if (isPrintable) {
135
+ printableCount += 1;
136
+ }
137
+ }
138
+
139
+ return (printableCount / sampleSize) >= 0.8;
140
+ }
141
+
142
+ function buildContentPreview(buffer) {
143
+ if (!looksLikeText(buffer)) {
144
+ return '';
145
+ }
146
+
147
+ return buffer
148
+ .toString('utf8', 0, Math.min(buffer.length, 4096))
149
+ .slice(0, 600)
150
+ .trim();
151
+ }
152
+
153
+ function defaultFileName(attachment, attachmentId) {
154
+ const kind = getAttachmentKind(attachment['@odata.type']);
155
+ const fromName = sanitizeFileName(attachment.name || '');
156
+
157
+ if (fromName !== 'attachment.bin') {
158
+ if (kind === 'itemAttachment' && !path.extname(fromName)) {
159
+ return `${fromName}.eml`;
160
+ }
161
+
162
+ return fromName;
163
+ }
164
+
165
+ if (kind === 'itemAttachment') {
166
+ return `attachment-${attachmentId}.eml`;
167
+ }
168
+
169
+ return `attachment-${attachmentId}.bin`;
170
+ }
171
+
172
+ async function getAttachmentBytes(accessToken, messageId, attachment, attachmentId) {
173
+ const kind = getAttachmentKind(attachment['@odata.type']);
174
+
175
+ if (kind === 'referenceAttachment') {
176
+ throw new Error('Reference attachments are cloud links and cannot be downloaded as raw bytes via this endpoint.');
177
+ }
178
+
179
+ if (kind === 'fileAttachment' && attachment.contentBytes) {
180
+ return Buffer.from(attachment.contentBytes, 'base64');
181
+ }
182
+
183
+ const rawResponse = await callGraphAPIRaw(
184
+ accessToken,
185
+ 'GET',
186
+ `me/messages/${messageId}/attachments/${attachmentId}/$value`
187
+ );
188
+
189
+ return rawResponse.body;
190
+ }
191
+
192
+ function formatAttachmentLine(attachment, index) {
193
+ const type = getAttachmentKind(attachment['@odata.type']);
194
+ const modified = attachment.lastModifiedDateTime
195
+ ? new Date(attachment.lastModifiedDateTime).toLocaleString()
196
+ : 'Unknown';
197
+
198
+ return `${index + 1}. ${attachment.name || '(unnamed attachment)'}
199
+ ID: ${attachment.id}
200
+ Type: ${type}
201
+ Content Type: ${attachment.contentType || 'unknown'}
202
+ Size: ${attachment.size || 0} bytes
203
+ Inline: ${attachment.isInline ? 'Yes' : 'No'}
204
+ Last Modified: ${modified}`;
205
+ }
206
+
207
+ /**
208
+ * List attachments for an email message
209
+ * @param {object} args - Tool arguments
210
+ * @returns {object} - MCP response
211
+ */
212
+ async function handleListAttachments(args = {}) {
213
+ const messageId = args.messageId || args.id;
214
+ const count = asCount(args.count, DEFAULT_ATTACHMENT_COUNT);
215
+
216
+ if (!messageId) {
217
+ return textResponse('Message ID is required.');
218
+ }
219
+
220
+ try {
221
+ const accessToken = await ensureAuthenticated();
222
+
223
+ const response = await callGraphAPI(
224
+ accessToken,
225
+ 'GET',
226
+ `me/messages/${messageId}/attachments`,
227
+ null,
228
+ {
229
+ $top: count,
230
+ $select: 'id,name,contentType,size,isInline,lastModifiedDateTime'
231
+ }
232
+ );
233
+
234
+ const attachments = Array.isArray(response.value) ? response.value : [];
235
+
236
+ if (attachments.length === 0) {
237
+ return textResponse(`No attachments found for message ${messageId}.`);
238
+ }
239
+
240
+ const formatted = attachments
241
+ .map((attachment, index) => formatAttachmentLine(attachment, index))
242
+ .join('\n\n');
243
+
244
+ return textResponse(`Found ${attachments.length} attachment(s) for message ${messageId}:\n\n${formatted}`);
245
+ } catch (error) {
246
+ if (error.message === 'Authentication required') {
247
+ return textResponse("Authentication required. Please use the 'authenticate' tool first.");
248
+ }
249
+
250
+ return textResponse(`Error listing attachments: ${error.message}`);
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Get one attachment's details and optionally save it to disk
256
+ * @param {object} args - Tool arguments
257
+ * @returns {object} - MCP response
258
+ */
259
+ async function handleGetAttachment(args = {}) {
260
+ const messageId = args.messageId || args.id;
261
+ const attachmentId = args.attachmentId;
262
+ const savePath = args.savePath || args.outputPath || args.out;
263
+ const includeContent = asBoolean(args.includeContent, false);
264
+ const expandItem = asBoolean(args.expandItem, false);
265
+ const overwrite = asBoolean(args.overwrite, false);
266
+
267
+ if (!messageId) {
268
+ return textResponse('Message ID is required.');
269
+ }
270
+
271
+ if (!attachmentId) {
272
+ return textResponse('Attachment ID is required.');
273
+ }
274
+
275
+ try {
276
+ const accessToken = await ensureAuthenticated();
277
+ const queryParams = {};
278
+
279
+ if (expandItem) {
280
+ queryParams.$expand = 'microsoft.graph.itemattachment/item';
281
+ }
282
+
283
+ const attachment = await callGraphAPI(
284
+ accessToken,
285
+ 'GET',
286
+ `me/messages/${messageId}/attachments/${attachmentId}`,
287
+ null,
288
+ queryParams
289
+ );
290
+
291
+ if (!attachment || !attachment.id) {
292
+ return textResponse(`Attachment ${attachmentId} not found in message ${messageId}.`);
293
+ }
294
+
295
+ const kind = getAttachmentKind(attachment['@odata.type']);
296
+ const summary = [
297
+ `Attachment: ${attachment.name || '(unnamed attachment)'}`,
298
+ `ID: ${attachment.id}`,
299
+ `Type: ${kind}`,
300
+ `Content Type: ${attachment.contentType || 'unknown'}`,
301
+ `Size: ${attachment.size || 0} bytes`,
302
+ `Inline: ${attachment.isInline ? 'Yes' : 'No'}`
303
+ ];
304
+
305
+ if (kind === 'referenceAttachment') {
306
+ const sourceUrl = attachment.sourceUrl || attachment.providerType || 'Unknown';
307
+ summary.push(`Reference Target: ${sourceUrl}`);
308
+ }
309
+
310
+ if (attachment.item && typeof attachment.item === 'object') {
311
+ summary.push(`Attached Item Type: ${attachment.item['@odata.type'] || 'unknown'}`);
312
+ if (attachment.item.subject) {
313
+ summary.push(`Attached Item Subject: ${attachment.item.subject}`);
314
+ }
315
+ }
316
+
317
+ let downloadedBuffer = null;
318
+ if (savePath || includeContent) {
319
+ downloadedBuffer = await getAttachmentBytes(accessToken, messageId, attachment, attachmentId);
320
+ summary.push(`Downloaded Bytes: ${downloadedBuffer.length}`);
321
+ }
322
+
323
+ if (savePath) {
324
+ const destination = resolveOutputFilePath(
325
+ savePath,
326
+ defaultFileName(attachment, attachmentId)
327
+ );
328
+
329
+ ensureOutputWritable(destination, overwrite);
330
+ fs.writeFileSync(destination, downloadedBuffer);
331
+ summary.push(`Saved To: ${destination}`);
332
+ }
333
+
334
+ if (includeContent && downloadedBuffer) {
335
+ const preview = buildContentPreview(downloadedBuffer);
336
+ if (preview) {
337
+ summary.push('');
338
+ summary.push('Content Preview:');
339
+ summary.push(preview);
340
+ } else {
341
+ summary.push('');
342
+ summary.push('Content Preview: Binary content detected (no text preview).');
343
+ }
344
+ }
345
+
346
+ return textResponse(summary.join('\n'));
347
+ } catch (error) {
348
+ if (error.message === 'Authentication required') {
349
+ return textResponse("Authentication required. Please use the 'authenticate' tool first.");
350
+ }
351
+
352
+ return textResponse(`Error getting attachment: ${error.message}`);
353
+ }
354
+ }
355
+
356
+ module.exports = {
357
+ handleListAttachments,
358
+ handleGetAttachment
359
+ };
package/email/index.js CHANGED
@@ -6,6 +6,7 @@ const handleSearchEmails = require('./search');
6
6
  const handleReadEmail = require('./read');
7
7
  const handleSendEmail = require('./send');
8
8
  const handleMarkAsRead = require('./mark-as-read');
9
+ const { handleListAttachments, handleGetAttachment } = require('./attachments');
9
10
 
10
11
  // Email tool definitions
11
12
  const emailTools = [
@@ -144,6 +145,60 @@ const emailTools = [
144
145
  required: ["id"]
145
146
  },
146
147
  handler: handleMarkAsRead
148
+ },
149
+ {
150
+ name: "list-attachments",
151
+ description: "Lists attachments for a specific email",
152
+ inputSchema: {
153
+ type: "object",
154
+ properties: {
155
+ messageId: {
156
+ type: "string",
157
+ description: "ID of the message that contains attachments"
158
+ },
159
+ count: {
160
+ type: "number",
161
+ description: "Maximum number of attachments to list (default: 25, max: 50)"
162
+ }
163
+ },
164
+ required: ["messageId"]
165
+ },
166
+ handler: handleListAttachments
167
+ },
168
+ {
169
+ name: "get-attachment",
170
+ description: "Gets one attachment's metadata and optionally downloads it",
171
+ inputSchema: {
172
+ type: "object",
173
+ properties: {
174
+ messageId: {
175
+ type: "string",
176
+ description: "ID of the message that contains the attachment"
177
+ },
178
+ attachmentId: {
179
+ type: "string",
180
+ description: "ID of the attachment to read or download"
181
+ },
182
+ savePath: {
183
+ type: "string",
184
+ description: "Optional output file path or output directory path"
185
+ },
186
+ includeContent: {
187
+ type: "boolean",
188
+ description: "When true, include a text preview for text-like attachments"
189
+ },
190
+ expandItem: {
191
+ type: "boolean",
192
+ description: "When true, expand item attachment metadata"
193
+ },
194
+ overwrite: {
195
+ type: "boolean",
196
+ description: "When true, overwrite an existing file at savePath"
197
+ }
198
+ },
199
+ required: ["messageId", "attachmentId"]
200
+ },
201
+ handler: handleGetAttachment
147
202
  }
148
203
  ];
149
204
 
@@ -153,5 +208,7 @@ module.exports = {
153
208
  handleSearchEmails,
154
209
  handleReadEmail,
155
210
  handleSendEmail,
156
- handleMarkAsRead
211
+ handleMarkAsRead,
212
+ handleListAttachments,
213
+ handleGetAttachment
157
214
  };
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
- console.error(`Using endpoint: ${endpoint} for folder: ${folder}`);
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
- console.error("Attempting combined search with params:", params);
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
- console.error(`Combined search successful: found ${response.value.length} results`);
90
+ debugLog(`Combined search successful: found ${response.value.length} results`);
85
91
  return response;
86
92
  }
87
93
  } catch (error) {
88
- console.error(`Combined search failed: ${error.message}`);
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
- console.error(`Attempting search with only ${term}: "${searchTerms[term]}"`);
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
- console.error(`Search with ${term} successful: found ${response.value.length} results`);
127
+ debugLog(`Search with ${term} successful: found ${response.value.length} results`);
122
128
  return response;
123
129
  }
124
130
  } catch (error) {
125
- console.error(`Search with ${term} failed: ${error.message}`);
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
- console.error("Attempting search with only boolean filters");
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
- console.error(`Boolean filter search found ${response.value?.length || 0} results`);
152
+ debugLog(`Boolean filter search found ${response.value?.length || 0} results`);
147
153
  return response;
148
154
  } catch (error) {
149
- console.error(`Boolean filter search failed: ${error.message}`);
155
+ debugLog(`Boolean filter search failed: ${error.message}`);
150
156
  }
151
157
  }
152
158
 
153
159
  // 4. Final fallback: just get recent emails
154
- console.error("All search strategies failed, falling back to recent emails");
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
- console.error(`Fallback to recent emails found ${response.value?.length || 0} results`);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "outlook-cli",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "description": "Production-ready Outlook CLI with optional MCP server mode powered by Microsoft Graph API",
5
5
  "keywords": [
6
6
  "outlook-cli",