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,2092 @@
|
|
|
1
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { basename } from 'node:path';
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { resolveAuth } from '../lib/auth.js';
|
|
5
|
+
import { getEmail } from '../lib/ews-client.js';
|
|
6
|
+
import { resolveGraphAuth } from '../lib/graph-auth.js';
|
|
7
|
+
import {
|
|
8
|
+
addChecklistItem,
|
|
9
|
+
addLinkedResource,
|
|
10
|
+
createTask,
|
|
11
|
+
createTaskFileAttachment,
|
|
12
|
+
createTaskLinkedResource,
|
|
13
|
+
createTaskReferenceAttachment,
|
|
14
|
+
createTodoList,
|
|
15
|
+
deleteAttachment,
|
|
16
|
+
deleteChecklistItem,
|
|
17
|
+
deleteTask,
|
|
18
|
+
deleteTaskLinkedResource,
|
|
19
|
+
deleteTaskOpenExtension,
|
|
20
|
+
deleteTodoList,
|
|
21
|
+
deleteTodoListOpenExtension,
|
|
22
|
+
getChecklistItem,
|
|
23
|
+
getTask,
|
|
24
|
+
getTaskAttachment,
|
|
25
|
+
getTaskAttachmentContent,
|
|
26
|
+
getTaskLinkedResource,
|
|
27
|
+
getTaskOpenExtension,
|
|
28
|
+
getTasks,
|
|
29
|
+
getTodoList,
|
|
30
|
+
getTodoListOpenExtension,
|
|
31
|
+
getTodoLists,
|
|
32
|
+
getTodoTasksDeltaPage,
|
|
33
|
+
listAttachments,
|
|
34
|
+
listTaskChecklistItems,
|
|
35
|
+
listTaskLinkedResources,
|
|
36
|
+
listTaskOpenExtensions,
|
|
37
|
+
listTodoListOpenExtensions,
|
|
38
|
+
removeLinkedResourceByWebUrl,
|
|
39
|
+
setTaskOpenExtension,
|
|
40
|
+
setTodoListOpenExtension,
|
|
41
|
+
type TodoImportance,
|
|
42
|
+
type TodoLinkedResource,
|
|
43
|
+
type TodoList,
|
|
44
|
+
type TodoStatus,
|
|
45
|
+
type TodoTask,
|
|
46
|
+
type TodoTasksQueryOptions,
|
|
47
|
+
updateChecklistItem,
|
|
48
|
+
updateTask,
|
|
49
|
+
updateTaskLinkedResource,
|
|
50
|
+
updateTaskOpenExtension,
|
|
51
|
+
updateTodoList,
|
|
52
|
+
updateTodoListOpenExtension,
|
|
53
|
+
uploadLargeFileAttachment
|
|
54
|
+
} from '../lib/todo-client.js';
|
|
55
|
+
import { checkReadOnly } from '../lib/utils.js';
|
|
56
|
+
|
|
57
|
+
function fmtDate(iso: string | undefined): string {
|
|
58
|
+
if (!iso) return '';
|
|
59
|
+
try {
|
|
60
|
+
return new Date(iso).toLocaleString('en-US', {
|
|
61
|
+
timeZone: 'UTC',
|
|
62
|
+
month: 'short',
|
|
63
|
+
day: 'numeric',
|
|
64
|
+
year: 'numeric',
|
|
65
|
+
hour: '2-digit',
|
|
66
|
+
minute: '2-digit',
|
|
67
|
+
hour12: false
|
|
68
|
+
});
|
|
69
|
+
} catch {
|
|
70
|
+
return iso;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function fmtDT(d: { dateTime: string; timeZone: string } | undefined): string {
|
|
75
|
+
if (!d) return '';
|
|
76
|
+
try {
|
|
77
|
+
return new Date(d.dateTime).toLocaleString('en-US', {
|
|
78
|
+
timeZone: d.timeZone || 'UTC',
|
|
79
|
+
month: 'short',
|
|
80
|
+
day: 'numeric',
|
|
81
|
+
year: 'numeric',
|
|
82
|
+
hour: '2-digit',
|
|
83
|
+
minute: '2-digit',
|
|
84
|
+
hour12: false
|
|
85
|
+
});
|
|
86
|
+
} catch {
|
|
87
|
+
return d.dateTime;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function impEmoji(i: TodoImportance | undefined): string {
|
|
92
|
+
return i === 'high' ? '\u{1F534}' : i === 'low' ? '\u{1F535}' : '\u26AA';
|
|
93
|
+
}
|
|
94
|
+
function stsEmoji(s: TodoStatus | undefined): string {
|
|
95
|
+
switch (s) {
|
|
96
|
+
case 'completed':
|
|
97
|
+
return '\u2705';
|
|
98
|
+
case 'inProgress':
|
|
99
|
+
return '\u{1F504}';
|
|
100
|
+
case 'waitingOnOthers':
|
|
101
|
+
return '\u23F3';
|
|
102
|
+
case 'deferred':
|
|
103
|
+
return '\u{1F4E6}';
|
|
104
|
+
case 'notStarted':
|
|
105
|
+
return '\u2B1B';
|
|
106
|
+
default:
|
|
107
|
+
return '\u26AA';
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function emailUrl(id: string): string {
|
|
112
|
+
return `https://outlook.office365.com/mail/${encodeURIComponent(id)}`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function linkedTitle(lr: Pick<TodoLinkedResource, 'displayName' | 'description'>): string {
|
|
116
|
+
const t = (lr.displayName || lr.description || '').trim();
|
|
117
|
+
return t || '(link)';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function resolveListId(
|
|
121
|
+
token: string,
|
|
122
|
+
nameOrId: string,
|
|
123
|
+
user?: string
|
|
124
|
+
): Promise<{ listId: string; listDisplay: string }> {
|
|
125
|
+
const listsR = await getTodoLists(token, user);
|
|
126
|
+
if (!listsR.ok || !listsR.data) {
|
|
127
|
+
console.error(`Error: ${listsR.error?.message}`);
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const matched = listsR.data.find(
|
|
132
|
+
(l) =>
|
|
133
|
+
l.id === nameOrId ||
|
|
134
|
+
l.displayName.toLowerCase() === nameOrId.toLowerCase() ||
|
|
135
|
+
l.wellknownListName?.toLowerCase() === nameOrId.toLowerCase()
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
if (matched) {
|
|
139
|
+
return { listId: matched.id, listDisplay: matched.displayName };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const s = await getTodoList(token, nameOrId, user);
|
|
143
|
+
if (!s.ok || !s.data) {
|
|
144
|
+
console.error(`List not found: "${nameOrId}".`);
|
|
145
|
+
console.error('Use "m365-agent-cli todo lists".');
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
return { listId: s.data.id, listDisplay: s.data.displayName };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export const todoCommand = new Command('todo').description('Manage Microsoft To-Do tasks');
|
|
152
|
+
|
|
153
|
+
todoCommand
|
|
154
|
+
.command('lists')
|
|
155
|
+
.description('List all To-Do task lists')
|
|
156
|
+
.option('--json', 'Output as JSON')
|
|
157
|
+
.option('--token <token>', 'Use a specific token')
|
|
158
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
159
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
160
|
+
.action(async (opts: { json?: boolean; token?: string; identity?: string; user?: string }) => {
|
|
161
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
162
|
+
if (!auth.success) {
|
|
163
|
+
console.error(`Auth error: ${auth.error}`);
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
const result = await getTodoLists(auth.token!, opts.user);
|
|
167
|
+
if (!result.ok || !result.data) {
|
|
168
|
+
console.error(`Error: ${result.error?.message}`);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
if (opts.json) {
|
|
172
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const lists: TodoList[] = result.data;
|
|
176
|
+
if (lists.length === 0) {
|
|
177
|
+
console.log('No task lists found.');
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
console.log(`\nTo-Do Lists (${lists.length}):\n`);
|
|
181
|
+
for (const l of lists) {
|
|
182
|
+
const tag = l.isShared ? ' [shared]' : l.isOwner === false ? ' [shared with me]' : '';
|
|
183
|
+
console.log(` ${l.displayName}${tag}`);
|
|
184
|
+
console.log(` ID: ${l.id}`);
|
|
185
|
+
if (l.wellknownListName) console.log(` Well-known: ${l.wellknownListName}`);
|
|
186
|
+
console.log('');
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
todoCommand
|
|
191
|
+
.command('get')
|
|
192
|
+
.description('List tasks in a list, or show a single task')
|
|
193
|
+
.option('-l, --list <name|id>', 'List name or ID (default: Tasks)', 'Tasks')
|
|
194
|
+
.option('-t, --task <id>', 'Show detail for a specific task ID')
|
|
195
|
+
.option('--status <status>', 'Filter by status: notStarted, inProgress, completed, waitingOnOthers, deferred')
|
|
196
|
+
.option('--importance <importance>', 'Filter by importance: low, normal, high')
|
|
197
|
+
.option('--filter <odata>', 'Raw OData $filter (not combined with --status / --importance; see Graph todoTask)')
|
|
198
|
+
.option('--orderby <expr>', 'OData $orderby (e.g. lastModifiedDateTime desc)')
|
|
199
|
+
.option('--select <fields>', 'OData $select (comma-separated field names)')
|
|
200
|
+
.option('--top <n>', 'Page size; when set, returns a single page (no auto follow nextLink)')
|
|
201
|
+
.option('--skip <n>', 'OData $skip (single-page request; combine with --top for paging)')
|
|
202
|
+
.option('--expand <expr>', 'OData $expand (e.g. attachments)')
|
|
203
|
+
.option('--count', 'Add $count=true (single-page response)')
|
|
204
|
+
.option('--json', 'Output as JSON')
|
|
205
|
+
.option('--token <token>', 'Use a specific token')
|
|
206
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
207
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
208
|
+
.action(
|
|
209
|
+
async (opts: {
|
|
210
|
+
list?: string;
|
|
211
|
+
task?: string;
|
|
212
|
+
status?: string;
|
|
213
|
+
importance?: string;
|
|
214
|
+
filter?: string;
|
|
215
|
+
orderby?: string;
|
|
216
|
+
select?: string;
|
|
217
|
+
top?: string;
|
|
218
|
+
skip?: string;
|
|
219
|
+
expand?: string;
|
|
220
|
+
count?: boolean;
|
|
221
|
+
json?: boolean;
|
|
222
|
+
token?: string;
|
|
223
|
+
identity?: string;
|
|
224
|
+
user?: string;
|
|
225
|
+
}) => {
|
|
226
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
227
|
+
if (!auth.success) {
|
|
228
|
+
console.error(`Auth error: ${auth.error}`);
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const listName = opts.list || 'Tasks';
|
|
233
|
+
const { listId, listDisplay } = await resolveListId(auth.token!, listName, opts.user);
|
|
234
|
+
|
|
235
|
+
if (opts.task) {
|
|
236
|
+
const r = await getTask(
|
|
237
|
+
auth.token!,
|
|
238
|
+
listId,
|
|
239
|
+
opts.task,
|
|
240
|
+
opts.user,
|
|
241
|
+
opts.select ? { select: opts.select } : undefined
|
|
242
|
+
);
|
|
243
|
+
if (!r.ok || !r.data) {
|
|
244
|
+
console.error(`Error: ${r.error?.message}`);
|
|
245
|
+
process.exit(1);
|
|
246
|
+
}
|
|
247
|
+
const t: TodoTask = r.data;
|
|
248
|
+
if (opts.json) {
|
|
249
|
+
console.log(JSON.stringify(t, null, 2));
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
const hr = '\u2500'.repeat(60);
|
|
253
|
+
console.log(`\n${hr}`);
|
|
254
|
+
console.log(`Title: ${t.title}`);
|
|
255
|
+
console.log(`Status: ${stsEmoji(t.status)} ${t.status}`);
|
|
256
|
+
console.log(`Importance: ${impEmoji(t.importance)} ${t.importance}`);
|
|
257
|
+
if (t.categories?.length) console.log(`Categories: ${t.categories.join(', ')}`);
|
|
258
|
+
if (t.dueDateTime) console.log(`Due: ${fmtDT(t.dueDateTime)} (${t.dueDateTime.timeZone})`);
|
|
259
|
+
if (t.startDateTime) console.log(`Start: ${fmtDT(t.startDateTime)} (${t.startDateTime.timeZone})`);
|
|
260
|
+
if (t.isReminderOn && t.reminderDateTime) console.log(`Reminder: ${fmtDT(t.reminderDateTime)}`);
|
|
261
|
+
if (t.completedDateTime) console.log(`Completed: ${fmtDT(t.completedDateTime)}`);
|
|
262
|
+
if (t.linkedResources?.length) {
|
|
263
|
+
console.log('Linked:');
|
|
264
|
+
for (const lr of t.linkedResources) console.log(` - ${linkedTitle(lr)}: ${lr.webUrl ?? ''}`);
|
|
265
|
+
}
|
|
266
|
+
if (t.body?.content) {
|
|
267
|
+
console.log(`\n${hr}\n${t.body.content}`);
|
|
268
|
+
}
|
|
269
|
+
if (t.checklistItems?.length) {
|
|
270
|
+
console.log('\nChecklist:');
|
|
271
|
+
for (const item of t.checklistItems)
|
|
272
|
+
console.log(` ${item.isChecked ? '\u2611' : '\u2610'} ${item.displayName}`);
|
|
273
|
+
}
|
|
274
|
+
console.log(`\n${hr}`);
|
|
275
|
+
console.log(`ID: ${t.id}`);
|
|
276
|
+
if (t.createdDateTime) console.log(`Created: ${fmtDate(t.createdDateTime)}`);
|
|
277
|
+
if (t.lastModifiedDateTime) console.log(`Modified: ${fmtDate(t.lastModifiedDateTime)}`);
|
|
278
|
+
console.log('');
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (opts.filter && (opts.status || opts.importance)) {
|
|
283
|
+
console.error('Error: use either --filter or --status/--importance, not both');
|
|
284
|
+
process.exit(1);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
let listQuery: TodoTasksQueryOptions | string | undefined;
|
|
288
|
+
const parseTopSkip = (q: TodoTasksQueryOptions) => {
|
|
289
|
+
if (opts.top !== undefined) {
|
|
290
|
+
const n = parseInt(opts.top, 10);
|
|
291
|
+
if (Number.isNaN(n) || n < 1) {
|
|
292
|
+
console.error('Error: --top must be a positive integer');
|
|
293
|
+
process.exit(1);
|
|
294
|
+
}
|
|
295
|
+
q.top = n;
|
|
296
|
+
}
|
|
297
|
+
if (opts.skip !== undefined) {
|
|
298
|
+
const s = parseInt(opts.skip, 10);
|
|
299
|
+
if (Number.isNaN(s) || s < 0) {
|
|
300
|
+
console.error('Error: --skip must be a non-negative integer');
|
|
301
|
+
process.exit(1);
|
|
302
|
+
}
|
|
303
|
+
q.skip = s;
|
|
304
|
+
}
|
|
305
|
+
if (opts.expand) q.expand = opts.expand;
|
|
306
|
+
if (opts.count) q.count = true;
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
if (opts.filter) {
|
|
310
|
+
const q: TodoTasksQueryOptions = { filter: opts.filter };
|
|
311
|
+
if (opts.orderby) q.orderby = opts.orderby;
|
|
312
|
+
if (opts.select) q.select = opts.select;
|
|
313
|
+
parseTopSkip(q);
|
|
314
|
+
listQuery = q;
|
|
315
|
+
} else {
|
|
316
|
+
const filters: string[] = [];
|
|
317
|
+
if (opts.status) {
|
|
318
|
+
const validStatuses: TodoStatus[] = ['notStarted', 'inProgress', 'completed', 'waitingOnOthers', 'deferred'];
|
|
319
|
+
if (!validStatuses.includes(opts.status as TodoStatus)) {
|
|
320
|
+
console.error(`Error: Invalid status "${opts.status}". Valid values: ${validStatuses.join(', ')}`);
|
|
321
|
+
process.exit(1);
|
|
322
|
+
}
|
|
323
|
+
filters.push(`status eq '${opts.status}'`);
|
|
324
|
+
}
|
|
325
|
+
if (opts.importance) {
|
|
326
|
+
const validImportance: TodoImportance[] = ['low', 'normal', 'high'];
|
|
327
|
+
if (!validImportance.includes(opts.importance as TodoImportance)) {
|
|
328
|
+
console.error(
|
|
329
|
+
`Error: Invalid importance "${opts.importance}". Valid values: ${validImportance.join(', ')}`
|
|
330
|
+
);
|
|
331
|
+
process.exit(1);
|
|
332
|
+
}
|
|
333
|
+
filters.push(`importance eq '${opts.importance}'`);
|
|
334
|
+
}
|
|
335
|
+
const filterStr = filters.join(' and ') || undefined;
|
|
336
|
+
if (
|
|
337
|
+
filterStr ||
|
|
338
|
+
opts.orderby ||
|
|
339
|
+
opts.select ||
|
|
340
|
+
opts.top !== undefined ||
|
|
341
|
+
opts.skip !== undefined ||
|
|
342
|
+
opts.expand ||
|
|
343
|
+
opts.count
|
|
344
|
+
) {
|
|
345
|
+
const q: TodoTasksQueryOptions = {};
|
|
346
|
+
if (filterStr) q.filter = filterStr;
|
|
347
|
+
if (opts.orderby) q.orderby = opts.orderby;
|
|
348
|
+
if (opts.select) q.select = opts.select;
|
|
349
|
+
parseTopSkip(q);
|
|
350
|
+
listQuery = q;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const result = await getTasks(auth.token!, listId, listQuery, opts.user);
|
|
355
|
+
if (!result.ok || !result.data) {
|
|
356
|
+
console.error(`Error: ${result.error?.message}`);
|
|
357
|
+
process.exit(1);
|
|
358
|
+
}
|
|
359
|
+
const tasks: TodoTask[] = result.data;
|
|
360
|
+
if (opts.json) {
|
|
361
|
+
console.log(JSON.stringify({ list: listDisplay, listId, tasks }, null, 2));
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
if (tasks.length === 0) {
|
|
365
|
+
console.log(`\n${listDisplay}: no tasks found.\n`);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
console.log(`\n${listDisplay} (${tasks.length} task${tasks.length === 1 ? '' : 's'}):\n`);
|
|
369
|
+
for (const t of tasks) {
|
|
370
|
+
const due = t.dueDateTime ? `\u{1F4C5} ${fmtDT(t.dueDateTime)}` : '';
|
|
371
|
+
console.log(` ${t.status === 'completed' ? '\u2705' : ' '} ${impEmoji(t.importance)} ${t.title} ${due}`);
|
|
372
|
+
console.log(` ID: ${t.id} | ${t.status || 'no status'} | ${t.importance || 'normal'}`);
|
|
373
|
+
if (t.categories?.length) console.log(` Categories: ${t.categories.join(', ')}`);
|
|
374
|
+
if (t.linkedResources?.length)
|
|
375
|
+
console.log(` \u21B3 linked: ${t.linkedResources.map((l) => linkedTitle(l)).join(', ')}`);
|
|
376
|
+
console.log('');
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
todoCommand
|
|
382
|
+
.command('create')
|
|
383
|
+
.description('Create a new task')
|
|
384
|
+
.requiredOption('-t, --title <text>', 'Task title')
|
|
385
|
+
.option('-l, --list <name|id>', 'List name or ID (default: Tasks)', 'Tasks')
|
|
386
|
+
.option('-b, --body <text>', 'Task body/notes')
|
|
387
|
+
.option('-d, --due <ISO-8601>', 'Due date (e.g. 2026-04-15T17:00:00Z)')
|
|
388
|
+
.option('--start <ISO-8601>', 'Start date/time')
|
|
389
|
+
.option('--importance <level>', 'Importance: low, normal, high', 'normal')
|
|
390
|
+
.option('--status <status>', 'Initial status: notStarted, inProgress, waitingOnOthers, deferred', 'notStarted')
|
|
391
|
+
.option('--reminder <ISO-8601>', 'Reminder datetime')
|
|
392
|
+
.option('--timezone <tz>', 'Default time zone for due/start/reminder (e.g. UTC, Eastern Standard Time)', 'UTC')
|
|
393
|
+
.option('--due-tz <tz>', 'Time zone for due only (overrides --timezone)')
|
|
394
|
+
.option('--start-tz <tz>', 'Time zone for start only')
|
|
395
|
+
.option('--reminder-tz <tz>', 'Time zone for reminder only')
|
|
396
|
+
.option('--link <msgId>', 'Link task to an email by message ID')
|
|
397
|
+
.option('--mailbox <email>', 'Delegated or shared mailbox (with --link, for EWS message lookup)')
|
|
398
|
+
.option(
|
|
399
|
+
'--category <name>',
|
|
400
|
+
'Category label (repeatable; To Do uses string categories)',
|
|
401
|
+
(v: string, prev: string[]) => [...prev, v],
|
|
402
|
+
[] as string[]
|
|
403
|
+
)
|
|
404
|
+
.option('--recurrence-json <path>', 'JSON file: Graph patternedRecurrence object')
|
|
405
|
+
.option('--json', 'Output as JSON')
|
|
406
|
+
.option('--token <token>', 'Use a specific token')
|
|
407
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
408
|
+
.option('--ews-identity <name>', 'EWS token cache identity for --link (default: default)')
|
|
409
|
+
.option('--user <email>', 'Target user or shared mailbox for the task (Graph delegation)')
|
|
410
|
+
.action(
|
|
411
|
+
async (
|
|
412
|
+
opts: {
|
|
413
|
+
title: string;
|
|
414
|
+
list?: string;
|
|
415
|
+
body?: string;
|
|
416
|
+
due?: string;
|
|
417
|
+
start?: string;
|
|
418
|
+
importance?: string;
|
|
419
|
+
status?: string;
|
|
420
|
+
reminder?: string;
|
|
421
|
+
timezone?: string;
|
|
422
|
+
dueTz?: string;
|
|
423
|
+
startTz?: string;
|
|
424
|
+
reminderTz?: string;
|
|
425
|
+
link?: string;
|
|
426
|
+
mailbox?: string;
|
|
427
|
+
category?: string[];
|
|
428
|
+
recurrenceJson?: string;
|
|
429
|
+
json?: boolean;
|
|
430
|
+
token?: string;
|
|
431
|
+
identity?: string;
|
|
432
|
+
ewsIdentity?: string;
|
|
433
|
+
user?: string;
|
|
434
|
+
},
|
|
435
|
+
cmd: any
|
|
436
|
+
) => {
|
|
437
|
+
checkReadOnly(cmd);
|
|
438
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
439
|
+
if (!auth.success) {
|
|
440
|
+
console.error(`Auth error: ${auth.error}`);
|
|
441
|
+
process.exit(1);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const listName = opts.list || 'Tasks';
|
|
445
|
+
const { listId } = await resolveListId(auth.token!, listName, opts.user);
|
|
446
|
+
|
|
447
|
+
let recurrence: Record<string, unknown> | undefined;
|
|
448
|
+
if (opts.recurrenceJson) {
|
|
449
|
+
const raw = await readFile(opts.recurrenceJson, 'utf-8');
|
|
450
|
+
recurrence = JSON.parse(raw) as Record<string, unknown>;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
let linkedResources: any[] | undefined;
|
|
454
|
+
if (opts.link) {
|
|
455
|
+
// Do not pass the Graph --token to EWS auth, as they require different tokens
|
|
456
|
+
const ewsAuth = await resolveAuth({ identity: opts.ewsIdentity });
|
|
457
|
+
if (!ewsAuth.success) {
|
|
458
|
+
console.error(`EWS Auth error: ${ewsAuth.error}`);
|
|
459
|
+
process.exit(1);
|
|
460
|
+
}
|
|
461
|
+
const er = await getEmail(ewsAuth.token!, opts.link, opts.mailbox);
|
|
462
|
+
if (!er.ok || !er.data) {
|
|
463
|
+
console.error(`Could not fetch email: ${er.error?.message}`);
|
|
464
|
+
process.exit(1);
|
|
465
|
+
}
|
|
466
|
+
linkedResources = [{ webUrl: emailUrl(er.data.Id), displayName: er.data.Subject || 'Linked email' }];
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const cats = (opts.category ?? []).map((c) => c.trim()).filter(Boolean);
|
|
470
|
+
const result = await createTask(
|
|
471
|
+
auth.token!,
|
|
472
|
+
listId,
|
|
473
|
+
{
|
|
474
|
+
title: opts.title,
|
|
475
|
+
body: opts.body,
|
|
476
|
+
importance: opts.importance as TodoImportance,
|
|
477
|
+
status: opts.status as TodoStatus,
|
|
478
|
+
dueDateTime: opts.due,
|
|
479
|
+
startDateTime: opts.start,
|
|
480
|
+
reminderDateTime: opts.reminder,
|
|
481
|
+
timeZone: opts.timezone,
|
|
482
|
+
dueTimeZone: opts.dueTz,
|
|
483
|
+
startTimeZone: opts.startTz,
|
|
484
|
+
reminderTimeZone: opts.reminderTz,
|
|
485
|
+
isReminderOn: !!opts.reminder,
|
|
486
|
+
linkedResources,
|
|
487
|
+
categories: cats.length ? cats : undefined,
|
|
488
|
+
recurrence
|
|
489
|
+
},
|
|
490
|
+
opts.user
|
|
491
|
+
);
|
|
492
|
+
if (!result.ok || !result.data) {
|
|
493
|
+
console.error(`Error: ${result.error?.message}`);
|
|
494
|
+
process.exit(1);
|
|
495
|
+
}
|
|
496
|
+
if (opts.json) console.log(JSON.stringify(result.data, null, 2));
|
|
497
|
+
else {
|
|
498
|
+
console.log(`\n\u2705 Task created: "${result.data.title}"`);
|
|
499
|
+
console.log(` ID: ${result.data.id}`);
|
|
500
|
+
console.log(` List: ${listName}`);
|
|
501
|
+
if (opts.link) console.log(` \u21B3 Linked to email`);
|
|
502
|
+
console.log('');
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
);
|
|
506
|
+
|
|
507
|
+
todoCommand
|
|
508
|
+
.command('update')
|
|
509
|
+
.description('Update a task (title, body, due, importance, status, categories)')
|
|
510
|
+
.requiredOption('-l, --list <name|id>', 'List name or ID')
|
|
511
|
+
.requiredOption('-t, --task <id>', 'Task ID')
|
|
512
|
+
.option('--title <text>', 'New title')
|
|
513
|
+
.option('-b, --body <text>', 'New body/notes')
|
|
514
|
+
.option('-d, --due <ISO-8601>', 'Due date (or omit with --clear-due)')
|
|
515
|
+
.option('--clear-due', 'Remove due date')
|
|
516
|
+
.option('--start <ISO-8601>', 'Start date/time (or omit with --clear-start)')
|
|
517
|
+
.option('--clear-start', 'Remove start date/time')
|
|
518
|
+
.option('--importance <level>', 'Importance: low, normal, high')
|
|
519
|
+
.option('--status <status>', 'Status: notStarted, inProgress, completed, waitingOnOthers, deferred')
|
|
520
|
+
.option('--reminder <ISO-8601>', 'Reminder datetime')
|
|
521
|
+
.option('--clear-reminder', 'Turn off reminder')
|
|
522
|
+
.option('--timezone <tz>', 'Default time zone when setting due/start/reminder', 'UTC')
|
|
523
|
+
.option('--due-tz <tz>', 'Time zone for due only')
|
|
524
|
+
.option('--start-tz <tz>', 'Time zone for start only')
|
|
525
|
+
.option('--reminder-tz <tz>', 'Time zone for reminder only')
|
|
526
|
+
.option(
|
|
527
|
+
'--category <name>',
|
|
528
|
+
'Set categories to this list (repeatable; replaces existing categories)',
|
|
529
|
+
(v: string, prev: string[]) => [...prev, v],
|
|
530
|
+
[] as string[]
|
|
531
|
+
)
|
|
532
|
+
.option('--clear-categories', 'Remove all categories')
|
|
533
|
+
.option('--recurrence-json <path>', 'JSON file: patternedRecurrence (replaces recurrence)')
|
|
534
|
+
.option('--clear-recurrence', 'Remove recurrence from the task')
|
|
535
|
+
.option('--json', 'Output as JSON')
|
|
536
|
+
.option('--token <token>', 'Use a specific token')
|
|
537
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
538
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
539
|
+
.action(
|
|
540
|
+
async (
|
|
541
|
+
opts: {
|
|
542
|
+
list: string;
|
|
543
|
+
task: string;
|
|
544
|
+
title?: string;
|
|
545
|
+
body?: string;
|
|
546
|
+
due?: string;
|
|
547
|
+
clearDue?: boolean;
|
|
548
|
+
start?: string;
|
|
549
|
+
clearStart?: boolean;
|
|
550
|
+
importance?: string;
|
|
551
|
+
status?: string;
|
|
552
|
+
reminder?: string;
|
|
553
|
+
clearReminder?: boolean;
|
|
554
|
+
category?: string[];
|
|
555
|
+
clearCategories?: boolean;
|
|
556
|
+
recurrenceJson?: string;
|
|
557
|
+
clearRecurrence?: boolean;
|
|
558
|
+
timezone?: string;
|
|
559
|
+
dueTz?: string;
|
|
560
|
+
startTz?: string;
|
|
561
|
+
reminderTz?: string;
|
|
562
|
+
json?: boolean;
|
|
563
|
+
token?: string;
|
|
564
|
+
identity?: string;
|
|
565
|
+
user?: string;
|
|
566
|
+
},
|
|
567
|
+
cmd: any
|
|
568
|
+
) => {
|
|
569
|
+
checkReadOnly(cmd);
|
|
570
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
571
|
+
if (!auth.success) {
|
|
572
|
+
console.error(`Auth error: ${auth.error}`);
|
|
573
|
+
process.exit(1);
|
|
574
|
+
}
|
|
575
|
+
const { listId } = await resolveListId(auth.token!, opts.list, opts.user);
|
|
576
|
+
|
|
577
|
+
if (opts.clearDue && opts.due !== undefined) {
|
|
578
|
+
console.error('Error: use either --due or --clear-due, not both');
|
|
579
|
+
process.exit(1);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if (opts.clearRecurrence && opts.recurrenceJson) {
|
|
583
|
+
console.error('Error: use either --recurrence-json or --clear-recurrence, not both');
|
|
584
|
+
process.exit(1);
|
|
585
|
+
}
|
|
586
|
+
if (opts.clearStart && opts.start !== undefined) {
|
|
587
|
+
console.error('Error: use either --start or --clear-start, not both');
|
|
588
|
+
process.exit(1);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const hasField =
|
|
592
|
+
opts.title !== undefined ||
|
|
593
|
+
opts.body !== undefined ||
|
|
594
|
+
opts.due !== undefined ||
|
|
595
|
+
opts.clearDue ||
|
|
596
|
+
opts.start !== undefined ||
|
|
597
|
+
opts.clearStart ||
|
|
598
|
+
opts.importance !== undefined ||
|
|
599
|
+
opts.status !== undefined ||
|
|
600
|
+
opts.reminder !== undefined ||
|
|
601
|
+
opts.clearReminder ||
|
|
602
|
+
(opts.category !== undefined && opts.category.length > 0) ||
|
|
603
|
+
opts.clearCategories ||
|
|
604
|
+
opts.recurrenceJson !== undefined ||
|
|
605
|
+
opts.clearRecurrence;
|
|
606
|
+
|
|
607
|
+
if (!hasField) {
|
|
608
|
+
console.error(
|
|
609
|
+
'Error: specify at least one of --title, --body, --due, --clear-due, --start, --clear-start, --importance, --status, --reminder, --clear-reminder, --category, --clear-categories, --recurrence-json, --clear-recurrence'
|
|
610
|
+
);
|
|
611
|
+
process.exit(1);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
if (opts.clearCategories && opts.category !== undefined && opts.category.length > 0) {
|
|
615
|
+
console.error('Error: use either --clear-categories or --category, not both');
|
|
616
|
+
process.exit(1);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
let importance: TodoImportance | undefined;
|
|
620
|
+
if (opts.importance !== undefined) {
|
|
621
|
+
const valid: TodoImportance[] = ['low', 'normal', 'high'];
|
|
622
|
+
if (!valid.includes(opts.importance as TodoImportance)) {
|
|
623
|
+
console.error(`Invalid importance: ${opts.importance}`);
|
|
624
|
+
process.exit(1);
|
|
625
|
+
}
|
|
626
|
+
importance = opts.importance as TodoImportance;
|
|
627
|
+
}
|
|
628
|
+
let status: TodoStatus | undefined;
|
|
629
|
+
if (opts.status !== undefined) {
|
|
630
|
+
const valid: TodoStatus[] = ['notStarted', 'inProgress', 'completed', 'waitingOnOthers', 'deferred'];
|
|
631
|
+
if (!valid.includes(opts.status as TodoStatus)) {
|
|
632
|
+
console.error(`Invalid status: ${opts.status}`);
|
|
633
|
+
process.exit(1);
|
|
634
|
+
}
|
|
635
|
+
status = opts.status as TodoStatus;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const updateOpts: Parameters<typeof updateTask>[3] = {};
|
|
639
|
+
if (opts.title !== undefined) updateOpts.title = opts.title;
|
|
640
|
+
if (opts.body !== undefined) updateOpts.body = opts.body;
|
|
641
|
+
if (opts.clearDue) updateOpts.dueDateTime = null;
|
|
642
|
+
else if (opts.due !== undefined) updateOpts.dueDateTime = opts.due;
|
|
643
|
+
if (opts.clearStart) updateOpts.startDateTime = null;
|
|
644
|
+
else if (opts.start !== undefined) updateOpts.startDateTime = opts.start;
|
|
645
|
+
if (importance !== undefined) updateOpts.importance = importance;
|
|
646
|
+
if (status !== undefined) updateOpts.status = status;
|
|
647
|
+
if (opts.clearReminder) {
|
|
648
|
+
updateOpts.isReminderOn = false;
|
|
649
|
+
updateOpts.reminderDateTime = null;
|
|
650
|
+
} else if (opts.reminder !== undefined) {
|
|
651
|
+
updateOpts.isReminderOn = true;
|
|
652
|
+
updateOpts.reminderDateTime = opts.reminder;
|
|
653
|
+
}
|
|
654
|
+
if (opts.clearCategories) updateOpts.clearCategories = true;
|
|
655
|
+
else if (opts.category !== undefined && opts.category.length > 0) {
|
|
656
|
+
updateOpts.categories = opts.category.map((c) => c.trim()).filter(Boolean);
|
|
657
|
+
}
|
|
658
|
+
if (opts.clearRecurrence) updateOpts.recurrence = null;
|
|
659
|
+
else if (opts.recurrenceJson) {
|
|
660
|
+
const raw = await readFile(opts.recurrenceJson, 'utf-8');
|
|
661
|
+
updateOpts.recurrence = JSON.parse(raw) as Record<string, unknown>;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
if (opts.due !== undefined || opts.start !== undefined || opts.reminder !== undefined) {
|
|
665
|
+
updateOpts.timeZone = opts.timezone;
|
|
666
|
+
if (opts.dueTz !== undefined) updateOpts.dueTimeZone = opts.dueTz;
|
|
667
|
+
if (opts.startTz !== undefined) updateOpts.startTimeZone = opts.startTz;
|
|
668
|
+
if (opts.reminderTz !== undefined) updateOpts.reminderTimeZone = opts.reminderTz;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
const r = await updateTask(auth.token!, listId, opts.task, updateOpts, opts.user);
|
|
672
|
+
if (!r.ok || !r.data) {
|
|
673
|
+
console.error(`Error: ${r.error?.message}`);
|
|
674
|
+
process.exit(1);
|
|
675
|
+
}
|
|
676
|
+
if (opts.json) console.log(JSON.stringify(r.data, null, 2));
|
|
677
|
+
else console.log(`\n\u2705 Updated: "${r.data.title}"\n`);
|
|
678
|
+
}
|
|
679
|
+
);
|
|
680
|
+
|
|
681
|
+
todoCommand
|
|
682
|
+
.command('complete')
|
|
683
|
+
.description('Mark a task as completed')
|
|
684
|
+
.requiredOption('-l, --list <name|id>', 'List name or ID')
|
|
685
|
+
.requiredOption('-t, --task <id>', 'Task ID')
|
|
686
|
+
.option('--json', 'Output as JSON')
|
|
687
|
+
.option('--token <token>', 'Use a specific token')
|
|
688
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
689
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
690
|
+
.action(
|
|
691
|
+
async (
|
|
692
|
+
opts: { list: string; task: string; json?: boolean; token?: string; identity?: string; user?: string },
|
|
693
|
+
cmd: any
|
|
694
|
+
) => {
|
|
695
|
+
checkReadOnly(cmd);
|
|
696
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
697
|
+
if (!auth.success) {
|
|
698
|
+
console.error(`Auth error: ${auth.error}`);
|
|
699
|
+
process.exit(1);
|
|
700
|
+
}
|
|
701
|
+
const { listId } = await resolveListId(auth.token!, opts.list, opts.user);
|
|
702
|
+
// dateTime should not include Z/offset - keep dateTime and timeZone separate
|
|
703
|
+
const nowISO = new Date().toISOString();
|
|
704
|
+
const now = nowISO.replace('Z', '');
|
|
705
|
+
const r = await updateTask(
|
|
706
|
+
auth.token!,
|
|
707
|
+
listId,
|
|
708
|
+
opts.task,
|
|
709
|
+
{ status: 'completed', completedDateTime: now },
|
|
710
|
+
opts.user
|
|
711
|
+
);
|
|
712
|
+
if (!r.ok || !r.data) {
|
|
713
|
+
console.error(`Error: ${r.error?.message}`);
|
|
714
|
+
process.exit(1);
|
|
715
|
+
}
|
|
716
|
+
if (opts.json) console.log(JSON.stringify(r.data, null, 2));
|
|
717
|
+
else console.log(`\n\u2705 Completed: "${r.data.title}" (${fmtDate(nowISO)})\n`);
|
|
718
|
+
}
|
|
719
|
+
);
|
|
720
|
+
|
|
721
|
+
todoCommand
|
|
722
|
+
.command('delete')
|
|
723
|
+
.description('Delete a task')
|
|
724
|
+
.requiredOption('-l, --list <name|id>', 'List name or ID')
|
|
725
|
+
.requiredOption('-t, --task <id>', 'Task ID')
|
|
726
|
+
.option('--confirm', 'Skip confirmation prompt')
|
|
727
|
+
.option('--token <token>', 'Use a specific token')
|
|
728
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
729
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
730
|
+
.action(
|
|
731
|
+
async (
|
|
732
|
+
opts: { list: string; task: string; confirm?: boolean; token?: string; identity?: string; user?: string },
|
|
733
|
+
cmd: any
|
|
734
|
+
) => {
|
|
735
|
+
checkReadOnly(cmd);
|
|
736
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
737
|
+
if (!auth.success) {
|
|
738
|
+
console.error(`Auth error: ${auth.error}`);
|
|
739
|
+
process.exit(1);
|
|
740
|
+
}
|
|
741
|
+
const { listId, listDisplay: listName } = await resolveListId(auth.token!, opts.list, opts.user);
|
|
742
|
+
const taskR = await getTask(auth.token!, listId, opts.task, opts.user);
|
|
743
|
+
if (!taskR.ok || !taskR.data) {
|
|
744
|
+
console.error(`Task not found: ${taskR.error?.message}`);
|
|
745
|
+
process.exit(1);
|
|
746
|
+
}
|
|
747
|
+
if (!opts.confirm) {
|
|
748
|
+
console.log(`Delete "${taskR.data.title}" from "${listName}"? (ID: ${opts.task})`);
|
|
749
|
+
console.log('Run with --confirm to confirm.');
|
|
750
|
+
process.exit(1);
|
|
751
|
+
}
|
|
752
|
+
const r = await deleteTask(auth.token!, listId, opts.task, opts.user);
|
|
753
|
+
if (!r.ok) {
|
|
754
|
+
console.error(`Error: ${r.error?.message}`);
|
|
755
|
+
process.exit(1);
|
|
756
|
+
}
|
|
757
|
+
console.log(`\n\u{1F5D1} Deleted: "${taskR.data.title}"\n`);
|
|
758
|
+
}
|
|
759
|
+
);
|
|
760
|
+
|
|
761
|
+
todoCommand
|
|
762
|
+
.command('add-checklist')
|
|
763
|
+
.description('Add a checklist (subtask) item to a task')
|
|
764
|
+
.requiredOption('-l, --list <name|id>', 'List name or ID')
|
|
765
|
+
.requiredOption('-t, --task <id>', 'Task ID')
|
|
766
|
+
.requiredOption('-n, --name <text>', 'Checklist item text')
|
|
767
|
+
.option('--json', 'Output as JSON')
|
|
768
|
+
.option('--token <token>', 'Use a specific token')
|
|
769
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
770
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
771
|
+
.action(
|
|
772
|
+
async (
|
|
773
|
+
opts: {
|
|
774
|
+
list: string;
|
|
775
|
+
task: string;
|
|
776
|
+
name: string;
|
|
777
|
+
json?: boolean;
|
|
778
|
+
token?: string;
|
|
779
|
+
identity?: string;
|
|
780
|
+
user?: string;
|
|
781
|
+
},
|
|
782
|
+
cmd: any
|
|
783
|
+
) => {
|
|
784
|
+
checkReadOnly(cmd);
|
|
785
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
786
|
+
if (!auth.success) {
|
|
787
|
+
console.error(`Auth error: ${auth.error}`);
|
|
788
|
+
process.exit(1);
|
|
789
|
+
}
|
|
790
|
+
const { listId } = await resolveListId(auth.token!, opts.list, opts.user);
|
|
791
|
+
const r = await addChecklistItem(auth.token!, listId, opts.task, opts.name, opts.user);
|
|
792
|
+
if (!r.ok || !r.data) {
|
|
793
|
+
console.error(`Error: ${r.error?.message}`);
|
|
794
|
+
process.exit(1);
|
|
795
|
+
}
|
|
796
|
+
if (opts.json) console.log(JSON.stringify(r.data, null, 2));
|
|
797
|
+
else console.log(`\n\u2705 Added: "${r.data.displayName}" (${r.data.id})\n`);
|
|
798
|
+
}
|
|
799
|
+
);
|
|
800
|
+
|
|
801
|
+
todoCommand
|
|
802
|
+
.command('update-checklist')
|
|
803
|
+
.description('Update a checklist item (rename or check/uncheck)')
|
|
804
|
+
.requiredOption('-l, --list <name|id>', 'List name or ID')
|
|
805
|
+
.requiredOption('-t, --task <id>', 'Task ID')
|
|
806
|
+
.requiredOption('-c, --item <checklistItemId>', 'Checklist item ID')
|
|
807
|
+
.option('-n, --name <text>', 'New display text')
|
|
808
|
+
.option('--checked', 'Mark checked')
|
|
809
|
+
.option('--unchecked', 'Mark unchecked')
|
|
810
|
+
.option('--json', 'Output as JSON')
|
|
811
|
+
.option('--token <token>', 'Use a specific token')
|
|
812
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
813
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
814
|
+
.action(
|
|
815
|
+
async (
|
|
816
|
+
opts: {
|
|
817
|
+
list: string;
|
|
818
|
+
task: string;
|
|
819
|
+
item: string;
|
|
820
|
+
name?: string;
|
|
821
|
+
checked?: boolean;
|
|
822
|
+
unchecked?: boolean;
|
|
823
|
+
json?: boolean;
|
|
824
|
+
token?: string;
|
|
825
|
+
identity?: string;
|
|
826
|
+
user?: string;
|
|
827
|
+
},
|
|
828
|
+
cmd: any
|
|
829
|
+
) => {
|
|
830
|
+
checkReadOnly(cmd);
|
|
831
|
+
if (opts.checked && opts.unchecked) {
|
|
832
|
+
console.error('Error: use either --checked or --unchecked, not both');
|
|
833
|
+
process.exit(1);
|
|
834
|
+
}
|
|
835
|
+
if (opts.name === undefined && !opts.checked && !opts.unchecked) {
|
|
836
|
+
console.error('Error: specify --name and/or --checked/--unchecked');
|
|
837
|
+
process.exit(1);
|
|
838
|
+
}
|
|
839
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
840
|
+
if (!auth.success) {
|
|
841
|
+
console.error(`Auth error: ${auth.error}`);
|
|
842
|
+
process.exit(1);
|
|
843
|
+
}
|
|
844
|
+
const { listId } = await resolveListId(auth.token!, opts.list, opts.user);
|
|
845
|
+
const patch: { displayName?: string; isChecked?: boolean } = {};
|
|
846
|
+
if (opts.name !== undefined) patch.displayName = opts.name;
|
|
847
|
+
if (opts.checked) patch.isChecked = true;
|
|
848
|
+
if (opts.unchecked) patch.isChecked = false;
|
|
849
|
+
const r = await updateChecklistItem(auth.token!, listId, opts.task, opts.item, patch, opts.user);
|
|
850
|
+
if (!r.ok || !r.data) {
|
|
851
|
+
console.error(`Error: ${r.error?.message}`);
|
|
852
|
+
process.exit(1);
|
|
853
|
+
}
|
|
854
|
+
if (opts.json) console.log(JSON.stringify(r.data, null, 2));
|
|
855
|
+
else console.log(`\n\u2705 Updated checklist item: "${r.data.displayName}"\n`);
|
|
856
|
+
}
|
|
857
|
+
);
|
|
858
|
+
|
|
859
|
+
todoCommand
|
|
860
|
+
.command('delete-checklist')
|
|
861
|
+
.description('Delete a checklist item from a task')
|
|
862
|
+
.requiredOption('-l, --list <name|id>', 'List name or ID')
|
|
863
|
+
.requiredOption('-t, --task <id>', 'Task ID')
|
|
864
|
+
.requiredOption('-c, --item <checklistItemId>', 'Checklist item ID')
|
|
865
|
+
.option('--confirm', 'Confirm without prompt')
|
|
866
|
+
.option('--token <token>', 'Use a specific token')
|
|
867
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
868
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
869
|
+
.action(
|
|
870
|
+
async (
|
|
871
|
+
opts: {
|
|
872
|
+
list: string;
|
|
873
|
+
task: string;
|
|
874
|
+
item: string;
|
|
875
|
+
confirm?: boolean;
|
|
876
|
+
token?: string;
|
|
877
|
+
identity?: string;
|
|
878
|
+
user?: string;
|
|
879
|
+
},
|
|
880
|
+
cmd: any
|
|
881
|
+
) => {
|
|
882
|
+
checkReadOnly(cmd);
|
|
883
|
+
if (!opts.confirm) {
|
|
884
|
+
console.log(`Delete checklist item ${opts.item}? Run with --confirm.`);
|
|
885
|
+
process.exit(1);
|
|
886
|
+
}
|
|
887
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
888
|
+
if (!auth.success) {
|
|
889
|
+
console.error(`Auth error: ${auth.error}`);
|
|
890
|
+
process.exit(1);
|
|
891
|
+
}
|
|
892
|
+
const { listId } = await resolveListId(auth.token!, opts.list, opts.user);
|
|
893
|
+
const r = await deleteChecklistItem(auth.token!, listId, opts.task, opts.item, opts.user);
|
|
894
|
+
if (!r.ok) {
|
|
895
|
+
console.error(`Error: ${r.error?.message}`);
|
|
896
|
+
process.exit(1);
|
|
897
|
+
}
|
|
898
|
+
console.log(`\n\u2705 Deleted checklist item: ${opts.item}\n`);
|
|
899
|
+
}
|
|
900
|
+
);
|
|
901
|
+
|
|
902
|
+
todoCommand
|
|
903
|
+
.command('create-list')
|
|
904
|
+
.description('Create a new To Do list')
|
|
905
|
+
.requiredOption('-n, --name <displayName>', 'List display name')
|
|
906
|
+
.option('--json', 'Output as JSON')
|
|
907
|
+
.option('--token <token>', 'Use a specific token')
|
|
908
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
909
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
910
|
+
.action(
|
|
911
|
+
async (opts: { name: string; json?: boolean; token?: string; identity?: string; user?: string }, cmd: any) => {
|
|
912
|
+
checkReadOnly(cmd);
|
|
913
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
914
|
+
if (!auth.success) {
|
|
915
|
+
console.error(`Auth error: ${auth.error}`);
|
|
916
|
+
process.exit(1);
|
|
917
|
+
}
|
|
918
|
+
const r = await createTodoList(auth.token!, opts.name, opts.user);
|
|
919
|
+
if (!r.ok || !r.data) {
|
|
920
|
+
console.error(`Error: ${r.error?.message}`);
|
|
921
|
+
process.exit(1);
|
|
922
|
+
}
|
|
923
|
+
if (opts.json) console.log(JSON.stringify(r.data, null, 2));
|
|
924
|
+
else console.log(`\n\u2705 Created list: "${r.data.displayName}" (${r.data.id})\n`);
|
|
925
|
+
}
|
|
926
|
+
);
|
|
927
|
+
|
|
928
|
+
todoCommand
|
|
929
|
+
.command('update-list')
|
|
930
|
+
.description('Rename a To Do list')
|
|
931
|
+
.requiredOption('-l, --list <name|id>', 'Current list name or ID')
|
|
932
|
+
.requiredOption('-n, --name <displayName>', 'New display name')
|
|
933
|
+
.option('--json', 'Output as JSON')
|
|
934
|
+
.option('--token <token>', 'Use a specific token')
|
|
935
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
936
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
937
|
+
.action(
|
|
938
|
+
async (
|
|
939
|
+
opts: { list: string; name: string; json?: boolean; token?: string; identity?: string; user?: string },
|
|
940
|
+
cmd: any
|
|
941
|
+
) => {
|
|
942
|
+
checkReadOnly(cmd);
|
|
943
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
944
|
+
if (!auth.success) {
|
|
945
|
+
console.error(`Auth error: ${auth.error}`);
|
|
946
|
+
process.exit(1);
|
|
947
|
+
}
|
|
948
|
+
const { listId } = await resolveListId(auth.token!, opts.list, opts.user);
|
|
949
|
+
const r = await updateTodoList(auth.token!, listId, opts.name, opts.user);
|
|
950
|
+
if (!r.ok || !r.data) {
|
|
951
|
+
console.error(`Error: ${r.error?.message}`);
|
|
952
|
+
process.exit(1);
|
|
953
|
+
}
|
|
954
|
+
if (opts.json) console.log(JSON.stringify(r.data, null, 2));
|
|
955
|
+
else console.log(`\n\u2705 Renamed list to: "${r.data.displayName}"\n`);
|
|
956
|
+
}
|
|
957
|
+
);
|
|
958
|
+
|
|
959
|
+
todoCommand
|
|
960
|
+
.command('delete-list')
|
|
961
|
+
.description('Delete a To Do list')
|
|
962
|
+
.requiredOption('-l, --list <name|id>', 'List name or ID')
|
|
963
|
+
.option('--confirm', 'Confirm deletion')
|
|
964
|
+
.option('--json', 'Output as JSON')
|
|
965
|
+
.option('--token <token>', 'Use a specific token')
|
|
966
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
967
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
968
|
+
.action(
|
|
969
|
+
async (
|
|
970
|
+
opts: { list: string; confirm?: boolean; json?: boolean; token?: string; identity?: string; user?: string },
|
|
971
|
+
cmd: any
|
|
972
|
+
) => {
|
|
973
|
+
checkReadOnly(cmd);
|
|
974
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
975
|
+
if (!auth.success) {
|
|
976
|
+
console.error(`Auth error: ${auth.error}`);
|
|
977
|
+
process.exit(1);
|
|
978
|
+
}
|
|
979
|
+
const { listId, listDisplay } = await resolveListId(auth.token!, opts.list, opts.user);
|
|
980
|
+
if (!opts.confirm) {
|
|
981
|
+
console.log(`Delete list "${listDisplay}"? (ID: ${listId})`);
|
|
982
|
+
console.log('Run with --confirm to confirm.');
|
|
983
|
+
process.exit(1);
|
|
984
|
+
}
|
|
985
|
+
const r = await deleteTodoList(auth.token!, listId, opts.user);
|
|
986
|
+
if (!r.ok) {
|
|
987
|
+
console.error(`Error: ${r.error?.message}`);
|
|
988
|
+
process.exit(1);
|
|
989
|
+
}
|
|
990
|
+
if (opts.json) console.log(JSON.stringify({ deleted: listId }, null, 2));
|
|
991
|
+
else console.log(`\n\u2705 Deleted list: "${listDisplay}"\n`);
|
|
992
|
+
}
|
|
993
|
+
);
|
|
994
|
+
|
|
995
|
+
todoCommand
|
|
996
|
+
.command('list-attachments')
|
|
997
|
+
.description('List attachments on a task')
|
|
998
|
+
.requiredOption('-l, --list <name|id>', 'List name or ID')
|
|
999
|
+
.requiredOption('-t, --task <id>', 'Task ID')
|
|
1000
|
+
.option('--json', 'Output as JSON')
|
|
1001
|
+
.option('--token <token>', 'Use a specific token')
|
|
1002
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
1003
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
1004
|
+
.action(
|
|
1005
|
+
async (opts: { list: string; task: string; json?: boolean; token?: string; identity?: string; user?: string }) => {
|
|
1006
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
1007
|
+
if (!auth.success) {
|
|
1008
|
+
console.error(`Auth error: ${auth.error}`);
|
|
1009
|
+
process.exit(1);
|
|
1010
|
+
}
|
|
1011
|
+
const { listId } = await resolveListId(auth.token!, opts.list, opts.user);
|
|
1012
|
+
const r = await listAttachments(auth.token!, listId, opts.task, opts.user);
|
|
1013
|
+
if (!r.ok || !r.data) {
|
|
1014
|
+
console.error(`Error: ${r.error?.message}`);
|
|
1015
|
+
process.exit(1);
|
|
1016
|
+
}
|
|
1017
|
+
if (opts.json) console.log(JSON.stringify(r.data, null, 2));
|
|
1018
|
+
else {
|
|
1019
|
+
for (const a of r.data) {
|
|
1020
|
+
console.log(`- ${a.name || a.id} (${a.id})${a.size != null ? ` ${a.size} bytes` : ''}`);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
);
|
|
1025
|
+
|
|
1026
|
+
todoCommand
|
|
1027
|
+
.command('add-attachment')
|
|
1028
|
+
.description('Attach a small file to a task (base64 upload; Graph size limits apply)')
|
|
1029
|
+
.requiredOption('-l, --list <name|id>', 'List name or ID')
|
|
1030
|
+
.requiredOption('-t, --task <id>', 'Task ID')
|
|
1031
|
+
.requiredOption('-f, --file <path>', 'Local file path')
|
|
1032
|
+
.option('--name <filename>', 'Attachment name (default: file basename)')
|
|
1033
|
+
.option('--content-type <mime>', 'MIME type (default: application/octet-stream)')
|
|
1034
|
+
.option('--json', 'Output as JSON')
|
|
1035
|
+
.option('--token <token>', 'Use a specific token')
|
|
1036
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
1037
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
1038
|
+
.action(
|
|
1039
|
+
async (
|
|
1040
|
+
opts: {
|
|
1041
|
+
list: string;
|
|
1042
|
+
task: string;
|
|
1043
|
+
file: string;
|
|
1044
|
+
name?: string;
|
|
1045
|
+
contentType?: string;
|
|
1046
|
+
json?: boolean;
|
|
1047
|
+
token?: string;
|
|
1048
|
+
identity?: string;
|
|
1049
|
+
user?: string;
|
|
1050
|
+
},
|
|
1051
|
+
cmd: any
|
|
1052
|
+
) => {
|
|
1053
|
+
checkReadOnly(cmd);
|
|
1054
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
1055
|
+
if (!auth.success) {
|
|
1056
|
+
console.error(`Auth error: ${auth.error}`);
|
|
1057
|
+
process.exit(1);
|
|
1058
|
+
}
|
|
1059
|
+
const { listId } = await resolveListId(auth.token!, opts.list, opts.user);
|
|
1060
|
+
const buf = await readFile(opts.file);
|
|
1061
|
+
const b64 = buf.toString('base64');
|
|
1062
|
+
const attName = opts.name?.trim() || basename(opts.file);
|
|
1063
|
+
const ct = opts.contentType?.trim() || 'application/octet-stream';
|
|
1064
|
+
const r = await createTaskFileAttachment(auth.token!, listId, opts.task, attName, b64, ct, opts.user);
|
|
1065
|
+
if (!r.ok || !r.data) {
|
|
1066
|
+
console.error(`Error: ${r.error?.message}`);
|
|
1067
|
+
process.exit(1);
|
|
1068
|
+
}
|
|
1069
|
+
if (opts.json) console.log(JSON.stringify(r.data, null, 2));
|
|
1070
|
+
else console.log(`\n\u2705 Attached: ${r.data.name || r.data.id} (${r.data.id})\n`);
|
|
1071
|
+
}
|
|
1072
|
+
);
|
|
1073
|
+
|
|
1074
|
+
todoCommand
|
|
1075
|
+
.command('delete-attachment')
|
|
1076
|
+
.description('Remove an attachment from a task')
|
|
1077
|
+
.requiredOption('-l, --list <name|id>', 'List name or ID')
|
|
1078
|
+
.requiredOption('-t, --task <id>', 'Task ID')
|
|
1079
|
+
.requiredOption('-a, --attachment <attachmentId>', 'Attachment ID')
|
|
1080
|
+
.option('--confirm', 'Confirm without prompt')
|
|
1081
|
+
.option('--token <token>', 'Use a specific token')
|
|
1082
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
1083
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
1084
|
+
.action(
|
|
1085
|
+
async (
|
|
1086
|
+
opts: {
|
|
1087
|
+
list: string;
|
|
1088
|
+
task: string;
|
|
1089
|
+
attachment: string;
|
|
1090
|
+
confirm?: boolean;
|
|
1091
|
+
token?: string;
|
|
1092
|
+
identity?: string;
|
|
1093
|
+
user?: string;
|
|
1094
|
+
},
|
|
1095
|
+
cmd: any
|
|
1096
|
+
) => {
|
|
1097
|
+
checkReadOnly(cmd);
|
|
1098
|
+
if (!opts.confirm) {
|
|
1099
|
+
console.log(`Delete attachment ${opts.attachment}? Run with --confirm.`);
|
|
1100
|
+
process.exit(1);
|
|
1101
|
+
}
|
|
1102
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
1103
|
+
if (!auth.success) {
|
|
1104
|
+
console.error(`Auth error: ${auth.error}`);
|
|
1105
|
+
process.exit(1);
|
|
1106
|
+
}
|
|
1107
|
+
const { listId } = await resolveListId(auth.token!, opts.list, opts.user);
|
|
1108
|
+
const r = await deleteAttachment(auth.token!, listId, opts.task, opts.attachment, opts.user);
|
|
1109
|
+
if (!r.ok) {
|
|
1110
|
+
console.error(`Error: ${r.error?.message}`);
|
|
1111
|
+
process.exit(1);
|
|
1112
|
+
}
|
|
1113
|
+
console.log(`\n\u2705 Deleted attachment: ${opts.attachment}\n`);
|
|
1114
|
+
}
|
|
1115
|
+
);
|
|
1116
|
+
|
|
1117
|
+
todoCommand
|
|
1118
|
+
.command('get-attachment')
|
|
1119
|
+
.description('Fetch metadata for one task attachment')
|
|
1120
|
+
.requiredOption('-l, --list <name|id>', 'List name or ID')
|
|
1121
|
+
.requiredOption('-t, --task <id>', 'Task ID')
|
|
1122
|
+
.requiredOption('-a, --attachment <attachmentId>', 'Attachment ID')
|
|
1123
|
+
.option('--json', 'Output as JSON')
|
|
1124
|
+
.option('--token <token>', 'Use a specific token')
|
|
1125
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
1126
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
1127
|
+
.action(
|
|
1128
|
+
async (opts: {
|
|
1129
|
+
list: string;
|
|
1130
|
+
task: string;
|
|
1131
|
+
attachment: string;
|
|
1132
|
+
json?: boolean;
|
|
1133
|
+
token?: string;
|
|
1134
|
+
identity?: string;
|
|
1135
|
+
user?: string;
|
|
1136
|
+
}) => {
|
|
1137
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
1138
|
+
if (!auth.success) {
|
|
1139
|
+
console.error(`Auth error: ${auth.error}`);
|
|
1140
|
+
process.exit(1);
|
|
1141
|
+
}
|
|
1142
|
+
const { listId } = await resolveListId(auth.token!, opts.list, opts.user);
|
|
1143
|
+
const r = await getTaskAttachment(auth.token!, listId, opts.task, opts.attachment, opts.user);
|
|
1144
|
+
if (!r.ok || !r.data) {
|
|
1145
|
+
console.error(`Error: ${r.error?.message}`);
|
|
1146
|
+
process.exit(1);
|
|
1147
|
+
}
|
|
1148
|
+
if (opts.json) console.log(JSON.stringify(r.data, null, 2));
|
|
1149
|
+
else {
|
|
1150
|
+
const a = r.data;
|
|
1151
|
+
console.log(`Name: ${a.name || '(unnamed)'}`);
|
|
1152
|
+
console.log(`ID: ${a.id}`);
|
|
1153
|
+
if (a.contentType) console.log(`Type: ${a.contentType}`);
|
|
1154
|
+
if (a.size != null) console.log(`Size: ${a.size}`);
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
);
|
|
1158
|
+
|
|
1159
|
+
todoCommand
|
|
1160
|
+
.command('download-attachment')
|
|
1161
|
+
.description(
|
|
1162
|
+
'Download file attachment bytes (Graph GET .../attachments/{id}/$value); not for reference/URL attachments'
|
|
1163
|
+
)
|
|
1164
|
+
.requiredOption('-l, --list <name|id>', 'List name or ID')
|
|
1165
|
+
.requiredOption('-t, --task <id>', 'Task ID')
|
|
1166
|
+
.requiredOption('-a, --attachment <attachmentId>', 'Attachment ID')
|
|
1167
|
+
.requiredOption('-o, --output <path>', 'Write file to this path')
|
|
1168
|
+
.option('--token <token>', 'Use a specific token')
|
|
1169
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
1170
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
1171
|
+
.action(
|
|
1172
|
+
async (opts: {
|
|
1173
|
+
list: string;
|
|
1174
|
+
task: string;
|
|
1175
|
+
attachment: string;
|
|
1176
|
+
output: string;
|
|
1177
|
+
token?: string;
|
|
1178
|
+
identity?: string;
|
|
1179
|
+
user?: string;
|
|
1180
|
+
}) => {
|
|
1181
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
1182
|
+
if (!auth.success) {
|
|
1183
|
+
console.error(`Auth error: ${auth.error}`);
|
|
1184
|
+
process.exit(1);
|
|
1185
|
+
}
|
|
1186
|
+
const { listId } = await resolveListId(auth.token!, opts.list, opts.user);
|
|
1187
|
+
const r = await getTaskAttachmentContent(auth.token!, listId, opts.task, opts.attachment, opts.user);
|
|
1188
|
+
if (!r.ok || !r.data) {
|
|
1189
|
+
console.error(`Error: ${r.error?.message}`);
|
|
1190
|
+
process.exit(1);
|
|
1191
|
+
}
|
|
1192
|
+
await writeFile(opts.output, r.data);
|
|
1193
|
+
console.log(`Wrote ${r.data.byteLength} bytes to ${opts.output}`);
|
|
1194
|
+
}
|
|
1195
|
+
);
|
|
1196
|
+
|
|
1197
|
+
todoCommand
|
|
1198
|
+
.command('add-reference-attachment')
|
|
1199
|
+
.description('Add a URL reference attachment (not file bytes)')
|
|
1200
|
+
.requiredOption('-l, --list <name|id>', 'List name or ID')
|
|
1201
|
+
.requiredOption('-t, --task <id>', 'Task ID')
|
|
1202
|
+
.requiredOption('--url <url>', 'Target URL')
|
|
1203
|
+
.requiredOption('-n, --name <text>', 'Attachment display name')
|
|
1204
|
+
.option('--json', 'Output as JSON')
|
|
1205
|
+
.option('--token <token>', 'Use a specific token')
|
|
1206
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
1207
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
1208
|
+
.action(
|
|
1209
|
+
async (
|
|
1210
|
+
opts: {
|
|
1211
|
+
list: string;
|
|
1212
|
+
task: string;
|
|
1213
|
+
url: string;
|
|
1214
|
+
name: string;
|
|
1215
|
+
json?: boolean;
|
|
1216
|
+
token?: string;
|
|
1217
|
+
identity?: string;
|
|
1218
|
+
user?: string;
|
|
1219
|
+
},
|
|
1220
|
+
cmd: any
|
|
1221
|
+
) => {
|
|
1222
|
+
checkReadOnly(cmd);
|
|
1223
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
1224
|
+
if (!auth.success) {
|
|
1225
|
+
console.error(`Auth error: ${auth.error}`);
|
|
1226
|
+
process.exit(1);
|
|
1227
|
+
}
|
|
1228
|
+
const { listId } = await resolveListId(auth.token!, opts.list, opts.user);
|
|
1229
|
+
const r = await createTaskReferenceAttachment(auth.token!, listId, opts.task, opts.name, opts.url, opts.user);
|
|
1230
|
+
if (!r.ok || !r.data) {
|
|
1231
|
+
console.error(`Error: ${r.error?.message}`);
|
|
1232
|
+
process.exit(1);
|
|
1233
|
+
}
|
|
1234
|
+
if (opts.json) console.log(JSON.stringify(r.data, null, 2));
|
|
1235
|
+
else console.log(`\n\u2705 Reference attachment: ${r.data.name || r.data.id} (${r.data.id})\n`);
|
|
1236
|
+
}
|
|
1237
|
+
);
|
|
1238
|
+
|
|
1239
|
+
todoCommand
|
|
1240
|
+
.command('add-linked-resource')
|
|
1241
|
+
.description('Merge linked resources on the task (PATCH task). Prefer todo linked-resource create for Graph POST.')
|
|
1242
|
+
.requiredOption('-l, --list <name|id>', 'List name or ID')
|
|
1243
|
+
.requiredOption('-t, --task <id>', 'Task ID')
|
|
1244
|
+
.requiredOption('--url <url>', 'Resource webUrl')
|
|
1245
|
+
.option('-d, --description <text>', 'Title (Graph displayName; legacy alias)')
|
|
1246
|
+
.option('--display-name <text>', 'Graph displayName (same as -d)')
|
|
1247
|
+
.option('--icon <url>', 'Optional icon URL')
|
|
1248
|
+
.option('--json', 'Output as JSON')
|
|
1249
|
+
.option('--token <token>', 'Use a specific token')
|
|
1250
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
1251
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
1252
|
+
.action(
|
|
1253
|
+
async (
|
|
1254
|
+
opts: {
|
|
1255
|
+
list: string;
|
|
1256
|
+
task: string;
|
|
1257
|
+
url: string;
|
|
1258
|
+
description?: string;
|
|
1259
|
+
displayName?: string;
|
|
1260
|
+
icon?: string;
|
|
1261
|
+
json?: boolean;
|
|
1262
|
+
token?: string;
|
|
1263
|
+
identity?: string;
|
|
1264
|
+
user?: string;
|
|
1265
|
+
},
|
|
1266
|
+
cmd: any
|
|
1267
|
+
) => {
|
|
1268
|
+
checkReadOnly(cmd);
|
|
1269
|
+
const title = opts.displayName?.trim() || opts.description?.trim();
|
|
1270
|
+
if (!title) {
|
|
1271
|
+
console.error('Error: specify --display-name or -d/--description (Graph displayName)');
|
|
1272
|
+
process.exit(1);
|
|
1273
|
+
}
|
|
1274
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
1275
|
+
if (!auth.success) {
|
|
1276
|
+
console.error(`Auth error: ${auth.error}`);
|
|
1277
|
+
process.exit(1);
|
|
1278
|
+
}
|
|
1279
|
+
const { listId } = await resolveListId(auth.token!, opts.list, opts.user);
|
|
1280
|
+
const r = await addLinkedResource(
|
|
1281
|
+
auth.token!,
|
|
1282
|
+
listId,
|
|
1283
|
+
opts.task,
|
|
1284
|
+
{ webUrl: opts.url, displayName: title, iconUrl: opts.icon },
|
|
1285
|
+
opts.user
|
|
1286
|
+
);
|
|
1287
|
+
if (!r.ok || !r.data) {
|
|
1288
|
+
console.error(`Error: ${r.error?.message}`);
|
|
1289
|
+
process.exit(1);
|
|
1290
|
+
}
|
|
1291
|
+
if (opts.json) console.log(JSON.stringify(r.data, null, 2));
|
|
1292
|
+
else console.log(`\n\u2705 Linked resource added. Task: "${r.data.title}"\n`);
|
|
1293
|
+
}
|
|
1294
|
+
);
|
|
1295
|
+
|
|
1296
|
+
todoCommand
|
|
1297
|
+
.command('remove-linked-resource')
|
|
1298
|
+
.description('Remove a linked resource by matching webUrl')
|
|
1299
|
+
.requiredOption('-l, --list <name|id>', 'List name or ID')
|
|
1300
|
+
.requiredOption('-t, --task <id>', 'Task ID')
|
|
1301
|
+
.requiredOption('--url <url>', 'webUrl to remove')
|
|
1302
|
+
.option('--json', 'Output as JSON')
|
|
1303
|
+
.option('--token <token>', 'Use a specific token')
|
|
1304
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
1305
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
1306
|
+
.action(
|
|
1307
|
+
async (
|
|
1308
|
+
opts: {
|
|
1309
|
+
list: string;
|
|
1310
|
+
task: string;
|
|
1311
|
+
url: string;
|
|
1312
|
+
json?: boolean;
|
|
1313
|
+
token?: string;
|
|
1314
|
+
identity?: string;
|
|
1315
|
+
user?: string;
|
|
1316
|
+
},
|
|
1317
|
+
cmd: any
|
|
1318
|
+
) => {
|
|
1319
|
+
checkReadOnly(cmd);
|
|
1320
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
1321
|
+
if (!auth.success) {
|
|
1322
|
+
console.error(`Auth error: ${auth.error}`);
|
|
1323
|
+
process.exit(1);
|
|
1324
|
+
}
|
|
1325
|
+
const { listId } = await resolveListId(auth.token!, opts.list, opts.user);
|
|
1326
|
+
const r = await removeLinkedResourceByWebUrl(auth.token!, listId, opts.task, opts.url, opts.user);
|
|
1327
|
+
if (!r.ok || !r.data) {
|
|
1328
|
+
console.error(`Error: ${r.error?.message}`);
|
|
1329
|
+
process.exit(1);
|
|
1330
|
+
}
|
|
1331
|
+
if (opts.json) console.log(JSON.stringify(r.data, null, 2));
|
|
1332
|
+
else console.log(`\n\u2705 Removed linked resource matching URL.\n`);
|
|
1333
|
+
}
|
|
1334
|
+
);
|
|
1335
|
+
|
|
1336
|
+
todoCommand
|
|
1337
|
+
.command('upload-attachment-large')
|
|
1338
|
+
.description('Upload a large file via Graph upload session (chunked PUT)')
|
|
1339
|
+
.requiredOption('-l, --list <name|id>', 'List name or ID')
|
|
1340
|
+
.requiredOption('-t, --task <id>', 'Task ID')
|
|
1341
|
+
.requiredOption('-f, --file <path>', 'Local file path')
|
|
1342
|
+
.option('-n, --name <filename>', 'Attachment name (default: file basename)')
|
|
1343
|
+
.option('--json', 'Output as JSON')
|
|
1344
|
+
.option('--token <token>', 'Use a specific token')
|
|
1345
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
1346
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
1347
|
+
.action(
|
|
1348
|
+
async (
|
|
1349
|
+
opts: {
|
|
1350
|
+
list: string;
|
|
1351
|
+
task: string;
|
|
1352
|
+
file: string;
|
|
1353
|
+
name?: string;
|
|
1354
|
+
json?: boolean;
|
|
1355
|
+
token?: string;
|
|
1356
|
+
identity?: string;
|
|
1357
|
+
user?: string;
|
|
1358
|
+
},
|
|
1359
|
+
cmd: any
|
|
1360
|
+
) => {
|
|
1361
|
+
checkReadOnly(cmd);
|
|
1362
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
1363
|
+
if (!auth.success) {
|
|
1364
|
+
console.error(`Auth error: ${auth.error}`);
|
|
1365
|
+
process.exit(1);
|
|
1366
|
+
}
|
|
1367
|
+
const { listId } = await resolveListId(auth.token!, opts.list, opts.user);
|
|
1368
|
+
const r = await uploadLargeFileAttachment(auth.token!, listId, opts.task, opts.file, opts.name, opts.user);
|
|
1369
|
+
if (!r.ok || !r.data) {
|
|
1370
|
+
console.error(`Error: ${r.error?.message}`);
|
|
1371
|
+
process.exit(1);
|
|
1372
|
+
}
|
|
1373
|
+
if (opts.json) console.log(JSON.stringify(r.data, null, 2));
|
|
1374
|
+
else console.log(`\n\u2705 Large attachment: ${r.data.name || r.data.id} (${r.data.id})\n`);
|
|
1375
|
+
}
|
|
1376
|
+
);
|
|
1377
|
+
|
|
1378
|
+
todoCommand
|
|
1379
|
+
.command('delta')
|
|
1380
|
+
.description('One page of todo task delta (use -l for first page, or --url for nextLink/deltaLink)')
|
|
1381
|
+
.option('-l, --list <name|id>', 'List name or ID (first page only)')
|
|
1382
|
+
.option('--url <fullUrl>', 'Full nextLink or deltaLink URL from a previous response')
|
|
1383
|
+
.option('--token <token>', 'Use a specific token')
|
|
1384
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
1385
|
+
.option('--user <email>', 'Target user (first page only; --url encodes scope)')
|
|
1386
|
+
.action(async (opts: { list?: string; url?: string; token?: string; identity?: string; user?: string }) => {
|
|
1387
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
1388
|
+
if (!auth.success) {
|
|
1389
|
+
console.error(`Auth error: ${auth.error}`);
|
|
1390
|
+
process.exit(1);
|
|
1391
|
+
}
|
|
1392
|
+
const r = opts.url
|
|
1393
|
+
? await getTodoTasksDeltaPage(auth.token!, '', opts.url)
|
|
1394
|
+
: await (async () => {
|
|
1395
|
+
if (!opts.list) {
|
|
1396
|
+
console.error('Error: specify --list for the first delta page, or --url to follow nextLink/deltaLink');
|
|
1397
|
+
process.exit(1);
|
|
1398
|
+
}
|
|
1399
|
+
const { listId } = await resolveListId(auth.token!, opts.list, opts.user);
|
|
1400
|
+
return getTodoTasksDeltaPage(auth.token!, listId, undefined, opts.user);
|
|
1401
|
+
})();
|
|
1402
|
+
if (!r.ok || !r.data) {
|
|
1403
|
+
console.error(`Error: ${r.error?.message}`);
|
|
1404
|
+
process.exit(1);
|
|
1405
|
+
}
|
|
1406
|
+
console.log(JSON.stringify(r.data, null, 2));
|
|
1407
|
+
});
|
|
1408
|
+
|
|
1409
|
+
todoCommand
|
|
1410
|
+
.command('list-checklist-items')
|
|
1411
|
+
.description('List checklist items via GET collection (same items as on the task object)')
|
|
1412
|
+
.requiredOption('-l, --list <name|id>', 'List name or ID')
|
|
1413
|
+
.requiredOption('-t, --task <id>', 'Task ID')
|
|
1414
|
+
.option('--json', 'Output as JSON')
|
|
1415
|
+
.option('--token <token>', 'Use a specific token')
|
|
1416
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
1417
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
1418
|
+
.action(
|
|
1419
|
+
async (opts: { list: string; task: string; json?: boolean; token?: string; identity?: string; user?: string }) => {
|
|
1420
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
1421
|
+
if (!auth.success) {
|
|
1422
|
+
console.error(`Auth error: ${auth.error}`);
|
|
1423
|
+
process.exit(1);
|
|
1424
|
+
}
|
|
1425
|
+
const { listId } = await resolveListId(auth.token!, opts.list, opts.user);
|
|
1426
|
+
const r = await listTaskChecklistItems(auth.token!, listId, opts.task, opts.user);
|
|
1427
|
+
if (!r.ok || !r.data) {
|
|
1428
|
+
console.error(`Error: ${r.error?.message}`);
|
|
1429
|
+
process.exit(1);
|
|
1430
|
+
}
|
|
1431
|
+
if (opts.json) console.log(JSON.stringify(r.data, null, 2));
|
|
1432
|
+
else {
|
|
1433
|
+
for (const it of r.data) {
|
|
1434
|
+
console.log(`${it.isChecked ? '\u2611' : '\u2610'} ${it.displayName} (${it.id})`);
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
);
|
|
1439
|
+
|
|
1440
|
+
todoCommand
|
|
1441
|
+
.command('get-checklist-item')
|
|
1442
|
+
.description('Get one checklist item by id (Graph GET checklistItems/{id})')
|
|
1443
|
+
.requiredOption('-l, --list <name|id>', 'List name or ID')
|
|
1444
|
+
.requiredOption('-t, --task <id>', 'Task ID')
|
|
1445
|
+
.requiredOption('-c, --checklist-item <id>', 'Checklist item id')
|
|
1446
|
+
.option('--json', 'Output as JSON')
|
|
1447
|
+
.option('--token <token>', 'Use a specific token')
|
|
1448
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
1449
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
1450
|
+
.action(
|
|
1451
|
+
async (opts: {
|
|
1452
|
+
list: string;
|
|
1453
|
+
task: string;
|
|
1454
|
+
checklistItem: string;
|
|
1455
|
+
json?: boolean;
|
|
1456
|
+
token?: string;
|
|
1457
|
+
identity?: string;
|
|
1458
|
+
user?: string;
|
|
1459
|
+
}) => {
|
|
1460
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
1461
|
+
if (!auth.success) {
|
|
1462
|
+
console.error(`Auth error: ${auth.error}`);
|
|
1463
|
+
process.exit(1);
|
|
1464
|
+
}
|
|
1465
|
+
const { listId } = await resolveListId(auth.token!, opts.list, opts.user);
|
|
1466
|
+
const r = await getChecklistItem(auth.token!, listId, opts.task, opts.checklistItem, opts.user);
|
|
1467
|
+
if (!r.ok || !r.data) {
|
|
1468
|
+
console.error(`Error: ${r.error?.message}`);
|
|
1469
|
+
process.exit(1);
|
|
1470
|
+
}
|
|
1471
|
+
if (opts.json) console.log(JSON.stringify(r.data, null, 2));
|
|
1472
|
+
else {
|
|
1473
|
+
const it = r.data;
|
|
1474
|
+
console.log(`${it.isChecked ? '\u2611' : '\u2610'} ${it.displayName}`);
|
|
1475
|
+
console.log(`ID: ${it.id}`);
|
|
1476
|
+
if (it.createdDateTime) console.log(`Created: ${it.createdDateTime}`);
|
|
1477
|
+
if (it.checkedDateTime) console.log(`Checked: ${it.checkedDateTime}`);
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
);
|
|
1481
|
+
|
|
1482
|
+
const todoLinkedResourceCommand = new Command('linked-resource').description(
|
|
1483
|
+
'Graph linkedResource endpoints (per-item REST)'
|
|
1484
|
+
);
|
|
1485
|
+
|
|
1486
|
+
todoLinkedResourceCommand
|
|
1487
|
+
.command('list')
|
|
1488
|
+
.description('List linked resources for a task')
|
|
1489
|
+
.requiredOption('-l, --list <name|id>', 'List name or ID')
|
|
1490
|
+
.requiredOption('-t, --task <id>', 'Task ID')
|
|
1491
|
+
.option('--json', 'Output as JSON')
|
|
1492
|
+
.option('--token <token>', 'Use a specific token')
|
|
1493
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
1494
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
1495
|
+
.action(
|
|
1496
|
+
async (opts: { list: string; task: string; json?: boolean; token?: string; identity?: string; user?: string }) => {
|
|
1497
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
1498
|
+
if (!auth.success) {
|
|
1499
|
+
console.error(`Auth error: ${auth.error}`);
|
|
1500
|
+
process.exit(1);
|
|
1501
|
+
}
|
|
1502
|
+
const { listId } = await resolveListId(auth.token!, opts.list, opts.user);
|
|
1503
|
+
const r = await listTaskLinkedResources(auth.token!, listId, opts.task, opts.user);
|
|
1504
|
+
if (!r.ok || !r.data) {
|
|
1505
|
+
console.error(`Error: ${r.error?.message}`);
|
|
1506
|
+
process.exit(1);
|
|
1507
|
+
}
|
|
1508
|
+
if (opts.json) console.log(JSON.stringify(r.data, null, 2));
|
|
1509
|
+
else {
|
|
1510
|
+
for (const lr of r.data) {
|
|
1511
|
+
console.log(`- ${linkedTitle(lr)} ${lr.webUrl ?? ''} (${lr.id})`);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
);
|
|
1516
|
+
|
|
1517
|
+
todoLinkedResourceCommand
|
|
1518
|
+
.command('create')
|
|
1519
|
+
.description('POST a linkedResource (Graph native create)')
|
|
1520
|
+
.requiredOption('-l, --list <name|id>', 'List name or ID')
|
|
1521
|
+
.requiredOption('-t, --task <id>', 'Task ID')
|
|
1522
|
+
.option('--url <url>', 'webUrl')
|
|
1523
|
+
.requiredOption('-n, --name <text>', 'displayName')
|
|
1524
|
+
.option('--application-name <text>', 'applicationName')
|
|
1525
|
+
.option('--external-id <id>', 'externalId')
|
|
1526
|
+
.option('--json', 'Output as JSON')
|
|
1527
|
+
.option('--token <token>', 'Use a specific token')
|
|
1528
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
1529
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
1530
|
+
.action(
|
|
1531
|
+
async (
|
|
1532
|
+
opts: {
|
|
1533
|
+
list: string;
|
|
1534
|
+
task: string;
|
|
1535
|
+
url?: string;
|
|
1536
|
+
name: string;
|
|
1537
|
+
applicationName?: string;
|
|
1538
|
+
externalId?: string;
|
|
1539
|
+
json?: boolean;
|
|
1540
|
+
token?: string;
|
|
1541
|
+
identity?: string;
|
|
1542
|
+
user?: string;
|
|
1543
|
+
},
|
|
1544
|
+
cmd: any
|
|
1545
|
+
) => {
|
|
1546
|
+
checkReadOnly(cmd);
|
|
1547
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
1548
|
+
if (!auth.success) {
|
|
1549
|
+
console.error(`Auth error: ${auth.error}`);
|
|
1550
|
+
process.exit(1);
|
|
1551
|
+
}
|
|
1552
|
+
const { listId } = await resolveListId(auth.token!, opts.list, opts.user);
|
|
1553
|
+
const r = await createTaskLinkedResource(
|
|
1554
|
+
auth.token!,
|
|
1555
|
+
listId,
|
|
1556
|
+
opts.task,
|
|
1557
|
+
{
|
|
1558
|
+
webUrl: opts.url,
|
|
1559
|
+
displayName: opts.name,
|
|
1560
|
+
applicationName: opts.applicationName,
|
|
1561
|
+
externalId: opts.externalId
|
|
1562
|
+
},
|
|
1563
|
+
opts.user
|
|
1564
|
+
);
|
|
1565
|
+
if (!r.ok || !r.data) {
|
|
1566
|
+
console.error(`Error: ${r.error?.message}`);
|
|
1567
|
+
process.exit(1);
|
|
1568
|
+
}
|
|
1569
|
+
if (opts.json) console.log(JSON.stringify(r.data, null, 2));
|
|
1570
|
+
else console.log(`\n\u2705 Linked resource: ${linkedTitle(r.data)} (${r.data.id})\n`);
|
|
1571
|
+
}
|
|
1572
|
+
);
|
|
1573
|
+
|
|
1574
|
+
todoLinkedResourceCommand
|
|
1575
|
+
.command('get')
|
|
1576
|
+
.description('GET one linked resource by id')
|
|
1577
|
+
.requiredOption('-l, --list <name|id>', 'List name or ID')
|
|
1578
|
+
.requiredOption('-t, --task <id>', 'Task ID')
|
|
1579
|
+
.requiredOption('-i, --id <linkedResourceId>', 'linkedResource id')
|
|
1580
|
+
.option('--json', 'Output as JSON')
|
|
1581
|
+
.option('--token <token>', 'Use a specific token')
|
|
1582
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
1583
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
1584
|
+
.action(
|
|
1585
|
+
async (opts: {
|
|
1586
|
+
list: string;
|
|
1587
|
+
task: string;
|
|
1588
|
+
id: string;
|
|
1589
|
+
json?: boolean;
|
|
1590
|
+
token?: string;
|
|
1591
|
+
identity?: string;
|
|
1592
|
+
user?: string;
|
|
1593
|
+
}) => {
|
|
1594
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
1595
|
+
if (!auth.success) {
|
|
1596
|
+
console.error(`Auth error: ${auth.error}`);
|
|
1597
|
+
process.exit(1);
|
|
1598
|
+
}
|
|
1599
|
+
const { listId } = await resolveListId(auth.token!, opts.list, opts.user);
|
|
1600
|
+
const r = await getTaskLinkedResource(auth.token!, listId, opts.task, opts.id, opts.user);
|
|
1601
|
+
if (!r.ok || !r.data) {
|
|
1602
|
+
console.error(`Error: ${r.error?.message}`);
|
|
1603
|
+
process.exit(1);
|
|
1604
|
+
}
|
|
1605
|
+
if (opts.json) console.log(JSON.stringify(r.data, null, 2));
|
|
1606
|
+
else console.log(JSON.stringify(r.data, null, 2));
|
|
1607
|
+
}
|
|
1608
|
+
);
|
|
1609
|
+
|
|
1610
|
+
todoLinkedResourceCommand
|
|
1611
|
+
.command('update')
|
|
1612
|
+
.description('PATCH a linked resource')
|
|
1613
|
+
.requiredOption('-l, --list <name|id>', 'List name or ID')
|
|
1614
|
+
.requiredOption('-t, --task <id>', 'Task ID')
|
|
1615
|
+
.requiredOption('-i, --id <linkedResourceId>', 'linkedResource id')
|
|
1616
|
+
.option('--url <url>', 'webUrl')
|
|
1617
|
+
.option('-n, --name <text>', 'displayName')
|
|
1618
|
+
.option('--application-name <text>', 'applicationName')
|
|
1619
|
+
.option('--external-id <id>', 'externalId')
|
|
1620
|
+
.option('--json', 'Output as JSON')
|
|
1621
|
+
.option('--token <token>', 'Use a specific token')
|
|
1622
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
1623
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
1624
|
+
.action(
|
|
1625
|
+
async (
|
|
1626
|
+
opts: {
|
|
1627
|
+
list: string;
|
|
1628
|
+
task: string;
|
|
1629
|
+
id: string;
|
|
1630
|
+
url?: string;
|
|
1631
|
+
name?: string;
|
|
1632
|
+
applicationName?: string;
|
|
1633
|
+
externalId?: string;
|
|
1634
|
+
json?: boolean;
|
|
1635
|
+
token?: string;
|
|
1636
|
+
identity?: string;
|
|
1637
|
+
user?: string;
|
|
1638
|
+
},
|
|
1639
|
+
cmd: any
|
|
1640
|
+
) => {
|
|
1641
|
+
checkReadOnly(cmd);
|
|
1642
|
+
if (
|
|
1643
|
+
opts.url === undefined &&
|
|
1644
|
+
opts.name === undefined &&
|
|
1645
|
+
opts.applicationName === undefined &&
|
|
1646
|
+
opts.externalId === undefined
|
|
1647
|
+
) {
|
|
1648
|
+
console.error('Error: specify at least one of --url, --name, --application-name, --external-id');
|
|
1649
|
+
process.exit(1);
|
|
1650
|
+
}
|
|
1651
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
1652
|
+
if (!auth.success) {
|
|
1653
|
+
console.error(`Auth error: ${auth.error}`);
|
|
1654
|
+
process.exit(1);
|
|
1655
|
+
}
|
|
1656
|
+
const { listId } = await resolveListId(auth.token!, opts.list, opts.user);
|
|
1657
|
+
const r = await updateTaskLinkedResource(
|
|
1658
|
+
auth.token!,
|
|
1659
|
+
listId,
|
|
1660
|
+
opts.task,
|
|
1661
|
+
opts.id,
|
|
1662
|
+
{
|
|
1663
|
+
webUrl: opts.url,
|
|
1664
|
+
displayName: opts.name,
|
|
1665
|
+
applicationName: opts.applicationName,
|
|
1666
|
+
externalId: opts.externalId
|
|
1667
|
+
},
|
|
1668
|
+
opts.user
|
|
1669
|
+
);
|
|
1670
|
+
if (!r.ok || !r.data) {
|
|
1671
|
+
console.error(`Error: ${r.error?.message}`);
|
|
1672
|
+
process.exit(1);
|
|
1673
|
+
}
|
|
1674
|
+
if (opts.json) console.log(JSON.stringify(r.data, null, 2));
|
|
1675
|
+
else console.log(`\n\u2705 Updated linked resource ${opts.id}\n`);
|
|
1676
|
+
}
|
|
1677
|
+
);
|
|
1678
|
+
|
|
1679
|
+
todoLinkedResourceCommand
|
|
1680
|
+
.command('delete')
|
|
1681
|
+
.description('DELETE a linked resource by id')
|
|
1682
|
+
.requiredOption('-l, --list <name|id>', 'List name or ID')
|
|
1683
|
+
.requiredOption('-t, --task <id>', 'Task ID')
|
|
1684
|
+
.requiredOption('-i, --id <linkedResourceId>', 'linkedResource id')
|
|
1685
|
+
.option('--confirm', 'Confirm without prompt')
|
|
1686
|
+
.option('--token <token>', 'Use a specific token')
|
|
1687
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
1688
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
1689
|
+
.action(
|
|
1690
|
+
async (
|
|
1691
|
+
opts: {
|
|
1692
|
+
list: string;
|
|
1693
|
+
task: string;
|
|
1694
|
+
id: string;
|
|
1695
|
+
confirm?: boolean;
|
|
1696
|
+
token?: string;
|
|
1697
|
+
identity?: string;
|
|
1698
|
+
user?: string;
|
|
1699
|
+
},
|
|
1700
|
+
cmd: any
|
|
1701
|
+
) => {
|
|
1702
|
+
checkReadOnly(cmd);
|
|
1703
|
+
if (!opts.confirm) {
|
|
1704
|
+
console.log(`Delete linked resource ${opts.id}? Run with --confirm.`);
|
|
1705
|
+
process.exit(1);
|
|
1706
|
+
}
|
|
1707
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
1708
|
+
if (!auth.success) {
|
|
1709
|
+
console.error(`Auth error: ${auth.error}`);
|
|
1710
|
+
process.exit(1);
|
|
1711
|
+
}
|
|
1712
|
+
const { listId } = await resolveListId(auth.token!, opts.list, opts.user);
|
|
1713
|
+
const r = await deleteTaskLinkedResource(auth.token!, listId, opts.task, opts.id, opts.user);
|
|
1714
|
+
if (!r.ok) {
|
|
1715
|
+
console.error(`Error: ${r.error?.message}`);
|
|
1716
|
+
process.exit(1);
|
|
1717
|
+
}
|
|
1718
|
+
console.log(`\n\u2705 Deleted linked resource ${opts.id}\n`);
|
|
1719
|
+
}
|
|
1720
|
+
);
|
|
1721
|
+
|
|
1722
|
+
todoCommand.addCommand(todoLinkedResourceCommand);
|
|
1723
|
+
|
|
1724
|
+
const todoListExtensionCommand = new Command('list-extension').description(
|
|
1725
|
+
'Open type extensions on a task list (Graph)'
|
|
1726
|
+
);
|
|
1727
|
+
|
|
1728
|
+
todoListExtensionCommand
|
|
1729
|
+
.command('list')
|
|
1730
|
+
.description('List open extensions on a task list')
|
|
1731
|
+
.requiredOption('-l, --list <name|id>', 'List name or ID')
|
|
1732
|
+
.option('--json', 'Output as JSON')
|
|
1733
|
+
.option('--token <token>', 'Use a specific token')
|
|
1734
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
1735
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
1736
|
+
.action(async (opts: { list: string; json?: boolean; token?: string; identity?: string; user?: string }) => {
|
|
1737
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
1738
|
+
if (!auth.success) {
|
|
1739
|
+
console.error(`Auth error: ${auth.error}`);
|
|
1740
|
+
process.exit(1);
|
|
1741
|
+
}
|
|
1742
|
+
const { listId } = await resolveListId(auth.token!, opts.list, opts.user);
|
|
1743
|
+
const r = await listTodoListOpenExtensions(auth.token!, listId, opts.user);
|
|
1744
|
+
if (!r.ok || !r.data) {
|
|
1745
|
+
console.error(`Error: ${r.error?.message}`);
|
|
1746
|
+
process.exit(1);
|
|
1747
|
+
}
|
|
1748
|
+
if (opts.json) console.log(JSON.stringify(r.data, null, 2));
|
|
1749
|
+
else {
|
|
1750
|
+
for (const ext of r.data) {
|
|
1751
|
+
const name = (ext.extensionName as string) || JSON.stringify(ext);
|
|
1752
|
+
console.log(`- ${name}`);
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
});
|
|
1756
|
+
|
|
1757
|
+
todoListExtensionCommand
|
|
1758
|
+
.command('get')
|
|
1759
|
+
.description('Get one open extension on a list')
|
|
1760
|
+
.requiredOption('-l, --list <name|id>', 'List name or ID')
|
|
1761
|
+
.requiredOption('-n, --name <id>', 'extensionName')
|
|
1762
|
+
.option('--json', 'Output as JSON')
|
|
1763
|
+
.option('--token <token>', 'Use a specific token')
|
|
1764
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
1765
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
1766
|
+
.action(
|
|
1767
|
+
async (opts: { list: string; name: string; json?: boolean; token?: string; identity?: string; user?: string }) => {
|
|
1768
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
1769
|
+
if (!auth.success) {
|
|
1770
|
+
console.error(`Auth error: ${auth.error}`);
|
|
1771
|
+
process.exit(1);
|
|
1772
|
+
}
|
|
1773
|
+
const { listId } = await resolveListId(auth.token!, opts.list, opts.user);
|
|
1774
|
+
const r = await getTodoListOpenExtension(auth.token!, listId, opts.name, opts.user);
|
|
1775
|
+
if (!r.ok || !r.data) {
|
|
1776
|
+
console.error(`Error: ${r.error?.message}`);
|
|
1777
|
+
process.exit(1);
|
|
1778
|
+
}
|
|
1779
|
+
if (opts.json) console.log(JSON.stringify(r.data, null, 2));
|
|
1780
|
+
else console.log(JSON.stringify(r.data, null, 2));
|
|
1781
|
+
}
|
|
1782
|
+
);
|
|
1783
|
+
|
|
1784
|
+
todoListExtensionCommand
|
|
1785
|
+
.command('set')
|
|
1786
|
+
.description('Create an open extension on a list')
|
|
1787
|
+
.requiredOption('-l, --list <name|id>', 'List name or ID')
|
|
1788
|
+
.requiredOption('-n, --name <id>', 'extensionName')
|
|
1789
|
+
.requiredOption('--json-file <path>', 'JSON object: custom properties')
|
|
1790
|
+
.option('--json', 'Output as JSON')
|
|
1791
|
+
.option('--token <token>', 'Use a specific token')
|
|
1792
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
1793
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
1794
|
+
.action(
|
|
1795
|
+
async (
|
|
1796
|
+
opts: {
|
|
1797
|
+
list: string;
|
|
1798
|
+
name: string;
|
|
1799
|
+
jsonFile: string;
|
|
1800
|
+
json?: boolean;
|
|
1801
|
+
token?: string;
|
|
1802
|
+
identity?: string;
|
|
1803
|
+
user?: string;
|
|
1804
|
+
},
|
|
1805
|
+
cmd: any
|
|
1806
|
+
) => {
|
|
1807
|
+
checkReadOnly(cmd);
|
|
1808
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
1809
|
+
if (!auth.success) {
|
|
1810
|
+
console.error(`Auth error: ${auth.error}`);
|
|
1811
|
+
process.exit(1);
|
|
1812
|
+
}
|
|
1813
|
+
const { listId } = await resolveListId(auth.token!, opts.list, opts.user);
|
|
1814
|
+
const raw = await readFile(opts.jsonFile, 'utf-8');
|
|
1815
|
+
const data = JSON.parse(raw) as Record<string, unknown>;
|
|
1816
|
+
const r = await setTodoListOpenExtension(auth.token!, listId, opts.name, data, opts.user);
|
|
1817
|
+
if (!r.ok || !r.data) {
|
|
1818
|
+
console.error(`Error: ${r.error?.message}`);
|
|
1819
|
+
process.exit(1);
|
|
1820
|
+
}
|
|
1821
|
+
if (opts.json) console.log(JSON.stringify(r.data, null, 2));
|
|
1822
|
+
else console.log(`\n\u2705 List extension set: ${opts.name}\n`);
|
|
1823
|
+
}
|
|
1824
|
+
);
|
|
1825
|
+
|
|
1826
|
+
todoListExtensionCommand
|
|
1827
|
+
.command('update')
|
|
1828
|
+
.description('PATCH a list open extension')
|
|
1829
|
+
.requiredOption('-l, --list <name|id>', 'List name or ID')
|
|
1830
|
+
.requiredOption('-n, --name <id>', 'extensionName')
|
|
1831
|
+
.requiredOption('--json-file <path>', 'JSON patch body')
|
|
1832
|
+
.option('--token <token>', 'Use a specific token')
|
|
1833
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
1834
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
1835
|
+
.action(
|
|
1836
|
+
async (
|
|
1837
|
+
opts: { list: string; name: string; jsonFile: string; token?: string; identity?: string; user?: string },
|
|
1838
|
+
cmd: any
|
|
1839
|
+
) => {
|
|
1840
|
+
checkReadOnly(cmd);
|
|
1841
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
1842
|
+
if (!auth.success) {
|
|
1843
|
+
console.error(`Auth error: ${auth.error}`);
|
|
1844
|
+
process.exit(1);
|
|
1845
|
+
}
|
|
1846
|
+
const { listId } = await resolveListId(auth.token!, opts.list, opts.user);
|
|
1847
|
+
const raw = await readFile(opts.jsonFile, 'utf-8');
|
|
1848
|
+
const patch = JSON.parse(raw) as Record<string, unknown>;
|
|
1849
|
+
const r = await updateTodoListOpenExtension(auth.token!, listId, opts.name, patch, opts.user);
|
|
1850
|
+
if (!r.ok) {
|
|
1851
|
+
console.error(`Error: ${r.error?.message}`);
|
|
1852
|
+
process.exit(1);
|
|
1853
|
+
}
|
|
1854
|
+
console.log('\n\u2705 List extension updated.\n');
|
|
1855
|
+
}
|
|
1856
|
+
);
|
|
1857
|
+
|
|
1858
|
+
todoListExtensionCommand
|
|
1859
|
+
.command('delete')
|
|
1860
|
+
.description('Delete a list open extension')
|
|
1861
|
+
.requiredOption('-l, --list <name|id>', 'List name or ID')
|
|
1862
|
+
.requiredOption('-n, --name <id>', 'extensionName')
|
|
1863
|
+
.option('--confirm', 'Confirm without prompt')
|
|
1864
|
+
.option('--token <token>', 'Use a specific token')
|
|
1865
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
1866
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
1867
|
+
.action(
|
|
1868
|
+
async (
|
|
1869
|
+
opts: { list: string; name: string; confirm?: boolean; token?: string; identity?: string; user?: string },
|
|
1870
|
+
cmd: any
|
|
1871
|
+
) => {
|
|
1872
|
+
checkReadOnly(cmd);
|
|
1873
|
+
if (!opts.confirm) {
|
|
1874
|
+
console.log(`Delete list extension "${opts.name}"? Run with --confirm.`);
|
|
1875
|
+
process.exit(1);
|
|
1876
|
+
}
|
|
1877
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
1878
|
+
if (!auth.success) {
|
|
1879
|
+
console.error(`Auth error: ${auth.error}`);
|
|
1880
|
+
process.exit(1);
|
|
1881
|
+
}
|
|
1882
|
+
const { listId } = await resolveListId(auth.token!, opts.list, opts.user);
|
|
1883
|
+
const r = await deleteTodoListOpenExtension(auth.token!, listId, opts.name, opts.user);
|
|
1884
|
+
if (!r.ok) {
|
|
1885
|
+
console.error(`Error: ${r.error?.message}`);
|
|
1886
|
+
process.exit(1);
|
|
1887
|
+
}
|
|
1888
|
+
console.log(`\n\u2705 Deleted list extension: ${opts.name}\n`);
|
|
1889
|
+
}
|
|
1890
|
+
);
|
|
1891
|
+
|
|
1892
|
+
todoCommand.addCommand(todoListExtensionCommand);
|
|
1893
|
+
|
|
1894
|
+
const todoExtensionCommand = new Command('extension').description('Open type extensions on a task (Graph)');
|
|
1895
|
+
|
|
1896
|
+
todoExtensionCommand
|
|
1897
|
+
.command('list')
|
|
1898
|
+
.description('List open extensions on a task')
|
|
1899
|
+
.requiredOption('-l, --list <name|id>', 'List name or ID')
|
|
1900
|
+
.requiredOption('-t, --task <id>', 'Task ID')
|
|
1901
|
+
.option('--json', 'Output as JSON')
|
|
1902
|
+
.option('--token <token>', 'Use a specific token')
|
|
1903
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
1904
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
1905
|
+
.action(
|
|
1906
|
+
async (opts: { list: string; task: string; json?: boolean; token?: string; identity?: string; user?: string }) => {
|
|
1907
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
1908
|
+
if (!auth.success) {
|
|
1909
|
+
console.error(`Auth error: ${auth.error}`);
|
|
1910
|
+
process.exit(1);
|
|
1911
|
+
}
|
|
1912
|
+
const { listId } = await resolveListId(auth.token!, opts.list, opts.user);
|
|
1913
|
+
const r = await listTaskOpenExtensions(auth.token!, listId, opts.task, opts.user);
|
|
1914
|
+
if (!r.ok || !r.data) {
|
|
1915
|
+
console.error(`Error: ${r.error?.message}`);
|
|
1916
|
+
process.exit(1);
|
|
1917
|
+
}
|
|
1918
|
+
if (opts.json) console.log(JSON.stringify(r.data, null, 2));
|
|
1919
|
+
else {
|
|
1920
|
+
for (const ext of r.data) {
|
|
1921
|
+
const name = (ext.extensionName as string) || JSON.stringify(ext);
|
|
1922
|
+
console.log(`- ${name}`);
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
);
|
|
1927
|
+
|
|
1928
|
+
todoExtensionCommand
|
|
1929
|
+
.command('get')
|
|
1930
|
+
.description('Get one open extension by name')
|
|
1931
|
+
.requiredOption('-l, --list <name|id>', 'List name or ID')
|
|
1932
|
+
.requiredOption('-t, --task <id>', 'Task ID')
|
|
1933
|
+
.requiredOption('-n, --name <id>', 'extensionName')
|
|
1934
|
+
.option('--json', 'Output as JSON')
|
|
1935
|
+
.option('--token <token>', 'Use a specific token')
|
|
1936
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
1937
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
1938
|
+
.action(
|
|
1939
|
+
async (opts: {
|
|
1940
|
+
list: string;
|
|
1941
|
+
task: string;
|
|
1942
|
+
name: string;
|
|
1943
|
+
json?: boolean;
|
|
1944
|
+
token?: string;
|
|
1945
|
+
identity?: string;
|
|
1946
|
+
user?: string;
|
|
1947
|
+
}) => {
|
|
1948
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
1949
|
+
if (!auth.success) {
|
|
1950
|
+
console.error(`Auth error: ${auth.error}`);
|
|
1951
|
+
process.exit(1);
|
|
1952
|
+
}
|
|
1953
|
+
const { listId } = await resolveListId(auth.token!, opts.list, opts.user);
|
|
1954
|
+
const r = await getTaskOpenExtension(auth.token!, listId, opts.task, opts.name, opts.user);
|
|
1955
|
+
if (!r.ok || !r.data) {
|
|
1956
|
+
console.error(`Error: ${r.error?.message}`);
|
|
1957
|
+
process.exit(1);
|
|
1958
|
+
}
|
|
1959
|
+
if (opts.json) console.log(JSON.stringify(r.data, null, 2));
|
|
1960
|
+
else console.log(JSON.stringify(r.data, null, 2));
|
|
1961
|
+
}
|
|
1962
|
+
);
|
|
1963
|
+
|
|
1964
|
+
todoExtensionCommand
|
|
1965
|
+
.command('set')
|
|
1966
|
+
.description('Create an open extension (POST); JSON file is merged with extensionName')
|
|
1967
|
+
.requiredOption('-l, --list <name|id>', 'List name or ID')
|
|
1968
|
+
.requiredOption('-t, --task <id>', 'Task ID')
|
|
1969
|
+
.requiredOption('-n, --name <id>', 'extensionName')
|
|
1970
|
+
.requiredOption('--json-file <path>', 'JSON object: custom properties (extensionName added automatically)')
|
|
1971
|
+
.option('--json', 'Output as JSON')
|
|
1972
|
+
.option('--token <token>', 'Use a specific token')
|
|
1973
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
1974
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
1975
|
+
.action(
|
|
1976
|
+
async (
|
|
1977
|
+
opts: {
|
|
1978
|
+
list: string;
|
|
1979
|
+
task: string;
|
|
1980
|
+
name: string;
|
|
1981
|
+
jsonFile: string;
|
|
1982
|
+
json?: boolean;
|
|
1983
|
+
token?: string;
|
|
1984
|
+
identity?: string;
|
|
1985
|
+
user?: string;
|
|
1986
|
+
},
|
|
1987
|
+
cmd: any
|
|
1988
|
+
) => {
|
|
1989
|
+
checkReadOnly(cmd);
|
|
1990
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
1991
|
+
if (!auth.success) {
|
|
1992
|
+
console.error(`Auth error: ${auth.error}`);
|
|
1993
|
+
process.exit(1);
|
|
1994
|
+
}
|
|
1995
|
+
const { listId } = await resolveListId(auth.token!, opts.list, opts.user);
|
|
1996
|
+
const raw = await readFile(opts.jsonFile, 'utf-8');
|
|
1997
|
+
const data = JSON.parse(raw) as Record<string, unknown>;
|
|
1998
|
+
const r = await setTaskOpenExtension(auth.token!, listId, opts.task, opts.name, data, opts.user);
|
|
1999
|
+
if (!r.ok || !r.data) {
|
|
2000
|
+
console.error(`Error: ${r.error?.message}`);
|
|
2001
|
+
process.exit(1);
|
|
2002
|
+
}
|
|
2003
|
+
if (opts.json) console.log(JSON.stringify(r.data, null, 2));
|
|
2004
|
+
else console.log(`\n\u2705 Extension set: ${opts.name}\n`);
|
|
2005
|
+
}
|
|
2006
|
+
);
|
|
2007
|
+
|
|
2008
|
+
todoExtensionCommand
|
|
2009
|
+
.command('update')
|
|
2010
|
+
.description('PATCH an open extension (partial update)')
|
|
2011
|
+
.requiredOption('-l, --list <name|id>', 'List name or ID')
|
|
2012
|
+
.requiredOption('-t, --task <id>', 'Task ID')
|
|
2013
|
+
.requiredOption('-n, --name <id>', 'extensionName')
|
|
2014
|
+
.requiredOption('--json-file <path>', 'JSON object: properties to patch')
|
|
2015
|
+
.option('--token <token>', 'Use a specific token')
|
|
2016
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
2017
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
2018
|
+
.action(
|
|
2019
|
+
async (
|
|
2020
|
+
opts: {
|
|
2021
|
+
list: string;
|
|
2022
|
+
task: string;
|
|
2023
|
+
name: string;
|
|
2024
|
+
jsonFile: string;
|
|
2025
|
+
token?: string;
|
|
2026
|
+
identity?: string;
|
|
2027
|
+
user?: string;
|
|
2028
|
+
},
|
|
2029
|
+
cmd: any
|
|
2030
|
+
) => {
|
|
2031
|
+
checkReadOnly(cmd);
|
|
2032
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
2033
|
+
if (!auth.success) {
|
|
2034
|
+
console.error(`Auth error: ${auth.error}`);
|
|
2035
|
+
process.exit(1);
|
|
2036
|
+
}
|
|
2037
|
+
const { listId } = await resolveListId(auth.token!, opts.list, opts.user);
|
|
2038
|
+
const raw = await readFile(opts.jsonFile, 'utf-8');
|
|
2039
|
+
const patch = JSON.parse(raw) as Record<string, unknown>;
|
|
2040
|
+
const r = await updateTaskOpenExtension(auth.token!, listId, opts.task, opts.name, patch, opts.user);
|
|
2041
|
+
if (!r.ok) {
|
|
2042
|
+
console.error(`Error: ${r.error?.message}`);
|
|
2043
|
+
process.exit(1);
|
|
2044
|
+
}
|
|
2045
|
+
console.log('\n\u2705 Extension updated.\n');
|
|
2046
|
+
}
|
|
2047
|
+
);
|
|
2048
|
+
|
|
2049
|
+
todoExtensionCommand
|
|
2050
|
+
.command('delete')
|
|
2051
|
+
.description('Delete an open extension')
|
|
2052
|
+
.requiredOption('-l, --list <name|id>', 'List name or ID')
|
|
2053
|
+
.requiredOption('-t, --task <id>', 'Task ID')
|
|
2054
|
+
.requiredOption('-n, --name <id>', 'extensionName')
|
|
2055
|
+
.option('--confirm', 'Confirm without prompt')
|
|
2056
|
+
.option('--token <token>', 'Use a specific token')
|
|
2057
|
+
.option('--identity <name>', 'Graph token cache identity (default: default)')
|
|
2058
|
+
.option('--user <email>', 'Target user or shared mailbox (Graph delegation)')
|
|
2059
|
+
.action(
|
|
2060
|
+
async (
|
|
2061
|
+
opts: {
|
|
2062
|
+
list: string;
|
|
2063
|
+
task: string;
|
|
2064
|
+
name: string;
|
|
2065
|
+
confirm?: boolean;
|
|
2066
|
+
token?: string;
|
|
2067
|
+
identity?: string;
|
|
2068
|
+
user?: string;
|
|
2069
|
+
},
|
|
2070
|
+
cmd: any
|
|
2071
|
+
) => {
|
|
2072
|
+
checkReadOnly(cmd);
|
|
2073
|
+
if (!opts.confirm) {
|
|
2074
|
+
console.log(`Delete extension "${opts.name}"? Run with --confirm.`);
|
|
2075
|
+
process.exit(1);
|
|
2076
|
+
}
|
|
2077
|
+
const auth = await resolveGraphAuth({ token: opts.token, identity: opts.identity });
|
|
2078
|
+
if (!auth.success) {
|
|
2079
|
+
console.error(`Auth error: ${auth.error}`);
|
|
2080
|
+
process.exit(1);
|
|
2081
|
+
}
|
|
2082
|
+
const { listId } = await resolveListId(auth.token!, opts.list, opts.user);
|
|
2083
|
+
const r = await deleteTaskOpenExtension(auth.token!, listId, opts.task, opts.name, opts.user);
|
|
2084
|
+
if (!r.ok) {
|
|
2085
|
+
console.error(`Error: ${r.error?.message}`);
|
|
2086
|
+
process.exit(1);
|
|
2087
|
+
}
|
|
2088
|
+
console.log(`\n\u2705 Deleted extension: ${opts.name}\n`);
|
|
2089
|
+
}
|
|
2090
|
+
);
|
|
2091
|
+
|
|
2092
|
+
todoCommand.addCommand(todoExtensionCommand);
|