@lifestreamdynamics/vault-cli 1.2.0 → 1.3.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/README.md +140 -30
- package/dist/client.d.ts +4 -0
- package/dist/client.js +12 -11
- package/dist/commands/admin.js +5 -5
- package/dist/commands/ai.js +17 -4
- package/dist/commands/auth.js +10 -105
- package/dist/commands/booking.d.ts +2 -0
- package/dist/commands/booking.js +739 -0
- package/dist/commands/calendar.js +725 -6
- package/dist/commands/completion.d.ts +5 -0
- package/dist/commands/completion.js +60 -0
- package/dist/commands/config.js +17 -16
- package/dist/commands/connectors.js +12 -1
- package/dist/commands/custom-domains.js +6 -1
- package/dist/commands/docs.js +12 -5
- package/dist/commands/hooks.js +6 -1
- package/dist/commands/links.js +9 -2
- package/dist/commands/mfa.js +1 -70
- package/dist/commands/plugins.d.ts +2 -0
- package/dist/commands/plugins.js +172 -0
- package/dist/commands/publish.js +13 -3
- package/dist/commands/saml.d.ts +2 -0
- package/dist/commands/saml.js +220 -0
- package/dist/commands/scim.d.ts +2 -0
- package/dist/commands/scim.js +238 -0
- package/dist/commands/shares.js +25 -3
- package/dist/commands/subscription.js +9 -2
- package/dist/commands/sync.js +3 -0
- package/dist/commands/teams.js +141 -8
- package/dist/commands/user.js +122 -9
- package/dist/commands/vaults.js +17 -8
- package/dist/commands/webhooks.js +6 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.js +7 -3
- package/dist/index.js +20 -1
- package/dist/lib/credential-manager.js +32 -7
- package/dist/lib/migration.js +2 -2
- package/dist/lib/profiles.js +4 -4
- package/dist/sync/config.js +2 -2
- package/dist/sync/daemon-worker.js +13 -6
- package/dist/sync/daemon.js +2 -1
- package/dist/sync/remote-poller.js +7 -3
- package/dist/sync/state.js +2 -2
- package/dist/utils/confirm.d.ts +11 -0
- package/dist/utils/confirm.js +23 -0
- package/dist/utils/format.js +1 -1
- package/dist/utils/output.js +4 -1
- package/dist/utils/prompt.d.ts +29 -0
- package/dist/utils/prompt.js +146 -0
- package/package.json +2 -2
|
@@ -8,8 +8,8 @@ export function registerCalendarCommands(program) {
|
|
|
8
8
|
addGlobalFlags(calendar.command('view')
|
|
9
9
|
.description('View calendar activity for a vault')
|
|
10
10
|
.argument('<vaultId>', 'Vault ID')
|
|
11
|
-
.option('--start <date>', 'Start date (YYYY-MM-DD)'
|
|
12
|
-
.option('--end <date>', 'End date (YYYY-MM-DD)'
|
|
11
|
+
.option('--start <date>', 'Start date (YYYY-MM-DD)')
|
|
12
|
+
.option('--end <date>', 'End date (YYYY-MM-DD)'))
|
|
13
13
|
.action(async (vaultId, _opts) => {
|
|
14
14
|
const flags = resolveFlags(_opts);
|
|
15
15
|
const out = createOutput(flags);
|
|
@@ -17,8 +17,8 @@ export function registerCalendarCommands(program) {
|
|
|
17
17
|
try {
|
|
18
18
|
const client = await getClientAsync();
|
|
19
19
|
const response = await client.calendar.getActivity(vaultId, {
|
|
20
|
-
start: _opts.start,
|
|
21
|
-
end: _opts.end,
|
|
20
|
+
start: _opts.start ?? getDefaultStart(),
|
|
21
|
+
end: _opts.end ?? getDefaultEnd(),
|
|
22
22
|
});
|
|
23
23
|
out.stopSpinner();
|
|
24
24
|
if (flags.output === 'text') {
|
|
@@ -50,6 +50,7 @@ export function registerCalendarCommands(program) {
|
|
|
50
50
|
}
|
|
51
51
|
});
|
|
52
52
|
// calendar due
|
|
53
|
+
const VALID_DUE_STATUSES = ['overdue', 'upcoming', 'all'];
|
|
53
54
|
addGlobalFlags(calendar.command('due')
|
|
54
55
|
.description('List documents with due dates')
|
|
55
56
|
.argument('<vaultId>', 'Vault ID')
|
|
@@ -57,11 +58,17 @@ export function registerCalendarCommands(program) {
|
|
|
57
58
|
.action(async (vaultId, _opts) => {
|
|
58
59
|
const flags = resolveFlags(_opts);
|
|
59
60
|
const out = createOutput(flags);
|
|
61
|
+
const statusVal = _opts.status;
|
|
62
|
+
if (statusVal && !VALID_DUE_STATUSES.includes(statusVal)) {
|
|
63
|
+
out.error(`Invalid --status "${statusVal}". Must be one of: ${VALID_DUE_STATUSES.join(', ')}`);
|
|
64
|
+
process.exitCode = 1;
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
60
67
|
out.startSpinner('Loading due dates...');
|
|
61
68
|
try {
|
|
62
69
|
const client = await getClientAsync();
|
|
63
70
|
const docs = await client.calendar.getDueDates(vaultId, {
|
|
64
|
-
status:
|
|
71
|
+
status: (statusVal ?? 'all'),
|
|
65
72
|
});
|
|
66
73
|
out.stopSpinner();
|
|
67
74
|
out.list(docs.map(d => ({
|
|
@@ -117,7 +124,7 @@ export function registerCalendarCommands(program) {
|
|
|
117
124
|
handleError(out, err, 'Set due date failed');
|
|
118
125
|
}
|
|
119
126
|
});
|
|
120
|
-
// calendar events
|
|
127
|
+
// calendar events (list)
|
|
121
128
|
addGlobalFlags(calendar.command('events')
|
|
122
129
|
.description('List calendar events')
|
|
123
130
|
.argument('<vaultId>', 'Vault ID')
|
|
@@ -154,6 +161,307 @@ export function registerCalendarCommands(program) {
|
|
|
154
161
|
handleError(out, err, 'Calendar events failed');
|
|
155
162
|
}
|
|
156
163
|
});
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
// calendar event subgroup (CRUD)
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
const event = calendar.command('event').description('Calendar event CRUD operations');
|
|
168
|
+
// calendar event create
|
|
169
|
+
addGlobalFlags(event.command('create')
|
|
170
|
+
.description('Create a new calendar event')
|
|
171
|
+
.argument('<vaultId>', 'Vault ID')
|
|
172
|
+
.requiredOption('--title <title>', 'Event title')
|
|
173
|
+
.requiredOption('--start <date>', 'Start date/time (YYYY-MM-DD or ISO 8601)')
|
|
174
|
+
.option('--end <date>', 'End date/time (YYYY-MM-DD or ISO 8601)')
|
|
175
|
+
.option('--all-day', 'Mark as all-day event')
|
|
176
|
+
.option('--priority <priority>', 'Priority (low/medium/high)')
|
|
177
|
+
.option('--color <color>', 'Event color (e.g. red, blue, #ff0000)')
|
|
178
|
+
.option('--description <description>', 'Event description'))
|
|
179
|
+
.action(async (vaultId, _opts) => {
|
|
180
|
+
const flags = resolveFlags(_opts);
|
|
181
|
+
const out = createOutput(flags);
|
|
182
|
+
out.startSpinner('Creating event...');
|
|
183
|
+
try {
|
|
184
|
+
const client = await getClientAsync();
|
|
185
|
+
const created = await client.calendar.createEvent(vaultId, {
|
|
186
|
+
title: _opts.title,
|
|
187
|
+
startDate: _opts.start,
|
|
188
|
+
endDate: _opts.end,
|
|
189
|
+
allDay: Boolean(_opts.allDay),
|
|
190
|
+
priority: _opts.priority,
|
|
191
|
+
color: _opts.color,
|
|
192
|
+
description: _opts.description,
|
|
193
|
+
});
|
|
194
|
+
out.stopSpinner();
|
|
195
|
+
if (flags.output === 'json') {
|
|
196
|
+
out.raw(JSON.stringify(created, null, 2) + '\n');
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
out.status(chalk.green(`Event created: ${created.title} (${created.id})`));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
catch (err) {
|
|
203
|
+
handleError(out, err, 'Create event failed');
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
// calendar event update
|
|
207
|
+
addGlobalFlags(event.command('update')
|
|
208
|
+
.description('Update an existing calendar event')
|
|
209
|
+
.argument('<vaultId>', 'Vault ID')
|
|
210
|
+
.argument('<eventId>', 'Event ID')
|
|
211
|
+
.option('--title <title>', 'Event title')
|
|
212
|
+
.option('--start <date>', 'Start date/time (YYYY-MM-DD or ISO 8601)')
|
|
213
|
+
.option('--end <date>', 'End date/time')
|
|
214
|
+
.option('--all-day', 'Mark as all-day event')
|
|
215
|
+
.option('--priority <priority>', 'Priority (low/medium/high)')
|
|
216
|
+
.option('--color <color>', 'Event color')
|
|
217
|
+
.option('--description <description>', 'Event description'))
|
|
218
|
+
.action(async (vaultId, eventId, _opts) => {
|
|
219
|
+
const flags = resolveFlags(_opts);
|
|
220
|
+
const out = createOutput(flags);
|
|
221
|
+
out.startSpinner('Updating event...');
|
|
222
|
+
try {
|
|
223
|
+
const client = await getClientAsync();
|
|
224
|
+
const data = {};
|
|
225
|
+
if (_opts.title)
|
|
226
|
+
data.title = _opts.title;
|
|
227
|
+
if (_opts.start)
|
|
228
|
+
data.startDate = _opts.start;
|
|
229
|
+
if (_opts.end)
|
|
230
|
+
data.endDate = _opts.end;
|
|
231
|
+
if (_opts.allDay !== undefined)
|
|
232
|
+
data.allDay = Boolean(_opts.allDay);
|
|
233
|
+
if (_opts.priority)
|
|
234
|
+
data.priority = _opts.priority;
|
|
235
|
+
if (_opts.color)
|
|
236
|
+
data.color = _opts.color;
|
|
237
|
+
if (_opts.description)
|
|
238
|
+
data.description = _opts.description;
|
|
239
|
+
const updated = await client.calendar.updateEvent(vaultId, eventId, data);
|
|
240
|
+
out.stopSpinner();
|
|
241
|
+
if (flags.output === 'json') {
|
|
242
|
+
out.raw(JSON.stringify(updated, null, 2) + '\n');
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
out.status(chalk.green(`Event updated: ${updated.title}`));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
catch (err) {
|
|
249
|
+
handleError(out, err, 'Update event failed');
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
// calendar event delete
|
|
253
|
+
addGlobalFlags(event.command('delete')
|
|
254
|
+
.description('Delete a calendar event')
|
|
255
|
+
.argument('<vaultId>', 'Vault ID')
|
|
256
|
+
.argument('<eventId>', 'Event ID')
|
|
257
|
+
.option('--confirm', 'Skip confirmation prompt'))
|
|
258
|
+
.action(async (vaultId, eventId, _opts) => {
|
|
259
|
+
const flags = resolveFlags(_opts);
|
|
260
|
+
const out = createOutput(flags);
|
|
261
|
+
if (!_opts.confirm) {
|
|
262
|
+
out.status(chalk.yellow(`Pass --confirm to delete event ${eventId}`));
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
out.startSpinner('Deleting event...');
|
|
266
|
+
try {
|
|
267
|
+
const client = await getClientAsync();
|
|
268
|
+
await client.calendar.deleteEvent(vaultId, eventId);
|
|
269
|
+
out.stopSpinner();
|
|
270
|
+
out.status(chalk.green(`Event ${eventId} deleted.`));
|
|
271
|
+
}
|
|
272
|
+
catch (err) {
|
|
273
|
+
handleError(out, err, 'Delete event failed');
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
// calendar event get
|
|
277
|
+
addGlobalFlags(event.command('get')
|
|
278
|
+
.description('Get a single calendar event by ID')
|
|
279
|
+
.argument('<vaultId>', 'Vault ID')
|
|
280
|
+
.argument('<eventId>', 'Event ID'))
|
|
281
|
+
.action(async (vaultId, eventId, _opts) => {
|
|
282
|
+
const flags = resolveFlags(_opts);
|
|
283
|
+
const out = createOutput(flags);
|
|
284
|
+
out.startSpinner('Loading event...');
|
|
285
|
+
try {
|
|
286
|
+
const client = await getClientAsync();
|
|
287
|
+
const ev = await client.calendar.getEvent(vaultId, eventId);
|
|
288
|
+
out.stopSpinner();
|
|
289
|
+
if (flags.output === 'json') {
|
|
290
|
+
out.raw(JSON.stringify(ev, null, 2) + '\n');
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
out.status(`${chalk.bold(ev.title)}\nID: ${ev.id}\nDate: ${ev.startDate}${ev.endDate ? ` – ${ev.endDate}` : ''}\nPriority: ${ev.priority || '-'}\nCompleted: ${ev.completed ? 'yes' : 'no'}`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
catch (err) {
|
|
297
|
+
handleError(out, err, 'Get event failed');
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
// ---------------------------------------------------------------------------
|
|
301
|
+
// calendar timeline
|
|
302
|
+
// ---------------------------------------------------------------------------
|
|
303
|
+
addGlobalFlags(calendar.command('timeline')
|
|
304
|
+
.description('View chronological event timeline for a vault')
|
|
305
|
+
.argument('<vaultId>', 'Vault ID')
|
|
306
|
+
.option('--limit <n>', 'Number of items to return')
|
|
307
|
+
.option('--cursor <cursor>', 'Pagination cursor'))
|
|
308
|
+
.action(async (vaultId, _opts) => {
|
|
309
|
+
const flags = resolveFlags(_opts);
|
|
310
|
+
const out = createOutput(flags);
|
|
311
|
+
out.startSpinner('Loading timeline...');
|
|
312
|
+
try {
|
|
313
|
+
const client = await getClientAsync();
|
|
314
|
+
const timeline = await client.calendar.getTimeline(vaultId, {
|
|
315
|
+
limit: _opts.limit ? Number(_opts.limit) : undefined,
|
|
316
|
+
cursor: _opts.cursor,
|
|
317
|
+
});
|
|
318
|
+
out.stopSpinner();
|
|
319
|
+
if (flags.output === 'json') {
|
|
320
|
+
out.raw(JSON.stringify(timeline, null, 2) + '\n');
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
process.stdout.write(`${chalk.dim(`${timeline.total} total items`)}\n\n`);
|
|
324
|
+
for (const item of timeline.items) {
|
|
325
|
+
const label = item.type === 'event'
|
|
326
|
+
? chalk.cyan(item.event?.title ?? 'Event')
|
|
327
|
+
: chalk.yellow(item.document?.path ?? 'Document');
|
|
328
|
+
process.stdout.write(`${chalk.dim(item.date)} ${label} [${item.type}]\n`);
|
|
329
|
+
}
|
|
330
|
+
if (timeline.nextCursor) {
|
|
331
|
+
process.stdout.write(chalk.dim(`\nNext cursor: ${timeline.nextCursor}\n`));
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
catch (err) {
|
|
336
|
+
handleError(out, err, 'Timeline failed');
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
// ---------------------------------------------------------------------------
|
|
340
|
+
// calendar upcoming
|
|
341
|
+
// ---------------------------------------------------------------------------
|
|
342
|
+
addGlobalFlags(calendar.command('upcoming')
|
|
343
|
+
.description('Show upcoming events and due items for a vault')
|
|
344
|
+
.argument('<vaultId>', 'Vault ID'))
|
|
345
|
+
.action(async (vaultId, _opts) => {
|
|
346
|
+
const flags = resolveFlags(_opts);
|
|
347
|
+
const out = createOutput(flags);
|
|
348
|
+
out.startSpinner('Loading upcoming items...');
|
|
349
|
+
try {
|
|
350
|
+
const client = await getClientAsync();
|
|
351
|
+
const upcoming = await client.calendar.getUpcoming(vaultId);
|
|
352
|
+
out.stopSpinner();
|
|
353
|
+
if (flags.output === 'json') {
|
|
354
|
+
out.raw(JSON.stringify(upcoming, null, 2) + '\n');
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
process.stdout.write(chalk.bold('Upcoming Events\n'));
|
|
358
|
+
if (upcoming.events.length === 0) {
|
|
359
|
+
process.stdout.write(chalk.dim(' None\n'));
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
for (const e of upcoming.events) {
|
|
363
|
+
process.stdout.write(` ${chalk.cyan(e.title)} — ${chalk.dim(e.startDate)}\n`);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
process.stdout.write(chalk.bold('\nDue Documents\n'));
|
|
367
|
+
if (upcoming.dueDocs.length === 0) {
|
|
368
|
+
process.stdout.write(chalk.dim(' None\n'));
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
for (const d of upcoming.dueDocs) {
|
|
372
|
+
const color = d.overdue ? chalk.red : chalk.yellow;
|
|
373
|
+
process.stdout.write(` ${color(d.path)} — due ${chalk.dim(d.dueAt)}\n`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
catch (err) {
|
|
379
|
+
handleError(out, err, 'Upcoming failed');
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
// ---------------------------------------------------------------------------
|
|
383
|
+
// calendar ical-token subgroup
|
|
384
|
+
// ---------------------------------------------------------------------------
|
|
385
|
+
const icalToken = calendar.command('ical-token').description('Manage iCal subscription tokens');
|
|
386
|
+
// calendar ical-token generate
|
|
387
|
+
addGlobalFlags(icalToken.command('generate')
|
|
388
|
+
.description('Generate a new iCal subscription token for a vault')
|
|
389
|
+
.argument('<vaultId>', 'Vault ID'))
|
|
390
|
+
.action(async (vaultId, _opts) => {
|
|
391
|
+
const flags = resolveFlags(_opts);
|
|
392
|
+
const out = createOutput(flags);
|
|
393
|
+
out.startSpinner('Generating iCal token...');
|
|
394
|
+
try {
|
|
395
|
+
const client = await getClientAsync();
|
|
396
|
+
const result = await client.calendar.generateICalToken(vaultId);
|
|
397
|
+
out.stopSpinner();
|
|
398
|
+
if (flags.output === 'json') {
|
|
399
|
+
out.raw(JSON.stringify(result, null, 2) + '\n');
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
out.status(chalk.green('iCal token generated.'));
|
|
403
|
+
process.stdout.write(`Feed URL: ${chalk.cyan(result.feedUrl)}\nToken: ${result.token}\n`);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
catch (err) {
|
|
407
|
+
handleError(out, err, 'Generate iCal token failed');
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
// calendar ical-token revoke
|
|
411
|
+
addGlobalFlags(icalToken.command('revoke')
|
|
412
|
+
.description('Revoke the iCal subscription token for a vault')
|
|
413
|
+
.argument('<vaultId>', 'Vault ID')
|
|
414
|
+
.option('--confirm', 'Skip confirmation prompt'))
|
|
415
|
+
.action(async (vaultId, _opts) => {
|
|
416
|
+
const flags = resolveFlags(_opts);
|
|
417
|
+
const out = createOutput(flags);
|
|
418
|
+
if (!_opts.confirm) {
|
|
419
|
+
out.status(chalk.yellow('Pass --confirm to revoke the iCal token. All subscribers will lose access.'));
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
out.startSpinner('Revoking iCal token...');
|
|
423
|
+
try {
|
|
424
|
+
const client = await getClientAsync();
|
|
425
|
+
await client.calendar.revokeICalToken(vaultId);
|
|
426
|
+
out.stopSpinner();
|
|
427
|
+
out.status(chalk.green('iCal token revoked.'));
|
|
428
|
+
}
|
|
429
|
+
catch (err) {
|
|
430
|
+
handleError(out, err, 'Revoke iCal token failed');
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
// ---------------------------------------------------------------------------
|
|
434
|
+
// calendar complete
|
|
435
|
+
// ---------------------------------------------------------------------------
|
|
436
|
+
addGlobalFlags(calendar.command('complete')
|
|
437
|
+
.description('Toggle completed state for a document')
|
|
438
|
+
.argument('<vaultId>', 'Vault ID')
|
|
439
|
+
.argument('<documentPath>', 'Document path')
|
|
440
|
+
.option('--completed', 'Mark as complete (default)', true)
|
|
441
|
+
.option('--no-completed', 'Mark as incomplete'))
|
|
442
|
+
.action(async (vaultId, documentPath, _opts) => {
|
|
443
|
+
const flags = resolveFlags(_opts);
|
|
444
|
+
const out = createOutput(flags);
|
|
445
|
+
out.startSpinner('Toggling completion...');
|
|
446
|
+
try {
|
|
447
|
+
const client = await getClientAsync();
|
|
448
|
+
const completed = _opts.completed !== false;
|
|
449
|
+
const result = await client.calendar.toggleComplete(vaultId, documentPath, completed);
|
|
450
|
+
out.stopSpinner();
|
|
451
|
+
if (flags.output === 'json') {
|
|
452
|
+
out.raw(JSON.stringify(result, null, 2) + '\n');
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
out.status(chalk.green(`Completion toggled for ${documentPath}`));
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
catch (err) {
|
|
459
|
+
handleError(out, err, 'Toggle completion failed');
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
// ---------------------------------------------------------------------------
|
|
463
|
+
// calendar agenda
|
|
464
|
+
// ---------------------------------------------------------------------------
|
|
157
465
|
addGlobalFlags(calendar.command('agenda')
|
|
158
466
|
.description('View due-date agenda grouped by time period')
|
|
159
467
|
.argument('<vaultId>', 'Vault ID')
|
|
@@ -189,6 +497,9 @@ export function registerCalendarCommands(program) {
|
|
|
189
497
|
handleError(out, err, 'Failed to fetch agenda');
|
|
190
498
|
}
|
|
191
499
|
});
|
|
500
|
+
// ---------------------------------------------------------------------------
|
|
501
|
+
// calendar ical (raw feed output)
|
|
502
|
+
// ---------------------------------------------------------------------------
|
|
192
503
|
addGlobalFlags(calendar.command('ical')
|
|
193
504
|
.description('Output iCal feed for a vault to stdout')
|
|
194
505
|
.argument('<vaultId>', 'Vault ID')
|
|
@@ -196,17 +507,425 @@ export function registerCalendarCommands(program) {
|
|
|
196
507
|
.action(async (vaultId, _opts) => {
|
|
197
508
|
const flags = resolveFlags(_opts);
|
|
198
509
|
const out = createOutput(flags);
|
|
510
|
+
out.startSpinner('Fetching iCal feed...');
|
|
199
511
|
try {
|
|
200
512
|
const client = await getClientAsync();
|
|
201
513
|
const ical = await client.calendar.getIcalFeed(vaultId, {
|
|
202
514
|
include: _opts.include,
|
|
203
515
|
});
|
|
516
|
+
out.stopSpinner();
|
|
204
517
|
process.stdout.write(ical);
|
|
205
518
|
}
|
|
206
519
|
catch (err) {
|
|
207
520
|
handleError(out, err, 'Failed to fetch iCal feed');
|
|
208
521
|
}
|
|
209
522
|
});
|
|
523
|
+
// ---------------------------------------------------------------------------
|
|
524
|
+
// calendar connector subgroup (Phase 6.6)
|
|
525
|
+
// ---------------------------------------------------------------------------
|
|
526
|
+
const connector = calendar.command('connector').description('Calendar connector management');
|
|
527
|
+
// calendar connector list
|
|
528
|
+
addGlobalFlags(connector.command('list')
|
|
529
|
+
.description('List calendar connectors for a vault')
|
|
530
|
+
.argument('<vaultId>', 'Vault ID'))
|
|
531
|
+
.action(async (vaultId, _opts) => {
|
|
532
|
+
const flags = resolveFlags(_opts);
|
|
533
|
+
const out = createOutput(flags);
|
|
534
|
+
out.startSpinner('Loading connectors...');
|
|
535
|
+
try {
|
|
536
|
+
const client = await getClientAsync();
|
|
537
|
+
const connectors = await client.calendar.listConnectors(vaultId);
|
|
538
|
+
out.stopSpinner();
|
|
539
|
+
out.list(connectors.map(c => ({
|
|
540
|
+
id: c.id,
|
|
541
|
+
provider: c.provider,
|
|
542
|
+
expires: c.expiresAt ? c.expiresAt.split('T')[0] : '-',
|
|
543
|
+
created: c.createdAt.split('T')[0],
|
|
544
|
+
})), {
|
|
545
|
+
emptyMessage: 'No calendar connectors.',
|
|
546
|
+
columns: [
|
|
547
|
+
{ key: 'id', header: 'ID' },
|
|
548
|
+
{ key: 'provider', header: 'Provider' },
|
|
549
|
+
{ key: 'expires', header: 'Expires' },
|
|
550
|
+
{ key: 'created', header: 'Created' },
|
|
551
|
+
],
|
|
552
|
+
textFn: (c) => `${chalk.cyan(String(c.provider))} — expires: ${chalk.dim(String(c.expires))} — created: ${chalk.dim(String(c.created))}`,
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
catch (err) {
|
|
556
|
+
handleError(out, err, 'List connectors failed');
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
// calendar connector sync
|
|
560
|
+
addGlobalFlags(connector.command('sync')
|
|
561
|
+
.description('Trigger a manual sync for a calendar connector')
|
|
562
|
+
.argument('<vaultId>', 'Vault ID')
|
|
563
|
+
.argument('<connectorId>', 'Connector ID'))
|
|
564
|
+
.action(async (vaultId, connectorId, _opts) => {
|
|
565
|
+
const flags = resolveFlags(_opts);
|
|
566
|
+
const out = createOutput(flags);
|
|
567
|
+
out.startSpinner('Syncing connector...');
|
|
568
|
+
try {
|
|
569
|
+
const client = await getClientAsync();
|
|
570
|
+
const result = await client.calendar.syncConnector(vaultId, connectorId);
|
|
571
|
+
out.stopSpinner();
|
|
572
|
+
if (flags.output === 'json') {
|
|
573
|
+
out.raw(JSON.stringify(result, null, 2) + '\n');
|
|
574
|
+
}
|
|
575
|
+
else {
|
|
576
|
+
out.status(chalk.green(`Sync complete. ${result.synced} synced, ${result.errors} errors.`));
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
catch (err) {
|
|
580
|
+
handleError(out, err, 'Sync connector failed');
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
// calendar connector disconnect
|
|
584
|
+
addGlobalFlags(connector.command('disconnect')
|
|
585
|
+
.description('Disconnect a calendar connector from a vault')
|
|
586
|
+
.argument('<vaultId>', 'Vault ID')
|
|
587
|
+
.argument('<connectorId>', 'Connector ID')
|
|
588
|
+
.option('--confirm', 'Skip confirmation prompt'))
|
|
589
|
+
.action(async (vaultId, connectorId, _opts) => {
|
|
590
|
+
const flags = resolveFlags(_opts);
|
|
591
|
+
const out = createOutput(flags);
|
|
592
|
+
if (!_opts.confirm) {
|
|
593
|
+
out.status(chalk.yellow(`Pass --confirm to disconnect connector ${connectorId}.`));
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
out.startSpinner('Disconnecting connector...');
|
|
597
|
+
try {
|
|
598
|
+
const client = await getClientAsync();
|
|
599
|
+
await client.calendar.disconnectConnector(vaultId, connectorId);
|
|
600
|
+
out.stopSpinner();
|
|
601
|
+
out.status(chalk.green(`Connector ${connectorId} disconnected.`));
|
|
602
|
+
}
|
|
603
|
+
catch (err) {
|
|
604
|
+
handleError(out, err, 'Disconnect connector failed');
|
|
605
|
+
}
|
|
606
|
+
});
|
|
607
|
+
// calendar connector connect
|
|
608
|
+
addGlobalFlags(connector.command('connect')
|
|
609
|
+
.description('Connect a Google or Outlook calendar to a vault via OAuth')
|
|
610
|
+
.argument('<vaultId>', 'Vault ID')
|
|
611
|
+
.requiredOption('--provider <provider>', 'Calendar provider: google or outlook'))
|
|
612
|
+
.action(async (vaultId, _opts) => {
|
|
613
|
+
const flags = resolveFlags(_opts);
|
|
614
|
+
const out = createOutput(flags);
|
|
615
|
+
const provider = _opts.provider;
|
|
616
|
+
if (provider !== 'google' && provider !== 'outlook') {
|
|
617
|
+
out.status(chalk.red('--provider must be "google" or "outlook"'));
|
|
618
|
+
process.exitCode = 1;
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
out.startSpinner(`Connecting ${provider} calendar...`);
|
|
622
|
+
try {
|
|
623
|
+
const client = await getClientAsync();
|
|
624
|
+
const result = provider === 'google'
|
|
625
|
+
? await client.calendar.connectGoogleCalendar(vaultId)
|
|
626
|
+
: await client.calendar.connectOutlookCalendar(vaultId);
|
|
627
|
+
out.stopSpinner();
|
|
628
|
+
if (flags.output === 'json') {
|
|
629
|
+
out.record({ authUrl: result.authUrl });
|
|
630
|
+
}
|
|
631
|
+
else {
|
|
632
|
+
out.status(chalk.cyan('Open this URL to connect: ') + result.authUrl);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
catch (err) {
|
|
636
|
+
handleError(out, err, 'Connect calendar failed');
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
// ---------------------------------------------------------------------------
|
|
640
|
+
// calendar participants subgroup (Pro tier)
|
|
641
|
+
// ---------------------------------------------------------------------------
|
|
642
|
+
const participants = calendar.command('participants').description('Manage event participants');
|
|
643
|
+
// calendar participants list <vaultId> <eventId>
|
|
644
|
+
addGlobalFlags(participants.command('list')
|
|
645
|
+
.description('List participants for a calendar event')
|
|
646
|
+
.argument('<vaultId>', 'Vault ID')
|
|
647
|
+
.argument('<eventId>', 'Calendar event ID'))
|
|
648
|
+
.action(async (vaultId, eventId, _opts) => {
|
|
649
|
+
const flags = resolveFlags(_opts);
|
|
650
|
+
const out = createOutput(flags);
|
|
651
|
+
out.startSpinner('Loading participants...');
|
|
652
|
+
try {
|
|
653
|
+
const client = await getClientAsync();
|
|
654
|
+
const items = await client.calendar.listParticipants(vaultId, eventId);
|
|
655
|
+
out.stopSpinner();
|
|
656
|
+
out.list(items.map((p) => ({
|
|
657
|
+
id: p.id,
|
|
658
|
+
email: p.email,
|
|
659
|
+
name: p.name ?? '-',
|
|
660
|
+
role: p.role,
|
|
661
|
+
status: p.status,
|
|
662
|
+
})), {
|
|
663
|
+
emptyMessage: 'No participants for this event.',
|
|
664
|
+
columns: [
|
|
665
|
+
{ key: 'id', header: 'ID' },
|
|
666
|
+
{ key: 'email', header: 'Email' },
|
|
667
|
+
{ key: 'name', header: 'Name' },
|
|
668
|
+
{ key: 'role', header: 'Role' },
|
|
669
|
+
{ key: 'status', header: 'Status' },
|
|
670
|
+
],
|
|
671
|
+
textFn: (p) => {
|
|
672
|
+
const statusColor = p.status === 'accepted'
|
|
673
|
+
? chalk.green
|
|
674
|
+
: p.status === 'declined'
|
|
675
|
+
? chalk.red
|
|
676
|
+
: chalk.yellow;
|
|
677
|
+
return `${chalk.cyan(String(p.email))} (${p.role}) — ${statusColor(String(p.status))}`;
|
|
678
|
+
},
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
catch (err) {
|
|
682
|
+
handleError(out, err, 'List participants failed');
|
|
683
|
+
}
|
|
684
|
+
});
|
|
685
|
+
// calendar participants add <vaultId> <eventId> --email <email> [--name <name>] [--role <role>]
|
|
686
|
+
addGlobalFlags(participants.command('add')
|
|
687
|
+
.description('Add a participant to a calendar event')
|
|
688
|
+
.argument('<vaultId>', 'Vault ID')
|
|
689
|
+
.argument('<eventId>', 'Calendar event ID')
|
|
690
|
+
.requiredOption('--email <email>', 'Participant email address')
|
|
691
|
+
.option('--name <name>', 'Participant display name')
|
|
692
|
+
.option('--role <role>', 'Participant role: organizer, attendee, optional', 'attendee'))
|
|
693
|
+
.action(async (vaultId, eventId, _opts) => {
|
|
694
|
+
const flags = resolveFlags(_opts);
|
|
695
|
+
const out = createOutput(flags);
|
|
696
|
+
out.startSpinner('Adding participant...');
|
|
697
|
+
try {
|
|
698
|
+
const client = await getClientAsync();
|
|
699
|
+
const participant = await client.calendar.addParticipant(vaultId, eventId, {
|
|
700
|
+
email: _opts.email,
|
|
701
|
+
name: _opts.name,
|
|
702
|
+
role: _opts.role,
|
|
703
|
+
});
|
|
704
|
+
out.stopSpinner();
|
|
705
|
+
if (flags.output === 'json') {
|
|
706
|
+
out.raw(JSON.stringify(participant, null, 2) + '\n');
|
|
707
|
+
}
|
|
708
|
+
else {
|
|
709
|
+
out.status(chalk.green(`Participant added: ${participant.email} (${participant.id})`));
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
catch (err) {
|
|
713
|
+
handleError(out, err, 'Add participant failed');
|
|
714
|
+
}
|
|
715
|
+
});
|
|
716
|
+
// calendar participants update <vaultId> <eventId> <participantId> --status <status>
|
|
717
|
+
addGlobalFlags(participants.command('update')
|
|
718
|
+
.description('Update a participant status')
|
|
719
|
+
.argument('<vaultId>', 'Vault ID')
|
|
720
|
+
.argument('<eventId>', 'Calendar event ID')
|
|
721
|
+
.argument('<participantId>', 'Participant ID')
|
|
722
|
+
.requiredOption('--status <status>', 'New status: accepted, declined, tentative'))
|
|
723
|
+
.action(async (vaultId, eventId, participantId, _opts) => {
|
|
724
|
+
const flags = resolveFlags(_opts);
|
|
725
|
+
const out = createOutput(flags);
|
|
726
|
+
out.startSpinner('Updating participant...');
|
|
727
|
+
try {
|
|
728
|
+
const client = await getClientAsync();
|
|
729
|
+
const participant = await client.calendar.updateParticipant(vaultId, eventId, participantId, {
|
|
730
|
+
status: _opts.status,
|
|
731
|
+
});
|
|
732
|
+
out.stopSpinner();
|
|
733
|
+
if (flags.output === 'json') {
|
|
734
|
+
out.raw(JSON.stringify(participant, null, 2) + '\n');
|
|
735
|
+
}
|
|
736
|
+
else {
|
|
737
|
+
out.status(chalk.green(`Participant ${participant.email} updated to ${participant.status}.`));
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
catch (err) {
|
|
741
|
+
handleError(out, err, 'Update participant failed');
|
|
742
|
+
}
|
|
743
|
+
});
|
|
744
|
+
// calendar participants remove <vaultId> <eventId> <participantId>
|
|
745
|
+
addGlobalFlags(participants.command('remove')
|
|
746
|
+
.description('Remove a participant from a calendar event')
|
|
747
|
+
.argument('<vaultId>', 'Vault ID')
|
|
748
|
+
.argument('<eventId>', 'Calendar event ID')
|
|
749
|
+
.argument('<participantId>', 'Participant ID')
|
|
750
|
+
.option('--confirm', 'Skip confirmation prompt'))
|
|
751
|
+
.action(async (vaultId, eventId, participantId, _opts) => {
|
|
752
|
+
const flags = resolveFlags(_opts);
|
|
753
|
+
const out = createOutput(flags);
|
|
754
|
+
if (!_opts.confirm) {
|
|
755
|
+
out.status(chalk.yellow(`Pass --confirm to remove participant ${participantId}.`));
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
out.startSpinner('Removing participant...');
|
|
759
|
+
try {
|
|
760
|
+
const client = await getClientAsync();
|
|
761
|
+
await client.calendar.removeParticipant(vaultId, eventId, participantId);
|
|
762
|
+
out.stopSpinner();
|
|
763
|
+
out.status(chalk.green(`Participant ${participantId} removed.`));
|
|
764
|
+
}
|
|
765
|
+
catch (err) {
|
|
766
|
+
handleError(out, err, 'Remove participant failed');
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
// ---------------------------------------------------------------------------
|
|
770
|
+
// calendar templates subgroup (Pro tier)
|
|
771
|
+
// ---------------------------------------------------------------------------
|
|
772
|
+
const templates = calendar.command('templates').description('Manage calendar event templates');
|
|
773
|
+
// calendar templates list
|
|
774
|
+
addGlobalFlags(templates.command('list')
|
|
775
|
+
.description('List event templates for a vault')
|
|
776
|
+
.argument('<vaultId>', 'Vault ID'))
|
|
777
|
+
.action(async (vaultId, _opts) => {
|
|
778
|
+
const flags = resolveFlags(_opts);
|
|
779
|
+
const out = createOutput(flags);
|
|
780
|
+
out.startSpinner('Loading templates...');
|
|
781
|
+
try {
|
|
782
|
+
const client = await getClientAsync();
|
|
783
|
+
const items = await client.calendar.listTemplates(vaultId);
|
|
784
|
+
out.stopSpinner();
|
|
785
|
+
out.list(items.map((t) => ({
|
|
786
|
+
id: t.id,
|
|
787
|
+
name: t.name,
|
|
788
|
+
duration: String(t.duration),
|
|
789
|
+
description: t.description ?? '-',
|
|
790
|
+
})), {
|
|
791
|
+
emptyMessage: 'No event templates.',
|
|
792
|
+
columns: [
|
|
793
|
+
{ key: 'id', header: 'ID' },
|
|
794
|
+
{ key: 'name', header: 'Name' },
|
|
795
|
+
{ key: 'duration', header: 'Duration (min)' },
|
|
796
|
+
{ key: 'description', header: 'Description' },
|
|
797
|
+
],
|
|
798
|
+
textFn: (t) => `${chalk.cyan(String(t.name))} — ${chalk.dim(String(t.duration))} min${t.description !== '-' ? ` — ${t.description}` : ''}`,
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
catch (err) {
|
|
802
|
+
handleError(out, err, 'List templates failed');
|
|
803
|
+
}
|
|
804
|
+
});
|
|
805
|
+
// calendar templates create
|
|
806
|
+
addGlobalFlags(templates.command('create')
|
|
807
|
+
.description('Create a new event template for a vault')
|
|
808
|
+
.argument('<vaultId>', 'Vault ID')
|
|
809
|
+
.requiredOption('--name <name>', 'Template name')
|
|
810
|
+
.requiredOption('--duration <minutes>', 'Duration in minutes')
|
|
811
|
+
.option('--description <description>', 'Template description')
|
|
812
|
+
.option('--location <location>', 'Default location')
|
|
813
|
+
.option('--color <color>', 'Default color (e.g. red, blue, #ff0000)'))
|
|
814
|
+
.action(async (vaultId, _opts) => {
|
|
815
|
+
const flags = resolveFlags(_opts);
|
|
816
|
+
const out = createOutput(flags);
|
|
817
|
+
out.startSpinner('Creating template...');
|
|
818
|
+
try {
|
|
819
|
+
const client = await getClientAsync();
|
|
820
|
+
const created = await client.calendar.createTemplate(vaultId, {
|
|
821
|
+
name: _opts.name,
|
|
822
|
+
duration: Number(_opts.duration),
|
|
823
|
+
description: _opts.description,
|
|
824
|
+
location: _opts.location,
|
|
825
|
+
color: _opts.color,
|
|
826
|
+
});
|
|
827
|
+
out.stopSpinner();
|
|
828
|
+
if (flags.output === 'json') {
|
|
829
|
+
out.raw(JSON.stringify(created, null, 2) + '\n');
|
|
830
|
+
}
|
|
831
|
+
else {
|
|
832
|
+
out.success(`Template created: ${created.name} (${created.id})`);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
catch (err) {
|
|
836
|
+
handleError(out, err, 'Create template failed');
|
|
837
|
+
}
|
|
838
|
+
});
|
|
839
|
+
// calendar templates get
|
|
840
|
+
addGlobalFlags(templates.command('get')
|
|
841
|
+
.description('Get a single event template by ID')
|
|
842
|
+
.argument('<vaultId>', 'Vault ID')
|
|
843
|
+
.argument('<templateId>', 'Template ID'))
|
|
844
|
+
.action(async (vaultId, templateId, _opts) => {
|
|
845
|
+
const flags = resolveFlags(_opts);
|
|
846
|
+
const out = createOutput(flags);
|
|
847
|
+
out.startSpinner('Loading template...');
|
|
848
|
+
try {
|
|
849
|
+
const client = await getClientAsync();
|
|
850
|
+
const t = await client.calendar.getTemplate(vaultId, templateId);
|
|
851
|
+
out.stopSpinner();
|
|
852
|
+
out.record({
|
|
853
|
+
id: t.id,
|
|
854
|
+
name: t.name,
|
|
855
|
+
duration: String(t.duration),
|
|
856
|
+
description: t.description ?? '-',
|
|
857
|
+
location: t.location ?? '-',
|
|
858
|
+
color: t.color ?? '-',
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
catch (err) {
|
|
862
|
+
handleError(out, err, 'Get template failed');
|
|
863
|
+
}
|
|
864
|
+
});
|
|
865
|
+
// calendar templates update
|
|
866
|
+
addGlobalFlags(templates.command('update')
|
|
867
|
+
.description('Update an event template')
|
|
868
|
+
.argument('<vaultId>', 'Vault ID')
|
|
869
|
+
.argument('<templateId>', 'Template ID')
|
|
870
|
+
.option('--name <name>', 'New template name')
|
|
871
|
+
.option('--duration <minutes>', 'New duration in minutes')
|
|
872
|
+
.option('--description <description>', 'New description')
|
|
873
|
+
.option('--location <location>', 'Default location')
|
|
874
|
+
.option('--color <color>', 'Default color (e.g. red, blue, #ff0000)'))
|
|
875
|
+
.action(async (vaultId, templateId, _opts) => {
|
|
876
|
+
const flags = resolveFlags(_opts);
|
|
877
|
+
const out = createOutput(flags);
|
|
878
|
+
out.startSpinner('Updating template...');
|
|
879
|
+
try {
|
|
880
|
+
const client = await getClientAsync();
|
|
881
|
+
const data = {};
|
|
882
|
+
if (_opts.name)
|
|
883
|
+
data.name = _opts.name;
|
|
884
|
+
if (_opts.duration)
|
|
885
|
+
data.duration = Number(_opts.duration);
|
|
886
|
+
if (_opts.description)
|
|
887
|
+
data.description = _opts.description;
|
|
888
|
+
if (_opts.location)
|
|
889
|
+
data.location = _opts.location;
|
|
890
|
+
if (_opts.color)
|
|
891
|
+
data.color = _opts.color;
|
|
892
|
+
const updated = await client.calendar.updateTemplate(vaultId, templateId, data);
|
|
893
|
+
out.stopSpinner();
|
|
894
|
+
if (flags.output === 'json') {
|
|
895
|
+
out.raw(JSON.stringify(updated, null, 2) + '\n');
|
|
896
|
+
}
|
|
897
|
+
else {
|
|
898
|
+
out.success(`Template updated: ${updated.name}`);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
catch (err) {
|
|
902
|
+
handleError(out, err, 'Update template failed');
|
|
903
|
+
}
|
|
904
|
+
});
|
|
905
|
+
// calendar templates delete
|
|
906
|
+
addGlobalFlags(templates.command('delete')
|
|
907
|
+
.description('Delete an event template')
|
|
908
|
+
.argument('<vaultId>', 'Vault ID')
|
|
909
|
+
.argument('<templateId>', 'Template ID')
|
|
910
|
+
.option('--confirm', 'Skip confirmation prompt'))
|
|
911
|
+
.action(async (vaultId, templateId, _opts) => {
|
|
912
|
+
const flags = resolveFlags(_opts);
|
|
913
|
+
const out = createOutput(flags);
|
|
914
|
+
if (!_opts.confirm) {
|
|
915
|
+
out.status(chalk.yellow(`Pass --confirm to delete template ${templateId}`));
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
out.startSpinner('Deleting template...');
|
|
919
|
+
try {
|
|
920
|
+
const client = await getClientAsync();
|
|
921
|
+
await client.calendar.deleteTemplate(vaultId, templateId);
|
|
922
|
+
out.stopSpinner();
|
|
923
|
+
out.success(`Template ${templateId} deleted.`);
|
|
924
|
+
}
|
|
925
|
+
catch (err) {
|
|
926
|
+
handleError(out, err, 'Delete template failed');
|
|
927
|
+
}
|
|
928
|
+
});
|
|
210
929
|
}
|
|
211
930
|
function getDefaultStart() {
|
|
212
931
|
const d = new Date();
|