@@ -254,11 +254,199 @@ async def add_task(calendar_name: str, title: str, description: str, due_date: O
254254
255255 return True
256256
257+ def list_tasks_sync (calendar_name : Optional [str ] = None ):
258+ principal = ncSync .cal .principal ()
259+ calendars = principal .calendars ()
260+
261+ tasks = []
262+ if calendar_name :
263+ calendar = {cal .name : cal for cal in calendars }[calendar_name ]
264+ calendars_to_check = [calendar ]
265+ else :
266+ calendars_to_check = calendars
267+
268+ for cal in calendars_to_check :
269+ todos = cal .todos ()
270+ for todo in todos :
271+ # Parse the todo data using ics library
272+ try :
273+ ical_data = todo .data
274+ parsed_cal = Calendar (ical_data )
275+
276+ for ics_todo in parsed_cal .todos :
277+ task_data = {
278+ 'calendar' : cal .name ,
279+ 'summary' : ics_todo .name or '' ,
280+ 'uid' : ics_todo .uid or '' ,
281+ 'status' : ics_todo .status or 'NEEDS-ACTION' ,
282+ 'due' : str (ics_todo .due ) if ics_todo .due else None ,
283+ 'priority' : ics_todo .priority ,
284+ 'description' : ics_todo .description or '' ,
285+ }
286+ tasks .append (task_data )
287+ except :
288+ # Fallback if parsing fails
289+ continue
290+
291+ return tasks
292+
293+ @tool
294+ @safe_tool
295+ async def list_tasks (calendar_name : Optional [str ] = None , filter_status : Optional [str ] = None ):
296+ """
297+ List tasks from calendars. Can filter by calendar name and status.
298+ :param calendar_name: Optional name of the calendar to list tasks from (obtainable via list_calendars). If not provided, lists from all calendars.
299+ :param filter_status: Optional filter by status - one of: 'NEEDS-ACTION', 'COMPLETED', 'IN-PROCESS', 'CANCELLED'
300+ :return: list of tasks with their details
301+ """
302+ tasks = await asyncio .to_thread (list_tasks_sync , calendar_name )
303+
304+ if filter_status :
305+ tasks = [t for t in tasks if t .get ('status' ) == filter_status ]
306+
307+ return tasks
308+
309+ def complete_task_sync (calendar_name : str , task_uid : str ):
310+ principal = ncSync .cal .principal ()
311+ calendars = principal .calendars ()
312+ calendar = {cal .name : cal for cal in calendars }[calendar_name ]
313+
314+ todos = calendar .todos ()
315+ for todo in todos :
316+ # Parse the todo data using ics library
317+ try :
318+ ical_data = todo .data
319+ parsed_cal = Calendar (ical_data )
320+
321+ for ics_todo in parsed_cal .todos :
322+ if ics_todo .uid == task_uid :
323+ # Mark as completed
324+ ics_todo .status = 'COMPLETED'
325+ ics_todo .completed = datetime .now (timezone .utc )
326+
327+ # Serialize and save
328+ todo .data = str (parsed_cal )
329+ todo .save ()
330+ return True
331+ except :
332+ continue
333+
334+ return False
335+
336+ @tool
337+ @dangerous_tool
338+ async def complete_task (calendar_name : str , task_uid : str ):
339+ """
340+ Mark a task as completed
341+ :param calendar_name: The name of the calendar containing the task (obtainable via list_calendars)
342+ :param task_uid: The UID of the task to complete (obtainable via list_tasks)
343+ :return: bool indicating success
344+ """
345+ return await asyncio .to_thread (complete_task_sync , calendar_name , task_uid )
346+
347+ def update_task_sync (calendar_name : str , task_uid : str , title : Optional [str ] = None , description : Optional [str ] = None , due_date : Optional [str ] = None , due_time : Optional [str ] = None , timezone_str : Optional [str ] = None , priority : Optional [int ] = None ):
348+ principal = ncSync .cal .principal ()
349+ calendars = principal .calendars ()
350+ calendar = {cal .name : cal for cal in calendars }[calendar_name ]
351+
352+ todos = calendar .todos ()
353+ for todo in todos :
354+ # Parse the todo data using ics library
355+ try :
356+ ical_data = todo .data
357+ parsed_cal = Calendar (ical_data )
358+
359+ for ics_todo in parsed_cal .todos :
360+ if ics_todo .uid == task_uid :
361+ # Update fields if provided
362+ if title :
363+ ics_todo .name = title
364+ if description :
365+ ics_todo .description = description
366+ if priority is not None :
367+ ics_todo .priority = priority
368+ if due_date :
369+ parsed_date = datetime .strptime (due_date , "%Y-%m-%d" )
370+ if due_time :
371+ parsed_time = datetime .strptime (due_time , "%I:%M %p" ).time ()
372+ due_datetime = datetime .combine (parsed_date , parsed_time )
373+ else :
374+ due_datetime = parsed_date
375+
376+ if timezone_str :
377+ tz = pytz .timezone (timezone_str )
378+ due_datetime = tz .localize (due_datetime )
379+
380+ ics_todo .due = due_datetime
381+
382+ # Serialize and save
383+ todo .data = str (parsed_cal )
384+ todo .save ()
385+ return True
386+ except :
387+ continue
388+
389+ return False
390+
391+ @tool
392+ @dangerous_tool
393+ async def update_task (calendar_name : str , task_uid : str , title : Optional [str ] = None , description : Optional [str ] = None , due_date : Optional [str ] = None , due_time : Optional [str ] = None , timezone : Optional [str ] = None , priority : Optional [int ] = None ):
394+ """
395+ Update an existing task
396+ :param calendar_name: The name of the calendar containing the task (obtainable via list_calendars)
397+ :param task_uid: The UID of the task to update (obtainable via list_tasks)
398+ :param title: New title for the task
399+ :param description: New description for the task
400+ :param due_date: New due date in the form: YYYY-MM-DD e.g. '2024-12-01'
401+ :param due_time: New due time in the form: HH:MM AM/PM e.g. '3:00 PM'
402+ :param timezone: Timezone (e.g., 'America/New_York')
403+ :param priority: Priority from 0 (undefined) to 9 (lowest), where 1 is highest priority
404+ :return: bool indicating success
405+ """
406+ return await asyncio .to_thread (update_task_sync , calendar_name , task_uid , title , description , due_date , due_time , timezone , priority )
407+
408+ def delete_task_sync (calendar_name : str , task_uid : str ):
409+ principal = ncSync .cal .principal ()
410+ calendars = principal .calendars ()
411+ calendar = {cal .name : cal for cal in calendars }[calendar_name ]
412+
413+ todos = calendar .todos ()
414+ for todo in todos :
415+ # Parse the todo data using ics library to find the right one
416+ try :
417+ ical_data = todo .data
418+ parsed_cal = Calendar (ical_data )
419+
420+ for ics_todo in parsed_cal .todos :
421+ if ics_todo .uid == task_uid :
422+ # Delete the todo
423+ todo .delete ()
424+ return True
425+ except :
426+ continue
427+
428+ return False
429+
430+ @tool
431+ @dangerous_tool
432+ async def delete_task (calendar_name : str , task_uid : str ):
433+ """
434+ Delete a task
435+ :param calendar_name: The name of the calendar containing the task (obtainable via list_calendars)
436+ :param task_uid: The UID of the task to delete (obtainable via list_tasks)
437+ :return: bool indicating success
438+ """
439+ return await asyncio .to_thread (delete_task_sync , calendar_name , task_uid )
440+
257441 return [
258442 list_calendars ,
259443 schedule_event ,
260444 find_free_time_slot_in_calendar ,
261- add_task
445+ add_task ,
446+ list_tasks ,
447+ complete_task ,
448+ update_task ,
449+ delete_task
262450 ]
263451
264452def get_category_name ():
0 commit comments