@wirecat/cli-core 0.462.0 → 0.464.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 +106 -0
- package/dist/background/after-upgrade.d.ts +51 -0
- package/dist/background/after-upgrade.js +245 -0
- package/dist/background/budget.d.ts +37 -0
- package/dist/background/budget.js +48 -0
- package/dist/background/index.d.ts +1 -0
- package/dist/background/index.js +1 -0
- package/dist/calendar/busy.d.ts +68 -0
- package/dist/calendar/busy.js +148 -0
- package/dist/calendar/cli/index.d.ts +7 -0
- package/dist/calendar/cli/index.js +94 -12
- package/dist/calendar/index.d.ts +3 -2
- package/dist/calendar/index.js +2 -1
- package/dist/calendar/mcp/index.d.ts +55 -13
- package/dist/calendar/mcp/index.js +19 -4
- package/dist/calendar/recurrence.d.ts +10 -0
- package/dist/calendar/recurrence.js +65 -0
- package/dist/calendar/source.d.ts +19 -0
- package/dist/calendar/sync.d.ts +86 -7
- package/dist/calendar/sync.js +271 -105
- package/dist/calendar/testing/source-contract.js +14 -0
- package/dist/calendar/testing/source.d.ts +6 -0
- package/dist/calendar/testing/source.js +30 -2
- package/dist/calendar/testing/sync-contract.d.ts +2 -1
- package/dist/calendar/testing/sync-contract.js +80 -5
- package/dist/calendar/testing/writer-contract.d.ts +2 -1
- package/dist/calendar/testing/writer-contract.js +46 -2
- package/dist/calendar/testing/writer.js +1 -1
- package/dist/calendar/writer.d.ts +7 -2
- package/dist/calendar/writes.d.ts +12 -2
- package/dist/calendar/writes.js +32 -6
- package/dist/cli/bot/mcp.js +11 -3
- package/dist/cli/context.d.ts +8 -0
- package/dist/cli/context.js +15 -0
- package/dist/cli/execution.d.ts +7 -0
- package/dist/cli/execution.js +7 -0
- package/dist/cli/guide.js +1 -1
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/mcp-server-command.d.ts +9 -4
- package/dist/cli/mcp-server-command.js +32 -25
- package/dist/cli/meeting-preparation-answer.d.ts +1 -0
- package/dist/cli/meeting-preparation-projects.d.ts +1 -0
- package/dist/cli/meeting-preparation-read.d.ts +1 -0
- package/dist/cli/messenger/chat-answers.js +1 -0
- package/dist/cli/messenger/mcp-command.d.ts +3 -4
- package/dist/cli/messenger/mcp-command.js +42 -26
- package/dist/cli/messenger/search-answers.js +29 -4
- package/dist/cli/messenger/store-maintenance-command.d.ts +1 -1
- package/dist/cli/program.js +15 -5
- package/dist/cli/upgrade-command.d.ts +7 -2
- package/dist/cli/upgrade-command.js +65 -9
- package/dist/entities/fields.d.ts +2 -0
- package/dist/entities/fields.js +2 -0
- package/dist/entities/hit.d.ts +1 -1
- package/dist/entities/hit.js +3 -3
- package/dist/entities/index.d.ts +10 -800
- package/dist/entities/index.js +37 -31
- package/dist/entities/meeting-preparation.d.ts +1 -0
- package/dist/entities/topic.d.ts +1 -0
- package/dist/entities/topic.js +2 -1
- package/dist/errors/reasons.js +4 -0
- package/dist/kit/mcp-stdio.d.ts +2 -2
- package/dist/kit/mcp-stdio.js +2 -2
- package/dist/mcp/http/activity.d.ts +1 -1
- package/dist/mcp/http/activity.js +1 -1
- package/dist/mcp/http/oauth.js +1 -1
- package/dist/mcp/http/state.d.ts +14 -0
- package/dist/mcp/http/state.js +16 -0
- package/dist/mcp/meeting-preparation.d.ts +1 -0
- package/dist/meetings/answers.d.ts +2 -0
- package/dist/meetings/answers.js +3 -0
- package/dist/meetings/mcp/search.d.ts +1 -0
- package/dist/meetings/project-evidence-answer.d.ts +1 -0
- package/dist/ports/app.d.ts +5 -0
- package/dist/services/package-upgrade.d.ts +9 -0
- package/dist/services/package-upgrade.js +8 -1
- package/package.json +3 -3
- package/parity.json +30 -22
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { CliError } from "@wirecat/cli-base";
|
|
2
|
+
import { dayBoundary, zonedMoment } from "@wirecat/cli-base/query";
|
|
3
|
+
import { calendarIn, plusDays } from "./reads.js";
|
|
4
|
+
export const BUSY_QUERY_HELP = `
|
|
5
|
+
Words, any order:
|
|
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.`;
|
|
11
|
+
/** Google answers for 50 calendars at most (`calendarExpansionMax`, reference/freebusy/query). */
|
|
12
|
+
const MOST = 50;
|
|
13
|
+
const EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/u;
|
|
14
|
+
const HOURS = /^([01]\d|2[0-3]):([0-5]\d)-([01]\d|2[0-4]):([0-5]\d)$/u;
|
|
15
|
+
const MINUTE = 60_000;
|
|
16
|
+
const localDate = (at, zone) => new Intl.DateTimeFormat("en-CA", { timeZone: zone, year: "numeric", month: "2-digit", day: "2-digit" }).format(at);
|
|
17
|
+
const span = (text) => ({ start: 0, end: text.length });
|
|
18
|
+
export const busyQuery = async (words, context) => {
|
|
19
|
+
const today = localDate(context.now, context.zone);
|
|
20
|
+
const day = (date) => dayBoundary(date, context.zone, false, span(date));
|
|
21
|
+
let from;
|
|
22
|
+
let to;
|
|
23
|
+
const calendars = [];
|
|
24
|
+
const people = new Set();
|
|
25
|
+
for (const word of words) {
|
|
26
|
+
const [, field, value = ""] = /^([a-z]+):(.+)$/u.exec(word) ?? [];
|
|
27
|
+
if (word === "today" || word === "week") {
|
|
28
|
+
from = day(today);
|
|
29
|
+
to = day(plusDays(today, word === "today" ? 1 : 7));
|
|
30
|
+
}
|
|
31
|
+
else if (field === "from")
|
|
32
|
+
from = zonedMoment(value, context.zone, span(value));
|
|
33
|
+
else if (field === "to")
|
|
34
|
+
to = zonedMoment(value, context.zone, span(value), { end: true }) + 1;
|
|
35
|
+
else if (field === "calendar") {
|
|
36
|
+
const calendar = await calendarIn(context.store, context.accountId, value);
|
|
37
|
+
if (!calendars.some(({ id }) => id === calendar.id))
|
|
38
|
+
calendars.push(calendar);
|
|
39
|
+
}
|
|
40
|
+
else if (field === "person") {
|
|
41
|
+
const email = value.trim().toLowerCase();
|
|
42
|
+
if (!EMAIL.test(email))
|
|
43
|
+
throw new CliError("validation_error", `${value} is not an email address`);
|
|
44
|
+
people.add(email);
|
|
45
|
+
}
|
|
46
|
+
else
|
|
47
|
+
throw new CliError("validation_error", `${word} is not a calendars busy word${BUSY_QUERY_HELP}`, {
|
|
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;
|
|
53
|
+
if (to <= from)
|
|
54
|
+
throw new CliError("validation_error", "the window ends before it starts");
|
|
55
|
+
const asked = calendars.length
|
|
56
|
+
? calendars
|
|
57
|
+
: (await context.store.calendars(context.accountId)).filter(({ synced, deletedAt }) => synced && !deletedAt);
|
|
58
|
+
if (asked.length === 0 && people.size === 0)
|
|
59
|
+
throw new CliError("validation_error", "no calendar syncs yet; name one with calendar:<id>, or a person:<email>", {
|
|
60
|
+
reason: "busy.no-calendars",
|
|
61
|
+
});
|
|
62
|
+
if (asked.length + people.size > MOST)
|
|
63
|
+
throw new CliError("validation_error", `at most ${MOST} calendars and people at once`);
|
|
64
|
+
return { window: { from, to }, calendars: asked, people: [...people] };
|
|
65
|
+
};
|
|
66
|
+
export const workingHoursOf = (text) => {
|
|
67
|
+
const match = HOURS.exec(text.trim());
|
|
68
|
+
const [start, end] = match ? [Number(match[1]) * 60 + Number(match[2]), Number(match[3]) * 60 + Number(match[4])] : [];
|
|
69
|
+
if (start === undefined || end === undefined || end <= start || end > 24 * 60)
|
|
70
|
+
throw new CliError("validation_error", "working hours are HH:MM-HH:MM in your time zone, such as 09:00-17:00");
|
|
71
|
+
return { start, end };
|
|
72
|
+
};
|
|
73
|
+
const clock = (minutes) => `${String(Math.floor(minutes / 60)).padStart(2, "0")}:${String(minutes % 60).padStart(2, "0")}`;
|
|
74
|
+
/** Each weekday's working hours as moments in `zone`, so a day that changes its clock is still read in local time. */
|
|
75
|
+
const workingSpans = (window, hours, zone) => {
|
|
76
|
+
const spans = [];
|
|
77
|
+
const last = localDate(window.to - 1, zone);
|
|
78
|
+
for (let date = localDate(window.from, zone); date <= last; date = plusDays(date, 1)) {
|
|
79
|
+
const weekday = new Date(`${date}T12:00:00Z`).getUTCDay();
|
|
80
|
+
if (weekday === 0 || weekday === 6)
|
|
81
|
+
continue;
|
|
82
|
+
const at = (minutes) => minutes === 24 * 60
|
|
83
|
+
? dayBoundary(plusDays(date, 1), zone, false, span(date))
|
|
84
|
+
: zonedMoment(`${date}T${clock(minutes)}`, zone, span(date));
|
|
85
|
+
spans.push({ from: at(hours.start), to: at(hours.end) });
|
|
86
|
+
}
|
|
87
|
+
return spans;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* The stretches of at least `minutes` inside working hours on weekdays, after `now`, that no busy time touches.
|
|
91
|
+
* A stretch is returned whole; the caller picks where in it to put the meeting.
|
|
92
|
+
*/
|
|
93
|
+
export const freeSlots = (busy, window, options) => {
|
|
94
|
+
const taken = [...busy].sort((a, b) => a.from - b.from);
|
|
95
|
+
const slots = [];
|
|
96
|
+
for (const day of workingSpans(window, options.hours, options.zone)) {
|
|
97
|
+
let cursor = Math.max(day.from, window.from, options.now);
|
|
98
|
+
const end = Math.min(day.to, window.to);
|
|
99
|
+
for (const one of taken) {
|
|
100
|
+
if (one.to <= cursor || one.from >= end)
|
|
101
|
+
continue;
|
|
102
|
+
if (one.from - cursor >= options.minutes * MINUTE)
|
|
103
|
+
slots.push({ from: cursor, to: one.from });
|
|
104
|
+
cursor = Math.max(cursor, one.to);
|
|
105
|
+
}
|
|
106
|
+
if (end - cursor >= options.minutes * MINUTE)
|
|
107
|
+
slots.push({ from: cursor, to: end });
|
|
108
|
+
}
|
|
109
|
+
return slots;
|
|
110
|
+
};
|
|
111
|
+
const iso = (at) => new Date(at).toISOString();
|
|
112
|
+
const times = ({ from, to }) => ({ start: iso(from), end: iso(to) });
|
|
113
|
+
/**
|
|
114
|
+
* `calendars busy`: the provider's busy times for the query, and with `slot` the free stretches every calendar and
|
|
115
|
+
* person shares. Nothing is stored, and a person appears only by the address the caller gave.
|
|
116
|
+
*/
|
|
117
|
+
export const busyAnswer = async (source, query, options) => {
|
|
118
|
+
if (!source.freeBusy)
|
|
119
|
+
throw new CliError("configuration_error", "this calendar provider cannot tell busy times");
|
|
120
|
+
const answered = await source.freeBusy({
|
|
121
|
+
calendars: query.calendars.map(({ externalId }) => externalId),
|
|
122
|
+
people: query.people,
|
|
123
|
+
window: query.window,
|
|
124
|
+
});
|
|
125
|
+
const byId = new Map(answered.map((one) => [one.id, one]));
|
|
126
|
+
const missing = (id) => byId.get(id) ?? { id, busy: [], error: "notAnswered" };
|
|
127
|
+
const rows = [
|
|
128
|
+
...query.calendars.map((calendar) => ({ calendar: String(calendar.id), ...missing(calendar.externalId) })),
|
|
129
|
+
...query.people.map((person) => ({ person, ...missing(person) })),
|
|
130
|
+
];
|
|
131
|
+
const unknown = rows.filter(({ error }) => error !== undefined).map(({ id }) => id);
|
|
132
|
+
return {
|
|
133
|
+
...times(query.window),
|
|
134
|
+
timezone: options.zone,
|
|
135
|
+
calendars: rows.map(({ busy, error, ...row }) => ({
|
|
136
|
+
...row,
|
|
137
|
+
busy: busy.map(times),
|
|
138
|
+
...(error === undefined ? {} : { error }),
|
|
139
|
+
})),
|
|
140
|
+
...(options.slot
|
|
141
|
+
? {
|
|
142
|
+
slots: freeSlots(rows.flatMap(({ busy }) => busy), query.window, { ...options.slot, zone: options.zone, now: options.now }).map(times),
|
|
143
|
+
// Free here means free in every calendar the provider answered for: these were not.
|
|
144
|
+
...(unknown.length ? { unknown } : {}),
|
|
145
|
+
}
|
|
146
|
+
: {}),
|
|
147
|
+
};
|
|
148
|
+
};
|
|
@@ -9,10 +9,17 @@ export interface CalendarCommandDeps {
|
|
|
9
9
|
accountId: number;
|
|
10
10
|
/** The same account as the shared store names it, for search and people. */
|
|
11
11
|
account: AccountKey;
|
|
12
|
+
/** The profile's name, which `status` gives as each sync's `profile`. */
|
|
13
|
+
profile?: string;
|
|
12
14
|
/** The shared store: `search`, `people link` and the person of an attendee read it. */
|
|
13
15
|
messages?: MessageStore;
|
|
14
16
|
/** The provider; `sync` and `calendars list` read it. */
|
|
15
17
|
source?: CalendarSource;
|
|
18
|
+
/**
|
|
19
|
+
* The source answers `freeBusy`: mounts `calendars busy`. A flag rather than a look at `source`, which a CLI may
|
|
20
|
+
* build only once a profile is open.
|
|
21
|
+
*/
|
|
22
|
+
freeBusy?: boolean;
|
|
16
23
|
/** For a source with `expands: "local"`. */
|
|
17
24
|
expand?: CalendarExpander;
|
|
18
25
|
/** After `sync --embed`: embeds new events for search by meaning (`embedEvents`), as drive's `sync --embed`. */
|
|
@@ -2,16 +2,21 @@ import { CliError } from "@wirecat/cli-base";
|
|
|
2
2
|
import { annotate, sharedCommand } from "@wirecat/cli-base/commands";
|
|
3
3
|
import { timezoneOf } from "@wirecat/cli-base/query";
|
|
4
4
|
import { Argument, InvalidArgumentError } from "commander";
|
|
5
|
+
import { environmentOf, runBudgetOf } from "../../cli/context.js";
|
|
5
6
|
import { honoursDryRun } from "../../cli/dry-run-guard.js";
|
|
6
7
|
import { terminalAsker } from "../../cli/messenger/ask.js";
|
|
8
|
+
import { lineOf } from "../../cli/steps.js";
|
|
7
9
|
import { searchEvents } from "../../services/events-search.js";
|
|
8
10
|
import { linkPeople } from "../../services/people.js";
|
|
11
|
+
import { BUSY_QUERY_HELP, busyAnswer, busyQuery, workingHoursOf } from "../busy.js";
|
|
9
12
|
import { CALENDAR_UNTRUSTED_FIELDS, calendarIn, calendarItem, EVENTS_QUERY_HELP, eventAnswer, eventIdOf, eventOf, eventsAnswer, eventsFilter, peopleAnswer, resolveAnswer, reviewsAnswer, } from "../reads.js";
|
|
10
|
-
import {
|
|
13
|
+
import { REPEAT_HELP } from "../recurrence.js";
|
|
14
|
+
import { calendarStatus, syncCalendars } from "../sync.js";
|
|
11
15
|
import { createEvent, deleteEvent, EVENT_RESPONSES, respondToEvent, updateEvent, } from "../writes.js";
|
|
12
16
|
const LOCAL_READ = { remote: "none", local: "read" };
|
|
13
17
|
const LOCAL_WRITE = { remote: "none", local: "write" };
|
|
14
18
|
const FETCH = { remote: "read", local: "write" };
|
|
19
|
+
const REMOTE_READ = { remote: "read", local: "none" };
|
|
15
20
|
const REMOTE_WRITE = { remote: "write", local: "write" };
|
|
16
21
|
const GROUP = { remote: "none", local: "none" };
|
|
17
22
|
/** How many pieces one `sync --embed` embeds, as drive's. */
|
|
@@ -30,6 +35,12 @@ const integer = (text) => {
|
|
|
30
35
|
throw new InvalidArgumentError("expected a positive integer");
|
|
31
36
|
return Number(text);
|
|
32
37
|
};
|
|
38
|
+
const minutes = (text) => {
|
|
39
|
+
const value = integer(text);
|
|
40
|
+
if (value < 5 || value > 1440)
|
|
41
|
+
throw new InvalidArgumentError("expected 5 to 1440 minutes");
|
|
42
|
+
return value;
|
|
43
|
+
};
|
|
33
44
|
const addCalendarCommandsDefinition = (program, deps) => {
|
|
34
45
|
const now = () => deps.now?.() ?? Date.now();
|
|
35
46
|
const zone = () => timezoneOf(deps.timezone);
|
|
@@ -85,27 +96,89 @@ const addCalendarCommandsDefinition = (program, deps) => {
|
|
|
85
96
|
await deps.store.setCalendarSynced(calendar.id, synced, now());
|
|
86
97
|
print(calendarItem({ ...calendar, synced }), command);
|
|
87
98
|
});
|
|
88
|
-
|
|
89
|
-
.
|
|
99
|
+
if (deps.freeBusy)
|
|
100
|
+
output(example(annotate(calendars.command("busy"), { effects: REMOTE_READ, offline: false })
|
|
101
|
+
.description("when calendars and people are taken, asked of the provider and not stored; with --duration-minutes, " +
|
|
102
|
+
"the free stretches they share")
|
|
103
|
+
.argument("[words...]", "today, week, from:<date>, to:<date>, calendar:<id>, person:<email>")
|
|
104
|
+
.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"))
|
|
105
|
+
.option("--duration-minutes <minutes>", "find free stretches at least this long, 5–1440", minutes)
|
|
106
|
+
.option("--hours <from-to>", "working hours in your time zone, Monday to Friday, with --duration-minutes", "09:00-17:00")
|
|
107
|
+
.action(async (words, opts, command) => {
|
|
108
|
+
if (command.optsWithGlobals().offline)
|
|
109
|
+
throw new CliError("permission_error", "calendars busy asks the provider; leave out --offline", {
|
|
110
|
+
reason: "busy.offline",
|
|
111
|
+
});
|
|
112
|
+
const hours = workingHoursOf(opts.hours);
|
|
113
|
+
const query = await busyQuery(words, { store: deps.store, accountId: deps.accountId, zone: zone(), now: now() });
|
|
114
|
+
print(await busyAnswer(source(), query, {
|
|
115
|
+
zone: zone(),
|
|
116
|
+
now: now(),
|
|
117
|
+
...(opts.durationMinutes === undefined ? {} : { slot: { minutes: opts.durationMinutes, hours } }),
|
|
118
|
+
}), command);
|
|
119
|
+
});
|
|
120
|
+
output(example(annotate(program.command("sync"), {
|
|
121
|
+
effects: FETCH,
|
|
122
|
+
offline: false,
|
|
123
|
+
cooperative: true,
|
|
124
|
+
partial: true,
|
|
125
|
+
}).description("bring the synced calendars up to date: what changed since the last sync, or everything with --full; a run " +
|
|
126
|
+
"stops before its deadline or --max-pages and the next one goes on from there"), "sync", "sync --full --embed", "sync --timeout 2m"))
|
|
127
|
+
.option("--full", "list every synced calendar's window again, dropping a round in progress")
|
|
128
|
+
.option("--max-pages <pages>", "at most this many requests to the provider in this run; as many as fit the deadline if not given", integer)
|
|
90
129
|
.option("--embed", "also index new events for meaning search, with a model already downloaded")
|
|
91
130
|
.action(async (opts, command) => {
|
|
92
131
|
// The shared program has a global --full of its own, which claims the flag before this command sees it.
|
|
93
132
|
const full = command.optsWithGlobals().full === true;
|
|
133
|
+
const { signal } = environmentOf(command);
|
|
94
134
|
const report = await syncCalendars(source(), deps.store, {
|
|
95
135
|
accountId: deps.accountId,
|
|
96
136
|
now: now(),
|
|
137
|
+
budget: runBudgetOf(command, opts.maxPages),
|
|
138
|
+
...(signal ? { signal } : {}),
|
|
97
139
|
...(full ? { full: true } : {}),
|
|
98
140
|
...(deps.expand ? { expand: deps.expand } : {}),
|
|
99
141
|
});
|
|
100
142
|
if (opts.embed && !deps.embed)
|
|
101
143
|
throw new CliError("configuration_error", "this tool cannot embed events");
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
144
|
+
// A run its budget stopped has no time left for the model; the run that finishes the round embeds.
|
|
145
|
+
const embeddings = opts.embed && !report.stopped ? await deps.embed?.(EMBED_MAX) : undefined;
|
|
146
|
+
const timeout = command.optsWithGlobals().timeout;
|
|
147
|
+
// The same command again, never --full: that would drop the round this run leaves in progress. A run that moved
|
|
148
|
+
// nothing on would only repeat itself, so it gets no step an agent may run unasked.
|
|
149
|
+
const stuck = report.state === "partial" && report.progress.steps === 0 && !report.synced.some((one) => one.error);
|
|
150
|
+
const next = report.state === "partial" && !stuck && !report.synced.some((one) => one.error !== undefined)
|
|
151
|
+
? [
|
|
152
|
+
{
|
|
153
|
+
kind: "command",
|
|
154
|
+
step: "calendar.sync",
|
|
155
|
+
command: "sync",
|
|
156
|
+
args: Object.fromEntries(Object.entries({ max_pages: opts.maxPages, timeout, embed: opts.embed }).filter(([, value]) => value !== undefined)),
|
|
157
|
+
why: "the sync stopped before its end to answer in time; running it again goes on from where it stopped",
|
|
158
|
+
safe_to_auto_execute: true,
|
|
159
|
+
},
|
|
160
|
+
]
|
|
161
|
+
: undefined;
|
|
162
|
+
const progressAt = lineOf(command, "status");
|
|
163
|
+
report.hint = stuck
|
|
164
|
+
? `this run moved nothing on: raise --timeout or --max-pages, then run sync again; see progress: ${progressAt}`
|
|
165
|
+
: report.state === "partial"
|
|
166
|
+
? `run sync again to continue; see progress: ${progressAt}`
|
|
167
|
+
: report.hint;
|
|
168
|
+
if (report.hint === undefined)
|
|
169
|
+
delete report.hint;
|
|
170
|
+
print({ ...report, ...(embeddings === undefined ? {} : { embeddings }), ...(next ? { next } : {}) }, command);
|
|
171
|
+
if (report.synced.some((one) => one.error !== undefined))
|
|
105
172
|
throw new CliError("provider_error", "some calendars did not sync; run sync again to retry them", {
|
|
106
173
|
reason: "sync.partial",
|
|
107
174
|
});
|
|
108
175
|
});
|
|
176
|
+
output(example(annotate(program.command("status"), { effects: LOCAL_READ }).description("how far each synced calendar's sync is: entries done, partial or complete, and when it last moved"), "status --json")).action(async (_opts, command) => {
|
|
177
|
+
print(await calendarStatus(deps.store, deps.accountId, {
|
|
178
|
+
profile: deps.profile ?? `account ${deps.accountId}`,
|
|
179
|
+
now: now(),
|
|
180
|
+
}), command);
|
|
181
|
+
});
|
|
109
182
|
const events = annotate(program.command("events").description("the account's stored events"), { effects: GROUP });
|
|
110
183
|
output(example(annotate(events.command("list"), { effects: LOCAL_READ, output: { entity: "event" } })
|
|
111
184
|
.description("list stored events, earliest first")
|
|
@@ -199,7 +272,13 @@ const addEventWrites = (events, writes, { deps, now, zone, output, print, exampl
|
|
|
199
272
|
ask: terminalAsker(command),
|
|
200
273
|
now: now(),
|
|
201
274
|
zone: zone(),
|
|
202
|
-
names: {
|
|
275
|
+
names: {
|
|
276
|
+
notify: "--notify or --no-notify",
|
|
277
|
+
id: "--id",
|
|
278
|
+
calendar: "--calendar",
|
|
279
|
+
series: "--series",
|
|
280
|
+
repeat: "--repeat",
|
|
281
|
+
},
|
|
203
282
|
});
|
|
204
283
|
const printWritten = (value, command) => print({ ...value, untrusted: CALENDAR_UNTRUSTED_FIELDS }, command);
|
|
205
284
|
// Left out on purpose: with other attendees the owner must choose, so neither switch is the default.
|
|
@@ -216,7 +295,8 @@ const addEventWrites = (events, writes, { deps, now, zone, output, print, exampl
|
|
|
216
295
|
.option("--location <text>", "where; empty clears it")
|
|
217
296
|
.option("--description <text>", "the details; empty clears it")
|
|
218
297
|
// The empty default marks the flag as repeatable, so an agent's tool takes a list; empty is "not given".
|
|
219
|
-
.option("--attendee <email>", `an attendee; repeat for each${replacing ? ", replacing the list" : ""}`, collect, [])
|
|
298
|
+
.option("--attendee <email>", `an attendee; repeat for each${replacing ? ", replacing the list" : ""}`, collect, [])
|
|
299
|
+
.option("--repeat <rule>", `how it repeats${replacing ? ", with --series" : ""}: ${REPEAT_HELP}`);
|
|
220
300
|
const fieldsOf = ({ attendee, ...given }) => ({
|
|
221
301
|
...Object.fromEntries(Object.entries(given).filter(([, value]) => value !== undefined)),
|
|
222
302
|
...(attendee === undefined || attendee.length === 0 ? {} : { attendees: attendee }),
|
|
@@ -231,9 +311,10 @@ const addEventWrites = (events, writes, { deps, now, zone, output, print, exampl
|
|
|
231
311
|
.argument("<event>", "the event's id, or event:<id>")
|
|
232
312
|
.option("--calendar <id>", "the calendar, when the event is on more than one of the account's");
|
|
233
313
|
output(example(notify(fields(write("create"), false)
|
|
234
|
-
.description("add an event to a calendar; one that repeats
|
|
314
|
+
.description("add an event to a calendar; with --repeat, one that repeats")
|
|
235
315
|
.argument("<calendar>", "the calendar's id, or the provider's id")).option("--id <id>", "the id a create that ended in outcome_unknown named: a retry finds that event"), "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 " +
|
|
236
|
-
"--attendee ana@example.com --no-notify"
|
|
316
|
+
"--attendee ana@example.com --no-notify", "events create 3 --title 'Example standup' --start-time 2026-11-02T09:30 --end-time 2026-11-02T09:45 " +
|
|
317
|
+
"--repeat 'FREQ=WEEKLY;BYDAY=MO,WE,FR;COUNT=12'")).action(async (calendar, opts, command) => {
|
|
237
318
|
const { notify: choice, id, ...rest } = opts;
|
|
238
319
|
printWritten(await createEvent(context(command), calendar, fieldsOf(rest), {
|
|
239
320
|
...(choice === undefined ? {} : { notify: choice }),
|
|
@@ -241,8 +322,9 @@ const addEventWrites = (events, writes, { deps, now, zone, output, print, exampl
|
|
|
241
322
|
}), command);
|
|
242
323
|
});
|
|
243
324
|
output(example(notify(target(fields(write("update"), true))
|
|
244
|
-
.description("change an event; with --series every occurrence of the repeating event"
|
|
245
|
-
|
|
325
|
+
.description("change an event; with --series every occurrence of the repeating event; new times or dates switch it " +
|
|
326
|
+
"between timed and all-day")
|
|
327
|
+
.option("--series", "change the whole repeating event, not only this occurrence")), "events update event:42 --start-time 2026-11-02T14:00 --end-time 2026-11-02T15:00 --notify", "events update event:42 --series --repeat 'FREQ=WEEKLY;BYDAY=TU;UNTIL=20261231T230000Z' --no-notify")).action(async (reference, opts, command) => {
|
|
246
328
|
const { calendar: _calendar, series: _series, notify: _notify, ...rest } = opts;
|
|
247
329
|
printWritten(await updateEvent(context(command), reference, fieldsOf(rest), choicesOf(opts)), command);
|
|
248
330
|
});
|
package/dist/calendar/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export type { CalendarAccessRole, CalendarInput, EntryAttachment, EntryAttachmentInput, EntryEventType, EntryInput, EntryKind, EntryParticipantInput, EntryRevision, EntryStatus, EntryTransparency, EntryVisibility, EventDetails, EventFilter, EventStore, LinkReview, ParticipantResponse, ReviewDecision, StoredCalendar, StoredEntry, StoredParticipant, } from "../meetings/index.js";
|
|
2
|
-
export
|
|
3
|
-
export {
|
|
2
|
+
export { repeatRule } from "./recurrence.js";
|
|
3
|
+
export type { BusyTimes, CalendarExpander, CalendarSource, CalendarSourceTraits, ChangePage, ChangeResult, FreeBusyRequest, SyncWindow, } from "./source.js";
|
|
4
|
+
export { type CalendarCursor, type CalendarSyncOptions, type CalendarSyncReport, type CalendarSyncResult, calendarStatus, calendarSyncWindow, type PendingRound, parseCalendarCursor, syncCalendars, } from "./sync.js";
|
|
4
5
|
export { CALENDAR_WRITE_DEFAULTS, CALENDAR_WRITE_KEYS, type CalendarWrite, type CalendarWriter, calendarConflict, EVENTS_DELETE_SERIES_KEY, EVENTS_NOTIFY_KEY, type EventDraft, type EventPatch, type EventResponse, type WriteScope, } from "./writer.js";
|
|
5
6
|
export type { CalendarWrites } from "./writes.js";
|
package/dist/calendar/index.js
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { repeatRule } from "./recurrence.js";
|
|
2
|
+
export { calendarStatus, calendarSyncWindow, parseCalendarCursor, syncCalendars, } from "./sync.js";
|
|
2
3
|
export { CALENDAR_WRITE_DEFAULTS, CALENDAR_WRITE_KEYS, calendarConflict, EVENTS_DELETE_SERIES_KEY, EVENTS_NOTIFY_KEY, } from "./writer.js";
|
|
@@ -10,13 +10,13 @@ export declare const calendarTools: ({
|
|
|
10
10
|
inputSchema: {
|
|
11
11
|
type: string;
|
|
12
12
|
properties: {
|
|
13
|
+
limit?: undefined;
|
|
13
14
|
account_id: {
|
|
14
15
|
type: string;
|
|
15
16
|
minimum: number;
|
|
16
17
|
};
|
|
17
18
|
words?: undefined;
|
|
18
19
|
timezone?: undefined;
|
|
19
|
-
limit?: undefined;
|
|
20
20
|
event?: undefined;
|
|
21
21
|
} | {
|
|
22
22
|
account_id: {
|
|
@@ -38,9 +38,9 @@ export declare const calendarTools: ({
|
|
|
38
38
|
};
|
|
39
39
|
event?: undefined;
|
|
40
40
|
} | {
|
|
41
|
+
limit?: undefined;
|
|
41
42
|
words?: undefined;
|
|
42
43
|
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
|
+
limit?: undefined;
|
|
53
54
|
words?: undefined;
|
|
54
55
|
timezone?: undefined;
|
|
55
|
-
limit?: undefined;
|
|
56
56
|
event?: undefined;
|
|
57
57
|
account_id?: undefined;
|
|
58
58
|
};
|
|
@@ -71,13 +71,13 @@ export declare const calendarTools: ({
|
|
|
71
71
|
inputSchema: {
|
|
72
72
|
type: string;
|
|
73
73
|
properties: {
|
|
74
|
+
limit?: undefined;
|
|
74
75
|
account_id: {
|
|
75
76
|
type: string;
|
|
76
77
|
minimum: number;
|
|
77
78
|
};
|
|
78
79
|
words?: undefined;
|
|
79
80
|
timezone?: undefined;
|
|
80
|
-
limit?: undefined;
|
|
81
81
|
event?: undefined;
|
|
82
82
|
} | {
|
|
83
83
|
account_id: {
|
|
@@ -99,9 +99,9 @@ export declare const calendarTools: ({
|
|
|
99
99
|
};
|
|
100
100
|
event?: undefined;
|
|
101
101
|
} | {
|
|
102
|
+
limit?: undefined;
|
|
102
103
|
words?: undefined;
|
|
103
104
|
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
|
+
limit?: undefined;
|
|
114
115
|
words?: undefined;
|
|
115
116
|
timezone?: undefined;
|
|
116
|
-
limit?: undefined;
|
|
117
117
|
event?: undefined;
|
|
118
118
|
account_id?: undefined;
|
|
119
119
|
};
|
|
@@ -132,13 +132,13 @@ export declare const calendarTools: ({
|
|
|
132
132
|
inputSchema: {
|
|
133
133
|
type: string;
|
|
134
134
|
properties: {
|
|
135
|
+
limit?: undefined;
|
|
135
136
|
account_id: {
|
|
136
137
|
type: string;
|
|
137
138
|
minimum: number;
|
|
138
139
|
};
|
|
139
140
|
words?: undefined;
|
|
140
141
|
timezone?: undefined;
|
|
141
|
-
limit?: undefined;
|
|
142
142
|
event?: undefined;
|
|
143
143
|
} | {
|
|
144
144
|
account_id: {
|
|
@@ -160,9 +160,9 @@ export declare const calendarTools: ({
|
|
|
160
160
|
};
|
|
161
161
|
event?: undefined;
|
|
162
162
|
} | {
|
|
163
|
+
limit?: undefined;
|
|
163
164
|
words?: undefined;
|
|
164
165
|
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
|
+
limit?: undefined;
|
|
175
176
|
words?: undefined;
|
|
176
177
|
timezone?: undefined;
|
|
177
|
-
limit?: undefined;
|
|
178
178
|
event?: undefined;
|
|
179
179
|
account_id?: undefined;
|
|
180
180
|
};
|
|
@@ -193,13 +193,13 @@ export declare const calendarTools: ({
|
|
|
193
193
|
inputSchema: {
|
|
194
194
|
type: string;
|
|
195
195
|
properties: {
|
|
196
|
+
limit?: undefined;
|
|
196
197
|
account_id: {
|
|
197
198
|
type: string;
|
|
198
199
|
minimum: number;
|
|
199
200
|
};
|
|
200
201
|
words?: undefined;
|
|
201
202
|
timezone?: undefined;
|
|
202
|
-
limit?: undefined;
|
|
203
203
|
event?: undefined;
|
|
204
204
|
} | {
|
|
205
205
|
account_id: {
|
|
@@ -221,9 +221,9 @@ export declare const calendarTools: ({
|
|
|
221
221
|
};
|
|
222
222
|
event?: undefined;
|
|
223
223
|
} | {
|
|
224
|
+
limit?: undefined;
|
|
224
225
|
words?: undefined;
|
|
225
226
|
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
|
+
limit?: undefined;
|
|
236
237
|
words?: undefined;
|
|
237
238
|
timezone?: undefined;
|
|
238
|
-
limit?: undefined;
|
|
239
239
|
event?: undefined;
|
|
240
240
|
account_id?: undefined;
|
|
241
241
|
};
|
|
@@ -303,6 +303,11 @@ export declare const calendarWriteTools: ({
|
|
|
303
303
|
};
|
|
304
304
|
description: string;
|
|
305
305
|
};
|
|
306
|
+
repeat: {
|
|
307
|
+
type: string;
|
|
308
|
+
minLength: number;
|
|
309
|
+
description: string;
|
|
310
|
+
};
|
|
306
311
|
account_id: {
|
|
307
312
|
type: string;
|
|
308
313
|
minimum: number;
|
|
@@ -365,6 +370,11 @@ export declare const calendarWriteTools: ({
|
|
|
365
370
|
};
|
|
366
371
|
description: string;
|
|
367
372
|
};
|
|
373
|
+
repeat: {
|
|
374
|
+
type: string;
|
|
375
|
+
minLength: number;
|
|
376
|
+
description: string;
|
|
377
|
+
};
|
|
368
378
|
account_id: {
|
|
369
379
|
type: string;
|
|
370
380
|
minimum: number;
|
|
@@ -488,6 +498,11 @@ export declare const calendarWriteTools: ({
|
|
|
488
498
|
};
|
|
489
499
|
description: string;
|
|
490
500
|
};
|
|
501
|
+
repeat: {
|
|
502
|
+
type: string;
|
|
503
|
+
minLength: number;
|
|
504
|
+
description: string;
|
|
505
|
+
};
|
|
491
506
|
account_id: {
|
|
492
507
|
type: string;
|
|
493
508
|
minimum: number;
|
|
@@ -550,6 +565,11 @@ export declare const calendarWriteTools: ({
|
|
|
550
565
|
};
|
|
551
566
|
description: string;
|
|
552
567
|
};
|
|
568
|
+
repeat: {
|
|
569
|
+
type: string;
|
|
570
|
+
minLength: number;
|
|
571
|
+
description: string;
|
|
572
|
+
};
|
|
553
573
|
account_id: {
|
|
554
574
|
type: string;
|
|
555
575
|
minimum: number;
|
|
@@ -673,6 +693,11 @@ export declare const calendarWriteTools: ({
|
|
|
673
693
|
};
|
|
674
694
|
description: string;
|
|
675
695
|
};
|
|
696
|
+
repeat: {
|
|
697
|
+
type: string;
|
|
698
|
+
minLength: number;
|
|
699
|
+
description: string;
|
|
700
|
+
};
|
|
676
701
|
account_id: {
|
|
677
702
|
type: string;
|
|
678
703
|
minimum: number;
|
|
@@ -735,6 +760,11 @@ export declare const calendarWriteTools: ({
|
|
|
735
760
|
};
|
|
736
761
|
description: string;
|
|
737
762
|
};
|
|
763
|
+
repeat: {
|
|
764
|
+
type: string;
|
|
765
|
+
minLength: number;
|
|
766
|
+
description: string;
|
|
767
|
+
};
|
|
738
768
|
account_id: {
|
|
739
769
|
type: string;
|
|
740
770
|
minimum: number;
|
|
@@ -858,6 +888,11 @@ export declare const calendarWriteTools: ({
|
|
|
858
888
|
};
|
|
859
889
|
description: string;
|
|
860
890
|
};
|
|
891
|
+
repeat: {
|
|
892
|
+
type: string;
|
|
893
|
+
minLength: number;
|
|
894
|
+
description: string;
|
|
895
|
+
};
|
|
861
896
|
account_id: {
|
|
862
897
|
type: string;
|
|
863
898
|
minimum: number;
|
|
@@ -920,6 +955,11 @@ export declare const calendarWriteTools: ({
|
|
|
920
955
|
};
|
|
921
956
|
description: string;
|
|
922
957
|
};
|
|
958
|
+
repeat: {
|
|
959
|
+
type: string;
|
|
960
|
+
minLength: number;
|
|
961
|
+
description: string;
|
|
962
|
+
};
|
|
923
963
|
account_id: {
|
|
924
964
|
type: string;
|
|
925
965
|
minimum: number;
|
|
@@ -998,8 +1038,10 @@ export declare const calendarWriteTools: ({
|
|
|
998
1038
|
* Runs one calendar tool over the accounts the host serves. Without `account_id` a tool reads every one of them and
|
|
999
1039
|
* names them in `accounts`; with one it must be among them.
|
|
1000
1040
|
*/
|
|
1001
|
-
export declare const callCalendarTool: (store: EventStore, name: string, input: unknown, { accountIds, failure, now, writes, }: {
|
|
1041
|
+
export declare const callCalendarTool: (store: EventStore, name: string, input: unknown, { accountIds, failure, now, writes, profileOf, }: {
|
|
1002
1042
|
accountIds: readonly number[];
|
|
1043
|
+
/** What `status` calls each account's sync. */
|
|
1044
|
+
profileOf?: (accountId: number) => string;
|
|
1003
1045
|
failure?: FailureContext;
|
|
1004
1046
|
now?: number;
|
|
1005
1047
|
/** Each account's writer and profile; without it the write tools refuse. */
|