@wirecat/cli-core 0.537.0 → 0.539.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/CHANGELOG.md +59 -0
- package/dist/calendar/busy.js +1 -1
- package/dist/calendar/cli/answers.d.ts +179 -0
- package/dist/calendar/cli/answers.js +45 -1
- package/dist/calendar/cli/index.d.ts +4 -0
- package/dist/calendar/cli/index.js +109 -10
- package/dist/calendar/ics.d.ts +55 -0
- package/dist/calendar/ics.js +314 -0
- package/dist/calendar/index.d.ts +1 -0
- package/dist/calendar/index.js +1 -0
- package/dist/calendar/prepare.d.ts +134 -0
- package/dist/calendar/prepare.js +179 -0
- package/dist/calendar/reads.d.ts +17 -4
- package/dist/calendar/reads.js +115 -27
- package/dist/calendar/testing/answers.d.ts +2 -1
- package/dist/calendar/testing/answers.js +3 -2
- package/dist/calendar/testing/memory.js +5 -0
- package/dist/calendar/testing/writer.d.ts +1 -1
- package/dist/calendar/testing/writer.js +40 -0
- package/dist/calendar/writer.d.ts +15 -1
- package/dist/calendar/writer.js +2 -0
- package/dist/calendar/writes.d.ts +46 -0
- package/dist/calendar/writes.js +98 -0
- package/dist/cli/list-query.d.ts +3 -2
- package/dist/cli/list-query.js +12 -3
- package/dist/cli/messenger/store-maintenance-command.d.ts +1 -1
- package/dist/entities/contact-context.d.ts +35 -0
- package/dist/entities/contact-context.js +3 -3
- package/dist/errors/reasons.d.ts +1 -1
- package/dist/errors/reasons.js +13 -0
- package/dist/mail/server-search.js +20 -4
- package/dist/meetings/event-store.d.ts +6 -0
- package/dist/meetings/mcp/index.d.ts +15 -15
- package/dist/meetings/testing/event-store-contract.js +15 -0
- package/dist/services/person-context.d.ts +6 -1
- package/dist/services/person-context.js +9 -3
- package/dist/store/sqlite/calendar-events.js +15 -0
- package/dist/store/sqlite/evidence-findings.js +4 -2
- package/dist/store/sqlite/task-board.js +16 -0
- package/dist/store/sqlite/work-tasks.d.ts +4 -1
- package/dist/store/sqlite/work-tasks.js +6 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,65 @@ shape of this file.
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
## 0.539.0 — 01.11.2026
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`events create --text <line>`**: an event the provider reads from one line, such as 'Lunch with Ana tomorrow
|
|
15
|
+
1pm', when the writer has the new optional `CalendarWriter.quickAdd`. Checked as `events.create` and
|
|
16
|
+
`extraKeys("quick-add")`; takes no `--operation-id`, since the provider picks the id, and mails nobody without
|
|
17
|
+
`--notify`.
|
|
18
|
+
- **`events export [query…] [--output <file>]`**: the events `events list`'s query finds, as iCalendar (RFC 5545).
|
|
19
|
+
`--output` exists when the host passes `deps.writeFile`, and is left out of MCP tools: an agent gets the text.
|
|
20
|
+
`writeIcs` and `readIcs` are in `./calendar`.
|
|
21
|
+
- **`events import <calendar> <file>`**: an iCalendar file's events into a calendar, each under its UID, when the
|
|
22
|
+
writer has the new optional `CalendarWriter.importEvent` and the host passes `deps.readFile`. Its key `events.import`
|
|
23
|
+
asks by default (`CALENDAR_WRITE_DEFAULTS`).
|
|
24
|
+
- **`events list` takes `id:<id>` and `event_type:<kind>`**, and `-event_type:<kind>` or `NOT event_type:<kind>` to
|
|
25
|
+
leave a kind out, such as birthdays and working-location blocks; filtered in the store
|
|
26
|
+
(`EventFilter.id`, `eventType`, `notEventTypes`). `nativeTermLists` takes the fields a list lets the query exclude.
|
|
27
|
+
- **A calendar by its title**: `calendar:`, `<calendar>`, `--calendar` and `--to-calendar` take a calendar's title,
|
|
28
|
+
case aside, after its id and the provider's id. A title two calendars share is refused (`calendar.ambiguous`) with
|
|
29
|
+
their ids.
|
|
30
|
+
|
|
31
|
+
### Changed — may break callers
|
|
32
|
+
|
|
33
|
+
- `CalendarWrite` has `quick-add` and `import`: a `Record<CalendarWrite, …>`, such as a CLI's map of write to its API
|
|
34
|
+
keys, needs both.
|
|
35
|
+
|
|
36
|
+
## 0.538.0 — 01.11.2026
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- **`events prepare [event]`, in the shared calendar commands: what the store holds for one meeting.** The event as
|
|
41
|
+
`events show` gives it, and for each attendee other than the owner their linked identities, latest one-to-one
|
|
42
|
+
messages and mail, their messages in groups, past events and recorded meetings with them, read in every account that
|
|
43
|
+
saw them. Without an event, the next meeting with other attendees. `meta.accounts` names what was read, and
|
|
44
|
+
`meta.complete: false` names a chat the store does not hold whole (`chat.not-read`) or meetings not yet indexed
|
|
45
|
+
(`meetings.index-pending`); `untrusted` marks all third-party text. It summarises nothing. `recordContext` is
|
|
46
|
+
`personContext` for a person already found, read in the accounts the caller names.
|
|
47
|
+
- **A task error names the tasks it is about**: `task.has-subtasks` gives `details.subtasks` (their keys) and
|
|
48
|
+
`task.is-subtask` gives `details.parent`, in the JSON a CLI prints. The keys were raised but not kept.
|
|
49
|
+
- **`parent:` in the project task query**: `parent:<key>` lists a task's subtasks, also by a key it had before it
|
|
50
|
+
moved to another project; `parent:none` and `parent:any`.
|
|
51
|
+
|
|
52
|
+
### Fixed
|
|
53
|
+
|
|
54
|
+
- **Mail a server search stores first gets the owner's rule tags.** `searchMailServer` saved new mail without the
|
|
55
|
+
`tag:` labels and `mailRuleId` its rules give, and sync never reads mail whose place it already knows, so that mail
|
|
56
|
+
stayed untagged until `rules apply`. It now tags it as `syncMail` does, and writes reused or refused labels back into
|
|
57
|
+
the rules the same way.
|
|
58
|
+
- **A findings batch stays approvable after a sibling batch of the same project is decided.** Accepting checks only
|
|
59
|
+
the evidence and assignee the accepted findings cite. Before, every task a
|
|
60
|
+
decision created moved the project's row, and every other waiting batch of that project was refused as
|
|
61
|
+
`evidence-findings.stale`. A renamed project no longer refuses a batch either; the project must still exist.
|
|
62
|
+
Meeting findings are unchanged.
|
|
63
|
+
|
|
64
|
+
### Changed — may break callers
|
|
65
|
+
|
|
66
|
+
- **Moving a subtask to another project is refused** with `task.is-subtask` (details `parent`, its parent's key),
|
|
67
|
+
unless the same edit detaches it with `parent: null`. Before, the move dropped its parent without saying so.
|
|
68
|
+
|
|
10
69
|
## 0.537.0 — 01.11.2026
|
|
11
70
|
|
|
12
71
|
### Added
|
package/dist/calendar/busy.js
CHANGED
|
@@ -29,7 +29,7 @@ export const BUSY_QUERY = {
|
|
|
29
29
|
calendar: {
|
|
30
30
|
type: "kw",
|
|
31
31
|
example: "calendar:3",
|
|
32
|
-
relation: "one of the account's calendars, by its id
|
|
32
|
+
relation: "one of the account's calendars, by its id, the provider's id or its title; repeat for more",
|
|
33
33
|
},
|
|
34
34
|
person: { type: "kw", example: "person:ana@example.com", relation: "someone else, by address; repeat for more" },
|
|
35
35
|
},
|
|
@@ -236,6 +236,42 @@ export declare const calendarEventWrite: v.StrictObjectSchema<{
|
|
|
236
236
|
readonly hint: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
237
237
|
readonly untrusted: v.OptionalSchema<v.ArraySchema<v.StringSchema<undefined>, undefined>, undefined>;
|
|
238
238
|
}, undefined>;
|
|
239
|
+
export declare const calendarExport: v.StrictObjectSchema<{
|
|
240
|
+
readonly format: v.LiteralSchema<"ics", undefined>;
|
|
241
|
+
readonly events: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
242
|
+
/** The iCalendar text, when no `--output` took it. */
|
|
243
|
+
readonly content: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
244
|
+
readonly output: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
245
|
+
readonly untrusted: v.OptionalSchema<v.ArraySchema<v.StringSchema<undefined>, undefined>, undefined>;
|
|
246
|
+
}, undefined>;
|
|
247
|
+
export declare const calendarImport: v.StrictObjectSchema<{
|
|
248
|
+
readonly operation_id: v.StringSchema<undefined>;
|
|
249
|
+
readonly calendar: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
250
|
+
readonly imported: v.ArraySchema<v.StrictObjectSchema<{
|
|
251
|
+
readonly uid: v.StringSchema<undefined>;
|
|
252
|
+
readonly event: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
253
|
+
readonly entry: v.NullableSchema<v.StrictObjectSchema<{
|
|
254
|
+
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
255
|
+
readonly calendar_id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
256
|
+
readonly external_id: v.StringSchema<undefined>;
|
|
257
|
+
readonly kind: v.StringSchema<undefined>;
|
|
258
|
+
readonly title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
259
|
+
readonly status: v.StringSchema<undefined>;
|
|
260
|
+
readonly event_type: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
261
|
+
readonly own_response: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
262
|
+
readonly starts_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
263
|
+
readonly ends_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
264
|
+
readonly join_url: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
265
|
+
readonly url: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
266
|
+
readonly deleted_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
267
|
+
}, undefined>, undefined>;
|
|
268
|
+
}, undefined>, undefined>;
|
|
269
|
+
readonly skipped: v.ArraySchema<v.StrictObjectSchema<{
|
|
270
|
+
readonly uid: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
271
|
+
readonly reason: v.StringSchema<undefined>;
|
|
272
|
+
}, undefined>, undefined>;
|
|
273
|
+
readonly untrusted: v.OptionalSchema<v.ArraySchema<v.StringSchema<undefined>, undefined>, undefined>;
|
|
274
|
+
}, undefined>;
|
|
239
275
|
export declare const calendarAttendee: v.StrictObjectSchema<{
|
|
240
276
|
readonly email: v.StringSchema<undefined>;
|
|
241
277
|
readonly name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
@@ -249,6 +285,149 @@ export declare const calendarAttendee: v.StrictObjectSchema<{
|
|
|
249
285
|
readonly events: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
|
|
250
286
|
readonly untrusted: v.OptionalSchema<v.ArraySchema<v.StringSchema<undefined>, undefined>, undefined>;
|
|
251
287
|
}, undefined>;
|
|
288
|
+
export declare const calendarEventPreparation: v.StrictObjectSchema<{
|
|
289
|
+
readonly event: v.StrictObjectSchema<{
|
|
290
|
+
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
291
|
+
readonly event_series_id: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
292
|
+
readonly title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
293
|
+
readonly description: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
294
|
+
readonly location: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
295
|
+
readonly starts_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
296
|
+
readonly ends_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
297
|
+
readonly timezone: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
298
|
+
readonly origin: v.PicklistSchema<["auto", "owner", "calendar"], undefined>;
|
|
299
|
+
readonly status: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
300
|
+
readonly all_day: v.BooleanSchema<undefined>;
|
|
301
|
+
readonly source_deleted_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
302
|
+
readonly deleted_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
303
|
+
readonly created_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
|
|
304
|
+
readonly updated_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
|
|
305
|
+
readonly change: v.OptionalSchema<v.PicklistSchema<["new", "moved", "cancelled"], undefined>, undefined>;
|
|
306
|
+
readonly changed_at: v.OptionalSchema<v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>, undefined>;
|
|
307
|
+
readonly was: v.OptionalSchema<v.StrictObjectSchema<{
|
|
308
|
+
readonly starts_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
309
|
+
readonly ends_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
310
|
+
}, undefined>, undefined>;
|
|
311
|
+
readonly overlapping: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
|
|
312
|
+
readonly overlaps: v.OptionalSchema<v.ArraySchema<v.StringSchema<undefined>, undefined>, undefined>;
|
|
313
|
+
readonly ref: v.StringSchema<undefined>;
|
|
314
|
+
readonly entries: v.ArraySchema<v.StrictObjectSchema<{
|
|
315
|
+
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
316
|
+
readonly calendar_id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
317
|
+
readonly external_id: v.StringSchema<undefined>;
|
|
318
|
+
readonly kind: v.StringSchema<undefined>;
|
|
319
|
+
readonly title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
320
|
+
readonly status: v.StringSchema<undefined>;
|
|
321
|
+
readonly event_type: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
322
|
+
readonly own_response: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
323
|
+
readonly starts_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
324
|
+
readonly ends_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
325
|
+
readonly join_url: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
326
|
+
readonly url: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
327
|
+
readonly deleted_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
328
|
+
}, undefined>, undefined>;
|
|
329
|
+
readonly attendees: v.ArraySchema<v.StrictObjectSchema<{
|
|
330
|
+
email: v.StringSchema<undefined>;
|
|
331
|
+
name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
332
|
+
response: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
333
|
+
organizer: v.BooleanSchema<undefined>;
|
|
334
|
+
optional: v.BooleanSchema<undefined>;
|
|
335
|
+
resource: v.BooleanSchema<undefined>;
|
|
336
|
+
self: v.BooleanSchema<undefined>;
|
|
337
|
+
/** `person:<id>` when the shared store knows the address. */
|
|
338
|
+
person: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
339
|
+
}, undefined>, undefined>;
|
|
340
|
+
readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
|
|
341
|
+
readonly file_id: v.StringSchema<undefined>;
|
|
342
|
+
readonly title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
343
|
+
readonly document: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
344
|
+
}, undefined>, undefined>;
|
|
345
|
+
readonly history: v.ArraySchema<v.StrictObjectSchema<{
|
|
346
|
+
readonly title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
347
|
+
readonly starts_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
348
|
+
readonly ends_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
349
|
+
readonly location: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
350
|
+
readonly status: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
351
|
+
readonly changed_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
|
|
352
|
+
}, undefined>, undefined>;
|
|
353
|
+
readonly untrusted: v.OptionalSchema<v.ArraySchema<v.StringSchema<undefined>, undefined>, undefined>;
|
|
354
|
+
}, undefined>;
|
|
355
|
+
readonly people: v.ArraySchema<v.StrictObjectSchema<{
|
|
356
|
+
readonly email: v.StringSchema<undefined>;
|
|
357
|
+
readonly name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
358
|
+
readonly response: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
359
|
+
readonly organizer: v.BooleanSchema<undefined>;
|
|
360
|
+
readonly optional: v.BooleanSchema<undefined>;
|
|
361
|
+
readonly person: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
362
|
+
readonly read: v.BooleanSchema<undefined>;
|
|
363
|
+
readonly identities: v.ArraySchema<v.StringSchema<undefined>, undefined>;
|
|
364
|
+
readonly recent: v.StrictObjectSchema<{
|
|
365
|
+
readonly direct: v.ArraySchema<v.StrictObjectSchema<{
|
|
366
|
+
readonly locator: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
|
|
367
|
+
readonly provider: v.StringSchema<undefined>;
|
|
368
|
+
readonly account: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
369
|
+
readonly chat_id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
370
|
+
readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
371
|
+
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
372
|
+
readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
|
|
373
|
+
readonly sender_id: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>, undefined>;
|
|
374
|
+
readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
375
|
+
readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
|
|
376
|
+
readonly text: v.StringSchema<undefined>;
|
|
377
|
+
}, undefined>, undefined>;
|
|
378
|
+
readonly groups: v.ArraySchema<v.StrictObjectSchema<{
|
|
379
|
+
readonly locator: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
|
|
380
|
+
readonly provider: v.StringSchema<undefined>;
|
|
381
|
+
readonly account: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
382
|
+
readonly chat_id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
383
|
+
readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
384
|
+
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
385
|
+
readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
|
|
386
|
+
readonly sender_id: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>, undefined>;
|
|
387
|
+
readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
388
|
+
readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
|
|
389
|
+
readonly text: v.StringSchema<undefined>;
|
|
390
|
+
}, undefined>, undefined>;
|
|
391
|
+
}, undefined>;
|
|
392
|
+
readonly past_events: v.ArraySchema<v.StrictObjectSchema<{
|
|
393
|
+
readonly ref: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
|
|
394
|
+
readonly title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
395
|
+
readonly starts_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
396
|
+
readonly ends_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
397
|
+
readonly all_day: v.BooleanSchema<undefined>;
|
|
398
|
+
readonly location: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
399
|
+
readonly status: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
400
|
+
}, undefined>, undefined>;
|
|
401
|
+
readonly meetings: v.ArraySchema<v.StrictObjectSchema<{
|
|
402
|
+
readonly reference: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
|
|
403
|
+
readonly cues: v.ArraySchema<v.StrictObjectSchema<{
|
|
404
|
+
readonly reference: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
|
|
405
|
+
readonly fingerprint: v.StringSchema<undefined>;
|
|
406
|
+
readonly transcript_id: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
407
|
+
readonly content_hash: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
408
|
+
readonly position: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
409
|
+
readonly start_ms: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
410
|
+
readonly end_ms: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
411
|
+
readonly speaker_participant_id: v.NullableSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
|
|
412
|
+
readonly speaker_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
413
|
+
readonly text: v.StringSchema<undefined>;
|
|
414
|
+
readonly revision: v.PicklistSchema<["current", "superseded"], undefined>;
|
|
415
|
+
}, undefined>, undefined>;
|
|
416
|
+
readonly cue_coverage: v.StrictObjectSchema<{
|
|
417
|
+
readonly included: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
418
|
+
readonly omitted: v.NullableSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
|
|
419
|
+
readonly has_more: v.BooleanSchema<undefined>;
|
|
420
|
+
}, undefined>;
|
|
421
|
+
}, undefined>, undefined>;
|
|
422
|
+
readonly has_more: v.BooleanSchema<undefined>;
|
|
423
|
+
}, undefined>, undefined>;
|
|
424
|
+
readonly limits: v.StrictObjectSchema<{
|
|
425
|
+
readonly people: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
426
|
+
readonly messages: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
427
|
+
readonly bytes: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
428
|
+
}, undefined>;
|
|
429
|
+
readonly untrusted: v.OptionalSchema<v.ArraySchema<v.StringSchema<undefined>, undefined>, undefined>;
|
|
430
|
+
}, undefined>;
|
|
252
431
|
export declare const calendarLinkedPerson: v.StrictObjectSchema<{
|
|
253
432
|
readonly person: v.StringSchema<undefined>;
|
|
254
433
|
readonly name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
@@ -2,7 +2,8 @@ import { annotate } from "@wirecat/cli-base/commands";
|
|
|
2
2
|
import { queryFieldsOf } from "@wirecat/cli-base/query";
|
|
3
3
|
import * as v from "valibot";
|
|
4
4
|
import { answerEntity } from "../../cli/answer-entity.js";
|
|
5
|
-
import {
|
|
5
|
+
import { contextEvent, contextMessage, meetingCue } from "../../entities/contact-context.js";
|
|
6
|
+
import { count, id, reference, text, time, untrusted } from "../../entities/fields.js";
|
|
6
7
|
import { event } from "../../meetings/index.js";
|
|
7
8
|
const nullableText = v.nullable(text);
|
|
8
9
|
const nullableTime = v.nullable(time);
|
|
@@ -174,12 +175,52 @@ export const calendarEventWrite = v.strictObject({
|
|
|
174
175
|
hint: v.optional(text),
|
|
175
176
|
untrusted,
|
|
176
177
|
});
|
|
178
|
+
export const calendarExport = v.strictObject({
|
|
179
|
+
format: v.literal("ics"),
|
|
180
|
+
events: count,
|
|
181
|
+
/** The iCalendar text, when no `--output` took it. */
|
|
182
|
+
content: v.optional(text),
|
|
183
|
+
output: v.optional(text),
|
|
184
|
+
untrusted: v.optional(v.array(text)),
|
|
185
|
+
});
|
|
186
|
+
export const calendarImport = v.strictObject({
|
|
187
|
+
operation_id: text,
|
|
188
|
+
calendar: id,
|
|
189
|
+
imported: v.array(v.strictObject({ uid: text, event: nullableText, entry: v.nullable(entry) })),
|
|
190
|
+
skipped: v.array(v.strictObject({ uid: nullableText, reason: text })),
|
|
191
|
+
untrusted,
|
|
192
|
+
});
|
|
177
193
|
export const calendarAttendee = v.strictObject({
|
|
178
194
|
...attendee,
|
|
179
195
|
/** How many of the listed events the person attends; only on the week's list. */
|
|
180
196
|
events: v.optional(count),
|
|
181
197
|
untrusted,
|
|
182
198
|
});
|
|
199
|
+
/** An attendee of the prepared event other than the owner, with what the store holds of them; `read` false: nothing. */
|
|
200
|
+
const preparedPerson = v.strictObject({
|
|
201
|
+
email: text,
|
|
202
|
+
name: nullableText,
|
|
203
|
+
response: nullableText,
|
|
204
|
+
organizer: v.boolean(),
|
|
205
|
+
optional: v.boolean(),
|
|
206
|
+
person: nullableText,
|
|
207
|
+
read: v.boolean(),
|
|
208
|
+
identities: v.array(text),
|
|
209
|
+
recent: v.strictObject({ direct: v.array(contextMessage), groups: v.array(contextMessage) }),
|
|
210
|
+
past_events: v.array(contextEvent),
|
|
211
|
+
meetings: v.array(v.strictObject({
|
|
212
|
+
reference,
|
|
213
|
+
cues: v.array(meetingCue),
|
|
214
|
+
cue_coverage: v.strictObject({ included: count, omitted: v.nullable(count), has_more: v.boolean() }),
|
|
215
|
+
})),
|
|
216
|
+
has_more: v.boolean(),
|
|
217
|
+
});
|
|
218
|
+
export const calendarEventPreparation = v.strictObject({
|
|
219
|
+
event: calendarEvent,
|
|
220
|
+
people: v.array(preparedPerson),
|
|
221
|
+
limits: v.strictObject({ people: count, messages: count, bytes: count }),
|
|
222
|
+
untrusted,
|
|
223
|
+
});
|
|
183
224
|
export const calendarLinkedPerson = v.strictObject({ person: text, name: nullableText, identities: v.array(text) });
|
|
184
225
|
export const calendarLinkReview = v.strictObject({
|
|
185
226
|
id,
|
|
@@ -198,6 +239,9 @@ const ANSWERS = {
|
|
|
198
239
|
"store status": { name: "calendar_sync_progress", schema: calendarSyncProgress },
|
|
199
240
|
"events list": { name: "event" },
|
|
200
241
|
"events show": { name: "calendar_event", schema: calendarEvent },
|
|
242
|
+
"events prepare": { name: "calendar_event_preparation", schema: calendarEventPreparation },
|
|
243
|
+
"events export": { name: "calendar_export", schema: calendarExport },
|
|
244
|
+
"events import": { name: "calendar_import", schema: calendarImport },
|
|
201
245
|
"events create": { name: "calendar_event_write", schema: calendarEventWrite },
|
|
202
246
|
"events update": { name: "calendar_event_write", schema: calendarEventWrite },
|
|
203
247
|
"events delete": { name: "calendar_event_write", schema: calendarEventWrite },
|
|
@@ -39,6 +39,10 @@ export interface CalendarCommandDeps {
|
|
|
39
39
|
timezone?: string;
|
|
40
40
|
/** The provider's writer and the profile's guard: `events create|update|delete|respond` exist only with it. */
|
|
41
41
|
writes?: CalendarWrites;
|
|
42
|
+
/** Writes `events export --output`'s file; without it the export is printed only. */
|
|
43
|
+
writeFile?: (path: string, content: string) => Promise<void>;
|
|
44
|
+
/** Reads `events import`'s file; without it, and the writer's `import`, there is no `events import`. */
|
|
45
|
+
readFile?: (path: string) => Promise<string>;
|
|
42
46
|
/** A profile lock pins the process (WC-ACCT-5): `events list --source` may then read only the profile's account. */
|
|
43
47
|
locked?: boolean;
|
|
44
48
|
}
|
|
@@ -4,17 +4,20 @@ import { timezoneOf } from "@wirecat/cli-base/query";
|
|
|
4
4
|
import { Argument, InvalidArgumentError, Option } from "commander";
|
|
5
5
|
import { environmentOf, runBudgetOf } from "../../cli/context.js";
|
|
6
6
|
import { honoursDryRun } from "../../cli/dry-run-guard.js";
|
|
7
|
+
import { ownerOnlyOption } from "../../cli/effects.js";
|
|
7
8
|
import { queryTerms, withQuery } from "../../cli/list-query.js";
|
|
8
9
|
import { terminalAsker } from "../../cli/messenger/ask.js";
|
|
9
10
|
import { lineOf } from "../../cli/steps.js";
|
|
10
11
|
import { searchEvents } from "../../services/events-search.js";
|
|
12
|
+
import { dateWindow } from "../../services/moment.js";
|
|
11
13
|
import { linkPeople } from "../../services/people.js";
|
|
12
14
|
import { BUSY_QUERY, BUSY_QUERY_NOTE, busyAnswer, busyQuery, workingHoursOf } from "../busy.js";
|
|
13
|
-
import {
|
|
15
|
+
import { eventPreparation, nextMeeting, PREPARE_MESSAGES } from "../prepare.js";
|
|
16
|
+
import { accountsMeta, CALENDAR_UNTRUSTED_FIELDS, calendarIn, calendarItem, EVENTS_QUERY, EVENTS_QUERY_NOTE, EXPORT_MAX, eventAnswer, eventIdOf, eventOf, eventsAnswer, eventsFilter, eventsIcs, peopleAnswer, resolveAnswer, reviewsAnswer, sourceAccounts, weekFilter, } from "../reads.js";
|
|
14
17
|
import { REPEAT_HELP } from "../recurrence.js";
|
|
15
18
|
import { bothSearchAnswer, serverSearchAnswer } from "../server-search.js";
|
|
16
19
|
import { calendarStatus, syncCalendars } from "../sync.js";
|
|
17
|
-
import { createEvent, deleteEvent, EVENT_RESPONSES, moveEvent, respondToEvent, updateEvent, } from "../writes.js";
|
|
20
|
+
import { createEvent, createEventFromText, deleteEvent, EVENT_RESPONSES, IMPORT_MAX, importEvents, moveEvent, respondToEvent, updateEvent, } from "../writes.js";
|
|
18
21
|
import { CALENDARS_QUERY, declareCalendarAnswers, PEOPLE_QUERY } from "./answers.js";
|
|
19
22
|
const LOCAL_READ = { remote: "none", local: "read" };
|
|
20
23
|
const LOCAL_WRITE = { remote: "none", local: "write" };
|
|
@@ -31,6 +34,9 @@ const ATTENDEE_UNTRUSTED = ["name"];
|
|
|
31
34
|
const REVIEW_UNTRUSTED = ["candidates.title"];
|
|
32
35
|
const SEARCH_UNTRUSTED = ["hits.title", "hits.location", "hits.excerpt"];
|
|
33
36
|
const SERVER_SEARCH_UNTRUSTED = ["hits.title", "hits.location"];
|
|
37
|
+
const IMPORT_UNTRUSTED = ["imported.uid", "imported.entry.title", "skipped.uid", "skipped.reason"];
|
|
38
|
+
/** The largest .ics file `events import` reads. */
|
|
39
|
+
const ICS_MAX_BYTES = 10 * 1024 * 1024;
|
|
34
40
|
const eachMarked = (answer, untrusted) => ({
|
|
35
41
|
...answer,
|
|
36
42
|
items: answer.items.map((item) => ({ ...item, untrusted })),
|
|
@@ -40,6 +46,12 @@ const integer = (text) => {
|
|
|
40
46
|
throw new InvalidArgumentError("expected a positive integer");
|
|
41
47
|
return Number(text);
|
|
42
48
|
};
|
|
49
|
+
const upTo = (most) => (text) => {
|
|
50
|
+
const value = integer(text);
|
|
51
|
+
if (value > most)
|
|
52
|
+
throw new InvalidArgumentError(`expected 1 to ${most}`);
|
|
53
|
+
return value;
|
|
54
|
+
};
|
|
43
55
|
const minutes = (text) => {
|
|
44
56
|
const value = integer(text);
|
|
45
57
|
if (value < 5 || value > 1440)
|
|
@@ -93,7 +105,7 @@ const addCalendarCommandsDefinition = (program, deps) => {
|
|
|
93
105
|
])
|
|
94
106
|
example(annotate(calendars.command(name), { effects: LOCAL_WRITE })
|
|
95
107
|
.description(synced ? "sync this calendar from now on" : "stop syncing this calendar; what is stored stays")
|
|
96
|
-
.argument("<calendar>", "the calendar's id,
|
|
108
|
+
.argument("<calendar>", "the calendar's id, the provider's id, or its title"), `calendars ${name} 3`).action(async (reference, _opts, command) => {
|
|
97
109
|
const calendar = await calendarIn(deps.store, deps.accountId, reference);
|
|
98
110
|
await deps.store.setCalendarSynced(calendar.id, synced, now());
|
|
99
111
|
print(calendarItem({ ...calendar, synced }), command);
|
|
@@ -226,12 +238,67 @@ const addCalendarCommandsDefinition = (program, deps) => {
|
|
|
226
238
|
meta: await accountsMeta(deps.store, accounts, skipped, now()),
|
|
227
239
|
}, command);
|
|
228
240
|
});
|
|
241
|
+
const exporter = example(withQuery(annotate(events.command("export"), { effects: deps.writeFile ? LOCAL_WRITE : LOCAL_READ }).description(`write stored events as iCalendar (.ics), at most ${EXPORT_MAX}: one with id:<id>, or those the query ` +
|
|
242
|
+
"finds; each occurrence of a repeating event as an event of its own"), EVENTS_QUERY).addHelpText("after", EVENTS_QUERY_NOTE), ...(deps.writeFile
|
|
243
|
+
? [
|
|
244
|
+
"events export id:42 --output planning.ics",
|
|
245
|
+
"events export starts_at:[2026-11-02 TO 2026-11-09} NOT event_type:birthday --output week.ics",
|
|
246
|
+
]
|
|
247
|
+
: ["events export id:42", "events export starts_at:[2026-11-02 TO 2026-11-09} NOT event_type:birthday"]));
|
|
248
|
+
// Not over MCP: an agent would choose where on the owner's disk to write. It gets the text in the answer.
|
|
249
|
+
if (deps.writeFile)
|
|
250
|
+
exporter.addOption(ownerOnlyOption("--output <file>", "the file to write; printed in the answer when absent"));
|
|
251
|
+
exporter.action(async (words, opts, command) => {
|
|
252
|
+
const filter = await eventsFilter(words, {
|
|
253
|
+
store: deps.store,
|
|
254
|
+
accountId: deps.accountId,
|
|
255
|
+
zone: zone(),
|
|
256
|
+
now: now(),
|
|
257
|
+
personOf: personId,
|
|
258
|
+
});
|
|
259
|
+
const exported = await eventsIcs(deps.store, deps.accountId, filter, { now: now(), product: program.name() });
|
|
260
|
+
if (opts.output === undefined) {
|
|
261
|
+
print({ ...exported, untrusted: ["content"] }, command);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (!opts.output.trim())
|
|
265
|
+
throw new CliError("validation_error", "--output names no file");
|
|
266
|
+
await deps.writeFile?.(opts.output, exported.content);
|
|
267
|
+
print({ format: exported.format, events: exported.events, output: opts.output }, command);
|
|
268
|
+
});
|
|
229
269
|
example(annotate(events.command("show"), { effects: LOCAL_READ })
|
|
230
270
|
.description("one event: its calendar copies, attendees with their person, Drive files and history")
|
|
231
271
|
.argument("<event>", "the event's id, or event:<id>"), "events show event:42 --json").action(async (reference, _opts, command) => {
|
|
232
272
|
const shown = await eventAnswer(await eventOf(deps.store, deps.accountId, eventIdOf(reference)), personOfEmail);
|
|
233
273
|
print({ ...shown, untrusted: CALENDAR_UNTRUSTED_FIELDS }, command);
|
|
234
274
|
});
|
|
275
|
+
example(annotate(events.command("prepare"), { effects: LOCAL_READ, partial: true })
|
|
276
|
+
.description("before a meeting: the event, its attendees with their answer, and each one's latest messages, mail and " +
|
|
277
|
+
"meetings the store holds; the next meeting when no event is named")
|
|
278
|
+
.summary("what the store holds for one meeting and the people in it")
|
|
279
|
+
.argument("[event]", "the event's id, or event:<id>; the next meeting with other attendees when absent"), "events prepare --json", "events prepare event:42 --limit 3", "events prepare event:42 --since-time 2026-10-01")
|
|
280
|
+
.option("--limit <n>", `messages per person in each list, 1–50; ${PREPARE_MESSAGES} if not given`, upTo(50))
|
|
281
|
+
.option("--since-time <time>", "no message or meeting older than this ISO 8601 time, or 30d ago")
|
|
282
|
+
.action(async (reference, opts, command) => {
|
|
283
|
+
const window = dateWindow({
|
|
284
|
+
...(opts.sinceTime === undefined ? {} : { sinceTime: opts.sinceTime }),
|
|
285
|
+
zone: zone(),
|
|
286
|
+
now: now(),
|
|
287
|
+
});
|
|
288
|
+
const details = reference === undefined
|
|
289
|
+
? await nextMeeting(deps.store, deps.accountId, now())
|
|
290
|
+
: await eventOf(deps.store, deps.accountId, eventIdOf(reference));
|
|
291
|
+
const { meta, ...prepared } = await eventPreparation(details, {
|
|
292
|
+
store: deps.store,
|
|
293
|
+
messages: messages("events prepare"),
|
|
294
|
+
account: { id: deps.accountId, key: deps.account },
|
|
295
|
+
personOf: personOfEmail ?? (async () => null),
|
|
296
|
+
now: now(),
|
|
297
|
+
...(opts.limit === undefined ? {} : { limit: opts.limit }),
|
|
298
|
+
...(window.since === undefined ? {} : { since: window.since }),
|
|
299
|
+
});
|
|
300
|
+
print({ ...prepared, meta: { ...meta, ...window.meta } }, command);
|
|
301
|
+
});
|
|
235
302
|
const searches = annotate(program.command("search").description("find stored things by text: events"), {
|
|
236
303
|
effects: GROUP,
|
|
237
304
|
});
|
|
@@ -369,6 +436,7 @@ const addEventWrites = (events, writes, { deps, now, zone, print, example }) =>
|
|
|
369
436
|
// The empty default marks the flag as repeatable, so an agent's tool takes a list; empty is "not given".
|
|
370
437
|
.option("--attendee <email>", `an attendee; repeat for each${replacing ? ", replacing the list" : ""}`, collect, [])
|
|
371
438
|
.option("--repeat <rule>", `how it repeats${replacing ? ", with --series" : ""}: ${REPEAT_HELP}`);
|
|
439
|
+
const flagOf = (field) => field === "attendees" ? "--attendee" : `--${field.replace(/[A-Z]/gu, (letter) => `-${letter.toLowerCase()}`)}`;
|
|
372
440
|
const fieldsOf = ({ attendee, ...given }) => ({
|
|
373
441
|
...Object.fromEntries(Object.entries(given).filter(([, value]) => value !== undefined)),
|
|
374
442
|
...(attendee === undefined || attendee.length === 0 ? {} : { attendees: attendee }),
|
|
@@ -382,13 +450,30 @@ const addEventWrites = (events, writes, { deps, now, zone, print, example }) =>
|
|
|
382
450
|
const write = (name) => annotate(events.command(name), { effects: REMOTE_WRITE, offline: false });
|
|
383
451
|
const target = (command) => command
|
|
384
452
|
.argument("<event>", "the event's id, or event:<id>")
|
|
385
|
-
.option("--calendar <id>", "the calendar, when the event is on more than one of the account's");
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
.
|
|
453
|
+
.option("--calendar <id>", "the calendar, by id or title, when the event is on more than one of the account's");
|
|
454
|
+
const quick = writes.writer.quickAdd !== undefined;
|
|
455
|
+
const create = example(notify(fields(write("create"), false)
|
|
456
|
+
.description(quick
|
|
457
|
+
? "add an event to a calendar; with --repeat, one that repeats; with --text, one the provider reads " +
|
|
458
|
+
"from a line such as 'Lunch with Ana tomorrow 1pm'"
|
|
459
|
+
: "add an event to a calendar; with --repeat, one that repeats")
|
|
460
|
+
.argument("<calendar>", "the calendar's id, the provider's id, or its title")).option("--operation-id <id>", "the operation_id a create that ended in outcome_unknown named: a retry finds that event"), quick
|
|
461
|
+
? "events create Work --text 'Example lunch with Ana tomorrow 1pm at Example Cafe'"
|
|
462
|
+
: "events create 3 --title 'Example planning' --start-time 2026-11-02T10:00 --end-time 2026-11-02T11:00", "events create 3 --title 'Example review' --start-date 2026-11-03 --end-date 2026-11-03 " +
|
|
389
463
|
"--attendee ana@example.com --no-notify", "events create 3 --title 'Example standup' --start-time 2026-11-02T09:30 --end-time 2026-11-02T09:45 " +
|
|
390
|
-
"--repeat 'FREQ=WEEKLY;BYDAY=MO,WE,FR;COUNT=12'")
|
|
391
|
-
|
|
464
|
+
"--repeat 'FREQ=WEEKLY;BYDAY=MO,WE,FR;COUNT=12'");
|
|
465
|
+
if (quick)
|
|
466
|
+
create.option("--text <text>", "one line the provider reads the title, date, time and place from, in place of the other fields; nobody is " +
|
|
467
|
+
"mailed without --notify, and a retry after outcome_unknown may add the event twice");
|
|
468
|
+
create.action(async (calendar, opts, command) => {
|
|
469
|
+
const { notify: choice, operationId: id, text, ...rest } = opts;
|
|
470
|
+
if (text !== undefined) {
|
|
471
|
+
const given = Object.keys(fieldsOf(rest));
|
|
472
|
+
if (given.length || id !== undefined)
|
|
473
|
+
throw new CliError("validation_error", `--text takes the whole event from its line; leave out ${[...given.map(flagOf), ...(id === undefined ? [] : ["--operation-id"])].join(", ")}`, { reason: "event.text-with-fields", param: "--text" });
|
|
474
|
+
printWritten(await createEventFromText(context(command), calendar, text, choice === undefined ? {} : { notify: choice }), command);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
392
477
|
printWritten(await createEvent(context(command), calendar, fieldsOf(rest), {
|
|
393
478
|
...(choice === undefined ? {} : { notify: choice }),
|
|
394
479
|
...(id === undefined ? {} : { id }),
|
|
@@ -414,10 +499,24 @@ const addEventWrites = (events, writes, { deps, now, zone, print, example }) =>
|
|
|
414
499
|
.option("--following", "delete this occurrence and every later one: the repeating event ends before it")), "events delete event:42 --no-notify", "events delete event:42 --following --no-notify").action(async (reference, opts, command) => {
|
|
415
500
|
printWritten(await deleteEvent(context(command), reference, choicesOf(opts)), command);
|
|
416
501
|
});
|
|
502
|
+
const readFile = deps.readFile;
|
|
503
|
+
if (writes.writer.importEvent && readFile)
|
|
504
|
+
example(write("import")
|
|
505
|
+
.description(`add the events of an iCalendar (.ics) file to a calendar, at most ${IMPORT_MAX}, each under its own UID; ` +
|
|
506
|
+
"nobody is mailed; a changed occurrence of a repeating event (RECURRENCE-ID) is skipped")
|
|
507
|
+
.argument("<calendar>", "the calendar's id, the provider's id, or its title")
|
|
508
|
+
.argument("<file>", "the .ics file to read"), "events import Work planning.ics").action(async (calendar, file, _opts, command) => {
|
|
509
|
+
const text = await readFile(file);
|
|
510
|
+
if (Buffer.byteLength(text, "utf8") > ICS_MAX_BYTES)
|
|
511
|
+
throw new CliError("validation_error", `${file} is larger than ${ICS_MAX_BYTES / 1024 / 1024} MB`, {
|
|
512
|
+
reason: "ics.too-large",
|
|
513
|
+
});
|
|
514
|
+
print({ ...(await importEvents(context(command), calendar, text)), untrusted: IMPORT_UNTRUSTED }, command);
|
|
515
|
+
});
|
|
417
516
|
if (writes.writer.move)
|
|
418
517
|
example(notify(target(write("move"))
|
|
419
518
|
.description("move an event, with its id, to another calendar of the account; a repeating one moves whole")
|
|
420
|
-
.requiredOption("--to-calendar <id>", "the calendar to move it to: its id,
|
|
519
|
+
.requiredOption("--to-calendar <id>", "the calendar to move it to: its id, the provider's id, or its title")
|
|
421
520
|
.option("--series", "move the whole repeating event an occurrence belongs to")), "events move event:42 --to-calendar 4 --no-notify").action(async (reference, opts, command) => {
|
|
422
521
|
const { series, calendar, notify } = opts;
|
|
423
522
|
printWritten(await moveEvent(context(command), reference, opts.toCalendar, choicesOf({ series, calendar, notify })), command);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { EntryStatus, EntryTransparency, ParticipantResponse } from "../meetings/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* The part of iCalendar (RFC 5545, https://www.rfc-editor.org/rfc/rfc5545) the calendar commands read and write: one
|
|
4
|
+
* VCALENDAR of VEVENTs, with no VTIMEZONE. Times are written in UTC; a time read with a TZID keeps that zone's name,
|
|
5
|
+
* which the provider takes as it is.
|
|
6
|
+
*/
|
|
7
|
+
export interface IcsEvent {
|
|
8
|
+
uid: string;
|
|
9
|
+
title: string | null;
|
|
10
|
+
description?: string | null;
|
|
11
|
+
location?: string | null;
|
|
12
|
+
/** A timed event, epoch ms, end exclusive. */
|
|
13
|
+
startsAt?: number;
|
|
14
|
+
endsAt?: number;
|
|
15
|
+
/** An all-day event, `YYYY-MM-DD`, end exclusive. */
|
|
16
|
+
startDate?: string;
|
|
17
|
+
endDate?: string;
|
|
18
|
+
/** The zone a read time named (`TZID`), or the reader's zone for a floating one. */
|
|
19
|
+
timezone?: string | null;
|
|
20
|
+
/** RRULE, RDATE and EXDATE lines, as written. */
|
|
21
|
+
recurrence?: string[];
|
|
22
|
+
status?: EntryStatus | null;
|
|
23
|
+
transparency?: EntryTransparency | null;
|
|
24
|
+
organizer?: {
|
|
25
|
+
email: string;
|
|
26
|
+
name?: string | null;
|
|
27
|
+
} | null;
|
|
28
|
+
attendees?: {
|
|
29
|
+
email: string;
|
|
30
|
+
name?: string | null;
|
|
31
|
+
response?: ParticipantResponse | null;
|
|
32
|
+
optional?: boolean;
|
|
33
|
+
}[];
|
|
34
|
+
url?: string | null;
|
|
35
|
+
}
|
|
36
|
+
/** Lines longer than 75 octets go on under a leading space, never splitting a character's UTF-8 bytes. */
|
|
37
|
+
export declare const foldLine: (line: string) => string;
|
|
38
|
+
/** One VCALENDAR holding the events, CRLF line ends and folded lines, as RFC 5545 §3.1 asks. */
|
|
39
|
+
export declare const writeIcs: (events: readonly IcsEvent[], options: {
|
|
40
|
+
now: number;
|
|
41
|
+
product: string;
|
|
42
|
+
}) => string;
|
|
43
|
+
/** An event the reader could not take, by its UID when it had one. */
|
|
44
|
+
export interface IcsSkip {
|
|
45
|
+
uid: string | null;
|
|
46
|
+
reason: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The VEVENTs of an iCalendar text; times with neither `Z` nor a TZID are read in `zone`. An event that cannot be
|
|
50
|
+
* imported alone is in `skipped` with the reason; a text with no VCALENDAR is refused.
|
|
51
|
+
*/
|
|
52
|
+
export declare const readIcs: (text: string, zone: string) => {
|
|
53
|
+
events: IcsEvent[];
|
|
54
|
+
skipped: IcsSkip[];
|
|
55
|
+
};
|