@wirecat/cli-core 0.500.0 → 0.502.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 +53 -1
- package/dist/calendar/busy.d.ts +9 -2
- package/dist/calendar/busy.js +61 -39
- package/dist/calendar/cli/answers.js +2 -2
- package/dist/calendar/cli/index.js +20 -21
- package/dist/calendar/mcp/index.d.ts +28 -28
- package/dist/calendar/mcp/index.js +8 -9
- package/dist/calendar/reads.d.ts +15 -4
- package/dist/calendar/reads.js +93 -43
- package/dist/calendar/testing/memory.js +4 -0
- package/dist/cli/envelope.d.ts +11 -1
- package/dist/cli/envelope.js +15 -3
- package/dist/cli/index.d.ts +2 -2
- package/dist/cli/index.js +2 -2
- package/dist/cli/list-query.d.ts +5 -0
- package/dist/cli/list-query.js +9 -3
- package/dist/cli/meeting-preparation-answer.d.ts +8 -0
- package/dist/cli/meeting-preparation-projects.d.ts +8 -0
- package/dist/cli/meeting-preparation-read.d.ts +8 -0
- package/dist/cli/meeting-series-inventory-read.d.ts +8 -0
- package/dist/cli/messenger/messages-search-command.js +4 -1
- package/dist/cli/messenger/search-answers.d.ts +3 -0
- package/dist/cli/messenger/search-answers.js +6 -0
- package/dist/cli/messenger/search-command.js +4 -1
- package/dist/entities/index.d.ts +24 -0
- package/dist/entities/meeting-preparation.d.ts +8 -0
- package/dist/entities/meeting-series-inventory.d.ts +8 -0
- package/dist/mcp/meeting-preparation.d.ts +8 -0
- package/dist/mcp/meeting-series-inventory.d.ts +8 -0
- package/dist/meetings/event-store.d.ts +4 -0
- package/dist/meetings/mcp/index.d.ts +15 -15
- package/dist/meetings/project-evidence-answer.d.ts +8 -0
- package/dist/meetings/testing/event-store-contract.js +23 -0
- package/dist/search/total.d.ts +13 -0
- package/dist/search/total.js +14 -0
- package/dist/services/index.d.ts +2 -1
- package/dist/services/index.js +2 -1
- package/dist/services/messages-archive-search.js +13 -3
- package/dist/services/messages-discovery.d.ts +6 -0
- package/dist/services/messages-discovery.js +14 -11
- package/dist/services/messages-search.d.ts +5 -0
- package/dist/services/messages-search.js +22 -0
- package/dist/services/messages.d.ts +3 -0
- package/dist/services/notes-search.d.ts +14 -2
- package/dist/services/notes-search.js +101 -19
- package/dist/store/queries.d.ts +2 -0
- package/dist/store/queries.js +1 -0
- package/dist/store/sqlite/calendar-events.js +5 -0
- package/dist/store/sqlite/lucene.d.ts +5 -0
- package/dist/store/sqlite/lucene.js +17 -0
- package/dist/store/sqlite/note-search.d.ts +18 -0
- package/dist/store/sqlite/note-search.js +40 -4
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,59 @@ 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
|
-
##
|
|
8
|
+
## 0.502.0 — 31.10.2026
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **The event store finds events by their earliest start: `EventFilter.startsFrom`.** It keeps events that start at
|
|
13
|
+
or after an instant, and `startsFromDate` keeps all-day events by their local date, as `toDate` does (WC-TIME-5).
|
|
14
|
+
Both stores (SQLite and `memoryEventStore`) answer it, and the event store contract checks the boundary.
|
|
15
|
+
- **`nativeTermLists`** from `@wirecat/cli-core/cli`: as `nativeTerms`, but named fields may repeat
|
|
16
|
+
(`person:a person:b`), every term kept.
|
|
17
|
+
|
|
18
|
+
### Changed — may break callers
|
|
19
|
+
|
|
20
|
+
- **The shared calendar commands take the standard query and the paths the other tools use.** A CLI that mounts
|
|
21
|
+
`addCalendarCommands` (gcal) gets:
|
|
22
|
+
- `store status` in place of `status`, and `search events <query…>` in place of `search <words…>`; the permission
|
|
23
|
+
keys are `store.status` and `search.events`, and the sync's hint names `store status`.
|
|
24
|
+
- `events list [query…]` (`EVENTS_QUERY`): `starts_at` either way, now including `starts_at>=…` and
|
|
25
|
+
`starts_at:[a TO b}`, `ends_at>=…`, `changed_at>=…`, `calendar:` and `person:`. The words `today`, `week`,
|
|
26
|
+
`from:`, `to:`, `changed:` and `overlapping` are refused as query errors.
|
|
27
|
+
- `calendars busy [query…]` (`BUSY_QUERY`): the window as `start>=…` and `end<=…`, and `calendar:` and `person:`,
|
|
28
|
+
each repeatable. `today`, `week`, `from:` and `to:` are refused.
|
|
29
|
+
- `eventsFilter(query, …)` takes the query text in place of words, and `busyQuery(query, …)` as well;
|
|
30
|
+
`EVENTS_QUERY_HELP` and `BUSY_QUERY_HELP` are gone (`EVENTS_QUERY_NOTE`, `BUSY_QUERY_NOTE`), and `weekFilter`
|
|
31
|
+
builds `people list`'s week.
|
|
32
|
+
- `calendarTools`: `status` is `store_status`, and `events_list` takes `query` in place of `words`.
|
|
33
|
+
|
|
34
|
+
## 0.501.0 — 31.10.2026
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
|
|
38
|
+
- **Search answers say how many match in all: `page.total`.** `search notes` and `search messages` add
|
|
39
|
+
`page.total: { value, relation }` to `--json`, and `NotesFound.total` / `SearchFound.total` carry it to a tool that
|
|
40
|
+
builds its own answer (drive prints it as `item.total`; memo's notes, messages and mail pass it on with their next
|
|
41
|
+
release). It is shaped as Elasticsearch's `hits.total`: `relation` is `eq` for an exact count and `gte` for a lower bound. The terminal adds a line such as
|
|
42
|
+
`showing 1–20 of 144` (`144+` for a lower bound). Notes count every note the words find plus every note found by
|
|
43
|
+
meaning; messages count the strict query's matches, or discovery's candidates with `--discover`. Counting stops at
|
|
44
|
+
10,000 (`TOTAL_CAP`, Elasticsearch's default), and a query whose leaves are checked row by row (a preset, a
|
|
45
|
+
pattern) gets the lower bound the page proves. New: `SearchTotal`, `NotesFound.total`, `SearchFound.total`,
|
|
46
|
+
`NoteSearch.matchingRefs` (refs only, no note read), `NoteSearch.scoreNotes` and `QueriesStore.countMatches`;
|
|
47
|
+
`listOf` takes `total`, and `shownOf(shown, offset, total)` from `@wirecat/cli-core/cli` writes the terminal line.
|
|
48
|
+
|
|
49
|
+
### Changed — may break callers
|
|
50
|
+
|
|
51
|
+
- **cli-core needs `@wirecat/cli-base` 0.67.** The peer range moves from `>=0.65.0 <0.66.0` to `>=0.67.0 <0.68.0`. A
|
|
52
|
+
CLI that pins cli-base moves its pin with this release. cli-base 0.67 adds `page.total` to the list envelope.
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
|
|
56
|
+
- **Search by meaning inside a filter ranks the filter's own notes.** With a filter that holds at most 2,000 notes
|
|
57
|
+
(`RANK_WITHIN`), `search notes` scores every one of them by meaning, instead of taking the 50 nearest notes overall
|
|
58
|
+
and dropping those outside the filter. On the owner's store, Drive's `budget date>=30d` went from 7 hits to a full
|
|
59
|
+
page of 20, and 50 of the 144 documents in the date range match in all; no hit falls outside the filter, as
|
|
60
|
+
before. A larger filter keeps the old way, as does one the store cannot list cheaply.
|
|
9
61
|
|
|
10
62
|
## 0.500.0 — 31.10.2026
|
|
11
63
|
|
package/dist/calendar/busy.d.ts
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
|
+
import { type QueryRegistry } from "@wirecat/cli-base/query";
|
|
2
|
+
import { type QueryWords } from "../cli/list-query.js";
|
|
1
3
|
import type { EventStore, StoredCalendar } from "../meetings/index.js";
|
|
2
4
|
import type { CalendarSource, SyncWindow } from "./source.js";
|
|
3
|
-
export declare const BUSY_QUERY_HELP = "\nWords, any order:\n today | week today, or today and the next six days, in your time zone\n from:<date> to:<date> from the start of one day through the end of another (or a time with a zone)\n calendar:<id> one of the account's calendars, by its id or the provider's id; repeat for more\n person:<email> someone else, by email address; repeat for more\nWithout a time word: this week. Without calendar:, every calendar that syncs.";
|
|
4
5
|
export interface BusyQuery {
|
|
5
6
|
window: SyncWindow;
|
|
6
7
|
calendars: StoredCalendar[];
|
|
7
8
|
people: string[];
|
|
8
9
|
}
|
|
9
|
-
|
|
10
|
+
/**
|
|
11
|
+
* `calendars busy`'s query: the window by the answer's own `start` and `end`, and whom to ask. `calendar` and
|
|
12
|
+
* `person` repeat, and every one named is asked: the free stretches are those they all share.
|
|
13
|
+
*/
|
|
14
|
+
export declare const BUSY_QUERY: QueryRegistry;
|
|
15
|
+
export declare const BUSY_QUERY_NOTE: string;
|
|
16
|
+
export declare const busyQuery: (query: QueryWords, context: {
|
|
10
17
|
store: EventStore;
|
|
11
18
|
accountId: number;
|
|
12
19
|
zone: string;
|
package/dist/calendar/busy.js
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
|
-
import { dayBoundary, zonedMoment } from "@wirecat/cli-base/query";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
today | week today, or today and the next six days, in your time zone
|
|
7
|
-
from:<date> to:<date> from the start of one day through the end of another (or a time with a zone)
|
|
8
|
-
calendar:<id> one of the account's calendars, by its id or the provider's id; repeat for more
|
|
9
|
-
person:<email> someone else, by email address; repeat for more
|
|
10
|
-
Without a time word: this week. Without calendar:, every calendar that syncs.`;
|
|
2
|
+
import { dayBoundary, queryError, queryFieldsOf, zonedMoment, } from "@wirecat/cli-base/query";
|
|
3
|
+
import { LIST_QUERY_GUIDE, listQuery, nativeTermLists, typedQuery } from "../cli/list-query.js";
|
|
4
|
+
import { calendarBusy } from "./cli/answers.js";
|
|
5
|
+
import { calendarIn, plusDays, timeWindow } from "./reads.js";
|
|
11
6
|
/** Google answers for 50 calendars at most (`calendarExpansionMax`, reference/freebusy/query). */
|
|
12
7
|
const MOST = 50;
|
|
13
8
|
const EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/u;
|
|
@@ -15,41 +10,68 @@ const HOURS = /^([01]\d|2[0-3]):([0-5]\d)-([01]\d|2[0-4]):([0-5]\d)$/u;
|
|
|
15
10
|
const MINUTE = 60_000;
|
|
16
11
|
const localDate = (at, zone) => new Intl.DateTimeFormat("en-CA", { timeZone: zone, year: "numeric", month: "2-digit", day: "2-digit" }).format(at);
|
|
17
12
|
const span = (text) => ({ start: 0, end: text.length });
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
13
|
+
const BUSY = "calendars busy";
|
|
14
|
+
const bound = (field, side, example) => (node) => {
|
|
15
|
+
const open = node.operator === "range" && (side === "lower" ? node.upper === "*" : node.value === "*");
|
|
16
|
+
if (!open)
|
|
17
|
+
queryError("query.unsupported-operator", node.span, `${field} takes only ${side === "lower" ? "the window's start" : "the window's end"}, such as ${example}`, { command: BUSY, guide: LIST_QUERY_GUIDE });
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* `calendars busy`'s query: the window by the answer's own `start` and `end`, and whom to ask. `calendar` and
|
|
21
|
+
* `person` repeat, and every one named is asked: the free stretches are those they all share.
|
|
22
|
+
*/
|
|
23
|
+
export const BUSY_QUERY = {
|
|
24
|
+
entity: "calendar_busy",
|
|
25
|
+
backend: "native",
|
|
26
|
+
fields: queryFieldsOf(calendarBusy, {
|
|
27
|
+
native: ["start", "end", "calendar", "person"],
|
|
28
|
+
relations: {
|
|
29
|
+
calendar: {
|
|
30
|
+
type: "kw",
|
|
31
|
+
example: "calendar:3",
|
|
32
|
+
relation: "one of the account's calendars, by its id or the provider's id; repeat for more",
|
|
33
|
+
},
|
|
34
|
+
person: { type: "kw", example: "person:ana@example.com", relation: "someone else, by address; repeat for more" },
|
|
35
|
+
},
|
|
36
|
+
examples: { start: "start>=2026-11-02", end: "end<=2026-11-06" },
|
|
37
|
+
validate: {
|
|
38
|
+
start: bound("start", "lower", "start>=2026-11-02"),
|
|
39
|
+
end: bound("end", "upper", "end<=2026-11-06"),
|
|
40
|
+
},
|
|
41
|
+
}).map((field) => field.native
|
|
42
|
+
? { ...field, operators: field.type === "time" ? ["range"] : ["term", "phrase"] }
|
|
43
|
+
: field),
|
|
44
|
+
};
|
|
45
|
+
export const BUSY_QUERY_NOTE = "\nWithout start: from the start of today, in your time zone. Without end: seven days after the start. " +
|
|
46
|
+
"Without calendar: every calendar that syncs.";
|
|
47
|
+
export const busyQuery = async (query, context) => {
|
|
48
|
+
const { zone, now } = context;
|
|
49
|
+
const today = localDate(now, zone);
|
|
21
50
|
let from;
|
|
22
51
|
let to;
|
|
23
52
|
const calendars = [];
|
|
24
53
|
const people = new Set();
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
throw new CliError("validation_error", `${value} is not an email address`);
|
|
44
|
-
people.add(email);
|
|
54
|
+
const terms = nativeTermLists(listQuery(typedQuery(query), BUSY_QUERY, BUSY), BUSY, ["calendar", "person"]);
|
|
55
|
+
for (const [field, nodes] of terms)
|
|
56
|
+
for (const node of nodes) {
|
|
57
|
+
if (field === "start")
|
|
58
|
+
from = timeWindow(node, zone, now).lower;
|
|
59
|
+
else if (field === "end")
|
|
60
|
+
to = timeWindow(node, zone, now).upper;
|
|
61
|
+
else if (field === "calendar") {
|
|
62
|
+
const calendar = await calendarIn(context.store, context.accountId, node.value);
|
|
63
|
+
if (!calendars.some(({ id }) => id === calendar.id))
|
|
64
|
+
calendars.push(calendar);
|
|
65
|
+
}
|
|
66
|
+
else if (field === "person") {
|
|
67
|
+
const email = node.value.trim().toLowerCase();
|
|
68
|
+
if (!EMAIL.test(email))
|
|
69
|
+
throw new CliError("validation_error", `${node.value} is not an email address`);
|
|
70
|
+
people.add(email);
|
|
71
|
+
}
|
|
45
72
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
reason: "query.unknown_word",
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
from ??= day(today);
|
|
52
|
-
to ??= from === day(today) ? day(plusDays(today, 7)) : from + 7 * 86_400_000;
|
|
73
|
+
from ??= dayBoundary(today, zone, false, span(today));
|
|
74
|
+
to ??= from + 7 * 86_400_000;
|
|
53
75
|
if (to <= from)
|
|
54
76
|
throw new CliError("validation_error", "the window ends before it starts");
|
|
55
77
|
const asked = calendars.length
|
|
@@ -192,7 +192,7 @@ const ANSWERS = {
|
|
|
192
192
|
"calendars unsync": { name: "calendar", schema: calendar },
|
|
193
193
|
"calendars busy": { name: "calendar_busy", schema: calendarBusy },
|
|
194
194
|
sync: { name: "calendar_sync", schema: calendarSync },
|
|
195
|
-
status: { name: "calendar_sync_progress", schema: calendarSyncProgress },
|
|
195
|
+
"store status": { name: "calendar_sync_progress", schema: calendarSyncProgress },
|
|
196
196
|
"events list": { name: "event" },
|
|
197
197
|
"events show": { name: "calendar_event", schema: calendarEvent },
|
|
198
198
|
"events create": { name: "calendar_event_write", schema: calendarEventWrite },
|
|
@@ -200,7 +200,7 @@ const ANSWERS = {
|
|
|
200
200
|
"events delete": { name: "calendar_event_write", schema: calendarEventWrite },
|
|
201
201
|
"events move": { name: "calendar_event_write", schema: calendarEventWrite },
|
|
202
202
|
"events respond": { name: "calendar_event_write", schema: calendarEventWrite },
|
|
203
|
-
search: { name: "calendar_search_hit", schema: calendarSearchHit },
|
|
203
|
+
"search events": { name: "calendar_search_hit", schema: calendarSearchHit },
|
|
204
204
|
"people list": { name: "calendar_attendee", schema: calendarAttendee },
|
|
205
205
|
"people link": { name: "calendar_linked_person", schema: calendarLinkedPerson },
|
|
206
206
|
"links review": { name: "calendar_link_review", schema: calendarLinkReview },
|
|
@@ -9,8 +9,8 @@ import { terminalAsker } from "../../cli/messenger/ask.js";
|
|
|
9
9
|
import { lineOf } from "../../cli/steps.js";
|
|
10
10
|
import { searchEvents } from "../../services/events-search.js";
|
|
11
11
|
import { linkPeople } from "../../services/people.js";
|
|
12
|
-
import {
|
|
13
|
-
import { CALENDAR_UNTRUSTED_FIELDS, calendarIn, calendarItem,
|
|
12
|
+
import { BUSY_QUERY, BUSY_QUERY_NOTE, busyAnswer, busyQuery, workingHoursOf } from "../busy.js";
|
|
13
|
+
import { CALENDAR_UNTRUSTED_FIELDS, calendarIn, calendarItem, EVENTS_QUERY, EVENTS_QUERY_NOTE, eventAnswer, eventIdOf, eventOf, eventsAnswer, eventsFilter, peopleAnswer, resolveAnswer, reviewsAnswer, weekFilter, } from "../reads.js";
|
|
14
14
|
import { REPEAT_HELP } from "../recurrence.js";
|
|
15
15
|
import { serverSearchAnswer } from "../server-search.js";
|
|
16
16
|
import { calendarStatus, syncCalendars } from "../sync.js";
|
|
@@ -99,11 +99,9 @@ const addCalendarCommandsDefinition = (program, deps) => {
|
|
|
99
99
|
print(calendarItem({ ...calendar, synced }), command);
|
|
100
100
|
});
|
|
101
101
|
if (deps.freeBusy)
|
|
102
|
-
example(annotate(calendars.command("busy"), { effects: REMOTE_READ, offline: false })
|
|
103
|
-
|
|
104
|
-
"
|
|
105
|
-
.argument("[words...]", "today, week, from:<date>, to:<date>, calendar:<id>, person:<email>")
|
|
106
|
-
.addHelpText("after", BUSY_QUERY_HELP), "calendars busy week person:ana@example.com --json", "calendars busy from:2026-11-02 to:2026-11-06 person:ana@example.com --duration-minutes 30 --hours 10:00-16:00")
|
|
102
|
+
example(withQuery(annotate(calendars.command("busy"), { effects: REMOTE_READ, offline: false }).description("when calendars and people are taken, asked of the provider and not stored; with --duration-minutes, " +
|
|
103
|
+
"the free stretches they share"), BUSY_QUERY).addHelpText("after", BUSY_QUERY_NOTE), "calendars busy person:ana@example.com --json", "calendars busy start>=2026-11-02 end<=2026-11-06 person:ana@example.com --duration-minutes 30 " +
|
|
104
|
+
"--hours 10:00-16:00")
|
|
107
105
|
.option("--duration-minutes <minutes>", "find free stretches at least this long, 5–1440", minutes)
|
|
108
106
|
.option("--hours <from-to>", "working hours in your time zone, Monday to Friday, with --duration-minutes", "09:00-17:00")
|
|
109
107
|
.action(async (words, opts, command) => {
|
|
@@ -162,7 +160,7 @@ const addCalendarCommandsDefinition = (program, deps) => {
|
|
|
162
160
|
},
|
|
163
161
|
]
|
|
164
162
|
: undefined;
|
|
165
|
-
const progressAt = lineOf(command, "status");
|
|
163
|
+
const progressAt = lineOf(command, "store status");
|
|
166
164
|
report.hint = stuck
|
|
167
165
|
? `this run moved nothing on: raise --timeout or --max-pages, then run sync again; see progress: ${progressAt}`
|
|
168
166
|
: report.state === "partial"
|
|
@@ -193,17 +191,17 @@ const addCalendarCommandsDefinition = (program, deps) => {
|
|
|
193
191
|
});
|
|
194
192
|
print({ ...report, ...(embeddings === undefined ? {} : { embeddings }), ...(next ? { next } : {}) }, command);
|
|
195
193
|
});
|
|
196
|
-
|
|
194
|
+
const store = annotate(program.command("store").description("the local store of calendar events"), {
|
|
195
|
+
effects: GROUP,
|
|
196
|
+
});
|
|
197
|
+
example(annotate(store.command("status"), { effects: LOCAL_READ }).description("how far each synced calendar's sync is: entries done, partial or complete, and when it last moved"), "store status --json").action(async (_opts, command) => {
|
|
197
198
|
print(await calendarStatus(deps.store, deps.accountId, {
|
|
198
199
|
profile: deps.profile ?? `account ${deps.accountId}`,
|
|
199
200
|
now: now(),
|
|
200
201
|
}), command);
|
|
201
202
|
});
|
|
202
203
|
const events = annotate(program.command("events").description("the account's stored events"), { effects: GROUP });
|
|
203
|
-
example(annotate(events.command("list"), { effects: LOCAL_READ })
|
|
204
|
-
.description("list stored events, earliest first")
|
|
205
|
-
.argument("[words...]", "today, week, from:<date>, to:<date>, person:<who>, calendar:<id>")
|
|
206
|
-
.addHelpText("after", EVENTS_QUERY_HELP), "events list week --json", "events list from:2026-11-02 to:2026-11-06 person:ana@example.com")
|
|
204
|
+
example(withQuery(annotate(events.command("list"), { effects: LOCAL_READ }).description("list stored events, earliest first"), EVENTS_QUERY).addHelpText("after", EVENTS_QUERY_NOTE), "events list starts_at>=2026-11-02 starts_at<2026-11-07 --json", "events list ends_at>=today person:ana@example.com")
|
|
207
205
|
.option("--limit <n>", "events at most, 1–1000", integer, 100)
|
|
208
206
|
.action(async (words, opts, command) => {
|
|
209
207
|
const filter = await eventsFilter(words, {
|
|
@@ -221,12 +219,15 @@ const addCalendarCommandsDefinition = (program, deps) => {
|
|
|
221
219
|
const shown = await eventAnswer(await eventOf(deps.store, deps.accountId, eventIdOf(reference)), personOfEmail);
|
|
222
220
|
print({ ...shown, untrusted: CALENDAR_UNTRUSTED_FIELDS }, command);
|
|
223
221
|
});
|
|
224
|
-
const
|
|
222
|
+
const searches = annotate(program.command("search").description("find stored things by text: events"), {
|
|
223
|
+
effects: GROUP,
|
|
224
|
+
});
|
|
225
|
+
const search = example(annotate(searches.command("events"), { effects: deps.serverSearch ? LOCAL_OR_REMOTE_READ : LOCAL_READ })
|
|
225
226
|
.description(deps.serverSearch
|
|
226
227
|
? "find stored events by words, and by meaning when its model is downloaded; with --backend server, ask " +
|
|
227
228
|
"the provider instead"
|
|
228
229
|
: "find stored events by words, and by meaning when its model is downloaded")
|
|
229
|
-
.argument("<
|
|
230
|
+
.argument("<query...>", "what to find in titles, descriptions, places and attendees"), "search events roadmap planning --json", ...(deps.serverSearch ? ["search events --backend server quarterly review"] : []))
|
|
230
231
|
.option("--limit <n>", "events per page", integer, 20)
|
|
231
232
|
.option("--cursor <cursor>", "continue from a page's next_offset", (text) => (text === "0" ? 0 : integer(text)), 0)
|
|
232
233
|
.option("--exact", "every word as written, no word stems")
|
|
@@ -238,16 +239,16 @@ const addCalendarCommandsDefinition = (program, deps) => {
|
|
|
238
239
|
.default("archive"));
|
|
239
240
|
search.action(async (words, opts, command) => {
|
|
240
241
|
if (opts.backend === "both")
|
|
241
|
-
throw new CliError("validation_error", "search --backend both is not offered: the store's search and the provider's rank and page events " +
|
|
242
|
+
throw new CliError("validation_error", "search events --backend both is not offered: the store's search and the provider's rank and page events " +
|
|
242
243
|
"differently, so one merged list would have no single order or next page; run --backend archive and " +
|
|
243
244
|
"--backend server", { reason: "search.one-backend" });
|
|
244
245
|
if (opts.backend === "server") {
|
|
245
246
|
if (command.optsWithGlobals().offline)
|
|
246
|
-
throw new CliError("permission_error", "search --backend server asks the provider; leave out --offline", {
|
|
247
|
+
throw new CliError("permission_error", "search events --backend server asks the provider; leave out --offline", {
|
|
247
248
|
reason: "search.offline",
|
|
248
249
|
});
|
|
249
250
|
if (opts.exact || opts.cursor || opts.newest)
|
|
250
|
-
throw new CliError("validation_error", "search --backend server takes neither --exact, --cursor nor --newest: the provider ranks and pages it", { reason: "search.server-options" });
|
|
251
|
+
throw new CliError("validation_error", "search events --backend server takes neither --exact, --cursor nor --newest: the provider ranks and pages it", { reason: "search.server-options" });
|
|
251
252
|
const found = await serverSearchAnswer(source(), deps.store, {
|
|
252
253
|
accountId: deps.accountId,
|
|
253
254
|
account: deps.account,
|
|
@@ -276,9 +277,7 @@ const addCalendarCommandsDefinition = (program, deps) => {
|
|
|
276
277
|
withQuery(example(annotate(people.command("list"), { effects: LOCAL_READ }).description("one event's attendees, or everyone attending this week's events"), "people list --json", "people list event:42"), PEOPLE_QUERY).action(async (words, _opts, command) => {
|
|
277
278
|
const reference = queryTerms(words, PEOPLE_QUERY, "people list").get("event");
|
|
278
279
|
const event = reference === undefined ? undefined : eventIdOf(reference);
|
|
279
|
-
const filter = event === undefined
|
|
280
|
-
? await eventsFilter(["week"], { store: deps.store, accountId: deps.accountId, zone: zone(), now: now() })
|
|
281
|
-
: undefined;
|
|
280
|
+
const filter = event === undefined ? weekFilter(deps.accountId, zone(), now()) : undefined;
|
|
282
281
|
const people = await peopleAnswer(deps.store, deps.accountId, {
|
|
283
282
|
...(event === undefined ? {} : { event }),
|
|
284
283
|
...(filter ? { filter } : {}),
|
|
@@ -10,20 +10,20 @@ export declare const calendarTools: ({
|
|
|
10
10
|
inputSchema: {
|
|
11
11
|
type: string;
|
|
12
12
|
properties: {
|
|
13
|
-
limit?: undefined;
|
|
14
13
|
account_id: {
|
|
15
14
|
type: string;
|
|
16
15
|
minimum: number;
|
|
17
16
|
};
|
|
18
|
-
|
|
17
|
+
query?: undefined;
|
|
19
18
|
timezone?: undefined;
|
|
19
|
+
limit?: undefined;
|
|
20
20
|
event?: undefined;
|
|
21
21
|
} | {
|
|
22
22
|
account_id: {
|
|
23
23
|
type: string;
|
|
24
24
|
minimum: number;
|
|
25
25
|
};
|
|
26
|
-
|
|
26
|
+
query: {
|
|
27
27
|
type: string;
|
|
28
28
|
minLength: number;
|
|
29
29
|
};
|
|
@@ -38,9 +38,9 @@ export declare const calendarTools: ({
|
|
|
38
38
|
};
|
|
39
39
|
event?: undefined;
|
|
40
40
|
} | {
|
|
41
|
-
|
|
42
|
-
words?: undefined;
|
|
41
|
+
query?: undefined;
|
|
43
42
|
timezone?: undefined;
|
|
43
|
+
limit?: undefined;
|
|
44
44
|
event: {
|
|
45
45
|
type: string;
|
|
46
46
|
minLength: number;
|
|
@@ -50,9 +50,9 @@ export declare const calendarTools: ({
|
|
|
50
50
|
minimum: number;
|
|
51
51
|
};
|
|
52
52
|
} | {
|
|
53
|
-
|
|
54
|
-
words?: undefined;
|
|
53
|
+
query?: undefined;
|
|
55
54
|
timezone?: undefined;
|
|
55
|
+
limit?: undefined;
|
|
56
56
|
event?: undefined;
|
|
57
57
|
account_id?: undefined;
|
|
58
58
|
};
|
|
@@ -71,20 +71,20 @@ export declare const calendarTools: ({
|
|
|
71
71
|
inputSchema: {
|
|
72
72
|
type: string;
|
|
73
73
|
properties: {
|
|
74
|
-
limit?: undefined;
|
|
75
74
|
account_id: {
|
|
76
75
|
type: string;
|
|
77
76
|
minimum: number;
|
|
78
77
|
};
|
|
79
|
-
|
|
78
|
+
query?: undefined;
|
|
80
79
|
timezone?: undefined;
|
|
80
|
+
limit?: undefined;
|
|
81
81
|
event?: undefined;
|
|
82
82
|
} | {
|
|
83
83
|
account_id: {
|
|
84
84
|
type: string;
|
|
85
85
|
minimum: number;
|
|
86
86
|
};
|
|
87
|
-
|
|
87
|
+
query: {
|
|
88
88
|
type: string;
|
|
89
89
|
minLength: number;
|
|
90
90
|
};
|
|
@@ -99,9 +99,9 @@ export declare const calendarTools: ({
|
|
|
99
99
|
};
|
|
100
100
|
event?: undefined;
|
|
101
101
|
} | {
|
|
102
|
-
|
|
103
|
-
words?: undefined;
|
|
102
|
+
query?: undefined;
|
|
104
103
|
timezone?: undefined;
|
|
104
|
+
limit?: undefined;
|
|
105
105
|
event: {
|
|
106
106
|
type: string;
|
|
107
107
|
minLength: number;
|
|
@@ -111,9 +111,9 @@ export declare const calendarTools: ({
|
|
|
111
111
|
minimum: number;
|
|
112
112
|
};
|
|
113
113
|
} | {
|
|
114
|
-
|
|
115
|
-
words?: undefined;
|
|
114
|
+
query?: undefined;
|
|
116
115
|
timezone?: undefined;
|
|
116
|
+
limit?: undefined;
|
|
117
117
|
event?: undefined;
|
|
118
118
|
account_id?: undefined;
|
|
119
119
|
};
|
|
@@ -132,20 +132,20 @@ export declare const calendarTools: ({
|
|
|
132
132
|
inputSchema: {
|
|
133
133
|
type: string;
|
|
134
134
|
properties: {
|
|
135
|
-
limit?: undefined;
|
|
136
135
|
account_id: {
|
|
137
136
|
type: string;
|
|
138
137
|
minimum: number;
|
|
139
138
|
};
|
|
140
|
-
|
|
139
|
+
query?: undefined;
|
|
141
140
|
timezone?: undefined;
|
|
141
|
+
limit?: undefined;
|
|
142
142
|
event?: undefined;
|
|
143
143
|
} | {
|
|
144
144
|
account_id: {
|
|
145
145
|
type: string;
|
|
146
146
|
minimum: number;
|
|
147
147
|
};
|
|
148
|
-
|
|
148
|
+
query: {
|
|
149
149
|
type: string;
|
|
150
150
|
minLength: number;
|
|
151
151
|
};
|
|
@@ -160,9 +160,9 @@ export declare const calendarTools: ({
|
|
|
160
160
|
};
|
|
161
161
|
event?: undefined;
|
|
162
162
|
} | {
|
|
163
|
-
|
|
164
|
-
words?: undefined;
|
|
163
|
+
query?: undefined;
|
|
165
164
|
timezone?: undefined;
|
|
165
|
+
limit?: undefined;
|
|
166
166
|
event: {
|
|
167
167
|
type: string;
|
|
168
168
|
minLength: number;
|
|
@@ -172,9 +172,9 @@ export declare const calendarTools: ({
|
|
|
172
172
|
minimum: number;
|
|
173
173
|
};
|
|
174
174
|
} | {
|
|
175
|
-
|
|
176
|
-
words?: undefined;
|
|
175
|
+
query?: undefined;
|
|
177
176
|
timezone?: undefined;
|
|
177
|
+
limit?: undefined;
|
|
178
178
|
event?: undefined;
|
|
179
179
|
account_id?: undefined;
|
|
180
180
|
};
|
|
@@ -193,20 +193,20 @@ export declare const calendarTools: ({
|
|
|
193
193
|
inputSchema: {
|
|
194
194
|
type: string;
|
|
195
195
|
properties: {
|
|
196
|
-
limit?: undefined;
|
|
197
196
|
account_id: {
|
|
198
197
|
type: string;
|
|
199
198
|
minimum: number;
|
|
200
199
|
};
|
|
201
|
-
|
|
200
|
+
query?: undefined;
|
|
202
201
|
timezone?: undefined;
|
|
202
|
+
limit?: undefined;
|
|
203
203
|
event?: undefined;
|
|
204
204
|
} | {
|
|
205
205
|
account_id: {
|
|
206
206
|
type: string;
|
|
207
207
|
minimum: number;
|
|
208
208
|
};
|
|
209
|
-
|
|
209
|
+
query: {
|
|
210
210
|
type: string;
|
|
211
211
|
minLength: number;
|
|
212
212
|
};
|
|
@@ -221,9 +221,9 @@ export declare const calendarTools: ({
|
|
|
221
221
|
};
|
|
222
222
|
event?: undefined;
|
|
223
223
|
} | {
|
|
224
|
-
|
|
225
|
-
words?: undefined;
|
|
224
|
+
query?: undefined;
|
|
226
225
|
timezone?: undefined;
|
|
226
|
+
limit?: undefined;
|
|
227
227
|
event: {
|
|
228
228
|
type: string;
|
|
229
229
|
minLength: number;
|
|
@@ -233,9 +233,9 @@ export declare const calendarTools: ({
|
|
|
233
233
|
minimum: number;
|
|
234
234
|
};
|
|
235
235
|
} | {
|
|
236
|
-
|
|
237
|
-
words?: undefined;
|
|
236
|
+
query?: undefined;
|
|
238
237
|
timezone?: undefined;
|
|
238
|
+
limit?: undefined;
|
|
239
239
|
event?: undefined;
|
|
240
240
|
account_id?: undefined;
|
|
241
241
|
};
|
|
@@ -19,7 +19,7 @@ export const calendarTools = [
|
|
|
19
19
|
required: [],
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
|
-
name: "
|
|
22
|
+
name: "store_status",
|
|
23
23
|
description: "how far each synced calendar's sync is: done, state partial or complete, and cursor_age_ms, which grows while " +
|
|
24
24
|
"nothing moves it",
|
|
25
25
|
properties: { account_id: integer },
|
|
@@ -27,10 +27,10 @@ export const calendarTools = [
|
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
29
|
name: "events_list",
|
|
30
|
-
description: "stored events, earliest first; `
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
properties: { account_id: integer,
|
|
30
|
+
description: "stored events, earliest first; `query` takes field terms joined by AND: starts_at (>=, <, or " +
|
|
31
|
+
"[a TO b}), ends_at>=, changed_at>= (events new, moved or cancelled since, each with change and was), " +
|
|
32
|
+
"calendar:<id>; a date in the zone given, UTC when absent; without starts_at or ends_at, from today on",
|
|
33
|
+
properties: { account_id: integer, query: text, timezone: text, limit: { ...integer, maximum: 1000 } },
|
|
34
34
|
required: [],
|
|
35
35
|
},
|
|
36
36
|
{
|
|
@@ -163,7 +163,7 @@ const argumentsOf = (name, input) => {
|
|
|
163
163
|
(typeof value !== "number" || !Number.isSafeInteger(value) || value < 1))
|
|
164
164
|
throw new CliError("validation_error", `${key} must be a positive integer`);
|
|
165
165
|
else if ([
|
|
166
|
-
"
|
|
166
|
+
"query",
|
|
167
167
|
"timezone",
|
|
168
168
|
"event",
|
|
169
169
|
"calendar",
|
|
@@ -225,13 +225,12 @@ export const callCalendarTool = async (store, name, input, { accountIds, failure
|
|
|
225
225
|
case "calendars_list":
|
|
226
226
|
answer = listed((await Promise.all(accounts.map((id) => store.calendars(id)))).flat().map(calendarItem));
|
|
227
227
|
break;
|
|
228
|
-
case "
|
|
228
|
+
case "store_status":
|
|
229
229
|
answer = listed((await Promise.all(accounts.map((id) => calendarStatus(store, id, { profile: profileOf(id), now }).then(({ items }) => items)))).flat());
|
|
230
230
|
break;
|
|
231
231
|
case "events_list": {
|
|
232
|
-
const words = typeof args.words === "string" ? args.words.trim().split(/\s+/u) : [];
|
|
233
232
|
const limit = args.limit ?? 100;
|
|
234
|
-
const lists = await Promise.all(accounts.map(async (accountId) => eventsAnswer(store, await eventsFilter(
|
|
233
|
+
const lists = await Promise.all(accounts.map(async (accountId) => eventsAnswer(store, await eventsFilter(args.query, {
|
|
235
234
|
store,
|
|
236
235
|
accountId,
|
|
237
236
|
zone: args.timezone ?? "UTC",
|
package/dist/calendar/reads.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type Predicate, type QueryRegistry } from "@wirecat/cli-base/query";
|
|
2
|
+
import { type QueryWords } from "../cli/list-query.js";
|
|
1
3
|
import { type EventDetails, type EventFilter, type EventStore, type StoredCalendar, type StoredEntry } from "../meetings/index.js";
|
|
2
4
|
/** Who looks a person up: the shared store's `people.personOf`, for an attendee's `email` contact. */
|
|
3
5
|
export type PersonOfEmail = (email: string) => Promise<string | null>;
|
|
@@ -17,18 +19,27 @@ export declare const calendarItem: (one: StoredCalendar) => {
|
|
|
17
19
|
export declare const calendarIn: (store: EventStore, accountId: number, reference: string) => Promise<StoredCalendar>;
|
|
18
20
|
export declare const eventIdOf: (reference: string | number) => number;
|
|
19
21
|
export declare const plusDays: (date: string, days: number) => string;
|
|
20
|
-
|
|
22
|
+
/** `events list`'s query. The store answers `ends_at` and `changed_at` from below only; `starts_at` either way. */
|
|
23
|
+
export declare const EVENTS_QUERY: QueryRegistry;
|
|
24
|
+
export declare const EVENTS_QUERY_NOTE: string;
|
|
25
|
+
/** A time term as epoch ms: from `lower` on, and before `upper`. */
|
|
26
|
+
export declare const timeWindow: (node: Predicate, zone: string, now: number) => {
|
|
27
|
+
lower?: number;
|
|
28
|
+
upper?: number;
|
|
29
|
+
};
|
|
21
30
|
/**
|
|
22
|
-
* `events list`
|
|
23
|
-
* store; one nobody knows matches no event.
|
|
31
|
+
* `events list`'s query into a store filter, for one account. A person named by address is found through the
|
|
32
|
+
* shared store; one nobody knows matches no event.
|
|
24
33
|
*/
|
|
25
|
-
export declare const eventsFilter: (
|
|
34
|
+
export declare const eventsFilter: (query: QueryWords, context: {
|
|
26
35
|
store: EventStore;
|
|
27
36
|
accountId: number;
|
|
28
37
|
zone: string;
|
|
29
38
|
now: number;
|
|
30
39
|
personOf?: (reference: string) => Promise<number | null>;
|
|
31
40
|
}) => Promise<EventFilter | null>;
|
|
41
|
+
/** Events that end after the start of today and start within seven days, in `zone`. */
|
|
42
|
+
export declare const weekFilter: (accountId: number, zone: string, now: number) => EventFilter;
|
|
32
43
|
/** An event the account holds, with only its copies, or `not_found`: another account's event is not shown. */
|
|
33
44
|
export declare const eventOf: (store: EventStore, accountId: number, id: number) => Promise<EventDetails>;
|
|
34
45
|
export declare const entryItem: (entry: StoredEntry) => {
|