@shin1ohno/sage 0.7.8 → 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.
- package/README.md +26 -8
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +30 -1
- package/dist/config/loader.js.map +1 -1
- package/dist/config/validation.d.ts +130 -0
- package/dist/config/validation.d.ts.map +1 -0
- package/dist/config/validation.js +53 -0
- package/dist/config/validation.js.map +1 -0
- package/dist/index.js +677 -214
- package/dist/index.js.map +1 -1
- package/dist/integrations/calendar-service.d.ts +6 -3
- package/dist/integrations/calendar-service.d.ts.map +1 -1
- package/dist/integrations/calendar-service.js +26 -4
- package/dist/integrations/calendar-service.js.map +1 -1
- package/dist/integrations/calendar-source-manager.d.ts +302 -0
- package/dist/integrations/calendar-source-manager.d.ts.map +1 -0
- package/dist/integrations/calendar-source-manager.js +862 -0
- package/dist/integrations/calendar-source-manager.js.map +1 -0
- package/dist/integrations/google-calendar-service.d.ts +176 -0
- package/dist/integrations/google-calendar-service.d.ts.map +1 -0
- package/dist/integrations/google-calendar-service.js +745 -0
- package/dist/integrations/google-calendar-service.js.map +1 -0
- package/dist/oauth/google-oauth-handler.d.ts +149 -0
- package/dist/oauth/google-oauth-handler.d.ts.map +1 -0
- package/dist/oauth/google-oauth-handler.js +365 -0
- package/dist/oauth/google-oauth-handler.js.map +1 -0
- package/dist/types/config.d.ts +15 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +21 -0
- package/dist/types/config.js.map +1 -1
- package/dist/types/google-calendar-types.d.ts +139 -0
- package/dist/types/google-calendar-types.d.ts.map +1 -0
- package/dist/types/google-calendar-types.js +46 -0
- package/dist/types/google-calendar-types.js.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/version.d.ts +2 -2
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +22 -4
- package/dist/version.js.map +1 -1
- 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
|
-
|
|
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 (!
|
|
684
|
+
if (!calendarSourceManager) {
|
|
656
685
|
initializeServices(config);
|
|
657
686
|
}
|
|
658
687
|
try {
|
|
659
|
-
//
|
|
660
|
-
const
|
|
661
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
696
|
-
//
|
|
697
|
-
const
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
const
|
|
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
|
-
?
|
|
705
|
-
:
|
|
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
|
-
|
|
716
|
-
method: platformInfo.recommendedMethod,
|
|
737
|
+
sources: enabledSources,
|
|
717
738
|
searchRange: { start: searchStart, end: searchEnd },
|
|
718
|
-
|
|
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
|
-
|
|
781
|
+
calendarId: z
|
|
761
782
|
.string()
|
|
762
783
|
.optional()
|
|
763
|
-
.describe("Optional: filter events by calendar name"),
|
|
764
|
-
}, async ({ startDate, endDate,
|
|
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 (!
|
|
799
|
+
if (!calendarSourceManager) {
|
|
779
800
|
initializeServices(config);
|
|
780
801
|
}
|
|
781
802
|
try {
|
|
782
|
-
//
|
|
783
|
-
const
|
|
784
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
801
|
-
const
|
|
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
|
-
|
|
813
|
-
|
|
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:
|
|
824
|
-
totalEvents:
|
|
825
|
-
message:
|
|
826
|
-
? `${
|
|
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
|
-
|
|
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
|
-
//
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
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
|
-
//
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
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:
|
|
985
|
+
success: false,
|
|
906
986
|
eventId: result.eventId,
|
|
907
987
|
eventTitle: result.eventTitle,
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
924
|
-
|
|
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
|
-
|
|
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 (!
|
|
1153
|
+
if (!calendarSourceManager) {
|
|
1074
1154
|
initializeServices(config);
|
|
1075
1155
|
}
|
|
1076
1156
|
try {
|
|
1077
|
-
//
|
|
1078
|
-
const
|
|
1079
|
-
if (
|
|
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: "
|
|
1166
|
+
message: "有効なカレンダーソースがありません。設定でEventKitまたはGoogle Calendarを有効にしてください。",
|
|
1087
1167
|
}, null, 2),
|
|
1088
1168
|
},
|
|
1089
1169
|
],
|
|
1090
1170
|
};
|
|
1091
1171
|
}
|
|
1092
|
-
//
|
|
1093
|
-
const
|
|
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
|
-
|
|
1100
|
-
alarms
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
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:
|
|
1128
|
-
|
|
1129
|
-
|
|
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
|
-
|
|
1156
|
-
.
|
|
1224
|
+
source: z
|
|
1225
|
+
.enum(['eventkit', 'google'])
|
|
1157
1226
|
.optional()
|
|
1158
|
-
.describe("Calendar
|
|
1159
|
-
}, async ({ eventId,
|
|
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 (!
|
|
1242
|
+
if (!calendarSourceManager) {
|
|
1174
1243
|
initializeServices(config);
|
|
1175
1244
|
}
|
|
1176
1245
|
try {
|
|
1177
|
-
//
|
|
1178
|
-
const
|
|
1179
|
-
if (
|
|
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
|
-
|
|
1186
|
-
message: "
|
|
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
|
-
//
|
|
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:
|
|
1220
|
-
|
|
1221
|
-
|
|
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
|
-
|
|
1248
|
-
.
|
|
1300
|
+
source: z
|
|
1301
|
+
.enum(['eventkit', 'google'])
|
|
1249
1302
|
.optional()
|
|
1250
|
-
.describe("Calendar
|
|
1251
|
-
}, async ({ eventIds,
|
|
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 (!
|
|
1318
|
+
if (!calendarSourceManager) {
|
|
1266
1319
|
initializeServices(config);
|
|
1267
1320
|
}
|
|
1268
1321
|
try {
|
|
1269
|
-
//
|
|
1270
|
-
const
|
|
1271
|
-
if (
|
|
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
|
-
|
|
1278
|
-
message: "
|
|
1330
|
+
success: false,
|
|
1331
|
+
message: "有効なカレンダーソースがありません。設定でEventKitまたはGoogle Calendarを有効にしてください。",
|
|
1279
1332
|
}, null, 2),
|
|
1280
1333
|
},
|
|
1281
1334
|
],
|
|
1282
1335
|
};
|
|
1283
1336
|
}
|
|
1284
|
-
// Delete events
|
|
1285
|
-
const
|
|
1286
|
-
|
|
1287
|
-
|
|
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:
|
|
1295
|
-
totalCount:
|
|
1296
|
-
successCount
|
|
1297
|
-
failedCount
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
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
|