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