@davidwoods/skylight-mcp 0.0.0-stage → 1.8.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.
Files changed (168) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +633 -3
  3. package/THIRD_PARTY_NOTICES.md +38 -0
  4. package/dist/api/auth.d.ts +34 -0
  5. package/dist/api/auth.d.ts.map +1 -0
  6. package/dist/api/auth.js +328 -0
  7. package/dist/api/auth.js.map +1 -0
  8. package/dist/api/client.d.ts +103 -0
  9. package/dist/api/client.d.ts.map +1 -0
  10. package/dist/api/client.js +324 -0
  11. package/dist/api/client.js.map +1 -0
  12. package/dist/api/constants.d.ts +4 -0
  13. package/dist/api/constants.d.ts.map +1 -0
  14. package/dist/api/constants.js +4 -0
  15. package/dist/api/constants.js.map +1 -0
  16. package/dist/api/credential-store.d.ts +38 -0
  17. package/dist/api/credential-store.d.ts.map +1 -0
  18. package/dist/api/credential-store.js +274 -0
  19. package/dist/api/credential-store.js.map +1 -0
  20. package/dist/api/credential-types.d.ts +15 -0
  21. package/dist/api/credential-types.d.ts.map +1 -0
  22. package/dist/api/credential-types.js +11 -0
  23. package/dist/api/credential-types.js.map +1 -0
  24. package/dist/api/endpoints/calendar.d.ts +36 -0
  25. package/dist/api/endpoints/calendar.d.ts.map +1 -0
  26. package/dist/api/endpoints/calendar.js +74 -0
  27. package/dist/api/endpoints/calendar.js.map +1 -0
  28. package/dist/api/endpoints/categories.d.ts +32 -0
  29. package/dist/api/endpoints/categories.d.ts.map +1 -0
  30. package/dist/api/endpoints/categories.js +70 -0
  31. package/dist/api/endpoints/categories.js.map +1 -0
  32. package/dist/api/endpoints/chores.d.ts +58 -0
  33. package/dist/api/endpoints/chores.d.ts.map +1 -0
  34. package/dist/api/endpoints/chores.js +115 -0
  35. package/dist/api/endpoints/chores.js.map +1 -0
  36. package/dist/api/endpoints/devices.d.ts +6 -0
  37. package/dist/api/endpoints/devices.d.ts.map +1 -0
  38. package/dist/api/endpoints/devices.js +10 -0
  39. package/dist/api/endpoints/devices.js.map +1 -0
  40. package/dist/api/endpoints/frames.d.ts +6 -0
  41. package/dist/api/endpoints/frames.d.ts.map +1 -0
  42. package/dist/api/endpoints/frames.js +10 -0
  43. package/dist/api/endpoints/frames.js.map +1 -0
  44. package/dist/api/endpoints/lists.d.ts +55 -0
  45. package/dist/api/endpoints/lists.d.ts.map +1 -0
  46. package/dist/api/endpoints/lists.js +125 -0
  47. package/dist/api/endpoints/lists.js.map +1 -0
  48. package/dist/api/endpoints/meals.d.ts +154 -0
  49. package/dist/api/endpoints/meals.d.ts.map +1 -0
  50. package/dist/api/endpoints/meals.js +194 -0
  51. package/dist/api/endpoints/meals.js.map +1 -0
  52. package/dist/api/endpoints/messages.d.ts +98 -0
  53. package/dist/api/endpoints/messages.d.ts.map +1 -0
  54. package/dist/api/endpoints/messages.js +116 -0
  55. package/dist/api/endpoints/messages.js.map +1 -0
  56. package/dist/api/endpoints/misc.d.ts +27 -0
  57. package/dist/api/endpoints/misc.d.ts.map +1 -0
  58. package/dist/api/endpoints/misc.js +19 -0
  59. package/dist/api/endpoints/misc.js.map +1 -0
  60. package/dist/api/endpoints/photos.d.ts +13 -0
  61. package/dist/api/endpoints/photos.d.ts.map +1 -0
  62. package/dist/api/endpoints/photos.js +10 -0
  63. package/dist/api/endpoints/photos.js.map +1 -0
  64. package/dist/api/endpoints/rewards.d.ts +49 -0
  65. package/dist/api/endpoints/rewards.d.ts.map +1 -0
  66. package/dist/api/endpoints/rewards.js +106 -0
  67. package/dist/api/endpoints/rewards.js.map +1 -0
  68. package/dist/api/endpoints/shape.d.ts +11 -0
  69. package/dist/api/endpoints/shape.d.ts.map +1 -0
  70. package/dist/api/endpoints/shape.js +34 -0
  71. package/dist/api/endpoints/shape.js.map +1 -0
  72. package/dist/api/endpoints/taskbox.d.ts +21 -0
  73. package/dist/api/endpoints/taskbox.d.ts.map +1 -0
  74. package/dist/api/endpoints/taskbox.js +39 -0
  75. package/dist/api/endpoints/taskbox.js.map +1 -0
  76. package/dist/api/oauth.d.ts +62 -0
  77. package/dist/api/oauth.d.ts.map +1 -0
  78. package/dist/api/oauth.js +206 -0
  79. package/dist/api/oauth.js.map +1 -0
  80. package/dist/api/types.d.ts +291 -0
  81. package/dist/api/types.d.ts.map +1 -0
  82. package/dist/api/types.js +5 -0
  83. package/dist/api/types.js.map +1 -0
  84. package/dist/cli.d.ts +23 -0
  85. package/dist/cli.d.ts.map +1 -0
  86. package/dist/cli.js +106 -0
  87. package/dist/cli.js.map +1 -0
  88. package/dist/config.d.ts +27 -0
  89. package/dist/config.d.ts.map +1 -0
  90. package/dist/config.js +78 -0
  91. package/dist/config.js.map +1 -0
  92. package/dist/context.d.ts +17 -0
  93. package/dist/context.d.ts.map +1 -0
  94. package/dist/context.js +17 -0
  95. package/dist/context.js.map +1 -0
  96. package/dist/doctor.d.ts +29 -0
  97. package/dist/doctor.d.ts.map +1 -0
  98. package/dist/doctor.js +125 -0
  99. package/dist/doctor.js.map +1 -0
  100. package/dist/index.d.ts +3 -0
  101. package/dist/index.d.ts.map +1 -0
  102. package/dist/index.js +24 -0
  103. package/dist/index.js.map +1 -0
  104. package/dist/server.d.ts +7 -0
  105. package/dist/server.d.ts.map +1 -0
  106. package/dist/server.js +29 -0
  107. package/dist/server.js.map +1 -0
  108. package/dist/tools/calendar.d.ts +8 -0
  109. package/dist/tools/calendar.d.ts.map +1 -0
  110. package/dist/tools/calendar.js +334 -0
  111. package/dist/tools/calendar.js.map +1 -0
  112. package/dist/tools/chores.d.ts +3 -0
  113. package/dist/tools/chores.d.ts.map +1 -0
  114. package/dist/tools/chores.js +321 -0
  115. package/dist/tools/chores.js.map +1 -0
  116. package/dist/tools/family.d.ts +3 -0
  117. package/dist/tools/family.d.ts.map +1 -0
  118. package/dist/tools/family.js +182 -0
  119. package/dist/tools/family.js.map +1 -0
  120. package/dist/tools/lists.d.ts +3 -0
  121. package/dist/tools/lists.d.ts.map +1 -0
  122. package/dist/tools/lists.js +481 -0
  123. package/dist/tools/lists.js.map +1 -0
  124. package/dist/tools/meals.d.ts +3 -0
  125. package/dist/tools/meals.d.ts.map +1 -0
  126. package/dist/tools/meals.js +491 -0
  127. package/dist/tools/meals.js.map +1 -0
  128. package/dist/tools/messages.d.ts +3 -0
  129. package/dist/tools/messages.d.ts.map +1 -0
  130. package/dist/tools/messages.js +183 -0
  131. package/dist/tools/messages.js.map +1 -0
  132. package/dist/tools/misc.d.ts +3 -0
  133. package/dist/tools/misc.d.ts.map +1 -0
  134. package/dist/tools/misc.js +102 -0
  135. package/dist/tools/misc.js.map +1 -0
  136. package/dist/tools/photos.d.ts +3 -0
  137. package/dist/tools/photos.d.ts.map +1 -0
  138. package/dist/tools/photos.js +42 -0
  139. package/dist/tools/photos.js.map +1 -0
  140. package/dist/tools/planning.d.ts +15 -0
  141. package/dist/tools/planning.d.ts.map +1 -0
  142. package/dist/tools/planning.js +304 -0
  143. package/dist/tools/planning.js.map +1 -0
  144. package/dist/tools/registry.d.ts +39 -0
  145. package/dist/tools/registry.d.ts.map +1 -0
  146. package/dist/tools/registry.js +143 -0
  147. package/dist/tools/registry.js.map +1 -0
  148. package/dist/tools/rewards.d.ts +3 -0
  149. package/dist/tools/rewards.d.ts.map +1 -0
  150. package/dist/tools/rewards.js +323 -0
  151. package/dist/tools/rewards.js.map +1 -0
  152. package/dist/tools/tasks.d.ts +3 -0
  153. package/dist/tools/tasks.d.ts.map +1 -0
  154. package/dist/tools/tasks.js +103 -0
  155. package/dist/tools/tasks.js.map +1 -0
  156. package/dist/utils/dates.d.ts +26 -0
  157. package/dist/utils/dates.d.ts.map +1 -0
  158. package/dist/utils/dates.js +109 -0
  159. package/dist/utils/dates.js.map +1 -0
  160. package/dist/utils/errors.d.ts +45 -0
  161. package/dist/utils/errors.d.ts.map +1 -0
  162. package/dist/utils/errors.js +102 -0
  163. package/dist/utils/errors.js.map +1 -0
  164. package/dist/utils/schedule.d.ts +86 -0
  165. package/dist/utils/schedule.d.ts.map +1 -0
  166. package/dist/utils/schedule.js +189 -0
  167. package/dist/utils/schedule.js.map +1 -0
  168. package/package.json +78 -6
@@ -0,0 +1,334 @@
1
+ import { z } from "zod";
2
+ import { getCalendarEvents, getSourceCalendars, createCalendarEvent, updateCalendarEvent, deleteCalendarEvent, withFrameOffset, } from "../api/endpoints/calendar.js";
3
+ import { getTodayDate, parseDate, formatDateForDisplay } from "../utils/dates.js";
4
+ import { formatErrorForMcp } from "../utils/errors.js";
5
+ import { getConfig } from "../config.js";
6
+ import { getCategories, resolveProfileIds } from "../api/endpoints/categories.js";
7
+ // Fields the event listing carries that identify people or accounts and tell a model nothing it
8
+ // can act on. Everything else is still printed, so new Skylight fields show up without a code change.
9
+ const HIDDEN_EVENT_FIELDS = new Set([
10
+ "owner_email",
11
+ "calendar_id",
12
+ "calendar_account_id",
13
+ "invited_emails",
14
+ "uid",
15
+ "supports_notification_settings",
16
+ ]);
17
+ /** Profile names an event is assigned to, or [] when none or not determinable. */
18
+ export function eventProfileNames(event, labelById) {
19
+ const ids = new Set();
20
+ for (const ref of event.relationships?.categories?.data ?? [])
21
+ if (ref?.id)
22
+ ids.add(String(ref.id));
23
+ const single = event.relationships?.category?.data?.id;
24
+ if (single)
25
+ ids.add(String(single));
26
+ const fromAttrs = event.attributes.category_ids;
27
+ if (Array.isArray(fromAttrs))
28
+ for (const id of fromAttrs)
29
+ ids.add(String(id));
30
+ return [...ids].map((id) => labelById.get(id)).filter((n) => Boolean(n));
31
+ }
32
+ /** One event as text: no emails, calendar ids or UIDs, and the assigned profiles when known. */
33
+ export function formatEventForTool(event, labelById) {
34
+ const parts = [];
35
+ const who = eventProfileNames(event, labelById);
36
+ if (who.length)
37
+ parts.push(` profiles: ${who.join(", ")}`);
38
+ for (const [key, value] of Object.entries(event.attributes)) {
39
+ if (HIDDEN_EVENT_FIELDS.has(key))
40
+ continue;
41
+ if (value !== null && value !== undefined)
42
+ parts.push(` ${key}: ${value}`);
43
+ }
44
+ return `- Event (ID: ${event.id})\n${parts.join("\n")}`;
45
+ }
46
+ export function registerCalendarTools(server) {
47
+ // get_calendar_events tool
48
+ server.tool("get_calendar_events", `Get calendar events from Skylight.
49
+
50
+ Use this to answer questions like:
51
+ - "What's on my calendar today?"
52
+ - "What do we have scheduled this weekend?"
53
+ - "Are there any events on Friday?"
54
+
55
+ Returns a list of events with their titles, times, and details.`, {
56
+ date: z
57
+ .string()
58
+ .optional()
59
+ .describe("Start date (YYYY-MM-DD or 'today', 'tomorrow', day name). Defaults to today."),
60
+ dateEnd: z
61
+ .string()
62
+ .optional()
63
+ .describe("End date (YYYY-MM-DD). Defaults to same as start date."),
64
+ }, async ({ date, dateEnd }) => {
65
+ try {
66
+ const config = getConfig();
67
+ const startDate = date ? parseDate(date, config.timezone) : getTodayDate(config.timezone);
68
+ const endDate = dateEnd ? parseDate(dateEnd, config.timezone) : startDate;
69
+ const events = await getCalendarEvents({
70
+ dateMin: startDate,
71
+ dateMax: endDate,
72
+ timezone: config.timezone,
73
+ });
74
+ if (events.length === 0) {
75
+ const dateRange = startDate === endDate
76
+ ? formatDateForDisplay(startDate)
77
+ : `${formatDateForDisplay(startDate)} to ${formatDateForDisplay(endDate)}`;
78
+ return {
79
+ content: [
80
+ {
81
+ type: "text",
82
+ text: `No calendar events found for ${dateRange}.`,
83
+ },
84
+ ],
85
+ };
86
+ }
87
+ // Profile names are a best-effort extra: a failed lookup must not hide the events.
88
+ const labelById = new Map();
89
+ try {
90
+ for (const c of await getCategories())
91
+ if (c.attributes.label)
92
+ labelById.set(String(c.id), c.attributes.label);
93
+ }
94
+ catch {
95
+ /* leave empty */
96
+ }
97
+ const eventList = events.map((event) => formatEventForTool(event, labelById)).join("\n\n");
98
+ const dateRange = startDate === endDate
99
+ ? formatDateForDisplay(startDate)
100
+ : `${formatDateForDisplay(startDate)} to ${formatDateForDisplay(endDate)}`;
101
+ return {
102
+ content: [
103
+ {
104
+ type: "text",
105
+ text: `Calendar events for ${dateRange}:\n\n${eventList}`,
106
+ },
107
+ ],
108
+ };
109
+ }
110
+ catch (error) {
111
+ return {
112
+ content: [
113
+ {
114
+ type: "text",
115
+ text: formatErrorForMcp(error),
116
+ },
117
+ ],
118
+ isError: true,
119
+ };
120
+ }
121
+ });
122
+ // get_source_calendars tool
123
+ server.tool("get_source_calendars", `Get connected calendar sources synced to Skylight.
124
+
125
+ Use this to answer:
126
+ - "Which calendars are synced to Skylight?"
127
+ - "What calendar accounts are connected?"
128
+
129
+ Returns a list of connected calendar sources (Google, iCloud, etc.).`, {}, async () => {
130
+ try {
131
+ const calendars = await getSourceCalendars();
132
+ if (calendars.length === 0) {
133
+ return {
134
+ content: [
135
+ {
136
+ type: "text",
137
+ text: "No calendar sources are connected to Skylight.",
138
+ },
139
+ ],
140
+ };
141
+ }
142
+ const calendarList = calendars
143
+ .map((cal) => {
144
+ const attrs = cal.attributes;
145
+ const parts = [`- Calendar (ID: ${cal.id})`];
146
+ for (const [key, value] of Object.entries(attrs)) {
147
+ if (value !== null && value !== undefined) {
148
+ parts.push(` ${key}: ${value}`);
149
+ }
150
+ }
151
+ return parts.join("\n");
152
+ })
153
+ .join("\n\n");
154
+ return {
155
+ content: [
156
+ {
157
+ type: "text",
158
+ text: `Connected calendar sources:\n\n${calendarList}`,
159
+ },
160
+ ],
161
+ };
162
+ }
163
+ catch (error) {
164
+ return {
165
+ content: [
166
+ {
167
+ type: "text",
168
+ text: formatErrorForMcp(error),
169
+ },
170
+ ],
171
+ isError: true,
172
+ };
173
+ }
174
+ });
175
+ // create_calendar_event tool
176
+ server.tool("create_calendar_event", `Create a new calendar event in Skylight.
177
+
178
+ Use this when:
179
+ - Scheduling a new event: "Add a dentist appointment on Friday at 2pm"
180
+ - Creating family activities: "Schedule soccer practice every Saturday at 10am"
181
+ - Adding reminders: "Put Mom's birthday on the calendar"
182
+
183
+ Parameters:
184
+ - summary (required): Event title (e.g., "Dentist Appointment")
185
+ - startsAt (required): Start time in ISO format or natural language
186
+ - endsAt (required): End time in ISO format or natural language
187
+ - allDay: Set to true for all-day events
188
+ - description: Additional notes for the event
189
+ - location: Where the event takes place
190
+ - profiles: Profile names to assign (e.g. ["Alex"]); categoryIds is the ID fallback
191
+
192
+ Returns: The created event details.
193
+
194
+ Related: get_family_members lists the profile names and IDs.`, {
195
+ summary: z.string().describe("Event title (e.g., 'Dentist Appointment')"),
196
+ startsAt: z.string().describe("Start time (ISO format like '2025-01-15T14:00:00')"),
197
+ endsAt: z.string().describe("End time (ISO format like '2025-01-15T15:00:00')"),
198
+ allDay: z.boolean().optional().default(false).describe("True for all-day events"),
199
+ description: z.string().optional().describe("Additional notes for the event"),
200
+ location: z.string().optional().describe("Event location"),
201
+ profiles: z.array(z.string()).optional().describe("Profile names to assign, e.g. [\"Alex\", \"Jordan\"] (see get_family_members)."),
202
+ categoryIds: z.array(z.string()).optional().describe("Profile IDs to assign. Prefer profiles."),
203
+ }, async ({ summary, startsAt, endsAt, allDay, description, location, profiles, categoryIds }) => {
204
+ try {
205
+ const config = getConfig();
206
+ if (profiles?.length && categoryIds?.length)
207
+ throw new Error("Give profiles or categoryIds, not both.");
208
+ const resolved = profiles?.length ? (await resolveProfileIds(profiles)).ids : categoryIds;
209
+ const event = await createCalendarEvent({
210
+ summary,
211
+ starts_at: withFrameOffset(startsAt, config.timezone),
212
+ ends_at: withFrameOffset(endsAt, config.timezone),
213
+ all_day: allDay,
214
+ description,
215
+ location,
216
+ category_ids: resolved,
217
+ timezone: config.timezone,
218
+ kind: "standard",
219
+ });
220
+ return {
221
+ content: [
222
+ {
223
+ type: "text",
224
+ text: `Created calendar event "${summary}" (ID: ${event.id})`,
225
+ },
226
+ ],
227
+ };
228
+ }
229
+ catch (error) {
230
+ return {
231
+ content: [{ type: "text", text: formatErrorForMcp(error) }],
232
+ isError: true,
233
+ };
234
+ }
235
+ });
236
+ // update_calendar_event tool
237
+ server.tool("update_calendar_event", `Update an existing calendar event.
238
+
239
+ Use this when:
240
+ - Changing event time: "Move the dentist appointment to 3pm"
241
+ - Updating event details: "Add location to the meeting"
242
+ - Renaming an event: "Change 'Doctor' to 'Dr. Smith checkup'"
243
+
244
+ Parameters:
245
+ - eventId (required): ID of the event to update (from get_calendar_events)
246
+ - summary: New title for the event
247
+ - startsAt: New start time (ISO format)
248
+ - endsAt: New end time (ISO format)
249
+ - description: Updated notes
250
+ - location: Updated location
251
+ - categoryIds: Updated family member assignments
252
+
253
+ Returns: The updated event details.`, {
254
+ eventId: z.string().describe("ID of the event to update"),
255
+ summary: z.string().optional().describe("New event title"),
256
+ startsAt: z.string().optional().describe("New start time (ISO format)"),
257
+ endsAt: z.string().optional().describe("New end time (ISO format)"),
258
+ allDay: z.boolean().optional().describe("Change to all-day event"),
259
+ description: z.string().optional().describe("Updated notes"),
260
+ location: z.string().optional().describe("Updated location"),
261
+ profiles: z.array(z.string()).optional().describe("Profile names to assign instead of the current ones, e.g. [\"Alex\"]."),
262
+ categoryIds: z.array(z.string()).optional().describe("Profile IDs to assign. Prefer profiles."),
263
+ }, async ({ eventId, summary, startsAt, endsAt, allDay, description, location, profiles, categoryIds }) => {
264
+ try {
265
+ if (profiles?.length && categoryIds?.length)
266
+ throw new Error("Give profiles or categoryIds, not both.");
267
+ const updates = {};
268
+ if (summary !== undefined)
269
+ updates.summary = summary;
270
+ const tz = getConfig().timezone;
271
+ if (startsAt !== undefined)
272
+ updates.starts_at = withFrameOffset(startsAt, tz);
273
+ if (endsAt !== undefined)
274
+ updates.ends_at = withFrameOffset(endsAt, tz);
275
+ if (allDay !== undefined)
276
+ updates.all_day = allDay;
277
+ if (description !== undefined)
278
+ updates.description = description;
279
+ if (location !== undefined)
280
+ updates.location = location;
281
+ if (profiles?.length)
282
+ updates.category_ids = (await resolveProfileIds(profiles)).ids;
283
+ else if (categoryIds !== undefined)
284
+ updates.category_ids = categoryIds;
285
+ const event = await updateCalendarEvent(eventId, updates);
286
+ return {
287
+ content: [
288
+ {
289
+ type: "text",
290
+ text: `Updated calendar event (ID: ${event.id})`,
291
+ },
292
+ ],
293
+ };
294
+ }
295
+ catch (error) {
296
+ return {
297
+ content: [{ type: "text", text: formatErrorForMcp(error) }],
298
+ isError: true,
299
+ };
300
+ }
301
+ });
302
+ // delete_calendar_event tool
303
+ server.tool("delete_calendar_event", `Delete a calendar event from Skylight.
304
+
305
+ Use this when:
306
+ - Canceling an event: "Remove the dentist appointment"
307
+ - Deleting old events: "Delete the meeting from yesterday"
308
+
309
+ Parameters:
310
+ - eventId (required): ID of the event to delete (from get_calendar_events)
311
+
312
+ Note: This permanently removes the event. For recurring events, this may only delete one instance.`, {
313
+ eventId: z.string().describe("ID of the event to delete"),
314
+ }, async ({ eventId }) => {
315
+ try {
316
+ await deleteCalendarEvent(eventId);
317
+ return {
318
+ content: [
319
+ {
320
+ type: "text",
321
+ text: `Deleted calendar event (ID: ${eventId})`,
322
+ },
323
+ ],
324
+ };
325
+ }
326
+ catch (error) {
327
+ return {
328
+ content: [{ type: "text", text: formatErrorForMcp(error) }],
329
+ isError: true,
330
+ };
331
+ }
332
+ });
333
+ }
334
+ //# sourceMappingURL=calendar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"calendar.js","sourceRoot":"","sources":["../../src/tools/calendar.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerChoreTools(server: McpServer): void;
3
+ //# sourceMappingURL=chores.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chores.d.ts","sourceRoot":"","sources":["../../src/tools/chores.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAQzE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAwV1D"}
@@ -0,0 +1,321 @@
1
+ import { z } from "zod";
2
+ import { getChores, createChore, updateChore, deleteChore, isChoreDone } from "../api/endpoints/chores.js";
3
+ import { resolveProfile } from "../api/endpoints/categories.js";
4
+ import { getTodayDate, getDateOffset, parseDate, parseTime, formatDateForDisplay } from "../utils/dates.js";
5
+ import { formatErrorForMcp } from "../utils/errors.js";
6
+ import { getConfig } from "../config.js";
7
+ export function registerChoreTools(server) {
8
+ // get_chores tool
9
+ server.tool("get_chores", `Get chores from Skylight.
10
+
11
+ Use this to answer:
12
+ - "What chores do I need to do today?"
13
+ - "Show me this week's chores"
14
+ - "What's on the chore chart?"
15
+ - "What chores does [name] have?"
16
+
17
+ Returns chores with their assignees, due dates, and completion status.`, {
18
+ date: z
19
+ .string()
20
+ .optional()
21
+ .describe("Start date (YYYY-MM-DD or 'today'). Defaults to today."),
22
+ dateEnd: z
23
+ .string()
24
+ .optional()
25
+ .describe("End date (YYYY-MM-DD). Defaults to 7 days from start."),
26
+ includeLate: z
27
+ .boolean()
28
+ .optional()
29
+ .default(true)
30
+ .describe("Include overdue chores from past dates"),
31
+ assignee: z
32
+ .string()
33
+ .optional()
34
+ .describe("Filter by family member name (e.g., 'Casey', 'Mom')"),
35
+ status: z
36
+ .enum(["pending", "completed", "all"])
37
+ .optional()
38
+ .default("pending")
39
+ .describe("Filter by completion status"),
40
+ }, async ({ date, dateEnd, includeLate, assignee, status }) => {
41
+ try {
42
+ const config = getConfig();
43
+ const startDate = date ? parseDate(date, config.timezone) : getTodayDate(config.timezone);
44
+ const endDate = dateEnd ? parseDate(dateEnd, config.timezone) : getDateOffset(7, config.timezone);
45
+ const result = await getChores({
46
+ after: startDate,
47
+ before: endDate,
48
+ includeLate: includeLate ?? true,
49
+ includeUpForGrabs: true,
50
+ });
51
+ let chores = result.chores;
52
+ // Filter by status
53
+ if (status !== "all") {
54
+ const wantDone = status === "completed";
55
+ chores = chores.filter((chore) => isChoreDone(chore.attributes.status) === wantDone);
56
+ }
57
+ // Build category lookup for assignee names
58
+ const categoryMap = new Map(result.categories.map((c) => [c.id, c.attributes.label ?? "Unknown"]));
59
+ // Filter by assignee if specified: exact profile name, so "Ben" never matches "Benjamin"
60
+ if (assignee) {
61
+ const wanted = (await resolveProfile(assignee)).id;
62
+ chores = chores.filter((chore) => chore.relationships?.category?.data?.id === wanted);
63
+ }
64
+ if (chores.length === 0) {
65
+ return {
66
+ content: [
67
+ {
68
+ type: "text",
69
+ text: `No ${status === "all" ? "" : status + " "}chores found${assignee ? ` for ${assignee}` : ""}.`,
70
+ },
71
+ ],
72
+ };
73
+ }
74
+ // Format chores for display
75
+ const choreList = chores
76
+ .map((chore) => {
77
+ const attrs = chore.attributes;
78
+ const categoryId = chore.relationships?.category?.data?.id;
79
+ const assigneeName = categoryId ? categoryMap.get(categoryId) : null;
80
+ const parts = [
81
+ `- ${attrs.summary}`,
82
+ ` Date: ${formatDateForDisplay(attrs.start)}${attrs.start_time ? ` at ${attrs.start_time}` : ""}`,
83
+ ` Status: ${attrs.status}`,
84
+ ];
85
+ if (assigneeName) {
86
+ parts.push(` Assigned to: ${assigneeName}`);
87
+ }
88
+ else if (attrs.up_for_grabs) {
89
+ parts.push(` Assigned to: nobody (up for grabs)`);
90
+ }
91
+ if (attrs.recurring) {
92
+ parts.push(` Recurring: Yes${attrs.recurrence_set ? ` (${attrs.recurrence_set})` : ""}`);
93
+ }
94
+ if (attrs.reward_points) {
95
+ parts.push(` Reward points: ${attrs.reward_points}`);
96
+ }
97
+ return parts.join("\n");
98
+ })
99
+ .join("\n\n");
100
+ return {
101
+ content: [
102
+ {
103
+ type: "text",
104
+ text: `Chores:\n\n${choreList}`,
105
+ },
106
+ ],
107
+ };
108
+ }
109
+ catch (error) {
110
+ return {
111
+ content: [
112
+ {
113
+ type: "text",
114
+ text: formatErrorForMcp(error),
115
+ },
116
+ ],
117
+ isError: true,
118
+ };
119
+ }
120
+ });
121
+ // create_chore tool
122
+ server.tool("create_chore", `Add a new chore to Skylight.
123
+
124
+ Use this when the user wants to:
125
+ - Add a new task like "empty the dishwasher"
126
+ - Assign chores to family members
127
+ - Create recurring chores
128
+
129
+ The chore will appear on the Skylight display.`, {
130
+ summary: z.string().describe("Chore description (e.g., 'Empty the dishwasher')"),
131
+ date: z
132
+ .string()
133
+ .optional()
134
+ .describe("Due date (YYYY-MM-DD or 'today', 'tomorrow', day name). Defaults to today."),
135
+ time: z
136
+ .string()
137
+ .optional()
138
+ .describe("Due time (e.g., '10:00 AM', '14:30'). Optional."),
139
+ assignee: z
140
+ .string()
141
+ .optional()
142
+ .describe("Family member to assign (e.g., 'Casey', 'Mom', 'Kids')"),
143
+ recurring: z
144
+ .boolean()
145
+ .optional()
146
+ .default(false)
147
+ .describe("Is this a recurring chore?"),
148
+ recurrencePattern: z
149
+ .string()
150
+ .optional()
151
+ .describe("For recurring: 'daily', 'weekly', 'weekdays', or RRULE string"),
152
+ rewardPoints: z
153
+ .number()
154
+ .optional()
155
+ .describe("Reward points for completing this chore"),
156
+ }, async ({ summary, date, time, assignee, recurring, recurrencePattern, rewardPoints }) => {
157
+ try {
158
+ const config = getConfig();
159
+ const choreDate = date ? parseDate(date, config.timezone) : getTodayDate(config.timezone);
160
+ // Resolve assignee to category ID
161
+ let categoryId;
162
+ if (assignee)
163
+ categoryId = (await resolveProfile(assignee)).id;
164
+ // Convert simple recurrence patterns to RRULE
165
+ let recurrenceSet;
166
+ if (recurring && recurrencePattern) {
167
+ const pattern = recurrencePattern.toLowerCase();
168
+ if (pattern === "daily") {
169
+ recurrenceSet = "RRULE:FREQ=DAILY";
170
+ }
171
+ else if (pattern === "weekly") {
172
+ recurrenceSet = "RRULE:FREQ=WEEKLY";
173
+ }
174
+ else if (pattern === "weekdays") {
175
+ recurrenceSet = "RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR";
176
+ }
177
+ else if (pattern.startsWith("RRULE:")) {
178
+ recurrenceSet = pattern;
179
+ }
180
+ else {
181
+ recurrenceSet = recurrencePattern;
182
+ }
183
+ }
184
+ const chore = await createChore({
185
+ summary,
186
+ start: choreDate,
187
+ startTime: time ? parseTime(time) : undefined,
188
+ categoryId,
189
+ recurring: recurring ?? false,
190
+ recurrenceSet,
191
+ rewardPoints,
192
+ });
193
+ const parts = [
194
+ `Created chore: "${chore.attributes.summary}"`,
195
+ `Date: ${formatDateForDisplay(chore.attributes.start)}${chore.attributes.start_time ? ` at ${chore.attributes.start_time}` : ""}`,
196
+ ];
197
+ if (assignee) {
198
+ parts.push(`Assigned to: ${assignee}`);
199
+ }
200
+ if (chore.attributes.recurring) {
201
+ parts.push(`Recurring: Yes`);
202
+ }
203
+ return {
204
+ content: [
205
+ {
206
+ type: "text",
207
+ text: parts.join("\n"),
208
+ },
209
+ ],
210
+ };
211
+ }
212
+ catch (error) {
213
+ return {
214
+ content: [
215
+ {
216
+ type: "text",
217
+ text: formatErrorForMcp(error),
218
+ },
219
+ ],
220
+ isError: true,
221
+ };
222
+ }
223
+ });
224
+ // update_chore tool
225
+ server.tool("update_chore", `Update an existing chore in Skylight.
226
+
227
+ Use this when:
228
+ - Marking a chore as complete: "Mark 'dishes' as done"
229
+ - Changing chore assignment: "Reassign the trash to Casey"
230
+ - Updating chore details: "Change the time for the homework chore"
231
+
232
+ Parameters:
233
+ - choreId (required): ID of the chore (from get_chores)
234
+ - summary: New description for the chore
235
+ - status: "completed" to mark done, "pending" to mark incomplete
236
+ - date: New due date
237
+ - time: New due time
238
+ - assignee: New family member assignment
239
+
240
+ Returns: The updated chore details.`, {
241
+ choreId: z.string().describe("ID of the chore to update"),
242
+ summary: z.string().optional().describe("New chore description"),
243
+ status: z.enum(["pending", "completed"]).optional().describe("'completed' to mark done, 'pending' to mark incomplete"),
244
+ date: z.string().optional().describe("New due date (YYYY-MM-DD or 'today', 'tomorrow')"),
245
+ time: z.string().nullable().optional().describe("New due time (e.g., '10:00 AM', or null to clear)"),
246
+ assignee: z.string().nullable().optional().describe("New family member assignment (or null to unassign)"),
247
+ rewardPoints: z.number().nullable().optional().describe("New reward points (or null to clear)"),
248
+ }, async ({ choreId, summary, status, date, time, assignee, rewardPoints }) => {
249
+ try {
250
+ const config = getConfig();
251
+ const updates = {};
252
+ if (summary !== undefined)
253
+ updates.summary = summary;
254
+ if (status !== undefined)
255
+ updates.status = status;
256
+ if (date !== undefined)
257
+ updates.start = parseDate(date, config.timezone);
258
+ if (time !== undefined)
259
+ updates.startTime = time ? parseTime(time) : null;
260
+ if (rewardPoints !== undefined)
261
+ updates.rewardPoints = rewardPoints;
262
+ // Handle assignee
263
+ if (assignee !== undefined) {
264
+ if (assignee === null) {
265
+ updates.categoryId = null;
266
+ }
267
+ else {
268
+ updates.categoryId = (await resolveProfile(assignee)).id;
269
+ }
270
+ }
271
+ const chore = await updateChore(choreId, updates);
272
+ const statusText = status === "completed" ? " (marked complete)" : status === "pending" ? " (marked pending)" : "";
273
+ return {
274
+ content: [
275
+ {
276
+ type: "text",
277
+ text: `Updated chore: "${chore.attributes.summary}"${statusText}`,
278
+ },
279
+ ],
280
+ };
281
+ }
282
+ catch (error) {
283
+ return {
284
+ content: [{ type: "text", text: formatErrorForMcp(error) }],
285
+ isError: true,
286
+ };
287
+ }
288
+ });
289
+ // delete_chore tool
290
+ server.tool("delete_chore", `Delete a chore from Skylight.
291
+
292
+ Use this when:
293
+ - Removing an old or irrelevant chore
294
+ - Deleting a chore that was added by mistake
295
+
296
+ Parameters:
297
+ - choreId (required): ID of the chore to delete (from get_chores)
298
+
299
+ Note: This permanently removes the chore. For recurring chores, this may only delete one instance.`, {
300
+ choreId: z.string().describe("ID of the chore to delete"),
301
+ }, async ({ choreId }) => {
302
+ try {
303
+ await deleteChore(choreId);
304
+ return {
305
+ content: [
306
+ {
307
+ type: "text",
308
+ text: `Deleted chore (ID: ${choreId})`,
309
+ },
310
+ ],
311
+ };
312
+ }
313
+ catch (error) {
314
+ return {
315
+ content: [{ type: "text", text: formatErrorForMcp(error) }],
316
+ isError: true,
317
+ };
318
+ }
319
+ });
320
+ }
321
+ //# sourceMappingURL=chores.js.map