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,204 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { AttachmentLinkSpecError, parseAttachLinkSpec } from '../lib/attach-link-spec.js';
|
|
4
|
+
import { AttachmentPathError, validateAttachmentPath } from '../lib/attachments.js';
|
|
5
|
+
import { resolveAuth } from '../lib/auth.js';
|
|
6
|
+
import { type EmailAttachment, type ReferenceAttachmentInput, sendEmail } from '../lib/ews-client.js';
|
|
7
|
+
import { markdownToHtml } from '../lib/markdown.js';
|
|
8
|
+
import { lookupMimeType } from '../lib/mime-type.js';
|
|
9
|
+
import { checkReadOnly } from '../lib/utils.js';
|
|
10
|
+
|
|
11
|
+
export const sendCommand = new Command('send')
|
|
12
|
+
.description('Send an email')
|
|
13
|
+
.requiredOption('--to <emails>', 'Recipient email(s), comma-separated')
|
|
14
|
+
.requiredOption('--subject <text>', 'Email subject')
|
|
15
|
+
.option('--body <text>', 'Email body', '')
|
|
16
|
+
.option('--category <name>', 'Category label (repeatable)', (v, acc) => [...acc, v], [] as string[])
|
|
17
|
+
.option('--cc <emails>', 'CC recipient(s), comma-separated')
|
|
18
|
+
.option('--bcc <emails>', 'BCC recipient(s), comma-separated')
|
|
19
|
+
.option('--attach <files>', 'Attach file(s), comma-separated paths')
|
|
20
|
+
.option(
|
|
21
|
+
'--attach-link <spec>',
|
|
22
|
+
'Attach link: "Title|https://url" or bare https URL (repeatable)',
|
|
23
|
+
(v: string, prev: string[]) => [...prev, v],
|
|
24
|
+
[] as string[]
|
|
25
|
+
)
|
|
26
|
+
.option('--html', 'Send body as HTML')
|
|
27
|
+
.option('--markdown', 'Parse body as markdown (bold, links, lists)')
|
|
28
|
+
.option('--json', 'Output as JSON')
|
|
29
|
+
.option('--token <token>', 'Use a specific token')
|
|
30
|
+
.option('--mailbox <email>', 'Send from shared mailbox (Send As)')
|
|
31
|
+
.option('--identity <name>', 'Use a specific authentication identity (default: default)')
|
|
32
|
+
.action(
|
|
33
|
+
async (
|
|
34
|
+
options: {
|
|
35
|
+
to: string;
|
|
36
|
+
subject: string;
|
|
37
|
+
body?: string;
|
|
38
|
+
cc?: string;
|
|
39
|
+
bcc?: string;
|
|
40
|
+
attach?: string;
|
|
41
|
+
attachLink?: string[];
|
|
42
|
+
html?: boolean;
|
|
43
|
+
markdown?: boolean;
|
|
44
|
+
json?: boolean;
|
|
45
|
+
token?: string;
|
|
46
|
+
mailbox?: string;
|
|
47
|
+
identity?: string;
|
|
48
|
+
category?: string[];
|
|
49
|
+
},
|
|
50
|
+
cmd: any
|
|
51
|
+
) => {
|
|
52
|
+
checkReadOnly(cmd);
|
|
53
|
+
const authResult = await resolveAuth({
|
|
54
|
+
token: options.token,
|
|
55
|
+
identity: options.identity
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
if (!authResult.success) {
|
|
59
|
+
if (options.json) {
|
|
60
|
+
console.log(JSON.stringify({ error: authResult.error }, null, 2));
|
|
61
|
+
} else {
|
|
62
|
+
console.error(`Error: ${authResult.error}`);
|
|
63
|
+
console.error('\nCheck your .env file for EWS_CLIENT_ID and EWS_REFRESH_TOKEN.');
|
|
64
|
+
}
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const toList = options.to
|
|
69
|
+
.split(',')
|
|
70
|
+
.map((e) => e.trim())
|
|
71
|
+
.filter(Boolean);
|
|
72
|
+
const ccList = options.cc
|
|
73
|
+
? options.cc
|
|
74
|
+
.split(',')
|
|
75
|
+
.map((e) => e.trim())
|
|
76
|
+
.filter(Boolean)
|
|
77
|
+
: undefined;
|
|
78
|
+
const bccList = options.bcc
|
|
79
|
+
? options.bcc
|
|
80
|
+
.split(',')
|
|
81
|
+
.map((e) => e.trim())
|
|
82
|
+
.filter(Boolean)
|
|
83
|
+
: undefined;
|
|
84
|
+
|
|
85
|
+
if (toList.length === 0) {
|
|
86
|
+
console.error('At least one recipient is required.');
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let body = options.body ?? '';
|
|
91
|
+
let bodyType: 'Text' | 'HTML' = 'Text';
|
|
92
|
+
|
|
93
|
+
if (options.markdown) {
|
|
94
|
+
body = markdownToHtml(body);
|
|
95
|
+
bodyType = 'HTML';
|
|
96
|
+
} else if (options.html) {
|
|
97
|
+
bodyType = 'HTML';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Process attachments
|
|
101
|
+
let attachments: EmailAttachment[] | undefined;
|
|
102
|
+
const workingDirectory = process.cwd();
|
|
103
|
+
if (options.attach) {
|
|
104
|
+
const filePaths = options.attach
|
|
105
|
+
.split(',')
|
|
106
|
+
.map((f) => f.trim())
|
|
107
|
+
.filter(Boolean);
|
|
108
|
+
attachments = [];
|
|
109
|
+
|
|
110
|
+
for (const filePath of filePaths) {
|
|
111
|
+
try {
|
|
112
|
+
const validated = await validateAttachmentPath(filePath, workingDirectory);
|
|
113
|
+
const content = await readFile(validated.absolutePath);
|
|
114
|
+
const contentType = lookupMimeType(validated.fileName);
|
|
115
|
+
|
|
116
|
+
attachments.push({
|
|
117
|
+
name: validated.fileName,
|
|
118
|
+
contentType,
|
|
119
|
+
contentBytes: content.toString('base64')
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
if (!options.json) {
|
|
123
|
+
console.log(` Attaching: ${validated.fileName} (${Math.round(validated.size / 1024)} KB)`);
|
|
124
|
+
}
|
|
125
|
+
} catch (err) {
|
|
126
|
+
console.error(`Failed to read attachment: ${filePath}`);
|
|
127
|
+
if (err instanceof AttachmentPathError) {
|
|
128
|
+
console.error(err.message);
|
|
129
|
+
} else {
|
|
130
|
+
console.error(err instanceof Error ? err.message : 'Unknown error');
|
|
131
|
+
}
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let referenceAttachments: ReferenceAttachmentInput[] | undefined;
|
|
138
|
+
const linkSpecs = options.attachLink ?? [];
|
|
139
|
+
if (linkSpecs.length > 0) {
|
|
140
|
+
referenceAttachments = [];
|
|
141
|
+
for (const spec of linkSpecs) {
|
|
142
|
+
try {
|
|
143
|
+
const { name, url } = parseAttachLinkSpec(spec);
|
|
144
|
+
referenceAttachments.push({ name, url, contentType: 'text/html' });
|
|
145
|
+
if (!options.json) {
|
|
146
|
+
console.log(` Attaching link: ${name}`);
|
|
147
|
+
}
|
|
148
|
+
} catch (err) {
|
|
149
|
+
const msg =
|
|
150
|
+
err instanceof AttachmentLinkSpecError ? err.message : err instanceof Error ? err.message : String(err);
|
|
151
|
+
console.error(`Invalid --attach-link: ${msg}`);
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const result = await sendEmail(authResult.token!, {
|
|
158
|
+
to: toList,
|
|
159
|
+
cc: ccList,
|
|
160
|
+
bcc: bccList,
|
|
161
|
+
subject: options.subject,
|
|
162
|
+
body,
|
|
163
|
+
bodyType,
|
|
164
|
+
attachments,
|
|
165
|
+
referenceAttachments,
|
|
166
|
+
mailbox: options.mailbox,
|
|
167
|
+
categories: options.category && options.category.length > 0 ? options.category : undefined
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
if (!result.ok) {
|
|
171
|
+
if (options.json) {
|
|
172
|
+
console.log(JSON.stringify({ error: result.error?.message || 'Failed to send email' }, null, 2));
|
|
173
|
+
} else {
|
|
174
|
+
console.error(`Error: ${result.error?.message || 'Failed to send email'}`);
|
|
175
|
+
}
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (options.json) {
|
|
180
|
+
console.log(
|
|
181
|
+
JSON.stringify(
|
|
182
|
+
{
|
|
183
|
+
success: true,
|
|
184
|
+
to: toList,
|
|
185
|
+
subject: options.subject,
|
|
186
|
+
attachments: attachments?.map((a) => a.name),
|
|
187
|
+
attachLinks: referenceAttachments?.map((a) => a.name)
|
|
188
|
+
},
|
|
189
|
+
null,
|
|
190
|
+
2
|
|
191
|
+
)
|
|
192
|
+
);
|
|
193
|
+
} else {
|
|
194
|
+
console.log(`\n\u2713 Email sent to ${toList.join(', ')}`);
|
|
195
|
+
console.log(` Subject: ${options.subject}`);
|
|
196
|
+
const nFile = attachments?.length ?? 0;
|
|
197
|
+
const nLink = referenceAttachments?.length ?? 0;
|
|
198
|
+
if (nFile + nLink > 0) {
|
|
199
|
+
console.log(` Attachments: ${nFile} file(s), ${nLink} link(s)`);
|
|
200
|
+
}
|
|
201
|
+
console.log();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { startWebhookServer } from '../lib/webhook-server.js';
|
|
3
|
+
|
|
4
|
+
export const serveCommand = new Command('serve')
|
|
5
|
+
.description('Start the webhook receiver server')
|
|
6
|
+
.option('-p, --port <port>', 'Port to listen on', '3000')
|
|
7
|
+
.action((options) => {
|
|
8
|
+
const port = parseInt(options.port, 10);
|
|
9
|
+
if (Number.isNaN(port) || port <= 0 || port > 65535) {
|
|
10
|
+
console.error(`Invalid port "${options.port}". Please provide an integer between 1 and 65535.`);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
startWebhookServer(port);
|
|
14
|
+
});
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { resolveGraphAuth } from '../lib/graph-auth.js';
|
|
3
|
+
import { createListItem, getListItems, getLists, updateListItem } from '../lib/sharepoint-client.js';
|
|
4
|
+
import { checkReadOnly } from '../lib/utils.js';
|
|
5
|
+
|
|
6
|
+
export const sharepointCommand = new Command('sharepoint').description('Manage Microsoft SharePoint Lists').alias('sp');
|
|
7
|
+
|
|
8
|
+
sharepointCommand
|
|
9
|
+
.command('lists')
|
|
10
|
+
.description('List all SharePoint lists in a site')
|
|
11
|
+
.requiredOption('--site-id <id>', 'SharePoint Site ID')
|
|
12
|
+
.option('--json', 'Output as JSON')
|
|
13
|
+
.option('--token <token>', 'Use a specific token')
|
|
14
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
15
|
+
.action(async (opts: { siteId: string; json?: boolean; token?: string; identity?: string }) => {
|
|
16
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
17
|
+
if (!auth.success || !auth.token) {
|
|
18
|
+
console.error(`Auth error: ${auth.error || 'Unknown error'}`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
const res = await getLists(auth.token, opts.siteId);
|
|
22
|
+
if (!res.ok) {
|
|
23
|
+
console.error(`Error listing lists: ${res.error?.message || 'Unknown error'}`);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
if (opts.json) {
|
|
27
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (!res.data || res.data.length === 0) {
|
|
31
|
+
console.log('No lists found in this site.');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
for (const list of res.data) {
|
|
35
|
+
console.log(`${list.name} (${list.id})`);
|
|
36
|
+
if (list.description) console.log(` ${list.description}`);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
sharepointCommand
|
|
41
|
+
.command('items')
|
|
42
|
+
.description('Get items from a SharePoint list')
|
|
43
|
+
.requiredOption('--site-id <id>', 'SharePoint Site ID')
|
|
44
|
+
.requiredOption('--list-id <id>', 'SharePoint List ID')
|
|
45
|
+
.option('--json', 'Output as JSON')
|
|
46
|
+
.option('--token <token>', 'Use a specific token')
|
|
47
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
48
|
+
.action(async (opts: { siteId: string; listId: string; json?: boolean; token?: string; identity?: string }) => {
|
|
49
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
50
|
+
if (!auth.success || !auth.token) {
|
|
51
|
+
console.error(`Auth error: ${auth.error || 'Unknown error'}`);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
const res = await getListItems(auth.token, opts.siteId, opts.listId);
|
|
55
|
+
if (!res.ok) {
|
|
56
|
+
console.error(`Error getting list items: ${res.error?.message || 'Unknown error'}`);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
if (opts.json) {
|
|
60
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (!res.data || res.data.length === 0) {
|
|
64
|
+
console.log('No items found in this list.');
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
for (const item of res.data) {
|
|
68
|
+
console.log(`Item ID: ${item.id}`);
|
|
69
|
+
if (item.fields) {
|
|
70
|
+
for (const [key, val] of Object.entries(item.fields)) {
|
|
71
|
+
if (!key.startsWith('@odata')) {
|
|
72
|
+
console.log(` ${key}: ${val}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
console.log('---');
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
sharepointCommand
|
|
81
|
+
.command('create-item')
|
|
82
|
+
.description('Create an item in a SharePoint list')
|
|
83
|
+
.requiredOption('--site-id <id>', 'SharePoint Site ID')
|
|
84
|
+
.requiredOption('--list-id <id>', 'SharePoint List ID')
|
|
85
|
+
.requiredOption('--fields <json>', 'JSON string of fields to set (e.g. \'{"Title": "My Item"}\')')
|
|
86
|
+
.option('--json', 'Output as JSON')
|
|
87
|
+
.option('--token <token>', 'Use a specific token')
|
|
88
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
89
|
+
.action(
|
|
90
|
+
async (
|
|
91
|
+
opts: { siteId: string; listId: string; fields: string; json?: boolean; token?: string; identity?: string },
|
|
92
|
+
cmd: any
|
|
93
|
+
) => {
|
|
94
|
+
checkReadOnly(cmd);
|
|
95
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
96
|
+
if (!auth.success || !auth.token) {
|
|
97
|
+
console.error(`Auth error: ${auth.error || 'Unknown error'}`);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
let parsedFields: Record<string, any>;
|
|
101
|
+
try {
|
|
102
|
+
parsedFields = JSON.parse(opts.fields);
|
|
103
|
+
} catch (err: any) {
|
|
104
|
+
console.error(`Error parsing fields JSON: ${err.message}`);
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
if (typeof parsedFields !== 'object' || parsedFields === null || Array.isArray(parsedFields)) {
|
|
108
|
+
console.error(
|
|
109
|
+
'Error: --fields JSON must be an object (e.g. "{"Title": "New Title"}"), not an array or primitive.'
|
|
110
|
+
);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
const res = await createListItem(auth.token, opts.siteId, opts.listId, parsedFields);
|
|
114
|
+
if (!res.ok) {
|
|
115
|
+
console.error(`Error creating list item: ${res.error?.message || 'Unknown error'}`);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
if (opts.json) {
|
|
119
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
console.log(`Successfully created item ${res.data?.id}`);
|
|
123
|
+
}
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
sharepointCommand
|
|
127
|
+
.command('update-item')
|
|
128
|
+
.description('Update an item in a SharePoint list')
|
|
129
|
+
.requiredOption('--site-id <id>', 'SharePoint Site ID')
|
|
130
|
+
.requiredOption('--list-id <id>', 'SharePoint List ID')
|
|
131
|
+
.requiredOption('--item-id <id>', 'SharePoint List Item ID')
|
|
132
|
+
.requiredOption('--fields <json>', 'JSON string of fields to set (e.g. \'{"Title": "New Title"}\')')
|
|
133
|
+
.option('--json', 'Output as JSON')
|
|
134
|
+
.option('--token <token>', 'Use a specific token')
|
|
135
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
136
|
+
.action(
|
|
137
|
+
async (
|
|
138
|
+
opts: {
|
|
139
|
+
siteId: string;
|
|
140
|
+
listId: string;
|
|
141
|
+
itemId: string;
|
|
142
|
+
fields: string;
|
|
143
|
+
json?: boolean;
|
|
144
|
+
token?: string;
|
|
145
|
+
identity?: string;
|
|
146
|
+
},
|
|
147
|
+
cmd: any
|
|
148
|
+
) => {
|
|
149
|
+
checkReadOnly(cmd);
|
|
150
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
151
|
+
if (!auth.success || !auth.token) {
|
|
152
|
+
console.error(`Auth error: ${auth.error || 'Unknown error'}`);
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
let parsedFields: Record<string, any>;
|
|
156
|
+
try {
|
|
157
|
+
parsedFields = JSON.parse(opts.fields);
|
|
158
|
+
} catch (err: any) {
|
|
159
|
+
console.error(`Error parsing fields JSON: ${err.message}`);
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
if (typeof parsedFields !== 'object' || parsedFields === null || Array.isArray(parsedFields)) {
|
|
163
|
+
console.error(
|
|
164
|
+
'Error: --fields JSON must be an object (e.g. "{"Title": "New Title"}"), not an array or primitive.'
|
|
165
|
+
);
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
const res = await updateListItem(auth.token, opts.siteId, opts.listId, opts.itemId, parsedFields);
|
|
169
|
+
if (!res.ok) {
|
|
170
|
+
console.error(`Error updating list item: ${res.error?.message || 'Unknown error'}`);
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
if (opts.json) {
|
|
174
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
console.log(`Successfully updated item ${opts.itemId}`);
|
|
178
|
+
}
|
|
179
|
+
);
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { resolveGraphAuth } from '../lib/graph-auth.js';
|
|
3
|
+
import {
|
|
4
|
+
getSitePage,
|
|
5
|
+
listSitePages,
|
|
6
|
+
publishSitePage,
|
|
7
|
+
type SitePage,
|
|
8
|
+
updateSitePage
|
|
9
|
+
} from '../lib/site-pages-client.js';
|
|
10
|
+
import { checkReadOnly } from '../lib/utils.js';
|
|
11
|
+
|
|
12
|
+
export const sitePagesCommand = new Command('pages').description('Manage SharePoint Site Pages');
|
|
13
|
+
|
|
14
|
+
sitePagesCommand
|
|
15
|
+
.command('list <siteId>')
|
|
16
|
+
.description('List site pages for a given site ID')
|
|
17
|
+
.option('--json', 'Output as JSON')
|
|
18
|
+
.option('--token <token>', 'Use a specific Graph token')
|
|
19
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
20
|
+
.action(async (siteId: string, options: { json?: boolean; token?: string; identity?: string }) => {
|
|
21
|
+
const auth = await resolveGraphAuth({ token: options.token, identity: options.identity });
|
|
22
|
+
if (!auth.success) {
|
|
23
|
+
console.error(`Error: ${auth.error}`);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const result = await listSitePages(auth.token!, siteId);
|
|
28
|
+
if (!result.ok || !result.data) {
|
|
29
|
+
console.error(`Error: ${result.error?.message || 'Request failed'}`);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (options.json) {
|
|
34
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!result.data || result.data.length === 0) {
|
|
39
|
+
console.log('No pages found.');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
for (const page of result.data) {
|
|
44
|
+
const state = page.publishingState
|
|
45
|
+
? `${page.publishingState.level} (v${page.publishingState.versionId})`
|
|
46
|
+
: 'Unknown';
|
|
47
|
+
console.log(`- ${page.name || page.title || page.id} (${page.id}) - State: ${state}`);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
sitePagesCommand
|
|
52
|
+
.command('get <siteId> <pageId>')
|
|
53
|
+
.description('Get a site page by ID')
|
|
54
|
+
.option('--json', 'Output as JSON')
|
|
55
|
+
.option('--token <token>', 'Use a specific Graph token')
|
|
56
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
57
|
+
.action(async (siteId: string, pageId: string, options: { json?: boolean; token?: string; identity?: string }) => {
|
|
58
|
+
const auth = await resolveGraphAuth({ token: options.token, identity: options.identity });
|
|
59
|
+
if (!auth.success) {
|
|
60
|
+
console.error(`Error: ${auth.error}`);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const result = await getSitePage(auth.token!, siteId, pageId);
|
|
65
|
+
if (!result.ok || !result.data) {
|
|
66
|
+
console.error(`Error: ${result.error?.message || 'Request failed'}`);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (options.json) {
|
|
71
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
console.log(`ID: ${result.data.id}`);
|
|
76
|
+
console.log(`Name: ${result.data.name || '-'}`);
|
|
77
|
+
console.log(`Title: ${result.data.title || '-'}`);
|
|
78
|
+
console.log(`Web URL: ${result.data.webUrl || '-'}`);
|
|
79
|
+
if (result.data.publishingState) {
|
|
80
|
+
console.log(`State: ${result.data.publishingState.level} (v${result.data.publishingState.versionId})`);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
sitePagesCommand
|
|
85
|
+
.command('update <siteId> <pageId>')
|
|
86
|
+
.description('Update a site page')
|
|
87
|
+
.option('--title <title>', 'New title')
|
|
88
|
+
.option('--name <name>', 'New name')
|
|
89
|
+
.option('--json', 'Output as JSON')
|
|
90
|
+
.option('--token <token>', 'Use a specific Graph token')
|
|
91
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
92
|
+
.action(
|
|
93
|
+
async (
|
|
94
|
+
siteId: string,
|
|
95
|
+
pageId: string,
|
|
96
|
+
options: { title?: string; name?: string; json?: boolean; token?: string; identity?: string },
|
|
97
|
+
cmd: any
|
|
98
|
+
) => {
|
|
99
|
+
checkReadOnly(cmd);
|
|
100
|
+
const auth = await resolveGraphAuth({ token: options.token, identity: options.identity });
|
|
101
|
+
if (!auth.success) {
|
|
102
|
+
console.error(`Error: ${auth.error}`);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const payload: Partial<SitePage> = {};
|
|
107
|
+
if (options.title) payload.title = options.title;
|
|
108
|
+
if (options.name) payload.name = options.name;
|
|
109
|
+
|
|
110
|
+
if (Object.keys(payload).length === 0) {
|
|
111
|
+
console.error('Error: Please provide at least one field to update (--title or --name)');
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const result = await updateSitePage(auth.token!, siteId, pageId, payload);
|
|
116
|
+
if (!result.ok || !result.data) {
|
|
117
|
+
console.error(`Error: ${result.error?.message || 'Request failed'}`);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (options.json) {
|
|
122
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
console.log(`✓ Updated page ${pageId}`);
|
|
127
|
+
}
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
sitePagesCommand
|
|
131
|
+
.command('publish <siteId> <pageId>')
|
|
132
|
+
.description('Publish a site page')
|
|
133
|
+
.option('--json', 'Output as JSON')
|
|
134
|
+
.option('--token <token>', 'Use a specific Graph token')
|
|
135
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
136
|
+
.action(
|
|
137
|
+
async (
|
|
138
|
+
siteId: string,
|
|
139
|
+
pageId: string,
|
|
140
|
+
options: { json?: boolean; token?: string; identity?: string },
|
|
141
|
+
cmd: any
|
|
142
|
+
) => {
|
|
143
|
+
checkReadOnly(cmd);
|
|
144
|
+
const auth = await resolveGraphAuth({ token: options.token, identity: options.identity });
|
|
145
|
+
if (!auth.success) {
|
|
146
|
+
console.error(`Error: ${auth.error}`);
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const result = await publishSitePage(auth.token!, siteId, pageId);
|
|
151
|
+
if (!result.ok) {
|
|
152
|
+
console.error(`Error: ${result.error?.message || 'Request failed'}`);
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (options.json) {
|
|
157
|
+
console.log(JSON.stringify({ ok: true }, null, 2));
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
console.log(`✓ Published page ${pageId}`);
|
|
162
|
+
}
|
|
163
|
+
);
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { createSubscription, deleteSubscription } from '../lib/graph-subscriptions.js';
|
|
3
|
+
import { checkReadOnly } from '../lib/utils.js';
|
|
4
|
+
|
|
5
|
+
export const subscribeCommand = new Command('subscribe')
|
|
6
|
+
.description('Subscribe to Microsoft Graph push notifications')
|
|
7
|
+
.argument('[resource]', 'Resource to subscribe to (e.g. mail, event, contact, todoTask)')
|
|
8
|
+
.option('--url <url>', 'Webhook notification URL')
|
|
9
|
+
.option('--expiry <datetime>', 'Expiration datetime (ISO 8601, defaults to 3 days from now)')
|
|
10
|
+
.option('--change-type <type>', 'Change type (comma-separated)', 'created,updated')
|
|
11
|
+
.option('--token <token>', 'Use a specific token')
|
|
12
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
13
|
+
.option('--user <email>', 'Subscribe under this user or shared mailbox (users/{id}/...)')
|
|
14
|
+
.action(async (resource, options, cmd) => {
|
|
15
|
+
if (!resource) {
|
|
16
|
+
return cmd.help();
|
|
17
|
+
}
|
|
18
|
+
if (!options.url) {
|
|
19
|
+
console.error('Error: --url is required.');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
checkReadOnly(cmd);
|
|
24
|
+
|
|
25
|
+
// Map friendly resource names to graph endpoints
|
|
26
|
+
const mapResource = (res: string, user?: string) => {
|
|
27
|
+
const prefix = user?.trim() ? `users/${encodeURIComponent(user.trim())}` : 'me';
|
|
28
|
+
switch (res.toLowerCase()) {
|
|
29
|
+
case 'mail':
|
|
30
|
+
return `${prefix}/messages`;
|
|
31
|
+
case 'event':
|
|
32
|
+
return `${prefix}/events`;
|
|
33
|
+
case 'contact':
|
|
34
|
+
return `${prefix}/contacts`;
|
|
35
|
+
case 'todotask':
|
|
36
|
+
// Note: Todo subscriptions require a specific list ID.
|
|
37
|
+
// Use the format: me/todo/lists/{listId}/tasks
|
|
38
|
+
// For the default Tasks list, use: me/todo/lists/Tasks/tasks
|
|
39
|
+
return `${prefix}/todo/lists/Tasks/tasks`;
|
|
40
|
+
default:
|
|
41
|
+
return res;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// Generate clientState for subscription validation (if GRAPH_CLIENT_STATE env is set)
|
|
46
|
+
const clientState = process.env.GRAPH_CLIENT_STATE;
|
|
47
|
+
|
|
48
|
+
const graphResource = mapResource(resource, options.user);
|
|
49
|
+
|
|
50
|
+
// Default expiration to 3 days (Graph allows up to 3 days for most resources)
|
|
51
|
+
let expiry = options.expiry;
|
|
52
|
+
if (!expiry) {
|
|
53
|
+
const date = new Date();
|
|
54
|
+
date.setDate(date.getDate() + 3);
|
|
55
|
+
// Ensure we don't exceed max limits by shaving off a minute
|
|
56
|
+
date.setMinutes(date.getMinutes() - 1);
|
|
57
|
+
expiry = date.toISOString();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
console.log(`Creating subscription for ${graphResource}...`);
|
|
62
|
+
const res = await createSubscription(
|
|
63
|
+
graphResource,
|
|
64
|
+
options.changeType,
|
|
65
|
+
options.url,
|
|
66
|
+
expiry,
|
|
67
|
+
clientState,
|
|
68
|
+
options.token,
|
|
69
|
+
options.identity
|
|
70
|
+
);
|
|
71
|
+
if (!res.ok) {
|
|
72
|
+
console.error(`Failed to create subscription: ${res.error?.message}`);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
const sub = res.data;
|
|
76
|
+
console.log('Subscription created successfully!');
|
|
77
|
+
console.log(JSON.stringify(sub, null, 2));
|
|
78
|
+
} catch (err) {
|
|
79
|
+
console.error(err instanceof Error ? err.message : err);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
subscribeCommand
|
|
85
|
+
.command('cancel <id>')
|
|
86
|
+
.description('Cancel an existing subscription')
|
|
87
|
+
.option('--token <token>', 'Use a specific token')
|
|
88
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
89
|
+
.action(async (id, options, cmd) => {
|
|
90
|
+
try {
|
|
91
|
+
checkReadOnly(cmd);
|
|
92
|
+
console.log(`Deleting subscription ${id}...`);
|
|
93
|
+
const res = await deleteSubscription(id, options.token, options.identity);
|
|
94
|
+
if (!res.ok) {
|
|
95
|
+
console.error(`Failed to delete subscription: ${res.error?.message}`);
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
console.log('Subscription deleted successfully.');
|
|
99
|
+
} catch (err) {
|
|
100
|
+
console.error(err instanceof Error ? err.message : err);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { listSubscriptions } from '../lib/graph-subscriptions.js';
|
|
3
|
+
|
|
4
|
+
export const subscriptionsCommand = new Command('subscriptions').description('Manage Microsoft Graph subscriptions');
|
|
5
|
+
|
|
6
|
+
subscriptionsCommand
|
|
7
|
+
.command('list')
|
|
8
|
+
.description('List all active subscriptions')
|
|
9
|
+
.option('--token <token>', 'Use a specific token')
|
|
10
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
11
|
+
.action(async (options: { token?: string; identity?: string }) => {
|
|
12
|
+
try {
|
|
13
|
+
const res = await listSubscriptions(options.token, options.identity);
|
|
14
|
+
if (!res.ok || !res.data) {
|
|
15
|
+
console.error(`Failed to list subscriptions: ${res.error?.message}`);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
const subs = res.data;
|
|
19
|
+
if (subs.length === 0) {
|
|
20
|
+
console.log('No active subscriptions found.');
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
console.log(`Found ${subs.length} active subscription(s):`);
|
|
24
|
+
console.log(JSON.stringify(subs, null, 2));
|
|
25
|
+
} catch (err) {
|
|
26
|
+
console.error(err instanceof Error ? err.message : err);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
});
|