m365-agent-cli 1.2.0
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 +22 -0
- package/README.md +916 -0
- package/package.json +50 -0
- package/src/cli.ts +100 -0
- package/src/commands/auto-reply.ts +182 -0
- package/src/commands/calendar.ts +576 -0
- package/src/commands/counter.ts +87 -0
- package/src/commands/create-event.ts +544 -0
- package/src/commands/delegates.ts +286 -0
- package/src/commands/delete-event.ts +321 -0
- package/src/commands/drafts.ts +502 -0
- package/src/commands/files.ts +532 -0
- package/src/commands/find.ts +195 -0
- package/src/commands/findtime.ts +270 -0
- package/src/commands/folders.ts +177 -0
- package/src/commands/forward-event.ts +49 -0
- package/src/commands/graph-calendar.ts +217 -0
- package/src/commands/login.ts +195 -0
- package/src/commands/mail.ts +950 -0
- package/src/commands/oof.ts +263 -0
- package/src/commands/outlook-categories.ts +173 -0
- package/src/commands/outlook-graph.ts +880 -0
- package/src/commands/planner.ts +1678 -0
- package/src/commands/respond.ts +291 -0
- package/src/commands/rooms.ts +210 -0
- package/src/commands/rules.ts +511 -0
- package/src/commands/schedule.ts +109 -0
- package/src/commands/send.ts +204 -0
- package/src/commands/serve.ts +14 -0
- package/src/commands/sharepoint.ts +179 -0
- package/src/commands/site-pages.ts +163 -0
- package/src/commands/subscribe.ts +103 -0
- package/src/commands/subscriptions.ts +29 -0
- package/src/commands/suggest.ts +155 -0
- package/src/commands/todo.ts +2092 -0
- package/src/commands/update-event.ts +608 -0
- package/src/commands/update.ts +88 -0
- package/src/commands/verify-token.ts +62 -0
- package/src/commands/whoami.ts +74 -0
- package/src/index.ts +190 -0
- package/src/lib/atomic-write.ts +20 -0
- package/src/lib/attach-link-spec.test.ts +24 -0
- package/src/lib/attach-link-spec.ts +70 -0
- package/src/lib/attachments.ts +79 -0
- package/src/lib/auth.ts +192 -0
- package/src/lib/calendar-range.test.ts +41 -0
- package/src/lib/calendar-range.ts +103 -0
- package/src/lib/dates.test.ts +74 -0
- package/src/lib/dates.ts +137 -0
- package/src/lib/delegate-client.test.ts +74 -0
- package/src/lib/delegate-client.ts +322 -0
- package/src/lib/ews-client.ts +3418 -0
- package/src/lib/git-commit.ts +4 -0
- package/src/lib/glitchtip-eligibility.ts +220 -0
- package/src/lib/glitchtip.ts +253 -0
- package/src/lib/global-env.ts +3 -0
- package/src/lib/graph-auth.ts +223 -0
- package/src/lib/graph-calendar-client.test.ts +118 -0
- package/src/lib/graph-calendar-client.ts +112 -0
- package/src/lib/graph-client.test.ts +107 -0
- package/src/lib/graph-client.ts +1058 -0
- package/src/lib/graph-constants.ts +12 -0
- package/src/lib/graph-directory.ts +116 -0
- package/src/lib/graph-event.ts +134 -0
- package/src/lib/graph-schedule.ts +173 -0
- package/src/lib/graph-subscriptions.ts +94 -0
- package/src/lib/graph-user-path.ts +13 -0
- package/src/lib/jwt-utils.ts +34 -0
- package/src/lib/markdown.test.ts +21 -0
- package/src/lib/markdown.ts +174 -0
- package/src/lib/mime-type.ts +106 -0
- package/src/lib/oof-client.test.ts +59 -0
- package/src/lib/oof-client.ts +122 -0
- package/src/lib/outlook-graph-client.test.ts +146 -0
- package/src/lib/outlook-graph-client.ts +649 -0
- package/src/lib/outlook-master-categories.ts +145 -0
- package/src/lib/package-info.ts +59 -0
- package/src/lib/places-client.ts +144 -0
- package/src/lib/planner-client.ts +1226 -0
- package/src/lib/rules-client.ts +178 -0
- package/src/lib/sharepoint-client.ts +101 -0
- package/src/lib/site-pages-client.ts +73 -0
- package/src/lib/todo-client.test.ts +298 -0
- package/src/lib/todo-client.ts +1309 -0
- package/src/lib/url-validation.ts +40 -0
- package/src/lib/utils.ts +45 -0
- package/src/lib/webhook-server.ts +51 -0
- package/src/test/auth.test.ts +104 -0
- package/src/test/cli.integration.test.ts +1083 -0
- package/src/test/ews-client.test.ts +268 -0
- package/src/test/mocks/index.ts +375 -0
- package/src/test/mocks/responses.ts +861 -0
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { resolveGraphAuth } from '../lib/graph-auth.js';
|
|
3
|
+
import {
|
|
4
|
+
type CreateMessageRulePayload,
|
|
5
|
+
createMessageRule,
|
|
6
|
+
deleteMessageRule,
|
|
7
|
+
getMessageRule,
|
|
8
|
+
listMessageRules,
|
|
9
|
+
type MessageRule,
|
|
10
|
+
type MessageRuleAction,
|
|
11
|
+
type MessageRuleCondition,
|
|
12
|
+
type UpdateMessageRulePayload,
|
|
13
|
+
updateMessageRule
|
|
14
|
+
} from '../lib/rules-client.js';
|
|
15
|
+
import { checkReadOnly } from '../lib/utils.js';
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Helpers
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
function parseEmailAddresses(raw: string): { emailAddress: { name?: string; address: string } }[] {
|
|
22
|
+
try {
|
|
23
|
+
const parsed = JSON.parse(raw);
|
|
24
|
+
if (Array.isArray(parsed)) {
|
|
25
|
+
return parsed.map((item) => (typeof item === 'string' ? { emailAddress: { address: item } } : item));
|
|
26
|
+
}
|
|
27
|
+
return parsed;
|
|
28
|
+
} catch {
|
|
29
|
+
return raw.split(',').map((s) => ({ emailAddress: { address: s.trim() } }));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function parseCondition(key: string, raw: string): unknown {
|
|
34
|
+
if (key === 'hasAttachments' || key === 'isAutomaticForward') {
|
|
35
|
+
return raw.toLowerCase() === 'true';
|
|
36
|
+
}
|
|
37
|
+
// Addresses fields expect JSON array; plain strings are split by comma
|
|
38
|
+
if (key === 'fromAddresses' || key === 'sentToAddresses') {
|
|
39
|
+
return parseEmailAddresses(raw);
|
|
40
|
+
}
|
|
41
|
+
// Contains fields expect string arrays
|
|
42
|
+
if (key === 'bodyContains' || key === 'subjectContains' || key === 'senderContains' || key === 'recipientContains') {
|
|
43
|
+
return [raw];
|
|
44
|
+
}
|
|
45
|
+
return raw;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function parseAction(key: string, raw: any): MessageRuleAction[keyof MessageRuleAction] {
|
|
49
|
+
if (key === 'delete' || key === 'permanentDelete' || key === 'markAsRead' || key === 'stopProcessingRules') {
|
|
50
|
+
if (typeof raw === 'boolean') return raw;
|
|
51
|
+
return String(raw).toLowerCase() === 'true';
|
|
52
|
+
}
|
|
53
|
+
if (key === 'forwardToRecipients' || key === 'forwardAsAttachmentToRecipients') {
|
|
54
|
+
return parseEmailAddresses(String(raw));
|
|
55
|
+
}
|
|
56
|
+
if (key === 'assignCategories') {
|
|
57
|
+
return String(raw)
|
|
58
|
+
.split(',')
|
|
59
|
+
.map((s: string) => s.trim());
|
|
60
|
+
}
|
|
61
|
+
return raw;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function conditionsFromOpts(options: Record<string, unknown>): MessageRuleCondition | undefined {
|
|
65
|
+
const conditions: MessageRuleCondition = {};
|
|
66
|
+
let used = false;
|
|
67
|
+
|
|
68
|
+
const entries: [keyof MessageRuleCondition, unknown][] = [
|
|
69
|
+
['bodyContains', options.bodyContains],
|
|
70
|
+
['subjectContains', options.subjectContains],
|
|
71
|
+
['senderContains', options.senderContains],
|
|
72
|
+
['recipientContains', options.recipientContains],
|
|
73
|
+
['fromAddresses', options.fromAddresses],
|
|
74
|
+
['sentToAddresses', options.sentToAddresses],
|
|
75
|
+
['hasAttachments', options.hasAttachments],
|
|
76
|
+
['importance', options.importance],
|
|
77
|
+
['isAutomaticForward', options.isAutomaticForward]
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
for (const [key, val] of entries) {
|
|
81
|
+
if (val !== undefined) {
|
|
82
|
+
(conditions as Record<string, unknown>)[key] = parseCondition(key as string, String(val));
|
|
83
|
+
used = true;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return used ? conditions : undefined;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function actionsFromOpts(options: Record<string, unknown>): MessageRuleAction {
|
|
90
|
+
const actions: MessageRuleAction = {};
|
|
91
|
+
|
|
92
|
+
if (options.delete === true || options.delete === 'true') actions.delete = true;
|
|
93
|
+
if (options.permanentDelete === true || options.permanentDelete === 'true') actions.permanentDelete = true;
|
|
94
|
+
if (options.markAsRead === true || options.markAsRead === 'true') actions.markAsRead = true;
|
|
95
|
+
if (options.stopProcessingRules === true || options.stopProcessingRules === 'true')
|
|
96
|
+
actions.stopProcessingRules = true;
|
|
97
|
+
|
|
98
|
+
if (options.moveToFolder !== undefined) actions.moveToFolder = String(options.moveToFolder);
|
|
99
|
+
if (options.copyToFolder !== undefined) actions.copyToFolder = String(options.copyToFolder);
|
|
100
|
+
if (options.markImportance !== undefined) actions.markImportance = String(options.markImportance) as any;
|
|
101
|
+
|
|
102
|
+
if (options.forwardTo !== undefined)
|
|
103
|
+
actions.forwardToRecipients = parseAction('forwardToRecipients', options.forwardTo as string) as any;
|
|
104
|
+
if (options.forwardAsAttachmentTo !== undefined)
|
|
105
|
+
actions.forwardAsAttachmentToRecipients = parseAction(
|
|
106
|
+
'forwardAsAttachmentToRecipients',
|
|
107
|
+
options.forwardAsAttachmentTo as string
|
|
108
|
+
) as any;
|
|
109
|
+
if (options.assignCategories !== undefined)
|
|
110
|
+
actions.assignCategories = parseAction('assignCategories', options.assignCategories as string) as any;
|
|
111
|
+
|
|
112
|
+
return actions;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function printRule(rule: MessageRule, json: boolean): void {
|
|
116
|
+
if (json) {
|
|
117
|
+
console.log(JSON.stringify(rule, null, 2));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
console.log(`\nRule: ${rule.displayName}`);
|
|
121
|
+
console.log(` ID: ${rule.id}`);
|
|
122
|
+
console.log(` Priority: ${rule.priority}`);
|
|
123
|
+
console.log(` Enabled: ${rule.isEnabled}`);
|
|
124
|
+
|
|
125
|
+
if (rule.conditions) {
|
|
126
|
+
const c = rule.conditions;
|
|
127
|
+
const parts: string[] = [];
|
|
128
|
+
if (c.bodyContains?.length) parts.push(`body contains: ${c.bodyContains.join(', ')}`);
|
|
129
|
+
if (c.subjectContains?.length) parts.push(`subject contains: ${c.subjectContains.join(', ')}`);
|
|
130
|
+
if (c.senderContains?.length) parts.push(`sender contains: ${c.senderContains.join(', ')}`);
|
|
131
|
+
if (c.recipientContains?.length) parts.push(`recipient contains: ${c.recipientContains.join(', ')}`);
|
|
132
|
+
if (c.fromAddresses?.length) parts.push(`from: ${c.fromAddresses.map((a) => a.emailAddress.address).join(', ')}`);
|
|
133
|
+
if (c.sentToAddresses?.length)
|
|
134
|
+
parts.push(`sent to: ${c.sentToAddresses.map((a) => a.emailAddress.address).join(', ')}`);
|
|
135
|
+
if (c.hasAttachments !== undefined) parts.push(`has attachments: ${c.hasAttachments}`);
|
|
136
|
+
if (c.importance) parts.push(`importance: ${c.importance}`);
|
|
137
|
+
if (c.isAutomaticForward !== undefined) parts.push(`auto forward: ${c.isAutomaticForward}`);
|
|
138
|
+
if (parts.length) console.log(` Conditions: ${parts.join(' | ')}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (rule.actions) {
|
|
142
|
+
const a = rule.actions;
|
|
143
|
+
const parts: string[] = [];
|
|
144
|
+
if (a.moveToFolder) parts.push(`move to: ${a.moveToFolder}`);
|
|
145
|
+
if (a.copyToFolder) parts.push(`copy to: ${a.copyToFolder}`);
|
|
146
|
+
if (a.delete) parts.push('delete');
|
|
147
|
+
if (a.permanentDelete) parts.push('permanent delete');
|
|
148
|
+
if (a.markAsRead) parts.push('mark as read');
|
|
149
|
+
if (a.markImportance) parts.push(`mark importance: ${a.markImportance}`);
|
|
150
|
+
if (a.forwardToRecipients?.length)
|
|
151
|
+
parts.push(`forward to: ${a.forwardToRecipients.map((r) => r.emailAddress.address).join(', ')}`);
|
|
152
|
+
if (a.forwardAsAttachmentToRecipients?.length) {
|
|
153
|
+
parts.push(
|
|
154
|
+
`forward as attachment to: ${a.forwardAsAttachmentToRecipients.map((r) => r.emailAddress.address).join(', ')}`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
if (a.assignCategories?.length) parts.push(`categories: ${a.assignCategories.join(', ')}`);
|
|
158
|
+
if (a.stopProcessingRules) parts.push('stop processing rules');
|
|
159
|
+
if (parts.length) console.log(` Actions: ${parts.join(' | ')}`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (rule.exceptionConditions) {
|
|
163
|
+
console.log(` Exceptions: (see --json for details)`);
|
|
164
|
+
}
|
|
165
|
+
console.log('');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
// List subcommand
|
|
170
|
+
// ---------------------------------------------------------------------------
|
|
171
|
+
const listCmd = new Command('list')
|
|
172
|
+
.description('List all inbox message rules')
|
|
173
|
+
.option('--json', 'Output as JSON')
|
|
174
|
+
.option('--token <token>', 'Use a specific token')
|
|
175
|
+
.addHelpText(
|
|
176
|
+
'after',
|
|
177
|
+
`
|
|
178
|
+
Conditions you can filter by:
|
|
179
|
+
--bodyContains <text> Match message body contains text
|
|
180
|
+
--subjectContains <text> Match subject contains text
|
|
181
|
+
--senderContains <text> Match sender display name/address contains text
|
|
182
|
+
--recipientContains <text> Match any recipient contains text
|
|
183
|
+
--fromAddresses <emails> Match from addresses (comma-separated or JSON)
|
|
184
|
+
--sentToAddresses <emails> Match sent to addresses (comma-separated or JSON)
|
|
185
|
+
--hasAttachments <true|false> Match attachment presence
|
|
186
|
+
--importance <Low|Normal|High> Match importance
|
|
187
|
+
--isAutomaticForward <true|false> Match auto-forward messages
|
|
188
|
+
`
|
|
189
|
+
)
|
|
190
|
+
.option('--bodyContains <text>', 'Match body contains text')
|
|
191
|
+
.option('--subjectContains <text>', 'Match subject contains text')
|
|
192
|
+
.option('--senderContains <text>', 'Match sender contains text')
|
|
193
|
+
.option('--recipientContains <text>', 'Match recipient contains text')
|
|
194
|
+
.option('--fromAddresses <emails>', 'Match from addresses (comma-separated or JSON)')
|
|
195
|
+
.option('--sentToAddresses <emails>', 'Match sent to addresses (comma-separated or JSON)')
|
|
196
|
+
.option('--hasAttachments <value>', 'Match has attachments (true|false)')
|
|
197
|
+
.option('--importance <level>', 'Match importance (Low|Normal|High)')
|
|
198
|
+
.option('--isAutomaticForward <value>', 'Match is auto-forward (true|false)')
|
|
199
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
200
|
+
.option('--user <email>', 'Target mailbox for inbox rules (Graph delegation)')
|
|
201
|
+
.action(async (opts) => {
|
|
202
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
203
|
+
if (!auth.success) {
|
|
204
|
+
console.error(`Error: ${auth.error}`);
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const result = await listMessageRules(auth.token!, opts.user);
|
|
209
|
+
if (!result.ok) {
|
|
210
|
+
console.error(`Error: ${result.error?.message}`);
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
let rules = result.data || [];
|
|
215
|
+
|
|
216
|
+
// Apply client-side filtering based on condition options
|
|
217
|
+
const filterConditions = conditionsFromOpts(opts as Record<string, unknown>);
|
|
218
|
+
if (filterConditions) {
|
|
219
|
+
rules = rules.filter((rule) => {
|
|
220
|
+
if (!rule.conditions) return false;
|
|
221
|
+
const c = rule.conditions;
|
|
222
|
+
|
|
223
|
+
// Check each filter condition
|
|
224
|
+
if (
|
|
225
|
+
filterConditions.bodyContains &&
|
|
226
|
+
(!c.bodyContains || !filterConditions.bodyContains.some((term) => c.bodyContains?.includes(term)))
|
|
227
|
+
) {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
if (
|
|
231
|
+
filterConditions.subjectContains &&
|
|
232
|
+
(!c.subjectContains || !filterConditions.subjectContains.some((term) => c.subjectContains?.includes(term)))
|
|
233
|
+
) {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
if (
|
|
237
|
+
filterConditions.senderContains &&
|
|
238
|
+
(!c.senderContains || !filterConditions.senderContains.some((term) => c.senderContains?.includes(term)))
|
|
239
|
+
) {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
if (
|
|
243
|
+
filterConditions.recipientContains &&
|
|
244
|
+
(!c.recipientContains ||
|
|
245
|
+
!filterConditions.recipientContains.some((term) => c.recipientContains?.includes(term)))
|
|
246
|
+
) {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
if (
|
|
250
|
+
filterConditions.fromAddresses &&
|
|
251
|
+
(!c.fromAddresses ||
|
|
252
|
+
!filterConditions.fromAddresses.some((addr) =>
|
|
253
|
+
c.fromAddresses?.some((ruleAddr) => ruleAddr.emailAddress.address === addr.emailAddress.address)
|
|
254
|
+
))
|
|
255
|
+
) {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
if (
|
|
259
|
+
filterConditions.sentToAddresses &&
|
|
260
|
+
(!c.sentToAddresses ||
|
|
261
|
+
!filterConditions.sentToAddresses.some((addr) =>
|
|
262
|
+
c.sentToAddresses?.some((ruleAddr) => ruleAddr.emailAddress.address === addr.emailAddress.address)
|
|
263
|
+
))
|
|
264
|
+
) {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
if (filterConditions.hasAttachments !== undefined && c.hasAttachments !== filterConditions.hasAttachments) {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
if (filterConditions.importance && c.importance !== filterConditions.importance) {
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
if (
|
|
274
|
+
filterConditions.isAutomaticForward !== undefined &&
|
|
275
|
+
c.isAutomaticForward !== filterConditions.isAutomaticForward
|
|
276
|
+
) {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return true;
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (rules.length === 0) {
|
|
285
|
+
console.log(opts.json ? '[]' : 'No inbox rules found.');
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (opts.json) {
|
|
290
|
+
console.log(JSON.stringify(rules, null, 2));
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
for (const rule of rules) {
|
|
295
|
+
printRule(rule, false);
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// ---------------------------------------------------------------------------
|
|
300
|
+
// Get subcommand
|
|
301
|
+
// ---------------------------------------------------------------------------
|
|
302
|
+
const getCmd = new Command('get')
|
|
303
|
+
.description('Get a single inbox rule by ID')
|
|
304
|
+
.argument('<ruleId>', 'The rule ID')
|
|
305
|
+
.option('--json', 'Output as JSON')
|
|
306
|
+
.option('--token <token>', 'Use a specific token')
|
|
307
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
308
|
+
.option('--user <email>', 'Target mailbox for inbox rules (Graph delegation)')
|
|
309
|
+
.action(async (ruleId, opts) => {
|
|
310
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
311
|
+
if (!auth.success) {
|
|
312
|
+
console.error(`Error: ${auth.error}`);
|
|
313
|
+
process.exit(1);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const result = await getMessageRule(auth.token!, ruleId, opts.user);
|
|
317
|
+
if (!result.ok) {
|
|
318
|
+
console.error(`Error: ${result.error?.message}`);
|
|
319
|
+
process.exit(1);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
printRule(result.data!, !!opts.json);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// ---------------------------------------------------------------------------
|
|
326
|
+
// Create subcommand
|
|
327
|
+
// ---------------------------------------------------------------------------
|
|
328
|
+
const createCmd = new Command('create')
|
|
329
|
+
.description('Create a new inbox message rule')
|
|
330
|
+
.requiredOption('--name <name>', 'Rule display name')
|
|
331
|
+
.option('--priority <number>', 'Rule priority (lower = runs first)', parseInt)
|
|
332
|
+
.option('--disable', 'Create rule in disabled state')
|
|
333
|
+
.option('--json', 'Output as JSON')
|
|
334
|
+
.option('--token <token>', 'Use a specific token')
|
|
335
|
+
// Conditions
|
|
336
|
+
.option('--bodyContains <text>', 'Condition: body contains text')
|
|
337
|
+
.option('--subjectContains <text>', 'Condition: subject contains text')
|
|
338
|
+
.option('--senderContains <text>', 'Condition: sender contains text')
|
|
339
|
+
.option('--recipientContains <text>', 'Condition: recipient contains text')
|
|
340
|
+
.option('--fromAddresses <emails>', 'Condition: from addresses (comma-separated or JSON)')
|
|
341
|
+
.option('--sentToAddresses <emails>', 'Condition: sent to addresses (comma-separated or JSON)')
|
|
342
|
+
.option('--hasAttachments <value>', 'Condition: has attachments (true|false)')
|
|
343
|
+
.option('--importance <level>', 'Condition: importance (Low|Normal|High)')
|
|
344
|
+
.option('--isAutomaticForward <value>', 'Condition: is auto-forward (true|false)')
|
|
345
|
+
// Actions
|
|
346
|
+
.option('--moveToFolder <folder>', 'Action: move to folder')
|
|
347
|
+
.option('--copyToFolder <folder>', 'Action: copy to folder')
|
|
348
|
+
.option('--delete', 'Action: soft-delete')
|
|
349
|
+
.option('--permanentDelete', 'Action: permanent delete')
|
|
350
|
+
.option('--markAsRead', 'Action: mark as read')
|
|
351
|
+
.option('--markImportance <level>', 'Action: mark importance (Low|Normal|High)')
|
|
352
|
+
.option('--forwardTo <emails>', 'Action: forward to recipients (comma-separated or JSON)')
|
|
353
|
+
.option('--forwardAsAttachmentTo <emails>', 'Action: forward as attachment to recipients (comma-separated or JSON)')
|
|
354
|
+
.option('--assignCategories <cats>', 'Action: assign categories (comma-separated)')
|
|
355
|
+
.option('--stopProcessingRules', 'Action: stop processing more rules')
|
|
356
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
357
|
+
.option('--user <email>', 'Target mailbox for inbox rules (Graph delegation)')
|
|
358
|
+
.action(async (opts, cmd: any) => {
|
|
359
|
+
checkReadOnly(cmd);
|
|
360
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
361
|
+
if (!auth.success) {
|
|
362
|
+
console.error(`Error: ${auth.error}`);
|
|
363
|
+
process.exit(1);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const conditions = conditionsFromOpts(opts as Record<string, unknown>);
|
|
367
|
+
const actions = actionsFromOpts(opts as Record<string, unknown>);
|
|
368
|
+
|
|
369
|
+
if (!conditions) {
|
|
370
|
+
console.error('Error: at least one condition is required.');
|
|
371
|
+
process.exit(1);
|
|
372
|
+
}
|
|
373
|
+
if (Object.keys(actions).length === 0) {
|
|
374
|
+
console.error('Error: at least one action is required.');
|
|
375
|
+
process.exit(1);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const payload: CreateMessageRulePayload = {
|
|
379
|
+
displayName: opts.name,
|
|
380
|
+
isEnabled: !opts.disable,
|
|
381
|
+
priority: opts.priority,
|
|
382
|
+
conditions,
|
|
383
|
+
actions
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
console.log('Creating rule…');
|
|
387
|
+
if (!opts.json) {
|
|
388
|
+
console.log(` Name: ${payload.displayName}`);
|
|
389
|
+
console.log(` Priority: ${payload.priority ?? 'default'}`);
|
|
390
|
+
console.log(` Enabled: ${payload.isEnabled}`);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const result = await createMessageRule(auth.token!, payload, opts.user);
|
|
394
|
+
if (!result.ok) {
|
|
395
|
+
console.error(`Error: ${result.error?.message}`);
|
|
396
|
+
process.exit(1);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
console.log(
|
|
400
|
+
opts.json
|
|
401
|
+
? JSON.stringify(result.data, null, 2)
|
|
402
|
+
: `\u2713 Rule created: ${result.data!.displayName} (${result.data!.id})`
|
|
403
|
+
);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// ---------------------------------------------------------------------------
|
|
407
|
+
// Update subcommand
|
|
408
|
+
// ---------------------------------------------------------------------------
|
|
409
|
+
const updateCmd = new Command('update')
|
|
410
|
+
.description('Update an existing inbox message rule')
|
|
411
|
+
.requiredOption('--id <ruleId>', 'The rule ID to update')
|
|
412
|
+
.option('--name <name>', 'New rule display name')
|
|
413
|
+
.option('--priority <number>', 'New rule priority', parseInt)
|
|
414
|
+
.option('--enable', 'Enable the rule')
|
|
415
|
+
.option('--disable', 'Disable the rule')
|
|
416
|
+
.option('--json', 'Output as JSON')
|
|
417
|
+
.option('--token <token>', 'Use a specific token')
|
|
418
|
+
// Conditions (replace all)
|
|
419
|
+
.option('--bodyContains <text>', 'Condition: body contains text')
|
|
420
|
+
.option('--subjectContains <text>', 'Condition: subject contains text')
|
|
421
|
+
.option('--senderContains <text>', 'Condition: sender contains text')
|
|
422
|
+
.option('--recipientContains <text>', 'Condition: recipient contains text')
|
|
423
|
+
.option('--fromAddresses <emails>', 'Condition: from addresses (comma-separated or JSON)')
|
|
424
|
+
.option('--sentToAddresses <emails>', 'Condition: sent to addresses (comma-separated or JSON)')
|
|
425
|
+
.option('--hasAttachments <value>', 'Condition: has attachments (true|false)')
|
|
426
|
+
.option('--importance <level>', 'Condition: importance (Low|Normal|High)')
|
|
427
|
+
.option('--isAutomaticForward <value>', 'Condition: is auto-forward (true|false)')
|
|
428
|
+
// Actions (replace all)
|
|
429
|
+
.option('--moveToFolder <folder>', 'Action: move to folder')
|
|
430
|
+
.option('--copyToFolder <folder>', 'Action: copy to folder')
|
|
431
|
+
.option('--delete', 'Action: soft-delete')
|
|
432
|
+
.option('--permanentDelete', 'Action: permanent delete')
|
|
433
|
+
.option('--markAsRead', 'Action: mark as read')
|
|
434
|
+
.option('--markImportance <level>', 'Action: mark importance (Low|Normal|High)')
|
|
435
|
+
.option('--forwardTo <emails>', 'Action: forward to recipients (comma-separated or JSON)')
|
|
436
|
+
.option('--forwardAsAttachmentTo <emails>', 'Action: forward as attachment to recipients (comma-separated or JSON)')
|
|
437
|
+
.option('--assignCategories <cats>', 'Action: assign categories (comma-separated)')
|
|
438
|
+
.option('--stopProcessingRules', 'Action: stop processing more rules')
|
|
439
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
440
|
+
.option('--user <email>', 'Target mailbox for inbox rules (Graph delegation)')
|
|
441
|
+
.action(async (opts, cmd: any) => {
|
|
442
|
+
checkReadOnly(cmd);
|
|
443
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
444
|
+
if (!auth.success) {
|
|
445
|
+
console.error(`Error: ${auth.error}`);
|
|
446
|
+
process.exit(1);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (opts.enable && opts.disable) {
|
|
450
|
+
console.error('Error: --enable and --disable cannot be used together.');
|
|
451
|
+
process.exit(1);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const conditions = conditionsFromOpts(opts as Record<string, unknown>);
|
|
455
|
+
const actions = actionsFromOpts(opts as Record<string, unknown>);
|
|
456
|
+
|
|
457
|
+
const payload: UpdateMessageRulePayload = {};
|
|
458
|
+
if (opts.name) payload.displayName = opts.name;
|
|
459
|
+
if (opts.priority !== undefined) payload.priority = opts.priority;
|
|
460
|
+
if (opts.enable) payload.isEnabled = true;
|
|
461
|
+
if (opts.disable) payload.isEnabled = false;
|
|
462
|
+
if (conditions) payload.conditions = conditions;
|
|
463
|
+
if (Object.keys(actions).length > 0) payload.actions = actions;
|
|
464
|
+
|
|
465
|
+
console.log(`Updating rule ${opts.id}…`);
|
|
466
|
+
const result = await updateMessageRule(auth.token!, opts.id, payload, opts.user);
|
|
467
|
+
if (!result.ok) {
|
|
468
|
+
console.error(`Error: ${result.error?.message}`);
|
|
469
|
+
process.exit(1);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
console.log(opts.json ? JSON.stringify(result.data, null, 2) : `\u2713 Rule updated: ${result.data!.displayName}`);
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
// ---------------------------------------------------------------------------
|
|
476
|
+
// Delete subcommand
|
|
477
|
+
// ---------------------------------------------------------------------------
|
|
478
|
+
const deleteCmd = new Command('delete')
|
|
479
|
+
.description('Delete an inbox message rule')
|
|
480
|
+
.argument('<ruleId>', 'The rule ID to delete')
|
|
481
|
+
.option('--token <token>', 'Use a specific token')
|
|
482
|
+
.option('--json', 'Output as JSON')
|
|
483
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
484
|
+
.option('--user <email>', 'Target mailbox for inbox rules (Graph delegation)')
|
|
485
|
+
.action(async (ruleId, opts, cmd: any) => {
|
|
486
|
+
checkReadOnly(cmd);
|
|
487
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
488
|
+
if (!auth.success) {
|
|
489
|
+
console.error(`Error: ${auth.error}`);
|
|
490
|
+
process.exit(1);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const result = await deleteMessageRule(auth.token!, ruleId, opts.user);
|
|
494
|
+
if (!result.ok) {
|
|
495
|
+
console.error(`Error: ${result.error?.message}`);
|
|
496
|
+
process.exit(1);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
console.log(opts.json ? JSON.stringify({ deleted: ruleId }) : `\u2713 Rule deleted: ${ruleId}`);
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
// ---------------------------------------------------------------------------
|
|
503
|
+
// Main command
|
|
504
|
+
// ---------------------------------------------------------------------------
|
|
505
|
+
export const rulesCommand = new Command('rules')
|
|
506
|
+
.description('Manage server-side inbox message rules (Graph messageRules API)')
|
|
507
|
+
.addCommand(listCmd)
|
|
508
|
+
.addCommand(getCmd)
|
|
509
|
+
.addCommand(createCmd)
|
|
510
|
+
.addCommand(updateCmd)
|
|
511
|
+
.addCommand(deleteCmd);
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { resolveGraphAuth } from '../lib/graph-auth.js';
|
|
3
|
+
import { getSchedule } from '../lib/graph-schedule.js';
|
|
4
|
+
|
|
5
|
+
export const scheduleCommand = new Command('schedule')
|
|
6
|
+
.description('Get merged free/busy schedule for multiple users')
|
|
7
|
+
.argument('<emails...>', 'One or more email addresses to check')
|
|
8
|
+
.requiredOption('--start <date>', 'Start date/time (e.g. 2026-04-01T00:00:00Z or 2026-04-01)')
|
|
9
|
+
.requiredOption('--end <date>', 'End date/time (e.g. 2026-04-07T00:00:00Z or 2026-04-07)')
|
|
10
|
+
.option('--json', 'Output as JSON')
|
|
11
|
+
.option('--token <token>', 'Graph access token (bypass interactive auth)')
|
|
12
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
13
|
+
.option('--user <email>', 'Mailbox whose calendar getSchedule runs in (delegation); omit to use signed-in user')
|
|
14
|
+
.action(
|
|
15
|
+
async (
|
|
16
|
+
emails: string[],
|
|
17
|
+
options: { start: string; end: string; json?: boolean; token?: string; identity?: string; user?: string }
|
|
18
|
+
) => {
|
|
19
|
+
const authResult = await resolveGraphAuth({ token: options.token, identity: options.identity });
|
|
20
|
+
if (!authResult.success || !authResult.token) {
|
|
21
|
+
if (options.json) {
|
|
22
|
+
console.log(JSON.stringify({ error: authResult.error }, null, 2));
|
|
23
|
+
} else {
|
|
24
|
+
console.error(`Error: ${authResult.error}`);
|
|
25
|
+
}
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const startDate = new Date(options.start);
|
|
30
|
+
const endDate = new Date(options.end);
|
|
31
|
+
|
|
32
|
+
if (Number.isNaN(startDate.getTime()) || Number.isNaN(endDate.getTime())) {
|
|
33
|
+
const errorMessage =
|
|
34
|
+
'Invalid start or end date. Please provide ISO 8601 date/time values (e.g. 2026-04-01T00:00:00Z or 2026-04-01).';
|
|
35
|
+
if (options.json) {
|
|
36
|
+
console.log(JSON.stringify({ error: errorMessage }, null, 2));
|
|
37
|
+
} else {
|
|
38
|
+
console.error(`Error: ${errorMessage}`);
|
|
39
|
+
}
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// dateTime should not include Z/offset - keep dateTime and timeZone separate
|
|
44
|
+
const startDateTime = startDate.toISOString().replace('Z', '');
|
|
45
|
+
const endDateTime = endDate.toISOString().replace('Z', '');
|
|
46
|
+
|
|
47
|
+
const result = await getSchedule(
|
|
48
|
+
authResult.token,
|
|
49
|
+
{
|
|
50
|
+
schedules: emails,
|
|
51
|
+
startTime: {
|
|
52
|
+
dateTime: startDateTime,
|
|
53
|
+
timeZone: 'UTC'
|
|
54
|
+
},
|
|
55
|
+
endTime: {
|
|
56
|
+
dateTime: endDateTime,
|
|
57
|
+
timeZone: 'UTC'
|
|
58
|
+
},
|
|
59
|
+
availabilityViewInterval: 60
|
|
60
|
+
},
|
|
61
|
+
options.user
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
if (!result.ok || !result.data) {
|
|
65
|
+
if (options.json) {
|
|
66
|
+
console.log(JSON.stringify({ error: result.error }, null, 2));
|
|
67
|
+
} else {
|
|
68
|
+
console.error('Error fetching schedule:', result.error?.message || 'Unknown error');
|
|
69
|
+
}
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (options.json) {
|
|
74
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
console.log(`\nSchedule for ${emails.join(', ')}`);
|
|
79
|
+
console.log(`From: ${startDateTime}\nTo: ${endDateTime}\n`);
|
|
80
|
+
|
|
81
|
+
for (const schedule of result.data.value) {
|
|
82
|
+
console.log(`User: ${schedule.scheduleId}`);
|
|
83
|
+
if (schedule.error) {
|
|
84
|
+
console.log(` Error: ${schedule.error.message || schedule.error.responseCode}`);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (schedule.workingHours) {
|
|
89
|
+
const wh = schedule.workingHours;
|
|
90
|
+
const days = wh.daysOfWeek?.join(', ') || 'N/A';
|
|
91
|
+
console.log(` Working Hours: ${wh.startTime} - ${wh.endTime} (${wh.timeZone?.name}) on ${days}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (schedule.scheduleItems && schedule.scheduleItems.length > 0) {
|
|
95
|
+
console.log(' Busy times:');
|
|
96
|
+
for (const item of schedule.scheduleItems) {
|
|
97
|
+
const status = item.status || 'Busy';
|
|
98
|
+
const start = item.start?.dateTime ? new Date(`${item.start.dateTime}Z`).toLocaleString() : 'Unknown';
|
|
99
|
+
const end = item.end?.dateTime ? new Date(`${item.end.dateTime}Z`).toLocaleString() : 'Unknown';
|
|
100
|
+
const subject = item.subject ? ` - ${item.subject}` : '';
|
|
101
|
+
console.log(` [${status}] ${start} to ${end}${subject}`);
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
console.log(' No busy times scheduled.');
|
|
105
|
+
}
|
|
106
|
+
console.log();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
);
|