@wirecat/cli-core 0.474.0 → 0.475.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 +15 -0
- package/dist/calendar/mcp/index.js +10 -4
- package/dist/calendar/reads.d.ts +23 -4
- package/dist/calendar/reads.js +23 -3
- package/dist/calendar/testing/memory.js +8 -2
- package/dist/meetings/event-store.d.ts +4 -0
- package/dist/meetings/reads.d.ts +10 -7
- package/dist/meetings/reads.js +5 -1
- package/dist/store/sqlite/calendar.js +13 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,21 @@ Notable changes to `@wirecat/cli-core` (`@wirecat/cli-messaging` up to 0.261.0,
|
|
|
5
5
|
minor version may break callers, and says how under "Changed — may break callers". `pnpm docs:check` checks the
|
|
6
6
|
shape of this file.
|
|
7
7
|
|
|
8
|
+
## Unreleased
|
|
9
|
+
|
|
10
|
+
## 0.475.0 — 29.10.2026
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **`events list` matches all-day events by their dates** (WC-TIME-5). An all-day event is stored as midnight in its
|
|
15
|
+
calendar's zone, so an owner in another zone got the next day's all-day event in `from:2026-11-02 to:2026-11-02`.
|
|
16
|
+
`eventsFilter` now also sets `fromDate` / `toDate` (the window's local days, `toDate` exclusive), and both event
|
|
17
|
+
stores compare an all-day event's `start_date` / `end_date` with them. Timed events, and a filter without the dates,
|
|
18
|
+
match as before.
|
|
19
|
+
- **`events list` reports `hasMore`.** It was always false; it is now true when another event follows the page (also
|
|
20
|
+
across accounts in the MCP `events_list`). `listed()` takes an optional `{ limit, hasMore }`.
|
|
21
|
+
- **`people list` says when it counted only its first 1000 events**, with `meta: { complete: true, truncated: true }`.
|
|
22
|
+
|
|
8
23
|
## 0.474.0 — 29.10.2026
|
|
9
24
|
|
|
10
25
|
### Fixed
|
|
@@ -209,14 +209,20 @@ export const callCalendarTool = async (store, name, input, { accountIds, failure
|
|
|
209
209
|
break;
|
|
210
210
|
case "events_list": {
|
|
211
211
|
const words = typeof args.words === "string" ? args.words.trim().split(/\s+/u) : [];
|
|
212
|
-
const
|
|
212
|
+
const limit = args.limit ?? 100;
|
|
213
|
+
const lists = await Promise.all(accounts.map(async (accountId) => eventsAnswer(store, await eventsFilter(words, {
|
|
213
214
|
store,
|
|
214
215
|
accountId,
|
|
215
216
|
zone: args.timezone ?? "UTC",
|
|
216
217
|
now,
|
|
217
|
-
}).then((filter) => filter && { ...filter, limit
|
|
218
|
-
const items = lists
|
|
219
|
-
|
|
218
|
+
}).then((filter) => filter && { ...filter, limit }))));
|
|
219
|
+
const items = lists
|
|
220
|
+
.flatMap((one) => one.items)
|
|
221
|
+
.sort((a, b) => (a.starts_at ?? "").localeCompare(b.starts_at ?? ""));
|
|
222
|
+
answer = listed(items.slice(0, limit), {
|
|
223
|
+
limit,
|
|
224
|
+
hasMore: items.length > limit || lists.some((one) => one.hasMore),
|
|
225
|
+
});
|
|
220
226
|
break;
|
|
221
227
|
}
|
|
222
228
|
case "event_show":
|
package/dist/calendar/reads.d.ts
CHANGED
|
@@ -109,6 +109,8 @@ export declare const peopleAnswer: (store: EventStore, accountId: number, option
|
|
|
109
109
|
filter?: EventFilter;
|
|
110
110
|
personOf?: PersonOfEmail;
|
|
111
111
|
}) => Promise<{
|
|
112
|
+
limit: number;
|
|
113
|
+
hasMore: boolean;
|
|
112
114
|
items: {
|
|
113
115
|
email: string;
|
|
114
116
|
name: string | null;
|
|
@@ -120,11 +122,30 @@ export declare const peopleAnswer: (store: EventStore, accountId: number, option
|
|
|
120
122
|
person: string | null;
|
|
121
123
|
}[];
|
|
122
124
|
page: number;
|
|
125
|
+
} | {
|
|
123
126
|
limit: number;
|
|
124
127
|
hasMore: boolean;
|
|
128
|
+
items: {
|
|
129
|
+
email: string;
|
|
130
|
+
name: string | null;
|
|
131
|
+
response: import("../meetings/event-store.js").ParticipantResponse | null;
|
|
132
|
+
organizer: boolean;
|
|
133
|
+
optional: boolean;
|
|
134
|
+
resource: boolean;
|
|
135
|
+
self: boolean;
|
|
136
|
+
person: string | null;
|
|
137
|
+
events: number;
|
|
138
|
+
}[];
|
|
139
|
+
page: number;
|
|
140
|
+
meta: {
|
|
141
|
+
complete: boolean;
|
|
142
|
+
truncated: boolean;
|
|
143
|
+
};
|
|
125
144
|
}>;
|
|
126
145
|
/** Open link choices, with each candidate event's title and time so the owner can pick. */
|
|
127
146
|
export declare const reviewsAnswer: (store: EventStore) => Promise<{
|
|
147
|
+
limit: number;
|
|
148
|
+
hasMore: boolean;
|
|
128
149
|
items: {
|
|
129
150
|
id: string;
|
|
130
151
|
entry_id: string;
|
|
@@ -136,8 +157,6 @@ export declare const reviewsAnswer: (store: EventStore) => Promise<{
|
|
|
136
157
|
}[];
|
|
137
158
|
}[];
|
|
138
159
|
page: number;
|
|
139
|
-
limit: number;
|
|
140
|
-
hasMore: boolean;
|
|
141
160
|
accounts: string;
|
|
142
161
|
}>;
|
|
143
162
|
export declare const resolveAnswer: (store: EventStore, review: string, choice: string, now: number) => Promise<{
|
|
@@ -146,6 +165,8 @@ export declare const resolveAnswer: (store: EventStore, review: string, choice:
|
|
|
146
165
|
chosen: string | null;
|
|
147
166
|
}>;
|
|
148
167
|
export declare const eventsAnswer: (store: EventStore, filter: EventFilter | null) => Promise<{
|
|
168
|
+
limit: number;
|
|
169
|
+
hasMore: boolean;
|
|
149
170
|
items: {
|
|
150
171
|
id: string;
|
|
151
172
|
event_series_id: string | null;
|
|
@@ -165,6 +186,4 @@ export declare const eventsAnswer: (store: EventStore, filter: EventFilter | nul
|
|
|
165
186
|
untrusted?: string[] | undefined;
|
|
166
187
|
}[];
|
|
167
188
|
page: number;
|
|
168
|
-
limit: number;
|
|
169
|
-
hasMore: boolean;
|
|
170
189
|
}>;
|
package/dist/calendar/reads.js
CHANGED
|
@@ -93,6 +93,10 @@ export const eventsFilter = async (words, context) => {
|
|
|
93
93
|
}
|
|
94
94
|
if (!timed)
|
|
95
95
|
filter.from = day(today);
|
|
96
|
+
if (filter.from !== undefined)
|
|
97
|
+
filter.fromDate = localDate(filter.from, context.zone);
|
|
98
|
+
if (filter.to !== undefined)
|
|
99
|
+
filter.toDate = plusDays(localDate(filter.to - 1, context.zone), 1);
|
|
96
100
|
return filter;
|
|
97
101
|
};
|
|
98
102
|
/**
|
|
@@ -169,13 +173,23 @@ export const eventAnswer = async (details, personOf) => ({
|
|
|
169
173
|
changed_at: new Date(one.createdAt).toISOString(),
|
|
170
174
|
})),
|
|
171
175
|
});
|
|
176
|
+
const EVENTS_COUNTED = 1000;
|
|
177
|
+
/** One page of events, and whether another follows; probed with one row, since a store reads at most 1000. */
|
|
178
|
+
const page = async (store, filter) => {
|
|
179
|
+
const rows = await store.events(filter);
|
|
180
|
+
const limit = filter.limit ?? 100;
|
|
181
|
+
const hasMore = rows.length === limit &&
|
|
182
|
+
(await store.events({ ...filter, offset: (filter.offset ?? 0) + limit, limit: 1 })).length > 0;
|
|
183
|
+
return { rows, limit, hasMore };
|
|
184
|
+
};
|
|
172
185
|
/** One event's attendees, or everyone attending the events of the filter, with how many of them each attends. */
|
|
173
186
|
export const peopleAnswer = async (store, accountId, options) => {
|
|
174
187
|
if (options.event !== undefined)
|
|
175
188
|
return listed(await attendees((await eventOf(store, accountId, options.event)).participants, options.personOf));
|
|
176
189
|
const counted = new Map();
|
|
177
190
|
const mine = await calendarIds(store, accountId);
|
|
178
|
-
|
|
191
|
+
const { rows, hasMore } = await page(store, { ...options.filter, accountId, limit: EVENTS_COUNTED });
|
|
192
|
+
for (const event of rows) {
|
|
179
193
|
const details = await store.event(event.id);
|
|
180
194
|
const seen = new Set();
|
|
181
195
|
for (const one of details ? ownCopies(details, mine).participants : []) {
|
|
@@ -190,9 +204,10 @@ export const peopleAnswer = async (store, accountId, options) => {
|
|
|
190
204
|
}
|
|
191
205
|
}
|
|
192
206
|
const people = await attendees([...counted.values()].map(({ participant }) => participant), options.personOf);
|
|
193
|
-
|
|
207
|
+
const answer = listed(people
|
|
194
208
|
.map((one) => ({ ...one, events: counted.get(one.email)?.events ?? 0 }))
|
|
195
209
|
.sort((a, b) => b.events - a.events || a.email.localeCompare(b.email)));
|
|
210
|
+
return hasMore ? { ...answer, meta: { complete: true, truncated: true } } : answer;
|
|
196
211
|
};
|
|
197
212
|
/** Open link choices, with each candidate event's title and time so the owner can pick. */
|
|
198
213
|
export const reviewsAnswer = async (store) => {
|
|
@@ -226,4 +241,9 @@ export const resolveAnswer = async (store, review, choice, now) => {
|
|
|
226
241
|
chosen: resolved.chosenId === null ? null : `event:${resolved.chosenId}`,
|
|
227
242
|
};
|
|
228
243
|
};
|
|
229
|
-
export const eventsAnswer = async (store, filter) =>
|
|
244
|
+
export const eventsAnswer = async (store, filter) => {
|
|
245
|
+
if (filter === null)
|
|
246
|
+
return listed([]);
|
|
247
|
+
const { rows, limit, hasMore } = await page(store, filter);
|
|
248
|
+
return listed(rows.map(eventItem), { limit, hasMore });
|
|
249
|
+
};
|
|
@@ -409,8 +409,14 @@ export const memoryEventStore = () => {
|
|
|
409
409
|
const has = (event, check) => entries.some((one) => one.eventId === event.id && check(one));
|
|
410
410
|
return [...events.values()]
|
|
411
411
|
.filter((event) => event.deletedAt === null &&
|
|
412
|
-
(filter.from === undefined ||
|
|
413
|
-
|
|
412
|
+
(filter.from === undefined ||
|
|
413
|
+
(event.allDay && filter.fromDate !== undefined
|
|
414
|
+
? has(event, (one) => (one.endDate ?? "") > (filter.fromDate ?? ""))
|
|
415
|
+
: (event.endsAt ?? (event.startsAt ?? 0) + 1) > filter.from)) &&
|
|
416
|
+
(filter.to === undefined ||
|
|
417
|
+
(event.allDay && filter.toDate !== undefined
|
|
418
|
+
? has(event, (one) => one.startDate !== null && one.startDate < (filter.toDate ?? ""))
|
|
419
|
+
: event.startsAt !== null && event.startsAt < filter.to)) &&
|
|
414
420
|
(filter.calendarId === undefined || has(event, (one) => one.calendarId === filter.calendarId)) &&
|
|
415
421
|
(filter.accountId === undefined ||
|
|
416
422
|
has(event, (one) => calendarOf(one.calendarId).accountId === filter.accountId)) &&
|
|
@@ -181,6 +181,10 @@ export interface EventFilter {
|
|
|
181
181
|
from?: number;
|
|
182
182
|
/** Events that start before this instant. */
|
|
183
183
|
to?: number;
|
|
184
|
+
/** All-day events, which have dates and no instant, ending after this local day (`YYYY-MM-DD`); `from` without it. */
|
|
185
|
+
fromDate?: string;
|
|
186
|
+
/** All-day events starting before this local day (exclusive); `to` without it. */
|
|
187
|
+
toDate?: string;
|
|
184
188
|
accountId?: number;
|
|
185
189
|
calendarId?: number;
|
|
186
190
|
/** Events whose entries list one of this person's contacts. */
|
package/dist/meetings/reads.d.ts
CHANGED
|
@@ -10,17 +10,20 @@ export declare class MeetingError extends CliError {
|
|
|
10
10
|
}
|
|
11
11
|
export declare const positiveInteger: (value: unknown) => number;
|
|
12
12
|
export declare const meetingShow: (store: MeetingStore, id: number, accountId?: number) => Promise<MeetingDetails>;
|
|
13
|
-
export declare const listed: <T>(items: T[]
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
export declare const listed: <T>(items: T[], page?: {
|
|
14
|
+
limit: number;
|
|
15
|
+
hasMore: boolean;
|
|
16
|
+
}) => {
|
|
16
17
|
limit: number;
|
|
17
18
|
hasMore: boolean;
|
|
19
|
+
items: T[];
|
|
20
|
+
page: number;
|
|
18
21
|
};
|
|
19
22
|
export declare const meetingPeople: (store: MeetingStore & Partial<MeetingParticipantSearchStore>, query: string, accountId?: number, options?: Omit<MeetingParticipantSearchOptions, "accountId">) => Promise<{
|
|
20
|
-
items: import("./model.js").Participant[];
|
|
21
|
-
page: number;
|
|
22
23
|
limit: number;
|
|
23
24
|
hasMore: boolean;
|
|
25
|
+
items: import("./model.js").Participant[];
|
|
26
|
+
page: number;
|
|
24
27
|
} | {
|
|
25
28
|
schema_version: 1;
|
|
26
29
|
kind: "meeting_participants";
|
|
@@ -76,8 +79,8 @@ export declare const meetingDetailsAnswer: (store: MeetingStore, id: number, acc
|
|
|
76
79
|
export declare const meetingTranscriptAnswer: (store: MeetingStore, id: number, accountId?: number, options?: TranscriptReadOptions) => Promise<MeetingListBody<MeetingTranscriptItem>>;
|
|
77
80
|
export declare const meetingSummaryAnswer: (store: MeetingStore, id: number, accountId?: number) => Promise<MeetingListBody<MeetingSummaryItem>>;
|
|
78
81
|
export declare const meetingsSearch: (store: MeetingStore, query: string, filter?: MeetingFilter) => Promise<{
|
|
79
|
-
items: import("./store.js").SearchHit[];
|
|
80
|
-
page: number;
|
|
81
82
|
limit: number;
|
|
82
83
|
hasMore: boolean;
|
|
84
|
+
items: import("./store.js").SearchHit[];
|
|
85
|
+
page: number;
|
|
83
86
|
}>;
|
package/dist/meetings/reads.js
CHANGED
|
@@ -33,7 +33,11 @@ export const meetingShow = async (store, id, accountId) => {
|
|
|
33
33
|
throw new MeetingError("not_found", "meeting not found");
|
|
34
34
|
return details;
|
|
35
35
|
};
|
|
36
|
-
export const listed = (items
|
|
36
|
+
export const listed = (items, page = { limit: items.length, hasMore: false }) => ({
|
|
37
|
+
items,
|
|
38
|
+
page: 1,
|
|
39
|
+
...page,
|
|
40
|
+
});
|
|
37
41
|
export const meetingPeople = async (store, query, accountId, options = {}) => {
|
|
38
42
|
if (accountId === undefined)
|
|
39
43
|
validateMeetingParticipantQuery(query);
|
|
@@ -542,14 +542,19 @@ export const eventStoreOver = (context) => {
|
|
|
542
542
|
const clauses = ["e.deleted_at IS NULL"];
|
|
543
543
|
const params = [];
|
|
544
544
|
const entries = (extra) => `EXISTS (SELECT 1 FROM calendar_entries ce JOIN calendars c ON c.id = ce.calendar_id WHERE ce.event_id = e.id AND ${extra})`;
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
545
|
+
const dated = (column, date, instant, at) => {
|
|
546
|
+
if (at === undefined)
|
|
547
|
+
return;
|
|
548
|
+
if (date === undefined) {
|
|
549
|
+
clauses.push(instant);
|
|
550
|
+
params.push(at);
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
clauses.push(`(CASE WHEN e.all_day = 1 THEN EXISTS (SELECT 1 FROM calendar_entries ce WHERE ce.event_id = e.id AND ce.${column}) ELSE ${instant} END)`);
|
|
554
|
+
params.push(date, at);
|
|
555
|
+
};
|
|
556
|
+
dated("end_date > ?", filter.fromDate, "coalesce(e.ends_at, e.starts_at + 1) > ?", filter.from);
|
|
557
|
+
dated("start_date < ?", filter.toDate, "e.starts_at < ?", filter.to);
|
|
553
558
|
if (filter.calendarId !== undefined) {
|
|
554
559
|
clauses.push(entries("ce.calendar_id = ?"));
|
|
555
560
|
params.push(filter.calendarId);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirecat/cli-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.475.0",
|
|
4
4
|
"description": "The messenger-neutral half of a messaging CLI: domain model, message locators, rendering, name resolution, the SQLite seam, the command skeleton and shared read commands, the send guard, run records and the message store",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Viacheslav Ptsarev",
|