@hmawla/co-assistant 1.0.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.
@@ -0,0 +1,328 @@
1
+ /**
2
+ * @module google-calendar/tools
3
+ * @description Google Calendar AI tool definitions for listing, creating,
4
+ * updating, and deleting calendar events via the Google Calendar v3 API.
5
+ */
6
+
7
+ import type { ToolDefinition } from "../../src/plugins/types.js";
8
+ import type { CalendarAuth } from "./auth.js";
9
+
10
+ /** Base URL for the Google Calendar v3 REST API. */
11
+ const CALENDAR_API = "https://www.googleapis.com/calendar/v3";
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // Helpers
15
+ // ---------------------------------------------------------------------------
16
+
17
+ /**
18
+ * Make an authenticated request to the Google Calendar API.
19
+ *
20
+ * @returns The parsed JSON response body.
21
+ */
22
+ async function calendarFetch(
23
+ auth: CalendarAuth,
24
+ path: string,
25
+ options: RequestInit = {},
26
+ ): Promise<unknown> {
27
+ const token = await auth.getAccessToken();
28
+ const res = await fetch(`${CALENDAR_API}${path}`, {
29
+ ...options,
30
+ headers: {
31
+ Authorization: `Bearer ${token}`,
32
+ "Content-Type": "application/json",
33
+ ...options.headers,
34
+ },
35
+ });
36
+
37
+ if (!res.ok) {
38
+ const text = await res.text();
39
+ throw new Error(`Google Calendar API error (${res.status}): ${text}`);
40
+ }
41
+
42
+ // DELETE returns 204 No Content
43
+ if (res.status === 204) return { success: true };
44
+ return res.json();
45
+ }
46
+
47
+ /** Formats Google Calendar event datetime for display. */
48
+ function formatEventTime(dt: { dateTime?: string; date?: string }): string {
49
+ if (dt.dateTime) {
50
+ return new Date(dt.dateTime).toLocaleString("en-US", {
51
+ weekday: "short",
52
+ month: "short",
53
+ day: "numeric",
54
+ year: "numeric",
55
+ hour: "numeric",
56
+ minute: "2-digit",
57
+ timeZoneName: "short",
58
+ });
59
+ }
60
+ if (dt.date) {
61
+ return new Date(dt.date + "T00:00:00").toLocaleDateString("en-US", {
62
+ weekday: "short",
63
+ month: "short",
64
+ day: "numeric",
65
+ year: "numeric",
66
+ });
67
+ }
68
+ return "N/A";
69
+ }
70
+
71
+ interface CalendarEvent {
72
+ id: string;
73
+ summary?: string;
74
+ description?: string;
75
+ location?: string;
76
+ start?: { dateTime?: string; date?: string };
77
+ end?: { dateTime?: string; date?: string };
78
+ attendees?: { email: string; responseStatus?: string }[];
79
+ htmlLink?: string;
80
+ status?: string;
81
+ }
82
+
83
+ /** Build a concise, human-readable summary of a single event. */
84
+ function formatEvent(event: CalendarEvent): string {
85
+ const lines: string[] = [];
86
+ lines.push(`📅 ${event.summary ?? "(no title)"}`);
87
+ if (event.start) lines.push(` Start: ${formatEventTime(event.start)}`);
88
+ if (event.end) lines.push(` End: ${formatEventTime(event.end)}`);
89
+ if (event.location) lines.push(` 📍 ${event.location}`);
90
+ if (event.description) lines.push(` ${event.description}`);
91
+ if (event.attendees?.length) {
92
+ const emails = event.attendees.map((a) => a.email).join(", ");
93
+ lines.push(` 👥 ${emails}`);
94
+ }
95
+ lines.push(` ID: ${event.id}`);
96
+ return lines.join("\n");
97
+ }
98
+
99
+ // ---------------------------------------------------------------------------
100
+ // Tool factory
101
+ // ---------------------------------------------------------------------------
102
+
103
+ /**
104
+ * Creates all Google Calendar tool definitions for the plugin.
105
+ *
106
+ * @param auth - An initialised {@link CalendarAuth} instance.
107
+ */
108
+ export function createCalendarTools(auth: CalendarAuth): ToolDefinition[] {
109
+ // -----------------------------------------------------------------------
110
+ // list_events
111
+ // -----------------------------------------------------------------------
112
+ const listEvents: ToolDefinition = {
113
+ name: "list_events",
114
+ description:
115
+ "List upcoming calendar events. Optionally filter by date range.",
116
+ parameters: {
117
+ type: "object",
118
+ properties: {
119
+ maxResults: {
120
+ type: "number",
121
+ description: "Maximum number of events to return (default 10).",
122
+ },
123
+ timeMin: {
124
+ type: "string",
125
+ description:
126
+ "Start of the time range as an ISO 8601 date-time string. Defaults to now.",
127
+ },
128
+ timeMax: {
129
+ type: "string",
130
+ description:
131
+ "End of the time range as an ISO 8601 date-time string.",
132
+ },
133
+ calendarId: {
134
+ type: "string",
135
+ description: 'Calendar ID to query (default "primary").',
136
+ },
137
+ },
138
+ },
139
+ handler: async (args) => {
140
+ try {
141
+ const calendarId = encodeURIComponent(
142
+ (args.calendarId as string) ?? "primary",
143
+ );
144
+ const params = new URLSearchParams({
145
+ maxResults: String((args.maxResults as number) ?? 10),
146
+ timeMin: (args.timeMin as string) ?? new Date().toISOString(),
147
+ orderBy: "startTime",
148
+ singleEvents: "true",
149
+ });
150
+ if (args.timeMax) params.set("timeMax", args.timeMax as string);
151
+
152
+ const data = (await calendarFetch(
153
+ auth,
154
+ `/calendars/${calendarId}/events?${params.toString()}`,
155
+ )) as { items?: CalendarEvent[] };
156
+
157
+ const events: CalendarEvent[] = data.items ?? [];
158
+ if (events.length === 0) {
159
+ return "No upcoming events found.";
160
+ }
161
+
162
+ return events.map(formatEvent).join("\n\n");
163
+ } catch (error) {
164
+ return `Error listing events: ${(error as Error).message}`;
165
+ }
166
+ },
167
+ };
168
+
169
+ // -----------------------------------------------------------------------
170
+ // create_event
171
+ // -----------------------------------------------------------------------
172
+ const createEvent: ToolDefinition = {
173
+ name: "create_event",
174
+ description: "Create a new calendar event.",
175
+ parameters: {
176
+ type: "object",
177
+ properties: {
178
+ summary: { type: "string", description: "Event title." },
179
+ description: { type: "string", description: "Event description." },
180
+ startTime: {
181
+ type: "string",
182
+ description: "Start date-time in ISO 8601 format.",
183
+ },
184
+ endTime: {
185
+ type: "string",
186
+ description: "End date-time in ISO 8601 format.",
187
+ },
188
+ location: { type: "string", description: "Event location." },
189
+ attendees: {
190
+ type: "array",
191
+ items: { type: "string" },
192
+ description: "List of attendee email addresses.",
193
+ },
194
+ calendarId: {
195
+ type: "string",
196
+ description: 'Calendar ID (default "primary").',
197
+ },
198
+ },
199
+ required: ["summary", "startTime", "endTime"],
200
+ },
201
+ handler: async (args) => {
202
+ try {
203
+ const calendarId = encodeURIComponent(
204
+ (args.calendarId as string) ?? "primary",
205
+ );
206
+
207
+ const body: Record<string, unknown> = {
208
+ summary: args.summary,
209
+ start: { dateTime: args.startTime },
210
+ end: { dateTime: args.endTime },
211
+ };
212
+ if (args.description) body.description = args.description;
213
+ if (args.location) body.location = args.location;
214
+ if (args.attendees) {
215
+ body.attendees = (args.attendees as string[]).map((email) => ({
216
+ email,
217
+ }));
218
+ }
219
+
220
+ const event = (await calendarFetch(
221
+ auth,
222
+ `/calendars/${calendarId}/events`,
223
+ { method: "POST", body: JSON.stringify(body) },
224
+ )) as CalendarEvent;
225
+
226
+ return `✅ Event created successfully!\n\n${formatEvent(event)}`;
227
+ } catch (error) {
228
+ return `Error creating event: ${(error as Error).message}`;
229
+ }
230
+ },
231
+ };
232
+
233
+ // -----------------------------------------------------------------------
234
+ // update_event
235
+ // -----------------------------------------------------------------------
236
+ const updateEvent: ToolDefinition = {
237
+ name: "update_event",
238
+ description: "Update an existing calendar event by event ID.",
239
+ parameters: {
240
+ type: "object",
241
+ properties: {
242
+ eventId: { type: "string", description: "The event ID to update." },
243
+ summary: { type: "string", description: "New event title." },
244
+ description: { type: "string", description: "New event description." },
245
+ startTime: {
246
+ type: "string",
247
+ description: "New start date-time in ISO 8601 format.",
248
+ },
249
+ endTime: {
250
+ type: "string",
251
+ description: "New end date-time in ISO 8601 format.",
252
+ },
253
+ location: { type: "string", description: "New event location." },
254
+ calendarId: {
255
+ type: "string",
256
+ description: 'Calendar ID (default "primary").',
257
+ },
258
+ },
259
+ required: ["eventId"],
260
+ },
261
+ handler: async (args) => {
262
+ try {
263
+ const calendarId = encodeURIComponent(
264
+ (args.calendarId as string) ?? "primary",
265
+ );
266
+ const eventId = encodeURIComponent(args.eventId as string);
267
+
268
+ const body: Record<string, unknown> = {};
269
+ if (args.summary !== undefined) body.summary = args.summary;
270
+ if (args.description !== undefined)
271
+ body.description = args.description;
272
+ if (args.startTime !== undefined)
273
+ body.start = { dateTime: args.startTime };
274
+ if (args.endTime !== undefined) body.end = { dateTime: args.endTime };
275
+ if (args.location !== undefined) body.location = args.location;
276
+
277
+ const event = (await calendarFetch(
278
+ auth,
279
+ `/calendars/${calendarId}/events/${eventId}`,
280
+ { method: "PATCH", body: JSON.stringify(body) },
281
+ )) as CalendarEvent;
282
+
283
+ return `✅ Event updated successfully!\n\n${formatEvent(event)}`;
284
+ } catch (error) {
285
+ return `Error updating event: ${(error as Error).message}`;
286
+ }
287
+ },
288
+ };
289
+
290
+ // -----------------------------------------------------------------------
291
+ // delete_event
292
+ // -----------------------------------------------------------------------
293
+ const deleteEvent: ToolDefinition = {
294
+ name: "delete_event",
295
+ description: "Delete a calendar event by event ID.",
296
+ parameters: {
297
+ type: "object",
298
+ properties: {
299
+ eventId: { type: "string", description: "The event ID to delete." },
300
+ calendarId: {
301
+ type: "string",
302
+ description: 'Calendar ID (default "primary").',
303
+ },
304
+ },
305
+ required: ["eventId"],
306
+ },
307
+ handler: async (args) => {
308
+ try {
309
+ const calendarId = encodeURIComponent(
310
+ (args.calendarId as string) ?? "primary",
311
+ );
312
+ const eventId = encodeURIComponent(args.eventId as string);
313
+
314
+ await calendarFetch(
315
+ auth,
316
+ `/calendars/${calendarId}/events/${eventId}`,
317
+ { method: "DELETE" },
318
+ );
319
+
320
+ return `✅ Event ${args.eventId} deleted successfully.`;
321
+ } catch (error) {
322
+ return `Error deleting event: ${(error as Error).message}`;
323
+ }
324
+ },
325
+ };
326
+
327
+ return [listEvents, createEvent, updateEvent, deleteEvent];
328
+ }
@@ -0,0 +1,38 @@
1
+ # User Profile
2
+
3
+ > **Copy this file to `user.md` and fill in your details.**
4
+ > The AI assistant uses this context to personalise responses, address you
5
+ > correctly, and understand your professional background.
6
+ > Changes take effect on the next message — no restart required.
7
+
8
+ ---
9
+
10
+ ## Basic Info
11
+
12
+ - **Name:** Jane Doe
13
+ - **Title:** Ms.
14
+ - **Preferred name:** Jane
15
+ - **Timezone:** America/New_York
16
+ - **Language:** English
17
+
18
+ ## Professional
19
+
20
+ - **Role:** Software Engineer
21
+ - **Company / Organisation:** Acme Corp
22
+ - **Industry:** Technology
23
+
24
+ ## Contact
25
+
26
+ - **Primary email:** jane@example.com
27
+ - **Calendar:** jane@example.com
28
+
29
+ ## Preferences
30
+
31
+ - **Communication style:** Brief and to the point
32
+ - **Working hours:** 9 AM – 6 PM
33
+ - **Days off:** Saturday, Sunday
34
+
35
+ ## Notes
36
+
37
+ <!-- Add anything else the assistant should know about you:
38
+ recurring meetings, VIP contacts, projects you're working on, etc. -->