@shin1ohno/sage 0.7.9 → 0.8.4

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 (43) hide show
  1. package/README.md +26 -8
  2. package/dist/config/loader.d.ts.map +1 -1
  3. package/dist/config/loader.js +30 -1
  4. package/dist/config/loader.js.map +1 -1
  5. package/dist/config/validation.d.ts +130 -0
  6. package/dist/config/validation.d.ts.map +1 -0
  7. package/dist/config/validation.js +53 -0
  8. package/dist/config/validation.js.map +1 -0
  9. package/dist/index.js +677 -214
  10. package/dist/index.js.map +1 -1
  11. package/dist/integrations/calendar-service.d.ts +6 -3
  12. package/dist/integrations/calendar-service.d.ts.map +1 -1
  13. package/dist/integrations/calendar-service.js +26 -4
  14. package/dist/integrations/calendar-service.js.map +1 -1
  15. package/dist/integrations/calendar-source-manager.d.ts +302 -0
  16. package/dist/integrations/calendar-source-manager.d.ts.map +1 -0
  17. package/dist/integrations/calendar-source-manager.js +862 -0
  18. package/dist/integrations/calendar-source-manager.js.map +1 -0
  19. package/dist/integrations/google-calendar-service.d.ts +176 -0
  20. package/dist/integrations/google-calendar-service.d.ts.map +1 -0
  21. package/dist/integrations/google-calendar-service.js +745 -0
  22. package/dist/integrations/google-calendar-service.js.map +1 -0
  23. package/dist/oauth/google-oauth-handler.d.ts +149 -0
  24. package/dist/oauth/google-oauth-handler.d.ts.map +1 -0
  25. package/dist/oauth/google-oauth-handler.js +365 -0
  26. package/dist/oauth/google-oauth-handler.js.map +1 -0
  27. package/dist/types/config.d.ts +15 -0
  28. package/dist/types/config.d.ts.map +1 -1
  29. package/dist/types/config.js +21 -0
  30. package/dist/types/config.js.map +1 -1
  31. package/dist/types/google-calendar-types.d.ts +139 -0
  32. package/dist/types/google-calendar-types.d.ts.map +1 -0
  33. package/dist/types/google-calendar-types.js +46 -0
  34. package/dist/types/google-calendar-types.js.map +1 -0
  35. package/dist/types/index.d.ts +1 -0
  36. package/dist/types/index.d.ts.map +1 -1
  37. package/dist/types/index.js +1 -0
  38. package/dist/types/index.js.map +1 -1
  39. package/dist/version.d.ts +2 -2
  40. package/dist/version.d.ts.map +1 -1
  41. package/dist/version.js +22 -4
  42. package/dist/version.js.map +1 -1
  43. package/package.json +4 -3
package/dist/index.js CHANGED
@@ -13,12 +13,12 @@ import { SetupWizard } from "./setup/wizard.js";
13
13
  import { TaskAnalyzer } from "./tools/analyze-tasks.js";
14
14
  import { ReminderManager } from "./integrations/reminder-manager.js";
15
15
  import { CalendarService } from "./integrations/calendar-service.js";
16
+ import { CalendarSourceManager } from "./integrations/calendar-source-manager.js";
17
+ import { GoogleCalendarService } from "./integrations/google-calendar-service.js";
16
18
  import { NotionMCPService } from "./integrations/notion-mcp.js";
17
19
  import { TodoListManager } from "./integrations/todo-list-manager.js";
18
20
  import { TaskSynchronizer } from "./integrations/task-synchronizer.js";
19
21
  import { CalendarEventResponseService } from "./integrations/calendar-event-response.js";
20
- import { CalendarEventCreatorService } from "./integrations/calendar-event-creator.js";
21
- import { CalendarEventDeleterService } from "./integrations/calendar-event-deleter.js";
22
22
  import { WorkingCadenceService } from "./services/working-cadence.js";
23
23
  import { VERSION, SERVER_NAME } from "./version.js";
24
24
  // Global state
@@ -26,12 +26,12 @@ let config = null;
26
26
  let wizardSession = null;
27
27
  let reminderManager = null;
28
28
  let calendarService = null;
29
+ let googleCalendarService = null;
30
+ let calendarSourceManager = null;
29
31
  let notionService = null;
30
32
  let todoListManager = null;
31
33
  let taskSynchronizer = null;
32
34
  let calendarEventResponseService = null;
33
- let calendarEventCreatorService = null;
34
- let calendarEventDeleterService = null;
35
35
  let workingCadenceService = null;
36
36
  /**
37
37
  * Validate config updates for a specific section
@@ -149,12 +149,33 @@ function initializeServices(userConfig) {
149
149
  notionDatabaseId: userConfig.integrations.notion.databaseId,
150
150
  });
151
151
  calendarService = new CalendarService();
152
+ // Initialize Google Calendar service if configured
153
+ // Note: GoogleCalendarService requires GoogleOAuthHandler which needs OAuth config
154
+ // For now, we initialize with a stub handler. Full OAuth setup will be done in Task 33.
155
+ try {
156
+ const { GoogleOAuthHandler } = require('./oauth/google-oauth-handler.js');
157
+ const oauthConfig = {
158
+ clientId: process.env.GOOGLE_CLIENT_ID || '',
159
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET || '',
160
+ redirectUri: process.env.GOOGLE_REDIRECT_URI || 'http://localhost:3000/oauth/callback',
161
+ };
162
+ const oauthHandler = new GoogleOAuthHandler(oauthConfig);
163
+ googleCalendarService = new GoogleCalendarService(oauthHandler);
164
+ }
165
+ catch (error) {
166
+ // If Google Calendar initialization fails, continue without it
167
+ console.error('Google Calendar service initialization failed:', error);
168
+ googleCalendarService = null;
169
+ }
170
+ calendarSourceManager = new CalendarSourceManager({
171
+ calendarService,
172
+ googleCalendarService: googleCalendarService || undefined,
173
+ config: userConfig,
174
+ });
152
175
  notionService = new NotionMCPService();
153
176
  todoListManager = new TodoListManager();
154
177
  taskSynchronizer = new TaskSynchronizer();
155
178
  calendarEventResponseService = new CalendarEventResponseService();
156
- calendarEventCreatorService = new CalendarEventCreatorService();
157
- calendarEventDeleterService = new CalendarEventDeleterService();
158
179
  workingCadenceService = new WorkingCadenceService();
159
180
  }
160
181
  /**
@@ -622,9 +643,9 @@ async function createServer() {
622
643
  });
623
644
  /**
624
645
  * find_available_slots - Find available time slots in calendar
625
- * Requirement: 3.3-3.6, 6.1-6.6
646
+ * Requirement: 3.3-3.6, 6.1-6.6, 7 (Task 28: Multi-source support)
626
647
  */
627
- server.tool("find_available_slots", "Find available time slots in the calendar for scheduling tasks.", {
648
+ server.tool("find_available_slots", "Find available time slots in the calendar for scheduling tasks from all enabled calendar sources.", {
628
649
  durationMinutes: z.number().describe("Required duration in minutes"),
629
650
  startDate: z
630
651
  .string()
@@ -638,7 +659,15 @@ async function createServer() {
638
659
  .boolean()
639
660
  .optional()
640
661
  .describe("Prefer deep work time slots"),
641
- }, async ({ durationMinutes, startDate, endDate, preferDeepWork }) => {
662
+ minDurationMinutes: z
663
+ .number()
664
+ .optional()
665
+ .describe("Minimum slot duration in minutes (default: 25)"),
666
+ maxDurationMinutes: z
667
+ .number()
668
+ .optional()
669
+ .describe("Maximum slot duration in minutes (default: 480)"),
670
+ }, async ({ durationMinutes, startDate, endDate, preferDeepWork, minDurationMinutes, maxDurationMinutes }) => {
642
671
  if (!config) {
643
672
  return {
644
673
  content: [
@@ -652,70 +681,62 @@ async function createServer() {
652
681
  ],
653
682
  };
654
683
  }
655
- if (!calendarService) {
684
+ if (!calendarSourceManager) {
656
685
  initializeServices(config);
657
686
  }
658
687
  try {
659
- // Check platform availability
660
- const platformInfo = await calendarService.detectPlatform();
661
- const isAvailable = await calendarService.isAvailable();
662
- if (!isAvailable) {
663
- // Return manual input prompt for unsupported platforms
664
- const manualPrompt = calendarService.generateManualInputPrompt(startDate ?? new Date().toISOString().split("T")[0], endDate ??
665
- new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
666
- .toISOString()
667
- .split("T")[0]);
688
+ // Get enabled sources
689
+ const enabledSources = calendarSourceManager.getEnabledSources();
690
+ if (enabledSources.length === 0) {
668
691
  return {
669
692
  content: [
670
693
  {
671
694
  type: "text",
672
695
  text: JSON.stringify({
673
696
  success: false,
674
- platform: platformInfo.platform,
675
- method: platformInfo.recommendedMethod,
676
- message: "カレンダー統合がこのプラットフォームで利用できません。手動で予定を入力してください。",
677
- manualPrompt,
697
+ message: "有効なカレンダーソースがありません。設定でEventKitまたはGoogle Calendarを有効にしてください。",
678
698
  }, null, 2),
679
699
  },
680
700
  ],
681
701
  };
682
702
  }
683
- // Fetch events from calendar
703
+ // Prepare date range
684
704
  const searchStart = startDate ?? new Date().toISOString().split("T")[0];
685
705
  const searchEnd = endDate ??
686
706
  new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
687
707
  .toISOString()
688
708
  .split("T")[0];
689
- const events = await calendarService.fetchEvents(searchStart, searchEnd);
690
- // Find available slots
709
+ // Get working hours from config
691
710
  const workingHours = {
692
711
  start: config.calendar.workingHours.start,
693
712
  end: config.calendar.workingHours.end,
694
713
  };
695
- const slots = calendarService.findAvailableSlotsFromEvents(events, durationMinutes, workingHours, searchStart);
696
- // Apply suitability scoring
697
- const suitabilityConfig = {
698
- deepWorkDays: config.calendar.deepWorkDays,
699
- meetingHeavyDays: config.calendar.meetingHeavyDays,
700
- };
701
- const scoredSlots = slots.map((slot) => calendarService.calculateSuitability(slot, suitabilityConfig));
714
+ // Use durationMinutes as default min duration for backwards compatibility
715
+ // New parameters minDurationMinutes and maxDurationMinutes take precedence
716
+ const minDuration = minDurationMinutes ?? durationMinutes ?? 25;
717
+ const maxDuration = maxDurationMinutes ?? 480;
718
+ // Find available slots using CalendarSourceManager
719
+ // This automatically fetches from all enabled sources, merges, and deduplicates
720
+ const slots = await calendarSourceManager.findAvailableSlots({
721
+ startDate: searchStart,
722
+ endDate: searchEnd,
723
+ minDurationMinutes: minDuration,
724
+ maxDurationMinutes: maxDuration,
725
+ workingHours,
726
+ });
702
727
  // Filter for deep work preference if requested
703
728
  const filteredSlots = preferDeepWork
704
- ? scoredSlots.filter((s) => s.dayType === "deep-work")
705
- : scoredSlots;
706
- // Sort by suitability (excellent > good > acceptable)
707
- const suitabilityOrder = { excellent: 0, good: 1, acceptable: 2 };
708
- filteredSlots.sort((a, b) => suitabilityOrder[a.suitability] - suitabilityOrder[b.suitability]);
729
+ ? slots.filter((s) => s.dayType === "deep-work")
730
+ : slots;
709
731
  return {
710
732
  content: [
711
733
  {
712
734
  type: "text",
713
735
  text: JSON.stringify({
714
736
  success: true,
715
- platform: platformInfo.platform,
716
- method: platformInfo.recommendedMethod,
737
+ sources: enabledSources,
717
738
  searchRange: { start: searchStart, end: searchEnd },
718
- eventsFound: events.length,
739
+ totalSlots: filteredSlots.length,
719
740
  slots: filteredSlots.slice(0, 10).map((slot) => ({
720
741
  start: slot.start,
721
742
  end: slot.end,
@@ -725,7 +746,7 @@ async function createServer() {
725
746
  reason: slot.reason,
726
747
  })),
727
748
  message: filteredSlots.length > 0
728
- ? `${filteredSlots.length}件の空き時間が見つかりました。`
749
+ ? `${filteredSlots.length}件の空き時間が見つかりました (ソース: ${enabledSources.join(', ')})。`
729
750
  : "指定した条件に合う空き時間が見つかりませんでした。",
730
751
  }, null, 2),
731
752
  },
@@ -748,20 +769,20 @@ async function createServer() {
748
769
  });
749
770
  /**
750
771
  * list_calendar_events - List calendar events for a specified period
751
- * Requirement: 16.1-16.12
772
+ * Requirement: 16.1-16.12, Task 27 (Multi-source support)
752
773
  */
753
- server.tool("list_calendar_events", "List calendar events for a specified period. Returns events with details including calendar name and location.", {
774
+ server.tool("list_calendar_events", "List calendar events for a specified period from enabled sources (EventKit, Google Calendar, or both). Returns events with details including calendar name and location.", {
754
775
  startDate: z
755
776
  .string()
756
777
  .describe("Start date in ISO 8601 format (e.g., 2025-01-15)"),
757
778
  endDate: z
758
779
  .string()
759
780
  .describe("End date in ISO 8601 format (e.g., 2025-01-20)"),
760
- calendarName: z
781
+ calendarId: z
761
782
  .string()
762
783
  .optional()
763
- .describe("Optional: filter events by calendar name"),
764
- }, async ({ startDate, endDate, calendarName }) => {
784
+ .describe("Optional: filter events by calendar ID or name"),
785
+ }, async ({ startDate, endDate, calendarId }) => {
765
786
  if (!config) {
766
787
  return {
767
788
  content: [
@@ -775,55 +796,49 @@ async function createServer() {
775
796
  ],
776
797
  };
777
798
  }
778
- if (!calendarService) {
799
+ if (!calendarSourceManager) {
779
800
  initializeServices(config);
780
801
  }
781
802
  try {
782
- // Check platform availability
783
- const platformInfo = await calendarService.detectPlatform();
784
- const isAvailable = await calendarService.isAvailable();
785
- if (!isAvailable) {
803
+ // Get enabled sources
804
+ const enabledSources = calendarSourceManager.getEnabledSources();
805
+ if (enabledSources.length === 0) {
786
806
  return {
787
807
  content: [
788
808
  {
789
809
  type: "text",
790
810
  text: JSON.stringify({
791
811
  success: false,
792
- platform: platformInfo.platform,
793
- method: platformInfo.recommendedMethod,
794
- message: "カレンダー統合がこのプラットフォームで利用できません。macOSで実行してください。",
812
+ message: "有効なカレンダーソースがありません。設定でEventKitまたはGoogle Calendarを有効にしてください。",
795
813
  }, null, 2),
796
814
  },
797
815
  ],
798
816
  };
799
817
  }
800
- // List events
801
- const result = await calendarService.listEvents({
802
- startDate,
803
- endDate,
804
- calendarName,
805
- });
818
+ // Get events from all enabled sources (already merged/deduplicated)
819
+ const events = await calendarSourceManager.getEvents(startDate, endDate, calendarId);
806
820
  return {
807
821
  content: [
808
822
  {
809
823
  type: "text",
810
824
  text: JSON.stringify({
811
825
  success: true,
812
- platform: platformInfo.platform,
813
- method: platformInfo.recommendedMethod,
814
- events: result.events.map((event) => ({
826
+ sources: enabledSources,
827
+ events: events.map((event) => ({
815
828
  id: event.id,
816
829
  title: event.title,
817
830
  start: event.start,
818
831
  end: event.end,
819
832
  isAllDay: event.isAllDay,
833
+ // Note: calendar and location are optional fields added in Task 25
820
834
  calendar: event.calendar,
821
835
  location: event.location,
836
+ source: event.source,
822
837
  })),
823
- period: result.period,
824
- totalEvents: result.totalEvents,
825
- message: result.totalEvents > 0
826
- ? `${result.totalEvents}件のイベントが見つかりました。`
838
+ period: { start: startDate, end: endDate },
839
+ totalEvents: events.length,
840
+ message: events.length > 0
841
+ ? `${events.length}件のイベントが見つかりました (ソース: ${enabledSources.join(', ')})。`
827
842
  : "指定した期間にイベントが見つかりませんでした。",
828
843
  }, null, 2),
829
844
  },
@@ -846,9 +861,9 @@ async function createServer() {
846
861
  });
847
862
  /**
848
863
  * respond_to_calendar_event - Respond to a single calendar event
849
- * Requirement: 17.1, 17.2, 17.5-17.11
864
+ * Requirement: 17.1, 17.2, 17.5-17.11, 6 (Google Calendar support)
850
865
  */
851
- server.tool("respond_to_calendar_event", "Respond to a calendar event with accept, decline, or tentative. Use this to RSVP to meeting invitations.", {
866
+ server.tool("respond_to_calendar_event", "Respond to a calendar event with accept, decline, or tentative. Supports both EventKit (macOS) and Google Calendar events. Use this to RSVP to meeting invitations from any enabled calendar source.", {
852
867
  eventId: z.string().describe("The ID of the calendar event to respond to"),
853
868
  response: z
854
869
  .enum(["accept", "decline", "tentative"])
@@ -856,8 +871,16 @@ async function createServer() {
856
871
  comment: z
857
872
  .string()
858
873
  .optional()
859
- .describe("Optional comment to include with the response (e.g., '年末年始休暇のため')"),
860
- }, async ({ eventId, response, comment }) => {
874
+ .describe("Optional comment to include with the response (e.g., '年末年始休暇のため'). Note: Comments are only supported for EventKit events."),
875
+ source: z
876
+ .enum(["eventkit", "google"])
877
+ .optional()
878
+ .describe("Optional: Specify the calendar source explicitly. If not provided, will try Google Calendar first, then EventKit."),
879
+ calendarId: z
880
+ .string()
881
+ .optional()
882
+ .describe("Optional: Google Calendar ID (defaults to 'primary'). Only used for Google Calendar events."),
883
+ }, async ({ eventId, response, comment, source, calendarId }) => {
861
884
  if (!config) {
862
885
  return {
863
886
  content: [
@@ -871,64 +894,117 @@ async function createServer() {
871
894
  ],
872
895
  };
873
896
  }
874
- if (!calendarEventResponseService) {
897
+ if (!calendarSourceManager || !calendarEventResponseService) {
875
898
  initializeServices(config);
876
899
  }
877
900
  try {
878
- // Check platform availability
879
- const isAvailable = await calendarEventResponseService.isEventKitAvailable();
880
- if (!isAvailable) {
881
- return {
882
- content: [
883
- {
884
- type: "text",
885
- text: JSON.stringify({
886
- success: false,
887
- message: "カレンダーイベント返信機能はmacOSでのみ利用可能です。",
888
- }, null, 2),
889
- },
890
- ],
891
- };
901
+ // If source is explicitly Google Calendar, or if source is not specified, try Google Calendar first
902
+ if (source === 'google' || !source) {
903
+ try {
904
+ const result = await calendarSourceManager.respondToEvent(eventId, response, source === 'google' ? 'google' : undefined, calendarId);
905
+ return {
906
+ content: [
907
+ {
908
+ type: "text",
909
+ text: JSON.stringify({
910
+ success: true,
911
+ eventId,
912
+ source: result.source || 'google',
913
+ message: result.message,
914
+ }, null, 2),
915
+ },
916
+ ],
917
+ };
918
+ }
919
+ catch (error) {
920
+ // If source was explicitly 'google', don't try EventKit
921
+ if (source === 'google') {
922
+ return {
923
+ content: [
924
+ {
925
+ type: "text",
926
+ text: JSON.stringify({
927
+ error: true,
928
+ message: `Google Calendarイベント返信に失敗しました: ${error instanceof Error ? error.message : "Unknown error"}`,
929
+ }, null, 2),
930
+ },
931
+ ],
932
+ };
933
+ }
934
+ // If source was not specified, continue to try EventKit
935
+ }
892
936
  }
893
- // Respond to the event
894
- const result = await calendarEventResponseService.respondToEvent({
895
- eventId,
896
- response,
897
- comment,
898
- });
899
- if (result.success) {
937
+ // Try EventKit (either explicitly requested or as fallback)
938
+ if (source === 'eventkit' || !source) {
939
+ // Check platform availability
940
+ const isAvailable = await calendarEventResponseService.isEventKitAvailable();
941
+ if (!isAvailable) {
942
+ return {
943
+ content: [
944
+ {
945
+ type: "text",
946
+ text: JSON.stringify({
947
+ success: false,
948
+ message: "EventKitカレンダーイベント返信機能はmacOSでのみ利用可能です。Google Calendarイベントの場合は、source='google'を指定してください。",
949
+ }, null, 2),
950
+ },
951
+ ],
952
+ };
953
+ }
954
+ // Respond to the event via EventKit
955
+ const result = await calendarEventResponseService.respondToEvent({
956
+ eventId,
957
+ response,
958
+ comment,
959
+ });
960
+ if (result.success) {
961
+ return {
962
+ content: [
963
+ {
964
+ type: "text",
965
+ text: JSON.stringify({
966
+ success: true,
967
+ eventId: result.eventId,
968
+ eventTitle: result.eventTitle,
969
+ newStatus: result.newStatus,
970
+ method: result.method,
971
+ instanceOnly: result.instanceOnly,
972
+ source: 'eventkit',
973
+ message: result.message,
974
+ }, null, 2),
975
+ },
976
+ ],
977
+ };
978
+ }
979
+ // Handle skipped or failed response
900
980
  return {
901
981
  content: [
902
982
  {
903
983
  type: "text",
904
984
  text: JSON.stringify({
905
- success: true,
985
+ success: false,
906
986
  eventId: result.eventId,
907
987
  eventTitle: result.eventTitle,
908
- newStatus: result.newStatus,
909
- method: result.method,
910
- instanceOnly: result.instanceOnly,
911
- message: result.message,
988
+ skipped: result.skipped,
989
+ reason: result.reason,
990
+ error: result.error,
991
+ source: 'eventkit',
992
+ message: result.skipped
993
+ ? `イベントをスキップしました: ${result.reason}`
994
+ : `イベント返信に失敗しました: ${result.error}`,
912
995
  }, null, 2),
913
996
  },
914
997
  ],
915
998
  };
916
999
  }
917
- // Handle skipped or failed response
1000
+ // Should not reach here, but just in case
918
1001
  return {
919
1002
  content: [
920
1003
  {
921
1004
  type: "text",
922
1005
  text: JSON.stringify({
923
- success: false,
924
- eventId: result.eventId,
925
- eventTitle: result.eventTitle,
926
- skipped: result.skipped,
927
- reason: result.reason,
928
- error: result.error,
929
- message: result.skipped
930
- ? `イベントをスキップしました: ${result.reason}`
931
- : `イベント返信に失敗しました: ${result.error}`,
1006
+ error: true,
1007
+ message: "有効なカレンダーソースが見つかりません。",
932
1008
  }, null, 2),
933
1009
  },
934
1010
  ],
@@ -1036,9 +1112,9 @@ async function createServer() {
1036
1112
  });
1037
1113
  /**
1038
1114
  * create_calendar_event - Create a new calendar event
1039
- * Requirement: 18.1-18.11
1115
+ * Requirement: 18.1-18.11, Task 29 (Multi-source support)
1040
1116
  */
1041
- server.tool("create_calendar_event", "Create a new calendar event with optional location, notes, and alarms.", {
1117
+ server.tool("create_calendar_event", "Create a new calendar event in the appropriate calendar source with optional location, notes, and alarms.", {
1042
1118
  title: z.string().describe("Event title"),
1043
1119
  startDate: z
1044
1120
  .string()
@@ -1056,7 +1132,11 @@ async function createServer() {
1056
1132
  .array(z.string())
1057
1133
  .optional()
1058
1134
  .describe("Optional: Override default alarms with custom settings (e.g., ['-15m', '-1h']). If omitted, calendar's default alarm settings apply."),
1059
- }, async ({ title, startDate, endDate, location, notes, calendarName, alarms }) => {
1135
+ preferredSource: z
1136
+ .enum(['eventkit', 'google'])
1137
+ .optional()
1138
+ .describe("Preferred calendar source to create the event in. If not specified, uses the first enabled source."),
1139
+ }, async ({ title, startDate, endDate, location, notes, calendarName: _calendarName, alarms: _alarms, preferredSource }) => {
1060
1140
  if (!config) {
1061
1141
  return {
1062
1142
  content: [
@@ -1070,63 +1150,52 @@ async function createServer() {
1070
1150
  ],
1071
1151
  };
1072
1152
  }
1073
- if (!calendarEventCreatorService) {
1153
+ if (!calendarSourceManager) {
1074
1154
  initializeServices(config);
1075
1155
  }
1076
1156
  try {
1077
- // Check platform availability
1078
- const isAvailable = await calendarEventCreatorService.isEventKitAvailable();
1079
- if (!isAvailable) {
1157
+ // Get enabled sources
1158
+ const enabledSources = calendarSourceManager.getEnabledSources();
1159
+ if (enabledSources.length === 0) {
1080
1160
  return {
1081
1161
  content: [
1082
1162
  {
1083
1163
  type: "text",
1084
1164
  text: JSON.stringify({
1085
1165
  success: false,
1086
- message: "カレンダーイベント作成機能はmacOSでのみ利用可能です。",
1166
+ message: "有効なカレンダーソースがありません。設定でEventKitまたはGoogle Calendarを有効にしてください。",
1087
1167
  }, null, 2),
1088
1168
  },
1089
1169
  ],
1090
1170
  };
1091
1171
  }
1092
- // Create the event
1093
- const result = await calendarEventCreatorService.createEvent({
1172
+ // Build create event request
1173
+ const request = {
1094
1174
  title,
1095
- startDate,
1096
- endDate,
1175
+ start: startDate,
1176
+ end: endDate,
1097
1177
  location,
1098
- notes,
1099
- calendarName,
1100
- alarms,
1101
- });
1102
- if (result.success) {
1103
- return {
1104
- content: [
1105
- {
1106
- type: "text",
1107
- text: JSON.stringify({
1108
- success: true,
1109
- eventId: result.eventId,
1110
- title: result.title,
1111
- startDate: result.startDate,
1112
- endDate: result.endDate,
1113
- calendarName: result.calendarName,
1114
- isAllDay: result.isAllDay,
1115
- message: result.message,
1116
- }, null, 2),
1117
- },
1118
- ],
1119
- };
1120
- }
1121
- // Handle creation failure
1178
+ description: notes,
1179
+ // Note: calendarId is passed separately to GoogleCalendarService.createEvent
1180
+ // alarms parameter is not supported in CreateEventRequest interface (uses reminders instead)
1181
+ };
1182
+ // Create the event using CalendarSourceManager
1183
+ // This automatically routes to the preferred source or falls back to other enabled sources
1184
+ const event = await calendarSourceManager.createEvent(request, preferredSource);
1122
1185
  return {
1123
1186
  content: [
1124
1187
  {
1125
1188
  type: "text",
1126
1189
  text: JSON.stringify({
1127
- success: false,
1128
- error: result.error,
1129
- message: result.message,
1190
+ success: true,
1191
+ eventId: event.id,
1192
+ title: event.title,
1193
+ startDate: event.start,
1194
+ endDate: event.end,
1195
+ source: event.source || 'unknown',
1196
+ calendarName: event.calendar,
1197
+ isAllDay: event.isAllDay,
1198
+ message: `カレンダーイベントを作成しました: ${event.title} (ソース: ${event.source || 'unknown'})`,
1130
1199
  }, null, 2),
1131
1200
  },
1132
1201
  ],
@@ -1148,15 +1217,15 @@ async function createServer() {
1148
1217
  });
1149
1218
  /**
1150
1219
  * delete_calendar_event - Delete a calendar event
1151
- * Requirement: 19.1-19.9
1220
+ * Requirement: 19.1-19.9, Task 30 (Multi-source support)
1152
1221
  */
1153
- server.tool("delete_calendar_event", "Delete a calendar event by its ID.", {
1222
+ server.tool("delete_calendar_event", "Delete a calendar event from enabled calendar sources by its ID. If source not specified, attempts deletion from all enabled sources.", {
1154
1223
  eventId: z.string().describe("Event ID (UUID or full ID from list_calendar_events)"),
1155
- calendarName: z
1156
- .string()
1224
+ source: z
1225
+ .enum(['eventkit', 'google'])
1157
1226
  .optional()
1158
- .describe("Calendar name (searches all calendars if not specified)"),
1159
- }, async ({ eventId, calendarName }) => {
1227
+ .describe("Calendar source to delete from. If not specified, attempts deletion from all enabled sources."),
1228
+ }, async ({ eventId, source }) => {
1160
1229
  if (!config) {
1161
1230
  return {
1162
1231
  content: [
@@ -1170,55 +1239,39 @@ async function createServer() {
1170
1239
  ],
1171
1240
  };
1172
1241
  }
1173
- if (!calendarEventDeleterService) {
1242
+ if (!calendarSourceManager) {
1174
1243
  initializeServices(config);
1175
1244
  }
1176
1245
  try {
1177
- // Check platform availability
1178
- const isAvailable = await calendarEventDeleterService.isEventKitAvailable();
1179
- if (!isAvailable) {
1246
+ // Get enabled sources
1247
+ const enabledSources = calendarSourceManager.getEnabledSources();
1248
+ if (enabledSources.length === 0) {
1180
1249
  return {
1181
1250
  content: [
1182
1251
  {
1183
1252
  type: "text",
1184
1253
  text: JSON.stringify({
1185
- error: true,
1186
- message: "カレンダー統合がこのプラットフォームで利用できません。macOSで実行してください。",
1187
- }, null, 2),
1188
- },
1189
- ],
1190
- };
1191
- }
1192
- // Delete the event
1193
- const result = await calendarEventDeleterService.deleteEvent({
1194
- eventId,
1195
- calendarName,
1196
- });
1197
- if (result.success) {
1198
- return {
1199
- content: [
1200
- {
1201
- type: "text",
1202
- text: JSON.stringify({
1203
- success: true,
1204
- eventId: result.eventId,
1205
- title: result.title,
1206
- calendarName: result.calendarName,
1207
- message: result.message,
1254
+ success: false,
1255
+ message: "有効なカレンダーソースがありません。設定でEventKitまたはGoogle Calendarを有効にしてください。",
1208
1256
  }, null, 2),
1209
1257
  },
1210
1258
  ],
1211
1259
  };
1212
1260
  }
1213
- // Handle deletion failure
1261
+ // Delete the event using CalendarSourceManager
1262
+ // This automatically handles deletion from specified source or all enabled sources
1263
+ await calendarSourceManager.deleteEvent(eventId, source);
1214
1264
  return {
1215
1265
  content: [
1216
1266
  {
1217
1267
  type: "text",
1218
1268
  text: JSON.stringify({
1219
- success: false,
1220
- error: result.error,
1221
- message: result.message,
1269
+ success: true,
1270
+ eventId,
1271
+ source: source || 'all enabled sources',
1272
+ message: source
1273
+ ? `カレンダーイベントを削除しました (ソース: ${source})`
1274
+ : `カレンダーイベントを削除しました (全ての有効なソースから)`,
1222
1275
  }, null, 2),
1223
1276
  },
1224
1277
  ],
@@ -1240,15 +1293,15 @@ async function createServer() {
1240
1293
  });
1241
1294
  /**
1242
1295
  * delete_calendar_events_batch - Delete multiple calendar events
1243
- * Requirement: 19.10-19.11
1296
+ * Requirement: 19.10-19.11, Task 30 (Multi-source support)
1244
1297
  */
1245
- server.tool("delete_calendar_events_batch", "Delete multiple calendar events by their IDs.", {
1298
+ server.tool("delete_calendar_events_batch", "Delete multiple calendar events from enabled calendar sources by their IDs. If source not specified, attempts deletion from all enabled sources.", {
1246
1299
  eventIds: z.array(z.string()).describe("Array of event IDs to delete"),
1247
- calendarName: z
1248
- .string()
1300
+ source: z
1301
+ .enum(['eventkit', 'google'])
1249
1302
  .optional()
1250
- .describe("Calendar name (searches all calendars if not specified)"),
1251
- }, async ({ eventIds, calendarName }) => {
1303
+ .describe("Calendar source to delete from. If not specified, attempts deletion from all enabled sources."),
1304
+ }, async ({ eventIds, source }) => {
1252
1305
  if (!config) {
1253
1306
  return {
1254
1307
  content: [
@@ -1262,47 +1315,56 @@ async function createServer() {
1262
1315
  ],
1263
1316
  };
1264
1317
  }
1265
- if (!calendarEventDeleterService) {
1318
+ if (!calendarSourceManager) {
1266
1319
  initializeServices(config);
1267
1320
  }
1268
1321
  try {
1269
- // Check platform availability
1270
- const isAvailable = await calendarEventDeleterService.isEventKitAvailable();
1271
- if (!isAvailable) {
1322
+ // Get enabled sources
1323
+ const enabledSources = calendarSourceManager.getEnabledSources();
1324
+ if (enabledSources.length === 0) {
1272
1325
  return {
1273
1326
  content: [
1274
1327
  {
1275
1328
  type: "text",
1276
1329
  text: JSON.stringify({
1277
- error: true,
1278
- message: "カレンダー統合がこのプラットフォームで利用できません。macOSで実行してください。",
1330
+ success: false,
1331
+ message: "有効なカレンダーソースがありません。設定でEventKitまたはGoogle Calendarを有効にしてください。",
1279
1332
  }, null, 2),
1280
1333
  },
1281
1334
  ],
1282
1335
  };
1283
1336
  }
1284
- // Delete events in batch
1285
- const result = await calendarEventDeleterService.deleteEventsBatch({
1286
- eventIds,
1287
- calendarName,
1288
- });
1337
+ // Delete events one by one using CalendarSourceManager
1338
+ const results = [];
1339
+ for (const eventId of eventIds) {
1340
+ try {
1341
+ await calendarSourceManager.deleteEvent(eventId, source);
1342
+ results.push({ eventId, success: true });
1343
+ }
1344
+ catch (error) {
1345
+ results.push({
1346
+ eventId,
1347
+ success: false,
1348
+ error: error instanceof Error ? error.message : 'Unknown error',
1349
+ });
1350
+ }
1351
+ }
1352
+ const successCount = results.filter((r) => r.success).length;
1353
+ const failedCount = results.filter((r) => !r.success).length;
1289
1354
  return {
1290
1355
  content: [
1291
1356
  {
1292
1357
  type: "text",
1293
1358
  text: JSON.stringify({
1294
- success: result.success,
1295
- totalCount: result.totalCount,
1296
- successCount: result.successCount,
1297
- failedCount: result.failedCount,
1298
- results: result.results.map((r) => ({
1299
- eventId: r.eventId,
1300
- success: r.success,
1301
- title: r.title,
1302
- calendarName: r.calendarName,
1303
- error: r.error,
1304
- })),
1305
- message: result.message,
1359
+ success: failedCount === 0,
1360
+ totalCount: eventIds.length,
1361
+ successCount,
1362
+ failedCount,
1363
+ source: source || 'all enabled sources',
1364
+ results,
1365
+ message: source
1366
+ ? `${successCount}/${eventIds.length}件のイベントを削除しました (ソース: ${source})`
1367
+ : `${successCount}/${eventIds.length}件のイベントを削除しました (全ての有効なソースから)`,
1306
1368
  }, null, 2),
1307
1369
  },
1308
1370
  ],
@@ -1888,6 +1950,407 @@ async function createServer() {
1888
1950
  };
1889
1951
  }
1890
1952
  });
1953
+ /**
1954
+ * list_calendar_sources - List available and enabled calendar sources
1955
+ * Requirement: Google Calendar API integration (Task 32)
1956
+ */
1957
+ server.tool("list_calendar_sources", "List available and enabled calendar sources (EventKit, Google Calendar) with their health status. Shows which sources can be used and their current state.", {}, async () => {
1958
+ if (!config) {
1959
+ return {
1960
+ content: [
1961
+ {
1962
+ type: "text",
1963
+ text: JSON.stringify({
1964
+ error: true,
1965
+ message: "sageが設定されていません。check_setup_statusを実行してください。",
1966
+ }, null, 2),
1967
+ },
1968
+ ],
1969
+ };
1970
+ }
1971
+ if (!calendarSourceManager) {
1972
+ initializeServices(config);
1973
+ }
1974
+ try {
1975
+ // Get available sources
1976
+ const availableSources = await calendarSourceManager.detectAvailableSources();
1977
+ // Get enabled sources
1978
+ const enabledSources = calendarSourceManager.getEnabledSources();
1979
+ // Get health status
1980
+ const healthStatus = await calendarSourceManager.healthCheck();
1981
+ return {
1982
+ content: [
1983
+ {
1984
+ type: "text",
1985
+ text: JSON.stringify({
1986
+ success: true,
1987
+ sources: {
1988
+ eventkit: {
1989
+ available: availableSources.eventkit,
1990
+ enabled: enabledSources.includes('eventkit'),
1991
+ healthy: healthStatus.eventkit,
1992
+ description: "macOS EventKit calendar integration (macOS only)",
1993
+ },
1994
+ google: {
1995
+ available: availableSources.google,
1996
+ enabled: enabledSources.includes('google'),
1997
+ healthy: healthStatus.google,
1998
+ description: "Google Calendar API integration (all platforms)",
1999
+ },
2000
+ },
2001
+ summary: {
2002
+ totalAvailable: Object.values(availableSources).filter(Boolean).length,
2003
+ totalEnabled: enabledSources.length,
2004
+ allHealthy: Object.values(healthStatus).every(Boolean),
2005
+ },
2006
+ }, null, 2),
2007
+ },
2008
+ ],
2009
+ };
2010
+ }
2011
+ catch (error) {
2012
+ return {
2013
+ content: [
2014
+ {
2015
+ type: "text",
2016
+ text: JSON.stringify({
2017
+ error: true,
2018
+ message: `カレンダーソース情報の取得に失敗しました: ${error instanceof Error ? error.message : "Unknown error"}`,
2019
+ }, null, 2),
2020
+ },
2021
+ ],
2022
+ };
2023
+ }
2024
+ });
2025
+ /**
2026
+ * set_calendar_source - Enable or disable a calendar source
2027
+ * Requirement: 9, 11, Task 33
2028
+ */
2029
+ server.tool("set_calendar_source", "Enable or disable a calendar source (EventKit or Google Calendar). When enabling Google Calendar for the first time, this will initiate the OAuth flow. Returns authorization URL if OAuth is required.", {
2030
+ source: z
2031
+ .enum(['eventkit', 'google'])
2032
+ .describe("Calendar source to configure: 'eventkit' (macOS only) or 'google' (all platforms)"),
2033
+ enabled: z
2034
+ .boolean()
2035
+ .describe("Whether to enable (true) or disable (false) the source"),
2036
+ }, async ({ source, enabled }) => {
2037
+ if (!config) {
2038
+ return {
2039
+ content: [
2040
+ {
2041
+ type: "text",
2042
+ text: JSON.stringify({
2043
+ error: true,
2044
+ message: "sageが設定されていません。check_setup_statusを実行してください。",
2045
+ }, null, 2),
2046
+ },
2047
+ ],
2048
+ };
2049
+ }
2050
+ if (!calendarSourceManager) {
2051
+ initializeServices(config);
2052
+ }
2053
+ try {
2054
+ // Check if source is available on this platform
2055
+ const availableSources = await calendarSourceManager.detectAvailableSources();
2056
+ if (source === 'eventkit' && !availableSources.eventkit) {
2057
+ return {
2058
+ content: [
2059
+ {
2060
+ type: "text",
2061
+ text: JSON.stringify({
2062
+ success: false,
2063
+ message: "EventKitはこのプラットフォームでは利用できません。EventKitはmacOSでのみ利用可能です。",
2064
+ }, null, 2),
2065
+ },
2066
+ ],
2067
+ };
2068
+ }
2069
+ if (enabled) {
2070
+ // Enable the source
2071
+ await calendarSourceManager.enableSource(source);
2072
+ // If enabling Google Calendar for the first time, check if OAuth is needed
2073
+ if (source === 'google' && googleCalendarService) {
2074
+ try {
2075
+ // Check if tokens already exist
2076
+ const { GoogleOAuthHandler } = await import('./oauth/google-oauth-handler.js');
2077
+ const oauthConfig = {
2078
+ clientId: process.env.GOOGLE_CLIENT_ID || '',
2079
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET || '',
2080
+ redirectUri: process.env.GOOGLE_REDIRECT_URI || 'http://localhost:3000/oauth/callback',
2081
+ };
2082
+ if (!oauthConfig.clientId || !oauthConfig.clientSecret) {
2083
+ return {
2084
+ content: [
2085
+ {
2086
+ type: "text",
2087
+ text: JSON.stringify({
2088
+ success: false,
2089
+ message: "Google Calendar OAuth設定が見つかりません。環境変数GOOGLE_CLIENT_IDとGOOGLE_CLIENT_SECRETを設定してください。",
2090
+ requiredEnvVars: [
2091
+ 'GOOGLE_CLIENT_ID',
2092
+ 'GOOGLE_CLIENT_SECRET',
2093
+ 'GOOGLE_REDIRECT_URI (optional, defaults to http://localhost:3000/oauth/callback)',
2094
+ ],
2095
+ }, null, 2),
2096
+ },
2097
+ ],
2098
+ };
2099
+ }
2100
+ const oauthHandler = new GoogleOAuthHandler(oauthConfig);
2101
+ // Try to get existing tokens
2102
+ const existingTokens = await oauthHandler.getTokens();
2103
+ if (!existingTokens) {
2104
+ // Need to initiate OAuth flow
2105
+ const authUrl = await oauthHandler.getAuthorizationUrl();
2106
+ // Save config before OAuth flow
2107
+ await ConfigLoader.save(config);
2108
+ return {
2109
+ content: [
2110
+ {
2111
+ type: "text",
2112
+ text: JSON.stringify({
2113
+ success: true,
2114
+ source,
2115
+ enabled: true,
2116
+ oauthRequired: true,
2117
+ authorizationUrl: authUrl,
2118
+ message: `Google Calendarを有効化しました。OAuth認証が必要です。以下のURLにアクセスして認証を完了してください: ${authUrl}`,
2119
+ instructions: [
2120
+ '1. 上記のURLをブラウザで開く',
2121
+ '2. Googleアカウントでログイン',
2122
+ '3. sage アプリケーションにカレンダーへのアクセスを許可',
2123
+ '4. リダイレクトされたURLから認証コードを取得',
2124
+ '5. 認証コードを使用してトークンを取得(別途実装予定)',
2125
+ ],
2126
+ }, null, 2),
2127
+ },
2128
+ ],
2129
+ };
2130
+ }
2131
+ }
2132
+ catch (error) {
2133
+ // OAuth check failed, but source is enabled in config
2134
+ await ConfigLoader.save(config);
2135
+ return {
2136
+ content: [
2137
+ {
2138
+ type: "text",
2139
+ text: JSON.stringify({
2140
+ success: true,
2141
+ source,
2142
+ enabled: true,
2143
+ warning: `Google Calendarを有効化しましたが、OAuth設定の確認に失敗しました: ${error instanceof Error ? error.message : "Unknown error"}`,
2144
+ message: "設定は保存されましたが、OAuth認証が必要な場合があります。",
2145
+ }, null, 2),
2146
+ },
2147
+ ],
2148
+ };
2149
+ }
2150
+ }
2151
+ // Save the updated config
2152
+ await ConfigLoader.save(config);
2153
+ return {
2154
+ content: [
2155
+ {
2156
+ type: "text",
2157
+ text: JSON.stringify({
2158
+ success: true,
2159
+ source,
2160
+ enabled: true,
2161
+ message: `${source === 'eventkit' ? 'EventKit' : 'Google Calendar'}を有効化しました。`,
2162
+ }, null, 2),
2163
+ },
2164
+ ],
2165
+ };
2166
+ }
2167
+ else {
2168
+ // Disable the source
2169
+ await calendarSourceManager.disableSource(source);
2170
+ // Save the updated config
2171
+ await ConfigLoader.save(config);
2172
+ return {
2173
+ content: [
2174
+ {
2175
+ type: "text",
2176
+ text: JSON.stringify({
2177
+ success: true,
2178
+ source,
2179
+ enabled: false,
2180
+ message: `${source === 'eventkit' ? 'EventKit' : 'Google Calendar'}を無効化しました。`,
2181
+ }, null, 2),
2182
+ },
2183
+ ],
2184
+ };
2185
+ }
2186
+ }
2187
+ catch (error) {
2188
+ return {
2189
+ content: [
2190
+ {
2191
+ type: "text",
2192
+ text: JSON.stringify({
2193
+ error: true,
2194
+ message: `カレンダーソース設定に失敗しました: ${error instanceof Error ? error.message : "Unknown error"}`,
2195
+ }, null, 2),
2196
+ },
2197
+ ],
2198
+ };
2199
+ }
2200
+ });
2201
+ /**
2202
+ * sync_calendar_sources - Sync events between EventKit and Google Calendar
2203
+ * Requirement: 8, Task 34
2204
+ */
2205
+ server.tool("sync_calendar_sources", "Synchronize calendar events between EventKit and Google Calendar. Both sources must be enabled for sync to work. Returns the number of events added, updated, and deleted.", {}, async () => {
2206
+ if (!config) {
2207
+ return {
2208
+ content: [
2209
+ {
2210
+ type: "text",
2211
+ text: JSON.stringify({
2212
+ error: true,
2213
+ message: "sageが設定されていません。check_setup_statusを実行してください。",
2214
+ }, null, 2),
2215
+ },
2216
+ ],
2217
+ };
2218
+ }
2219
+ if (!calendarSourceManager) {
2220
+ initializeServices(config);
2221
+ }
2222
+ try {
2223
+ // Check if both sources are enabled
2224
+ const enabledSources = calendarSourceManager.getEnabledSources();
2225
+ if (enabledSources.length < 2) {
2226
+ return {
2227
+ content: [
2228
+ {
2229
+ type: "text",
2230
+ text: JSON.stringify({
2231
+ success: false,
2232
+ message: "同期を実行するには、EventKitとGoogle Calendarの両方を有効化する必要があります。現在有効なソース: " +
2233
+ enabledSources.join(", "),
2234
+ enabledSources,
2235
+ }, null, 2),
2236
+ },
2237
+ ],
2238
+ };
2239
+ }
2240
+ // Execute sync
2241
+ const result = await calendarSourceManager.syncCalendars();
2242
+ if (result.success) {
2243
+ return {
2244
+ content: [
2245
+ {
2246
+ type: "text",
2247
+ text: JSON.stringify({
2248
+ success: true,
2249
+ eventsAdded: result.eventsAdded,
2250
+ eventsUpdated: result.eventsUpdated,
2251
+ eventsDeleted: result.eventsDeleted,
2252
+ conflicts: result.conflicts,
2253
+ errors: result.errors,
2254
+ message: `カレンダー同期が完了しました。追加: ${result.eventsAdded}件、更新: ${result.eventsUpdated}件、削除: ${result.eventsDeleted}件`,
2255
+ }, null, 2),
2256
+ },
2257
+ ],
2258
+ };
2259
+ }
2260
+ return {
2261
+ content: [
2262
+ {
2263
+ type: "text",
2264
+ text: JSON.stringify({
2265
+ success: false,
2266
+ eventsAdded: result.eventsAdded,
2267
+ eventsUpdated: result.eventsUpdated,
2268
+ eventsDeleted: result.eventsDeleted,
2269
+ conflicts: result.conflicts,
2270
+ errors: result.errors,
2271
+ message: "カレンダー同期中にエラーが発生しました。",
2272
+ }, null, 2),
2273
+ },
2274
+ ],
2275
+ };
2276
+ }
2277
+ catch (error) {
2278
+ return {
2279
+ content: [
2280
+ {
2281
+ type: "text",
2282
+ text: JSON.stringify({
2283
+ error: true,
2284
+ message: `カレンダー同期に失敗しました: ${error instanceof Error ? error.message : "Unknown error"}`,
2285
+ }, null, 2),
2286
+ },
2287
+ ],
2288
+ };
2289
+ }
2290
+ });
2291
+ /**
2292
+ * get_calendar_sync_status - Check sync status between calendar sources
2293
+ * Requirement: 8, Task 35
2294
+ */
2295
+ server.tool("get_calendar_sync_status", "Check the synchronization status between EventKit and Google Calendar. Returns last sync time, next sync time, and source availability.", {}, async () => {
2296
+ if (!config) {
2297
+ return {
2298
+ content: [
2299
+ {
2300
+ type: "text",
2301
+ text: JSON.stringify({
2302
+ error: true,
2303
+ message: "sageが設定されていません。check_setup_statusを実行してください。",
2304
+ }, null, 2),
2305
+ },
2306
+ ],
2307
+ };
2308
+ }
2309
+ if (!calendarSourceManager) {
2310
+ initializeServices(config);
2311
+ }
2312
+ try {
2313
+ const status = await calendarSourceManager.getSyncStatus();
2314
+ return {
2315
+ content: [
2316
+ {
2317
+ type: "text",
2318
+ text: JSON.stringify({
2319
+ isEnabled: status.isEnabled,
2320
+ lastSyncTime: status.lastSyncTime || "未実行",
2321
+ nextSyncTime: status.nextSyncTime || "N/A",
2322
+ sources: {
2323
+ eventkit: {
2324
+ available: status.sources.eventkit.available,
2325
+ lastError: status.sources.eventkit.lastError,
2326
+ },
2327
+ google: {
2328
+ available: status.sources.google.available,
2329
+ lastError: status.sources.google.lastError,
2330
+ },
2331
+ },
2332
+ message: status.isEnabled
2333
+ ? "カレンダー同期が有効です。"
2334
+ : "カレンダー同期を有効にするには、EventKitとGoogle Calendarの両方を有効化してください。",
2335
+ }, null, 2),
2336
+ },
2337
+ ],
2338
+ };
2339
+ }
2340
+ catch (error) {
2341
+ return {
2342
+ content: [
2343
+ {
2344
+ type: "text",
2345
+ text: JSON.stringify({
2346
+ error: true,
2347
+ message: `同期状態の取得に失敗しました: ${error instanceof Error ? error.message : "Unknown error"}`,
2348
+ }, null, 2),
2349
+ },
2350
+ ],
2351
+ };
2352
+ }
2353
+ });
1891
2354
  /**
1892
2355
  * get_working_cadence - Get user's working rhythm information
1893
2356
  * Requirement: 32.1-32.10