mcp-macos 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +360 -0
- package/bin/dev.cjs +10 -0
- package/bin/run.cjs +15 -0
- package/dist/config/index.d.ts +36 -0
- package/dist/config/index.js +127 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/schema.d.ts +161 -0
- package/dist/config/schema.js +45 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +75 -0
- package/dist/index.js.map +1 -0
- package/dist/server/handlers.d.ts +10 -0
- package/dist/server/handlers.js +43 -0
- package/dist/server/handlers.js.map +1 -0
- package/dist/server/promptAbstractions.d.ts +74 -0
- package/dist/server/promptAbstractions.js +150 -0
- package/dist/server/promptAbstractions.js.map +1 -0
- package/dist/server/prompts.d.ts +8 -0
- package/dist/server/prompts.js +480 -0
- package/dist/server/prompts.js.map +1 -0
- package/dist/server/server.d.ts +29 -0
- package/dist/server/server.js +52 -0
- package/dist/server/server.js.map +1 -0
- package/dist/server/transports/http/auth.d.ts +34 -0
- package/dist/server/transports/http/auth.js +148 -0
- package/dist/server/transports/http/auth.js.map +1 -0
- package/dist/server/transports/http/health.d.ts +35 -0
- package/dist/server/transports/http/health.js +93 -0
- package/dist/server/transports/http/health.js.map +1 -0
- package/dist/server/transports/http/index.d.ts +43 -0
- package/dist/server/transports/http/index.js +141 -0
- package/dist/server/transports/http/index.js.map +1 -0
- package/dist/server/transports/http/middleware.d.ts +53 -0
- package/dist/server/transports/http/middleware.js +133 -0
- package/dist/server/transports/http/middleware.js.map +1 -0
- package/dist/tools/definitions.d.ts +10 -0
- package/dist/tools/definitions.js +633 -0
- package/dist/tools/definitions.js.map +1 -0
- package/dist/tools/handlers/calendarHandlers.d.ts +11 -0
- package/dist/tools/handlers/calendarHandlers.js +123 -0
- package/dist/tools/handlers/calendarHandlers.js.map +1 -0
- package/dist/tools/handlers/contactsHandlers.d.ts +17 -0
- package/dist/tools/handlers/contactsHandlers.js +397 -0
- package/dist/tools/handlers/contactsHandlers.js.map +1 -0
- package/dist/tools/handlers/index.d.ts +11 -0
- package/dist/tools/handlers/index.js +12 -0
- package/dist/tools/handlers/index.js.map +1 -0
- package/dist/tools/handlers/listHandlers.d.ts +10 -0
- package/dist/tools/handlers/listHandlers.js +40 -0
- package/dist/tools/handlers/listHandlers.js.map +1 -0
- package/dist/tools/handlers/mailHandlers.d.ts +11 -0
- package/dist/tools/handlers/mailHandlers.js +301 -0
- package/dist/tools/handlers/mailHandlers.js.map +1 -0
- package/dist/tools/handlers/messagesHandlers.d.ts +17 -0
- package/dist/tools/handlers/messagesHandlers.js +350 -0
- package/dist/tools/handlers/messagesHandlers.js.map +1 -0
- package/dist/tools/handlers/notesHandlers.d.ts +12 -0
- package/dist/tools/handlers/notesHandlers.js +305 -0
- package/dist/tools/handlers/notesHandlers.js.map +1 -0
- package/dist/tools/handlers/reminderHandlers.d.ts +10 -0
- package/dist/tools/handlers/reminderHandlers.js +96 -0
- package/dist/tools/handlers/reminderHandlers.js.map +1 -0
- package/dist/tools/handlers/shared.d.ts +51 -0
- package/dist/tools/handlers/shared.js +107 -0
- package/dist/tools/handlers/shared.js.map +1 -0
- package/dist/tools/index.d.ts +10 -0
- package/dist/tools/index.js +125 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/types/index.d.ts +265 -0
- package/dist/types/index.js +67 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/prompts.d.ts +84 -0
- package/dist/types/prompts.js +6 -0
- package/dist/types/prompts.js.map +1 -0
- package/dist/types/repository.d.ts +102 -0
- package/dist/types/repository.js +6 -0
- package/dist/types/repository.js.map +1 -0
- package/dist/utils/binaryValidator.d.ts +52 -0
- package/dist/utils/binaryValidator.js +152 -0
- package/dist/utils/binaryValidator.js.map +1 -0
- package/dist/utils/calendarRepository.d.ts +25 -0
- package/dist/utils/calendarRepository.js +100 -0
- package/dist/utils/calendarRepository.js.map +1 -0
- package/dist/utils/cliExecutor.d.ts +28 -0
- package/dist/utils/cliExecutor.js +196 -0
- package/dist/utils/cliExecutor.js.map +1 -0
- package/dist/utils/constants.d.ts +96 -0
- package/dist/utils/constants.js +97 -0
- package/dist/utils/constants.js.map +1 -0
- package/dist/utils/contactResolver.d.ts +142 -0
- package/dist/utils/contactResolver.js +386 -0
- package/dist/utils/contactResolver.js.map +1 -0
- package/dist/utils/dateFiltering.d.ts +22 -0
- package/dist/utils/dateFiltering.js +72 -0
- package/dist/utils/dateFiltering.js.map +1 -0
- package/dist/utils/dateUtils.d.ts +20 -0
- package/dist/utils/dateUtils.js +36 -0
- package/dist/utils/dateUtils.js.map +1 -0
- package/dist/utils/errorHandling.d.ts +30 -0
- package/dist/utils/errorHandling.js +101 -0
- package/dist/utils/errorHandling.js.map +1 -0
- package/dist/utils/helpers.d.ts +35 -0
- package/dist/utils/helpers.js +59 -0
- package/dist/utils/helpers.js.map +1 -0
- package/dist/utils/jxaExecutor.d.ts +47 -0
- package/dist/utils/jxaExecutor.js +194 -0
- package/dist/utils/jxaExecutor.js.map +1 -0
- package/dist/utils/logging.d.ts +31 -0
- package/dist/utils/logging.js +98 -0
- package/dist/utils/logging.js.map +1 -0
- package/dist/utils/permissionPrompt.d.ts +16 -0
- package/dist/utils/permissionPrompt.js +42 -0
- package/dist/utils/permissionPrompt.js.map +1 -0
- package/dist/utils/preflight.d.ts +30 -0
- package/dist/utils/preflight.js +196 -0
- package/dist/utils/preflight.js.map +1 -0
- package/dist/utils/projectUtils.d.ts +11 -0
- package/dist/utils/projectUtils.js +76 -0
- package/dist/utils/projectUtils.js.map +1 -0
- package/dist/utils/reminderDateParser.d.ts +8 -0
- package/dist/utils/reminderDateParser.js +77 -0
- package/dist/utils/reminderDateParser.js.map +1 -0
- package/dist/utils/reminderRepository.d.ts +23 -0
- package/dist/utils/reminderRepository.js +91 -0
- package/dist/utils/reminderRepository.js.map +1 -0
- package/dist/utils/sqliteContactReader.d.ts +51 -0
- package/dist/utils/sqliteContactReader.js +216 -0
- package/dist/utils/sqliteContactReader.js.map +1 -0
- package/dist/utils/sqliteMailReader.d.ts +97 -0
- package/dist/utils/sqliteMailReader.js +310 -0
- package/dist/utils/sqliteMailReader.js.map +1 -0
- package/dist/utils/sqliteMessageReader.d.ts +71 -0
- package/dist/utils/sqliteMessageReader.js +400 -0
- package/dist/utils/sqliteMessageReader.js.map +1 -0
- package/dist/utils/timeHelpers.d.ts +40 -0
- package/dist/utils/timeHelpers.js +136 -0
- package/dist/utils/timeHelpers.js.map +1 -0
- package/dist/utils/timezone.d.ts +24 -0
- package/dist/utils/timezone.js +39 -0
- package/dist/utils/timezone.js.map +1 -0
- package/dist/validation/schemas.d.ts +610 -0
- package/dist/validation/schemas.js +354 -0
- package/dist/validation/schemas.js.map +1 -0
- package/package.json +97 -0
- package/scripts/build-swift.mjs +86 -0
- package/src/swift/EventKitCLI.swift +778 -0
- package/src/swift/Info.plist +38 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* utils/binaryValidator.ts
|
|
3
|
+
* Secure binary path validation and integrity checking
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Security configuration for binary validation
|
|
7
|
+
*/
|
|
8
|
+
interface BinarySecurityConfig {
|
|
9
|
+
expectedHash?: string;
|
|
10
|
+
maxFileSize: number;
|
|
11
|
+
allowedPaths: string[];
|
|
12
|
+
requireAbsolutePath: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Binary validation error
|
|
16
|
+
*/
|
|
17
|
+
export declare class BinaryValidationError extends Error {
|
|
18
|
+
code: string;
|
|
19
|
+
constructor(message: string, code: string);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Validates binary path for security
|
|
23
|
+
*/
|
|
24
|
+
export declare function validateBinaryPath(binaryPath: string, config?: Partial<BinarySecurityConfig>): void;
|
|
25
|
+
/**
|
|
26
|
+
* Calculates SHA256 hash of binary file
|
|
27
|
+
*/
|
|
28
|
+
export declare function calculateBinaryHash(binaryPath: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* Validates binary integrity using hash
|
|
31
|
+
*/
|
|
32
|
+
export declare function validateBinaryIntegrity(binaryPath: string, expectedHash: string): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Comprehensive binary security validation
|
|
35
|
+
*/
|
|
36
|
+
export declare function validateBinarySecurity(binaryPath: string, config?: Partial<BinarySecurityConfig>): {
|
|
37
|
+
isValid: boolean;
|
|
38
|
+
hash?: string;
|
|
39
|
+
errors: string[];
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Secure binary path finder with validation
|
|
43
|
+
*/
|
|
44
|
+
export declare function findSecureBinaryPath(possiblePaths: string[], config?: Partial<BinarySecurityConfig>): {
|
|
45
|
+
path: string | null;
|
|
46
|
+
validationResult?: ReturnType<typeof validateBinarySecurity>;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Environment-specific binary validation
|
|
50
|
+
*/
|
|
51
|
+
export declare function getEnvironmentBinaryConfig(): Partial<BinarySecurityConfig>;
|
|
52
|
+
export {};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* utils/binaryValidator.ts
|
|
3
|
+
* Secure binary path validation and integrity checking
|
|
4
|
+
*/
|
|
5
|
+
import crypto from 'node:crypto';
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
/**
|
|
9
|
+
* Default security configuration
|
|
10
|
+
*/
|
|
11
|
+
const DEFAULT_CONFIG = {
|
|
12
|
+
maxFileSize: 50 * 1024 * 1024, // 50MB max
|
|
13
|
+
allowedPaths: ['/dist/swift/bin/', '/src/swift/bin/', '/swift/bin/'],
|
|
14
|
+
requireAbsolutePath: true,
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Binary validation error
|
|
18
|
+
*/
|
|
19
|
+
export class BinaryValidationError extends Error {
|
|
20
|
+
constructor(message, code) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.code = code;
|
|
23
|
+
this.name = 'BinaryValidationError';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Validates binary path for security
|
|
28
|
+
*/
|
|
29
|
+
export function validateBinaryPath(binaryPath, config = {}) {
|
|
30
|
+
const fullConfig = { ...DEFAULT_CONFIG, ...config };
|
|
31
|
+
if (fullConfig.requireAbsolutePath && !path.isAbsolute(binaryPath)) {
|
|
32
|
+
throw new BinaryValidationError('Binary path must be absolute', 'INVALID_PATH');
|
|
33
|
+
}
|
|
34
|
+
const normalizedPath = path.normalize(binaryPath);
|
|
35
|
+
if (normalizedPath.includes('..')) {
|
|
36
|
+
throw new BinaryValidationError('Path traversal detected in binary path', 'PATH_TRAVERSAL');
|
|
37
|
+
}
|
|
38
|
+
if (!fullConfig.allowedPaths.some((p) => normalizedPath.includes(p))) {
|
|
39
|
+
throw new BinaryValidationError('Binary path not in allowed directories', 'FORBIDDEN_PATH');
|
|
40
|
+
}
|
|
41
|
+
if (!fs.existsSync(normalizedPath)) {
|
|
42
|
+
throw new BinaryValidationError(`Binary file not found: ${normalizedPath}`, 'FILE_NOT_FOUND');
|
|
43
|
+
}
|
|
44
|
+
const stats = fs.statSync(normalizedPath);
|
|
45
|
+
if (!stats.isFile()) {
|
|
46
|
+
throw new BinaryValidationError('Binary path does not point to a file', 'NOT_A_FILE');
|
|
47
|
+
}
|
|
48
|
+
if (stats.size > fullConfig.maxFileSize) {
|
|
49
|
+
throw new BinaryValidationError(`Binary file too large: ${stats.size} bytes`, 'FILE_TOO_LARGE');
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
fs.accessSync(normalizedPath, fs.constants.X_OK);
|
|
53
|
+
}
|
|
54
|
+
catch (_error) {
|
|
55
|
+
throw new BinaryValidationError('Binary file is not executable', 'NOT_EXECUTABLE');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Calculates SHA256 hash of binary file
|
|
60
|
+
*/
|
|
61
|
+
export function calculateBinaryHash(binaryPath) {
|
|
62
|
+
try {
|
|
63
|
+
const fileBuffer = fs.readFileSync(binaryPath);
|
|
64
|
+
return crypto.createHash('sha256').update(fileBuffer).digest('hex');
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
throw new BinaryValidationError(`Failed to calculate binary hash: ${error.message}`, 'HASH_CALCULATION_FAILED');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Validates binary integrity using hash
|
|
72
|
+
*/
|
|
73
|
+
export function validateBinaryIntegrity(binaryPath, expectedHash) {
|
|
74
|
+
try {
|
|
75
|
+
const actualHash = calculateBinaryHash(binaryPath);
|
|
76
|
+
const isValid = actualHash === expectedHash;
|
|
77
|
+
return isValid;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Comprehensive binary security validation
|
|
85
|
+
*/
|
|
86
|
+
export function validateBinarySecurity(binaryPath, config = {}) {
|
|
87
|
+
const errors = [];
|
|
88
|
+
let hash;
|
|
89
|
+
try {
|
|
90
|
+
// Path validation
|
|
91
|
+
validateBinaryPath(binaryPath, config);
|
|
92
|
+
// Calculate hash
|
|
93
|
+
hash = calculateBinaryHash(binaryPath);
|
|
94
|
+
// Integrity check if expected hash provided
|
|
95
|
+
if (config.expectedHash) {
|
|
96
|
+
const integrityValid = validateBinaryIntegrity(binaryPath, config.expectedHash);
|
|
97
|
+
if (!integrityValid) {
|
|
98
|
+
errors.push('Binary integrity check failed - hash mismatch');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
if (error instanceof BinaryValidationError) {
|
|
104
|
+
errors.push(`${error.code}: ${error.message}`);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
errors.push(`Unexpected validation error: ${error.message}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
isValid: errors.length === 0,
|
|
112
|
+
hash,
|
|
113
|
+
errors,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Secure binary path finder with validation
|
|
118
|
+
*/
|
|
119
|
+
export function findSecureBinaryPath(possiblePaths, config = {}) {
|
|
120
|
+
for (const binaryPath of possiblePaths) {
|
|
121
|
+
const validationResult = validateBinarySecurity(binaryPath, config);
|
|
122
|
+
if (validationResult.isValid) {
|
|
123
|
+
return { path: binaryPath, validationResult };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return { path: null };
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Environment-specific binary validation
|
|
130
|
+
*/
|
|
131
|
+
export function getEnvironmentBinaryConfig() {
|
|
132
|
+
if (process.env.NODE_ENV === 'test') {
|
|
133
|
+
// Relaxed validation for testing
|
|
134
|
+
return {
|
|
135
|
+
requireAbsolutePath: false,
|
|
136
|
+
maxFileSize: 100 * 1024 * 1024, // 100MB for test
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
if (process.env.NODE_ENV === 'development') {
|
|
140
|
+
// Development mode - log more details
|
|
141
|
+
return {
|
|
142
|
+
maxFileSize: 100 * 1024 * 1024, // 100MB for dev
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
// Production mode - strict validation
|
|
146
|
+
return {
|
|
147
|
+
expectedHash: process.env.SWIFT_BINARY_HASH,
|
|
148
|
+
maxFileSize: 50 * 1024 * 1024, // 50MB
|
|
149
|
+
requireAbsolutePath: true,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=binaryValidator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binaryValidator.js","sourceRoot":"","sources":["../../src/utils/binaryValidator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* calendarRepository.ts
|
|
3
|
+
* Repository pattern implementation for calendar event data access operations using EventKitCLI.
|
|
4
|
+
*/
|
|
5
|
+
import type { Calendar, CalendarEvent } from '../types/index.js';
|
|
6
|
+
import type { CreateEventData, EventJSON, UpdateEventData } from '../types/repository.js';
|
|
7
|
+
declare class CalendarRepository {
|
|
8
|
+
private readEvents;
|
|
9
|
+
private formatDate;
|
|
10
|
+
private defaultStartDate;
|
|
11
|
+
private defaultEndDate;
|
|
12
|
+
findEventById(id: string): Promise<CalendarEvent>;
|
|
13
|
+
findEvents(filters?: {
|
|
14
|
+
startDate?: string;
|
|
15
|
+
endDate?: string;
|
|
16
|
+
calendarName?: string;
|
|
17
|
+
search?: string;
|
|
18
|
+
}): Promise<CalendarEvent[]>;
|
|
19
|
+
findAllCalendars(): Promise<Calendar[]>;
|
|
20
|
+
createEvent(data: CreateEventData): Promise<EventJSON>;
|
|
21
|
+
updateEvent(data: UpdateEventData): Promise<EventJSON>;
|
|
22
|
+
deleteEvent(id: string): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
export declare const calendarRepository: CalendarRepository;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* calendarRepository.ts
|
|
3
|
+
* Repository pattern implementation for calendar event data access operations using EventKitCLI.
|
|
4
|
+
*/
|
|
5
|
+
import { executeCli } from './cliExecutor.js';
|
|
6
|
+
import { addOptionalArg, addOptionalBooleanArg, addOptionalNumberArg, nullToUndefined, } from './helpers.js';
|
|
7
|
+
class CalendarRepository {
|
|
8
|
+
async readEvents(startDate, endDate, calendarName, search) {
|
|
9
|
+
const args = ['--action', 'read-events'];
|
|
10
|
+
addOptionalArg(args, '--startDate', startDate);
|
|
11
|
+
addOptionalArg(args, '--endDate', endDate);
|
|
12
|
+
addOptionalArg(args, '--filterCalendar', calendarName);
|
|
13
|
+
addOptionalArg(args, '--search', search);
|
|
14
|
+
return executeCli(args);
|
|
15
|
+
}
|
|
16
|
+
formatDate(d) {
|
|
17
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} 00:00:00`;
|
|
18
|
+
}
|
|
19
|
+
defaultStartDate() {
|
|
20
|
+
const now = new Date();
|
|
21
|
+
return this.formatDate(new Date(now.getFullYear() - 2, now.getMonth(), now.getDate()));
|
|
22
|
+
}
|
|
23
|
+
defaultEndDate() {
|
|
24
|
+
const now = new Date();
|
|
25
|
+
return this.formatDate(new Date(now.getFullYear() + 2, now.getMonth(), now.getDate()));
|
|
26
|
+
}
|
|
27
|
+
async findEventById(id) {
|
|
28
|
+
// EventKit requires bounded date range — distantPast/distantFuture returns 0 events
|
|
29
|
+
const { events } = await this.readEvents(this.defaultStartDate(), this.defaultEndDate());
|
|
30
|
+
const event = events.find((e) => e.id === id);
|
|
31
|
+
if (!event) {
|
|
32
|
+
throw new Error(`Event with ID '${id}' not found.`);
|
|
33
|
+
}
|
|
34
|
+
return nullToUndefined(event, [
|
|
35
|
+
'notes',
|
|
36
|
+
'location',
|
|
37
|
+
'url',
|
|
38
|
+
'attendees',
|
|
39
|
+
]);
|
|
40
|
+
}
|
|
41
|
+
async findEvents(filters = {}) {
|
|
42
|
+
// EventKit requires bounded date range — distantPast/distantFuture returns 0 events
|
|
43
|
+
const startDate = filters.startDate ?? this.defaultStartDate();
|
|
44
|
+
const endDate = filters.endDate ?? this.defaultEndDate();
|
|
45
|
+
const { events } = await this.readEvents(startDate, endDate, filters.calendarName, filters.search);
|
|
46
|
+
return events.map((e) => nullToUndefined(e, ['notes', 'location', 'url', 'attendees']));
|
|
47
|
+
}
|
|
48
|
+
async findAllCalendars() {
|
|
49
|
+
return executeCli(['--action', 'read-calendars']);
|
|
50
|
+
}
|
|
51
|
+
async createEvent(data) {
|
|
52
|
+
const args = [
|
|
53
|
+
'--action',
|
|
54
|
+
'create-event',
|
|
55
|
+
'--title',
|
|
56
|
+
data.title,
|
|
57
|
+
'--startDate',
|
|
58
|
+
data.startDate,
|
|
59
|
+
'--endDate',
|
|
60
|
+
data.endDate,
|
|
61
|
+
];
|
|
62
|
+
addOptionalArg(args, '--targetCalendar', data.calendar);
|
|
63
|
+
addOptionalArg(args, '--note', data.notes);
|
|
64
|
+
addOptionalArg(args, '--location', data.location);
|
|
65
|
+
addOptionalArg(args, '--url', data.url);
|
|
66
|
+
addOptionalBooleanArg(args, '--isAllDay', data.isAllDay);
|
|
67
|
+
// Recurrence parameters
|
|
68
|
+
if (data.recurrence) {
|
|
69
|
+
addOptionalArg(args, '--recurrence', data.recurrence.frequency);
|
|
70
|
+
addOptionalNumberArg(args, '--recurrenceInterval', data.recurrence.interval);
|
|
71
|
+
addOptionalArg(args, '--recurrenceEnd', data.recurrence.endDate);
|
|
72
|
+
addOptionalNumberArg(args, '--recurrenceCount', data.recurrence.occurrenceCount);
|
|
73
|
+
}
|
|
74
|
+
return executeCli(args);
|
|
75
|
+
}
|
|
76
|
+
async updateEvent(data) {
|
|
77
|
+
const args = ['--action', 'update-event', '--id', data.id];
|
|
78
|
+
addOptionalArg(args, '--title', data.title);
|
|
79
|
+
addOptionalArg(args, '--targetCalendar', data.calendar);
|
|
80
|
+
addOptionalArg(args, '--startDate', data.startDate);
|
|
81
|
+
addOptionalArg(args, '--endDate', data.endDate);
|
|
82
|
+
addOptionalArg(args, '--note', data.notes);
|
|
83
|
+
addOptionalArg(args, '--location', data.location);
|
|
84
|
+
addOptionalArg(args, '--url', data.url);
|
|
85
|
+
addOptionalBooleanArg(args, '--isAllDay', data.isAllDay);
|
|
86
|
+
// Recurrence parameters
|
|
87
|
+
if (data.recurrence) {
|
|
88
|
+
addOptionalArg(args, '--recurrence', data.recurrence.frequency);
|
|
89
|
+
addOptionalNumberArg(args, '--recurrenceInterval', data.recurrence.interval);
|
|
90
|
+
addOptionalArg(args, '--recurrenceEnd', data.recurrence.endDate);
|
|
91
|
+
addOptionalNumberArg(args, '--recurrenceCount', data.recurrence.occurrenceCount);
|
|
92
|
+
}
|
|
93
|
+
return executeCli(args);
|
|
94
|
+
}
|
|
95
|
+
async deleteEvent(id) {
|
|
96
|
+
await executeCli(['--action', 'delete-event', '--id', id]);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
export const calendarRepository = new CalendarRepository();
|
|
100
|
+
//# sourceMappingURL=calendarRepository.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calendarRepository.js","sourceRoot":"","sources":["../../src/utils/calendarRepository.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Swift CLI execution and JSON response parsing
|
|
3
|
+
* @module utils/cliExecutor
|
|
4
|
+
* @description Executes the EventKitCLI binary for native macOS EventKit operations
|
|
5
|
+
*/
|
|
6
|
+
import { type PermissionDomain } from './permissionPrompt.js';
|
|
7
|
+
/**
|
|
8
|
+
* Custom error class for permission-related failures
|
|
9
|
+
*/
|
|
10
|
+
export declare class CliPermissionError extends Error {
|
|
11
|
+
readonly domain: PermissionDomain;
|
|
12
|
+
constructor(message: string, domain: PermissionDomain);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Executes the EventKitCLI binary for native macOS EventKit operations
|
|
16
|
+
* @template T - Expected return type from the Swift CLI
|
|
17
|
+
* @param {string[]} args - Array of arguments to pass to the CLI
|
|
18
|
+
* @returns {Promise<T>} Parsed JSON result from the CLI
|
|
19
|
+
* @throws {Error} If binary not found, validation fails, or CLI execution fails
|
|
20
|
+
* @description
|
|
21
|
+
* - Locates binary using secure path validation
|
|
22
|
+
* - Parses JSON response from Swift CLI
|
|
23
|
+
* - Proactively triggers permission prompts via AppleScript on first access
|
|
24
|
+
* - Automatically retries with AppleScript fallback on permission errors
|
|
25
|
+
* @example
|
|
26
|
+
* const result = await executeCli<Reminder[]>(['--action', 'read', '--showCompleted', 'true']);
|
|
27
|
+
*/
|
|
28
|
+
export declare function executeCli<T>(args: string[]): Promise<T>;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Swift CLI execution and JSON response parsing
|
|
3
|
+
* @module utils/cliExecutor
|
|
4
|
+
* @description Executes the EventKitCLI binary for native macOS EventKit operations
|
|
5
|
+
*/
|
|
6
|
+
import { execFile } from 'node:child_process';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { findSecureBinaryPath, getEnvironmentBinaryConfig, } from './binaryValidator.js';
|
|
9
|
+
import { FILE_SYSTEM } from './constants.js';
|
|
10
|
+
import { createCliPermissionHint } from './errorHandling.js';
|
|
11
|
+
import { hasBeenPrompted, triggerPermissionPrompt, } from './permissionPrompt.js';
|
|
12
|
+
import { findProjectRoot } from './projectUtils.js';
|
|
13
|
+
const execFilePromise = (cliPath, args) => new Promise((resolve, reject) => {
|
|
14
|
+
execFile(cliPath, args, (error, stdout, stderr) => {
|
|
15
|
+
if (error) {
|
|
16
|
+
const execError = error;
|
|
17
|
+
execError.stdout = stdout;
|
|
18
|
+
execError.stderr = stderr;
|
|
19
|
+
reject(execError);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
resolve({ stdout, stderr });
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
/**
|
|
26
|
+
* Permission error patterns from the Swift CLI
|
|
27
|
+
*/
|
|
28
|
+
const PERMISSION_ERROR_PATTERNS = {
|
|
29
|
+
reminders: [
|
|
30
|
+
/reminder permission denied/i,
|
|
31
|
+
/reminders access denied/i,
|
|
32
|
+
/not authorized.*reminders/i,
|
|
33
|
+
],
|
|
34
|
+
calendars: [
|
|
35
|
+
/calendar permission denied/i,
|
|
36
|
+
/calendar access denied/i,
|
|
37
|
+
/not authorized.*calendar/i,
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Calendar-specific action names used in Swift CLI
|
|
42
|
+
*/
|
|
43
|
+
const CALENDAR_ACTIONS = new Set([
|
|
44
|
+
'read-events',
|
|
45
|
+
'read-calendars',
|
|
46
|
+
'create-event',
|
|
47
|
+
'update-event',
|
|
48
|
+
'delete-event',
|
|
49
|
+
]);
|
|
50
|
+
/**
|
|
51
|
+
* Detects which permission domain an action requires
|
|
52
|
+
* @param args - CLI arguments array
|
|
53
|
+
* @returns The permission domain ('reminders' or 'calendars')
|
|
54
|
+
*/
|
|
55
|
+
function detectActionDomain(args) {
|
|
56
|
+
const actionIndex = args.indexOf('--action');
|
|
57
|
+
if (actionIndex !== -1 && actionIndex + 1 < args.length) {
|
|
58
|
+
const action = args[actionIndex + 1];
|
|
59
|
+
return CALENDAR_ACTIONS.has(action) ? 'calendars' : 'reminders';
|
|
60
|
+
}
|
|
61
|
+
return 'reminders'; // Default to reminders if action not found
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Detects if an error message indicates a permission issue
|
|
65
|
+
* @param message - Error message to check
|
|
66
|
+
* @returns The permission domain if detected, null otherwise
|
|
67
|
+
*/
|
|
68
|
+
function detectPermissionError(message) {
|
|
69
|
+
for (const [domain, patterns] of Object.entries(PERMISSION_ERROR_PATTERNS)) {
|
|
70
|
+
if (patterns.some((pattern) => pattern.test(message))) {
|
|
71
|
+
return domain;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Custom error class for permission-related failures
|
|
78
|
+
*/
|
|
79
|
+
export class CliPermissionError extends Error {
|
|
80
|
+
constructor(message, domain) {
|
|
81
|
+
super(message);
|
|
82
|
+
this.domain = domain;
|
|
83
|
+
this.name = 'CliPermissionError';
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Calendar action strings used in Swift CLI (different from MCP tool action names)
|
|
88
|
+
*/
|
|
89
|
+
const bufferToString = (data) => {
|
|
90
|
+
if (typeof data === 'string')
|
|
91
|
+
return data;
|
|
92
|
+
if (Buffer.isBuffer(data))
|
|
93
|
+
return data.toString('utf8');
|
|
94
|
+
return data == null ? null : String(data);
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Parses JSON output from CLI
|
|
98
|
+
*/
|
|
99
|
+
const parseCliOutput = (output) => {
|
|
100
|
+
let parsed;
|
|
101
|
+
try {
|
|
102
|
+
parsed = JSON.parse(output);
|
|
103
|
+
}
|
|
104
|
+
catch (_error) {
|
|
105
|
+
throw new Error('EventKitCLI execution failed: Invalid CLI output');
|
|
106
|
+
}
|
|
107
|
+
if (parsed.status === 'success') {
|
|
108
|
+
return parsed.result;
|
|
109
|
+
}
|
|
110
|
+
// Check for permission errors and throw specialized error with actionable hint
|
|
111
|
+
const permissionDomain = detectPermissionError(parsed.message);
|
|
112
|
+
if (permissionDomain) {
|
|
113
|
+
const hint = createCliPermissionHint(permissionDomain);
|
|
114
|
+
throw new CliPermissionError(`${parsed.message} ${hint}`, permissionDomain);
|
|
115
|
+
}
|
|
116
|
+
throw new Error(parsed.message);
|
|
117
|
+
};
|
|
118
|
+
const runCli = async (cliPath, args) => {
|
|
119
|
+
try {
|
|
120
|
+
const { stdout } = await execFilePromise(cliPath, args);
|
|
121
|
+
const normalized = bufferToString(stdout);
|
|
122
|
+
if (!normalized) {
|
|
123
|
+
throw new Error('EventKitCLI execution failed: Empty CLI output');
|
|
124
|
+
}
|
|
125
|
+
return parseCliOutput(normalized);
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
// Preserve CliPermissionError for retry logic
|
|
129
|
+
if (error instanceof CliPermissionError) {
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
const execError = error;
|
|
133
|
+
const normalized = bufferToString(execError?.stdout);
|
|
134
|
+
if (normalized) {
|
|
135
|
+
return parseCliOutput(normalized);
|
|
136
|
+
}
|
|
137
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
138
|
+
throw new Error(`EventKitCLI execution failed: ${errorMessage}`);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* Executes the EventKitCLI binary for native macOS EventKit operations
|
|
143
|
+
* @template T - Expected return type from the Swift CLI
|
|
144
|
+
* @param {string[]} args - Array of arguments to pass to the CLI
|
|
145
|
+
* @returns {Promise<T>} Parsed JSON result from the CLI
|
|
146
|
+
* @throws {Error} If binary not found, validation fails, or CLI execution fails
|
|
147
|
+
* @description
|
|
148
|
+
* - Locates binary using secure path validation
|
|
149
|
+
* - Parses JSON response from Swift CLI
|
|
150
|
+
* - Proactively triggers permission prompts via AppleScript on first access
|
|
151
|
+
* - Automatically retries with AppleScript fallback on permission errors
|
|
152
|
+
* @example
|
|
153
|
+
* const result = await executeCli<Reminder[]>(['--action', 'read', '--showCompleted', 'true']);
|
|
154
|
+
*/
|
|
155
|
+
export async function executeCli(args) {
|
|
156
|
+
const projectRoot = findProjectRoot();
|
|
157
|
+
const binaryName = FILE_SYSTEM.SWIFT_BINARY_NAME;
|
|
158
|
+
const possiblePaths = [path.join(projectRoot, 'bin', binaryName)];
|
|
159
|
+
const config = {
|
|
160
|
+
...getEnvironmentBinaryConfig(),
|
|
161
|
+
allowedPaths: [
|
|
162
|
+
'/bin/',
|
|
163
|
+
'/dist/swift/bin/',
|
|
164
|
+
'/src/swift/bin/',
|
|
165
|
+
'/swift/bin/',
|
|
166
|
+
],
|
|
167
|
+
};
|
|
168
|
+
const { path: cliPath } = findSecureBinaryPath(possiblePaths, config);
|
|
169
|
+
if (!cliPath) {
|
|
170
|
+
throw new Error(`EventKitCLI binary not found or validation failed. Searched: ${possiblePaths.join(', ')}`);
|
|
171
|
+
}
|
|
172
|
+
// Detect which permission domain this action requires
|
|
173
|
+
const domain = detectActionDomain(args);
|
|
174
|
+
// Proactively trigger AppleScript permission prompt on first access
|
|
175
|
+
// This ensures the permission dialog appears even in non-interactive contexts
|
|
176
|
+
// where the Swift binary's native EventKit permission request may be suppressed
|
|
177
|
+
if (!hasBeenPrompted(domain)) {
|
|
178
|
+
await triggerPermissionPrompt(domain);
|
|
179
|
+
}
|
|
180
|
+
let hasRetried = false;
|
|
181
|
+
while (true) {
|
|
182
|
+
try {
|
|
183
|
+
return await runCli(cliPath, args);
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
// On permission error, trigger AppleScript prompt and retry once
|
|
187
|
+
if (!hasRetried && error instanceof CliPermissionError) {
|
|
188
|
+
hasRetried = true;
|
|
189
|
+
await triggerPermissionPrompt(error.domain);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
throw error;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=cliExecutor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cliExecutor.js","sourceRoot":"","sources":["../../src/utils/cliExecutor.ts"],"names":[],"mappings":"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"}
|